diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java index 2668c116a..df7343580 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CherryPickCommandTest.java @@ -46,6 +46,7 @@ import static org.junit.Assert.assertEquals; 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.CherryPickResult.CherryPickStatus; 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 @@ public class CherryPickCommandTest extends RepositoryTestCase { .startsWith("cherry-pick: ")); } } + + /** + * Cherry-picking merge commit M onto T + *
+	 *    M
+	 *    |\
+	 *    C D
+	 *    |/
+	 * T  B
+	 * | /
+	 * A
+	 * 
+ * @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"); + } } diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties index b3049b07f..7b073217a 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -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] diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java index b3415b350..cf50160da 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java @@ -56,6 +56,7 @@ import org.eclipse.jgit.api.errors.NoMessageException; 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 { private MergeStrategy strategy = MergeStrategy.RECURSIVE; + private Integer mainlineParentNumber; + /** * @param repo */ @@ -139,15 +142,7 @@ public class CherryPickCommand extends GitCommand { 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 class CherryPickCommand extends GitCommand { 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 class CherryPickCommand extends GitCommand { 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; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java index 576ba0f49..39f203ca8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -147,6 +147,7 @@ public class JGitText extends TranslationBundle { /***/ public String cannotWorkWithOtherStagesThanZeroRightNow; /***/ public String canOnlyCherryPickCommitsWithOneParent; /***/ public String canOnlyRevertCommitsWithOneParent; + /***/ public String commitDoesNotHaveGivenParent; /***/ public String cantFindObjectInReversePackIndexForTheSpecifiedOffset; /***/ public String cantPassMeATree; /***/ public String channelMustBeInRange0_255;