Defer object flushing when merging

Mergers in the most recent JGit know how to read previously-inserted
objects back, so a series of merges does not require aggressively
flushing the objects. Instead, flush objects in MergeOp just before
updating the relevant refs.

Change-Id: Ia85c5114e86739b4a480bea65f3413fe47cf572d
This commit is contained in:
Dave Borowitz
2013-06-10 16:45:48 -07:00
parent 587c7a235d
commit 5dea1c0fc0
5 changed files with 120 additions and 130 deletions

View File

@@ -113,87 +113,89 @@ public class CherryPickChange {
Project.NameKey project = change.getProject(); Project.NameKey project = change.getProject();
IdentifiedUser identifiedUser = (IdentifiedUser) currentUser.get(); IdentifiedUser identifiedUser = (IdentifiedUser) currentUser.get();
final Repository git; Repository git = null;
ObjectInserter oi = null;
RevWalk revWalk = null;
try { try {
git = gitManager.openRepository(project); git = gitManager.openRepository(project);
oi = git.newObjectInserter();
revWalk = new RevWalk(oi.newReader());
Ref destRef = git.getRef(destinationBranch);
if (destRef == null) {
throw new InvalidChangeOperationException("Branch "
+ destinationBranch + " does not exist.");
}
final RevCommit mergeTip = revWalk.parseCommit(destRef.getObjectId());
RevCommit commitToCherryPick =
revWalk.parseCommit(ObjectId.fromString(patch.getRevision().get()));
PersonIdent committerIdent =
identifiedUser.newCommitterIdent(TimeUtil.nowTs(),
serverTimeZone);
final ObjectId computedChangeId =
ChangeIdUtil
.computeChangeId(commitToCherryPick.getTree(), mergeTip,
commitToCherryPick.getAuthorIdent(), committerIdent, message);
String commitMessage =
ChangeIdUtil.insertId(message, computedChangeId).trim() + '\n';
RevCommit cherryPickCommit;
ProjectState projectState = refControl.getProjectControl().getProjectState();
cherryPickCommit =
mergeUtilFactory.create(projectState).createCherryPickFromCommit(git, oi, mergeTip,
commitToCherryPick, committerIdent, commitMessage, revWalk);
oi.flush();
Change.Key changeKey;
final List<String> idList = cherryPickCommit.getFooterLines(
FooterConstants.CHANGE_ID);
if (!idList.isEmpty()) {
final String idStr = idList.get(idList.size() - 1).trim();
changeKey = new Change.Key(idStr);
} else {
changeKey = new Change.Key("I" + computedChangeId.name());
}
Branch.NameKey newDest =
new Branch.NameKey(change.getProject(), destRef.getName());
List<ChangeData> destChanges = queryProvider.get()
.setLimit(2)
.byBranchKey(newDest, changeKey);
if (destChanges.size() > 1) {
throw new InvalidChangeOperationException("Several changes with key "
+ changeKey + " reside on the same branch. "
+ "Cannot create a new patch set.");
} else if (destChanges.size() == 1) {
// The change key exists on the destination branch. The cherry pick
// will be added as a new patch set.
return insertPatchSet(git, revWalk, destChanges.get(0).change(),
cherryPickCommit, refControl, identifiedUser);
} else {
// Change key not found on destination branch. We can create a new
// change.
return createNewChange(git, revWalk, changeKey, project,
patch.getId(), destRef, cherryPickCommit, refControl,
identifiedUser, change.getTopic());
}
} catch (MergeIdenticalTreeException | MergeConflictException e) {
throw new MergeException("Cherry pick failed: " + e.getMessage());
} catch (RepositoryNotFoundException e) { } catch (RepositoryNotFoundException e) {
throw new NoSuchChangeException(change.getId(), e); throw new NoSuchChangeException(change.getId(), e);
} } finally {
if (revWalk != null) {
try {
RevWalk revWalk = new RevWalk(git);
try {
Ref destRef = git.getRef(destinationBranch);
if (destRef == null) {
throw new InvalidChangeOperationException("Branch "
+ destinationBranch + " does not exist.");
}
final RevCommit mergeTip = revWalk.parseCommit(destRef.getObjectId());
RevCommit commitToCherryPick =
revWalk.parseCommit(ObjectId.fromString(patch.getRevision().get()));
PersonIdent committerIdent =
identifiedUser.newCommitterIdent(TimeUtil.nowTs(),
serverTimeZone);
final ObjectId computedChangeId =
ChangeIdUtil
.computeChangeId(commitToCherryPick.getTree(), mergeTip,
commitToCherryPick.getAuthorIdent(), committerIdent, message);
String commitMessage =
ChangeIdUtil.insertId(message, computedChangeId).trim() + '\n';
RevCommit cherryPickCommit;
ObjectInserter oi = git.newObjectInserter();
try {
ProjectState projectState = refControl.getProjectControl().getProjectState();
cherryPickCommit =
mergeUtilFactory.create(projectState).createCherryPickFromCommit(git, oi, mergeTip,
commitToCherryPick, committerIdent, commitMessage, revWalk);
} catch (MergeIdenticalTreeException | MergeConflictException e) {
throw new MergeException("Cherry pick failed: " + e.getMessage());
} finally {
oi.release();
}
Change.Key changeKey;
final List<String> idList = cherryPickCommit.getFooterLines(
FooterConstants.CHANGE_ID);
if (!idList.isEmpty()) {
final String idStr = idList.get(idList.size() - 1).trim();
changeKey = new Change.Key(idStr);
} else {
changeKey = new Change.Key("I" + computedChangeId.name());
}
Branch.NameKey newDest =
new Branch.NameKey(change.getProject(), destRef.getName());
List<ChangeData> destChanges = queryProvider.get()
.setLimit(2)
.byBranchKey(newDest, changeKey);
if (destChanges.size() > 1) {
throw new InvalidChangeOperationException("Several changes with key "
+ changeKey + " reside on the same branch. "
+ "Cannot create a new patch set.");
} else if (destChanges.size() == 1) {
// The change key exists on the destination branch. The cherry pick
// will be added as a new patch set.
return insertPatchSet(git, revWalk, destChanges.get(0).change(),
cherryPickCommit, refControl, identifiedUser);
} else {
// Change key not found on destination branch. We can create a new
// change.
return createNewChange(git, revWalk, changeKey, project,
patch.getId(), destRef, cherryPickCommit, refControl,
identifiedUser, change.getTopic());
}
} finally {
revWalk.release(); revWalk.release();
} }
} finally { if (oi != null) {
git.close(); oi.release();
}
if (git != null) {
git.close();
}
} }
} }

View File

@@ -23,6 +23,7 @@ import com.google.gerrit.server.project.ChangeControl;
import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.RevWalk;
@@ -50,12 +51,11 @@ public class CodeReviewCommit extends RevCommit {
}).nullsFirst(); }).nullsFirst();
public static RevWalk newRevWalk(Repository repo) { public static RevWalk newRevWalk(Repository repo) {
return new RevWalk(repo) { return new CodeReviewRevWalk(repo);
@Override }
protected RevCommit createCommit(AnyObjectId id) {
return new CodeReviewCommit(id); public static RevWalk newRevWalk(ObjectReader reader) {
} return new CodeReviewRevWalk(reader);
};
} }
static CodeReviewCommit revisionGone(ChangeControl ctl) { static CodeReviewCommit revisionGone(ChangeControl ctl) {
@@ -85,6 +85,21 @@ public class CodeReviewCommit extends RevCommit {
return r; return r;
} }
private static class CodeReviewRevWalk extends RevWalk {
private CodeReviewRevWalk(Repository repo) {
super(repo);
}
private CodeReviewRevWalk(ObjectReader reader) {
super(reader);
}
@Override
protected RevCommit createCommit(AnyObjectId id) {
return new CodeReviewCommit(id);
}
}
/** /**
* Unique key of the PatchSet entity from the code review system. * Unique key of the PatchSet entity from the code review system.
* <p> * <p>

View File

@@ -430,13 +430,11 @@ public class MergeOp {
String m = "Error opening repository \"" + name.get() + '"'; String m = "Error opening repository \"" + name.get() + '"';
throw new MergeException(m, err); throw new MergeException(m, err);
} }
inserter = repo.newObjectInserter();
rw = CodeReviewCommit.newRevWalk(repo); rw = CodeReviewCommit.newRevWalk(inserter.newReader());
rw.sort(RevSort.TOPO); rw.sort(RevSort.TOPO);
rw.sort(RevSort.COMMIT_TIME_DESC, true); rw.sort(RevSort.COMMIT_TIME_DESC, true);
canMergeFlag = rw.newFlag("CAN_MERGE"); canMergeFlag = rw.newFlag("CAN_MERGE");
inserter = repo.newObjectInserter();
} }
private RefUpdate openBranch() private RefUpdate openBranch()
@@ -674,6 +672,11 @@ public class MergeOp {
+ destProject.getProject().getName(), e); + destProject.getProject().getName(), e);
} }
} }
try {
inserter.flush();
} catch (IOException e) {
throw new MergeException("Cannot flush merge results", e);
}
branchUpdate.setRefLogIdent(refLogIdent); branchUpdate.setRefLogIdent(refLogIdent);
branchUpdate.setForceUpdate(false); branchUpdate.setForceUpdate(false);

View File

@@ -43,7 +43,6 @@ import org.eclipse.jgit.errors.LargeObjectException;
import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.NoMergeBaseException; import org.eclipse.jgit.errors.NoMergeBaseException;
import org.eclipse.jgit.errors.NoMergeBaseException.MergeBaseFailureReason; import org.eclipse.jgit.errors.NoMergeBaseException.MergeBaseFailureReason;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.CommitBuilder; import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
@@ -67,7 +66,6 @@ import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@@ -79,6 +77,12 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.TimeZone; import java.util.TimeZone;
/**
* Utilities for various kinds of merges and cherry-picks.
* <p>
* <b>Note:</b> Unless otherwise noted, the methods in this class do not flush
* the {@link ObjectInserter}s passed in after performing a merge.
*/
public class MergeUtil { public class MergeUtil {
private static final Logger log = LoggerFactory.getLogger(MergeUtil.class); private static final Logger log = LoggerFactory.getLogger(MergeUtil.class);
private static final String R_HEADS_MASTER = private static final String R_HEADS_MASTER =
@@ -177,7 +181,7 @@ public class MergeUtil {
final ThreeWayMerger m = newThreeWayMerger(repo, inserter); final ThreeWayMerger m = newThreeWayMerger(repo, inserter);
m.setBase(originalCommit.getParent(0)); m.setBase(originalCommit.getParent(0));
if (m.merge(mergeTip, originalCommit)) { if (m.merge(false, mergeTip, originalCommit)) {
ObjectId tree = m.getResultTreeId(); ObjectId tree = m.getResultTreeId();
if (tree.equals(mergeTip.getTree())) { if (tree.equals(mergeTip.getTree())) {
throw new MergeIdenticalTreeException("identical tree"); throw new MergeIdenticalTreeException("identical tree");
@@ -189,7 +193,7 @@ public class MergeUtil {
mergeCommit.setAuthor(originalCommit.getAuthorIdent()); mergeCommit.setAuthor(originalCommit.getAuthorIdent());
mergeCommit.setCommitter(cherryPickCommitterIdent); mergeCommit.setCommitter(cherryPickCommitterIdent);
mergeCommit.setMessage(commitMsg); mergeCommit.setMessage(commitMsg);
return rw.parseCommit(commit(inserter, mergeCommit)); return rw.parseCommit(inserter.insert(mergeCommit));
} else { } else {
throw new MergeConflictException("merge conflict"); throw new MergeConflictException("merge conflict");
} }
@@ -393,7 +397,7 @@ public class MergeUtil {
ThreeWayMerger m = newThreeWayMerger(repo, createDryRunInserter(repo)); ThreeWayMerger m = newThreeWayMerger(repo, createDryRunInserter(repo));
try { try {
return m.merge(new AnyObjectId[] {mergeTip, toMerge}); return m.merge(false, mergeTip, toMerge);
} catch (LargeObjectException e) { } catch (LargeObjectException e) {
log.warn("Cannot merge due to LargeObjectException: " + toMerge.name()); log.warn("Cannot merge due to LargeObjectException: " + toMerge.name());
return false; return false;
@@ -442,7 +446,7 @@ public class MergeUtil {
try { try {
ThreeWayMerger m = newThreeWayMerger(repo, createDryRunInserter(repo)); ThreeWayMerger m = newThreeWayMerger(repo, createDryRunInserter(repo));
m.setBase(toMerge.getParent(0)); m.setBase(toMerge.getParent(0));
return m.merge(mergeTip, toMerge); return m.merge(false, mergeTip, toMerge);
} catch (IOException e) { } catch (IOException e) {
throw new MergeException("Cannot merge " + toMerge.name(), e); throw new MergeException("Cannot merge " + toMerge.name(), e);
} }
@@ -494,7 +498,7 @@ public class MergeUtil {
throws MergeException { throws MergeException {
final ThreeWayMerger m = newThreeWayMerger(repo, inserter); final ThreeWayMerger m = newThreeWayMerger(repo, inserter);
try { try {
if (m.merge(new AnyObjectId[] {mergeTip, n})) { if (m.merge(false, mergeTip, n)) {
return writeMergeCommit(myIdent, rw, inserter, canMergeFlag, destBranch, return writeMergeCommit(myIdent, rw, inserter, canMergeFlag, destBranch,
mergeTip, m.getResultTreeId(), n); mergeTip, m.getResultTreeId(), n);
} else { } else {
@@ -582,7 +586,7 @@ public class MergeUtil {
mergeCommit.setMessage(msgbuf.toString()); mergeCommit.setMessage(msgbuf.toString());
CodeReviewCommit mergeResult = CodeReviewCommit mergeResult =
(CodeReviewCommit) rw.parseCommit(commit(inserter, mergeCommit)); (CodeReviewCommit) rw.parseCommit(inserter.insert(mergeCommit));
mergeResult.setControl(n.getControl()); mergeResult.setControl(n.getControl());
return mergeResult; return mergeResult;
} }
@@ -656,31 +660,10 @@ public class MergeUtil {
Merger m = strategy.newMerger(repo, true); Merger m = strategy.newMerger(repo, true);
checkArgument(m instanceof ThreeWayMerger, checkArgument(m instanceof ThreeWayMerger,
"merge strategy %s does not support three-way merging", strategyName); "merge strategy %s does not support three-way merging", strategyName);
m.setObjectInserter(new ObjectInserter.Filter() { m.setObjectInserter(inserter);
@Override
protected ObjectInserter delegate() {
return inserter;
}
@Override
public void flush() {
}
@Override
public void release() {
}
});
return (ThreeWayMerger) m; return (ThreeWayMerger) m;
} }
public ObjectId commit(final ObjectInserter inserter,
final CommitBuilder mergeCommit) throws IOException,
UnsupportedEncodingException {
ObjectId id = inserter.insert(mergeCommit);
inserter.flush();
return id;
}
public PatchSetApproval markCleanMerges(final RevWalk rw, public PatchSetApproval markCleanMerges(final RevWalk rw,
final RevFlag canMergeFlag, final CodeReviewCommit mergeTip, final RevFlag canMergeFlag, final CodeReviewCommit mergeTip,
final Set<RevCommit> alreadyAccepted) throws MergeException { final Set<RevCommit> alreadyAccepted) throws MergeException {

View File

@@ -275,24 +275,11 @@ public class PatchListLoader extends CacheLoader<PatchListKey, PatchList> {
try { try {
DirCache dc = DirCache.newInCore(); DirCache dc = DirCache.newInCore();
m.setDirCache(dc); m.setDirCache(dc);
m.setObjectInserter(new ObjectInserter.Filter() { m.setObjectInserter(ins);
@Override
protected ObjectInserter delegate() {
return ins;
}
@Override
public void flush() {
}
@Override
public void release() {
}
});
boolean couldMerge; boolean couldMerge;
try { try {
couldMerge = m.merge(b.getParents()); couldMerge = m.merge(false, b.getParents());
} catch (IOException e) { } catch (IOException e) {
// It is not safe to continue further down in this method as throwing // It is not safe to continue further down in this method as throwing
// an exception most likely means that the merge tree was not created // an exception most likely means that the merge tree was not created