ChangeJson: Only list increasing values as permitted post-submit
Change-Id: Iddffea00e263007d1284fb0337474a9263d986fd
This commit is contained in:

committed by
Edwin Kempin

parent
d7424d36b0
commit
1decb0499e
@@ -20,6 +20,7 @@ import static com.google.gerrit.extensions.api.changes.SubmittedTogetherOption.N
|
|||||||
import static com.google.gerrit.reviewdb.client.Patch.COMMIT_MSG;
|
import static com.google.gerrit.reviewdb.client.Patch.COMMIT_MSG;
|
||||||
import static com.google.gerrit.reviewdb.client.Patch.MERGE_LIST;
|
import static com.google.gerrit.reviewdb.client.Patch.MERGE_LIST;
|
||||||
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
|
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
|
||||||
|
import static java.util.stream.Collectors.toList;
|
||||||
import static org.eclipse.jgit.lib.Constants.HEAD;
|
import static org.eclipse.jgit.lib.Constants.HEAD;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
@@ -139,6 +140,7 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -1148,4 +1150,18 @@ public abstract class AbstractDaemonTest {
|
|||||||
grant(Permission.SUBMIT, project, "refs/for/refs/heads/*");
|
grant(Permission.SUBMIT, project, "refs/for/refs/heads/*");
|
||||||
return cloneProject(project);
|
return cloneProject(project);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void assertPermitted(ChangeInfo info, String label,
|
||||||
|
Integer... expected) {
|
||||||
|
assertThat(info.permittedLabels).isNotNull();
|
||||||
|
Collection<String> strs = info.permittedLabels.get(label);
|
||||||
|
if (expected.length == 0) {
|
||||||
|
assertThat(strs).isNull();
|
||||||
|
} else {
|
||||||
|
assertThat(
|
||||||
|
strs.stream().map(s -> Integer.valueOf(s.trim()))
|
||||||
|
.collect(toList()))
|
||||||
|
.containsExactlyElementsIn(Arrays.asList(expected));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2304,6 +2304,8 @@ public class ChangeIT extends AbstractDaemonTest {
|
|||||||
.containsExactly("Code-Review", "Verified");
|
.containsExactly("Code-Review", "Verified");
|
||||||
assertThat(change.permittedLabels.keySet())
|
assertThat(change.permittedLabels.keySet())
|
||||||
.containsExactly("Code-Review", "Verified");
|
.containsExactly("Code-Review", "Verified");
|
||||||
|
assertPermitted(change, "Code-Review", -2, -1, 0, 1, 2);
|
||||||
|
assertPermitted(change, "Verified", -1, 0, 1);
|
||||||
|
|
||||||
// add an approval on the new label
|
// add an approval on the new label
|
||||||
gApi.changes()
|
gApi.changes()
|
||||||
@@ -2344,6 +2346,7 @@ public class ChangeIT extends AbstractDaemonTest {
|
|||||||
assertThat(change.status).isEqualTo(ChangeStatus.MERGED);
|
assertThat(change.status).isEqualTo(ChangeStatus.MERGED);
|
||||||
assertThat(change.labels.keySet()).containsExactly("Code-Review");
|
assertThat(change.labels.keySet()).containsExactly("Code-Review");
|
||||||
assertThat(change.permittedLabels.keySet()).containsExactly("Code-Review");
|
assertThat(change.permittedLabels.keySet()).containsExactly("Code-Review");
|
||||||
|
assertPermitted(change, "Code-Review", 2);
|
||||||
|
|
||||||
// add new label and assert that it's returned for existing changes
|
// add new label and assert that it's returned for existing changes
|
||||||
ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
|
ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
|
||||||
@@ -2363,6 +2366,8 @@ public class ChangeIT extends AbstractDaemonTest {
|
|||||||
.containsExactly("Code-Review", "Verified");
|
.containsExactly("Code-Review", "Verified");
|
||||||
assertThat(change.permittedLabels.keySet())
|
assertThat(change.permittedLabels.keySet())
|
||||||
.containsExactly("Code-Review", "Verified");
|
.containsExactly("Code-Review", "Verified");
|
||||||
|
assertPermitted(change, "Code-Review", 2);
|
||||||
|
assertPermitted(change, "Verified", 0, 1);
|
||||||
|
|
||||||
// ignore the new label by Prolog submit rule and assert that the label is
|
// ignore the new label by Prolog submit rule and assert that the label is
|
||||||
// no longer returned
|
// no longer returned
|
||||||
@@ -2378,8 +2383,8 @@ public class ChangeIT extends AbstractDaemonTest {
|
|||||||
change = gApi.changes()
|
change = gApi.changes()
|
||||||
.id(r.getChangeId())
|
.id(r.getChangeId())
|
||||||
.get();
|
.get();
|
||||||
assertThat(change.labels.keySet()).containsExactly("Code-Review");
|
assertPermitted(change, "Code-Review", 2);
|
||||||
assertThat(change.permittedLabels.keySet()).containsExactly("Code-Review");
|
assertPermitted(change, "Verified");
|
||||||
|
|
||||||
// add an approval on the new label and assert that the label is now
|
// add an approval on the new label and assert that the label is now
|
||||||
// returned although it is ignored by the Prolog submit rule and hence not
|
// returned although it is ignored by the Prolog submit rule and hence not
|
||||||
@@ -2395,7 +2400,8 @@ public class ChangeIT extends AbstractDaemonTest {
|
|||||||
.get();
|
.get();
|
||||||
assertThat(change.labels.keySet())
|
assertThat(change.labels.keySet())
|
||||||
.containsExactly("Code-Review", "Verified");
|
.containsExactly("Code-Review", "Verified");
|
||||||
assertThat(change.permittedLabels.keySet()).containsExactly("Code-Review");
|
assertPermitted(change, "Code-Review", 2);
|
||||||
|
assertPermitted(change, "Verified");
|
||||||
|
|
||||||
// remove label and assert that it's no longer returned for existing
|
// remove label and assert that it's no longer returned for existing
|
||||||
// changes, even if there is an approval for it
|
// changes, even if there is an approval for it
|
||||||
@@ -2410,6 +2416,7 @@ public class ChangeIT extends AbstractDaemonTest {
|
|||||||
.get();
|
.get();
|
||||||
assertThat(change.labels.keySet()).containsExactly("Code-Review");
|
assertThat(change.labels.keySet()).containsExactly("Code-Review");
|
||||||
assertThat(change.permittedLabels.keySet()).containsExactly("Code-Review");
|
assertThat(change.permittedLabels.keySet()).containsExactly("Code-Review");
|
||||||
|
assertPermitted(change, "Code-Review", 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -2425,7 +2432,7 @@ public class ChangeIT extends AbstractDaemonTest {
|
|||||||
.get();
|
.get();
|
||||||
assertThat(change.status).isEqualTo(ChangeStatus.MERGED);
|
assertThat(change.status).isEqualTo(ChangeStatus.MERGED);
|
||||||
assertThat(change.labels.keySet()).containsExactly("Code-Review");
|
assertThat(change.labels.keySet()).containsExactly("Code-Review");
|
||||||
assertThat(change.permittedLabels.keySet()).containsExactly("Code-Review");
|
assertPermitted(change, "Code-Review", 0, 1, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@@ -20,6 +20,7 @@ import static com.google.gerrit.acceptance.PushOneCommit.FILE_NAME;
|
|||||||
import static com.google.gerrit.acceptance.PushOneCommit.PATCH;
|
import static com.google.gerrit.acceptance.PushOneCommit.PATCH;
|
||||||
import static com.google.gerrit.acceptance.PushOneCommit.PATCH_FILE_ONLY;
|
import static com.google.gerrit.acceptance.PushOneCommit.PATCH_FILE_ONLY;
|
||||||
import static com.google.gerrit.acceptance.PushOneCommit.SUBJECT;
|
import static com.google.gerrit.acceptance.PushOneCommit.SUBJECT;
|
||||||
|
import static com.google.gerrit.extensions.client.ListChangesOption.DETAILED_LABELS;
|
||||||
import static com.google.gerrit.reviewdb.client.Patch.COMMIT_MSG;
|
import static com.google.gerrit.reviewdb.client.Patch.COMMIT_MSG;
|
||||||
import static com.google.gerrit.reviewdb.client.Patch.MERGE_LIST;
|
import static com.google.gerrit.reviewdb.client.Patch.MERGE_LIST;
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
@@ -44,7 +45,6 @@ import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput;
|
|||||||
import com.google.gerrit.extensions.api.changes.RevisionApi;
|
import com.google.gerrit.extensions.api.changes.RevisionApi;
|
||||||
import com.google.gerrit.extensions.api.projects.BranchInput;
|
import com.google.gerrit.extensions.api.projects.BranchInput;
|
||||||
import com.google.gerrit.extensions.client.ChangeStatus;
|
import com.google.gerrit.extensions.client.ChangeStatus;
|
||||||
import com.google.gerrit.extensions.client.ListChangesOption;
|
|
||||||
import com.google.gerrit.extensions.client.SubmitType;
|
import com.google.gerrit.extensions.client.SubmitType;
|
||||||
import com.google.gerrit.extensions.common.ApprovalInfo;
|
import com.google.gerrit.extensions.common.ApprovalInfo;
|
||||||
import com.google.gerrit.extensions.common.ChangeInfo;
|
import com.google.gerrit.extensions.common.ChangeInfo;
|
||||||
@@ -166,6 +166,9 @@ public class RevisionIT extends AbstractDaemonTest {
|
|||||||
approval = getApproval(changeId, label);
|
approval = getApproval(changeId, label);
|
||||||
assertThat(approval.value).isEqualTo(1);
|
assertThat(approval.value).isEqualTo(1);
|
||||||
assertThat(approval.postSubmit).isNull();
|
assertThat(approval.postSubmit).isNull();
|
||||||
|
assertPermitted(
|
||||||
|
gApi.changes().id(changeId).get(EnumSet.of(DETAILED_LABELS)),
|
||||||
|
"Code-Review", 1, 2);
|
||||||
|
|
||||||
// Repeating the current label is allowed. Does not flip the postSubmit bit
|
// Repeating the current label is allowed. Does not flip the postSubmit bit
|
||||||
// due to deduplication codepath.
|
// due to deduplication codepath.
|
||||||
@@ -200,6 +203,9 @@ public class RevisionIT extends AbstractDaemonTest {
|
|||||||
approval = getApproval(changeId, label);
|
approval = getApproval(changeId, label);
|
||||||
assertThat(approval.value).isEqualTo(2);
|
assertThat(approval.value).isEqualTo(2);
|
||||||
assertThat(approval.postSubmit).isTrue();
|
assertThat(approval.postSubmit).isTrue();
|
||||||
|
assertPermitted(
|
||||||
|
gApi.changes().id(changeId).get(EnumSet.of(DETAILED_LABELS)),
|
||||||
|
"Code-Review", 2);
|
||||||
|
|
||||||
// Decreasing to previous post-submit vote is still not allowed.
|
// Decreasing to previous post-submit vote is still not allowed.
|
||||||
try {
|
try {
|
||||||
@@ -1090,7 +1096,7 @@ public class RevisionIT extends AbstractDaemonTest {
|
|||||||
throws Exception {
|
throws Exception {
|
||||||
ChangeInfo info = gApi.changes()
|
ChangeInfo info = gApi.changes()
|
||||||
.id(changeId)
|
.id(changeId)
|
||||||
.get(EnumSet.of(ListChangesOption.DETAILED_LABELS));
|
.get(EnumSet.of(DETAILED_LABELS));
|
||||||
LabelInfo li = info.labels.get(label);
|
LabelInfo li = info.labels.get(label);
|
||||||
assertThat(li).isNotNull();
|
assertThat(li).isNotNull();
|
||||||
int accountId = atrScope.get().getUser().getAccountId().get();
|
int accountId = atrScope.get().getUser().getAccountId().get();
|
||||||
|
@@ -18,7 +18,6 @@ import static com.google.common.truth.Truth.assertThat;
|
|||||||
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
|
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
|
||||||
import static com.google.gerrit.server.project.Util.category;
|
import static com.google.gerrit.server.project.Util.category;
|
||||||
import static com.google.gerrit.server.project.Util.value;
|
import static com.google.gerrit.server.project.Util.value;
|
||||||
import static java.util.stream.Collectors.toList;
|
|
||||||
|
|
||||||
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
||||||
import com.google.gerrit.acceptance.NoHttpd;
|
import com.google.gerrit.acceptance.NoHttpd;
|
||||||
@@ -44,9 +43,6 @@ import org.junit.After;
|
|||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
@NoHttpd
|
@NoHttpd
|
||||||
public class CustomLabelIT extends AbstractDaemonTest {
|
public class CustomLabelIT extends AbstractDaemonTest {
|
||||||
|
|
||||||
@@ -193,8 +189,7 @@ public class CustomLabelIT extends AbstractDaemonTest {
|
|||||||
revision(r).submit();
|
revision(r).submit();
|
||||||
|
|
||||||
ChangeInfo info = get(r.getChangeId(), ListChangesOption.DETAILED_LABELS);
|
ChangeInfo info = get(r.getChangeId(), ListChangesOption.DETAILED_LABELS);
|
||||||
// TODO(dborowitz): Don't claim reducing vote is allowed.
|
assertPermitted(info, "Code-Review", 2);
|
||||||
assertPermitted(info, "Code-Review", -2, -1, 0, 1, 2);
|
|
||||||
assertPermitted(info, P.getName(), 0, 1);
|
assertPermitted(info, P.getName(), 0, 1);
|
||||||
assertPermitted(info, label.getName());
|
assertPermitted(info, label.getName());
|
||||||
|
|
||||||
@@ -210,20 +205,6 @@ public class CustomLabelIT extends AbstractDaemonTest {
|
|||||||
revision(r).review(in);
|
revision(r).review(in);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertPermitted(ChangeInfo info, String label,
|
|
||||||
Integer... expected) {
|
|
||||||
assertThat(info.permittedLabels).isNotNull();
|
|
||||||
Collection<String> strs = info.permittedLabels.get(label);
|
|
||||||
if (expected.length == 0) {
|
|
||||||
assertThat(strs).isNull();
|
|
||||||
} else {
|
|
||||||
assertThat(
|
|
||||||
strs.stream().map(s -> Integer.valueOf(s.trim()))
|
|
||||||
.collect(toList()))
|
|
||||||
.containsExactlyElementsIn(Arrays.asList(expected));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void saveLabelConfig() throws Exception {
|
private void saveLabelConfig() throws Exception {
|
||||||
ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
|
ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
|
||||||
cfg.getLabelSections().put(label.getName(), label);
|
cfg.getLabelSections().put(label.getName(), label);
|
||||||
|
@@ -36,6 +36,7 @@ import static com.google.gerrit.extensions.client.ListChangesOption.REVIEWER_UPD
|
|||||||
import static com.google.gerrit.extensions.client.ListChangesOption.SUBMITTABLE;
|
import static com.google.gerrit.extensions.client.ListChangesOption.SUBMITTABLE;
|
||||||
import static com.google.gerrit.extensions.client.ListChangesOption.WEB_LINKS;
|
import static com.google.gerrit.extensions.client.ListChangesOption.WEB_LINKS;
|
||||||
import static com.google.gerrit.server.CommonConverters.toGitPerson;
|
import static com.google.gerrit.server.CommonConverters.toGitPerson;
|
||||||
|
|
||||||
import static java.util.stream.Collectors.toList;
|
import static java.util.stream.Collectors.toList;
|
||||||
|
|
||||||
import com.google.auto.value.AutoValue;
|
import com.google.auto.value.AutoValue;
|
||||||
@@ -89,6 +90,7 @@ import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
|||||||
import com.google.gerrit.reviewdb.client.Project;
|
import com.google.gerrit.reviewdb.client.Project;
|
||||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||||
import com.google.gerrit.server.AnonymousUser;
|
import com.google.gerrit.server.AnonymousUser;
|
||||||
|
import com.google.gerrit.server.ApprovalsUtil;
|
||||||
import com.google.gerrit.server.ChangeMessagesUtil;
|
import com.google.gerrit.server.ChangeMessagesUtil;
|
||||||
import com.google.gerrit.server.CurrentUser;
|
import com.google.gerrit.server.CurrentUser;
|
||||||
import com.google.gerrit.server.GpgException;
|
import com.google.gerrit.server.GpgException;
|
||||||
@@ -192,6 +194,7 @@ public class ChangeJson {
|
|||||||
private final ChangeResource.Factory changeResourceFactory;
|
private final ChangeResource.Factory changeResourceFactory;
|
||||||
private final ChangeKindCache changeKindCache;
|
private final ChangeKindCache changeKindCache;
|
||||||
private final ChangeIndexCollection indexes;
|
private final ChangeIndexCollection indexes;
|
||||||
|
private final ApprovalsUtil approvalsUtil;
|
||||||
|
|
||||||
private boolean lazyLoad = true;
|
private boolean lazyLoad = true;
|
||||||
private AccountLoader accountLoader;
|
private AccountLoader accountLoader;
|
||||||
@@ -221,6 +224,7 @@ public class ChangeJson {
|
|||||||
ChangeResource.Factory changeResourceFactory,
|
ChangeResource.Factory changeResourceFactory,
|
||||||
ChangeKindCache changeKindCache,
|
ChangeKindCache changeKindCache,
|
||||||
ChangeIndexCollection indexes,
|
ChangeIndexCollection indexes,
|
||||||
|
ApprovalsUtil approvalsUtil,
|
||||||
@Assisted Set<ListChangesOption> options) {
|
@Assisted Set<ListChangesOption> options) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.labelNormalizer = ln;
|
this.labelNormalizer = ln;
|
||||||
@@ -244,6 +248,7 @@ public class ChangeJson {
|
|||||||
this.changeResourceFactory = changeResourceFactory;
|
this.changeResourceFactory = changeResourceFactory;
|
||||||
this.changeKindCache = changeKindCache;
|
this.changeKindCache = changeKindCache;
|
||||||
this.indexes = indexes;
|
this.indexes = indexes;
|
||||||
|
this.approvalsUtil = approvalsUtil;
|
||||||
this.options = options.isEmpty()
|
this.options = options.isEmpty()
|
||||||
? EnumSet.noneOf(ListChangesOption.class)
|
? EnumSet.noneOf(ListChangesOption.class)
|
||||||
: EnumSet.copyOf(options);
|
: EnumSet.copyOf(options);
|
||||||
@@ -891,10 +896,12 @@ public class ChangeJson {
|
|||||||
|
|
||||||
private Map<String, Collection<String>> permittedLabels(ChangeControl ctl, ChangeData cd)
|
private Map<String, Collection<String>> permittedLabels(ChangeControl ctl, ChangeData cd)
|
||||||
throws OrmException {
|
throws OrmException {
|
||||||
if (ctl == null) {
|
if (ctl == null || !ctl.getUser().isIdentifiedUser()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<String, Short> labels = null;
|
||||||
|
boolean isMerged = ctl.getChange().getStatus() == Change.Status.MERGED;
|
||||||
LabelTypes labelTypes = ctl.getLabelTypes();
|
LabelTypes labelTypes = ctl.getLabelTypes();
|
||||||
SetMultimap<String, String> permitted = LinkedHashMultimap.create();
|
SetMultimap<String, String> permitted = LinkedHashMultimap.create();
|
||||||
for (SubmitRecord rec : submitRecords(cd)) {
|
for (SubmitRecord rec : submitRecords(cd)) {
|
||||||
@@ -903,16 +910,20 @@ public class ChangeJson {
|
|||||||
}
|
}
|
||||||
for (SubmitRecord.Label r : rec.labels) {
|
for (SubmitRecord.Label r : rec.labels) {
|
||||||
LabelType type = labelTypes.byLabel(r.label);
|
LabelType type = labelTypes.byLabel(r.label);
|
||||||
if (type == null) {
|
if (type == null || (isMerged && !type.allowPostSubmit())) {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (ctl.getChange().getStatus() == Change.Status.MERGED
|
|
||||||
&& !type.allowPostSubmit()) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
PermissionRange range = ctl.getRange(Permission.forLabel(r.label));
|
PermissionRange range = ctl.getRange(Permission.forLabel(r.label));
|
||||||
for (LabelValue v : type.getValues()) {
|
for (LabelValue v : type.getValues()) {
|
||||||
if (range.contains(v.getValue())) {
|
boolean ok = range.contains(v.getValue());
|
||||||
|
if (isMerged) {
|
||||||
|
if (labels == null) {
|
||||||
|
labels = currentLabels(ctl);
|
||||||
|
}
|
||||||
|
short prev = labels.getOrDefault(type.getName(), (short) 0);
|
||||||
|
ok &= v.getValue() >= prev;
|
||||||
|
}
|
||||||
|
if (ok) {
|
||||||
permitted.put(r.label, v.formatValue());
|
permitted.put(r.label, v.formatValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -932,6 +943,17 @@ public class ChangeJson {
|
|||||||
return permitted.asMap();
|
return permitted.asMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Map<String, Short> currentLabels(ChangeControl ctl)
|
||||||
|
throws OrmException {
|
||||||
|
Map<String, Short> result = new HashMap<>();
|
||||||
|
for (PatchSetApproval psa : approvalsUtil.byPatchSetUser(
|
||||||
|
db.get(), ctl, ctl.getChange().currentPatchSetId(),
|
||||||
|
ctl.getUser().getAccountId())) {
|
||||||
|
result.put(psa.getLabel(), psa.getValue());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private Collection<ChangeMessageInfo> messages(ChangeControl ctl, ChangeData cd,
|
private Collection<ChangeMessageInfo> messages(ChangeControl ctl, ChangeData cd,
|
||||||
Map<PatchSet.Id, PatchSet> map)
|
Map<PatchSet.Id, PatchSet> map)
|
||||||
throws OrmException {
|
throws OrmException {
|
||||||
|
Reference in New Issue
Block a user