From 8bc13fb79d3ff44f09b67a1effda7155b47391ae Mon Sep 17 00:00:00 2001 From: Thomas Wolf Date: Sun, 23 Apr 2023 21:31:40 +0200 Subject: [PATCH] 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 --- .../jgit/api/CherryPickCommandTest.java | 90 +++++++++++++------ .../eclipse/jgit/api/CherryPickCommand.java | 10 ++- 2 files changed, 69 insertions(+), 31 deletions(-) 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 0d38197d9..301d6be66 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 @@ -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 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 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 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 { 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 ceba89d16..5f8c2b728 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java @@ -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()) {