From 6297491e8adb85e43d60ffe75fb71f335e733449 Mon Sep 17 00:00:00 2001 From: Ronald Bhuleskar Date: Wed, 3 Aug 2022 16:41:43 -0700 Subject: [PATCH] Adds FilteredRevCommit that can overwrites its parents in the DAG. Change-Id: I1ea63a3b56074099688fc45d6a22943a8ae3c2ae --- .../jgit/revwalk/FilteredRevCommitTest.java | 135 ++++++++++++++++++ .../jgit/revwalk/FilteredRevWalkTest.java | 121 ++++++++++++++++ .../jgit/revwalk/FirstParentRevWalkTest.java | 53 +++++-- .../jgit/revwalk/RevWalkFollowFilterTest.java | 15 +- .../jgit/revwalk/RevWalkPathFilter1Test.java | 102 +++++++++---- .../jgit/revwalk/FilteredRevCommit.java | 95 ++++++++++++ .../org/eclipse/jgit/revwalk/RevCommit.java | 18 ++- .../jgit/revwalk/RewriteGenerator.java | 37 ++++- 8 files changed, 523 insertions(+), 53 deletions(-) create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FilteredRevCommitTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FilteredRevWalkTest.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FilteredRevCommit.java diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FilteredRevCommitTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FilteredRevCommitTest.java new file mode 100644 index 000000000..49ce47ef4 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FilteredRevCommitTest.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2022, Google LLC. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * @since 6.3 + */ +package org.eclipse.jgit.revwalk; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertSame; + +import java.util.Arrays; + +import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription; +import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.ObjectLoader; +import org.junit.Before; +import org.junit.Test; + +public class FilteredRevCommitTest { + private TestRepository tr; + + private RevWalk rw; + + @Before + public void setUp() throws Exception { + tr = new TestRepository<>( + new InMemoryRepository(new DfsRepositoryDescription("test"))); + rw = tr.getRevWalk(); + } + + @Test + public void testParseHeaders_noParent() throws Exception { + RevCommit root = tr.commit().add("todelete", "to be deleted").create(); + RevCommit orig = tr.commit().parent(root).rm("todelete") + .add("foo", "foo contents").add("bar", "bar contents") + .add("dir/baz", "baz contents").create(); + FilteredRevCommit filteredRevCommit = new FilteredRevCommit(orig); + filteredRevCommit.parseHeaders(rw); + tr.branch("master").update(filteredRevCommit); + assertEquals("foo contents", blobAsString(filteredRevCommit, "foo")); + assertEquals("bar contents", blobAsString(filteredRevCommit, "bar")); + assertEquals("baz contents", + blobAsString(filteredRevCommit, "dir/baz")); + } + + @Test + public void testParents() throws Exception { + RevCommit commit1 = tr.commit().add("foo", "foo contents\n").create(); + RevCommit commit2 = tr.commit().parent(commit1) + .message("original message").add("bar", "bar contents") + .create(); + RevCommit commit3 = tr.commit().parent(commit2).message("commit3") + .add("foo", "foo contents\n new line\n").create(); + + FilteredRevCommit filteredCommitHead = new FilteredRevCommit(commit3, + commit1); + + assertEquals(commit1, Arrays.stream(filteredCommitHead.getParents()) + .findFirst().get()); + assertEquals("commit3", filteredCommitHead.getFullMessage()); + assertEquals("foo contents\n new line\n", + blobAsString(filteredCommitHead, "foo")); + assertEquals(filteredCommitHead.getTree(), commit3.getTree()); + + } + + @Test + public void testFlag() throws Exception { + RevCommit root = tr.commit().add("todelete", "to be deleted").create(); + RevCommit orig = tr.commit().parent(root).rm("todelete") + .add("foo", "foo contents").add("bar", "bar contents") + .add("dir/baz", "baz contents").create(); + + FilteredRevCommit filteredRevCommit = new FilteredRevCommit(orig, root); + assertEquals(RevObject.PARSED, orig.flags); + assertEquals(RevObject.PARSED, filteredRevCommit.flags); + } + + @Test + public void testCommitState() throws Exception { + RevCommit root = tr.commit().add("todelete", "to be deleted").create(); + RevCommit orig = tr.commit().parent(root).rm("todelete") + .add("foo", "foo contents").add("bar", "bar contents") + .add("dir/baz", "baz contents").create(); + + FilteredRevCommit filteredRevCommit = new FilteredRevCommit(orig, root); + assertEquals(filteredRevCommit.getParentCount(), 1); + assertSame(filteredRevCommit.getRawBuffer(), orig.getRawBuffer()); + assertSame(filteredRevCommit.getTree(), orig.getTree()); + assertEquals(filteredRevCommit.getFullMessage(), orig.getFullMessage()); + assertEquals(filteredRevCommit.commitTime, orig.commitTime); + assertSame(filteredRevCommit.parents, RevCommit.NO_PARENTS); + } + + @Test + public void testParseCommit_withParents_parsesRealParents() + throws Exception { + RevCommit commit1 = tr.commit().add("foo", "foo contents\n").create(); + RevCommit commit2 = tr.commit().parent(commit1) + .message("original message").add("bar", "bar contents") + .create(); + RevCommit commit3 = tr.commit().parent(commit2).message("commit3") + .add("foo", "foo contents\n new line\n").create(); + + FilteredRevCommit filteredCommitHead = new FilteredRevCommit(commit3, + commit1); + + RevCommit parsedCommit = rw.parseCommit(filteredCommitHead.getId()); + assertEquals(filteredCommitHead.getId(), commit3.getId()); + // This is an intended behavior as revWalk#parseCommit doesn't parse + // through the overridden parents rather uses the real parents. + assertNotEquals( + Arrays.stream(parsedCommit.getParents()).findFirst().get(), + Arrays.stream(filteredCommitHead.getParents()).findFirst() + .get()); + } + + private String blobAsString(AnyObjectId treeish, String path) + throws Exception { + RevObject obj = tr.get(rw.parseTree(treeish), path); + assertSame(RevBlob.class, obj.getClass()); + ObjectLoader loader = rw.getObjectReader().open(obj); + return new String(loader.getCachedBytes(), UTF_8); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FilteredRevWalkTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FilteredRevWalkTest.java new file mode 100644 index 000000000..b1f8c0c0e --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FilteredRevWalkTest.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2022, Google LLC. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +package org.eclipse.jgit.revwalk; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; + +import org.eclipse.jgit.internal.storage.file.FileRepository; +import org.eclipse.jgit.junit.TestRepository; +import org.junit.Before; +import org.junit.Test; + +public class FilteredRevWalkTest extends RevWalkTestCase { + private TestRepository repository; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + repository = new TestRepository<>(db); + } + + @Test + public void testWalk() throws Exception { + writeTrashFile("a.txt", "content"); + repository.git().add().addFilepattern("a.txt").call(); + RevCommit c1 = repository.git().commit().setMessage("first commit") + .call(); + + writeTrashFile("b.txt", "new file added"); + repository.git().add().addFilepattern("b.txt").call(); + repository.git().commit().setMessage("second commit").call(); + + writeTrashFile("a.txt", "content added"); + repository.git().add().addFilepattern("a.txt").call(); + RevCommit c3 = repository.git().commit().setMessage("third commit") + .call(); + + RevWalk revWalk = repository.getRevWalk(); + FilteredRevCommit filteredRevCommit = new FilteredRevCommit(c3, c1); + + revWalk.markStart(filteredRevCommit); + assertEquals(c3, revWalk.next()); + assertEquals(c1, revWalk.next()); + } + + @Test + public void testParseBody() throws Exception { + writeTrashFile("a.txt", "content"); + repository.git().add().addFilepattern("a.txt").call(); + RevCommit c1 = repository.git().commit().setMessage("first commit") + .call(); + + writeTrashFile("b.txt", "new file added"); + repository.git().add().addFilepattern("b.txt").call(); + repository.git().commit().setMessage("second commit").call(); + + writeTrashFile("a.txt", "content added"); + repository.git().add().addFilepattern("a.txt").call(); + RevCommit c3 = repository.git().commit().setMessage("third commit") + .call(); + + FilteredRevCommit filteredRevCommit = new FilteredRevCommit(c3, c1); + filteredRevCommit.disposeBody(); + + RevWalk revWalk = repository.getRevWalk(); + + revWalk.parseBody(filteredRevCommit); + assertEquals(filteredRevCommit.getFullMessage(), c3.getFullMessage()); + assertEquals(filteredRevCommit.getShortMessage(), c3.getShortMessage()); + assertEquals(filteredRevCommit.commitTime, c3.commitTime); + assertSame(filteredRevCommit.getTree(), c3.getTree()); + assertSame(filteredRevCommit.parents, RevCommit.NO_PARENTS); + + } + + /** + * Test that the uninteresting flag is carried over correctly. Every commit + * should have the uninteresting flag resulting in a RevWalk returning no + * commit. + * + * @throws Exception + */ + @Test + public void testRevWalkCarryUninteresting() throws Exception { + writeTrashFile("a.txt", "content"); + repository.git().add().addFilepattern("a.txt").call(); + RevCommit c1 = repository.git().commit().setMessage("first commit") + .call(); + + writeTrashFile("b.txt", "new file added"); + repository.git().add().addFilepattern("b.txt").call(); + RevCommit c2 = repository.git().commit().setMessage("second commit") + .call(); + + writeTrashFile("a.txt", "content added"); + repository.git().add().addFilepattern("a.txt").call(); + RevCommit c3 = repository.git().commit().setMessage("third commit") + .call(); + + RevWalk revWalk = repository.getRevWalk(); + FilteredRevCommit filteredCommit1 = new FilteredRevCommit(c1); + FilteredRevCommit filteredCommit2 = new FilteredRevCommit(c2, + filteredCommit1); + FilteredRevCommit filteredCommit3 = new FilteredRevCommit(c3, + filteredCommit2); + + revWalk.markStart(filteredCommit2); + markUninteresting(filteredCommit3); + assertNull("Found an unexpected commit", rw.next()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FirstParentRevWalkTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FirstParentRevWalkTest.java index c8256b89c..146d16953 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FirstParentRevWalkTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FirstParentRevWalkTest.java @@ -12,6 +12,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.revwalk.filter.MessageRevFilter; @@ -423,9 +424,41 @@ public void testWithTopoSortAndTreeFilter() throws Exception { rw.sort(RevSort.TOPO, true); rw.setTreeFilter(PathFilterGroup.createFromStrings("0")); markStart(d); - assertCommit(d, rw.next()); - assertCommit(c, rw.next()); - assertCommit(b, rw.next()); + + assertEquals(d, rw.next()); + assertEquals(c, rw.next()); + assertEquals(b, rw.next()); + assertNull(rw.next()); + } + + @Test + public void testWithTopoSortAndTreeFilter_shouldUseFilteredRevCommits() + throws Exception { + RevCommit a = commit(); + RevCommit b = commit(tree(file("0", blob("b"))), a); + RevCommit c = commit(tree(file("0", blob("c"))), b, a); + RevCommit d = commit(tree(file("0", blob("d"))), c); + + rw.reset(); + rw.setFirstParent(true); + rw.sort(RevSort.TOPO, true); + rw.setTreeFilter(PathFilterGroup.createFromStrings("0")); + markStart(d); + + RevCommit x = rw.next(); + assertTrue(x instanceof FilteredRevCommit); + assertEquals(1, x.getParentCount()); + assertEquals(c, x.getParent(0)); + + RevCommit y = rw.next(); + assertTrue(y instanceof FilteredRevCommit); + assertEquals(1, y.getParentCount()); + assertEquals(b, y.getParent(0)); + + RevCommit z = rw.next(); + assertTrue(z instanceof FilteredRevCommit); + assertEquals(0, z.getParentCount()); + assertNull(rw.next()); } @@ -441,8 +474,8 @@ public void testWithTopoSortAndTreeFilter2() throws Exception { rw.sort(RevSort.TOPO, true); rw.setTreeFilter(PathFilterGroup.createFromStrings("0")); markStart(d); - assertCommit(d, rw.next()); - assertCommit(c, rw.next()); + assertEquals(d, rw.next()); + assertEquals(c, rw.next()); assertNull(rw.next()); } @@ -458,9 +491,9 @@ public void testWithTopoNonIntermixSortAndTreeFilter() throws Exception { rw.sort(RevSort.TOPO_KEEP_BRANCH_TOGETHER, true); rw.setTreeFilter(PathFilterGroup.createFromStrings("0")); markStart(d); - assertCommit(d, rw.next()); - assertCommit(c, rw.next()); - assertCommit(b, rw.next()); + assertEquals(d, rw.next()); + assertEquals(c, rw.next()); + assertEquals(b, rw.next()); assertNull(rw.next()); } @@ -476,8 +509,8 @@ public void testWithTopoNonIntermixSortAndTreeFilter2() throws Exception { rw.sort(RevSort.TOPO_KEEP_BRANCH_TOGETHER, true); rw.setTreeFilter(PathFilterGroup.createFromStrings("0")); markStart(d); - assertCommit(d, rw.next()); - assertCommit(c, rw.next()); + assertEquals(d, rw.next()); + assertEquals(c, rw.next()); assertNull(rw.next()); } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFollowFilterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFollowFilterTest.java index c62136e64..20478ef70 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFollowFilterTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFollowFilterTest.java @@ -9,6 +9,7 @@ */ package org.eclipse.jgit.revwalk; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import java.util.ArrayList; @@ -55,7 +56,7 @@ public void testNoRename() throws Exception { final RevCommit a = commit(tree(file("0", blob("0")))); follow("0"); markStart(a); - assertCommit(a, rw.next()); + assertEquals(a, rw.next()); assertNull(rw.next()); assertNoRenames(); @@ -72,8 +73,8 @@ public void testSingleRename() throws Exception { follow("b"); markStart(renameCommit); - assertCommit(renameCommit, rw.next()); - assertCommit(a, rw.next()); + assertEquals(renameCommit, rw.next()); + assertEquals(a, rw.next()); assertNull(rw.next()); assertRenames("a->b"); @@ -101,10 +102,10 @@ public void testMultiRename() throws Exception { follow("a"); markStart(renameCommit3); - assertCommit(renameCommit3, rw.next()); - assertCommit(renameCommit2, rw.next()); - assertCommit(renameCommit1, rw.next()); - assertCommit(a, rw.next()); + assertEquals(renameCommit3, rw.next()); + assertEquals(renameCommit2, rw.next()); + assertEquals(renameCommit1, rw.next()); + assertEquals(a, rw.next()); assertNull(rw.next()); assertRenames("c->a", "b->c", "a->b"); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkPathFilter1Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkPathFilter1Test.java index 5cce11aa1..d933a6fc7 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkPathFilter1Test.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkPathFilter1Test.java @@ -11,6 +11,7 @@ package org.eclipse.jgit.revwalk; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import java.util.Collections; @@ -23,8 +24,8 @@ public class RevWalkPathFilter1Test extends RevWalkTestCase { protected void filter(String path) { - rw.setTreeFilter(AndTreeFilter.create(PathFilterGroup - .createFromStrings(Collections.singleton(path)), + rw.setTreeFilter(AndTreeFilter.create( + PathFilterGroup.createFromStrings(Collections.singleton(path)), TreeFilter.ANY_DIFF)); } @@ -49,7 +50,7 @@ public void testSimple1() throws Exception { final RevCommit a = commit(tree(file("0", blob("0")))); filter("0"); markStart(a); - assertCommit(a, rw.next()); + assertEquals(a, rw.next()); assertNull(rw.next()); } @@ -72,10 +73,10 @@ public void testEdits_MatchAll() throws Exception { final RevCommit d = commit(tree(file("0", blob("d"))), c); filter("0"); markStart(d); - assertCommit(d, rw.next()); - assertCommit(c, rw.next()); - assertCommit(b, rw.next()); - assertCommit(a, rw.next()); + assertEquals(d, rw.next()); + assertEquals(c, rw.next()); + assertEquals(b, rw.next()); + assertEquals(a, rw.next()); assertNull(rw.next()); } @@ -87,11 +88,11 @@ public void testStringOfPearls_FilePath1() throws Exception { filter("d/f"); markStart(c); - assertCommit(c, rw.next()); + assertEquals(c, rw.next()); assertEquals(1, c.getParentCount()); - assertCommit(a, c.getParent(0)); // b was skipped + assertEquals(a, c.getParent(0)); // b was skipped - assertCommit(a, rw.next()); + assertEquals(a, rw.next()); assertEquals(0, a.getParentCount()); assertNull(rw.next()); } @@ -106,11 +107,11 @@ public void testStringOfPearls_FilePath1_NoParentRewriting() markStart(c); rw.setRewriteParents(false); - assertCommit(c, rw.next()); + assertEquals(c, rw.next()); assertEquals(1, c.getParentCount()); - assertCommit(b, c.getParent(0)); + assertEquals(b, c.getParent(0)); - assertCommit(a, rw.next()); // b was skipped + assertEquals(a, rw.next()); // b was skipped assertEquals(0, a.getParentCount()); assertNull(rw.next()); } @@ -125,18 +126,18 @@ public void testStringOfPearls_FilePath2() throws Exception { markStart(d); // d was skipped - assertCommit(c, rw.next()); + assertEquals(c, rw.next()); assertEquals(1, c.getParentCount()); - assertCommit(a, c.getParent(0)); // b was skipped + assertEquals(a, c.getParent(0)); // b was skipped - assertCommit(a, rw.next()); + assertEquals(a, rw.next()); assertEquals(0, a.getParentCount()); assertNull(rw.next()); } @Test public void testStringOfPearls_FilePath2_NoParentRewriting() - throws Exception { + throws Exception { final RevCommit a = commit(tree(file("d/f", blob("a")))); final RevCommit b = commit(tree(file("d/f", blob("a"))), a); final RevCommit c = commit(tree(file("d/f", blob("b"))), b); @@ -146,12 +147,12 @@ public void testStringOfPearls_FilePath2_NoParentRewriting() rw.setRewriteParents(false); // d was skipped - assertCommit(c, rw.next()); + assertEquals(c, rw.next()); assertEquals(1, c.getParentCount()); - assertCommit(b, c.getParent(0)); + assertEquals(b, c.getParent(0)); // b was skipped - assertCommit(a, rw.next()); + assertEquals(a, rw.next()); assertEquals(0, a.getParentCount()); assertNull(rw.next()); } @@ -166,11 +167,11 @@ public void testStringOfPearls_DirPath2() throws Exception { markStart(d); // d was skipped - assertCommit(c, rw.next()); + assertEquals(c, rw.next()); assertEquals(1, c.getParentCount()); - assertCommit(a, c.getParent(0)); // b was skipped + assertEquals(a, c.getParent(0)); // b was skipped - assertCommit(a, rw.next()); + assertEquals(a, rw.next()); assertEquals(0, a.getParentCount()); assertNull(rw.next()); } @@ -211,15 +212,15 @@ public void testStringOfPearls_FilePath3() throws Exception { filter("d/f"); markStart(i); - assertCommit(i, rw.next()); + assertEquals(i, rw.next()); assertEquals(1, i.getParentCount()); - assertCommit(c, i.getParent(0)); // h..d was skipped + assertEquals(c, i.getParent(0)); // h..d was skipped - assertCommit(c, rw.next()); + assertEquals(c, rw.next()); assertEquals(1, c.getParentCount()); - assertCommit(a, c.getParent(0)); // b was skipped + assertEquals(a, c.getParent(0)); // b was skipped - assertCommit(a, rw.next()); + assertEquals(a, rw.next()); assertEquals(0, a.getParentCount()); assertNull(rw.next()); } @@ -273,4 +274,49 @@ public void testStopWhenPathDisappears() throws Exception { assertCommit(b, rw.next()); assertCommit(a, rw.next()); } + + @Test + public void testCommitHeaders_rewrittenParents() throws Exception { + final RevCommit a = commit(tree(file("d/f", blob("a")))); + final RevCommit b = commit(tree(file("d/f", blob("a"))), a); + final RevCommit c = commit(tree(file("d/f", blob("b"))), b); + filter("d/f"); + markStart(c); + + RevCommit cBar = rw.next(); + assertNotNull(cBar.getShortMessage()); + assertEquals(cBar.getCommitTime(), c.getCommitTime()); + + RevCommit aBar = rw.next(); + assertNotNull(aBar.getShortMessage()); + assertEquals(aBar.getCommitTime(), a.getCommitTime()); + + assertNull(rw.next()); + } + + @Test + public void testFlags_rewrittenParents() throws Exception { + final RevCommit a = commit(tree(file("d/f", blob("a")))); + final RevCommit b = commit(tree(file("d/f", blob("a"))), a); + final RevCommit c = commit(tree(file("d/f", blob("b"))), b); + + final RevFlag flag1 = rw.newFlag("flag1"); + final RevFlag flag2 = rw.newFlag("flag2"); + + a.add(flag1); + c.add(flag2); + + filter("d/f"); + markStart(c); + + RevCommit cBar = rw.next(); + assertEquals(cBar.flags & RevObject.PARSED, 1); + assertEquals(cBar.flags & flag2.mask, flag2.mask); + + RevCommit aBar = rw.next(); + assertEquals(aBar.flags & RevObject.PARSED, 1); + assertEquals(aBar.flags & flag1.mask, flag1.mask); + + assertNull(rw.next()); + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FilteredRevCommit.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FilteredRevCommit.java new file mode 100644 index 000000000..16beac390 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FilteredRevCommit.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2022, Google LLC. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.revwalk; + +/** + * A filtered commit reference that overrides its parent in the DAG. + * + * @since 6.3 + */ +public class FilteredRevCommit extends RevCommit { + private RevCommit[] overriddenParents; + + /** + * Create a new commit reference wrapping an underlying commit reference. + * + * @param commit + * commit that is being wrapped + */ + public FilteredRevCommit(RevCommit commit) { + this(commit, NO_PARENTS); + } + + /** + * Create a new commit reference wrapping an underlying commit reference. + * + * @param commit + * commit that is being wrapped + * @param parents + * overridden parents for the commit + */ + public FilteredRevCommit(RevCommit commit, RevCommit... parents) { + super(commit); + this.overriddenParents = parents; + this.parents = NO_PARENTS; + } + + /** + * Update parents on the commit + * + * @param overriddenParents + * parents to be overwritten + */ + public void setParents(RevCommit... overriddenParents) { + this.overriddenParents = overriddenParents; + } + + /** + * Get the number of parent commits listed in this commit. + * + * @return number of parents; always a positive value but can be 0 if it has + * no parents. + */ + @Override + public int getParentCount() { + return overriddenParents.length; + } + + /** + * Get the nth parent from this commit's parent list. + * + * @param nth + * parent index to obtain. Must be in the range 0 through + * {@link #getParentCount()}-1. + * @return the specified parent. + * @throws java.lang.ArrayIndexOutOfBoundsException + * an invalid parent index was specified. + */ + @Override + public RevCommit getParent(int nth) { + return overriddenParents[nth]; + } + + /** + * Obtain an array of all parents (NOTE - THIS IS NOT A COPY). + * + *

+ * This method is exposed only to provide very fast, efficient access to + * this commit's parent list. Applications relying on this list should be + * very careful to ensure they do not modify its contents during their use + * of it. + * + * @return the array of parents. + */ + @Override + public RevCommit[] getParents() { + return overriddenParents; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java index 70490eec7..7f1e88707 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java @@ -109,7 +109,7 @@ public static RevCommit parse(RevWalk rw, byte[] raw) throws IOException { * * @since 6.3 */ - protected RevCommit[] parents; + RevCommit[] parents; int commitTime; // An int here for performance, overflows in 2038 @@ -127,6 +127,22 @@ protected RevCommit(AnyObjectId id) { super(id); } + /** + * Create a new commit reference. + * + * @param orig + * commit to be copied from. + */ + RevCommit(RevCommit orig) { + super(orig.getId()); + this.buffer = orig.buffer; + this.commitTime = orig.commitTime; + this.flags = orig.flags; + this.parents = orig.parents; + this.tree = orig.tree; + this.inDegree = orig.inDegree; + } + @Override void parseHeaders(RevWalk walk) throws MissingObjectException, IncorrectObjectTypeException, IOException { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteGenerator.java index 2c88bb872..9ec331b69 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteGenerator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteGenerator.java @@ -11,6 +11,8 @@ package org.eclipse.jgit.revwalk; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; @@ -38,10 +40,13 @@ class RewriteGenerator extends Generator { private final FIFORevQueue pending; + private final Map transformedCommits; + RewriteGenerator(Generator s) { super(s.firstParent); source = s; pending = new FIFORevQueue(s.firstParent); + transformedCommits = new HashMap<>(); } @Override @@ -58,10 +63,10 @@ int outputType() { @Override RevCommit next() throws MissingObjectException, IncorrectObjectTypeException, IOException { - RevCommit c = pending.next(); + FilteredRevCommit c = (FilteredRevCommit) pending.next(); if (c == null) { - c = source.next(); + c = transform(source.next()); if (c == null) { // We are done: Both the source generator and our internal list // are completely exhausted. @@ -79,9 +84,9 @@ RevCommit next() throws MissingObjectException, final RevCommit newp = rewrite(oldp); if (firstParent) { if (newp == null) { - c.parents = RevCommit.NO_PARENTS; + c.setParents(RevCommit.NO_PARENTS); } else { - c.parents = new RevCommit[] { newp }; + c.setParents(newp); } return c; } @@ -91,7 +96,7 @@ RevCommit next() throws MissingObjectException, } } if (rewrote) { - c.parents = cleanup(pList); + c.setParents(cleanup(pList)); } return c; } @@ -111,7 +116,7 @@ private void applyFilterToParents(RevCommit c) for (RevCommit parent : c.getParents()) { while ((parent.flags & RevWalk.TREE_REV_FILTER_APPLIED) == 0) { - RevCommit n = source.next(); + FilteredRevCommit n = transform(source.next()); if (n != null) { pending.add(n); @@ -130,6 +135,8 @@ private RevCommit rewrite(RevCommit p) throws MissingObjectException, IncorrectObjectTypeException, IOException { for (;;) { + p = transform(p); + if (p.getParentCount() > 1) { // This parent is a merge, so keep it. // @@ -158,11 +165,27 @@ private RevCommit rewrite(RevCommit p) throws MissingObjectException, } applyFilterToParents(p.getParent(0)); - p = p.getParent(0); + p = transform(p.getParent(0)); } } + private FilteredRevCommit transform(RevCommit c) { + if (c == null) { + return null; + } + + if (c instanceof FilteredRevCommit) { + return (FilteredRevCommit) c; + } + + if (!transformedCommits.containsKey(c)) { + transformedCommits.put(c, new FilteredRevCommit(c, c.getParents())); + } + + return transformedCommits.get(c); + } + private RevCommit[] cleanup(RevCommit[] oldList) { // Remove any duplicate parents caused due to rewrites (e.g. a merge // with two sides that both simplified back into the merge base).