Introduce getMergedInto(RevCommit commit, Collection<Ref> 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 <quic_achakila@quicinc.com>
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
This commit is contained in:
Adithya Chakilam 2021-02-18 13:41:19 -06:00 committed by Matthias Sohn
parent 4a78d911c5
commit 0bd2f4bf77
3 changed files with 207 additions and 0 deletions

View File

@ -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<Ref> getRefs() throws IOException {
return db.getRefDatabase().getRefs();
}
/**
* Checkout a branch
*

View File

@ -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<String> 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()));
}
}

View File

@ -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<RevCommit>, 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.
* <p>
* A commit is merged into a ref if we can find a path of commits that leads
* from that specific ref and ends at <code>commit</code>.
* <p>
*
* @param commit
* commit the caller thinks is reachable from <code>refs</code>.
* @param refs
* refs to start iteration from, and which is most likely a
* descendant (child) of <code>commit</code>.
* @return list of refs that are reachable from <code>commit</code>.
* @throws java.io.IOException
* a pack file or loose object could not be read.
* @since 5.12
*/
public List<Ref> getMergedInto(RevCommit commit, Collection<Ref> refs)
throws IOException{
return getMergedInto(commit, refs, GetMergedIntoStrategy.EVALUATE_ALL);
}
/**
* Determine if a <code>commit</code> is merged into any of the given
* <code>refs</code>.
*
* @param commit
* commit the caller thinks is reachable from <code>refs</code>.
* @param refs
* refs to start iteration from, and which is most likely a
* descendant (child) of <code>commit</code>.
* @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<Ref> refs)
throws IOException {
return getMergedInto(commit, refs,
GetMergedIntoStrategy.RETURN_ON_FIRST_FOUND).size() > 0;
}
/**
* Determine if a <code>commit</code> is merged into all of the given
* <code>refs</code>.
*
* @param commit
* commit the caller thinks is reachable from <code>refs</code>.
* @param refs
* refs to start iteration from, and which is most likely a
* descendant (child) of <code>commit</code>.
* @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<Ref> refs)
throws IOException {
return getMergedInto(commit, refs,
GetMergedIntoStrategy.RETURN_ON_FIRST_NOT_FOUND).size()
== refs.size();
}
private List<Ref> getMergedInto(RevCommit needle, Collection<Ref> haystacks,
Enum returnStrategy) throws IOException {
List<Ref> 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.
*