Revert changes to MergeOp to batch object inserts and ref updates

The failure modes for these are problematic, making the cure worse
than the disease. Unlike a missing PatchSet record in the database, a
missing PatchSet ref causes the change screen to fail to load, and
even once abandoned, causes reindexing to fail.

A longer-term rewrite of MergeOp begins with I0771f5e8, which is
designed to have less problematic failure modes, but until then, just
revert.

This mostly reverts the following commits (except for some cleanups
and new helper methods that are not used now but will be eventually):
  c20148c8e2
  5dea1c0fc0

Change-Id: Id691318a6e28f18860b34a9a39f6e834161ff0dd
This commit is contained in:
Dave Borowitz
2015-01-30 07:31:15 -08:00
parent 02fc027d12
commit 875fc8e8a3
12 changed files with 221 additions and 212 deletions

View File

@@ -113,89 +113,87 @@ public class CherryPickChange {
Project.NameKey project = change.getProject(); Project.NameKey project = change.getProject();
IdentifiedUser identifiedUser = (IdentifiedUser) currentUser.get(); IdentifiedUser identifiedUser = (IdentifiedUser) currentUser.get();
Repository git = null; final Repository git;
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();
} }
if (oi != null) { } finally {
oi.release(); git.close();
}
if (git != null) {
git.close();
}
} }
} }

View File

@@ -241,7 +241,6 @@ public class MergeabilityCacheImpl implements MergeabilityCache {
rw, rw,
null /*inserter*/, null /*inserter*/,
canMerge, canMerge,
null /*batchRefUpdate*/,
accepted, accepted,
key.load.dest).dryRun(tip, rev); key.load.dest).dryRun(tip, rev);
} finally { } finally {

View File

@@ -52,7 +52,6 @@ import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
@@ -111,7 +110,6 @@ public class PatchSetInserter {
private boolean runHooks; private boolean runHooks;
private boolean sendMail; private boolean sendMail;
private Account.Id uploader; private Account.Id uploader;
private BatchRefUpdate batchRefUpdate;
@Inject @Inject
public PatchSetInserter(ChangeHooks hooks, public PatchSetInserter(ChangeHooks hooks,
@@ -216,11 +214,6 @@ public class PatchSetInserter {
return this; return this;
} }
public PatchSetInserter setBatchRefUpdate(BatchRefUpdate batchRefUpdate) {
this.batchRefUpdate = batchRefUpdate;
return this;
}
public Change insert() throws InvalidChangeOperationException, OrmException, public Change insert() throws InvalidChangeOperationException, OrmException,
IOException, NoSuchChangeException { IOException, NoSuchChangeException {
init(); init();
@@ -228,23 +221,16 @@ public class PatchSetInserter {
Change c = ctl.getChange(); Change c = ctl.getChange();
Change updatedChange; Change updatedChange;
RefUpdate ru = git.updateRef(patchSet.getRefName());
if (batchRefUpdate != null) { ru.setExpectedOldObjectId(ObjectId.zeroId());
// Caller passed in update; add command, but don't execute. ru.setNewObjectId(commit);
batchRefUpdate.addCommand(new ReceiveCommand(ObjectId.zeroId(), commit, ru.disableRefLog();
patchSet.getRefName(), ReceiveCommand.Type.CREATE)); if (ru.update(revWalk) != RefUpdate.Result.NEW) {
} else { throw new IOException(String.format(
RefUpdate ru = git.updateRef(patchSet.getRefName()); "Failed to create ref %s in %s: %s", patchSet.getRefName(),
ru.setExpectedOldObjectId(ObjectId.zeroId()); c.getDest().getParentKey().get(), ru.getResult()));
ru.setNewObjectId(commit);
ru.disableRefLog();
if (ru.update(revWalk) != RefUpdate.Result.NEW) {
throw new IOException(String.format(
"Failed to create ref %s in %s: %s", patchSet.getRefName(),
c.getDest().getParentKey().get(), ru.getResult()));
}
gitRefUpdated.fire(c.getProject(), ru);
} }
gitRefUpdated.fire(c.getProject(), ru);
final PatchSet.Id currentPatchSetId = c.currentPatchSetId(); final PatchSet.Id currentPatchSetId = c.currentPatchSetId();

View File

@@ -14,7 +14,6 @@
package com.google.gerrit.server.changedetail; package com.google.gerrit.server.changedetail;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.TimeUtil; import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.errors.EmailException; import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.reviewdb.client.Branch; import com.google.gerrit.reviewdb.client.Branch;
@@ -43,7 +42,6 @@ import com.google.inject.Provider;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import org.eclipse.jgit.errors.RepositoryNotFoundException; import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.CommitBuilder; import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ObjectInserter;
@@ -140,7 +138,7 @@ public class RebaseChange {
rebase(git, rw, inserter, patchSetId, change, rebase(git, rw, inserter, patchSetId, change,
uploader, baseCommit, mergeUtilFactory.create( uploader, baseCommit, mergeUtilFactory.create(
changeControl.getProjectControl().getProjectState(), true), changeControl.getProjectControl().getProjectState(), true),
committerIdent, true, true, ValidatePolicy.GERRIT, null); committerIdent, true, true, ValidatePolicy.GERRIT);
} catch (MergeConflictException e) { } catch (MergeConflictException e) {
throw new IOException(e.getMessage()); throw new IOException(e.getMessage());
} finally { } finally {
@@ -270,8 +268,6 @@ public class RebaseChange {
* @param sendMail if a mail notification should be sent for the new patch set * @param sendMail if a mail notification should be sent for the new patch set
* @param runHooks if hooks should be run for the new patch set * @param runHooks if hooks should be run for the new patch set
* @param validate if commit validation should be run for the new patch set * @param validate if commit validation should be run for the new patch set
* @param batchRefUpdate if not null, a batch ref update for creating new
* refs, which will not be executed.
* @return the new patch set which is based on the given base commit * @return the new patch set which is based on the given base commit
* @throws NoSuchChangeException thrown if the change to which the patch set * @throws NoSuchChangeException thrown if the change to which the patch set
* belongs does not exist or is not visible to the user * belongs does not exist or is not visible to the user
@@ -279,13 +275,14 @@ public class RebaseChange {
* @throws IOException thrown if rebase is not possible or not needed * @throws IOException thrown if rebase is not possible or not needed
* @throws InvalidChangeOperationException thrown if rebase is not allowed * @throws InvalidChangeOperationException thrown if rebase is not allowed
*/ */
public PatchSet rebase(Repository git, RevWalk revWalk, public PatchSet rebase(final Repository git, final RevWalk revWalk,
ObjectInserter inserter, PatchSet.Id patchSetId, Change change, final ObjectInserter inserter, final PatchSet.Id patchSetId,
IdentifiedUser uploader, RevCommit baseCommit, MergeUtil mergeUtil, final Change change, final IdentifiedUser uploader, final RevCommit baseCommit,
PersonIdent committerIdent, boolean sendMail, boolean runHooks, final MergeUtil mergeUtil, PersonIdent committerIdent,
ValidatePolicy validate, @Nullable BatchRefUpdate batchRefUpdate) boolean sendMail, boolean runHooks, ValidatePolicy validate)
throws NoSuchChangeException, OrmException, IOException, throws NoSuchChangeException,
InvalidChangeOperationException, MergeConflictException { OrmException, IOException, InvalidChangeOperationException,
MergeConflictException {
if (!change.currentPatchSetId().equals(patchSetId)) { if (!change.currentPatchSetId().equals(patchSetId)) {
throw new InvalidChangeOperationException("patch set is not current"); throw new InvalidChangeOperationException("patch set is not current");
} }
@@ -308,9 +305,6 @@ public class RebaseChange {
.setUploader(uploader.getAccountId()) .setUploader(uploader.getAccountId())
.setSendMail(sendMail) .setSendMail(sendMail)
.setRunHooks(runHooks); .setRunHooks(runHooks);
if (batchRefUpdate != null) {
patchSetInserter.setBatchRefUpdate(batchRefUpdate);
}
final PatchSet.Id newPatchSetId = patchSetInserter.getPatchSetId(); final PatchSet.Id newPatchSetId = patchSetInserter.getPatchSetId();
final ChangeMessage cmsg = new ChangeMessage( final ChangeMessage cmsg = new ChangeMessage(

View File

@@ -76,19 +76,17 @@ import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.RepositoryNotFoundException; import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
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.RevFlag; import org.eclipse.jgit.revwalk.RevFlag;
import org.eclipse.jgit.revwalk.RevSort; import org.eclipse.jgit.revwalk.RevSort;
import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.ReceiveCommand;
import org.joda.time.format.ISODateTimeFormat; import org.joda.time.format.ISODateTimeFormat;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -172,7 +170,6 @@ public class MergeOp {
private Repository repo; private Repository repo;
private RevWalk rw; private RevWalk rw;
private RevFlag canMergeFlag; private RevFlag canMergeFlag;
private ObjectId oldBranchTip;
private CodeReviewCommit branchTip; private CodeReviewCommit branchTip;
private MergeTip mergeTip; private MergeTip mergeTip;
private ObjectInserter inserter; private ObjectInserter inserter;
@@ -260,7 +257,7 @@ public class MergeOp {
openSchema(); openSchema();
openRepository(); openRepository();
BatchRefUpdate branchUpdate = openBranch(); RefUpdate branchUpdate = openBranch();
boolean reopen = false; boolean reopen = false;
ListMultimap<SubmitType, Change> toSubmit = ListMultimap<SubmitType, Change> toSubmit =
@@ -279,9 +276,9 @@ public class MergeOp {
logDebug("Reopening branch"); logDebug("Reopening branch");
branchUpdate = openBranch(); branchUpdate = openBranch();
} }
SubmitStrategy strategy = createStrategy(submitType, branchUpdate); SubmitStrategy strategy = createStrategy(submitType);
MergeTip mergeTip = preMerge(strategy, toMerge.get(submitType)); MergeTip mergeTip = preMerge(strategy, toMerge.get(submitType));
BatchRefUpdate update = updateBranch(strategy, branchUpdate); RefUpdate update = updateBranch(strategy, branchUpdate);
reopen = true; reopen = true;
updateChangeStatus(toSubmit.get(submitType), mergeTip); updateChangeStatus(toSubmit.get(submitType), mergeTip);
@@ -417,11 +414,10 @@ public class MergeOp {
return mergeTip; return mergeTip;
} }
private SubmitStrategy createStrategy(SubmitType submitType, private SubmitStrategy createStrategy(SubmitType submitType)
BatchRefUpdate branchUpdate)
throws MergeException, NoSuchProjectException { throws MergeException, NoSuchProjectException {
return submitStrategyFactory.create(submitType, db, repo, rw, inserter, return submitStrategyFactory.create(submitType, db, repo, rw, inserter,
canMergeFlag, branchUpdate, getAlreadyAccepted(branchTip), destBranch); canMergeFlag, getAlreadyAccepted(branchTip), destBranch);
} }
private void openRepository() throws MergeException, NoSuchProjectException { private void openRepository() throws MergeException, NoSuchProjectException {
@@ -434,23 +430,25 @@ 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(inserter.newReader()); rw = CodeReviewCommit.newRevWalk(repo);
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 BatchRefUpdate openBranch() private RefUpdate openBranch()
throws MergeException, OrmException, NoSuchChangeException { throws MergeException, OrmException, NoSuchChangeException {
try { try {
BatchRefUpdate branchUpdate = repo.getRefDatabase().newBatchUpdate(); RefUpdate branchUpdate = repo.updateRef(destBranch.get());
Ref oldRef = repo.getRef(destBranch.get()); if (branchUpdate.getOldObjectId() != null) {
oldBranchTip = oldRef != null ? oldRef.getObjectId() : ObjectId.zeroId(); branchTip =
if (!ObjectId.zeroId().equals(oldBranchTip)) { (CodeReviewCommit) rw.parseCommit(branchUpdate.getOldObjectId());
branchTip = (CodeReviewCommit) rw.parseCommit(oldBranchTip);
} else if (repo.getFullBranch().equals(destBranch.get())) { } else if (repo.getFullBranch().equals(destBranch.get())) {
branchTip = null; branchTip = null;
branchUpdate.setExpectedOldObjectId(ObjectId.zeroId());
} else { } else {
for (ChangeData cd : queryProvider.get().submitted(destBranch)) { for (ChangeData cd : queryProvider.get().submitted(destBranch)) {
try { try {
@@ -659,8 +657,8 @@ public class MergeOp {
} }
} }
private BatchRefUpdate updateBranch(SubmitStrategy strategy, private RefUpdate updateBranch(SubmitStrategy strategy,
BatchRefUpdate branchUpdate) throws MergeException { RefUpdate branchUpdate) throws MergeException {
CodeReviewCommit currentTip = CodeReviewCommit currentTip =
mergeTip != null ? mergeTip.getCurrentTip() : null; mergeTip != null ? mergeTip.getCurrentTip() : null;
if (Objects.equals(branchTip, currentTip)) { if (Objects.equals(branchTip, currentTip)) {
@@ -672,7 +670,7 @@ public class MergeOp {
return null; return null;
} }
if (RefNames.REFS_CONFIG.equals(destBranch.get())) { if (RefNames.REFS_CONFIG.equals(branchUpdate.getName())) {
logDebug("Loading new configuration from {}", RefNames.REFS_CONFIG); logDebug("Loading new configuration from {}", RefNames.REFS_CONFIG);
try { try {
ProjectConfig cfg = ProjectConfig cfg =
@@ -684,34 +682,27 @@ 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.setAllowNonFastForwards(false); branchUpdate.setForceUpdate(false);
// TODO(dborowitz): This message is also used by all new patch set ref branchUpdate.setNewObjectId(currentTip);
// updates; find a better wording that works for that case too.
branchUpdate.setRefLogMessage("merged", true); branchUpdate.setRefLogMessage("merged", true);
ReceiveCommand cmd =
new ReceiveCommand(oldBranchTip, currentTip, destBranch.get());
branchUpdate.addCommand(cmd);
try { try {
branchUpdate.execute(rw, NullProgressMonitor.INSTANCE); RefUpdate.Result result = branchUpdate.update(rw);
logDebug("Executed batch update: {}", branchUpdate); logDebug("Update of {}: {}..{} returned status {}",
switch (cmd.getResult()) { branchUpdate.getName(), branchUpdate.getOldObjectId(),
case OK: branchUpdate.getNewObjectId(), result);
if (cmd.getType() == ReceiveCommand.Type.UPDATE) { switch (result) {
case NEW:
case FAST_FORWARD:
if (branchUpdate.getResult() == RefUpdate.Result.FAST_FORWARD) {
tagCache.updateFastForward(destBranch.getParentKey(), tagCache.updateFastForward(destBranch.getParentKey(),
destBranch.get(), branchUpdate.getName(),
oldBranchTip, branchUpdate.getOldObjectId(),
currentTip); currentTip);
} }
if (RefNames.REFS_CONFIG.equals(destBranch.get())) { if (RefNames.REFS_CONFIG.equals(branchUpdate.getName())) {
Project p = destProject.getProject(); Project p = destProject.getProject();
projectCache.evict(p); projectCache.evict(p);
destProject = projectCache.get(p.getNameKey()); destProject = projectCache.get(p.getNameKey());
@@ -730,27 +721,23 @@ public class MergeOp {
} else { } else {
msg = "will not retry"; msg = "will not retry";
} }
throw new IOException(cmd.getResult().name() + ", " + msg // TODO(dborowitz): Implement RefUpdate.toString().
throw new IOException(branchUpdate.getResult().name() + ", " + msg
+ '\n' + branchUpdate); + '\n' + branchUpdate);
default: default:
throw new IOException(cmd.getResult().name() + '\n' + branchUpdate); throw new IOException(branchUpdate.getResult().name()
+ '\n' + branchUpdate);
} }
} catch (IOException e) { } catch (IOException e) {
throw new MergeException("Cannot update " + destBranch.get(), e); throw new MergeException("Cannot update " + branchUpdate.getName(), e);
} }
} }
private void fireRefUpdated(BatchRefUpdate branchUpdate) { private void fireRefUpdated(RefUpdate branchUpdate) {
logDebug("Firing ref updated hooks for {}", destBranch.get()); logDebug("Firing ref updated hooks for {}", branchUpdate.getName());
gitRefUpdated.fire(destBranch.getParentKey(), branchUpdate); gitRefUpdated.fire(destBranch.getParentKey(), branchUpdate);
Account account = getAccount(mergeTip.getCurrentTip()); hooks.doRefUpdatedHook(destBranch, branchUpdate,
for (ReceiveCommand cmd : branchUpdate.getCommands()) { getAccount(mergeTip.getCurrentTip()));
if (cmd.getRefName().equals(destBranch.get())) {
hooks.doRefUpdatedHook(
destBranch, cmd.getOldId(), cmd.getNewId(), account);
break;
}
}
} }
private Account getAccount(CodeReviewCommit codeReviewCommit) { private Account getAccount(CodeReviewCommit codeReviewCommit) {

View File

@@ -43,6 +43,7 @@ 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;
@@ -66,6 +67,7 @@ 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;
@@ -77,12 +79,6 @@ 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 =
@@ -181,7 +177,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(false, mergeTip, originalCommit)) { if (m.merge(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");
@@ -193,7 +189,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(inserter.insert(mergeCommit)); return rw.parseCommit(commit(inserter, mergeCommit));
} else { } else {
throw new MergeConflictException("merge conflict"); throw new MergeConflictException("merge conflict");
} }
@@ -397,7 +393,7 @@ public class MergeUtil {
ThreeWayMerger m = newThreeWayMerger(repo, createDryRunInserter(repo)); ThreeWayMerger m = newThreeWayMerger(repo, createDryRunInserter(repo));
try { try {
return m.merge(false, mergeTip, toMerge); return m.merge(new AnyObjectId[] {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;
@@ -446,7 +442,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(false, mergeTip, toMerge); return m.merge(mergeTip, toMerge);
} catch (IOException e) { } catch (IOException e) {
throw new MergeException("Cannot merge " + toMerge.name(), e); throw new MergeException("Cannot merge " + toMerge.name(), e);
} }
@@ -498,7 +494,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(false, mergeTip, n)) { if (m.merge(new AnyObjectId[] {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 {
@@ -586,7 +582,7 @@ public class MergeUtil {
mergeCommit.setMessage(msgbuf.toString()); mergeCommit.setMessage(msgbuf.toString());
CodeReviewCommit mergeResult = CodeReviewCommit mergeResult =
(CodeReviewCommit) rw.parseCommit(inserter.insert(mergeCommit)); (CodeReviewCommit) rw.parseCommit(commit(inserter, mergeCommit));
mergeResult.setControl(n.getControl()); mergeResult.setControl(n.getControl());
return mergeResult; return mergeResult;
} }
@@ -660,10 +656,31 @@ 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(inserter); m.setObjectInserter(new ObjectInserter.Filter() {
@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

@@ -24,6 +24,7 @@ import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeUtil; import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.CodeReviewCommit; import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.CommitMergeStatus; import com.google.gerrit.server.git.CommitMergeStatus;
import com.google.gerrit.server.git.MergeConflictException; import com.google.gerrit.server.git.MergeConflictException;
@@ -36,8 +37,8 @@ import com.google.gwtorm.server.OrmException;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.ReceiveCommand;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@@ -49,13 +50,16 @@ import java.util.Map;
public class CherryPick extends SubmitStrategy { public class CherryPick extends SubmitStrategy {
private final PatchSetInfoFactory patchSetInfoFactory; private final PatchSetInfoFactory patchSetInfoFactory;
private final GitReferenceUpdated gitRefUpdated;
private final Map<Change.Id, CodeReviewCommit> newCommits; private final Map<Change.Id, CodeReviewCommit> newCommits;
CherryPick(SubmitStrategy.Arguments args, CherryPick(SubmitStrategy.Arguments args,
PatchSetInfoFactory patchSetInfoFactory) { PatchSetInfoFactory patchSetInfoFactory,
GitReferenceUpdated gitRefUpdated) {
super(args); super(args);
this.patchSetInfoFactory = patchSetInfoFactory; this.patchSetInfoFactory = patchSetInfoFactory;
this.gitRefUpdated = gitRefUpdated;
this.newCommits = new HashMap<>(); this.newCommits = new HashMap<>();
} }
@@ -170,6 +174,8 @@ public class CherryPick extends SubmitStrategy {
ps.setUploader(cherryPickUser.getAccountId()); ps.setUploader(cherryPickUser.getAccountId());
ps.setRevision(new RevId(newCommit.getId().getName())); ps.setRevision(new RevId(newCommit.getId().getName()));
RefUpdate ru;
args.db.changes().beginTransaction(n.change().getId()); args.db.changes().beginTransaction(n.change().getId());
try { try {
insertAncestors(args.db, ps.getId(), newCommit); insertAncestors(args.db, ps.getId(), newCommit);
@@ -185,14 +191,23 @@ public class CherryPick extends SubmitStrategy {
} }
args.db.patchSetApprovals().insert(approvals); args.db.patchSetApprovals().insert(approvals);
args.batchRefUpdate.addCommand( ru = args.repo.updateRef(ps.getRefName());
new ReceiveCommand(ObjectId.zeroId(), newCommit, ps.getRefName())); ru.setExpectedOldObjectId(ObjectId.zeroId());
ru.setNewObjectId(newCommit);
ru.disableRefLog();
if (ru.update(args.rw) != RefUpdate.Result.NEW) {
throw new IOException(String.format(
"Failed to create ref %s in %s: %s", ps.getRefName(), n.change()
.getDest().getParentKey().get(), ru.getResult()));
}
args.db.commit(); args.db.commit();
} finally { } finally {
args.db.rollback(); args.db.rollback();
} }
gitRefUpdated.fire(n.change().getProject(), ru);
newCommit.copyFrom(n); newCommit.copyFrom(n);
newCommit.setStatusCode(CommitMergeStatus.CLEAN_PICK); newCommit.setStatusCode(CommitMergeStatus.CLEAN_PICK);
newCommit.setControl(args.changeControlFactory.controlFor(n.change(), cherryPickUser)); newCommit.setControl(args.changeControlFactory.controlFor(n.change(), cherryPickUser));

View File

@@ -87,11 +87,11 @@ public class RebaseIfNecessary extends SubmitStrategy {
IdentifiedUser uploader = IdentifiedUser uploader =
args.identifiedUserFactory.create(args.mergeUtil args.identifiedUserFactory.create(args.mergeUtil
.getSubmitter(n).getAccountId()); .getSubmitter(n).getAccountId());
PatchSet newPatchSet = rebaseChange.rebase(args.repo, args.rw, PatchSet newPatchSet =
args.inserter, n.getPatchsetId(), n.change(), uploader, rebaseChange.rebase(args.repo, args.rw, args.inserter,
mergeTip.getCurrentTip(), args.mergeUtil, n.getPatchsetId(), n.change(), uploader,
args.serverIdent.get(), false, false, ValidatePolicy.NONE, mergeTip.getCurrentTip(), args.mergeUtil,
args.batchRefUpdate); args.serverIdent.get(), false, false, ValidatePolicy.NONE);
List<PatchSetApproval> approvals = Lists.newArrayList(); List<PatchSetApproval> approvals = Lists.newArrayList();
for (PatchSetApproval a : args.approvalsUtil.byPatchSet(args.db, for (PatchSetApproval a : args.approvalsUtil.byPatchSet(args.db,

View File

@@ -30,7 +30,6 @@ import com.google.gerrit.server.index.ChangeIndexer;
import com.google.gerrit.server.project.ChangeControl; import com.google.gerrit.server.project.ChangeControl;
import com.google.inject.Provider; import com.google.inject.Provider;
import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.RefUpdate.Result;
@@ -61,7 +60,6 @@ public abstract class SubmitStrategy {
protected final RevWalk rw; protected final RevWalk rw;
protected final ObjectInserter inserter; protected final ObjectInserter inserter;
protected final RevFlag canMergeFlag; protected final RevFlag canMergeFlag;
protected final BatchRefUpdate batchRefUpdate;
protected final Set<RevCommit> alreadyAccepted; protected final Set<RevCommit> alreadyAccepted;
protected final Branch.NameKey destBranch; protected final Branch.NameKey destBranch;
protected final ApprovalsUtil approvalsUtil; protected final ApprovalsUtil approvalsUtil;
@@ -73,9 +71,9 @@ public abstract class SubmitStrategy {
Provider<PersonIdent> serverIdent, ReviewDb db, Provider<PersonIdent> serverIdent, ReviewDb db,
ChangeControl.GenericFactory changeControlFactory, Repository repo, ChangeControl.GenericFactory changeControlFactory, Repository repo,
RevWalk rw, ObjectInserter inserter, RevFlag canMergeFlag, RevWalk rw, ObjectInserter inserter, RevFlag canMergeFlag,
BatchRefUpdate batchRefUpdate, Set<RevCommit> alreadyAccepted, Set<RevCommit> alreadyAccepted, Branch.NameKey destBranch,
Branch.NameKey destBranch, ApprovalsUtil approvalsUtil, ApprovalsUtil approvalsUtil, MergeUtil mergeUtil,
MergeUtil mergeUtil, ChangeIndexer indexer) { ChangeIndexer indexer) {
this.identifiedUserFactory = identifiedUserFactory; this.identifiedUserFactory = identifiedUserFactory;
this.serverIdent = serverIdent; this.serverIdent = serverIdent;
this.db = db; this.db = db;
@@ -84,7 +82,6 @@ public abstract class SubmitStrategy {
this.repo = repo; this.repo = repo;
this.rw = rw; this.rw = rw;
this.inserter = inserter; this.inserter = inserter;
this.batchRefUpdate = batchRefUpdate;
this.canMergeFlag = canMergeFlag; this.canMergeFlag = canMergeFlag;
this.alreadyAccepted = alreadyAccepted; this.alreadyAccepted = alreadyAccepted;
this.destBranch = destBranch; this.destBranch = destBranch;

View File

@@ -21,6 +21,7 @@ import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.GerritPersonIdent; import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.changedetail.RebaseChange; import com.google.gerrit.server.changedetail.RebaseChange;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.MergeException; import com.google.gerrit.server.git.MergeException;
import com.google.gerrit.server.git.MergeUtil; import com.google.gerrit.server.git.MergeUtil;
import com.google.gerrit.server.index.ChangeIndexer; import com.google.gerrit.server.index.ChangeIndexer;
@@ -33,7 +34,6 @@ import com.google.inject.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
@@ -55,6 +55,7 @@ public class SubmitStrategyFactory {
private final Provider<PersonIdent> myIdent; private final Provider<PersonIdent> myIdent;
private final ChangeControl.GenericFactory changeControlFactory; private final ChangeControl.GenericFactory changeControlFactory;
private final PatchSetInfoFactory patchSetInfoFactory; private final PatchSetInfoFactory patchSetInfoFactory;
private final GitReferenceUpdated gitRefUpdated;
private final RebaseChange rebaseChange; private final RebaseChange rebaseChange;
private final ProjectCache projectCache; private final ProjectCache projectCache;
private final ApprovalsUtil approvalsUtil; private final ApprovalsUtil approvalsUtil;
@@ -67,7 +68,7 @@ public class SubmitStrategyFactory {
@GerritPersonIdent Provider<PersonIdent> myIdent, @GerritPersonIdent Provider<PersonIdent> myIdent,
final ChangeControl.GenericFactory changeControlFactory, final ChangeControl.GenericFactory changeControlFactory,
final PatchSetInfoFactory patchSetInfoFactory, final PatchSetInfoFactory patchSetInfoFactory,
final RebaseChange rebaseChange, final GitReferenceUpdated gitRefUpdated, final RebaseChange rebaseChange,
final ProjectCache projectCache, final ProjectCache projectCache,
final ApprovalsUtil approvalsUtil, final ApprovalsUtil approvalsUtil,
final MergeUtil.Factory mergeUtilFactory, final MergeUtil.Factory mergeUtilFactory,
@@ -76,6 +77,7 @@ public class SubmitStrategyFactory {
this.myIdent = myIdent; this.myIdent = myIdent;
this.changeControlFactory = changeControlFactory; this.changeControlFactory = changeControlFactory;
this.patchSetInfoFactory = patchSetInfoFactory; this.patchSetInfoFactory = patchSetInfoFactory;
this.gitRefUpdated = gitRefUpdated;
this.rebaseChange = rebaseChange; this.rebaseChange = rebaseChange;
this.projectCache = projectCache; this.projectCache = projectCache;
this.approvalsUtil = approvalsUtil; this.approvalsUtil = approvalsUtil;
@@ -83,19 +85,20 @@ public class SubmitStrategyFactory {
this.indexer = indexer; this.indexer = indexer;
} }
public SubmitStrategy create(SubmitType submitType, ReviewDb db, public SubmitStrategy create(final SubmitType submitType, final ReviewDb db,
Repository repo, RevWalk rw, ObjectInserter inserter, final Repository repo, final RevWalk rw, final ObjectInserter inserter,
RevFlag canMergeFlag, BatchRefUpdate batchRefUpdated, final RevFlag canMergeFlag, final Set<RevCommit> alreadyAccepted,
Set<RevCommit> alreadyAccepted, Branch.NameKey destBranch) final Branch.NameKey destBranch)
throws MergeException, NoSuchProjectException { throws MergeException, NoSuchProjectException {
ProjectState project = getProject(destBranch); ProjectState project = getProject(destBranch);
SubmitStrategy.Arguments args = new SubmitStrategy.Arguments( final SubmitStrategy.Arguments args =
identifiedUserFactory, myIdent, db, changeControlFactory, repo, rw, new SubmitStrategy.Arguments(identifiedUserFactory, myIdent, db,
inserter, canMergeFlag, batchRefUpdated, alreadyAccepted, destBranch, changeControlFactory, repo, rw, inserter, canMergeFlag,
approvalsUtil, mergeUtilFactory.create(project), indexer); alreadyAccepted, destBranch,approvalsUtil,
mergeUtilFactory.create(project), indexer);
switch (submitType) { switch (submitType) {
case CHERRY_PICK: case CHERRY_PICK:
return new CherryPick(args, patchSetInfoFactory); return new CherryPick(args, patchSetInfoFactory, gitRefUpdated);
case FAST_FORWARD_ONLY: case FAST_FORWARD_ONLY:
return new FastForwardOnly(args); return new FastForwardOnly(args);
case MERGE_ALWAYS: case MERGE_ALWAYS:

View File

@@ -275,11 +275,24 @@ 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(ins); m.setObjectInserter(new ObjectInserter.Filter() {
@Override
protected ObjectInserter delegate() {
return ins;
}
@Override
public void flush() {
}
@Override
public void release() {
}
});
boolean couldMerge; boolean couldMerge;
try { try {
couldMerge = m.merge(false, b.getParents()); couldMerge = m.merge(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

View File

@@ -119,7 +119,7 @@ class ConflictsPredicate extends OrPredicate<ChangeData> {
SubmitStrategy strategy = SubmitStrategy strategy =
args.submitStrategyFactory.create(submitType, args.submitStrategyFactory.create(submitType,
db.get(), repo, rw, null, canMergeFlag, db.get(), repo, rw, null, canMergeFlag,
null, getAlreadyAccepted(repo, rw, commit), getAlreadyAccepted(repo, rw, commit),
otherChange.getDest()); otherChange.getDest());
CodeReviewCommit otherCommit = CodeReviewCommit otherCommit =
(CodeReviewCommit) rw.parseCommit(other); (CodeReviewCommit) rw.parseCommit(other);