Get commentlinks per-project in UI code

Remove commentlinks from GerritConfig and instead provide them in JSON
form from GET /projects/X/config. On the client side, create new
CommentLinkProcessor instances when loading a change or patch.
Fortunately, even though this requires another RPC to get the project
config, in all existing screen instances, there is already an RPC we
can parallelize it with.

In the long term we may want to enable server-side HTML rendering, but
that requires enabling this option on a variety of RPC endpoints, not
all of which are converted to the new REST API. In addition, there is
the problem of defining a stable HTML fragment style. For example, the
commit message template is currently implemented using a
not-quite-trivial template in GWT's UiBinder, so some of that
formatting and styling would need to be hoisted out into the server
side; doable, but we're not there yet.

Change-Id: Iaecbeff939c8fcbc1c6f500e0b04ce03f35e1fd3
This commit is contained in:
Dave Borowitz 2013-04-08 10:42:23 -07:00
parent 39f5688fdb
commit 2002789f41
22 changed files with 266 additions and 167 deletions

View File

@ -439,7 +439,8 @@ read access to `refs/meta/config`.
"use_contributor_agreements": false,
"use_content_merge": true,
"use_signed_off_by": false,
"require_change_id": true
"require_change_id": true,
"commentlinks": {}
}
----
@ -958,6 +959,10 @@ author or the uploader in the commit message.
If set, require a valid link:user-changeid.html[Change-Id] footer in any
commit uploaded for review. This does not apply to commits pushed
directly to a branch or tag.
|`commentlinks`|
Comment link configuration for the project. Has the same format as the
link:config-gerrit.html#_a_id_commentlink_a_section_commentlink[commentlink section]
of `gerrit.config`.
|======================================

View File

@ -20,13 +20,7 @@ import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadComma
import com.google.gerrit.reviewdb.client.AccountGeneralPreferences.DownloadScheme;
import com.google.gerrit.reviewdb.client.AuthType;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gwtexpui.safehtml.client.FindReplace;
import com.google.gwtexpui.safehtml.client.LinkFindReplace;
import com.google.gwtexpui.safehtml.client.RawFindReplace;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
public class GerritConfig implements Cloneable {
@ -53,41 +47,6 @@ public class GerritConfig implements Cloneable {
protected String anonymousCowardName;
protected int suggestFrom;
// Hack to pass FindReplace across the JSON serialization boundary, which
// doesn't work with interfaces.
public static class CommentLink {
public static CommentLink newCommentLink(String find, String link) {
return new CommentLink(find, link, true);
}
public static CommentLink newRawCommentLink(String find, String repl) {
return new CommentLink(find, repl, false);
}
protected String find;
protected String repl;
protected boolean isLink;
protected CommentLink() {
}
private CommentLink(String find, String repl, boolean isLink) {
this.find = find;
this.repl = repl;
this.isLink = isLink;
}
public FindReplace asFindReplace() {
if (isLink) {
return new LinkFindReplace(find, repl);
} else {
return new RawFindReplace(find, repl);
}
}
}
protected List<CommentLink> commentLinks;
private transient List<FindReplace> findReplaceLinks;
public String getRegisterUrl() {
return registerUrl;
}
@ -226,28 +185,6 @@ public class GerritConfig implements Cloneable {
editableAccountFields = af;
}
public List<FindReplace> getCommentLinks() {
if (findReplaceLinks == null) {
if (commentLinks != null) {
findReplaceLinks = new ArrayList<FindReplace>(commentLinks.size());
for (CommentLink cl : commentLinks) {
findReplaceLinks.add(cl.asFindReplace());
}
findReplaceLinks = Collections.unmodifiableList(findReplaceLinks);
}
}
return findReplaceLinks;
}
public List<CommentLink> getSerializableCommentLinks() {
return commentLinks;
}
public void setSerializableCommentLinks(List<CommentLink> commentLinks) {
findReplaceLinks = null;
this.commentLinks = Collections.unmodifiableList(commentLinks);
}
public boolean isDocumentationAvailable() {
return documentationAvailable;
}

View File

@ -17,6 +17,7 @@ package com.google.gerrit.common.data;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetInfo;
import com.google.gerrit.reviewdb.client.Project;
import java.util.List;
@ -24,6 +25,7 @@ public class PatchSetDetail {
protected PatchSet patchSet;
protected PatchSetInfo info;
protected List<Patch> patches;
protected Project.NameKey project;
public PatchSetDetail() {
}
@ -51,4 +53,12 @@ public class PatchSetDetail {
public void setPatches(final List<Patch> p) {
patches = p;
}
public Project.NameKey getProject() {
return project;
}
public void setProject(final Project.NameKey p) {
project = p;
}
}

View File

@ -14,6 +14,7 @@
package com.google.gerrit.client.changes;
import com.google.gerrit.client.ui.CommentLinkProcessor;
import com.google.gerrit.common.data.AccountInfoCache;
import com.google.gerrit.common.data.SubmitTypeRecord;
import com.google.gerrit.reviewdb.client.Change;
@ -37,10 +38,11 @@ public class ChangeDescriptionBlock extends Composite {
}
public void display(Change chg, Boolean starred, Boolean canEditCommitMessage,
PatchSetInfo info,
final AccountInfoCache acc, SubmitTypeRecord submitTypeRecord) {
PatchSetInfo info, AccountInfoCache acc,
SubmitTypeRecord submitTypeRecord,
CommentLinkProcessor commentLinkProcessor) {
infoBlock.display(chg, acc, submitTypeRecord);
messageBlock.display(chg.currentPatchSetId(), starred,
canEditCommitMessage, info.getMessage());
canEditCommitMessage, info.getMessage(), commentLinkProcessor);
}
}

View File

@ -18,7 +18,11 @@ import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.FormatUtil;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.account.AccountInfo;
import com.google.gerrit.client.projects.ConfigInfo;
import com.google.gerrit.client.projects.ProjectApi;
import com.google.gerrit.client.rpc.CallbackGroup;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.ui.CommentLinkProcessor;
import com.google.gerrit.client.ui.CommentPanel;
import com.google.gerrit.client.ui.ComplexDisclosurePanel;
import com.google.gerrit.client.ui.ExpandAllCommand;
@ -79,6 +83,7 @@ public class ChangeScreen extends Screen
private PatchSetsBlock patchSetsBlock;
private Panel comments;
private CommentLinkProcessor commentLinkProcessor;
private KeyCommandSet keysNavigation;
private KeyCommandSet keysAction;
@ -260,10 +265,25 @@ public class ChangeScreen extends Screen
@Override
public void onValueChange(final ValueChangeEvent<ChangeDetail> event) {
if (isAttached()) {
// Until this screen is fully migrated to the new API, this call must be
// sequential, because we can't start an async get at the source of every
// call that might trigger a value change.
ChangeApi.detail(event.getValue().getChange().getId().get(),
// Until this screen is fully migrated to the new API, these calls must
// happen sequentially after the ChangeDetail lookup, because we can't
// start an async get at the source of every call that might trigger a
// value change.
CallbackGroup cbs = new CallbackGroup();
ProjectApi.config(event.getValue().getChange().getProject())
.get(cbs.add(new GerritCallback<ConfigInfo>() {
@Override
public void onSuccess(ConfigInfo result) {
commentLinkProcessor =
new CommentLinkProcessor(result.commentlinks());
}
@Override
public void onFailure(Throwable caught) {
// Handled by last callback's onFailure.
}
}));
ChangeApi.detail(event.getValue().getChange().getId().get(), cbs.add(
new GerritCallback<com.google.gerrit.client.changes.ChangeInfo>() {
@Override
public void onSuccess(
@ -271,7 +291,7 @@ public class ChangeScreen extends Screen
changeInfo = result;
display(event.getValue());
}
});
}));
}
}
@ -292,7 +312,8 @@ public class ChangeScreen extends Screen
detail.isStarred(),
detail.canEditCommitMessage(),
detail.getCurrentPatchSetDetail().getInfo(),
detail.getAccounts(), detail.getSubmitTypeRecord());
detail.getAccounts(), detail.getSubmitTypeRecord(),
commentLinkProcessor);
dependsOn.display(detail.getDependsOn());
neededBy.display(detail.getNeededBy());
approvals.display(changeInfo);
@ -411,8 +432,8 @@ public class ChangeScreen extends Screen
isRecent = msg.getWrittenOn().after(aged);
}
final CommentPanel cp =
new CommentPanel(author, msg.getWrittenOn(), msg.getMessage());
final CommentPanel cp = new CommentPanel(author, msg.getWrittenOn(),
msg.getMessage(), commentLinkProcessor);
cp.setRecent(isRecent);
cp.addStyleName(Gerrit.RESOURCES.css().commentPanelBorder());
if (i == msgList.size() - 1) {

View File

@ -68,8 +68,9 @@ public class CommitMessageBlock extends Composite {
initWidget(uiBinder.createAndBindUi(this));
}
public void display(final String commitMessage) {
display(null, null, false, commitMessage);
public void display(String commitMessage,
CommentLinkProcessor commentLinkProcessor) {
display(null, null, false, commitMessage, commentLinkProcessor);
}
private abstract class CommitMessageEditDialog extends CommentedActionDialog<ChangeDetail> {
@ -103,7 +104,8 @@ public class CommitMessageBlock extends Composite {
}
public void display(final PatchSet.Id patchSetId,
Boolean starred, Boolean canEditCommitMessage, final String commitMessage) {
Boolean starred, Boolean canEditCommitMessage, final String commitMessage,
CommentLinkProcessor commentLinkProcessor) {
starPanel.clear();
if (patchSetId != null && starred != null && Gerrit.isSignedIn()) {
Change.Id changeId = patchSetId.getParentKey();
@ -170,7 +172,7 @@ public class CommitMessageBlock extends Composite {
// Linkify commit summary
SafeHtml commitSummaryLinkified = new SafeHtmlBuilder().append(commitSummary);
commitSummaryLinkified = commitSummaryLinkified.linkify();
commitSummaryLinkified = CommentLinkProcessor.apply(commitSummaryLinkified);
commitSummaryLinkified = commentLinkProcessor.apply(commitSummaryLinkified);
commitSummaryPre.setInnerHTML(commitSummaryLinkified.asString());
// Hide commit body if there is no body
@ -180,7 +182,7 @@ public class CommitMessageBlock extends Composite {
// Linkify commit body
SafeHtml commitBodyLinkified = new SafeHtmlBuilder().append(commitBody);
commitBodyLinkified = commitBodyLinkified.linkify();
commitBodyLinkified = CommentLinkProcessor.apply(commitBodyLinkified);
commitBodyLinkified = commentLinkProcessor.apply(commitBodyLinkified);
commitBodyLinkified = commitBodyLinkified.replaceAll("\n\n", "<p></p>");
commitBodyLinkified = commitBodyLinkified.replaceAll("\n", "<br />");
commitBodyPre.setInnerHTML(commitBodyLinkified.asString());

View File

@ -20,6 +20,8 @@ import com.google.gerrit.client.changes.ChangeInfo.LabelInfo;
import com.google.gerrit.client.patches.AbstractPatchContentTable;
import com.google.gerrit.client.patches.CommentEditorContainer;
import com.google.gerrit.client.patches.CommentEditorPanel;
import com.google.gerrit.client.projects.ConfigInfo;
import com.google.gerrit.client.projects.ProjectApi;
import com.google.gerrit.client.rpc.CallbackGroup;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.Natives;
@ -78,6 +80,7 @@ public class PublishCommentScreen extends AccountScreen implements
private boolean saveStateOnUnload = true;
private List<CommentEditorPanel> commentEditors;
private ChangeInfo change;
private CommentLinkProcessor commentLinkProcessor;
public PublishCommentScreen(final PatchSet.Id psi) {
patchSetId = psi;
@ -148,8 +151,8 @@ public class PublishCommentScreen extends AccountScreen implements
super.onLoad();
CallbackGroup cbs = new CallbackGroup();
ChangeApi.revision(patchSetId).view("review").get(cbs.add(
new AsyncCallback<ChangeInfo>() {
ChangeApi.revision(patchSetId).view("review")
.get(cbs.add(new AsyncCallback<ChangeInfo>() {
@Override
public void onSuccess(ChangeInfo result) {
result.init();
@ -166,7 +169,7 @@ public class PublishCommentScreen extends AccountScreen implements
@Override
protected void preDisplay(final PatchSetPublishDetail result) {
send.setEnabled(true);
display(result);
PublishCommentScreen.this.preDisplay(result, this);
}
@Override
@ -176,6 +179,24 @@ public class PublishCommentScreen extends AccountScreen implements
}));
}
private void preDisplay(final PatchSetPublishDetail pubDetail,
final ScreenLoadCallback<PatchSetPublishDetail> origCb) {
ProjectApi.config(pubDetail.getChange().getProject())
.get(new AsyncCallback<ConfigInfo>() {
@Override
public void onSuccess(ConfigInfo result) {
commentLinkProcessor =
new CommentLinkProcessor(result.commentlinks());
display(pubDetail);
}
@Override
public void onFailure(Throwable caught) {
origCb.onFailure(caught);
}
});
}
@Override
protected void onUnload() {
super.onUnload();
@ -281,7 +302,7 @@ public class PublishCommentScreen extends AccountScreen implements
for (String value : values) {
ValueRadioButton b = new ValueRadioButton(label, value);
SafeHtml buf = new SafeHtmlBuilder().append(b.format());
buf = CommentLinkProcessor.apply(buf);
buf = commentLinkProcessor.apply(buf);
SafeHtml.set(b, buf);
if (lastState != null && patchSetId.equals(lastState.patchSetId)
@ -301,7 +322,7 @@ public class PublishCommentScreen extends AccountScreen implements
setPageTitle(Util.M.publishComments(r.getChange().getKey().abbreviate(),
patchSetId.get()));
descBlock.display(r.getChange(), null, false, r.getPatchSetInfo(), r.getAccounts(),
r.getSubmitTypeRecord());
r.getSubmitTypeRecord(), commentLinkProcessor);
if (r.getChange().getStatus().isOpen()) {
initApprovals(approvalPanel);
@ -337,7 +358,8 @@ public class PublishCommentScreen extends AccountScreen implements
priorFile = fn;
}
final CommentEditorPanel editor = new CommentEditorPanel(c);
final CommentEditorPanel editor =
new CommentEditorPanel(c, commentLinkProcessor);
if (c.getLine() == AbstractPatchContentTable.R_HEAD) {
editor.setAuthorNameText(Util.C.fileCommentHeader());
} else {

View File

@ -21,6 +21,7 @@ import com.google.gerrit.client.account.AccountInfo;
import com.google.gerrit.client.changes.PatchTable;
import com.google.gerrit.client.changes.Util;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.ui.CommentLinkProcessor;
import com.google.gerrit.client.ui.CommentPanel;
import com.google.gerrit.client.ui.NavigationTable;
import com.google.gerrit.client.ui.NeedsSignInKeyCommand;
@ -86,6 +87,7 @@ public abstract class AbstractPatchContentTable extends NavigationTable<Object>
private HandlerRegistration regComment;
private final KeyCommandSet keysOpenByEnter;
private HandlerRegistration regOpenByEnter;
private CommentLinkProcessor commentLinkProcessor;
boolean isDisplayBinary;
protected AbstractPatchContentTable() {
@ -241,6 +243,10 @@ public abstract class AbstractPatchContentTable extends NavigationTable<Object>
render(s, d);
}
void setCommentLinkProcessor(CommentLinkProcessor commentLinkProcessor) {
this.commentLinkProcessor = commentLinkProcessor;
}
protected boolean hasDifferences(PatchScript script) {
return hasEdits(script) || hasMeta(script);
}
@ -553,7 +559,8 @@ public abstract class AbstractPatchContentTable extends NavigationTable<Object>
return null;
}
final CommentEditorPanel ed = new CommentEditorPanel(newComment);
final CommentEditorPanel ed =
new CommentEditorPanel(newComment, commentLinkProcessor);
ed.addFocusHandler(this);
ed.addBlurHandler(this);
boolean isCommentRow = false;
@ -690,7 +697,8 @@ public abstract class AbstractPatchContentTable extends NavigationTable<Object>
protected void bindComment(final int row, final int col,
final PatchLineComment line, final boolean isLast, boolean expandComment) {
if (line.getStatus() == PatchLineComment.Status.DRAFT) {
final CommentEditorPanel plc = new CommentEditorPanel(line);
final CommentEditorPanel plc =
new CommentEditorPanel(line, commentLinkProcessor);
plc.addFocusHandler(this);
plc.addBlurHandler(this);
table.setWidget(row, col, plc);
@ -864,7 +872,7 @@ public abstract class AbstractPatchContentTable extends NavigationTable<Object>
final Button replyDone;
PublishedCommentPanel(final AccountInfo author, final PatchLineComment c) {
super(author, c.getWrittenOn(), c.getMessage());
super(author, c.getWrittenOn(), c.getMessage(), commentLinkProcessor);
this.comment = c;
reply = new Button(PatchUtil.C.buttonReply());

View File

@ -16,6 +16,7 @@ package com.google.gerrit.client.patches;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.ui.CommentLinkProcessor;
import com.google.gerrit.client.ui.CommentPanel;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gwt.event.dom.client.ClickEvent;
@ -25,10 +26,10 @@ import com.google.gwt.event.dom.client.DoubleClickHandler;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.user.client.Timer;
import com.google.gwtjsonrpc.common.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwtexpui.globalkey.client.NpTextArea;
import com.google.gwtjsonrpc.common.AsyncCallback;
import com.google.gwtjsonrpc.common.VoidResult;
import java.sql.Timestamp;
@ -58,7 +59,9 @@ public class CommentEditorPanel extends CommentPanel implements ClickHandler,
private final Button discard;
private final Timer expandTimer;
public CommentEditorPanel(final PatchLineComment plc) {
public CommentEditorPanel(final PatchLineComment plc,
final CommentLinkProcessor commentLinkProcessor) {
super(commentLinkProcessor);
comment = plc;
addStyleName(Gerrit.RESOURCES.css().commentEditorPanel());

View File

@ -21,8 +21,12 @@ import com.google.gerrit.client.RpcStatus;
import com.google.gerrit.client.changes.CommitMessageBlock;
import com.google.gerrit.client.changes.PatchTable;
import com.google.gerrit.client.changes.Util;
import com.google.gerrit.client.projects.ConfigInfo;
import com.google.gerrit.client.projects.ProjectApi;
import com.google.gerrit.client.rpc.CallbackGroup;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.ScreenLoadCallback;
import com.google.gerrit.client.ui.CommentLinkProcessor;
import com.google.gerrit.client.ui.ListenableAccountDiffPreference;
import com.google.gerrit.client.ui.Screen;
import com.google.gerrit.common.data.PatchScript;
@ -38,6 +42,7 @@ import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwtexpui.globalkey.client.GlobalKey;
import com.google.gwtexpui.globalkey.client.KeyCommand;
@ -97,6 +102,7 @@ public abstract class PatchScreen extends Screen implements
protected PatchSet.Id idSideB;
protected PatchScriptSettingsPanel settingsPanel;
protected TopView topView;
protected CommentLinkProcessor commentLinkProcessor;
private ReviewedPanels reviewedPanels;
private HistoryTable historyTable;
@ -365,8 +371,9 @@ public abstract class PatchScreen extends Screen implements
if (isFirst && fileList != null) {
fileList.movePointerTo(patchKey);
}
PatchUtil.DETAIL_SVC.patchScript(patchKey, idSideA, idSideB, //
settingsPanel.getValue(), new ScreenLoadCallback<PatchScript>(this) {
com.google.gwtjsonrpc.common.AsyncCallback<PatchScript> pscb =
new ScreenLoadCallback<PatchScript>(this) {
@Override
protected void preDisplay(final PatchScript result) {
if (rpcSequence == rpcseq) {
@ -381,7 +388,29 @@ public abstract class PatchScreen extends Screen implements
super.onFailure(caught);
}
}
});
};
if (commentLinkProcessor == null) {
// Fetch config in parallel if we haven't previously.
CallbackGroup cb = new CallbackGroup();
ProjectApi.config(patchSetDetail.getProject())
.get(cb.add(new AsyncCallback<ConfigInfo>() {
@Override
public void onSuccess(ConfigInfo result) {
commentLinkProcessor =
new CommentLinkProcessor(result.commentlinks());
contentTable.setCommentLinkProcessor(commentLinkProcessor);
}
@Override
public void onFailure(Throwable caught) {
// Handled by ScreenLoadCallback.onFailure.
}
}));
pscb = cb.addGwtjsonrpc(pscb);
}
PatchUtil.DETAIL_SVC.patchScript(patchKey, idSideA, idSideB, //
settingsPanel.getValue(), pscb);
}
private void onResult(final PatchScript script, final boolean isFirst) {
@ -397,7 +426,8 @@ public abstract class PatchScreen extends Screen implements
if (idSideB.equals(patchSetDetail.getPatchSet().getId())) {
commitMessageBlock.setVisible(true);
commitMessageBlock.display(patchSetDetail.getInfo().getMessage());
commitMessageBlock.display(patchSetDetail.getInfo().getMessage(),
commentLinkProcessor);
} else {
commitMessageBlock.setVisible(false);
Util.DETAIL_SVC.patchSetDetail(idSideB,
@ -405,7 +435,8 @@ public abstract class PatchScreen extends Screen implements
@Override
public void onSuccess(PatchSetDetail result) {
commitMessageBlock.setVisible(true);
commitMessageBlock.display(result.getInfo().getMessage());
commitMessageBlock.display(result.getInfo().getMessage(),
commentLinkProcessor);
}
});
}
@ -432,6 +463,7 @@ public abstract class PatchScreen extends Screen implements
contentTable.removeFromParent();
contentTable = new UnifiedDiffTable();
contentTable.fileList = fileList;
contentTable.setCommentLinkProcessor(commentLinkProcessor);
contentPanel.add(contentTable);
setToken(Dispatcher.toPatchUnified(idSideA, patchKey));
}

View File

@ -20,6 +20,7 @@ import static com.google.gerrit.client.patches.PatchLine.Type.INSERT;
import static com.google.gerrit.client.patches.PatchLine.Type.REPLACE;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.ui.CommentLinkProcessor;
import com.google.gerrit.common.data.CommentDetail;
import com.google.gerrit.common.data.PatchScript;
import com.google.gerrit.common.data.PatchScript.FileMode;
@ -39,6 +40,7 @@ import com.google.gwt.user.client.ui.InlineLabel;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwtexpui.safehtml.client.SafeHtml;
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
import org.eclipse.jgit.diff.Edit;
import java.util.ArrayList;

View File

@ -19,6 +19,7 @@ import static com.google.gerrit.client.patches.PatchLine.Type.DELETE;
import static com.google.gerrit.client.patches.PatchLine.Type.INSERT;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.ui.CommentLinkProcessor;
import com.google.gerrit.common.data.CommentDetail;
import com.google.gerrit.common.data.PatchScript;
import com.google.gerrit.common.data.PatchScript.DisplayMethod;
@ -32,8 +33,8 @@ import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwtexpui.safehtml.client.SafeHtml;
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
import com.google.gwtorm.client.KeyUtil;

View File

@ -0,0 +1,75 @@
// Copyright (C) 2013 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.client.projects;
import com.google.gerrit.client.rpc.NativeMap;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwtexpui.safehtml.client.FindReplace;
import com.google.gwtexpui.safehtml.client.LinkFindReplace;
import com.google.gwtexpui.safehtml.client.RawFindReplace;
import java.util.ArrayList;
import java.util.List;
public class ConfigInfo extends JavaScriptObject {
public final native JavaScriptObject has_require_change_id()
/*-{ return this.hasOwnProperty('require_change_id'); }-*/;
public final native boolean require_change_id()
/*-{ return this.require_change_id; }-*/;
public final native JavaScriptObject has_use_content_merge()
/*-{ return this.hasOwnProperty('use_content_merge'); }-*/;
public final native boolean use_content_merge()
/*-{ return this.use_content_merge; }-*/;
public final native JavaScriptObject has_use_contributor_agreements()
/*-{ return this.hasOwnProperty('use_contributor_agreements'); }-*/;
public final native boolean use_contributor_agreements()
/*-{ return this.use_contributor_agreements; }-*/;
public final native JavaScriptObject has_use_signed_off_by()
/*-{ return this.hasOwnProperty('use_signed_off_by'); }-*/;
public final native boolean use_signed_off_by()
/*-{ return this.use_signed_off_by; }-*/;
private final native NativeMap<CommentLinkInfo> commentlinks0()
/*-{ return this.commentlinks; }-*/;
public final List<FindReplace> commentlinks() {
JsArray<CommentLinkInfo> cls = commentlinks0().values();
List<FindReplace> commentLinks = new ArrayList<FindReplace>(cls.length());
for (int i = 0; i < cls.length(); i++) {
CommentLinkInfo cl = cls.get(i);
if (cl.link() != null) {
commentLinks.add(new LinkFindReplace(cl.match(), cl.link()));
} else {
commentLinks.add(new RawFindReplace(cl.match(), cl.html()));
}
}
return commentLinks;
}
protected ConfigInfo() {
}
static class CommentLinkInfo extends JavaScriptObject {
final native String match() /*-{ return this.match; }-*/;
final native String link() /*-{ return this.link; }-*/;
final native String html() /*-{ return this.html; }-*/;
protected CommentLinkInfo() {
}
}
}

View File

@ -15,6 +15,7 @@ package com.google.gerrit.client.projects;
import com.google.gerrit.client.VoidResult;
import com.google.gerrit.client.rpc.RestApi;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.user.client.rpc.AsyncCallback;
@ -32,6 +33,10 @@ public class ProjectApi {
.put(input, asyncCallback);
}
public static RestApi config(Project.NameKey name) {
return new RestApi("/projects/").id(name.get()).view("config");
}
private static class ProjectInput extends JavaScriptObject {
static ProjectInput create() {
return (ProjectInput) createObject();

View File

@ -15,7 +15,6 @@
package com.google.gerrit.client.ui;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.common.data.GerritConfig.CommentLink;
import com.google.gwtexpui.safehtml.client.FindReplace;
import com.google.gwtexpui.safehtml.client.SafeHtml;
import com.google.gwtjsonrpc.common.AsyncCallback;
@ -26,27 +25,26 @@ import java.util.Collections;
import java.util.List;
public class CommentLinkProcessor {
public static SafeHtml apply(SafeHtml buf) {
try {
return buf.replaceAll(Gerrit.getConfig().getCommentLinks());
private List<FindReplace> commentLinks;
public CommentLinkProcessor(List<FindReplace> commentLinks) {
this.commentLinks = commentLinks;
}
public SafeHtml apply(SafeHtml buf) {
try {
return buf.replaceAll(commentLinks);
} catch (RuntimeException err) {
// One or more of the patterns isn't valid on this browser.
// Try to filter the list down and remove the invalid ones.
List<FindReplace> all = Gerrit.getConfig().getCommentLinks();
List<CommentLink> ser = Gerrit.getConfig().getSerializableCommentLinks();
List<FindReplace> safe = new ArrayList<FindReplace>(all.size());
List<CommentLink> safeSer = new ArrayList<CommentLink>(safe.size());
List<FindReplace> safe = new ArrayList<FindReplace>(commentLinks.size());
List<PatternError> bad = new ArrayList<PatternError>();
for (int i = 0; i < all.size(); i++) {
FindReplace r = all.get(i);
for (FindReplace r : commentLinks) {
try {
buf.replaceAll(Collections.singletonList(r));
safe.add(r);
safeSer.add(ser.get(i));
} catch (RuntimeException why) {
bad.add(new PatternError(r, why.getMessage()));
}
@ -75,13 +73,13 @@ public class CommentLinkProcessor {
}
try {
Gerrit.getConfig().setSerializableCommentLinks(safeSer);
commentLinks = safe;
return buf.replaceAll(safe);
} catch (RuntimeException err2) {
// To heck with it. The patterns passed individually above but
// failed as a group? Just render without.
//
Gerrit.getConfig().setSerializableCommentLinks(null);
commentLinks = null;
return buf;
}
}
@ -96,7 +94,4 @@ public class CommentLinkProcessor {
errorMessage = w;
}
}
private CommentLinkProcessor() {
}
}

View File

@ -53,11 +53,13 @@ public class CommentPanel extends Composite implements HasDoubleClickHandlers,
private final InlineLabel messageSummary;
private final FlowPanel content;
private final DoubleClickHTML messageText;
private CommentLinkProcessor commentLinkProcessor;
private FlowPanel buttons;
private boolean recent;
public CommentPanel(final AccountInfo author, final Date when, String message) {
this();
public CommentPanel(final AccountInfo author, final Date when, String message,
CommentLinkProcessor commentLinkProcessor) {
this(commentLinkProcessor);
setMessageText(message);
setAuthorNameText(FormatUtil.name(author));
@ -68,7 +70,8 @@ public class CommentPanel extends Composite implements HasDoubleClickHandlers,
fmt.getElement(0, 2).setTitle(FormatUtil.mediumFormat(when));
}
protected CommentPanel() {
protected CommentPanel(CommentLinkProcessor commentLinkProcessor) {
this.commentLinkProcessor = commentLinkProcessor;
final FlowPanel body = new FlowPanel();
initWidget(body);
setStyleName(Gerrit.RESOURCES.css().commentPanel());
@ -118,7 +121,7 @@ public class CommentPanel extends Composite implements HasDoubleClickHandlers,
messageSummary.setText(summarize(message));
SafeHtml buf = new SafeHtmlBuilder().append(message).wikify();
buf = CommentLinkProcessor.apply(buf);
buf = commentLinkProcessor.apply(buf);
SafeHtml.set(messageText, buf);
}

View File

@ -14,10 +14,7 @@
package com.google.gerrit.httpd;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.gerrit.common.data.GerritConfig;
import com.google.gerrit.common.data.GerritConfig.CommentLink;
import com.google.gerrit.common.data.GitwebConfig;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.Realm;
@ -37,10 +34,7 @@ import org.eclipse.jgit.lib.Config;
import java.net.MalformedURLException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.servlet.ServletContext;
@ -149,45 +143,9 @@ class GerritConfigProvider implements Provider<GerritConfig> {
config.setSshdAddress(sshInfo.getHostKeys().get(0).getHost());
}
config.setSerializableCommentLinks(buildCommentLinks(cfg));
return config;
}
private static List<CommentLink> buildCommentLinks(Config cfg) {
Set<String> sections = cfg.getSubsections("commentlink");
List<CommentLink> links = Lists.newArrayListWithCapacity(sections.size());
for (String name : cfg.getSubsections("commentlink")) {
String match = cfg.getString("commentlink", name, "match");
// Unfortunately this validation isn't entirely complete. Clients
// can have exceptions trying to evaluate the pattern if they don't
// support a token used, even if the server does support the token.
//
// At the minimum, we can trap problems related to unmatched groups.
try {
Pattern.compile(match);
} catch (PatternSyntaxException e) {
throw new ProvisionException("Invalid pattern \"" + match
+ "\" in commentlink." + name + ".match: " + e.getMessage());
}
String link = cfg.getString("commentlink", name, "link");
int hasLink = Strings.isNullOrEmpty(link) ? 0 : 1;
String html = cfg.getString("commentlink", name, "html");
int hasHtml = Strings.isNullOrEmpty(html) ? 0 : 1;
if (hasLink + hasHtml != 1) {
throw new ProvisionException(
"commentlink." + name + " must have either link or html");
} else if (hasLink == 1) {
links.add(CommentLink.newCommentLink(match, link));
} else if (hasHtml == 1) {
links.add(CommentLink.newRawCommentLink(match, html));
}
}
return links;
}
@Override
public GerritConfig get() {
try {

View File

@ -106,7 +106,7 @@ class PatchSetDetailFactory extends Handler<PatchSetDetail> {
throw new NoSuchEntityException();
}
}
projectKey = control.getProject().getNameKey();
final PatchList list;
try {
@ -114,8 +114,6 @@ class PatchSetDetailFactory extends Handler<PatchSetDetail> {
oldId = toObjectId(psIdBase);
newId = toObjectId(psIdNew);
projectKey = control.getProject().getNameKey();
list = listFor(keyFor(diffPrefs.getIgnoreWhitespace()));
} else { // OK, means use base to compare
list = patchListCache.get(control.getChange(), patchSet);
@ -139,6 +137,7 @@ class PatchSetDetailFactory extends Handler<PatchSetDetail> {
detail = new PatchSetDetail();
detail.setPatchSet(patchSet);
detail.setProject(projectKey);
detail.setInfo(infoFactory.get(db, psIdNew));
detail.setPatches(patches);

View File

@ -14,9 +14,12 @@
package com.google.gerrit.server.project;
import com.google.common.collect.Maps;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.git.GitRepositoryManager;
import java.util.Map;
public class GetConfig implements RestReadView<ProjectResource> {
public static class ConfigInfo {
public final String kind = "gerritcodereview#project_config";
@ -25,6 +28,8 @@ public class GetConfig implements RestReadView<ProjectResource> {
public Boolean useContentMerge;
public Boolean useSignedOffBy;
public Boolean requireChangeId;
public Map<String, CommentLinkInfo> commentlinks;
}
@Override
@ -39,6 +44,13 @@ public class GetConfig implements RestReadView<ProjectResource> {
result.useSignedOffBy = project.isUseSignedOffBy();
result.requireChangeId = project.isRequireChangeID();
}
// commentlinks are visible to anyone, as they are used for linkification
// on the client side.
result.commentlinks = Maps.newLinkedHashMap();
for (CommentLinkInfo cl : project.getCommentLinks()) {
result.commentlinks.put(cl.name, cl);
}
return result;
}
}

View File

@ -69,6 +69,7 @@ public class ProjectState {
private final PrologEnvironment.Factory envFactory;
private final GitRepositoryManager gitMgr;
private final RulesCache rulesCache;
private final List<CommentLinkInfo> commentLinks;
private final ProjectConfig config;
private final Set<AccountGroup.UUID> localOwners;
@ -93,6 +94,7 @@ public class ProjectState {
final PrologEnvironment.Factory envFactory,
final GitRepositoryManager gitMgr,
final RulesCache rulesCache,
final List<CommentLinkInfo> commentLinks,
@Assisted final ProjectConfig config) {
this.projectCache = projectCache;
this.isAllProjects = config.getProject().getNameKey().equals(allProjectsName);
@ -101,6 +103,7 @@ public class ProjectState {
this.envFactory = envFactory;
this.gitMgr = gitMgr;
this.rulesCache = rulesCache;
this.commentLinks = commentLinks;
this.config = config;
this.capabilities = isAllProjects
? new CapabilityCollection(config.getAccessSection(AccessSection.GLOBAL_CAPABILITIES))
@ -363,6 +366,10 @@ public class ProjectState {
return new LabelTypes(Collections.unmodifiableList(all));
}
public List<CommentLinkInfo> getCommentLinks() {
return commentLinks;
}
private boolean getInheritableBoolean(Function<Project, InheritableBoolean> func) {
for (ProjectState s : tree()) {
switch (func.apply(s.getProject())) {

View File

@ -80,8 +80,8 @@ public class GerritCommonTest extends PrologTestCase {
for (LabelType label : labelTypes.getLabelTypes()) {
config.getLabelSections().put(label.getName(), label);
}
allProjects = new ProjectState(this, allProjectsName, null,
null, null, null, config);
allProjects = new ProjectState(this, allProjectsName, null, null, null,
null, null, config);
}
@Override

View File

@ -541,10 +541,10 @@ public class RefControlTest extends TestCase {
RulesCache rulesCache = null;
all.put(local.getProject().getNameKey(), new ProjectState(
projectCache, allProjectsName, projectControlFactory,
envFactory, mgr, rulesCache, local));
envFactory, mgr, rulesCache, null, local));
all.put(parent.getProject().getNameKey(), new ProjectState(
projectCache, allProjectsName, projectControlFactory,
envFactory, mgr, rulesCache, parent));
envFactory, mgr, rulesCache, null, parent));
return all.get(local.getProject().getNameKey());
}