Merge "Document adding and removal of included groups via REST"
diff --git a/Documentation/database-setup.txt b/Documentation/database-setup.txt
index 2652952..0465d62 100644
--- a/Documentation/database-setup.txt
+++ b/Documentation/database-setup.txt
@@ -33,7 +33,7 @@
full rights on the newly created database:
----
- $ createuser --username=postgres -A -D -P -E gerrit2
+ $ createuser --username=postgres -S -R -D -P -E gerrit2
$ createdb --username=postgres -E UTF-8 -O gerrit2 reviewdb
----
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 3695d6a..6634915 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -140,6 +140,10 @@
* `LABELS`: a summary of each label required for submit, and
approvers that have granted (or rejected) with that label.
+* `DETAILED_LABELS`: detailed label information, including numeric
+ values of all existing approvals, recognized label values, and
+ values permitted to be set by the current user.
+
* `CURRENT_REVISION`: describe the current revision (patch set)
of the change, including the commit SHA-1 and URLs to fetch from.
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/changes/ListChangesOption.java b/gerrit-common/src/main/java/com/google/gerrit/common/changes/ListChangesOption.java
index 5072b76..e3f9236 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/changes/ListChangesOption.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/changes/ListChangesOption.java
@@ -19,6 +19,7 @@
/** Output options available when using {@code /changes/} RPCs. */
public enum ListChangesOption {
LABELS(0),
+ DETAILED_LABELS(8),
/** Return information on the current patch set of the change. */
CURRENT_REVISION(1),
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchSetPublishDetail.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchSetPublishDetail.java
index cf4b28b..7637f75 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchSetPublishDetail.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchSetPublishDetail.java
@@ -14,7 +14,6 @@
package com.google.gerrit.common.data;
-import com.google.gerrit.reviewdb.client.ApprovalCategory;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
@@ -123,9 +122,9 @@
return null;
}
- public PatchSetApproval getChangeApproval(ApprovalCategory.Id id) {
+ public PatchSetApproval getChangeApproval(String label) {
for (PatchSetApproval a : given) {
- if (a.getCategoryId().equals(id)) {
+ if (a.getLabel() != null && a.getLabel().equals(label)) {
return a;
}
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java
index 5341d9f..115c0c1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java
@@ -16,6 +16,7 @@
import com.google.gerrit.client.rpc.NativeString;
import com.google.gerrit.client.rpc.RestApi;
+import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.user.client.rpc.AsyncCallback;
@@ -60,6 +61,10 @@
}
}
+ public static RestApi revision(PatchSet.Id id) {
+ return change(id.getParentKey().get()).view("revisions").id(id.get());
+ }
+
/** Submit a specific revision of a change. */
public static void submit(int id, String commit, AsyncCallback<SubmitInfo> cb) {
SubmitInput in = SubmitInput.create();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
index 112c42f..f857169 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
@@ -14,17 +14,27 @@
package com.google.gerrit.client.changes;
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.rpc.NativeString;
import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.core.client.JsArrayString;
import com.google.gwtjsonrpc.client.impl.ser.JavaSqlTimestamp_JsonSerializer;
import java.sql.Timestamp;
import java.util.Set;
public class ChangeInfo extends JavaScriptObject {
+ public final void init() {
+ if (labels0() != null) {
+ labels0().copyKeysIntoChildren("_name");
+ }
+ }
+
public final Project.NameKey project_name_key() {
return new Project.NameKey(project());
}
@@ -58,7 +68,7 @@
}
public final Set<String> labels() {
- return Natives.keys(labels0());
+ return labels0().keySet();
}
public final native String id() /*-{ return this.id; }-*/;
@@ -74,8 +84,17 @@
public final native boolean starred() /*-{ return this.starred ? true : false; }-*/;
public final native boolean reviewed() /*-{ return this.reviewed ? true : false; }-*/;
public final native String _sortkey() /*-{ return this._sortkey; }-*/;
- private final native JavaScriptObject labels0() /*-{ return this.labels; }-*/;
+ private final native NativeMap<LabelInfo> labels0() /*-{ return this.labels; }-*/;
public final native LabelInfo label(String n) /*-{ return this.labels[n]; }-*/;
+
+ private final native NativeMap<JavaScriptObject> _permitted_labels()
+ /*-{ return this.permitted_labels; }-*/;
+ public final Set<String> permitted_labels() {
+ return Natives.keys(_permitted_labels());
+ }
+ public final native JsArrayString permitted_values(String n)
+ /*-{ return this.permitted_labels[n]; }-*/;
+
final native int _number() /*-{ return this._number; }-*/;
final native boolean _more_changes()
/*-{ return this._more_changes ? true : false; }-*/;
@@ -109,6 +128,15 @@
public final native AccountInfo recommended() /*-{ return this.recommended; }-*/;
public final native AccountInfo disliked() /*-{ return this.disliked; }-*/;
+
+ public final native JsArray<ApprovalInfo> all() /*-{ return this.all; }-*/;
+
+ private final native NativeMap<NativeString> _values() /*-{ return this.values; }-*/;
+ public final Set<String> values() {
+ return Natives.keys(_values());
+ }
+ public final native String value_text(String n) /*-{ return this.values[n]; }-*/;
+
public final native boolean optional() /*-{ return this.optional ? true : false; }-*/;
final native short _value()
/*-{
@@ -121,4 +149,14 @@
protected LabelInfo() {
}
}
+
+ public static class ApprovalInfo extends JavaScriptObject {
+ public final native int _account_id() /*-{ return this._account_id || 0; }-*/;
+ public final native String name() /*-{ return this.name; }-*/;
+ public final native String email() /*-{ return this.email; }-*/;
+ public final native short value() /*-{ return this.value; }-*/;
+
+ protected ApprovalInfo() {
+ }
+ }
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java
index 0b2a148..ecb9a04 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PublishCommentScreen.java
@@ -15,11 +15,14 @@
package com.google.gerrit.client.changes;
import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.changes.ChangeInfo.ApprovalInfo;
+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.rpc.CallbackGroup;
import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.client.rpc.RestApi;
import com.google.gerrit.client.rpc.ScreenLoadCallback;
import com.google.gerrit.client.ui.AccountScreen;
@@ -27,20 +30,16 @@
import com.google.gerrit.client.ui.PatchLink;
import com.google.gerrit.client.ui.SmallHeading;
import com.google.gerrit.common.PageLinks;
-import com.google.gerrit.common.data.ApprovalType;
-import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.common.data.PatchSetPublishDetail;
-import com.google.gerrit.common.data.PermissionRange;
-import com.google.gerrit.reviewdb.client.ApprovalCategory;
-import com.google.gerrit.reviewdb.client.ApprovalCategoryValue;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayString;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.FormPanel;
@@ -78,6 +77,7 @@
private Button cancel;
private boolean saveStateOnUnload = true;
private List<CommentEditorPanel> commentEditors;
+ private ChangeInfo change;
public PublishCommentScreen(final PatchSet.Id psi) {
patchSetId = psi;
@@ -148,6 +148,19 @@
super.onLoad();
CallbackGroup cbs = new CallbackGroup();
+ ChangeApi.revision(patchSetId).view("review").get(cbs.add(
+ new AsyncCallback<ChangeInfo>() {
+ @Override
+ public void onSuccess(ChangeInfo result) {
+ result.init();
+ change = result;
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ // Handled by ScreenLoadCallback.onFailure().
+ }
+ }));
Util.DETAIL_SVC.patchSetPublishDetail(patchSetId, cbs.addGwtjsonrpc(
new ScreenLoadCallback<PatchSetPublishDetail>(this) {
@Override
@@ -230,47 +243,48 @@
}
private void initApprovals(final PatchSetPublishDetail r, final Panel body) {
- ApprovalTypes types = Gerrit.getConfig().getApprovalTypes();
- for (PermissionRange range : r.getLabels()) {
- ApprovalType type = types.byLabel(range.getLabel());
- if (type != null) {
- // Legacy type, use radio buttons.
- initApprovalType(r, body, type, range);
- } else {
- // TODO Newer style label.
- }
+ for (String labelName : change.labels()) {
+ initLabel(r, labelName, body);
}
}
- private void initApprovalType(final PatchSetPublishDetail r,
- final Panel body, final ApprovalType ct, final PermissionRange range) {
- body.add(new SmallHeading(ct.getCategory().getName() + ":"));
+ private void initLabel(PatchSetPublishDetail r, String labelName,
+ Panel body) {
+ JsArrayString nativeValues = change.permitted_values(labelName);
+ if (nativeValues == null || nativeValues.length() == 0) {
+ return;
+ }
+ List<String> values = new ArrayList<String>(nativeValues.length());
+ for (int i = 0; i < nativeValues.length(); i++) {
+ values.add(nativeValues.get(i));
+ }
+ Collections.reverse(values);
+ LabelInfo label = change.label(labelName);
- final VerticalPanel vp = new VerticalPanel();
+ body.add(new SmallHeading(label.name() + ":"));
+
+ VerticalPanel vp = new VerticalPanel();
vp.setStyleName(Gerrit.RESOURCES.css().approvalCategoryList());
- final List<ApprovalCategoryValue> lst =
- new ArrayList<ApprovalCategoryValue>(ct.getValues());
- Collections.reverse(lst);
- final ApprovalCategory.Id catId = ct.getCategory().getId();
- final PatchSetApproval prior = r.getChangeApproval(catId);
- for (final ApprovalCategoryValue buttonValue : lst) {
- if (!range.contains(buttonValue.getValue())) {
- continue;
+ Short prior = null;
+ for (ApprovalInfo app : Natives.asList(label.all())) {
+ if (app._account_id() == Gerrit.getUserAccount().getId().get()) {
+ prior = app.value();
+ break;
}
+ }
- ValueRadioButton b = new ValueRadioButton(ct.getCategory(), buttonValue);
- SafeHtml buf = new SafeHtmlBuilder().append(buttonValue.format());
+ for (String value : values) {
+ ValueRadioButton b = new ValueRadioButton(label, value);
+ SafeHtml buf = new SafeHtmlBuilder().append(b.format());
buf = CommentLinkProcessor.apply(buf);
SafeHtml.set(b, buf);
if (lastState != null && patchSetId.equals(lastState.patchSetId)
- && lastState.approvals.containsKey(buttonValue.getCategoryId())) {
- b.setValue(lastState.approvals.get(buttonValue.getCategoryId()).equals(
- buttonValue));
+ && lastState.approvals.containsKey(label.name())) {
+ b.setValue(lastState.approvals.get(label.name()) == value);
} else {
- b.setValue(prior != null ? buttonValue.getValue() == prior.getValue()
- : buttonValue.getValue() == 0);
+ b.setValue(b.parseValue() == (prior != null ? prior : 0));
}
approvalButtons.add(b);
@@ -366,7 +380,7 @@
data.init();
for (final ValueRadioButton b : approvalButtons) {
if (b.getValue()) {
- data.label(b.category.getLabelName(), b.value.getValue());
+ data.label(b.label.name(), b.parseValue());
}
}
@@ -435,29 +449,45 @@
Gerrit.display(PageLinks.toChange(ck), new ChangeScreen(ck));
}
- private static class ValueRadioButton extends RadioButton {
- final ApprovalCategory category;
- final ApprovalCategoryValue value;
+ private static short parseLabelValue(String value) {
+ if (value.charAt(0) == ' ' || value.charAt(0) == '+') {
+ value = value.substring(1);
+ }
+ return Short.parseShort(value);
+ }
- ValueRadioButton(ApprovalCategory c, ApprovalCategoryValue v) {
- super(c.getLabelName());
- category = c;
- value = v;
+ private static class ValueRadioButton extends RadioButton {
+ final LabelInfo label;
+ final String value;
+
+ ValueRadioButton(LabelInfo label, String value) {
+ super(label.name());
+ this.label = label;
+ this.value = value;
+ }
+
+ String format() {
+ return new StringBuilder().append(value).append(' ')
+ .append(label.value_text(value)).toString();
+ }
+
+ short parseValue() {
+ return parseLabelValue(value);
}
}
private static class SavedState {
final PatchSet.Id patchSetId;
final String message;
- final Map<ApprovalCategory.Id, ApprovalCategoryValue> approvals;
+ final Map<String, String> approvals;
SavedState(final PublishCommentScreen p) {
patchSetId = p.patchSetId;
message = p.message.getText();
- approvals = new HashMap<ApprovalCategory.Id, ApprovalCategoryValue>();
+ approvals = new HashMap<String, String>();
for (final ValueRadioButton b : p.approvalButtons) {
if (b.getValue()) {
- approvals.put(b.value.getCategoryId(), b.value);
+ approvals.put(b.label.name(), b.value);
}
}
}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/ApprovalCategoryValue.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/ApprovalCategoryValue.java
index b7761c5..46e9892 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/ApprovalCategoryValue.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/ApprovalCategoryValue.java
@@ -88,21 +88,26 @@
name = n;
}
- public String formatValue() {
- if (getValue() < 0) {
- return Short.toString(getValue());
- } else if (getValue() == 0) {
+ public static String formatValue(short value) {
+ if (value < 0) {
+ return Short.toString(value);
+ } else if (value == 0) {
return " 0";
} else {
- return "+" + Short.toString(getValue());
+ return "+" + Short.toString(value);
}
}
+ public String formatValue() {
+ return formatValue(getValue());
+ }
+
+ public static String format(String name, short value) {
+ return new StringBuilder().append(formatValue(value))
+ .append(' ').append(name).toString();
+ }
+
public String format() {
- final StringBuilder m = new StringBuilder();
- m.append(formatValue());
- m.append(' ');
- m.append(getName());
- return m.toString();
+ return format(getName(), getValue());
}
}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSetApproval.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSetApproval.java
index 1c1cb35..e230880 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSetApproval.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSetApproval.java
@@ -96,6 +96,9 @@
@Column(id = 5, length = 16, notNull = false)
protected String changeSortKey;
+ /** Label name copied from corresponding {@link ApprovalCategory}. */
+ protected String label;
+
protected PatchSetApproval() {
}
@@ -154,4 +157,12 @@
changeOpen = c.open;
changeSortKey = c.sortKey;
}
+
+ public String getLabel() {
+ return label;
+ }
+
+ public void setLabel(String label) {
+ this.label = label;
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
index b0e59390..f9f35ba 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
@@ -21,19 +21,25 @@
import static com.google.gerrit.common.changes.ListChangesOption.CURRENT_FILES;
import static com.google.gerrit.common.changes.ListChangesOption.CURRENT_REVISION;
import static com.google.gerrit.common.changes.ListChangesOption.DETAILED_ACCOUNTS;
+import static com.google.gerrit.common.changes.ListChangesOption.DETAILED_LABELS;
import static com.google.gerrit.common.changes.ListChangesOption.LABELS;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gerrit.common.changes.ListChangesOption;
import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.ApprovalTypes;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.common.data.PermissionRange;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.ApprovalCategoryValue;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.reviewdb.client.Patch;
@@ -179,6 +185,12 @@
return formatList2(ImmutableList.of(tmp)).get(0).get(0);
}
+ public ChangeInfo format(RevisionResource rsrc) throws OrmException {
+ ChangeData cd = new ChangeData(rsrc.getControl());
+ cd.limitToPatchSets(ImmutableList.of(rsrc.getPatchSet().getId()));
+ return format(cd);
+ }
+
public List<List<ChangeInfo>> formatList2(List<List<ChangeData>> in)
throws OrmException {
accountLoader =
@@ -219,10 +231,15 @@
out._sortkey = in.getSortKey();
out.starred = user.getStarredChanges().contains(in.getId()) ? true : null;
out.reviewed = in.getStatus().isOpen() && isChangeReviewed(cd) ? true : null;
- out.labels = options.contains(LABELS) ? labelsFor(cd) : null;
+ out.labels = labelsFor(cd, options.contains(LABELS),
+ options.contains(DETAILED_LABELS));
+ if (options.contains(DETAILED_LABELS)) {
+ out.permitted_labels = permittedLabels(cd);
+ }
out.finish();
- if (options.contains(ALL_REVISIONS) || options.contains(CURRENT_REVISION)) {
+ if (options.contains(ALL_REVISIONS) || options.contains(CURRENT_REVISION)
+ || cd.getLimitedPatchSets() != null) {
out.revisions = revisions(cd);
for (String commit : out.revisions.keySet()) {
if (out.revisions.get(commit).isCurrent) {
@@ -259,7 +276,24 @@
return ctrl;
}
- private Map<String, LabelInfo> labelsFor(ChangeData cd) throws OrmException {
+ private List<SubmitRecord> submitRecords(ChangeData cd) throws OrmException {
+ if (cd.getSubmitRecords() != null) {
+ return cd.getSubmitRecords();
+ }
+ ChangeControl ctl = control(cd);
+ if (ctl == null) {
+ return ImmutableList.of();
+ }
+ PatchSet ps = cd.currentPatchSet(db);
+ if (ps == null) {
+ return ImmutableList.of();
+ }
+ cd.setSubmitRecords(ctl.canSubmit(db.get(), ps, cd, true, false));
+ return cd.getSubmitRecords();
+ }
+
+ private Map<String, LabelInfo> labelsFor(ChangeData cd, boolean standard,
+ boolean detailed) throws OrmException {
ChangeControl ctl = control(cd);
if (ctl == null) {
return Collections.emptyMap();
@@ -271,7 +305,31 @@
}
Map<String, LabelInfo> labels = Maps.newLinkedHashMap();
- for (SubmitRecord rec : ctl.canSubmit(db.get(), ps, cd, true, false)) {
+ initLabels(cd, labels, standard);
+
+ Collection<PatchSetApproval> approvals = null;
+ if (detailed) {
+ approvals = cd.currentApprovals(db);
+ setAllApprovals(labels, approvals);
+ }
+ for (Map.Entry<String, LabelInfo> e : labels.entrySet()) {
+ ApprovalType type = approvalTypes.byLabel(e.getKey());
+ if (type == null) {
+ continue; // TODO: Support arbitrary labels.
+ }
+ if (standard) {
+ approvals = setRecommendedAndDisliked(cd, approvals, type, e.getValue());
+ }
+ if (detailed) {
+ setLabelValues(type, e.getValue());
+ }
+ }
+ return labels;
+ }
+
+ private void initLabels(ChangeData cd, Map<String, LabelInfo> labels,
+ boolean standard) throws OrmException {
+ for (SubmitRecord rec : submitRecords(cd)) {
if (rec.labels == null) {
continue;
}
@@ -280,59 +338,127 @@
if (p == null || p._status.compareTo(r.status) < 0) {
LabelInfo n = new LabelInfo();
n._status = r.status;
- switch (r.status) {
- case OK:
- n.approved = accountLoader.get(r.appliedBy);
- break;
- case REJECT:
- n.rejected = accountLoader.get(r.appliedBy);
- break;
- default:
- break;
+ if (standard) {
+ switch (r.status) {
+ case OK:
+ n.approved = accountLoader.get(r.appliedBy);
+ break;
+ case REJECT:
+ n.rejected = accountLoader.get(r.appliedBy);
+ break;
+ default:
+ break;
+ }
}
+
n.optional = n._status == SubmitRecord.Label.Status.MAY ? true : null;
labels.put(r.label, n);
}
}
}
+ }
- Collection<PatchSetApproval> approvals = null;
- for (Map.Entry<String, LabelInfo> e : labels.entrySet()) {
- if (e.getValue().approved != null || e.getValue().rejected != null) {
+ private Collection<PatchSetApproval> setRecommendedAndDisliked(ChangeData cd,
+ Collection<PatchSetApproval> approvals, ApprovalType type,
+ LabelInfo label) throws OrmException {
+ if (label.approved != null || label.rejected != null) {
+ return approvals;
+ }
+
+ if (type.getMin() == null || type.getMax() == null) {
+ // Unknown or misconfigured type can't have intermediate scores.
+ return approvals;
+ }
+
+ short min = type.getMin().getValue();
+ short max = type.getMax().getValue();
+ if (-1 <= min && max <= 1) {
+ // Types with a range of -1..+1 can't have intermediate scores.
+ return approvals;
+ }
+
+ if (approvals == null) {
+ approvals = cd.currentApprovals(db);
+ }
+ for (PatchSetApproval psa : approvals) {
+ short val = psa.getValue();
+ if (val != 0 && min < val && val < max
+ && psa.getCategoryId().equals(type.getCategory().getId())) {
+ if (0 < val) {
+ label.recommended = accountLoader.get(psa.getAccountId());
+ label.value = val != 1 ? val : null;
+ } else {
+ label.disliked = accountLoader.get(psa.getAccountId());
+ label.value = val != -1 ? val : null;
+ }
+ }
+ }
+ return approvals;
+ }
+
+ private void setAllApprovals(Map<String, LabelInfo> labels,
+ Collection<PatchSetApproval> approvals) {
+ for (PatchSetApproval psa : approvals) {
+ ApprovalType at = approvalTypes.byId(psa.getCategoryId());
+ if (at == null) {
continue;
}
+ LabelInfo p = labels.get(at.getCategory().getLabelName());
+ if (p == null) {
+ continue; // TODO: support arbitrary labels.
+ }
+ if (p.all == null) {
+ p.all = Lists.newArrayList();
+ }
+ ApprovalInfo ai = new ApprovalInfo(psa.getAccountId());
+ accountLoader.put(ai);
+ ai.value = psa.getValue();
+ p.all.add(ai);
+ }
+ }
- ApprovalType type = approvalTypes.byLabel(e.getKey());
- if (type == null || type.getMin() == null || type.getMax() == null) {
- // Unknown or misconfigured type can't have intermediate scores.
+ private static boolean isOnlyZero(Collection<String> values) {
+ return values.isEmpty() || (values.size() == 1 && values.contains(" 0"));
+ }
+
+ private void setLabelValues(ApprovalType type, LabelInfo label) {
+ label.values = Maps.newLinkedHashMap();
+ for (ApprovalCategoryValue acv : type.getValues()) {
+ label.values.put(acv.formatValue(), acv.getName());
+ }
+ if (isOnlyZero(label.values.keySet())) {
+ label.values = null;
+ }
+ }
+
+
+ private Map<String, Collection<String>> permittedLabels(ChangeData cd)
+ throws OrmException {
+ ChangeControl ctl = control(cd);
+ ListMultimap<String, String> permitted = LinkedListMultimap.create();
+ for (SubmitRecord rec : submitRecords(cd)) {
+ if (rec.labels == null) {
continue;
}
-
- short min = type.getMin().getValue();
- short max = type.getMax().getValue();
- if (-1 <= min && max <= 1) {
- // Types with a range of -1..+1 can't have intermediate scores.
- continue;
- }
-
- if (approvals == null) {
- approvals = cd.currentApprovals(db);
- }
- for (PatchSetApproval psa : approvals) {
- short val = psa.getValue();
- if (val != 0 && min < val && val < max
- && psa.getCategoryId().equals(type.getCategory().getId())) {
- if (0 < val) {
- e.getValue().recommended = accountLoader.get(psa.getAccountId());
- e.getValue().value = val != 1 ? val : null;
- } else {
- e.getValue().disliked = accountLoader.get(psa.getAccountId());
- e.getValue().value = val != -1 ? val : null;
+ for (SubmitRecord.Label r : rec.labels) {
+ ApprovalType type = approvalTypes.byLabel(r.label);
+ if (type == null) {
+ continue; // TODO: Support arbitrary labels.
+ }
+ PermissionRange range = ctl.getRange(Permission.forLabel(r.label));
+ for (ApprovalCategoryValue acv : type.getValues()) {
+ if (range.contains(acv.getValue())) {
+ permitted.put(r.label, acv.formatValue());
}
}
}
}
- return labels;
+ for (Collection<String> values : permitted.asMap().values()) {
+ if (isOnlyZero(values)) {
+ values.clear();
+ }
+ }
+ return permitted.asMap();
}
private boolean isChangeReviewed(ChangeData cd) throws OrmException {
@@ -377,7 +503,7 @@
}
Collection<PatchSet> src;
- if (options.contains(ALL_REVISIONS)) {
+ if (cd.getLimitedPatchSets() != null || options.contains(ALL_REVISIONS)) {
src = cd.patches(db);
} else {
src = Collections.singletonList(cd.currentPatchSet(db));
@@ -531,6 +657,7 @@
AccountInfo owner;
Map<String, LabelInfo> labels;
+ Map<String, Collection<String>> permitted_labels;
String current_revision;
Map<String, RevisionInfo> revisions;
public Boolean _moreChanges;
@@ -588,12 +715,24 @@
static class LabelInfo {
transient SubmitRecord.Label.Status _status;
+
AccountInfo approved;
AccountInfo rejected;
-
AccountInfo recommended;
AccountInfo disliked;
+ List<ApprovalInfo> all;
+
+ Map<String, String> values;
+
Short value;
Boolean optional;
}
+
+ static class ApprovalInfo extends AccountInfo {
+ short value;
+
+ ApprovalInfo(Account.Id id) {
+ super(id);
+ }
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetReview.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetReview.java
new file mode 100644
index 0000000..997f5e7
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetReview.java
@@ -0,0 +1,35 @@
+// 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.server.change;
+
+import com.google.gerrit.common.changes.ListChangesOption;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+
+public class GetReview implements RestReadView<RevisionResource> {
+ private final ChangeJson json;
+
+ @Inject
+ GetReview(ChangeJson json) {
+ this.json = json.addOption(ListChangesOption.DETAILED_LABELS)
+ .addOption(ListChangesOption.DETAILED_ACCOUNTS);
+ }
+
+ @Override
+ public Object apply(RevisionResource resource) throws OrmException {
+ return json.format(resource);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
index 571439d..6586725 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
@@ -54,6 +54,7 @@
delete(REVIEWER_KIND).to(DeleteReviewer.class);
child(CHANGE_KIND, "revisions").to(Revisions.class);
+ get(REVISION_KIND, "review").to(GetReview.class);
post(REVISION_KIND, "review").to(PostReview.class);
post(REVISION_KIND, "submit").to(Submit.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
index afde702..07222fa 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -14,14 +14,17 @@
package com.google.gerrit.server.query.change;
+import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSet.Id;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.TrackingId;
@@ -47,9 +50,9 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
public class ChangeData {
public static void ensureChangeLoaded(
@@ -79,7 +82,9 @@
for (PatchSet ps : db.get().patchSets().get(missing.keySet())) {
ChangeData cd = missing.get(ps.getId());
cd.currentPatchSet = ps;
- cd.patches = Lists.newArrayList(ps);
+ if (cd.limitedIds == null) {
+ cd.patches = Lists.newArrayList(ps);
+ }
}
}
}
@@ -107,9 +112,9 @@
private Change change;
private String commitMessage;
private PatchSet currentPatchSet;
+ private Set<PatchSet.Id> limitedIds;
private Collection<PatchSet> patches;
- private Collection<PatchSetApproval> approvals;
- private Map<PatchSet.Id,Collection<PatchSetApproval>> approvalsMap;
+ private Multimap<PatchSet.Id, PatchSetApproval> approvals;
private Collection<PatchSetApproval> currentApprovals;
private String[] currentFiles;
private Collection<PatchLineComment> comments;
@@ -117,6 +122,7 @@
private CurrentUser visibleTo;
private ChangeControl changeControl;
private List<ChangeMessage> messages;
+ private List<SubmitRecord> submitRecords;
public ChangeData(final Change.Id id) {
legacyId = id;
@@ -133,6 +139,21 @@
changeControl = c;
}
+ public void limitToPatchSets(Collection<PatchSet.Id> ids) {
+ limitedIds = Sets.newLinkedHashSetWithExpectedSize(ids.size());
+ for (PatchSet.Id id : ids) {
+ if (!id.getParentKey().equals(legacyId)) {
+ throw new IllegalArgumentException(String.format(
+ "invalid patch set %s for change %s", id, legacyId));
+ }
+ limitedIds.add(id);
+ }
+ }
+
+ public Collection<PatchSet.Id> getLimitedPatchSets() {
+ return limitedIds;
+ }
+
public void setCurrentFilePaths(String[] filePaths) {
currentFiles = filePaths;
}
@@ -239,13 +260,9 @@
Change c = change(db);
if (c == null) {
currentApprovals = Collections.emptyList();
- } else if (approvals != null) {
- Map<Id, Collection<PatchSetApproval>> map = approvalsMap(db);
- currentApprovals = map.get(c.currentPatchSetId());
- if (currentApprovals == null) {
- currentApprovals = Collections.emptyList();
- map.put(c.currentPatchSetId(), currentApprovals);
- }
+ } else if (approvals != null &&
+ (limitedIds == null || limitedIds.contains(c.currentPatchSetId()))) {
+ return approvals.get(c.currentPatchSetId());
} else {
currentApprovals = db.get().patchSetApprovals()
.byPatchSet(c.currentPatchSetId()).toList();
@@ -276,37 +293,60 @@
return commitMessage;
}
+ /**
+ * @param db review database.
+ * @return patches for the change. If {@link #limitToPatchSets(Collection)}
+ * was previously called, only contains patches with the specified IDs.
+ * @throws OrmException an error occurred reading the database.
+ */
public Collection<PatchSet> patches(Provider<ReviewDb> db)
throws OrmException {
if (patches == null) {
- patches = db.get().patchSets().byChange(legacyId).toList();
+ if (limitedIds != null) {
+ patches = Lists.newArrayList();
+ for (PatchSet ps : db.get().patchSets().byChange(legacyId)) {
+ if (limitedIds.contains(ps.getId())) {
+ patches.add(ps);
+ }
+ }
+ } else {
+ patches = db.get().patchSets().byChange(legacyId).toList();
+ }
}
return patches;
}
+ /**
+ * @param db review database.
+ * @return patch set approvals for the change. If
+ * {@link #limitToPatchSets(Collection)} was previously called, only contains
+ * approvals for the patches with the specified IDs.
+ * @throws OrmException an error occurred reading the database.
+ */
public Collection<PatchSetApproval> approvals(Provider<ReviewDb> db)
throws OrmException {
- if (approvals == null) {
- approvals = db.get().patchSetApprovals().byChange(legacyId).toList();
- }
- return approvals;
+ return approvalsMap(db).values();
}
- public Map<PatchSet.Id,Collection<PatchSetApproval>> approvalsMap(Provider<ReviewDb> db)
- throws OrmException {
- if (approvalsMap == null) {
- Collection<PatchSetApproval> all = approvals(db);
- approvalsMap = new HashMap<PatchSet.Id,Collection<PatchSetApproval>>(all.size());
- for (PatchSetApproval psa : all) {
- Collection<PatchSetApproval> c = approvalsMap.get(psa.getPatchSetId());
- if (c == null) {
- c = new ArrayList<PatchSetApproval>();
- approvalsMap.put(psa.getPatchSetId(), c);
+ /**
+ * @param db review database.
+ * @return patch set approvals for the change, keyed by ID. If
+ * {@link #limitToPatchSets(Collection)} was previously called, only
+ * contains approvals for the patches with the specified IDs.
+ * @throws OrmException an error occurred reading the database.
+ */
+ public Multimap<PatchSet.Id,PatchSetApproval> approvalsMap(
+ Provider<ReviewDb> db) throws OrmException {
+ if (approvals == null) {
+ approvals = ArrayListMultimap.create();
+ for (PatchSetApproval psa :
+ db.get().patchSetApprovals().byChange(legacyId)) {
+ if (limitedIds == null || limitedIds.contains(legacyId)) {
+ approvals.put(psa.getPatchSetId(), psa);
}
- c.add(psa);
}
}
- return approvalsMap;
+ return approvals;
}
public Collection<PatchLineComment> comments(Provider<ReviewDb> db)
@@ -332,4 +372,12 @@
}
return messages;
}
+
+ public void setSubmitRecords(List<SubmitRecord> records) {
+ submitRecords = records;
+ }
+
+ public List<SubmitRecord> getSubmitRecords() {
+ return submitRecords;
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
index 8cc9e78..8ca0548 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
@@ -286,11 +286,11 @@
if (includePatchSets) {
if (includeFiles) {
eventFactory.addPatchSets(c, d.patches(db),
- includeApprovals ? d.approvalsMap(db) : null,
+ includeApprovals ? d.approvalsMap(db).asMap() : null,
includeFiles, d.change(db));
} else {
eventFactory.addPatchSets(c, d.patches(db),
- includeApprovals ? d.approvalsMap(db) : null);
+ includeApprovals ? d.approvalsMap(db).asMap() : null);
}
}