Support cherry-picking a root commit

Handle the case of the commit to be picked not having any parents.

Since JGit implements cherry-pick as a 3-way-merge between the commit
to be picked and the target commit, using the parent of the picked
commit as merge base, this is super simple: just don't set a base tree.
The merger will not find any merge base and will supply an empty tree
iterator for the base.

Bug: 581832
Change-Id: I88985f1b1723db5b35ce58bf228bc48d23d6fca3
Signed-off-by: Thomas Wolf <twolf@apache.org>
This commit is contained in:
Thomas Wolf 2023-04-23 21:31:40 +02:00
parent 3ed4cdda6b
commit 8bc13fb79d
2 changed files with 69 additions and 31 deletions

View File

@ -100,41 +100,73 @@ private void doTestCherryPick(boolean noCommit) throws IOException,
} }
} }
@Test @Test
public void testSequentialCherryPick() throws IOException, JGitInternalException, public void testSequentialCherryPick()
GitAPIException { throws IOException, JGitInternalException, GitAPIException {
try (Git git = new Git(db)) { try (Git git = new Git(db)) {
writeTrashFile("a", "first line\nsec. line\nthird line\n"); writeTrashFile("a", "first line\nsec. line\nthird line\n");
git.add().addFilepattern("a").call(); git.add().addFilepattern("a").call();
RevCommit firstCommit = git.commit().setMessage("create a").call(); RevCommit firstCommit = git.commit().setMessage("create a").call();
writeTrashFile("a", "first line\nsec. line\nthird line\nfourth line\n"); writeTrashFile("a",
git.add().addFilepattern("a").call(); "first line\nsec. line\nthird line\nfourth line\n");
RevCommit enlargingA = git.commit().setMessage("enlarged a").call(); git.add().addFilepattern("a").call();
RevCommit enlargingA = git.commit().setMessage("enlarged a").call();
writeTrashFile("a", writeTrashFile("a",
"first line\nsecond line\nthird line\nfourth line\n"); "first line\nsecond line\nthird line\nfourth line\n");
git.add().addFilepattern("a").call(); git.add().addFilepattern("a").call();
RevCommit fixingA = git.commit().setMessage("fixed a").call(); RevCommit fixingA = git.commit().setMessage("fixed a").call();
git.branchCreate().setName("side").setStartPoint(firstCommit).call(); git.branchCreate().setName("side").setStartPoint(firstCommit)
checkoutBranch("refs/heads/side"); .call();
checkoutBranch("refs/heads/side");
writeTrashFile("b", "nothing to do with a"); writeTrashFile("b", "nothing to do with a");
git.add().addFilepattern("b").call(); git.add().addFilepattern("b").call();
git.commit().setMessage("create b").call(); git.commit().setMessage("create b").call();
CherryPickResult result = git.cherryPick().include(enlargingA).include(fixingA).call(); CherryPickResult result = git.cherryPick().include(enlargingA)
assertEquals(CherryPickResult.CherryPickStatus.OK, result.getStatus()); .include(fixingA).call();
assertEquals(CherryPickResult.CherryPickStatus.OK,
result.getStatus());
Iterator<RevCommit> history = git.log().call().iterator(); Iterator<RevCommit> history = git.log().call().iterator();
assertEquals("fixed a", history.next().getFullMessage()); assertEquals("fixed a", history.next().getFullMessage());
assertEquals("enlarged a", history.next().getFullMessage()); assertEquals("enlarged a", history.next().getFullMessage());
assertEquals("create b", history.next().getFullMessage()); assertEquals("create b", history.next().getFullMessage());
assertEquals("create a", history.next().getFullMessage()); assertEquals("create a", history.next().getFullMessage());
assertFalse(history.hasNext()); assertFalse(history.hasNext());
} }
} }
@Test
public void testRootCherryPick()
throws IOException, JGitInternalException, GitAPIException {
try (Git git = new Git(db)) {
writeTrashFile("a", "a");
writeTrashFile("b", "b");
git.add().addFilepattern("a").addFilepattern("b").call();
RevCommit firstCommit = git.commit().setMessage("Create a and b")
.call();
git.checkout().setOrphan(true).setName("orphan").call();
git.rm().addFilepattern("a").addFilepattern("b").call();
writeTrashFile("a", "a");
git.add().addFilepattern("a").call();
git.commit().setMessage("Orphan a").call();
CherryPickResult result = git.cherryPick().include(firstCommit)
.call();
assertEquals(CherryPickResult.CherryPickStatus.OK,
result.getStatus());
Iterator<RevCommit> history = git.log().call().iterator();
assertEquals("Create a and b", history.next().getFullMessage());
assertEquals("Orphan a", history.next().getFullMessage());
assertFalse(history.hasNext());
}
}
@Test @Test
public void testCherryPickDirtyIndex() throws Exception { public void testCherryPickDirtyIndex() throws Exception {

View File

@ -142,7 +142,9 @@ public CherryPickResult call() throws GitAPIException, NoMessageException,
new String[] { "BASE", ourName, cherryPickName }); //$NON-NLS-1$ new String[] { "BASE", ourName, cherryPickName }); //$NON-NLS-1$
resolveMerger resolveMerger
.setWorkingTreeIterator(new FileTreeIterator(repo)); .setWorkingTreeIterator(new FileTreeIterator(repo));
resolveMerger.setBase(srcParent.getTree()); if (srcParent != null) {
resolveMerger.setBase(srcParent.getTree());
}
noProblems = merger.merge(newHead, srcCommit); noProblems = merger.merge(newHead, srcCommit);
failingPaths = resolveMerger.getFailingPaths(); failingPaths = resolveMerger.getFailingPaths();
unmergedPaths = resolveMerger.getUnmergedPaths(); unmergedPaths = resolveMerger.getUnmergedPaths();
@ -217,12 +219,16 @@ private RevCommit getParentCommit(RevCommit srcCommit, RevWalk revWalk)
IOException { IOException {
final RevCommit srcParent; final RevCommit srcParent;
if (mainlineParentNumber == null) { if (mainlineParentNumber == null) {
if (srcCommit.getParentCount() != 1) int nOfParents = srcCommit.getParentCount();
if (nOfParents == 0) {
return null;
} else if (nOfParents != 1) {
throw new MultipleParentsNotAllowedException( throw new MultipleParentsNotAllowedException(
MessageFormat.format( MessageFormat.format(
JGitText.get().canOnlyCherryPickCommitsWithOneParent, JGitText.get().canOnlyCherryPickCommitsWithOneParent,
srcCommit.name(), srcCommit.name(),
Integer.valueOf(srcCommit.getParentCount()))); Integer.valueOf(srcCommit.getParentCount())));
}
srcParent = srcCommit.getParent(0); srcParent = srcCommit.getParent(0);
} else { } else {
if (mainlineParentNumber.intValue() > srcCommit.getParentCount()) { if (mainlineParentNumber.intValue() > srcCommit.getParentCount()) {