From 8c0c96e0a7e88a12cb4478fc5983680e24c34855 Mon Sep 17 00:00:00 2001 From: Thomas Wolf Date: Sun, 23 Apr 2023 21:34:37 +0200 Subject: [PATCH] Support rebasing independent branches With completely independent branches, there is no merge base. In this case, the list of commits must include the root commit of the branch to be rebased. Bug: 581832 Change-Id: I0f5bdf179d5b07ff09f1a274d61c7a0b1c0011c6 Signed-off-by: Thomas Wolf --- .../eclipse/jgit/api/RebaseCommandTest.java | 141 ++++++++++++++++++ .../org/eclipse/jgit/api/RebaseCommand.java | 30 ++-- 2 files changed, 160 insertions(+), 11 deletions(-) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java index d574e45f6..7f820b043 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java @@ -76,6 +76,10 @@ public class RebaseCommandTest extends RepositoryTestCase { private static final String FILE1 = "file1"; + private static final String FILE2 = "file2"; + + private static final String FILE3 = "file3"; + protected Git git; @Override @@ -190,6 +194,143 @@ public void testFastForwardWithMultipleCommits() throws Exception { topicLog.get(0).getComment()); } + /** + * Rebase a single root commit onto an independent branch. + * + *
+	 * A (master)
+	 *
+	 * B - C (orphan)
+	 * 
+ * + * to + * + *
+	 * A
+	 *
+	 * B - C (orphan) - A' (master)
+	 * 
+ * + * @throws Exception + */ + @Test + public void testRebaseRootCommit() throws Exception { + writeTrashFile(FILE1, FILE1); + writeTrashFile(FILE2, FILE2); + git.add().addFilepattern(FILE1).addFilepattern(FILE2).call(); + RevCommit first = git.commit().setMessage("Add files").call(); + File file1 = new File(db.getWorkTree(), FILE1); + File file2 = new File(db.getWorkTree(), FILE2); + assertTrue(file1.exists()); + assertTrue(file2.exists()); + // Create an independent branch + git.checkout().setOrphan(true).setName("orphan").call(); + git.rm().addFilepattern(FILE1).addFilepattern(FILE2).call(); + assertFalse(file1.exists()); + assertFalse(file2.exists()); + writeTrashFile(FILE1, "something else"); + git.add().addFilepattern(FILE1).call(); + RevCommit orphanBase = git.commit().setMessage("Orphan base").call(); + writeTrashFile(FILE1, FILE1); + git.add().addFilepattern(FILE1).call(); + RevCommit orphanTop = git.commit().setMessage("Same file1").call(); + checkoutBranch("refs/heads/master"); + assertEquals(first.getId(), db.resolve("HEAD")); + RebaseResult res = git.rebase().setUpstream("refs/heads/orphan").call(); + assertEquals(Status.OK, res.getStatus()); + Iterable log = git.log().add(db.resolve("HEAD")).call(); + ObjectId[] ids = { orphanTop.getId(), orphanBase.getId() }; + int nOfCommits = 0; + for (RevCommit c : log) { + nOfCommits++; + if (nOfCommits == 1) { + assertEquals("Add files", c.getFullMessage()); + } else { + assertEquals(ids[nOfCommits - 2], c.getId()); + } + } + assertEquals(3, nOfCommits); + assertTrue(file1.exists()); + checkFile(file1, FILE1); + assertTrue(file2.exists()); + checkFile(file2, FILE2); + } + + /** + * Rebase a branch onto an independent branch. + * + *
+	 * A - B (master)
+	 *
+	 * C - D (orphan)
+	 * 
+ * + * to + * + *
+	 * A - B
+	 *
+	 * C - D (orphan) - A' - B' (master)
+	 * 
+ * + * @throws Exception + */ + @Test + public void testRebaseNoMergeBase() throws Exception { + writeTrashFile(FILE1, FILE1); + writeTrashFile(FILE2, FILE2); + git.add().addFilepattern(FILE1).addFilepattern(FILE2).call(); + git.commit().setMessage("Add files").call(); + writeTrashFile(FILE3, FILE3); + git.add().addFilepattern(FILE3).call(); + RevCommit first = git.commit().setMessage("File3").call(); + File file1 = new File(db.getWorkTree(), FILE1); + File file2 = new File(db.getWorkTree(), FILE2); + File file3 = new File(db.getWorkTree(), FILE3); + assertTrue(file1.exists()); + assertTrue(file2.exists()); + assertTrue(file3.exists()); + // Create an independent branch + git.checkout().setOrphan(true).setName("orphan").call(); + git.rm() + .addFilepattern(FILE1) + .addFilepattern(FILE2) + .addFilepattern(FILE3) + .call(); + assertFalse(file1.exists()); + assertFalse(file2.exists()); + assertFalse(file3.exists()); + writeTrashFile(FILE1, "something else"); + git.add().addFilepattern(FILE1).call(); + RevCommit orphanBase = git.commit().setMessage("Orphan base").call(); + writeTrashFile(FILE1, FILE1); + git.add().addFilepattern(FILE1).call(); + RevCommit orphanTop = git.commit().setMessage("Same file1").call(); + checkoutBranch("refs/heads/master"); + assertEquals(first.getId(), db.resolve("HEAD")); + RebaseResult res = git.rebase().setUpstream("refs/heads/orphan").call(); + assertEquals(Status.OK, res.getStatus()); + Iterable log = git.log().add(db.resolve("HEAD")).call(); + String[] msgs = { "File3", "Add files" }; + ObjectId[] ids = { orphanTop.getId(), orphanBase.getId() }; + int nOfCommits = 0; + for (RevCommit c : log) { + nOfCommits++; + if (nOfCommits <= msgs.length) { + assertEquals(msgs[nOfCommits - 1], c.getFullMessage()); + } else { + assertEquals(ids[nOfCommits - msgs.length - 1], c.getId()); + } + } + assertEquals(4, nOfCommits); + assertTrue(file1.exists()); + checkFile(file1, FILE1); + assertTrue(file2.exists()); + checkFile(file2, FILE2); + assertTrue(file3.exists()); + checkFile(file3, FILE3); + } + /** * Create the following commits and then attempt to rebase topic onto * master. This will serialize the branches. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java index 1e5523f27..19fd3bb2c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java @@ -1217,7 +1217,7 @@ private List calculatePickList(RevCommit headCommit) Iterator commitsToUse = r.iterator(); while (commitsToUse.hasNext()) { RevCommit commit = commitsToUse.next(); - if (preserveMerges || commit.getParentCount() == 1) { + if (preserveMerges || commit.getParentCount() <= 1) { cherryPickList.add(commit); } } @@ -1234,23 +1234,31 @@ private List calculatePickList(RevCommit headCommit) walk.markStart(upstreamCommit); walk.markStart(headCommit); RevCommit base; - while ((base = walk.next()) != null) + while ((base = walk.next()) != null) { RebaseState.createFile(rewrittenDir, base.getName(), upstreamCommit.getName()); - + } Iterator iterator = cherryPickList.iterator(); pickLoop: while(iterator.hasNext()){ RevCommit commit = iterator.next(); - for (int i = 0; i < commit.getParentCount(); i++) { - boolean parentRewritten = new File(rewrittenDir, commit - .getParent(i).getName()).exists(); - if (parentRewritten) { - new File(rewrittenDir, commit.getName()).createNewFile(); - continue pickLoop; + int nOfParents = commit.getParentCount(); + if (nOfParents == 0) { + // Must be the very first commit in the cherryPickList. We + // have independent branches. + new File(rewrittenDir, commit.getName()).createNewFile(); + } else { + for (int i = 0; i < nOfParents; i++) { + boolean parentRewritten = new File(rewrittenDir, + commit.getParent(i).getName()).exists(); + if (parentRewritten) { + new File(rewrittenDir, commit.getName()) + .createNewFile(); + continue pickLoop; + } } + // commit is only merged in, needs not be rewritten + iterator.remove(); } - // commit is only merged in, needs not be rewritten - iterator.remove(); } } return cherryPickList;