TreeWalkConnectivityChecker: Support non-commit objects in refs Running in shadow mode in production turned up the case of the reference database containing references to annotated tags and other non-commit objects. Fixed that and added tests. Change-Id: I7f1029e2512c96500658d9556d7c7d7c2753c817
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/transport/connectivity/TreeWalkConnectivityCheckerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/transport/connectivity/TreeWalkConnectivityCheckerTest.java index 284c56e..371f876 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/transport/connectivity/TreeWalkConnectivityCheckerTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/transport/connectivity/TreeWalkConnectivityCheckerTest.java
@@ -40,6 +40,7 @@ import org.eclipse.jgit.lib.TreeFormatter; import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.transport.ConnectivityChecker.ConnectivityCheckInfo; import org.eclipse.jgit.transport.PackParser; @@ -517,10 +518,74 @@ public void testSuccessFallbackToFullRefDatabase() throws Exception { info.setCommands(Collections.singletonList(new ReceiveCommand( ObjectId.zeroId(), newCommit.getId(), "refs/heads/master"))); mockNewPackObjects(newCommit, newCommit.getTree()); + runCheckAndAssertCount(5); } @Test + public void testCheckReachabilityWithBlobInHaves() throws Exception { + RevCommit base = tr.commit().create(); + haves.add(base.getId()); + + RevBlob blob = tr.blob("blob content"); + haves.add(blob.getId()); + + RevCommit newCommit = tr.commit().parent(base).create(); + + setupSingleReceiveCommand(ObjectId.zeroId(), newCommit.getId()); + mockNewPackObjects(newCommit, newCommit.getTree()); + + runCheckAndAssertCount(4); + } + + @Test + public void testCheckReachabilityWithAnnotatedTagInHaves() + throws Exception { + RevCommit base = tr.commit().create(); + + RevTag tag = tr.tag("my-tag", base); + haves.add(tag.getId()); + + RevCommit newCommit = tr.commit().parent(base).create(); + + setupSingleReceiveCommand(ObjectId.zeroId(), newCommit.getId()); + mockNewPackObjects(newCommit, newCommit.getTree()); + + runCheckAndAssertCount(5); + } + + @Test + public void testCheckReachabilityWithBlobInRefs() throws Exception { + RevCommit base = tr.commit().create(); + haves.add(base.getId()); + + RevBlob blob = tr.blob("blob content"); + tr.update("refs/tags/my-blob", blob); + + RevCommit newCommit = tr.commit().parent(base).create(); + + setupSingleReceiveCommand(ObjectId.zeroId(), newCommit.getId()); + mockNewPackObjects(newCommit, newCommit.getTree()); + + runCheckAndAssertCount(4); + } + + @Test + public void testCheckReachabilityWithAnnotatedTagInRefs() throws Exception { + RevCommit base = tr.commit().create(); + + RevTag tag = tr.tag("my-tag", base); + tr.update("refs/tags/my-tag", tag); + + RevCommit newCommit = tr.commit().parent(base).create(); + + setupSingleReceiveCommand(ObjectId.zeroId(), newCommit.getId()); + mockNewPackObjects(newCommit, newCommit.getTree()); + + runCheckAndAssertCount(4); + } + + @Test public void testSuccessWithDeletion() throws Exception { RevBlob blob = tr.blob("hello"); RevCommit base = tr.commit().add("foo", blob).create(); @@ -598,6 +663,98 @@ public void testThinPackSuccess() throws Exception { } @Test + public void testThinPackWithBlobInHaves() throws Exception { + RevCommit base = tr.commit().create(); + haves.add(base.getId()); + + RevBlob baseBlob = tr.blob("base blob content"); + + RevBlob unrelatedBlob = tr.blob("unrelated blob content"); + haves.add(unrelatedBlob.getId()); + + RevCommit newCommit = tr.commit().parent(base).create(); + + setupSingleReceiveCommand(base.getId(), newCommit.getId()); + mockNewPackObjects(newCommit, newCommit.getTree()); + + ObjectIdSubclassMap<ObjectId> baseObjectIds = new ObjectIdSubclassMap<>(); + baseObjectIds.add(baseBlob.getId()); + when(parser.getBaseObjectIds()).thenReturn(baseObjectIds); + info.setCheckObjects(true); + + runCheckAndAssertCount(4); + } + + @Test + public void testThinPackWithSignedTagInHaves() throws Exception { + RevCommit base = tr.commit().create(); + haves.add(base.getId()); + + RevBlob baseBlob = tr.blob("base blob content"); + + RevTag tag = tr.tag("my-tag", base); + haves.add(tag.getId()); + + RevCommit newCommit = tr.commit().parent(base).create(); + + setupSingleReceiveCommand(base.getId(), newCommit.getId()); + mockNewPackObjects(newCommit, newCommit.getTree()); + + ObjectIdSubclassMap<ObjectId> baseObjectIds = new ObjectIdSubclassMap<>(); + baseObjectIds.add(baseBlob.getId()); + when(parser.getBaseObjectIds()).thenReturn(baseObjectIds); + info.setCheckObjects(true); + + runCheckAndAssertCount(4); + } + + @Test + public void testThinPackWithBlobInRefs() throws Exception { + RevCommit base = tr.commit().create(); + haves.add(base.getId()); + + RevBlob baseBlob = tr.blob("base blob content"); + + RevBlob unrelatedBlob = tr.blob("unrelated blob content"); + tr.update("refs/tags/my-blob", unrelatedBlob); + + RevCommit newCommit = tr.commit().parent(base).create(); + + setupSingleReceiveCommand(base.getId(), newCommit.getId()); + mockNewPackObjects(newCommit, newCommit.getTree()); + + ObjectIdSubclassMap<ObjectId> baseObjectIds = new ObjectIdSubclassMap<>(); + baseObjectIds.add(baseBlob.getId()); + when(parser.getBaseObjectIds()).thenReturn(baseObjectIds); + info.setCheckObjects(true); + + runCheckAndAssertCount(4); + } + + @Test + public void testThinPackWithAnnotatedTagInRefs() throws Exception { + RevCommit base = tr.commit().create(); + haves.add(base.getId()); + + RevBlob baseBlob = tr.blob("base blob content"); + + RevTag tag = tr.tag("my-tag", base); + tr.update("refs/tags/my-tag", tag); + + RevCommit newCommit = tr.commit().parent(base).create(); + + setupSingleReceiveCommand(base.getId(), newCommit.getId()); + mockNewPackObjects(newCommit, newCommit.getTree()); + + ObjectIdSubclassMap<ObjectId> baseObjectIds = new ObjectIdSubclassMap<>(); + baseObjectIds.add(baseBlob.getId()); + when(parser.getBaseObjectIds()).thenReturn(baseObjectIds); + info.setCheckObjects(true); + + runCheckAndAssertCount(4); + } + + @Test public void testMultipleCommandsDifferentParents() throws Exception { RevCommit base1 = tr.commit().create(); RevCommit base2 = tr.commit().create();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/connectivity/TreeWalkConnectivityChecker.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/connectivity/TreeWalkConnectivityChecker.java index 8c667df..cfe6bcf 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/connectivity/TreeWalkConnectivityChecker.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/connectivity/TreeWalkConnectivityChecker.java
@@ -21,6 +21,7 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; +import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; @@ -215,7 +216,7 @@ private void checkReachability(RevWalk rw, Repository repo, ReachabilityChecker checker = rw.getObjectReader() .createReachabilityChecker(rw); Stream<RevCommit> starterCommits = advertisedHaves.stream() - .map(id -> rw.lookupCommit(id)); + .map(id -> parseCommitOrNull(rw, id)).filter(Objects::nonNull); Optional<RevCommit> unreachable = checker.areAllReachable( nonAdvertisedParentCommitsOutsidePack, starterCommits); @@ -224,7 +225,8 @@ private void checkReachability(RevWalk rw, Repository repo, // Fallback to check against full ref database Stream<RevCommit> allRefCommits = repo.getRefDatabase() .getRefs().stream() - .map(ref -> rw.lookupCommit(ref.getObjectId())); + .map(ref -> parseCommitOrNull(rw, ref.getObjectId())) + .filter(Objects::nonNull); unreachable = checker.areAllReachable( nonAdvertisedParentCommitsOutsidePack, allRefCommits); if (unreachable.isPresent()) { @@ -276,7 +278,8 @@ private void checkThinPackBases(RevWalk rw, // Fallback to check against full ref database Stream<RevObject> allRefCommits = connectivityCheckInfo .getRepository().getRefDatabase().getRefs().stream() - .map(ref -> rw.lookupCommit(ref.getObjectId())); + .map(ref -> parseAnyUnchecked(ow, ref.getObjectId())) + .filter(Objects::nonNull); unreachable = checker.areAllReachable(targetObjs, allRefCommits); if (unreachable.isPresent()) { @@ -401,6 +404,27 @@ private static RevObject parseAnyUnchecked(RevWalk rw, ObjectId id) { } /** + * Parses an object as a commit, peeling tags if necessary. Returns null if + * the object is not a commit or cannot be peeled to a commit. Throws + * UncheckedIOException on other I/O failures. For use with streams. + * + * @param rw + * the RevWalk to use + * @param id + * the object ID + * @return the parsed commit, or null + */ + private static RevCommit parseCommitOrNull(RevWalk rw, ObjectId id) { + try { + return rw.parseCommit(id); + } catch (IncorrectObjectTypeException e) { + return null; + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + /** * Checks if the new object ID matches any parent tree's object ID at the * current path. *