Submit: Include ancestors into problem description

When the merge queue was killed, the submission model was changed to
include the ancestors of the set of changes to be submitted, which
consisted only of the changes in the same topic. Now the set to be
submitted consists of changes in the same topic and all its ancestors.
If those ancestors have a topic, they will aggregate further changes with
their topic recursively.

Currently the model for generating the description and title for the
submit button still follow the old model, so it is easy to come up with
a situation where the change screen makes a user believe a change
can be submitted just fine, but it cannot, which is revealed during the
merge process.

Align the model for generating the submit button tooltip with the
actual model used during merge.

Change-Id: I41c3c4445ed8f4707b3aacb6745a23f435c7e943
This commit is contained in:
Stefan Beller
2015-07-20 21:14:38 -07:00
parent e71ed6fcde
commit 0e655543a9
4 changed files with 142 additions and 68 deletions

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.GitRepositoryManager;
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.NoSuchChangeException;
import com.google.gerrit.server.project.SubmitRuleEvaluator;
@@ -64,8 +65,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -77,16 +76,21 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
private static final String DEFAULT_TOOLTIP =
"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 =
"Submit all ${topicSize} changes of the same topic";
private static final String BLOCKED_TOPIC_TOOLTIP =
"Other changes in this topic are not ready";
private static final String BLOCKED_HIDDEN_TOPIC_TOOLTIP =
"Other hidden changes in this topic are not ready";
private static final String CLICK_FAILURE_OTHER_TOOLTIP =
"Clicking the button would fail for other changes in the topic";
"Submit all ${topicSize} changes of the same topic " +
"(${submitSize} changes including ancestors and other " +
"changes related by topic)";
private static final String BLOCKED_SUBMIT_TOOLTIP =
"This change depends on other changes which are not ready";
private static final String BLOCKED_HIDDEN_SUBMIT_TOOLTIP =
"This change depends on other hidden changes which are not ready";
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 {
transient Change change;
@@ -100,11 +104,14 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
private final GitRepositoryManager repoManager;
private final ChangeData.Factory changeDataFactory;
private final ChangeMessagesUtil cmUtil;
private final ChangeControl.GenericFactory changeControlFactory;
private final Provider<MergeOp> mergeOpProvider;
private final MergeSuperSet mergeSuperSet;
private final AccountsCollection accounts;
private final ChangesCollection changes;
private final String label;
private final ParameterizedString titlePattern;
private final ParameterizedString titlePatternWithAncestors;
private final String submitTopicLabel;
private final ParameterizedString submitTopicTooltip;
private final boolean submitWholeTopic;
@@ -115,7 +122,9 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
GitRepositoryManager repoManager,
ChangeData.Factory changeDataFactory,
ChangeMessagesUtil cmUtil,
ChangeControl.GenericFactory changeControlFactory,
Provider<MergeOp> mergeOpProvider,
MergeSuperSet mergeSuperSet,
AccountsCollection accounts,
ChangesCollection changes,
@GerritServerConfig Config cfg,
@@ -124,7 +133,9 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
this.repoManager = repoManager;
this.changeDataFactory = changeDataFactory;
this.cmUtil = cmUtil;
this.changeControlFactory = changeControlFactory;
this.mergeOpProvider = mergeOpProvider;
this.mergeSuperSet = mergeSuperSet;
this.accounts = accounts;
this.changes = changes;
this.label = MoreObjects.firstNonNull(
@@ -133,6 +144,10 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
this.titlePattern = new ParameterizedString(MoreObjects.firstNonNull(
cfg.getString("change", null, "submitTooltip"),
DEFAULT_TOOLTIP));
this.titlePatternWithAncestors = new ParameterizedString(
MoreObjects.firstNonNull(
cfg.getString("change", null, "submitTooltipAncestors"),
DEFAULT_TOOLTIP_ANCESTORS));
submitWholeTopic = wholeTopicEnabled(cfg);
this.submitTopicLabel = MoreObjects.firstNonNull(
Strings.emptyToNull(cfg.getString("change", null, "submitTopicLabel")),
@@ -170,16 +185,7 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
rsrc.getPatchSet().getRevision().get()));
}
List<Change> changes;
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);
ChangeSet submittedChanges = ChangeSet.create(change);
try {
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
* @return a reason why any of the changes is not submittable or null
*/
private String problemsForSubmittingChanges(
List<ChangeData> changeList,
IdentifiedUser identifiedUser) {
private String problemsForSubmittingChangeset(
ChangeSet cs, IdentifiedUser identifiedUser) {
try {
for (ChangeData c : changeList) {
ChangeControl changeControl = c.changeControl().forUser(
identifiedUser);
for (PatchSet.Id psId : cs.patchIds()) {
ReviewDb db = dbProvider.get();
ChangeControl changeControl = changeControlFactory
.controlFor(psId.getParentKey(), identifiedUser);
ChangeData c = changeDataFactory.create(db, changeControl);
if (!changeControl.isVisible(dbProvider.get())) {
return BLOCKED_HIDDEN_TOPIC_TOOLTIP;
return BLOCKED_HIDDEN_SUBMIT_TOOLTIP;
}
if (!changeControl.canSubmit()) {
return BLOCKED_TOPIC_TOOLTIP;
return BLOCKED_SUBMIT_TOOLTIP;
}
// Recheck mergeability rather than using value stored in the index,
// which may be stale.
@@ -240,8 +248,8 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
checkSubmitRule(c, c.currentPatchSet(), false);
}
} catch (ResourceConflictException e) {
return BLOCKED_TOPIC_TOOLTIP;
} catch (OrmException e) {
return BLOCKED_SUBMIT_TOOLTIP;
} catch (NoSuchChangeException | OrmException e) {
log.error("Error checking if change is submittable", 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);
}
List<ChangeData> changesByTopic = null;
if (submitWholeTopic && !Strings.isNullOrEmpty(topic)) {
changesByTopic = getChangesByTopic(topic);
ChangeSet cs;
try {
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)
&& changesByTopic.size() > 1) {
&& topicSize > 1;
String submitProblems = problemsForSubmittingChangeset(cs,
resource.getUser());
if (submitProblems != null) {
return new UiAction.Description()
.setLabel(treatWithTopic ? submitTopicLabel : label)
.setTitle(submitProblems)
.setVisible(true)
.setEnabled(false);
}
if (treatWithTopic) {
Map<String, String> params = ImmutableMap.of(
"topicSize", String.valueOf(changesByTopic.size()));
String topicProblems = problemsForSubmittingChanges(changesByTopic,
resource.getUser());
if (topicProblems != null) {
return new UiAction.Description()
.setLabel(submitTopicLabel)
.setTitle(topicProblems)
.setVisible(true)
.setEnabled(false);
} else {
return new UiAction.Description()
"topicSize", String.valueOf(topicSize),
"submitSize", String.valueOf(cs.size()));
return new UiAction.Description()
.setLabel(submitTopicLabel)
.setTitle(Strings.emptyToNull(
submitTopicTooltip.replace(params)))
.setVisible(true)
.setEnabled(Boolean.TRUE.equals(enabled));
}
} else {
RevId revId = resource.getPatchSet().getRevision();
Map<String, String> params = ImmutableMap.of(
"patchSet", String.valueOf(resource.getPatchSet().getPatchSetId()),
"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()
.setLabel(label)
.setTitle(Strings.emptyToNull(titlePattern.replace(params)))
.setTitle(Strings.emptyToNull(tp.replace(params)))
.setVisible(true)
.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.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
/** 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<Branch.NameKey> bb = 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();
ImmutableSetMultimap.Builder<Project.NameKey, Change.Id> pcb =
@@ -42,13 +44,14 @@ public abstract class ChangeSet {
pb.add(project);
bb.add(branch);
ib.add(c.getId());
psb.add(c.currentPatchSetId());
pbb.put(project, branch);
pcb.put(project, c.getId());
cbb.put(branch, c.getId());
}
return new AutoValue_ChangeSet(pb.build(), bb.build(),
ib.build(), pbb.build(), pcb.build(), cbb.build());
return new AutoValue_ChangeSet(pb.build(), bb.build(), ib.build(),
psb.build(), pbb.build(), pcb.build(), cbb.build());
}
public static ChangeSet create(Change change) {
@@ -58,6 +61,7 @@ public abstract class ChangeSet {
public abstract ImmutableSet<Project.NameKey> projects();
public abstract ImmutableSet<Branch.NameKey> branches();
public abstract ImmutableSet<Change.Id> ids();
public abstract ImmutableSet<PatchSet.Id> patchIds();
public abstract ImmutableSetMultimap<Project.NameKey, Branch.NameKey>
branchesByProject();
public abstract ImmutableSetMultimap<Project.NameKey, Change.Id>
@@ -69,4 +73,8 @@ public abstract class ChangeSet {
public int hashCode() {
return ids().hashCode();
}
public int size() {
return ids().size();
}
}