reftable: resolve symbolic references
resolve(Ref) helps callers recursively chase symbolic references and
is a useful function when wrapping a Reftable inside a RefDatabase, as
RefCursor does not resolve symbolic references during iteration.
Change-Id: I1ba143f403773497972e225dc92c35ecb989e154
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 b53853b..6809d7b 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;
@@ -240,6 +241,42 @@
}
@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";
Ref exp = newRef(name);
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 e07bd28..1189ed3 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 @@
}
}
+ /**
+ * 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;
}