Merge changes from topic 'mergeop-refactor'

* changes:
  MergeOp: Throw more descriptive ResourceConflictExceptions
  MergeOp: Get rid of ChangeData.Factory
  MergeOp: Cache SubmitRecords in ChangeData
  MergeOp: Move destProject into OpenRepo
  MergeOp: Cache open branches within open repos
This commit is contained in:
Edwin Kempin
2015-12-22 10:04:49 +00:00
committed by Gerrit Code Review
3 changed files with 260 additions and 252 deletions

View File

@@ -34,7 +34,6 @@ import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.git.IntegrationException;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.VersionedMetaData;
import com.google.gerrit.testutil.ConfigSuite;
@@ -237,13 +236,11 @@ public class SubmitTypeRuleIT extends AbstractDaemonTest {
gApi.changes().id(r2.getChangeId()).current().submit();
fail("Expected ResourceConflictException");
} catch (ResourceConflictException e) {
assertThat(e).hasMessage("Merge Conflict");
Throwable t = e.getCause();
assertThat(t).isInstanceOf(IntegrationException.class);
assertThat(t.getMessage()).isEqualTo(
"Change " + r1.getChange().getId() + " has submit type CHERRY_PICK, "
+ "but previously chose submit type MERGE_IF_NECESSARY from change "
+ r2.getChange().getId() + " in the same batch");
assertThat(e).hasMessage(
"Failed to submit 2 changes due to the following problems:\n"
+ "Change " + r1.getChange().getId() + ": Change has submit type "
+ "CHERRY_PICK, but previously chose submit type MERGE_IF_NECESSARY "
+ "from change " + r2.getChange().getId() + " in the same batch");
}
}

View File

@@ -73,9 +73,8 @@ public class SubmitByFastForwardIT extends AbstractSubmit {
Change.Id id1 = change1.getPatchSetId().getParentKey();
submitWithConflict(change2.getChangeId(),
"The change could not be submitted because it depends on change(s) [" +
id1 + "], which could not be submitted because:\n" +
id1 + ": needs Code-Review;");
"Failed to submit 2 changes due to the following problems:\n"
+ "Change " + id1 + ": needs Code-Review");
RevCommit head = getRemoteHead();
assertThat(head.getId()).isEqualTo(oldHead.getId());

View File

@@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkState;
import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
import com.google.auto.value.AutoValue;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.HashBasedTable;
@@ -26,6 +27,8 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
import com.google.common.collect.Sets;
import com.google.common.collect.Table;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
@@ -127,9 +130,13 @@ public class MergeOp implements AutoCloseable {
final CodeReviewRevWalk rw;
final RevFlag canMergeFlag;
final ObjectInserter ins;
ProjectState project;
OpenRepo(Repository repo) {
private final Map<Branch.NameKey, OpenBranch> branches;
OpenRepo(Repository repo, ProjectState project) {
this.repo = repo;
this.project = project;
rw = CodeReviewCommit.newRevWalk(repo);
rw.sort(RevSort.TOPO);
rw.sort(RevSort.COMMIT_TIME_DESC, true);
@@ -137,6 +144,20 @@ public class MergeOp implements AutoCloseable {
canMergeFlag = rw.newFlag("CAN_MERGE");
ins = repo.newObjectInserter();
branches = Maps.newHashMapWithExpectedSize(1);
}
OpenBranch getBranch(Branch.NameKey branch) throws IntegrationException {
OpenBranch ob = branches.get(branch);
if (ob == null) {
ob = new OpenBranch(this, branch);
branches.put(branch, ob);
}
return ob;
}
Project.NameKey getProjectName() {
return project.getProject().getNameKey();
}
void close() {
@@ -146,10 +167,38 @@ public class MergeOp implements AutoCloseable {
}
}
private static class OpenBranch {
final Branch.NameKey name;
final RefUpdate update;
final CodeReviewCommit oldTip;
MergeTip mergeTip;
OpenBranch(OpenRepo or, Branch.NameKey name) throws IntegrationException {
this.name = name;
try {
update = or.repo.updateRef(name.get());
if (update.getOldObjectId() != null) {
oldTip = or.rw.parseCommit(update.getOldObjectId());
} else if (Objects.equals(or.repo.getFullBranch(), name.get())) {
oldTip = null;
update.setExpectedOldObjectId(ObjectId.zeroId());
} else {
throw new IntegrationException("The destination branch "
+ name + " does not exist anymore.");
}
} catch (IOException e) {
throw new IntegrationException("Cannot open branch " + name, e);
}
}
CodeReviewCommit getCurrentTip() {
return mergeTip != null ? mergeTip.getCurrentTip() : oldTip;
}
}
private final AccountCache accountCache;
private final ApprovalsUtil approvalsUtil;
private final ChangeControl.GenericFactory changeControlFactory;
private final ChangeData.Factory changeDataFactory;
private final ChangeHooks hooks;
private final ChangeIndexer indexer;
private final ChangeMessagesUtil cmUtil;
@@ -170,8 +219,8 @@ public class MergeOp implements AutoCloseable {
private final TagCache tagCache;
private final Map<Project.NameKey, OpenRepo> openRepos;
private final Map<Change.Id, List<SubmitRecord>> records;
private final Map<Change.Id, CodeReviewCommit> commits;
private final Multimap<Change.Id, String> problems;
private static final String MACHINE_ID;
static {
@@ -186,17 +235,12 @@ public class MergeOp implements AutoCloseable {
private String staticSubmissionId;
private String submissionId;
private ProjectState destProject;
private ReviewDb db;
private Map<Branch.NameKey, RefUpdate> pendingRefUpdates;
private Map<Branch.NameKey, CodeReviewCommit> openBranches;
private Map<Branch.NameKey, MergeTip> mergeTips;
@Inject
MergeOp(AccountCache accountCache,
ApprovalsUtil approvalsUtil,
ChangeControl.GenericFactory changeControlFactory,
ChangeData.Factory changeDataFactory,
ChangeHooks hooks,
ChangeIndexer indexer,
ChangeMessagesUtil cmUtil,
@@ -218,7 +262,6 @@ public class MergeOp implements AutoCloseable {
this.accountCache = accountCache;
this.approvalsUtil = approvalsUtil;
this.changeControlFactory = changeControlFactory;
this.changeDataFactory = changeDataFactory;
this.hooks = hooks;
this.indexer = indexer;
this.cmUtil = cmUtil;
@@ -239,20 +282,20 @@ public class MergeOp implements AutoCloseable {
this.tagCache = tagCache;
commits = new HashMap<>();
pendingRefUpdates = new HashMap<>();
openBranches = new HashMap<>();
pendingRefUpdates = new HashMap<>();
openRepos = new HashMap<>();
records = new HashMap<>();
mergeTips = new HashMap<>();
problems = MultimapBuilder.linkedHashKeys().arrayListValues(1).build();
}
private OpenRepo openRepo(Project.NameKey project)
throws NoSuchProjectException, IOException {
OpenRepo repo = openRepos.get(project);
if (repo == null) {
ProjectState projectState = projectCache.get(project);
if (projectState == null) {
throw new NoSuchProjectException(project);
}
try {
repo = new OpenRepo(repoManager.openRepository(project));
repo = new OpenRepo(repoManager.openRepository(project), projectState);
} catch (RepositoryNotFoundException e) {
throw new NoSuchProjectException(project);
}
@@ -268,16 +311,11 @@ public class MergeOp implements AutoCloseable {
}
}
private void setDestProject(Branch.NameKey destBranch)
throws IntegrationException {
destProject = projectCache.get(destBranch.getParentKey());
if (destProject == null) {
throw new IntegrationException(
"No such project: " + destBranch.getParentKey());
private static Optional<SubmitRecord> findOkRecord(
Collection<SubmitRecord> in) {
if (in == null) {
return Optional.absent();
}
}
private static Optional<SubmitRecord> findOkRecord(Collection<SubmitRecord> in) {
return Iterables.tryFind(in, new Predicate<SubmitRecord>() {
@Override
public boolean apply(SubmitRecord input) {
@@ -286,20 +324,23 @@ public class MergeOp implements AutoCloseable {
});
}
public static List<SubmitRecord> checkSubmitRule(ChangeData cd)
public static void checkSubmitRule(ChangeData cd)
throws ResourceConflictException, OrmException {
PatchSet patchSet = cd.currentPatchSet();
if (patchSet == null) {
throw new ResourceConflictException(
"missing current patch set for change " + cd.getId());
}
List<SubmitRecord> results = new SubmitRuleEvaluator(cd)
.setPatchSet(patchSet)
.evaluate();
Optional<SubmitRecord> ok = findOkRecord(results);
if (ok.isPresent()) {
List<SubmitRecord> results = cd.getSubmitRecords();
if (results == null) {
results = new SubmitRuleEvaluator(cd)
.setPatchSet(patchSet)
.evaluate();
cd.setSubmitRecords(results);
}
if (findOkRecord(results).isPresent()) {
// Rules supplied a valid solution.
return ImmutableList.of(ok.get());
return;
} else if (results.isEmpty()) {
throw new IllegalStateException(String.format(
"SubmitRuleEvaluator.evaluate for change %s " +
@@ -312,49 +353,15 @@ public class MergeOp implements AutoCloseable {
for (SubmitRecord record : results) {
switch (record.status) {
case CLOSED:
throw new ResourceConflictException(String.format(
"change %s is closed", cd.getId()));
throw new ResourceConflictException("change is closed");
case RULE_ERROR:
throw new ResourceConflictException(String.format(
"rule error for change %s: %s",
cd.getId(), record.errorMessage));
throw new ResourceConflictException(
"submit rule error: " + record.errorMessage);
case NOT_READY:
StringBuilder msg = new StringBuilder();
msg.append(cd.getId() + ":");
for (SubmitRecord.Label lbl : record.labels) {
switch (lbl.status) {
case OK:
case MAY:
continue;
case REJECT:
msg.append(" blocked by ").append(lbl.label);
msg.append(";");
continue;
case NEED:
msg.append(" needs ").append(lbl.label);
msg.append(";");
continue;
case IMPOSSIBLE:
msg.append(" needs ").append(lbl.label)
.append(" (check project access)");
msg.append(";");
continue;
default:
throw new IllegalStateException(String.format(
"Unsupported SubmitRecord.Label %s for %s in %s in %s",
lbl.toString(),
patchSet.getId(),
cd.getId(),
cd.change().getProject().get()));
}
}
throw new ResourceConflictException(msg.toString());
throw new ResourceConflictException(
describeLabels(cd, record.labels));
default:
throw new IllegalStateException(String.format(
@@ -367,32 +374,55 @@ public class MergeOp implements AutoCloseable {
throw new IllegalStateException();
}
private void checkSubmitRulesAndState(ChangeSet cs)
throws ResourceConflictException, OrmException {
private static String describeLabels(ChangeData cd,
List<SubmitRecord.Label> labels) throws OrmException {
List<String> labelResults = new ArrayList<>();
for (SubmitRecord.Label lbl : labels) {
switch (lbl.status) {
case OK:
case MAY:
break;
StringBuilder msgbuf = new StringBuilder();
List<Change.Id> problemChanges = new ArrayList<>();
for (Change.Id id : cs.ids()) {
try {
ChangeData cd = changeDataFactory.create(db, id);
if (cd.change().getStatus() != Change.Status.NEW){
throw new ResourceConflictException("Change " +
cd.change().getChangeId() + " is in state " +
cd.change().getStatus());
} else {
records.put(cd.change().getId(), checkSubmitRule(cd));
}
} catch (ResourceConflictException e) {
msgbuf.append(e.getMessage() + "\n");
problemChanges.add(id);
case REJECT:
labelResults.add("blocked by " + lbl.label);
break;
case NEED:
labelResults.add("needs " + lbl.label);
break;
case IMPOSSIBLE:
labelResults.add(
"needs " + lbl.label + " (check project access)");
break;
default:
throw new IllegalStateException(String.format(
"Unsupported SubmitRecord.Label %s for %s in %s",
lbl,
cd.change().currentPatchSetId(),
cd.change().getProject()));
}
}
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);
return Joiner.on("; ").join(labelResults);
}
private void checkSubmitRulesAndState(ChangeSet cs) {
for (ChangeData cd : cs.changes()) {
try {
if (cd.change().getStatus() != Change.Status.NEW) {
problems.put(cd.getId(), "Change " + cd.getId() + " is "
+ cd.change().getStatus().toString().toLowerCase());
} else {
checkSubmitRule(cd);
}
} catch (ResourceConflictException e) {
problems.put(cd.getId(), e.getMessage());
} catch (OrmException e) {
String msg = "Error checking submit rules for change";
log.warn(msg + " " + cd.getId(), e);
problems.put(cd.getId(), msg);
}
}
}
@@ -413,10 +443,12 @@ public class MergeOp implements AutoCloseable {
logDebug("Beginning integration of {}", change);
try {
ChangeSet cs = mergeSuperSet.completeChangeSet(db, change);
reloadChanges(cs);
logDebug("Calculated to merge {}", cs);
if (checkSubmitRules) {
logDebug("Checking submit rules and state");
checkSubmitRulesAndState(cs);
failFast(cs); // Done checks that don't involve opening repo.
}
try {
integrateIntoHistory(cs, caller);
@@ -430,6 +462,26 @@ public class MergeOp implements AutoCloseable {
}
}
private static void reloadChanges(ChangeSet cs) throws OrmException {
// Reload changes in case index was stale.
for (ChangeData cd : cs.changes()) {
cd.reloadChange();
}
}
private void failFast(ChangeSet cs) throws ResourceConflictException {
if (problems.isEmpty()) {
return;
}
String msg = "Failed to submit " + cs.size() + " change"
+ (cs.size() > 1 ? "s" : "") + " due to the following problems:\n";
List<String> ps = new ArrayList<>(problems.keySet().size());
for (Change.Id id : problems.keySet()) {
ps.add("Change " + id + ": " + Joiner.on("; ").join(problems.get(id)));
}
throw new ResourceConflictException(msg + Joiner.on('\n').join(ps));
}
private void integrateIntoHistory(ChangeSet cs, IdentifiedUser caller)
throws IntegrationException, NoSuchChangeException,
ResourceConflictException {
@@ -439,43 +491,49 @@ public class MergeOp implements AutoCloseable {
try {
Multimap<Project.NameKey, Branch.NameKey> br = cs.branchesByProject();
Multimap<Branch.NameKey, ChangeData> cbb = cs.changesByBranch();
for (Branch.NameKey branch : cbb.keySet()) {
OpenRepo or = openRepo(branch.getParentKey());
toSubmit.put(branch, validateChangeList(or, cbb.get(branch)));
}
failFast(cs); // Done checks that don't involve running submit strategies.
for (Project.NameKey project : br.keySet()) {
OpenRepo or = openRepo(project);
for (Branch.NameKey branch : br.get(project)) {
setDestProject(branch);
BranchBatch submitting = validateChangeList(or, cbb.get(branch));
toSubmit.put(branch, submitting);
OpenBranch ob = or.getBranch(branch);
BranchBatch submitting = toSubmit.get(branch);
SubmitStrategy strategy = createStrategy(or, branch,
submitting.submitType(), getBranchTip(or, branch), caller);
MergeTip mergeTip = preMerge(strategy, submitting.changes(),
getBranchTip(or, branch));
mergeTips.put(branch, mergeTip);
updateChangeStatus(submitting.changes(), branch, true, caller);
submitting.submitType(), ob.oldTip, caller);
ob.mergeTip = preMerge(strategy, submitting.changes(), ob.oldTip);
updateChangeStatus(ob, submitting.changes(), true, caller);
or.ins.flush();
}
}
Set<Branch.NameKey> done =
Sets.newHashSetWithExpectedSize(cbb.keySet().size());
logDebug("Write out the new branch tips");
SubmoduleOp subOp = subOpProvider.get();
for (Project.NameKey project : br.keySet()) {
OpenRepo or = openRepo(project);
for (Branch.NameKey branch : br.get(project)) {
RefUpdate update = updateBranch(or, branch, caller);
pendingRefUpdates.remove(branch);
OpenBranch ob = or.getBranch(branch);
boolean updated = updateBranch(or, branch, caller);
setDestProject(branch);
BranchBatch submitting = toSubmit.get(branch);
updateChangeStatus(submitting.changes(), branch, false, caller);
updateSubmoduleSubscriptions(subOp, branch, getBranchTip(or, branch));
if (update != null) {
fireRefUpdated(branch, update);
updateChangeStatus(ob, submitting.changes(), false, caller);
updateSubmoduleSubscriptions(ob, subOp);
if (updated) {
fireRefUpdated(ob);
}
done.add(branch);
}
}
updateSuperProjects(subOp, br.values());
checkState(pendingRefUpdates.isEmpty(), "programmer error: "
+ "pending ref update list not emptied");
checkState(done.equals(cbb.keySet()), "programmer error: did not process"
+ " all branches in input set.\nExpected: %s\nActual: %s",
done, cbb.keySet());
} catch (NoSuchProjectException noProject) {
logWarn("Project " + noProject.project() + " no longer exists, "
+ "abandoning open changes");
@@ -513,47 +571,6 @@ public class MergeOp implements AutoCloseable {
or.canMergeFlag, getAlreadyAccepted(or, branchTip), destBranch, caller);
}
private RefUpdate getPendingRefUpdate(OpenRepo repo,
Branch.NameKey destBranch) throws IntegrationException {
if (pendingRefUpdates.containsKey(destBranch)) {
logDebug("Access cached open branch {}: {}", destBranch.get(),
openBranches.get(destBranch));
return pendingRefUpdates.get(destBranch);
}
try {
RefUpdate branchUpdate = repo.repo.updateRef(destBranch.get());
CodeReviewCommit branchTip;
if (branchUpdate.getOldObjectId() != null) {
branchTip = repo.rw.parseCommit(branchUpdate.getOldObjectId());
} else if (Objects.equals(repo.repo.getFullBranch(), destBranch.get())) {
branchTip = null;
branchUpdate.setExpectedOldObjectId(ObjectId.zeroId());
} else {
throw new IntegrationException("The destination branch "
+ destBranch.get() + " does not exist anymore.");
}
logDebug("Opened branch {}: {}", destBranch.get(), branchTip);
pendingRefUpdates.put(destBranch, branchUpdate);
openBranches.put(destBranch, branchTip);
return branchUpdate;
} catch (IOException e) {
throw new IntegrationException("Cannot open branch", e);
}
}
private CodeReviewCommit getBranchTip(OpenRepo repo,
Branch.NameKey destBranch) throws IntegrationException {
if (openBranches.containsKey(destBranch)) {
return openBranches.get(destBranch);
} else {
getPendingRefUpdate(repo, destBranch);
return openBranches.get(destBranch);
}
}
private Set<RevCommit> getAlreadyAccepted(OpenRepo or,
CodeReviewCommit branchTip) throws IntegrationException {
Set<RevCommit> alreadyAccepted = new HashSet<>();
@@ -586,6 +603,17 @@ public class MergeOp implements AutoCloseable {
abstract List<ChangeData> changes();
}
private void logProblem(Change.Id id, Throwable t) {
String msg = "Error reading change";
log.error(msg + " " + id, t);
problems.put(id, msg);
}
private void logProblem(Change.Id id, String msg) {
log.error(msg + " " + id);
problems.put(id, msg);
}
private BranchBatch validateChangeList(OpenRepo or,
Collection<ChangeData> submitted) throws IntegrationException {
logDebug("Validating {} changes", submitted.size());
@@ -595,22 +623,20 @@ public class MergeOp implements AutoCloseable {
SubmitType submitType = null;
ChangeData choseSubmitTypeFrom = null;
for (ChangeData cd : submitted) {
Change.Id changeId = cd.getId();
ChangeControl ctl;
Change chg;
try {
ctl = cd.changeControl();
chg = cd.change();
} catch (OrmException e) {
throw new IntegrationException("Failed to validate changes", e);
}
Change.Id changeId = cd.getId();
if (chg.getStatus() != Change.Status.NEW) {
logDebug("Change {} is not new: {}", changeId, chg.getStatus());
logProblem(changeId, e);
continue;
}
if (chg.currentPatchSetId() == null) {
logError("Missing current patch set on change " + changeId);
commits.put(changeId, CodeReviewCommit.noPatchSet(ctl));
String msg = "Missing current patch set on change";
logError(msg + " " + changeId);
problems.put(changeId, msg);
continue;
}
@@ -619,12 +645,12 @@ public class MergeOp implements AutoCloseable {
try {
ps = cd.currentPatchSet();
} catch (OrmException e) {
throw new IntegrationException("Cannot query the database", e);
logProblem(changeId, e);
continue;
}
if (ps == null || ps.getRevision() == null
|| ps.getRevision().get() == null) {
logError("Missing patch set or revision on change " + changeId);
commits.put(changeId, CodeReviewCommit.noPatchSet(ctl));
logProblem(changeId, "Missing patch set or revision on change");
continue;
}
@@ -632,9 +658,8 @@ public class MergeOp implements AutoCloseable {
ObjectId id;
try {
id = ObjectId.fromString(idstr);
} catch (IllegalArgumentException iae) {
logError("Invalid revision on patch set " + ps.getId());
commits.put(changeId, CodeReviewCommit.noPatchSet(ctl));
} catch (IllegalArgumentException e) {
logProblem(changeId, e);
continue;
}
@@ -643,9 +668,9 @@ public class MergeOp implements AutoCloseable {
// want to merge the issue. We can't safely do that if the
// tip is not reachable.
//
logError("Revision " + idstr + " of patch set " + ps.getId()
+ " does not match " + ps.getId().toRefName());
commits.put(changeId, CodeReviewCommit.revisionGone(ctl));
logProblem(changeId, "Revision " + idstr + " of patch set "
+ ps.getPatchSetId() + " does not match " + ps.getId().toRefName()
+ " for change");
continue;
}
@@ -653,8 +678,7 @@ public class MergeOp implements AutoCloseable {
try {
commit = or.rw.parseCommit(id);
} catch (IOException e) {
logError("Invalid commit " + idstr + " on patch set " + ps.getId(), e);
commits.put(changeId, CodeReviewCommit.revisionGone(ctl));
logProblem(changeId, e);
continue;
}
@@ -666,29 +690,27 @@ public class MergeOp implements AutoCloseable {
MergeValidators mergeValidators = mergeValidatorsFactory.create();
try {
mergeValidators.validatePreMerge(
or.repo, commit, destProject, destBranch, ps.getId());
or.repo, commit, or.project, destBranch, ps.getId());
} catch (MergeValidationException mve) {
logDebug("Revision {} of patch set {} failed validation: {}",
idstr, ps.getId(), mve.getStatus());
commit.setStatusCode(mve.getStatus());
problems.put(changeId, mve.getStatus().toString());
continue;
}
SubmitType st = getSubmitType(commit.getControl(), ps);
SubmitType st = getSubmitType(cd, ps);
if (st == null) {
logError("No submit type for revision " + idstr + " of patch set "
+ ps.getId());
throw new IntegrationException(
"No submit type for change " + cd.getId());
logProblem(changeId, "No submit type for change");
continue;
}
if (submitType == null) {
submitType = st;
choseSubmitTypeFrom = cd;
} else if (st != submitType) {
throw new IntegrationException(String.format(
"Change %s has submit type %s, but previously chose submit type %s "
problems.put(changeId, String.format(
"Change has submit type %s, but previously chose submit type %s "
+ "from change %s in the same batch",
cd.getId(), st, submitType, choseSubmitTypeFrom.getId()));
st, submitType, choseSubmitTypeFrom.getId()));
continue;
}
commit.add(or.canMergeFlag);
toSubmit.add(cd);
@@ -702,8 +724,6 @@ public class MergeOp implements AutoCloseable {
try {
List<String> refNames = new ArrayList<>(cds.size());
for (ChangeData cd : cds) {
// Reload change in case index was stale.
cd.reloadChange();
Change c = cd.change();
if (c != null) {
refNames.add(c.currentPatchSetId().toRefName());
@@ -722,104 +742,97 @@ public class MergeOp implements AutoCloseable {
}
}
private SubmitType getSubmitType(ChangeControl ctl, PatchSet ps) {
private SubmitType getSubmitType(ChangeData cd, PatchSet ps) {
try {
ChangeData cd = changeDataFactory.create(db, ctl);
SubmitTypeRecord r = new SubmitRuleEvaluator(cd).setPatchSet(ps)
.getSubmitType();
if (r.status != SubmitTypeRecord.Status.OK) {
logError("Failed to get submit type for " + ctl.getChange().getKey());
logError("Failed to get submit type for " + cd.getId());
return null;
}
return r.type;
} catch (OrmException e) {
logError("Failed to get submit type for " + ctl.getChange().getKey(), e);
logError("Failed to get submit type for " + cd.getId(), e);
return null;
}
}
private RefUpdate updateBranch(OpenRepo or, Branch.NameKey destBranch,
private boolean updateBranch(OpenRepo or, Branch.NameKey destBranch,
IdentifiedUser caller) throws IntegrationException {
RefUpdate branchUpdate = getPendingRefUpdate(or, destBranch);
CodeReviewCommit branchTip = getBranchTip(or, destBranch);
MergeTip mergeTip = mergeTips.get(destBranch);
CodeReviewCommit currentTip =
mergeTip != null ? mergeTip.getCurrentTip() : null;
if (Objects.equals(branchTip, currentTip)) {
OpenBranch ob = or.getBranch(destBranch);
CodeReviewCommit currentTip = ob.getCurrentTip();
if (Objects.equals(ob.oldTip, currentTip)) {
if (currentTip != null) {
logDebug("Branch already at merge tip {}, no update to perform",
currentTip.name());
} else {
logDebug("Both branch and merge tip are nonexistent, no update");
}
return null;
return false;
} else if (currentTip == null) {
logDebug("No merge tip, no update to perform");
return null;
return false;
}
if (RefNames.REFS_CONFIG.equals(branchUpdate.getName())) {
if (RefNames.REFS_CONFIG.equals(ob.update.getName())) {
logDebug("Loading new configuration from {}", RefNames.REFS_CONFIG);
try {
ProjectConfig cfg =
new ProjectConfig(destProject.getProject().getNameKey());
ProjectConfig cfg = new ProjectConfig(or.getProjectName());
cfg.load(or.repo, currentTip);
} catch (Exception e) {
throw new IntegrationException("Submit would store invalid"
+ " project configuration " + currentTip.name() + " for "
+ destProject.getProject().getName(), e);
+ or.getProjectName(), e);
}
}
branchUpdate.setRefLogIdent(
ob.update.setRefLogIdent(
identifiedUserFactory.create(caller.getAccountId()).newRefLogIdent());
branchUpdate.setForceUpdate(false);
branchUpdate.setNewObjectId(currentTip);
branchUpdate.setRefLogMessage("merged", true);
ob.update.setForceUpdate(false);
ob.update.setNewObjectId(currentTip);
ob.update.setRefLogMessage("merged", true);
try {
RefUpdate.Result result = branchUpdate.update(or.rw);
RefUpdate.Result result = ob.update.update(or.rw);
logDebug("Update of {}: {}..{} returned status {}",
branchUpdate.getName(), branchUpdate.getOldObjectId(),
branchUpdate.getNewObjectId(), result);
ob.update.getName(), ob.update.getOldObjectId(),
ob.update.getNewObjectId(), result);
switch (result) {
case NEW:
case FAST_FORWARD:
if (branchUpdate.getResult() == RefUpdate.Result.FAST_FORWARD) {
if (ob.update.getResult() == RefUpdate.Result.FAST_FORWARD) {
tagCache.updateFastForward(destBranch.getParentKey(),
branchUpdate.getName(),
branchUpdate.getOldObjectId(),
ob.update.getName(),
ob.update.getOldObjectId(),
currentTip);
}
if (RefNames.REFS_CONFIG.equals(branchUpdate.getName())) {
Project p = destProject.getProject();
if (RefNames.REFS_CONFIG.equals(ob.update.getName())) {
Project p = or.project.getProject();
projectCache.evict(p);
destProject = projectCache.get(p.getNameKey());
or.project = projectCache.get(p.getNameKey());
repoManager.setProjectDescription(
p.getNameKey(), p.getDescription());
}
return branchUpdate;
return true;
case LOCK_FAILURE:
throw new IntegrationException("Failed to lock " + branchUpdate.getName());
throw new IntegrationException(
"Failed to lock " + ob.update.getName());
default:
throw new IOException(branchUpdate.getResult().name()
+ '\n' + branchUpdate);
throw new IOException(
ob.update.getResult().name() + '\n' + ob.update);
}
} catch (IOException e) {
throw new IntegrationException("Cannot update " + branchUpdate.getName(), e);
throw new IntegrationException("Cannot update " + ob.update.getName(), e);
}
}
private void fireRefUpdated(Branch.NameKey destBranch,
RefUpdate branchUpdate) {
logDebug("Firing ref updated hooks for {}", branchUpdate.getName());
gitRefUpdated.fire(destBranch.getParentKey(), branchUpdate);
hooks.doRefUpdatedHook(destBranch, branchUpdate,
getAccount(mergeTips.get(destBranch).getCurrentTip()));
private void fireRefUpdated(OpenBranch ob) {
logDebug("Firing ref updated hooks for {}", ob.name);
gitRefUpdated.fire(ob.name.getParentKey(), ob.update);
hooks.doRefUpdatedHook(ob.name, ob.update,
getAccount(ob.mergeTip.getCurrentTip()));
}
private Account getAccount(CodeReviewCommit codeReviewCommit) {
@@ -840,17 +853,16 @@ public class MergeOp implements AutoCloseable {
return "";
}
private void updateChangeStatus(List<ChangeData> submitted,
Branch.NameKey destBranch, boolean dryRun, IdentifiedUser caller)
throws NoSuchChangeException, IntegrationException, ResourceConflictException,
OrmException {
private void updateChangeStatus(OpenBranch ob, List<ChangeData> submitted,
boolean dryRun, IdentifiedUser caller) throws NoSuchChangeException,
IntegrationException, ResourceConflictException, OrmException {
if (!dryRun) {
logDebug("Updating change status for {} changes", submitted.size());
} else {
logDebug("Checking change state for {} changes in a dry run",
submitted.size());
}
MergeTip mergeTip = mergeTips.get(destBranch);
for (ChangeData cd : submitted) {
Change c = cd.change();
CodeReviewCommit commit = commits.get(c.getId());
@@ -876,8 +888,8 @@ public class MergeOp implements AutoCloseable {
logDebug("Status of change {} ({}) on {}: {}", c.getId(), commit.name(),
c.getDest(), s);
// If mergeTip is null merge failed and mergeResultRev will not be read.
ObjectId mergeResultRev =
mergeTip != null ? mergeTip.getMergeResults().get(commit) : null;
ObjectId mergeResultRev = ob.mergeTip != null
? ob.mergeTip.getMergeResults().get(commit) : null;
// The change notes must be forcefully reloaded so that the SUBMIT
// approval that we added earlier is visible
commit.notes().reload();
@@ -949,14 +961,14 @@ public class MergeOp implements AutoCloseable {
}
}
private void updateSubmoduleSubscriptions(SubmoduleOp subOp,
Branch.NameKey destBranch, CodeReviewCommit branchTip) {
MergeTip mergeTip = mergeTips.get(destBranch);
private void updateSubmoduleSubscriptions(OpenBranch ob, SubmoduleOp subOp) {
CodeReviewCommit branchTip = ob.oldTip;
MergeTip mergeTip = ob.mergeTip;
if (mergeTip != null
&& (branchTip == null || branchTip != mergeTip.getCurrentTip())) {
logDebug("Updating submodule subscriptions for branch {}", destBranch);
logDebug("Updating submodule subscriptions for branch {}", ob.name);
try {
subOp.updateSubmoduleSubscriptions(db, destBranch);
subOp.updateSubmoduleSubscriptions(db, ob.name);
} catch (SubmoduleException e) {
logError("The submodule subscriptions were not updated according"
+ "to the .gitmodules files", e);
@@ -1073,9 +1085,9 @@ public class MergeOp implements AutoCloseable {
logDebug("Add approval for " + cd + " from user " + user);
ChangeUpdate update = updateFactory.create(control, timestamp);
update.putReviewer(user.getAccountId(), REVIEWER);
List<SubmitRecord> record = records.get(cd.change().getId());
if (record != null) {
update.merge(record);
Optional<SubmitRecord> okRecord = findOkRecord(cd.getSubmitRecords());
if (okRecord.isPresent()) {
update.merge(ImmutableList.of(okRecord.get()));
}
db.changes().beginTransaction(cd.change().getId());
try {