diff options
Diffstat (limited to 'org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkCommitGraphTest.java')
-rw-r--r-- | org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkCommitGraphTest.java | 915 |
1 files changed, 915 insertions, 0 deletions
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkCommitGraphTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkCommitGraphTest.java new file mode 100644 index 0000000000..e47dd898b0 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkCommitGraphTest.java @@ -0,0 +1,915 @@ +/* + * Copyright (C) 2023, Tencent. + * + * 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 java.util.Arrays.asList; +import static org.eclipse.jgit.internal.storage.commitgraph.CommitGraph.EMPTY; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.diff.DiffConfig; +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.internal.storage.file.GC; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.revwalk.filter.MessageRevFilter; +import org.eclipse.jgit.revwalk.filter.RevFilter; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.treewalk.filter.AndTreeFilter; +import org.eclipse.jgit.treewalk.filter.ChangedPathTreeFilter; +import org.eclipse.jgit.treewalk.filter.OrTreeFilter; +import org.eclipse.jgit.treewalk.filter.PathFilter; +import org.eclipse.jgit.treewalk.filter.TreeFilter; +import org.junit.Test; + +public class RevWalkCommitGraphTest extends RevWalkTestCase { + + private RevWalk rw; + + @Override + public void setUp() throws Exception { + super.setUp(); + rw = new RevWalk(db); + mockSystemReader.setJGitConfig(new MockConfig()); + } + + @Test + public void testParseHeaders() throws Exception { + RevCommit c1 = commitFile("file1", "1", "master"); + + RevCommit notParseInGraph = rw.lookupCommit(c1); + rw.parseHeaders(notParseInGraph); + assertFalse(notParseInGraph instanceof RevCommitCG); + assertNotNull(notParseInGraph.getRawBuffer()); + assertEquals(Constants.COMMIT_GENERATION_UNKNOWN, + notParseInGraph.getGeneration()); + + enableAndWriteCommitGraph(); + + reinitializeRevWalk(); + RevCommit parseInGraph = rw.lookupCommit(c1); + parseInGraph.parseHeaders(rw); + + assertTrue(parseInGraph instanceof RevCommitCG); + assertNotNull(parseInGraph.getRawBuffer()); + assertEquals(1, parseInGraph.getGeneration()); + assertEquals(notParseInGraph.getId(), parseInGraph.getId()); + assertEquals(notParseInGraph.getTree(), parseInGraph.getTree()); + assertEquals(notParseInGraph.getCommitTime(), parseInGraph.getCommitTime()); + assertArrayEquals(notParseInGraph.getParents(), parseInGraph.getParents()); + + reinitializeRevWalk(); + rw.setRetainBody(false); + RevCommit noBody = rw.lookupCommit(c1); + noBody.parseHeaders(rw); + + assertTrue(noBody instanceof RevCommitCG); + assertNull(noBody.getRawBuffer()); + assertEquals(1, noBody.getGeneration()); + assertEquals(notParseInGraph.getId(), noBody.getId()); + assertEquals(notParseInGraph.getTree(), noBody.getTree()); + assertEquals(notParseInGraph.getCommitTime(), noBody.getCommitTime()); + assertArrayEquals(notParseInGraph.getParents(), noBody.getParents()); + } + + @Test + public void testParseCanonical() throws Exception { + RevCommit c1 = commitFile("file1", "1", "master"); + enableAndWriteCommitGraph(); + + RevCommit notParseInGraph = rw.lookupCommit(c1); + rw.parseHeaders(notParseInGraph); + + reinitializeRevWalk(); + RevCommit parseInGraph = rw.lookupCommit(c1); + parseInGraph.parseCanonical(rw, rw.getCachedBytes(c1)); + + assertTrue(parseInGraph instanceof RevCommitCG); + assertNotNull(parseInGraph.getRawBuffer()); + assertEquals(1, parseInGraph.getGeneration()); + assertEquals(notParseInGraph.getId(), parseInGraph.getId()); + assertEquals(notParseInGraph.getTree(), parseInGraph.getTree()); + assertEquals(notParseInGraph.getCommitTime(), + parseInGraph.getCommitTime()); + assertArrayEquals(notParseInGraph.getParents(), + parseInGraph.getParents()); + + reinitializeRevWalk(); + rw.setRetainBody(false); + RevCommit noBody = rw.lookupCommit(c1); + noBody.parseCanonical(rw, rw.getCachedBytes(c1)); + + assertTrue(noBody instanceof RevCommitCG); + assertNull(noBody.getRawBuffer()); + assertEquals(1, noBody.getGeneration()); + assertEquals(notParseInGraph.getId(), noBody.getId()); + assertEquals(notParseInGraph.getTree(), noBody.getTree()); + assertEquals(notParseInGraph.getCommitTime(), noBody.getCommitTime()); + assertArrayEquals(notParseInGraph.getParents(), noBody.getParents()); + } + + @Test + public void testInitializeShallowCommits() throws Exception { + RevCommit c1 = commit(commit()); + branch(c1, "master"); + enableAndWriteCommitGraph(); + assertCommitCntInGraph(2); + + db.getObjectDatabase().setShallowCommits(Collections.singleton(c1)); + RevCommit parseInGraph = rw.lookupCommit(c1); + parseInGraph.parseHeaders(rw); + + assertTrue(parseInGraph instanceof RevCommitCG); + assertNotNull(parseInGraph.getRawBuffer()); + assertEquals(2, parseInGraph.getGeneration()); + assertEquals(0, parseInGraph.getParentCount()); + } + + @Test + public void testTreeFilter() throws Exception { + RevCommit c1 = commitFile("file1", "1", "master"); + RevCommit c2 = commitFile("file2", "2", "master"); + RevCommit c3 = commitFile("file1", "3", "master"); + RevCommit c4 = commitFile("file2", "4", "master"); + + enableAndWriteCommitGraph(); + assertCommitCntInGraph(4); + + rw.markStart(rw.lookupCommit(c4)); + rw.setTreeFilter(AndTreeFilter.create(PathFilter.create("file1"), + TreeFilter.ANY_DIFF)); + assertEquals(c3, rw.next()); + assertEquals(c1, rw.next()); + assertNull(rw.next()); + + reinitializeRevWalk(); + rw.markStart(rw.lookupCommit(c4)); + rw.setTreeFilter(AndTreeFilter.create(PathFilter.create("file2"), + TreeFilter.ANY_DIFF)); + assertEquals(c4, rw.next()); + assertEquals(c2, rw.next()); + assertNull(rw.next()); + } + + @Test + public void testChangedPathFilter_allModify() throws Exception { + RevCommit c1 = commit(tree(file("file1", blob("1")))); + RevCommit c2 = commit(tree(file("file2", blob("2"))), c1); + RevCommit c3 = commit(tree(file("file1", blob("3"))), c2); + RevCommit c4 = commit(tree(file("file2", blob("4"))), c3); + + branch(c4, "master"); + + enableAndWriteCommitGraph(); + + TreeRevFilter trf = new TreeRevFilter(rw, + ChangedPathTreeFilter.create("file1")); + rw.markStart(rw.lookupCommit(c4)); + rw.setRevFilter(trf); + assertEquals(c4, rw.next()); + assertEquals(c3, rw.next()); + assertEquals(c2, rw.next()); + assertEquals(c1, rw.next()); + assertNull(rw.next()); + + // all commits modified file1 but c1 did not have a parent + assertEquals(3, trf.getChangedPathFilterTruePositive()); + + // No false positives + assertEquals(0, trf.getChangedPathFilterFalsePositive()); + + // No negatives because all 4 commits had modified file1 + assertEquals(0, trf.getChangedPathFilterNegative()); + } + + @Test + public void testChangedPathFilter_someModify() throws Exception { + RevCommit c1 = commit(tree(file("file1", blob("1")))); + RevCommit c2 = commit(tree(file("file1", blob("1"))), c1); + RevCommit c3 = commit(tree(file("file1", blob("2"))), c2); + RevCommit c4 = commit(tree(file("file1", blob("1"))), c3); + + branch(c4, "master"); + + enableAndWriteCommitGraph(); + + TreeRevFilter trf = new TreeRevFilter(rw, + ChangedPathTreeFilter.create("file1")); + rw.markStart(rw.lookupCommit(c4)); + rw.setRevFilter(trf); + assertEquals(c4, rw.next()); + assertEquals(c3, rw.next()); + assertEquals(c1, rw.next()); + assertNull(rw.next()); + + // c4 and c3 modified file1. c1 did not have a parent + assertEquals(2, trf.getChangedPathFilterTruePositive()); + + // No false positives + assertEquals(0, trf.getChangedPathFilterFalsePositive()); + + // c2 did not modify file1 + assertEquals(1, trf.getChangedPathFilterNegative()); + } + + @Test + public void testChangedPathFilterWithMultiPaths() throws Exception { + RevCommit c1 = commit(tree(file("file1", blob("1")))); + RevCommit c2 = commit(tree(file("file1", blob("2"))), c1); + RevCommit c3 = commit(tree(file("file2", blob("3"))), c2); + RevCommit c4 = commit(tree(file("file3", blob("4"))), c3); + + branch(c4, "master"); + + enableAndWriteCommitGraph(); + + TreeRevFilter trf = new TreeRevFilter(rw, + ChangedPathTreeFilter.create("file1", "file2")); + rw.markStart(rw.lookupCommit(c4)); + rw.setRevFilter(trf); + assertEquals(c4, rw.next()); + assertEquals(c3, rw.next()); + assertEquals(c2, rw.next()); + assertEquals(c1, rw.next()); + assertNull(rw.next()); + + // all commits have modified either file1 or file2, c1 did not have a + // parent + assertEquals(3, trf.getChangedPathFilterTruePositive()); + + // No false positives + assertEquals(0, trf.getChangedPathFilterFalsePositive()); + + // No negative + assertEquals(0, trf.getChangedPathFilterNegative()); + } + + @Test + public void testChangedPathFilterWithFollowFilter() throws Exception { + RevCommit c0 = commit(tree()); + RevCommit c1 = commit(tree(file("file", blob("contents"))), c0); + RevCommit c2 = commit(tree(file("file", blob("contents")), + file("unrelated", blob("unrelated change"))), c1); + RevCommit c3 = commit(tree(file("renamed-file", blob("contents")), + file("unrelated", blob("unrelated change"))), c2); + RevCommit c4 = commit( + tree(file("renamed-file", blob("contents")), + file("unrelated", blob("another unrelated change"))), + c3); + branch(c4, "master"); + + enableAndWriteCommitGraph(); + + db.getConfig().setString(ConfigConstants.CONFIG_DIFF_SECTION, null, + ConfigConstants.CONFIG_KEY_RENAMES, "true"); + + TreeRevFilter trf = new TreeRevFilter(rw, FollowFilter + .create("renamed-file", db.getConfig().get(DiffConfig.KEY))); + rw.markStart(rw.lookupCommit(c4)); + rw.setRevFilter(trf); + assertEquals(c3, rw.next()); + assertEquals(c1, rw.next()); + assertNull(rw.next()); + + // Path "renamed-file" is in c3's bloom filter, and another path "file" + // is in c1's bloom filter (we know of "file" because the rev walk + // detected that "renamed-file" is a renaming of "file") + assertEquals(2, trf.getChangedPathFilterTruePositive()); + + // No false positives + assertEquals(0, trf.getChangedPathFilterFalsePositive()); + + // 2 commits that have exactly one parent and don't match path + assertEquals(2, trf.getChangedPathFilterNegative()); + } + + @Test + public void testChangedPathFilter_pathFilter_or_pathFilter_binaryOperation() + throws Exception { + RevCommit c1 = commit(tree(file("file1", blob("1")))); + RevCommit c2 = commit( + tree(file("file1", blob("1")), file("file2", blob("2"))), c1); + RevCommit c3 = commit(tree(file("file2", blob("2"))), c2); + RevCommit c4 = commit( + tree(file("file2", blob("2")), file("file3", blob("3"))), c3); + RevCommit c5 = commit( + tree(file("file2", blob("2")), file("file3", blob("3"))), c4); + + branch(c5, "master"); + + enableAndWriteCommitGraph(); + + ChangedPathTreeFilter pf1 = ChangedPathTreeFilter.create("file1"); + ChangedPathTreeFilter pf2 = ChangedPathTreeFilter.create("file2"); + + TreeFilter tf = OrTreeFilter + .create(new ChangedPathTreeFilter[] { pf1, pf2 }); + + TreeRevFilter trf = new TreeRevFilter(rw, tf); + rw.markStart(rw.lookupCommit(c5)); + rw.setRevFilter(trf); + assertEquals(c3, rw.next()); + assertEquals(c2, rw.next()); + assertEquals(c1, rw.next()); + assertNull(rw.next()); + + // c2 and c3 has either file1 or file2, c1 is not counted as + // ChangedPathFilter only applies to commits with 1 parent + assertEquals(2, trf.getChangedPathFilterTruePositive()); + + // No false positives + assertEquals(0, trf.getChangedPathFilterFalsePositive()); + + // c4 and c5 did not modify file1 or file2 + assertEquals(2, trf.getChangedPathFilterNegative()); + } + + @Test + public void testChangedPathFilter_pathFilter_or_pathFilter_or_pathFilter_listOperation() + throws Exception { + RevCommit c1 = commit(tree(file("file1", blob("1")))); + RevCommit c2 = commit( + tree(file("file1", blob("1")), file("file2", blob("2"))), c1); + RevCommit c3 = commit(tree(file("file2", blob("2"))), c2); + RevCommit c4 = commit(tree(file("file3", blob("3"))), c3); + RevCommit c5 = commit(tree(file("file3", blob("3"))), c4); + + branch(c5, "master"); + + enableAndWriteCommitGraph(); + + ChangedPathTreeFilter pf1 = ChangedPathTreeFilter.create("file1"); + ChangedPathTreeFilter pf2 = ChangedPathTreeFilter.create("file2"); + ChangedPathTreeFilter pf3 = ChangedPathTreeFilter.create("file3"); + + TreeFilter tf = OrTreeFilter + .create(new ChangedPathTreeFilter[] { pf1, pf2, pf3 }); + + TreeRevFilter trf = new TreeRevFilter(rw, tf); + rw.markStart(rw.lookupCommit(c5)); + rw.setRevFilter(trf); + assertEquals(c4, rw.next()); + assertEquals(c3, rw.next()); + assertEquals(c2, rw.next()); + assertEquals(c1, rw.next()); + assertNull(rw.next()); + + // c2 and c3 has either modified file1 or file2 or file3, c1 is not + // counted as ChangedPathFilter only applies to commits with 1 parent + assertEquals(3, trf.getChangedPathFilterTruePositive()); + + // No false positives + assertEquals(0, trf.getChangedPathFilterFalsePositive()); + + // c5 does not modify either file1 or file2 or file3 + assertEquals(1, trf.getChangedPathFilterNegative()); + } + + @Test + public void testChangedPathFilter_pathFilter_or_nonPathFilter_binaryOperation() + throws Exception { + RevCommit c1 = commit(tree(file("file1", blob("1")))); + RevCommit c2 = commit(tree(file("file2", blob("2"))), c1); + RevCommit c3 = commit(tree(file("file2", blob("3"))), c2); + RevCommit c4 = commit(tree(file("file2", blob("3"))), c3); + + branch(c4, "master"); + + enableAndWriteCommitGraph(); + + ChangedPathTreeFilter pf = ChangedPathTreeFilter.create("file1"); + TreeFilter npf = TreeFilter.ANY_DIFF; + + TreeFilter tf = OrTreeFilter.create(new TreeFilter[] { pf, npf }); + + TreeRevFilter trf = new TreeRevFilter(rw, tf); + rw.markStart(rw.lookupCommit(c4)); + rw.setRevFilter(trf); + assertEquals(c3, rw.next()); + assertEquals(c2, rw.next()); + assertEquals(c1, rw.next()); + assertNull(rw.next()); + + // c2 modified file1, c3 defaulted positive due to ANY_DIFF, c1 is not + // counted as ChangedPathFilter only applies to commits with 1 parent + assertEquals(2, trf.getChangedPathFilterTruePositive()); + + // c4 defaulted positive due to ANY_DIFF, but didn't no diff with its + // parent c3 + assertEquals(1, trf.getChangedPathFilterFalsePositive()); + + // No negative due to the OrTreeFilter + assertEquals(0, trf.getChangedPathFilterNegative()); + } + + @Test + public void testChangedPathFilter_nonPathFilter_or_nonPathFilter_binaryOperation() + throws Exception { + RevCommit c1 = commitFile("file1", "1", "master"); + RevCommit c2 = commitFile("file2", "2", "master"); + RevCommit c3 = commitFile("file3", "3", "master"); + RevCommit c4 = commitFile("file4", "4", "master"); + + enableAndWriteCommitGraph(); + + TreeFilter npf1 = TreeFilter.ANY_DIFF; + TreeFilter npf2 = TreeFilter.ANY_DIFF; + + TreeFilter tf = OrTreeFilter.create(new TreeFilter[] { npf1, npf2 }); + + TreeRevFilter trf = new TreeRevFilter(rw, tf); + rw.markStart(rw.lookupCommit(c4)); + rw.setRevFilter(trf); + assertEquals(c4, rw.next()); + assertEquals(c3, rw.next()); + assertEquals(c2, rw.next()); + assertEquals(c1, rw.next()); + assertNull(rw.next()); + + // No true positives since there's no pathFilter + assertEquals(0, trf.getChangedPathFilterTruePositive()); + + // No false positives since there's no pathFilter + assertEquals(0, trf.getChangedPathFilterFalsePositive()); + + // No negative since there's no pathFilter + assertEquals(0, trf.getChangedPathFilterNegative()); + } + + @Test + public void testChangedPathFilter_pathFilter_and_pathFilter_binaryOperation() + throws Exception { + RevCommit c1 = commit(tree(file("file1", blob("1")))); + RevCommit c2 = commit(tree(file("file2", blob("2"))), c1); + + branch(c2, "master"); + + enableAndWriteCommitGraph(); + + ChangedPathTreeFilter pf1 = ChangedPathTreeFilter.create("file1"); + ChangedPathTreeFilter pf2 = ChangedPathTreeFilter.create("file2"); + + TreeFilter atf = AndTreeFilter + .create(new ChangedPathTreeFilter[] { pf1, pf2 }); + TreeRevFilter trf = new TreeRevFilter(rw, atf); + + rw.markStart(rw.lookupCommit(c2)); + rw.setRevFilter(trf); + + assertNull(rw.next()); + + // c1 is not counted as ChangedPathFilter only applies to commits with 1 + // parent + assertEquals(0, trf.getChangedPathFilterTruePositive()); + + // c2 has modified both file 1 and file2, + // however nothing is returned from TreeWalk since a TreeHead + // cannot be two paths at once + assertEquals(1, trf.getChangedPathFilterFalsePositive()); + + // No negatives + assertEquals(0, trf.getChangedPathFilterNegative()); + } + + @Test + public void testChangedPathFilter_pathFilter_and_pathFilter_and_pathFilter_listOperation() + throws Exception { + RevCommit c1 = commit(tree(file("file1", blob("1")))); + RevCommit c2 = commit(tree(file("file2", blob("2"))), c1); + RevCommit c3 = commit(tree(file("file3", blob("3"))), c2); + + branch(c3, "master"); + + enableAndWriteCommitGraph(); + + ChangedPathTreeFilter pf1 = ChangedPathTreeFilter.create("file1"); + ChangedPathTreeFilter pf2 = ChangedPathTreeFilter.create("file2"); + ChangedPathTreeFilter pf3 = ChangedPathTreeFilter.create("file3"); + + TreeFilter tf = AndTreeFilter + .create(new ChangedPathTreeFilter[] { pf1, pf2, pf3 }); + + TreeRevFilter trf = new TreeRevFilter(rw, tf); + rw.markStart(rw.lookupCommit(c3)); + rw.setRevFilter(trf); + assertNull(rw.next()); + + // c1 is not counted as ChangedPathFilter only applies to commits with 1 + // parent + assertEquals(0, trf.getChangedPathFilterTruePositive()); + + // No false positives + assertEquals(0, trf.getChangedPathFilterFalsePositive()); + + // c2 and c3 can not possibly have both file1, file2, and file3 as + // treeHead at once + assertEquals(2, trf.getChangedPathFilterNegative()); + } + + @Test + public void testChangedPathFilter_pathFilter_and_nonPathFilter_binaryOperation() + throws Exception { + RevCommit c1 = commit(tree(file("file1", blob("1")))); + RevCommit c2 = commit(tree(file("file1", blob("2"))), c1); + RevCommit c3 = commit(tree(file("file1", blob("2"))), c2); + + branch(c3, "master"); + + enableAndWriteCommitGraph(); + + ChangedPathTreeFilter pf = ChangedPathTreeFilter.create("file1"); + TreeFilter npf = TreeFilter.ANY_DIFF; + + TreeFilter tf = AndTreeFilter.create(new TreeFilter[] { pf, npf }); + + TreeRevFilter trf = new TreeRevFilter(rw, tf); + rw.markStart(rw.lookupCommit(c3)); + rw.setRevFilter(trf); + assertEquals(c2, rw.next()); + assertEquals(c1, rw.next()); + assertNull(rw.next()); + + // c2 modified file1 and c1 is not counted as ChangedPathFilter only + // applies to commits with 1 parent + assertEquals(1, trf.getChangedPathFilterTruePositive()); + + // No false positives + assertEquals(0, trf.getChangedPathFilterFalsePositive()); + + // c3 did not modify file1 + assertEquals(1, trf.getChangedPathFilterNegative()); + } + + @Test + public void testChangedPathFilter_nonPathFilter_and_nonPathFilter_binaryOperation() + throws Exception { + RevCommit c1 = commitFile("file1", "1", "master"); + commitFile("file1", "1", "master"); + RevCommit c3 = commitFile("file3", "3", "master"); + RevCommit c4 = commitFile("file4", "4", "master"); + + enableAndWriteCommitGraph(); + + TreeFilter npf1 = TreeFilter.ANY_DIFF; + TreeFilter npf2 = TreeFilter.ANY_DIFF; + + TreeFilter tf = AndTreeFilter.create(new TreeFilter[] { npf1, npf2 }); + + TreeRevFilter trf = new TreeRevFilter(rw, tf); + rw.markStart(rw.lookupCommit(c4)); + rw.setRevFilter(trf); + assertEquals(c4, rw.next()); + assertEquals(c3, rw.next()); + assertEquals(c1, rw.next()); + assertNull(rw.next()); + + // No true positives since there's no path + assertEquals(0, trf.getChangedPathFilterTruePositive()); + + // No false positives since there's no path + assertEquals(0, trf.getChangedPathFilterFalsePositive()); + + // No negative since there's no path + assertEquals(0, trf.getChangedPathFilterNegative()); + } + + @Test + public void testWalkWithCommitMessageFilter() throws Exception { + RevCommit a = commit(); + RevCommit b = commitBuilder().parent(a) + .message("The quick brown fox jumps over the lazy dog!") + .create(); + RevCommit c = commitBuilder().parent(b).message("commit-c").create(); + branch(c, "master"); + + enableAndWriteCommitGraph(); + assertCommitCntInGraph(3); + + rw.setRevFilter(MessageRevFilter.create("quick brown fox jumps")); + rw.markStart(rw.lookupCommit(c)); + assertEquals(b, rw.next()); + assertNull(rw.next()); + } + + @Test + public void testCommitsWalk() throws Exception { + RevCommit c1 = commit(); + branch(c1, "commits/1"); + RevCommit c2 = commit(c1); + branch(c2, "commits/2"); + RevCommit c3 = commit(c2); + branch(c3, "commits/3"); + + enableAndWriteCommitGraph(); + assertCommitCntInGraph(3); + testRevWalkBehavior("commits/1", "commits/3"); + + // add more commits + RevCommit c4 = commit(c1); + RevCommit c5 = commit(c4); + RevCommit c6 = commit(c1); + RevCommit c7 = commit(c6); + + RevCommit m1 = commit(c2, c4); + branch(m1, "merge/1"); + RevCommit m2 = commit(c4, c6); + branch(m2, "merge/2"); + RevCommit m3 = commit(c3, c5, c7); + branch(m3, "merge/3"); + + /* + * <pre> + * current graph structure: + * + * __M3___ + * / | \ + * 3 M1 5 M2 7 + * |/ \|/ \| + * 2 4 6 + * |___/____/ + * 1 + * </pre> + */ + enableAndWriteCommitGraph(); + reinitializeRevWalk(); + assertCommitCntInGraph(10); + testRevWalkBehavior("merge/1", "merge/2"); + testRevWalkBehavior("merge/1", "merge/3"); + testRevWalkBehavior("merge/2", "merge/3"); + + // add one more commit + RevCommit c8 = commit(m3); + branch(c8, "commits/8"); + + /* + * <pre> + * current graph structure: + * 8 + * | + * __M3___ + * / | \ + * 3 M1 5 M2 7 + * |/ \|/ \| + * 2 4 6 + * |___/____/ + * 1 + * </pre> + */ + testRevWalkBehavior("commits/8", "merge/1"); + testRevWalkBehavior("commits/8", "merge/2"); + + enableAndWriteCommitGraph(); + reinitializeRevWalk(); + assertCommitCntInGraph(11); + testRevWalkBehavior("commits/8", "merge/1"); + testRevWalkBehavior("commits/8", "merge/2"); + } + + @Test + public void testMergedInto() throws Exception { + RevCommit c1 = commit(); + Ref branch1 = branch(c1, "commits/1"); + RevCommit c2 = commit(c1); + Ref branch2 = branch(c2, "commits/2"); + RevCommit c3 = commit(c2); + Ref branch3 = branch(c3, "commits/3"); + RevCommit c4 = commit(c1); + Ref branch4 = branch(c4, "commits/4"); + RevCommit c5 = commit(c4); + Ref branch5 = branch(c5, "commits/5"); + enableAndWriteCommitGraph(); + RevCommit c6 = commit(c1); + Ref branch6 = branch(c6, "commits/6"); + RevCommit c7 = commit(c2, c4); + Ref branch7 = branch(c7, "commits/7"); + RevCommit c8 = commit(c5); + Ref branch8 = branch(c8, "commits/8"); + RevCommit c9 = commit(c4, c6); + Ref branch9 = branch(c9, "commits/9"); + + /* + * <pre> + * current graph structure: + * 8 + * | + * 3 7 5 9 + * |/ \|/ \ + * 2 4 6 + * |___/____/ + * 1 + * </pre> + * + * [6, 7, 8, 9] are not in commit-graph. + */ + + reinitializeRevWalk(); + assertFalse(isObjectIdInGraph(c9)); + assertRefsEquals(asList(branch9), allMergedInto(c9)); + + assertFalse(isObjectIdInGraph(c8)); + assertRefsEquals(asList(branch8), allMergedInto(c8)); + + assertFalse(isObjectIdInGraph(c7)); + assertRefsEquals(asList(branch7), allMergedInto(c7)); + + assertFalse(isObjectIdInGraph(c6)); + assertRefsEquals(asList(branch6, branch9), allMergedInto(c6)); + + assertTrue(isObjectIdInGraph(c5)); + assertRefsEquals(asList(branch5, branch8), allMergedInto(c5)); + + assertTrue(isObjectIdInGraph(c4)); + assertRefsEquals(asList(branch4, branch5, branch7, branch8, branch9), + allMergedInto(c4)); + + assertTrue(isObjectIdInGraph(c3)); + assertRefsEquals(asList(branch3), allMergedInto(c3)); + + assertTrue(isObjectIdInGraph(c2)); + assertRefsEquals(asList(branch2, branch3, branch7), allMergedInto(c2)); + + assertTrue(isObjectIdInGraph(c1)); + assertRefsEquals(asList(branch1, branch2, branch3, branch4, branch5, + branch6, branch7, branch8, branch9), allMergedInto(c1)); + } + + boolean isObjectIdInGraph(AnyObjectId id) { + return rw.commitGraph().findGraphPosition(id) >= 0; + } + + List<Ref> allMergedInto(RevCommit needle) throws IOException { + List<Ref> refs = db.getRefDatabase().getRefs(); + return rw.getMergedInto(rw.lookupCommit(needle), refs); + } + + void assertRefsEquals(List<Ref> expecteds, List<Ref> actuals) { + assertEquals(expecteds.size(), actuals.size()); + Collections.sort(expecteds, Comparator.comparing(Ref::getName)); + Collections.sort(actuals, Comparator.comparing(Ref::getName)); + for (int i = 0; i < expecteds.size(); i++) { + Ref expected = expecteds.get(i); + Ref actual = actuals.get(i); + assertEquals(expected.getName(), actual.getName()); + assertEquals(expected.getObjectId(), actual.getObjectId()); + } + } + + void testRevWalkBehavior(String branch, String compare) throws Exception { + assertCommits( + travel(TreeFilter.ALL, RevFilter.MERGE_BASE, RevSort.NONE, true, + branch, compare), + travel(TreeFilter.ALL, RevFilter.MERGE_BASE, RevSort.NONE, + false, branch, compare)); + + assertCommits( + travel(TreeFilter.ALL, RevFilter.ALL, RevSort.TOPO, true, + branch), + travel(TreeFilter.ALL, RevFilter.ALL, RevSort.TOPO, false, + branch)); + + assertCommits( + travel(TreeFilter.ALL, RevFilter.ALL, RevSort.TOPO, true, + compare), + travel(TreeFilter.ALL, RevFilter.ALL, RevSort.TOPO, false, + compare)); + + assertCommits( + travel(TreeFilter.ALL, RevFilter.ALL, RevSort.COMMIT_TIME_DESC, + true, branch), + travel(TreeFilter.ALL, RevFilter.ALL, RevSort.COMMIT_TIME_DESC, + false, branch)); + + assertCommits( + travel(TreeFilter.ALL, RevFilter.ALL, RevSort.COMMIT_TIME_DESC, + true, compare), + travel(TreeFilter.ALL, RevFilter.ALL, RevSort.COMMIT_TIME_DESC, + false, compare)); + } + + void assertCommitCntInGraph(int expect) { + assertEquals(expect, rw.commitGraph().getCommitCnt()); + } + + void assertCommits(List<RevCommit> expect, List<RevCommit> actual) { + assertEquals(expect.size(), actual.size()); + + for (int i = 0; i < expect.size(); i++) { + RevCommit c1 = expect.get(i); + RevCommit c2 = actual.get(i); + + assertEquals(c1.getId(), c2.getId()); + assertEquals(c1.getTree(), c2.getTree()); + assertEquals(c1.getCommitTime(), c2.getCommitTime()); + assertArrayEquals(c1.getParents(), c2.getParents()); + assertArrayEquals(c1.getRawBuffer(), c2.getRawBuffer()); + } + } + + Ref branch(RevCommit commit, String name) throws Exception { + return Git.wrap(db).branchCreate().setName(name) + .setStartPoint(commit.name()).call(); + } + + List<RevCommit> travel(TreeFilter treeFilter, RevFilter revFilter, + RevSort revSort, boolean enableCommitGraph, String... starts) + throws Exception { + db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_COMMIT_GRAPH, enableCommitGraph); + + try (RevWalk walk = new RevWalk(db)) { + walk.setTreeFilter(treeFilter); + walk.setRevFilter(revFilter); + walk.sort(revSort); + walk.setRetainBody(false); + for (String start : starts) { + walk.markStart(walk.lookupCommit(db.resolve(start))); + } + List<RevCommit> commits = new ArrayList<>(); + + if (enableCommitGraph) { + assertTrue(walk.commitGraph().getCommitCnt() > 0); + } else { + assertEquals(EMPTY, walk.commitGraph()); + } + + for (RevCommit commit : walk) { + commits.add(commit); + } + return commits; + } + } + + void enableAndWriteCommitGraph() throws Exception { + db.getConfig().setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_COMMIT_GRAPH, true); + db.getConfig().setBoolean(ConfigConstants.CONFIG_GC_SECTION, null, + ConfigConstants.CONFIG_KEY_WRITE_COMMIT_GRAPH, true); + db.getConfig().setBoolean(ConfigConstants.CONFIG_GC_SECTION, null, + ConfigConstants.CONFIG_KEY_WRITE_CHANGED_PATHS, true); + GC gc = new GC(db); + gc.gc().get(); + } + + private void reinitializeRevWalk() { + rw.close(); + rw = new RevWalk(db); + } + + private static final class MockConfig extends FileBasedConfig { + private MockConfig() { + super(null, null); + } + + @Override + public void load() throws IOException, ConfigInvalidException { + // Do nothing + } + + @Override + public void save() throws IOException { + // Do nothing + } + + @Override + public boolean isOutdated() { + return false; + } + + @Override + public String toString() { + return "MockConfig"; + } + + @Override + public boolean getBoolean(final String section, final String name, + final boolean defaultValue) { + if (section.equals(ConfigConstants.CONFIG_COMMIT_GRAPH_SECTION) + && name.equals( + ConfigConstants.CONFIG_KEY_READ_CHANGED_PATHS)) { + return true; + } + return defaultValue; + } + } +} |