Refactor MergeOp: extract utility methods into MergeUtil class

The MergeOp class is difficult to maintain and to extend. It is a huge
class that implements four different submit strategies. The code for
the different submit strategies is only partially the same and there
are special coding blocks which just apply to one of the submit
strategies. The class has many member variables to hold the current
state of the merge operation. They are read and updated in many methods
which makes it difficult to understand the pre and post conditions for
a method. Also some of the methods do more than their names suggest.

The goal of the refactoring is to have an abstraction for a submit
strategy. By having polymorphism for the submit type the coding for the
different submit strategies will be seperated. This will make it much
easier to add a new submit strategy because then only a clearly defined
interface has to be implemented.

The motivation for this refactoring is to support different submit
types for changes pushed to the same destination branch. The idea is to
control the submit type for a change from Prolog. The current MergeOp
algorithm assumes that the submit type is the same for all changes to
be merged. To support different submit types for changes going to the
same destination branch MergeOp should scan all changes and determine
their submit type, then cluster changes with the same submit type and
loop through the submit strategies to execute those clustered groups in
turn. This will be implemented by follow-up changes, but this is why
the submit strategy will be designed in such a way that it can be
executed several times, each time for a different set of submitted
changes.

Doing the complete refactoring in one step results in a large change
which is difficult to review. This is why the refactoring is done in
several smaller steps. As a first step for the refactoring this change
moves all merge related utility methods from MergeOp into a new
MergeUtil class. Having such util classes is a common pattern in Gerrit
(e.g. see ChangeUtil class). Having the utility methods in the
MergeUtil class makes them usable from different classes. In particular
this allows us in a next step to implement each submit strategy in an
own class and make use of the merge utility methods from there.

Change-Id: If4bb7c91b57677b58fc09bed5c56834dfd56db20
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
This commit is contained in:
Edwin Kempin
2012-09-25 13:11:01 +02:00
parent 4305d7134d
commit 2b3bafa470
2 changed files with 395 additions and 289 deletions

View File

@@ -14,6 +14,15 @@
package com.google.gerrit.server.git;
import static com.google.gerrit.server.git.MergeUtil.commit;
import static com.google.gerrit.server.git.MergeUtil.computeMergeCommitAuthor;
import static com.google.gerrit.server.git.MergeUtil.getFirstFastForward;
import static com.google.gerrit.server.git.MergeUtil.getSubmitter;
import static com.google.gerrit.server.git.MergeUtil.hasMissingDependencies;
import static com.google.gerrit.server.git.MergeUtil.markCleanMerges;
import static com.google.gerrit.server.git.MergeUtil.mergeOneCommit;
import static com.google.gerrit.server.git.MergeUtil.newThreeWayMerger;
import static com.google.gerrit.server.git.MergeUtil.reduceToMinimalMerge;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.MINUTES;
@@ -58,7 +67,6 @@ import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.CommitBuilder;
@@ -69,7 +77,6 @@ import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.merge.MergeStrategy;
import org.eclipse.jgit.merge.Merger;
import org.eclipse.jgit.merge.ThreeWayMerger;
import org.eclipse.jgit.revwalk.FooterKey;
@@ -82,20 +89,16 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.TimeZone;
import javax.annotation.Nullable;
@@ -119,8 +122,6 @@ public class MergeOp {
}
private static final Logger log = LoggerFactory.getLogger(MergeOp.class);
private static final String R_HEADS_MASTER =
Constants.R_HEADS + Constants.MASTER;
private static final ApprovalCategory.Id CRVW =
new ApprovalCategory.Id("CRVW");
private static final ApprovalCategory.Id VRIF =
@@ -316,9 +317,12 @@ public class MergeOp {
case MERGE_ALWAYS:
case MERGE_IF_NECESSARY:
default:
reduceToMinimalMerge();
reduceToMinimalMerge(new MergeSorter(rw, alreadyAccepted, CAN_MERGE),
toMerge);
mergeTopics();
markCleanMerges();
final PatchSetApproval submitApproval =
markCleanMerges(db, rw, CAN_MERGE, mergeTip, alreadyAccepted);
setRefLogIdent(submitApproval);
break;
}
}
@@ -479,40 +483,11 @@ public class MergeOp {
}
}
private void reduceToMinimalMerge() throws MergeException {
final Collection<CodeReviewCommit> heads;
try {
heads = new MergeSorter(rw, alreadyAccepted, CAN_MERGE).sort(toMerge);
} catch (IOException e) {
throw new MergeException("Branch head sorting failed", e);
}
toMerge.clear();
toMerge.addAll(heads);
Collections.sort(toMerge, new Comparator<CodeReviewCommit>() {
@Override
public int compare(final CodeReviewCommit a, final CodeReviewCommit b) {
return a.originalOrder - b.originalOrder;
}
});
}
private void mergeTopics() throws MergeException {
// Take the first fast-forward available, if any is available in the set.
//
if (destProject.getSubmitType() != Project.SubmitType.MERGE_ALWAYS) {
for (final Iterator<CodeReviewCommit> i = toMerge.iterator(); i.hasNext();) {
try {
final CodeReviewCommit n = i.next();
if (mergeTip == null || rw.isMergedInto(mergeTip, n)) {
mergeTip = n;
i.remove();
break;
}
} catch (IOException e) {
throw new MergeException("Cannot fast-forward test during merge", e);
}
}
mergeTip = getFirstFastForward(mergeTip, rw, toMerge);
}
if (destProject.getSubmitType() == Project.SubmitType.FAST_FORWARD_ONLY) {
@@ -527,212 +502,14 @@ public class MergeOp {
// For every other commit do a pair-wise merge.
//
while (!toMerge.isEmpty()) {
mergeOneCommit(toMerge.remove(0));
mergeTip =
mergeOneCommit(db, identifiedUserFactory, myIdent, repo, rw,
inserter, destProject.isUseContentMerge(), destBranch,
mergeTip, toMerge.remove(0));
}
}
}
private void mergeOneCommit(final CodeReviewCommit n) throws MergeException {
final ThreeWayMerger m = newThreeWayMerger();
try {
if (m.merge(new AnyObjectId[] {mergeTip, n})) {
writeMergeCommit(m.getResultTreeId(), n);
} else {
failed(n, CommitMergeStatus.PATH_CONFLICT);
}
} catch (IOException e) {
if (e.getMessage().startsWith("Multiple merge bases for")) {
try {
failed(n, CommitMergeStatus.CRISS_CROSS_MERGE);
} catch (IOException e2) {
throw new MergeException("Cannot merge " + n.name(), e);
}
} else {
throw new MergeException("Cannot merge " + n.name(), e);
}
}
}
private ThreeWayMerger newThreeWayMerger() {
ThreeWayMerger m;
if (destProject.isUseContentMerge()) {
// Settings for this project allow us to try and
// automatically resolve conflicts within files if needed.
// Use ResolveMerge and instruct to operate in core.
m = MergeStrategy.RESOLVE.newMerger(repo, true);
} else {
// No auto conflict resolving allowed. If any of the
// affected files was modified, merge will fail.
m = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(repo);
}
m.setObjectInserter(new ObjectInserter.Filter() {
@Override
protected ObjectInserter delegate() {
return inserter;
}
@Override
public void flush() {
}
@Override
public void release() {
}
});
return m;
}
private CodeReviewCommit failed(final CodeReviewCommit n,
final CommitMergeStatus failure) throws MissingObjectException,
IncorrectObjectTypeException, IOException {
rw.reset();
rw.markStart(n);
rw.markUninteresting(mergeTip);
CodeReviewCommit failed;
while ((failed = (CodeReviewCommit) rw.next()) != null) {
failed.statusCode = failure;
}
return failed;
}
private void writeMergeCommit(ObjectId treeId, CodeReviewCommit n)
throws IOException, MissingObjectException, IncorrectObjectTypeException {
final List<CodeReviewCommit> merged = new ArrayList<CodeReviewCommit>();
rw.reset();
rw.markStart(n);
rw.markUninteresting(mergeTip);
for (final RevCommit c : rw) {
final CodeReviewCommit crc = (CodeReviewCommit) c;
if (crc.patchsetId != null) {
merged.add(crc);
}
}
final StringBuilder msgbuf = new StringBuilder();
if (merged.size() == 1) {
final CodeReviewCommit c = merged.get(0);
rw.parseBody(c);
msgbuf.append("Merge \"");
msgbuf.append(c.getShortMessage());
msgbuf.append("\"");
} else {
msgbuf.append("Merge changes ");
for (final Iterator<CodeReviewCommit> i = merged.iterator(); i.hasNext();) {
msgbuf.append(i.next().change.getKey().abbreviate());
if (i.hasNext()) {
msgbuf.append(',');
}
}
}
if (!R_HEADS_MASTER.equals(destBranch.get())) {
msgbuf.append(" into ");
msgbuf.append(destBranch.getShortName());
}
if (merged.size() > 1) {
msgbuf.append("\n\n* changes:\n");
for (final CodeReviewCommit c : merged) {
rw.parseBody(c);
msgbuf.append(" ");
msgbuf.append(c.getShortMessage());
msgbuf.append("\n");
}
}
PersonIdent authorIdent = computeAuthor(merged);
final CommitBuilder mergeCommit = new CommitBuilder();
mergeCommit.setTreeId(treeId);
mergeCommit.setParentIds(mergeTip, n);
mergeCommit.setAuthor(authorIdent);
mergeCommit.setCommitter(myIdent);
mergeCommit.setMessage(msgbuf.toString());
mergeTip = (CodeReviewCommit) rw.parseCommit(commit(mergeCommit));
}
private PersonIdent computeAuthor(
final List<CodeReviewCommit> codeReviewCommits) {
PatchSetApproval submitter = null;
for (final CodeReviewCommit c : codeReviewCommits) {
PatchSetApproval s = getSubmitter(db, c.patchsetId);
if (submitter == null
|| (s != null && s.getGranted().compareTo(submitter.getGranted()) > 0)) {
submitter = s;
}
}
// Try to use the submitter's identity for the merge commit author.
// If all of the commits being merged are created by the submitter,
// prefer the identity line they used in the commits rather than the
// preferred identity stored in the user account. This way the Git
// commit records are more consistent internally.
//
PersonIdent authorIdent;
if (submitter != null) {
IdentifiedUser who =
identifiedUserFactory.create(submitter.getAccountId());
Set<String> emails = new HashSet<String>();
for (RevCommit c : codeReviewCommits) {
try {
rw.parseBody(c);
} catch (IOException e) {
log.warn("Cannot parse commit " + c.name() + " in " + destBranch, e);
continue;
}
emails.add(c.getAuthorIdent().getEmailAddress());
}
final Timestamp dt = submitter.getGranted();
final TimeZone tz = myIdent.getTimeZone();
if (emails.size() == 1
&& who.getEmailAddresses().contains(emails.iterator().next())) {
authorIdent =
new PersonIdent(codeReviewCommits.get(0).getAuthorIdent(), dt, tz);
} else {
authorIdent = who.newCommitterIdent(dt, tz);
}
} else {
authorIdent = myIdent;
}
return authorIdent;
}
private void markCleanMerges() throws MergeException {
if (mergeTip == null) {
// If mergeTip is null here, branchTip was null, indicating a new branch
// at the start of the merge process. We also elected to merge nothing,
// probably due to missing dependencies. Nothing was cleanly merged.
//
return;
}
try {
rw.reset();
rw.sort(RevSort.TOPO);
rw.sort(RevSort.REVERSE, true);
rw.markStart(mergeTip);
for (RevCommit c : alreadyAccepted) {
rw.markUninteresting(c);
}
CodeReviewCommit c;
while ((c = (CodeReviewCommit) rw.next()) != null) {
if (c.patchsetId != null) {
c.statusCode = CommitMergeStatus.CLEAN_MERGE;
if (branchUpdate.getRefLogIdent() == null) {
setRefLogIdent(getSubmitter(db, c.patchsetId));
}
}
}
} catch (IOException e) {
throw new MergeException("Cannot mark clean merges", e);
}
}
private void setRefLogIdent(final PatchSetApproval submitAudit) {
if (submitAudit != null) {
branchUpdate.setRefLogIdent(identifiedUserFactory.create(
@@ -743,7 +520,7 @@ public class MergeOp {
private void cherryPickChanges() throws MergeException, OrmException {
while (!toMerge.isEmpty()) {
final CodeReviewCommit n = toMerge.remove(0);
final ThreeWayMerger m = newThreeWayMerger();
final ThreeWayMerger m = newThreeWayMerger(repo, inserter, destProject.isUseContentMerge());
try {
if (mergeTip == null) {
// The branch is unborn. Take a fast-forward resolution to
@@ -778,13 +555,18 @@ public class MergeOp {
// by an equivalent merge with a different first parent. So
// instead behave as though MERGE_IF_NECESSARY was configured.
//
if (hasDependenciesMet(n)) {
if (!hasMissingDependencies(new MergeSorter(rw, alreadyAccepted, CAN_MERGE), n)) {
if (rw.isMergedInto(mergeTip, n)) {
mergeTip = n;
} else {
mergeOneCommit(n);
mergeTip =
mergeOneCommit(db, identifiedUserFactory, myIdent, repo, rw,
inserter, destProject.isUseContentMerge(), destBranch,
mergeTip, n);
}
markCleanMerges();
final PatchSetApproval submitApproval =
markCleanMerges(db, rw, CAN_MERGE, mergeTip, alreadyAccepted);
setRefLogIdent(submitApproval);
} else {
// One or more dependencies were not met. The status was
@@ -800,17 +582,6 @@ public class MergeOp {
}
}
private boolean hasDependenciesMet(final CodeReviewCommit n)
throws IOException {
// Oddly we can determine this by running the merge sorter and
// look for the one commit to come out as a result. This works
// as the merge sorter checks the dependency chain as part of
// its logic trying to find a minimal merge path.
//
return new MergeSorter(rw, alreadyAccepted, CAN_MERGE).sort(
Collections.singleton(n)).contains(n);
}
private void writeCherryPickCommit(final Merger m, final CodeReviewCommit n)
throws IOException, OrmException {
rw.parseBody(n);
@@ -939,7 +710,7 @@ public class MergeOp {
mergeCommit.setCommitter(toCommitterIdent(submitAudit));
mergeCommit.setMessage(msgbuf.toString());
final ObjectId id = commit(mergeCommit);
final ObjectId id = commit(inserter, mergeCommit);
final CodeReviewCommit newCommit = (CodeReviewCommit) rw.parseCommit(id);
final Change oldChange = n.change;
@@ -1013,13 +784,6 @@ public class MergeOp {
db.patchSetAncestors().insert(toInsert);
}
private ObjectId commit(CommitBuilder mergeCommit)
throws IOException, UnsupportedEncodingException {
ObjectId id = inserter.insert(mergeCommit);
inserter.flush();
return id;
}
private boolean contains(List<FooterLine> footers, FooterKey key, String val) {
for (final FooterLine line : footers) {
if (line.matches(key) && val.equals(line.getValue())) {
@@ -1188,7 +952,10 @@ public class MergeOp {
CreateCodeReviewNotes codeReviewNotes =
codeReviewNotesFactory.create(db, repo);
try {
codeReviewNotes.create(merged, computeAuthor(merged));
codeReviewNotes.create(
merged,
computeMergeCommitAuthor(db, identifiedUserFactory, myIdent, rw,
merged));
} catch (CodeReviewNoteCreationException e) {
log.error(e.getMessage());
}
@@ -1360,29 +1127,6 @@ public class MergeOp {
return m;
}
private static PatchSetApproval getSubmitter(ReviewDb reviewDb,
PatchSet.Id c) {
if (c == null) {
return null;
}
PatchSetApproval submitter = null;
try {
final List<PatchSetApproval> approvals =
reviewDb.patchSetApprovals().byPatchSet(c).toList();
for (PatchSetApproval a : approvals) {
if (a.getValue() > 0
&& ApprovalCategory.SUBMIT.equals(a.getCategoryId())) {
if (submitter == null
|| a.getGranted().compareTo(submitter.getGranted()) > 0) {
submitter = a;
}
}
}
} catch (OrmException e) {
}
return submitter;
}
private void setMerged(final Change c, final ChangeMessage msg) {
final Change.Id changeId = c.getId();
// We must pull the patchset out of commits, because the patchset ID is

View File

@@ -0,0 +1,362 @@
// Copyright (C) 2012 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.git;
import com.google.gerrit.reviewdb.client.ApprovalCategory;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gwtorm.server.OrmException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.merge.MergeStrategy;
import org.eclipse.jgit.merge.ThreeWayMerger;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevFlag;
import org.eclipse.jgit.revwalk.RevSort;
import org.eclipse.jgit.revwalk.RevWalk;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TimeZone;
public class MergeUtil {
private static final Logger log = LoggerFactory.getLogger(MergeUtil.class);
private static final String R_HEADS_MASTER =
Constants.R_HEADS + Constants.MASTER;
public static CodeReviewCommit getFirstFastForward(
final CodeReviewCommit mergeTip, final RevWalk rw,
final List<CodeReviewCommit> toMerge) throws MergeException {
for (final Iterator<CodeReviewCommit> i = toMerge.iterator(); i.hasNext();) {
try {
final CodeReviewCommit n = i.next();
if (mergeTip == null || rw.isMergedInto(mergeTip, n)) {
i.remove();
return n;
}
} catch (IOException e) {
throw new MergeException("Cannot fast-forward test during merge", e);
}
}
return mergeTip;
}
public static void reduceToMinimalMerge(final MergeSorter mergeSorter,
final List<CodeReviewCommit> toSort) throws MergeException {
final Collection<CodeReviewCommit> heads;
try {
heads = mergeSorter.sort(toSort);
} catch (IOException e) {
throw new MergeException("Branch head sorting failed", e);
}
toSort.clear();
toSort.addAll(heads);
Collections.sort(toSort, new Comparator<CodeReviewCommit>() {
@Override
public int compare(final CodeReviewCommit a, final CodeReviewCommit b) {
return a.originalOrder - b.originalOrder;
}
});
}
public static PatchSetApproval getSubmitter(final ReviewDb reviewDb,
final PatchSet.Id c) {
if (c == null) {
return null;
}
PatchSetApproval submitter = null;
try {
final List<PatchSetApproval> approvals =
reviewDb.patchSetApprovals().byPatchSet(c).toList();
for (PatchSetApproval a : approvals) {
if (a.getValue() > 0
&& ApprovalCategory.SUBMIT.equals(a.getCategoryId())) {
if (submitter == null
|| a.getGranted().compareTo(submitter.getGranted()) > 0) {
submitter = a;
}
}
}
} catch (OrmException e) {
}
return submitter;
}
public static PersonIdent computeMergeCommitAuthor(final ReviewDb reviewDb,
final IdentifiedUser.GenericFactory identifiedUserFactory,
final PersonIdent myIdent, final RevWalk rw,
final List<CodeReviewCommit> codeReviewCommits) {
PatchSetApproval submitter = null;
for (final CodeReviewCommit c : codeReviewCommits) {
PatchSetApproval s = getSubmitter(reviewDb, c.patchsetId);
if (submitter == null
|| (s != null && s.getGranted().compareTo(submitter.getGranted()) > 0)) {
submitter = s;
}
}
// Try to use the submitter's identity for the merge commit author.
// If all of the commits being merged are created by the submitter,
// prefer the identity line they used in the commits rather than the
// preferred identity stored in the user account. This way the Git
// commit records are more consistent internally.
//
PersonIdent authorIdent;
if (submitter != null) {
IdentifiedUser who =
identifiedUserFactory.create(submitter.getAccountId());
Set<String> emails = new HashSet<String>();
for (RevCommit c : codeReviewCommits) {
try {
rw.parseBody(c);
} catch (IOException e) {
log.warn("Cannot parse commit " + c.name(), e);
continue;
}
emails.add(c.getAuthorIdent().getEmailAddress());
}
final Timestamp dt = submitter.getGranted();
final TimeZone tz = myIdent.getTimeZone();
if (emails.size() == 1
&& who.getEmailAddresses().contains(emails.iterator().next())) {
authorIdent =
new PersonIdent(codeReviewCommits.get(0).getAuthorIdent(), dt, tz);
} else {
authorIdent = who.newCommitterIdent(dt, tz);
}
} else {
authorIdent = myIdent;
}
return authorIdent;
}
public static boolean hasMissingDependencies(final MergeSorter mergeSorter,
final CodeReviewCommit toMerge) throws MergeException {
try {
return !mergeSorter.sort(Collections.singleton(toMerge)).contains(toMerge);
} catch (IOException e) {
throw new MergeException("Branch head sorting failed", e);
}
}
public static CodeReviewCommit mergeOneCommit(final ReviewDb reviewDb,
final IdentifiedUser.GenericFactory identifiedUserFactory,
final PersonIdent myIdent, final Repository repo, final RevWalk rw,
final ObjectInserter inserter, final boolean useContentMerge,
final Branch.NameKey destBranch, final CodeReviewCommit mergeTip,
final CodeReviewCommit n) throws MergeException {
final ThreeWayMerger m = newThreeWayMerger(repo, inserter, useContentMerge);
try {
if (m.merge(new AnyObjectId[] {mergeTip, n})) {
return writeMergeCommit(reviewDb, identifiedUserFactory, myIdent, rw,
inserter, destBranch, mergeTip, m.getResultTreeId(), n);
} else {
failed(rw, mergeTip, n, CommitMergeStatus.PATH_CONFLICT);
}
} catch (IOException e) {
if (e.getMessage().startsWith("Multiple merge bases for")) {
try {
failed(rw, mergeTip, n, CommitMergeStatus.CRISS_CROSS_MERGE);
} catch (IOException e2) {
throw new MergeException("Cannot merge " + n.name(), e);
}
} else {
throw new MergeException("Cannot merge " + n.name(), e);
}
}
return mergeTip;
}
private static CodeReviewCommit failed(final RevWalk rw,
final CodeReviewCommit mergeTip, final CodeReviewCommit n,
final CommitMergeStatus failure) throws MissingObjectException,
IncorrectObjectTypeException, IOException {
rw.reset();
rw.markStart(n);
rw.markUninteresting(mergeTip);
CodeReviewCommit failed;
while ((failed = (CodeReviewCommit) rw.next()) != null) {
failed.statusCode = failure;
}
return failed;
}
public static CodeReviewCommit writeMergeCommit(final ReviewDb reviewDb,
final IdentifiedUser.GenericFactory identifiedUserFactory,
final PersonIdent myIdent, final RevWalk rw,
final ObjectInserter inserter, final Branch.NameKey destBranch,
final CodeReviewCommit mergeTip, final ObjectId treeId,
final CodeReviewCommit n) throws IOException, MissingObjectException,
IncorrectObjectTypeException {
final List<CodeReviewCommit> merged = new ArrayList<CodeReviewCommit>();
rw.reset();
rw.markStart(n);
rw.markUninteresting(mergeTip);
for (final RevCommit c : rw) {
final CodeReviewCommit crc = (CodeReviewCommit) c;
if (crc.patchsetId != null) {
merged.add(crc);
}
}
final StringBuilder msgbuf = new StringBuilder();
if (merged.size() == 1) {
final CodeReviewCommit c = merged.get(0);
rw.parseBody(c);
msgbuf.append("Merge \"");
msgbuf.append(c.getShortMessage());
msgbuf.append("\"");
} else {
msgbuf.append("Merge changes ");
for (final Iterator<CodeReviewCommit> i = merged.iterator(); i.hasNext();) {
msgbuf.append(i.next().change.getKey().abbreviate());
if (i.hasNext()) {
msgbuf.append(',');
}
}
}
if (!R_HEADS_MASTER.equals(destBranch.get())) {
msgbuf.append(" into ");
msgbuf.append(destBranch.getShortName());
}
if (merged.size() > 1) {
msgbuf.append("\n\n* changes:\n");
for (final CodeReviewCommit c : merged) {
rw.parseBody(c);
msgbuf.append(" ");
msgbuf.append(c.getShortMessage());
msgbuf.append("\n");
}
}
PersonIdent authorIdent =
computeMergeCommitAuthor(reviewDb, identifiedUserFactory, myIdent, rw,
merged);
final CommitBuilder mergeCommit = new CommitBuilder();
mergeCommit.setTreeId(treeId);
mergeCommit.setParentIds(mergeTip, n);
mergeCommit.setAuthor(authorIdent);
mergeCommit.setCommitter(myIdent);
mergeCommit.setMessage(msgbuf.toString());
return (CodeReviewCommit) rw.parseCommit(commit(inserter, mergeCommit));
}
public static ThreeWayMerger newThreeWayMerger(final Repository repo,
final ObjectInserter inserter, final boolean useContentMerge) {
ThreeWayMerger m;
if (useContentMerge) {
// Settings for this project allow us to try and
// automatically resolve conflicts within files if needed.
// Use ResolveMerge and instruct to operate in core.
m = MergeStrategy.RESOLVE.newMerger(repo, true);
} else {
// No auto conflict resolving allowed. If any of the
// affected files was modified, merge will fail.
m = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(repo);
}
m.setObjectInserter(new ObjectInserter.Filter() {
@Override
protected ObjectInserter delegate() {
return inserter;
}
@Override
public void flush() {
}
@Override
public void release() {
}
});
return m;
}
public static ObjectId commit(final ObjectInserter inserter,
final CommitBuilder mergeCommit) throws IOException,
UnsupportedEncodingException {
ObjectId id = inserter.insert(mergeCommit);
inserter.flush();
return id;
}
public static PatchSetApproval markCleanMerges(final ReviewDb reviewDb,
final RevWalk rw, final RevFlag canMergeFlag,
final CodeReviewCommit mergeTip, final Set<RevCommit> alreadyAccepted)
throws MergeException {
if (mergeTip == null) {
// If mergeTip is null here, branchTip was null, indicating a new branch
// at the start of the merge process. We also elected to merge nothing,
// probably due to missing dependencies. Nothing was cleanly merged.
//
return null;
}
try {
PatchSetApproval submitApproval = null;
rw.resetRetain(canMergeFlag);
rw.sort(RevSort.TOPO);
rw.sort(RevSort.REVERSE, true);
rw.markStart(mergeTip);
for (RevCommit c : alreadyAccepted) {
rw.markUninteresting(c);
}
CodeReviewCommit c;
while ((c = (CodeReviewCommit) rw.next()) != null) {
if (c.patchsetId != null) {
c.statusCode = CommitMergeStatus.CLEAN_MERGE;
if (submitApproval == null) {
submitApproval = getSubmitter(reviewDb, c.patchsetId);
}
}
}
return submitApproval;
} catch (IOException e) {
throw new MergeException("Cannot mark clean merges", e);
}
}
}