diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java index b53853b2e..6809d7b2b 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java @@ -52,6 +52,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -239,6 +240,42 @@ public void oneSymbolicRef() throws IOException { } } + @Test + public void resolveSymbolicRef() throws IOException { + Reftable t = read(write( + sym(HEAD, "refs/heads/tmp"), + sym("refs/heads/tmp", MASTER), + ref(MASTER, 1))); + + Ref head = t.exactRef(HEAD); + assertNull(head.getObjectId()); + assertEquals("refs/heads/tmp", head.getTarget().getName()); + + head = t.resolve(head); + assertNotNull(head); + assertEquals(id(1), head.getObjectId()); + + Ref master = t.exactRef(MASTER); + assertNotNull(master); + assertSame(master, t.resolve(master)); + } + + @Test + public void failDeepChainOfSymbolicRef() throws IOException { + Reftable t = read(write( + sym(HEAD, "refs/heads/1"), + sym("refs/heads/1", "refs/heads/2"), + sym("refs/heads/2", "refs/heads/3"), + sym("refs/heads/3", "refs/heads/4"), + sym("refs/heads/4", "refs/heads/5"), + sym("refs/heads/5", MASTER), + ref(MASTER, 1))); + + Ref head = t.exactRef(HEAD); + assertNull(head.getObjectId()); + assertNull(t.resolve(head)); + } + @Test public void oneDeletedRef() throws IOException { String name = "refs/heads/gone"; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/Reftable.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/Reftable.java index e07bd28b6..1189ed3b9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/Reftable.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/Reftable.java @@ -43,6 +43,8 @@ package org.eclipse.jgit.internal.storage.reftable; +import static org.eclipse.jgit.lib.RefDatabase.MAX_SYMBOLIC_REF_DEPTH; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Collection; @@ -51,6 +53,7 @@ import org.eclipse.jgit.internal.storage.io.BlockSource; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.SymbolicRef; /** Abstract table of references. */ public abstract class Reftable implements AutoCloseable { @@ -218,6 +221,42 @@ public boolean hasId(AnyObjectId id) throws IOException { } } + /** + * Resolve a symbolic reference to populate its value. + * + * @param symref + * reference to resolve. + * @return resolved {@code symref}, or {@code null}. + * @throws IOException + * if references cannot be read. + */ + @Nullable + public Ref resolve(Ref symref) throws IOException { + return resolve(symref, 0); + } + + private Ref resolve(Ref ref, int depth) throws IOException { + if (!ref.isSymbolic()) { + return ref; + } + + Ref dst = ref.getTarget(); + if (MAX_SYMBOLIC_REF_DEPTH <= depth) { + return null; // claim it doesn't exist + } + + dst = exactRef(dst.getName()); + if (dst == null) { + return ref; + } + + dst = resolve(dst, depth + 1); + if (dst == null) { + return null; // claim it doesn't exist + } + return new SymbolicRef(ref.getName(), dst); + } + @Override public abstract void close() throws IOException; }