@@ -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 | |||
* <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"); | |||
} | |||
} |
@@ -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] |
@@ -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<CherryPickResult> { | |||
private MergeStrategy strategy = MergeStrategy.RECURSIVE; | |||
private Integer mainlineParentNumber; | |||
/** | |||
* @param repo | |||
*/ | |||
@@ -139,15 +142,7 @@ public class CherryPickCommand extends GitCommand<CherryPickResult> { | |||
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<CherryPickResult> { | |||
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<CherryPickResult> { | |||
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; |
@@ -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; |