Cherry-Pick: Support --mainline to pick merges

By specifying a mainline parent, a merge is cherry picked as if this
parent was its only parent. If no mainline parent is given, cherry
picking merges is not allowed, as before.

Change-Id: I391cb73bf8f49e2df61428c17b40fae8c86a8b76
Signed-off-by: Konrad Kügler <swamblumat-eclipsebugs@yahoo.de>
This commit is contained in:
Konrad Kügler 2014-05-16 18:42:44 +02:00
parent 2aa2b3af31
commit b84057ad62
4 changed files with 101 additions and 9 deletions

View File

@ -46,6 +46,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.File;
import java.io.IOException;
@ -55,11 +56,13 @@
import org.eclipse.jgit.api.ResetCommand.ResetType;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.api.errors.MultipleParentsNotAllowedException;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ReflogReader;
import org.eclipse.jgit.lib.RepositoryState;
import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
@ -336,4 +339,59 @@ private void doCherryPickAndCheckResult(final Git git,
.startsWith("cherry-pick: "));
}
}
/**
* Cherry-picking merge commit M onto T
* <pre>
* M
* |\
* C D
* |/
* T B
* | /
* A
* </pre>
* @throws Exception
*/
@Test
public void testCherryPickMerge() throws Exception {
Git git = new Git(db);
commitFile("file", "1\n2\n3\n", "master");
commitFile("file", "1\n2\n3\n", "side");
checkoutBranch("refs/heads/side");
RevCommit commitD = commitFile("file", "1\n2\n3\n4\n5\n", "side2");
commitFile("file", "a\n2\n3\n", "side");
MergeResult mergeResult = git.merge().include(commitD).call();
ObjectId commitM = mergeResult.getNewHead();
checkoutBranch("refs/heads/master");
RevCommit commitT = commitFile("another", "t", "master");
try {
git.cherryPick().include(commitM).call();
fail("merges should not be cherry-picked by default");
} catch (MultipleParentsNotAllowedException e) {
// expected
}
try {
git.cherryPick().include(commitM).setMainlineParentNumber(3).call();
fail("specifying a non-existent parent should fail");
} catch (JGitInternalException e) {
// expected
assertTrue(e.getMessage().endsWith(
"does not have a parent number 3."));
}
CherryPickResult result = git.cherryPick().include(commitM)
.setMainlineParentNumber(1).call();
assertEquals(CherryPickStatus.OK, result.getStatus());
checkFile(new File(db.getWorkTree(), "file"), "1\n2\n3\n4\n5\n");
git.reset().setMode(ResetType.HARD).setRef(commitT.getName()).call();
CherryPickResult result2 = git.cherryPick().include(commitM)
.setMainlineParentNumber(2).call();
assertEquals(CherryPickStatus.OK, result2.getStatus());
checkFile(new File(db.getWorkTree(), "file"), "a\n2\n3\n");
}
}

View File

@ -85,6 +85,7 @@ cannotUnloadAModifiedTree=Cannot unload a modified tree.
cannotWorkWithOtherStagesThanZeroRightNow=Cannot work with other stages than zero right now. Won't write corrupt index.
canOnlyCherryPickCommitsWithOneParent=Cannot cherry-pick commit ''{0}'' because it has {1} parents, only commits with exactly one parent are supported.
canOnlyRevertCommitsWithOneParent=Cannot revert commit ''{0}'' because it has {1} parents, only commits with exactly one parent are supported
commitDoesNotHaveGivenParent=The commit ''{0}'' does not have a parent number {1}.
cantFindObjectInReversePackIndexForTheSpecifiedOffset=Can't find object in (reverse) pack index for the specified offset {0}
cantPassMeATree=Can't pass me a tree!
channelMustBeInRange0_255=channel {0} must be in range [0, 255]

View File

@ -56,6 +56,7 @@
import org.eclipse.jgit.api.errors.UnmergedPathsException;
import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
import org.eclipse.jgit.dircache.DirCacheCheckout;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
@ -90,6 +91,8 @@ public class CherryPickCommand extends GitCommand<CherryPickResult> {
private MergeStrategy strategy = MergeStrategy.RECURSIVE;
private Integer mainlineParentNumber;
/**
* @param repo
*/
@ -139,15 +142,7 @@ public CherryPickResult call() throws GitAPIException, NoMessageException,
RevCommit srcCommit = revWalk.parseCommit(srcObjectId);
// get the parent of the commit to cherry-pick
if (srcCommit.getParentCount() != 1)
throw new MultipleParentsNotAllowedException(
MessageFormat.format(
JGitText.get().canOnlyCherryPickCommitsWithOneParent,
srcCommit.name(),
Integer.valueOf(srcCommit.getParentCount())));
RevCommit srcParent = srcCommit.getParent(0);
revWalk.parseHeaders(srcParent);
final RevCommit srcParent = getParentCommit(srcCommit, revWalk);
String ourName = calculateOurName(headRef);
String cherryPickName = srcCommit.getId().abbreviate(7).name()
@ -200,6 +195,31 @@ public CherryPickResult call() throws GitAPIException, NoMessageException,
return new CherryPickResult(newHead, cherryPickedRefs);
}
private RevCommit getParentCommit(RevCommit srcCommit, RevWalk revWalk)
throws MultipleParentsNotAllowedException, MissingObjectException,
IOException {
final RevCommit srcParent;
if (mainlineParentNumber == null) {
if (srcCommit.getParentCount() != 1)
throw new MultipleParentsNotAllowedException(
MessageFormat.format(
JGitText.get().canOnlyCherryPickCommitsWithOneParent,
srcCommit.name(),
Integer.valueOf(srcCommit.getParentCount())));
srcParent = srcCommit.getParent(0);
} else {
if (mainlineParentNumber.intValue() > srcCommit.getParentCount())
throw new JGitInternalException(MessageFormat.format(
JGitText.get().commitDoesNotHaveGivenParent, srcCommit,
mainlineParentNumber));
srcParent = srcCommit
.getParent(mainlineParentNumber.intValue() - 1);
}
revWalk.parseHeaders(srcParent);
return srcParent;
}
/**
* @param commit
* a reference to a commit which is cherry-picked to the current
@ -271,6 +291,18 @@ public CherryPickCommand setStrategy(MergeStrategy strategy) {
return this;
}
/**
* @param mainlineParentNumber
* the (1-based) parent number to diff against. This allows
* cherry-picking of merges.
* @return {@code this}
* @since 3.4
*/
public CherryPickCommand setMainlineParentNumber(int mainlineParentNumber) {
this.mainlineParentNumber = Integer.valueOf(mainlineParentNumber);
return this;
}
private String calculateOurName(Ref headRef) {
if (ourCommitName != null)
return ourCommitName;

View File

@ -147,6 +147,7 @@ public static JGitText get() {
/***/ public String cannotWorkWithOtherStagesThanZeroRightNow;
/***/ public String canOnlyCherryPickCommitsWithOneParent;
/***/ public String canOnlyRevertCommitsWithOneParent;
/***/ public String commitDoesNotHaveGivenParent;
/***/ public String cantFindObjectInReversePackIndexForTheSpecifiedOffset;
/***/ public String cantPassMeATree;
/***/ public String channelMustBeInRange0_255;