Merge changes Ice62183c,I41c3c444

* changes:
  MergeOp: Report change id in case of submit rule problems
  Submit: Include ancestors into problem description
This commit is contained in:
Dave Borowitz
2015-07-28 22:00:54 +00:00
committed by Gerrit Code Review
5 changed files with 185 additions and 96 deletions

View File

@@ -871,6 +871,17 @@ abbreviated commit SHA-1 (`c9c0edb`).
+ +
Default is "Submit patch set ${patchSet} into ${branch}". Default is "Submit patch set ${patchSet} into ${branch}".
[[change.submitTooltipAncestors]]change.submitTooltipAncestors::
+
Tooltip for the submit button if there are ancestors which would
also be submitted by submitting the change. Additionally to the variables
as in link:#change.submitTooltip[change.submitTooltip], there is the
variable `${submitSize}` indicating the number of changes which are
submitted.
+
Default is "Submit all ${topicSize} changes of the same topic (${submitSize}
changes including ancestors and other changes related by topic)".
[[change.submitWholeTopic]]change.submitWholeTopic:: [[change.submitWholeTopic]]change.submitWholeTopic::
+ +
Determines if the submit button submits the whole topic instead of Determines if the submit button submits the whole topic instead of

View File

@@ -52,15 +52,14 @@ public class ActionsIT extends AbstractDaemonTest {
commonActionsAssertions(actions); commonActionsAssertions(actions);
// We want to treat a single change in a topic not as a whole topic, // We want to treat a single change in a topic not as a whole topic,
// so regardless of how submitWholeTopic is configured: // so regardless of how submitWholeTopic is configured:
noSubmitWholeTopicAssertions(actions); noSubmitWholeTopicAssertions(actions, 1);
} }
@Test @Test
public void revisionActionsTwoChangeChangesInTopic() throws Exception { public void revisionActionsTwoChangesInTopic() throws Exception {
String changeId = createChangeWithTopic().getChangeId(); String changeId = createChangeWithTopic().getChangeId();
approve(changeId); approve(changeId);
// create another change with the same topic String changeId2 = createChangeWithTopic().getChangeId();
createChangeWithTopic().getChangeId();
Map<String, ActionInfo> actions = getActions(changeId); Map<String, ActionInfo> actions = getActions(changeId);
commonActionsAssertions(actions); commonActionsAssertions(actions);
if (isSubmitWholeTopicEnabled()) { if (isSubmitWholeTopicEnabled()) {
@@ -68,14 +67,19 @@ public class ActionsIT extends AbstractDaemonTest {
assertThat(info.enabled).isNull(); assertThat(info.enabled).isNull();
assertThat(info.label).isEqualTo("Submit whole topic"); assertThat(info.label).isEqualTo("Submit whole topic");
assertThat(info.method).isEqualTo("POST"); assertThat(info.method).isEqualTo("POST");
assertThat(info.title).isEqualTo("Other changes in this topic are not ready"); assertThat(info.title).isEqualTo("This change depends on other " +
"changes which are not ready");
} else { } else {
noSubmitWholeTopicAssertions(actions); noSubmitWholeTopicAssertions(actions, 1);
assertThat(getActions(changeId2).get("submit")).isNull();
approve(changeId2);
noSubmitWholeTopicAssertions(getActions(changeId2), 2);
} }
} }
@Test @Test
public void revisionActionsTwoChangeChangesInTopic_conflicting() throws Exception { public void revisionActionsTwoChangesInTopic_conflicting() throws Exception {
String changeId = createChangeWithTopic().getChangeId(); String changeId = createChangeWithTopic().getChangeId();
approve(changeId); approve(changeId);
@@ -99,38 +103,66 @@ public class ActionsIT extends AbstractDaemonTest {
assertThat(info.label).isEqualTo("Submit whole topic"); assertThat(info.label).isEqualTo("Submit whole topic");
assertThat(info.method).isEqualTo("POST"); assertThat(info.method).isEqualTo("POST");
assertThat(info.title).isEqualTo( assertThat(info.title).isEqualTo(
"Clicking the button would fail for other changes in the topic"); "Clicking the button would fail for other changes");
} else { } else {
noSubmitWholeTopicAssertions(actions); noSubmitWholeTopicAssertions(actions, 1);
} }
} }
@Test @Test
public void revisionActionsTwoChangeChangesInTopicReady() throws Exception { public void revisionActionsTwoChangesInTopicWithAncestorReady()
String changeId = createChangeWithTopic().getChangeId(); throws Exception {
String changeId = createChange().getChangeId();
approve(changeId); approve(changeId);
approve(changeId);
String changeId1 = createChangeWithTopic().getChangeId();
approve(changeId1);
// create another change with the same topic // create another change with the same topic
String changeId2 = createChangeWithTopic().getChangeId(); String changeId2 = createChangeWithTopic().getChangeId();
approve(changeId2); approve(changeId2);
Map<String, ActionInfo> actions = getActions(changeId); Map<String, ActionInfo> actions = getActions(changeId1);
commonActionsAssertions(actions); commonActionsAssertions(actions);
if (isSubmitWholeTopicEnabled()) { if (isSubmitWholeTopicEnabled()) {
ActionInfo info = actions.get("submit"); ActionInfo info = actions.get("submit");
assertThat(info.enabled).isTrue(); assertThat(info.enabled).isTrue();
assertThat(info.label).isEqualTo("Submit whole topic"); assertThat(info.label).isEqualTo("Submit whole topic");
assertThat(info.method).isEqualTo("POST"); assertThat(info.method).isEqualTo("POST");
assertThat(info.title).isEqualTo("Submit all 2 changes of the same topic"); assertThat(info.title).isEqualTo("Submit all 2 changes of the same " +
"topic (3 changes including ancestors " +
"and other changes related by topic)");
} else { } else {
noSubmitWholeTopicAssertions(actions); noSubmitWholeTopicAssertions(actions, 2);
} }
} }
private void noSubmitWholeTopicAssertions(Map<String, ActionInfo> actions) { @Test
public void revisionActionsReadyWithAncestors() throws Exception {
String changeId = createChange().getChangeId();
approve(changeId);
approve(changeId);
String changeId1 = createChange().getChangeId();
approve(changeId1);
String changeId2 = createChangeWithTopic().getChangeId();
approve(changeId2);
Map<String, ActionInfo> actions = getActions(changeId2);
commonActionsAssertions(actions);
// The topic contains only one change, so standard text applies
noSubmitWholeTopicAssertions(actions, 3);
}
private void noSubmitWholeTopicAssertions(Map<String, ActionInfo> actions,
int nrChanges) {
ActionInfo info = actions.get("submit"); ActionInfo info = actions.get("submit");
assertThat(info.enabled).isTrue(); assertThat(info.enabled).isTrue();
assertThat(info.label).isEqualTo("Submit"); assertThat(info.label).isEqualTo("Submit");
assertThat(info.method).isEqualTo("POST"); assertThat(info.method).isEqualTo("POST");
if (nrChanges == 1) {
assertThat(info.title).isEqualTo("Submit patch set 1 into master"); assertThat(info.title).isEqualTo("Submit patch set 1 into master");
} else {
assertThat(info.title).isEqualTo(String.format(
"Submit patch set 1 and ancestors (%d changes " +
"altogether) into master", nrChanges));
}
} }
private void commonActionsAssertions(Map<String, ActionInfo> actions) { private void commonActionsAssertions(Map<String, ActionInfo> actions) {

View File

@@ -46,6 +46,7 @@ import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.ChangeSet; import com.google.gerrit.server.git.ChangeSet;
import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MergeOp; import com.google.gerrit.server.git.MergeOp;
import com.google.gerrit.server.git.MergeSuperSet;
import com.google.gerrit.server.project.ChangeControl; import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException; import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.SubmitRuleEvaluator; import com.google.gerrit.server.project.SubmitRuleEvaluator;
@@ -64,8 +65,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -77,16 +76,21 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
private static final String DEFAULT_TOOLTIP = private static final String DEFAULT_TOOLTIP =
"Submit patch set ${patchSet} into ${branch}"; "Submit patch set ${patchSet} into ${branch}";
private static final String DEFAULT_TOOLTIP_ANCESTORS =
"Submit patch set ${patchSet} and ancestors (${submitSize} changes " +
"altogether) into ${branch}";
private static final String DEFAULT_TOPIC_TOOLTIP = private static final String DEFAULT_TOPIC_TOOLTIP =
"Submit all ${topicSize} changes of the same topic"; "Submit all ${topicSize} changes of the same topic " +
private static final String BLOCKED_TOPIC_TOOLTIP = "(${submitSize} changes including ancestors and other " +
"Other changes in this topic are not ready"; "changes related by topic)";
private static final String BLOCKED_HIDDEN_TOPIC_TOOLTIP = private static final String BLOCKED_SUBMIT_TOOLTIP =
"Other hidden changes in this topic are not ready"; "This change depends on other changes which are not ready";
private static final String CLICK_FAILURE_OTHER_TOOLTIP = private static final String BLOCKED_HIDDEN_SUBMIT_TOOLTIP =
"Clicking the button would fail for other changes in the topic"; "This change depends on other hidden changes which are not ready";
private static final String CLICK_FAILURE_TOOLTIP = private static final String CLICK_FAILURE_TOOLTIP =
"Clicking the button would fail."; "Clicking the button would fail";
private static final String CLICK_FAILURE_OTHER_TOOLTIP =
"Clicking the button would fail for other changes";
public static class Output { public static class Output {
transient Change change; transient Change change;
@@ -100,11 +104,14 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
private final GitRepositoryManager repoManager; private final GitRepositoryManager repoManager;
private final ChangeData.Factory changeDataFactory; private final ChangeData.Factory changeDataFactory;
private final ChangeMessagesUtil cmUtil; private final ChangeMessagesUtil cmUtil;
private final ChangeControl.GenericFactory changeControlFactory;
private final Provider<MergeOp> mergeOpProvider; private final Provider<MergeOp> mergeOpProvider;
private final MergeSuperSet mergeSuperSet;
private final AccountsCollection accounts; private final AccountsCollection accounts;
private final ChangesCollection changes; private final ChangesCollection changes;
private final String label; private final String label;
private final ParameterizedString titlePattern; private final ParameterizedString titlePattern;
private final ParameterizedString titlePatternWithAncestors;
private final String submitTopicLabel; private final String submitTopicLabel;
private final ParameterizedString submitTopicTooltip; private final ParameterizedString submitTopicTooltip;
private final boolean submitWholeTopic; private final boolean submitWholeTopic;
@@ -115,7 +122,9 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
GitRepositoryManager repoManager, GitRepositoryManager repoManager,
ChangeData.Factory changeDataFactory, ChangeData.Factory changeDataFactory,
ChangeMessagesUtil cmUtil, ChangeMessagesUtil cmUtil,
ChangeControl.GenericFactory changeControlFactory,
Provider<MergeOp> mergeOpProvider, Provider<MergeOp> mergeOpProvider,
MergeSuperSet mergeSuperSet,
AccountsCollection accounts, AccountsCollection accounts,
ChangesCollection changes, ChangesCollection changes,
@GerritServerConfig Config cfg, @GerritServerConfig Config cfg,
@@ -124,7 +133,9 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
this.repoManager = repoManager; this.repoManager = repoManager;
this.changeDataFactory = changeDataFactory; this.changeDataFactory = changeDataFactory;
this.cmUtil = cmUtil; this.cmUtil = cmUtil;
this.changeControlFactory = changeControlFactory;
this.mergeOpProvider = mergeOpProvider; this.mergeOpProvider = mergeOpProvider;
this.mergeSuperSet = mergeSuperSet;
this.accounts = accounts; this.accounts = accounts;
this.changes = changes; this.changes = changes;
this.label = MoreObjects.firstNonNull( this.label = MoreObjects.firstNonNull(
@@ -133,6 +144,10 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
this.titlePattern = new ParameterizedString(MoreObjects.firstNonNull( this.titlePattern = new ParameterizedString(MoreObjects.firstNonNull(
cfg.getString("change", null, "submitTooltip"), cfg.getString("change", null, "submitTooltip"),
DEFAULT_TOOLTIP)); DEFAULT_TOOLTIP));
this.titlePatternWithAncestors = new ParameterizedString(
MoreObjects.firstNonNull(
cfg.getString("change", null, "submitTooltipAncestors"),
DEFAULT_TOOLTIP_ANCESTORS));
submitWholeTopic = wholeTopicEnabled(cfg); submitWholeTopic = wholeTopicEnabled(cfg);
this.submitTopicLabel = MoreObjects.firstNonNull( this.submitTopicLabel = MoreObjects.firstNonNull(
Strings.emptyToNull(cfg.getString("change", null, "submitTopicLabel")), Strings.emptyToNull(cfg.getString("change", null, "submitTopicLabel")),
@@ -170,16 +185,7 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
rsrc.getPatchSet().getRevision().get())); rsrc.getPatchSet().getRevision().get()));
} }
List<Change> changes; ChangeSet submittedChanges = ChangeSet.create(change);
if (submitWholeTopic && !Strings.isNullOrEmpty(change.getTopic())) {
changes = new ArrayList<>();
for (ChangeData cd : getChangesByTopic(change.getTopic())) {
changes.add(cd.change());
}
} else {
changes = Arrays.asList(change);
}
ChangeSet submittedChanges = ChangeSet.create(changes);
try { try {
ReviewDb db = dbProvider.get(); ReviewDb db = dbProvider.get();
@@ -207,22 +213,24 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
} }
/** /**
* @param changeList list of changes to be submitted at once * @param cs set of changes to be submitted at once
* @param identifiedUser the user who is checking to submit * @param identifiedUser the user who is checking to submit
* @return a reason why any of the changes is not submittable or null * @return a reason why any of the changes is not submittable or null
*/ */
private String problemsForSubmittingChanges( private String problemsForSubmittingChangeset(
List<ChangeData> changeList, ChangeSet cs, IdentifiedUser identifiedUser) {
IdentifiedUser identifiedUser) {
try { try {
for (ChangeData c : changeList) { for (PatchSet.Id psId : cs.patchIds()) {
ChangeControl changeControl = c.changeControl().forUser( ReviewDb db = dbProvider.get();
identifiedUser); ChangeControl changeControl = changeControlFactory
.controlFor(psId.getParentKey(), identifiedUser);
ChangeData c = changeDataFactory.create(db, changeControl);
if (!changeControl.isVisible(dbProvider.get())) { if (!changeControl.isVisible(dbProvider.get())) {
return BLOCKED_HIDDEN_TOPIC_TOOLTIP; return BLOCKED_HIDDEN_SUBMIT_TOOLTIP;
} }
if (!changeControl.canSubmit()) { if (!changeControl.canSubmit()) {
return BLOCKED_TOPIC_TOOLTIP; return BLOCKED_SUBMIT_TOOLTIP;
} }
// Recheck mergeability rather than using value stored in the index, // Recheck mergeability rather than using value stored in the index,
// which may be stale. // which may be stale.
@@ -240,8 +248,8 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
checkSubmitRule(c, c.currentPatchSet(), false); checkSubmitRule(c, c.currentPatchSet(), false);
} }
} catch (ResourceConflictException e) { } catch (ResourceConflictException e) {
return BLOCKED_TOPIC_TOOLTIP; return BLOCKED_SUBMIT_TOOLTIP;
} catch (OrmException e) { } catch (NoSuchChangeException | OrmException e) {
log.error("Error checking if change is submittable", e); log.error("Error checking if change is submittable", e);
throw new OrmRuntimeException("Could not determine problems for the change", e); throw new OrmRuntimeException("Could not determine problems for the change", e);
} }
@@ -282,40 +290,55 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
throw new OrmRuntimeException("Could not determine mergeability", e); throw new OrmRuntimeException("Could not determine mergeability", e);
} }
List<ChangeData> changesByTopic = null; ChangeSet cs;
if (submitWholeTopic && !Strings.isNullOrEmpty(topic)) { try {
changesByTopic = getChangesByTopic(topic); cs = mergeSuperSet.completeChangeSet(db,
ChangeSet.create(cd.change()));
} catch (OrmException | IOException e) {
throw new OrmRuntimeException("Could not determine complete set of " +
"changes to be submitted", e);
} }
if (submitWholeTopic
int topicSize = 0;
if (!Strings.isNullOrEmpty(topic)) {
topicSize = getChangesByTopic(topic).size();
}
boolean treatWithTopic = submitWholeTopic
&& !Strings.isNullOrEmpty(topic) && !Strings.isNullOrEmpty(topic)
&& changesByTopic.size() > 1) { && topicSize > 1;
Map<String, String> params = ImmutableMap.of(
"topicSize", String.valueOf(changesByTopic.size())); String submitProblems = problemsForSubmittingChangeset(cs,
String topicProblems = problemsForSubmittingChanges(changesByTopic,
resource.getUser()); resource.getUser());
if (topicProblems != null) { if (submitProblems != null) {
return new UiAction.Description() return new UiAction.Description()
.setLabel(submitTopicLabel) .setLabel(treatWithTopic ? submitTopicLabel : label)
.setTitle(topicProblems) .setTitle(submitProblems)
.setVisible(true) .setVisible(true)
.setEnabled(false); .setEnabled(false);
} else { }
if (treatWithTopic) {
Map<String, String> params = ImmutableMap.of(
"topicSize", String.valueOf(topicSize),
"submitSize", String.valueOf(cs.size()));
return new UiAction.Description() return new UiAction.Description()
.setLabel(submitTopicLabel) .setLabel(submitTopicLabel)
.setTitle(Strings.emptyToNull( .setTitle(Strings.emptyToNull(
submitTopicTooltip.replace(params))) submitTopicTooltip.replace(params)))
.setVisible(true) .setVisible(true)
.setEnabled(Boolean.TRUE.equals(enabled)); .setEnabled(Boolean.TRUE.equals(enabled));
}
} else { } else {
RevId revId = resource.getPatchSet().getRevision(); RevId revId = resource.getPatchSet().getRevision();
Map<String, String> params = ImmutableMap.of( Map<String, String> params = ImmutableMap.of(
"patchSet", String.valueOf(resource.getPatchSet().getPatchSetId()), "patchSet", String.valueOf(resource.getPatchSet().getPatchSetId()),
"branch", resource.getChange().getDest().getShortName(), "branch", resource.getChange().getDest().getShortName(),
"commit", ObjectId.fromString(revId.get()).abbreviate(7).name()); "commit", ObjectId.fromString(revId.get()).abbreviate(7).name(),
"submitSize", String.valueOf(cs.size()));
ParameterizedString tp = cs.size() > 1 ? titlePatternWithAncestors :
titlePattern;
return new UiAction.Description() return new UiAction.Description()
.setLabel(label) .setLabel(label)
.setTitle(Strings.emptyToNull(titlePattern.replace(params))) .setTitle(Strings.emptyToNull(tp.replace(params)))
.setVisible(true) .setVisible(true)
.setEnabled(Boolean.TRUE.equals(enabled)); .setEnabled(Boolean.TRUE.equals(enabled));
} }

View File

@@ -20,6 +20,7 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.ImmutableSetMultimap;
import com.google.gerrit.reviewdb.client.Branch; import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.reviewdb.client.Project;
/** A set of changes grouped together to be submitted atomically.*/ /** A set of changes grouped together to be submitted atomically.*/
@@ -29,6 +30,7 @@ public abstract class ChangeSet {
ImmutableSet.Builder<Project.NameKey> pb = ImmutableSet.builder(); ImmutableSet.Builder<Project.NameKey> pb = ImmutableSet.builder();
ImmutableSet.Builder<Branch.NameKey> bb = ImmutableSet.builder(); ImmutableSet.Builder<Branch.NameKey> bb = ImmutableSet.builder();
ImmutableSet.Builder<Change.Id> ib = ImmutableSet.builder(); ImmutableSet.Builder<Change.Id> ib = ImmutableSet.builder();
ImmutableSet.Builder<PatchSet.Id> psb = ImmutableSet.builder();
ImmutableSetMultimap.Builder<Project.NameKey, Branch.NameKey> pbb = ImmutableSetMultimap.Builder<Project.NameKey, Branch.NameKey> pbb =
ImmutableSetMultimap.builder(); ImmutableSetMultimap.builder();
ImmutableSetMultimap.Builder<Project.NameKey, Change.Id> pcb = ImmutableSetMultimap.Builder<Project.NameKey, Change.Id> pcb =
@@ -42,13 +44,14 @@ public abstract class ChangeSet {
pb.add(project); pb.add(project);
bb.add(branch); bb.add(branch);
ib.add(c.getId()); ib.add(c.getId());
psb.add(c.currentPatchSetId());
pbb.put(project, branch); pbb.put(project, branch);
pcb.put(project, c.getId()); pcb.put(project, c.getId());
cbb.put(branch, c.getId()); cbb.put(branch, c.getId());
} }
return new AutoValue_ChangeSet(pb.build(), bb.build(), return new AutoValue_ChangeSet(pb.build(), bb.build(), ib.build(),
ib.build(), pbb.build(), pcb.build(), cbb.build()); psb.build(), pbb.build(), pcb.build(), cbb.build());
} }
public static ChangeSet create(Change change) { public static ChangeSet create(Change change) {
@@ -58,6 +61,7 @@ public abstract class ChangeSet {
public abstract ImmutableSet<Project.NameKey> projects(); public abstract ImmutableSet<Project.NameKey> projects();
public abstract ImmutableSet<Branch.NameKey> branches(); public abstract ImmutableSet<Branch.NameKey> branches();
public abstract ImmutableSet<Change.Id> ids(); public abstract ImmutableSet<Change.Id> ids();
public abstract ImmutableSet<PatchSet.Id> patchIds();
public abstract ImmutableSetMultimap<Project.NameKey, Branch.NameKey> public abstract ImmutableSetMultimap<Project.NameKey, Branch.NameKey>
branchesByProject(); branchesByProject();
public abstract ImmutableSetMultimap<Project.NameKey, Change.Id> public abstract ImmutableSetMultimap<Project.NameKey, Change.Id>
@@ -69,4 +73,8 @@ public abstract class ChangeSet {
public int hashCode() { public int hashCode() {
return ids().hashCode(); return ids().hashCode();
} }
public int size() {
return ids().size();
}
} }

View File

@@ -237,7 +237,9 @@ public class MergeOp {
return ImmutableList.of(ok.get()); return ImmutableList.of(ok.get());
} else if (results.isEmpty()) { } else if (results.isEmpty()) {
throw new IllegalStateException(String.format( throw new IllegalStateException(String.format(
"SubmitRuleEvaluator.evaluate returned empty list for %s in %s", "SubmitRuleEvaluator.evaluate for change %s " +
"returned empty list for %s in %s",
cd.getId(),
patchSet.getId(), patchSet.getId(),
cd.change().getProject().get())); cd.change().getProject().get()));
} }
@@ -245,15 +247,17 @@ public class MergeOp {
for (SubmitRecord record : results) { for (SubmitRecord record : results) {
switch (record.status) { switch (record.status) {
case CLOSED: case CLOSED:
throw new ResourceConflictException("change is closed"); throw new ResourceConflictException(String.format(
"change %s is closed", cd.getId()));
case RULE_ERROR: case RULE_ERROR:
throw new ResourceConflictException(String.format( throw new ResourceConflictException(String.format(
"rule error: %s", "rule error for change %s: %s",
record.errorMessage)); cd.getId(), record.errorMessage));
case NOT_READY: case NOT_READY:
StringBuilder msg = new StringBuilder(); StringBuilder msg = new StringBuilder();
msg.append(cd.getId() + ":");
for (SubmitRecord.Label lbl : record.labels) { for (SubmitRecord.Label lbl : record.labels) {
switch (lbl.status) { switch (lbl.status) {
case OK: case OK:
@@ -261,32 +265,27 @@ public class MergeOp {
continue; continue;
case REJECT: case REJECT:
if (msg.length() > 0) {
msg.append("; ");
}
msg.append(" blocked by ").append(lbl.label); msg.append(" blocked by ").append(lbl.label);
msg.append(";");
continue; continue;
case NEED: case NEED:
if (msg.length() > 0) {
msg.append("; ");
}
msg.append(" needs ").append(lbl.label); msg.append(" needs ").append(lbl.label);
msg.append(";");
continue; continue;
case IMPOSSIBLE: case IMPOSSIBLE:
if (msg.length() > 0) {
msg.append("; ");
}
msg.append(" needs ").append(lbl.label) msg.append(" needs ").append(lbl.label)
.append(" (check project access)"); .append(" (check project access)");
msg.append(";");
continue; continue;
default: default:
throw new IllegalStateException(String.format( throw new IllegalStateException(String.format(
"Unsupported SubmitRecord.Label %s for %s in %s", "Unsupported SubmitRecord.Label %s for %s in %s in %s",
lbl.toString(), lbl.toString(),
patchSet.getId(), patchSet.getId(),
cd.getId(),
cd.change().getProject().get())); cd.change().getProject().get()));
} }
} }
@@ -303,21 +302,37 @@ public class MergeOp {
throw new IllegalStateException(); throw new IllegalStateException();
} }
private void checkPermissions(ChangeSet cs) private void checkSubmitRulesAndState(ChangeSet cs)
throws ResourceConflictException, OrmException { throws ResourceConflictException, OrmException {
StringBuilder msgbuf = new StringBuilder();
List<Change.Id> problemChanges = new ArrayList<>();
for (Change.Id id : cs.ids()) { for (Change.Id id : cs.ids()) {
try {
ChangeData cd = changeDataFactory.create(db, id); ChangeData cd = changeDataFactory.create(db, id);
if (cd.change().getStatus() != Change.Status.NEW){ if (cd.change().getStatus() != Change.Status.NEW){
throw new OrmException("Change " + cd.change().getChangeId() throw new ResourceConflictException("Change " +
+ " is in state " + cd.change().getStatus()); cd.change().getChangeId() + " is in state " +
cd.change().getStatus());
} else { } else {
records.put(cd.change().getId(), checkSubmitRule(cd)); records.put(cd.change().getId(), checkSubmitRule(cd));
} }
} catch (ResourceConflictException e) {
msgbuf.append(e.getMessage() + "\n");
problemChanges.add(id);
}
}
String reason = msgbuf.toString();
if (!reason.isEmpty()) {
throw new ResourceConflictException("The change could not be " +
"submitted because it depends on change(s) " +
problemChanges.toString() + ", which could not be submitted " +
"because:\n" + reason);
} }
} }
public void merge(ReviewDb db, ChangeSet changes, IdentifiedUser caller, public void merge(ReviewDb db, ChangeSet changes, IdentifiedUser caller,
boolean checkPermissions) throws NoSuchChangeException, boolean checkSubmitRules) throws NoSuchChangeException,
OrmException, ResourceConflictException { OrmException, ResourceConflictException {
logPrefix = String.format("[%s]: ", String.valueOf(changes.hashCode())); logPrefix = String.format("[%s]: ", String.valueOf(changes.hashCode()));
this.db = db; this.db = db;
@@ -325,9 +340,9 @@ public class MergeOp {
try { try {
ChangeSet cs = mergeSuperSet.completeChangeSet(db, changes); ChangeSet cs = mergeSuperSet.completeChangeSet(db, changes);
logDebug("Calculated to merge {}", cs); logDebug("Calculated to merge {}", cs);
if (checkPermissions) { if (checkSubmitRules) {
logDebug("Checking permissions"); logDebug("Checking submit rules and state");
checkPermissions(cs); checkSubmitRulesAndState(cs);
} }
try { try {
integrateIntoHistory(cs, caller); integrateIntoHistory(cs, caller);