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. *