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>
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 0d38197..301d6be 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<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 {
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 ceba89d..5f8c2b7 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()) {