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
public void testSequentialCherryPick() throws IOException, JGitInternalException,
GitAPIException {
try (Git git = new Git(db)) {
writeTrashFile("a", "first line\nsec. line\nthird line\n");
git.add().addFilepattern("a").call();
RevCommit firstCommit = git.commit().setMessage("create a").call();
@Test
public void testSequentialCherryPick()
throws IOException, JGitInternalException, GitAPIException {
try (Git git = new Git(db)) {
writeTrashFile("a", "first line\nsec. line\nthird line\n");
git.add().addFilepattern("a").call();
RevCommit firstCommit = git.commit().setMessage("create a").call();
writeTrashFile("a", "first line\nsec. line\nthird line\nfourth line\n");
git.add().addFilepattern("a").call();
RevCommit enlargingA = git.commit().setMessage("enlarged a").call();
writeTrashFile("a",
"first line\nsec. line\nthird line\nfourth line\n");
git.add().addFilepattern("a").call();
RevCommit enlargingA = git.commit().setMessage("enlarged a").call();
writeTrashFile("a",
"first line\nsecond line\nthird line\nfourth line\n");
git.add().addFilepattern("a").call();
RevCommit fixingA = git.commit().setMessage("fixed a").call();
writeTrashFile("a",
"first line\nsecond line\nthird line\nfourth line\n");
git.add().addFilepattern("a").call();
RevCommit fixingA = git.commit().setMessage("fixed a").call();
git.branchCreate().setName("side").setStartPoint(firstCommit).call();
checkoutBranch("refs/heads/side");
git.branchCreate().setName("side").setStartPoint(firstCommit)
.call();
checkoutBranch("refs/heads/side");
writeTrashFile("b", "nothing to do with a");
git.add().addFilepattern("b").call();
git.commit().setMessage("create b").call();
writeTrashFile("b", "nothing to do with a");
git.add().addFilepattern("b").call();
git.commit().setMessage("create b").call();
CherryPickResult result = git.cherryPick().include(enlargingA).include(fixingA).call();
assertEquals(CherryPickResult.CherryPickStatus.OK, result.getStatus());
CherryPickResult result = git.cherryPick().include(enlargingA)
.include(fixingA).call();
assertEquals(CherryPickResult.CherryPickStatus.OK,
result.getStatus());
Iterator<RevCommit> history = git.log().call().iterator();
assertEquals("fixed a", history.next().getFullMessage());
assertEquals("enlarged a", history.next().getFullMessage());
assertEquals("create b", history.next().getFullMessage());
assertEquals("create a", history.next().getFullMessage());
assertFalse(history.hasNext());
}
}
Iterator<RevCommit> history = git.log().call().iterator();
assertEquals("fixed a", history.next().getFullMessage());
assertEquals("enlarged a", history.next().getFullMessage());
assertEquals("create b", history.next().getFullMessage());
assertEquals("create a", history.next().getFullMessage());
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
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$
resolveMerger
.setWorkingTreeIterator(new FileTreeIterator(repo));
resolveMerger.setBase(srcParent.getTree());
if (srcParent != null) {
resolveMerger.setBase(srcParent.getTree());
}
noProblems = merger.merge(newHead, srcCommit);
failingPaths = resolveMerger.getFailingPaths();
unmergedPaths = resolveMerger.getUnmergedPaths();
@ -217,12 +219,16 @@ private RevCommit getParentCommit(RevCommit srcCommit, RevWalk revWalk)
IOException {
final RevCommit srcParent;
if (mainlineParentNumber == null) {
if (srcCommit.getParentCount() != 1)
int nOfParents = srcCommit.getParentCount();
if (nOfParents == 0) {
return null;
} else if (nOfParents != 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()) {