From 0bd2f4bf77c856213e09d656a948e71f71cfd038 Mon Sep 17 00:00:00 2001 From: Adithya Chakilam Date: Thu, 18 Feb 2021 13:41:19 -0600 Subject: [PATCH] Introduce getMergedInto(RevCommit commit, Collection refs) In cases where we need to determine if a given commit is merged into many refs, using isMergedInto(base, tip) for each ref would cause multiple unwanted walks. getMergedInto() marks the unreachable commits as uninteresting which would then avoid walking that same path again. Using the same api, also introduce isMergedIntoAny() and isMergedIntoAll() Change-Id: I65de9873dce67af9c415d1d236bf52d31b67e8fe Signed-off-by: Adithya Chakilam Signed-off-by: Matthias Sohn --- .../jgit/junit/RepositoryTestCase.java | 12 ++ .../jgit/revwalk/RevWalkMergedIntoTest.java | 81 +++++++++++++ .../src/org/eclipse/jgit/revwalk/RevWalk.java | 114 ++++++++++++++++++ 3 files changed, 207 insertions(+) diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java index 64556acc1..5622108dc 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java @@ -25,6 +25,7 @@ import java.io.Reader; import java.nio.file.Path; import java.time.Instant; +import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -39,6 +40,7 @@ import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; @@ -385,6 +387,16 @@ protected void createBranch(ObjectId objectId, String branchName) updateRef.update(); } + /** + * Get all Refs + * + * @return list of refs + * @throws IOException + */ + public List getRefs() throws IOException { + return db.getRefDatabase().getRefs(); + } + /** * Checkout a branch * diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkMergedIntoTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkMergedIntoTest.java index 2c21eb60d..2f16aa49e 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkMergedIntoTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkMergedIntoTest.java @@ -11,6 +11,9 @@ import static org.junit.Assert.assertTrue; +import java.util.List; +import java.util.stream.Collectors; +import org.eclipse.jgit.lib.Ref; import org.junit.Test; public class RevWalkMergedIntoTest extends RevWalkTestCase { @@ -44,4 +47,82 @@ public void testOldCommitWalk() throws Exception { final RevCommit t = commit(n, o); assertTrue(rw.isMergedInto(b, t)); } + + @Test + public void testGetMergedInto() throws Exception { + /* + * i + * / \ + * A o + * / \ \ + * o1 o2 E + * / \ / \ + * B C D + */ + String b = "refs/heads/b"; + String c = "refs/heads/c"; + String d = "refs/heads/d"; + String e = "refs/heads/e"; + final RevCommit i = commit(); + final RevCommit a = commit(i); + final RevCommit o1 = commit(a); + final RevCommit o2 = commit(a); + createBranch(commit(o1), b); + createBranch(commit(o1, o2), c); + createBranch(commit(o2), d); + createBranch(commit(commit(i)), e); + + List modifiedResult = rw.getMergedInto(a, getRefs()) + .stream().map(Ref::getName).collect(Collectors.toList()); + + assertTrue(modifiedResult.size() == 3); + assertTrue(modifiedResult.contains(b)); + assertTrue(modifiedResult.contains(c)); + assertTrue(modifiedResult.contains(d)); + } + + @Test + public void testIsMergedIntoAny() throws Exception { + /* + * i + * / \ + * A o + * / \ + * o C + * / + * B + */ + String b = "refs/heads/b"; + String c = "refs/heads/c"; + final RevCommit i = commit(); + final RevCommit a = commit(i); + createBranch(commit(commit(a)), b); + createBranch(commit(commit(i)), c); + + assertTrue( rw.isMergedIntoAny(a, getRefs())); + } + + @Test + public void testIsMergedIntoAll() throws Exception { + /* + * + * A + * / \ + * o1 o2 + * / \ / \ + * B C D + */ + + String b = "refs/heads/b"; + String c = "refs/heads/c"; + String d = "refs/heads/c"; + final RevCommit a = commit(); + final RevCommit o1 = commit(a); + final RevCommit o2 = commit(a); + createBranch(commit(o1), b); + createBranch(commit(o1, o2), c); + createBranch(commit(o2), d); + + assertTrue(rw.isMergedIntoAll(a, getRefs())); + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java index 631d861c0..3ca2ff603 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java @@ -36,6 +36,7 @@ import org.eclipse.jgit.lib.ObjectIdOwnerMap; import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.filter.RevFilter; import org.eclipse.jgit.treewalk.filter.TreeFilter; @@ -181,6 +182,12 @@ public class RevWalk implements Iterable, AutoCloseable { boolean shallowCommitsInitialized; + private enum GetMergedIntoStrategy { + RETURN_ON_FIRST_FOUND, + RETURN_ON_FIRST_NOT_FOUND, + EVALUATE_ALL + } + /** * Create a new revision walker for a given repository. * @@ -424,6 +431,113 @@ public boolean isMergedInto(RevCommit base, RevCommit tip) } } + /** + * Determine the Refs into which a commit is merged. + *

+ * A commit is merged into a ref if we can find a path of commits that leads + * from that specific ref and ends at commit. + *

+ * + * @param commit + * commit the caller thinks is reachable from refs. + * @param refs + * refs to start iteration from, and which is most likely a + * descendant (child) of commit. + * @return list of refs that are reachable from commit. + * @throws java.io.IOException + * a pack file or loose object could not be read. + * @since 5.12 + */ + public List getMergedInto(RevCommit commit, Collection refs) + throws IOException{ + return getMergedInto(commit, refs, GetMergedIntoStrategy.EVALUATE_ALL); + } + + /** + * Determine if a commit is merged into any of the given + * refs. + * + * @param commit + * commit the caller thinks is reachable from refs. + * @param refs + * refs to start iteration from, and which is most likely a + * descendant (child) of commit. + * @return true if commit is merged into any of the refs; false otherwise. + * @throws java.io.IOException + * a pack file or loose object could not be read. + * @since 5.12 + */ + public boolean isMergedIntoAny(RevCommit commit, Collection refs) + throws IOException { + return getMergedInto(commit, refs, + GetMergedIntoStrategy.RETURN_ON_FIRST_FOUND).size() > 0; + } + + /** + * Determine if a commit is merged into all of the given + * refs. + * + * @param commit + * commit the caller thinks is reachable from refs. + * @param refs + * refs to start iteration from, and which is most likely a + * descendant (child) of commit. + * @return true if commit is merged into all of the refs; false otherwise. + * @throws java.io.IOException + * a pack file or loose object could not be read. + * @since 5.12 + */ + public boolean isMergedIntoAll(RevCommit commit, Collection refs) + throws IOException { + return getMergedInto(commit, refs, + GetMergedIntoStrategy.RETURN_ON_FIRST_NOT_FOUND).size() + == refs.size(); + } + + private List getMergedInto(RevCommit needle, Collection haystacks, + Enum returnStrategy) throws IOException { + List result = new ArrayList<>(); + RevFilter oldRF = filter; + TreeFilter oldTF = treeFilter; + try { + finishDelayedFreeFlags(); + filter = RevFilter.ALL; + treeFilter = TreeFilter.ALL; + for (Ref r: haystacks) { + RevObject o = parseAny(r.getObjectId()); + if (!(o instanceof RevCommit)) { + continue; + } + RevCommit c = (RevCommit) o; + resetRetain(RevFlag.UNINTERESTING); + markStart(c); + boolean commitFound = false; + RevCommit next; + while ((next = next()) != null) { + if (References.isSameObject(next, needle)) { + result.add(r); + if (returnStrategy == GetMergedIntoStrategy.RETURN_ON_FIRST_FOUND) { + return result; + } + commitFound = true; + break; + } + } + if(!commitFound){ + markUninteresting(c); + if (returnStrategy == GetMergedIntoStrategy.RETURN_ON_FIRST_NOT_FOUND) { + return result; + } + } + } + } finally { + reset(~freeFlags & APP_FLAGS); + filter = oldRF; + treeFilter = oldTF; + } + return result; + } + /** * Pop the next most recent commit. *