Browse Source

RefDatabase/Ref: Add versioning to reference database

In DFS implementations the reference table can fall out of sync, but
it is not possible to check this situation in the current API.

Add a property to the Refs indicating the order of its updates.  This
version is set only by RefDatabase implementations that support
versioning (e.g reftable based).

Caller is responsible to check if the reference db creates versioned
refs before accessing getUpdateIndex(). E.g:

   Ref ref = refdb.exactRef(...);
   if (refdb.hasVersioning()) {
       ref.getUpdateIndex();
   }

Change-Id: I0d5ec8e8df47c730301b2e12851a6bf3dac9d120
Signed-off-by: Ivan Frade <ifrade@google.com>
stable-5.3
Ivan Frade 6 years ago
parent
commit
6ea888a036
  1. 28
      org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java
  2. 80
      org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/MergedReftableTest.java
  3. 4
      org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableCompactorTest.java
  4. 19
      org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java
  5. 39
      org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdRefTest.java
  6. 8
      org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SymbolicRefTest.java
  7. 6
      org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableDatabase.java
  8. 19
      org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockReader.java
  9. 2
      org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/Reftable.java
  10. 12
      org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReader.java
  11. 84
      org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java
  12. 23
      org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java
  13. 13
      org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
  14. 33
      org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java

28
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java

@ -61,6 +61,7 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
@ -134,6 +135,33 @@ public class RefDirectoryTest extends LocalDiskRepositoryTestCase {
assertEquals("ref: refs/heads/master\n", read(new File(d, HEAD))); assertEquals("ref: refs/heads/master\n", read(new File(d, HEAD)));
} }
@Test(expected = UnsupportedOperationException.class)
public void testVersioningNotImplemented_exactRef() throws IOException {
assertFalse(refdir.hasVersioning());
Ref ref = refdir.exactRef(HEAD);
assertNotNull(ref);
ref.getUpdateIndex(); // Not implemented on FS
}
@Test
public void testVersioningNotImplemented_getRefs() throws Exception {
assertFalse(refdir.hasVersioning());
RevCommit C = repo.commit().parent(B).create();
repo.update("master", C);
List<Ref> refs = refdir.getRefs();
for (Ref ref : refs) {
try {
ref.getUpdateIndex();
fail("FS doesn't implement ref versioning");
} catch (UnsupportedOperationException e) {
// ok
}
}
}
@Test @Test
public void testGetRefs_EmptyDatabase() throws IOException { public void testGetRefs_EmptyDatabase() throws IOException {
Map<String, Ref> all; Map<String, Ref> all;

80
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/MergedReftableTest.java

@ -44,6 +44,7 @@
package org.eclipse.jgit.internal.storage.reftable; package org.eclipse.jgit.internal.storage.reftable;
import static org.eclipse.jgit.lib.Constants.HEAD; import static org.eclipse.jgit.lib.Constants.HEAD;
import static org.eclipse.jgit.lib.Constants.MASTER;
import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH; import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
import static org.eclipse.jgit.lib.Constants.R_HEADS; import static org.eclipse.jgit.lib.Constants.R_HEADS;
import static org.eclipse.jgit.lib.Ref.Storage.NEW; import static org.eclipse.jgit.lib.Ref.Storage.NEW;
@ -68,6 +69,7 @@ import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdRef; import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefComparator; import org.eclipse.jgit.lib.RefComparator;
import org.eclipse.jgit.lib.SymbolicRef;
import org.junit.Test; import org.junit.Test;
public class MergedReftableTest { public class MergedReftableTest {
@ -128,6 +130,7 @@ public class MergedReftableTest {
Ref act = rc.getRef(); Ref act = rc.getRef();
assertEquals(exp.getName(), act.getName()); assertEquals(exp.getName(), act.getName());
assertEquals(exp.getObjectId(), act.getObjectId()); assertEquals(exp.getObjectId(), act.getObjectId());
assertEquals(1, act.getUpdateIndex());
} }
assertFalse(rc.next()); assertFalse(rc.next());
} }
@ -145,6 +148,7 @@ public class MergedReftableTest {
assertTrue(rc.next()); assertTrue(rc.next());
assertEquals("refs/heads/master", rc.getRef().getName()); assertEquals("refs/heads/master", rc.getRef().getName());
assertEquals(id(2), rc.getRef().getObjectId()); assertEquals(id(2), rc.getRef().getObjectId());
assertEquals(1, rc.getRef().getUpdateIndex());
assertFalse(rc.next()); assertFalse(rc.next());
} }
} }
@ -162,6 +166,7 @@ public class MergedReftableTest {
assertEquals("refs/heads/master", rc.getRef().getName()); assertEquals("refs/heads/master", rc.getRef().getName());
assertEquals(id(2), rc.getRef().getObjectId()); assertEquals(id(2), rc.getRef().getObjectId());
assertFalse(rc.next()); assertFalse(rc.next());
assertEquals(1, rc.getRef().getUpdateIndex());
} }
} }
@ -177,6 +182,7 @@ public class MergedReftableTest {
assertTrue(rc.next()); assertTrue(rc.next());
assertEquals("refs/heads/master", rc.getRef().getName()); assertEquals("refs/heads/master", rc.getRef().getName());
assertEquals(id(2), rc.getRef().getObjectId()); assertEquals(id(2), rc.getRef().getObjectId());
assertEquals(1, rc.getRef().getUpdateIndex());
assertFalse(rc.next()); assertFalse(rc.next());
} }
} }
@ -212,6 +218,7 @@ public class MergedReftableTest {
Ref act = rc.getRef(); Ref act = rc.getRef();
assertEquals(exp.getName(), act.getName()); assertEquals(exp.getName(), act.getName());
assertEquals(exp.getObjectId(), act.getObjectId()); assertEquals(exp.getObjectId(), act.getObjectId());
assertEquals(1, rc.getRef().getUpdateIndex());
} }
assertFalse(rc.next()); assertFalse(rc.next());
} }
@ -231,9 +238,11 @@ public class MergedReftableTest {
assertTrue(rc.next()); assertTrue(rc.next());
assertEquals("refs/heads/apple", rc.getRef().getName()); assertEquals("refs/heads/apple", rc.getRef().getName());
assertEquals(id(3), rc.getRef().getObjectId()); assertEquals(id(3), rc.getRef().getObjectId());
assertEquals(2000, rc.getRef().getUpdateIndex());
assertTrue(rc.next()); assertTrue(rc.next());
assertEquals("refs/heads/banana", rc.getRef().getName()); assertEquals("refs/heads/banana", rc.getRef().getName());
assertEquals(id(2), rc.getRef().getObjectId()); assertEquals(id(2), rc.getRef().getObjectId());
assertEquals(1000, rc.getRef().getUpdateIndex());
assertFalse(rc.next()); assertFalse(rc.next());
} }
} }
@ -251,12 +260,14 @@ public class MergedReftableTest {
Ref r = rc.getRef(); Ref r = rc.getRef();
assertEquals("refs/heads/master", r.getName()); assertEquals("refs/heads/master", r.getName());
assertEquals(id(8), r.getObjectId()); assertEquals(id(8), r.getObjectId());
assertEquals(1, rc.getRef().getUpdateIndex());
assertTrue(rc.next()); assertTrue(rc.next());
r = rc.getRef(); r = rc.getRef();
assertEquals("refs/heads/next", r.getName()); assertEquals("refs/heads/next", r.getName());
assertEquals(NEW, r.getStorage()); assertEquals(NEW, r.getStorage());
assertNull(r.getObjectId()); assertNull(r.getObjectId());
assertEquals(1, rc.getRef().getUpdateIndex());
assertFalse(rc.next()); assertFalse(rc.next());
} }
@ -277,6 +288,7 @@ public class MergedReftableTest {
Ref act = rc.getRef(); Ref act = rc.getRef();
assertEquals(exp.getName(), act.getName()); assertEquals(exp.getName(), act.getName());
assertEquals(exp.getObjectId(), act.getObjectId()); assertEquals(exp.getObjectId(), act.getObjectId());
assertEquals(1, act.getUpdateIndex());
assertFalse(rc.next()); assertFalse(rc.next());
} }
} }
@ -303,16 +315,19 @@ public class MergedReftableTest {
assertTrue(rc.next()); assertTrue(rc.next());
assertEquals("refs/heads/a", rc.getRef().getName()); assertEquals("refs/heads/a", rc.getRef().getName());
assertEquals(id(1), rc.getRef().getObjectId()); assertEquals(id(1), rc.getRef().getObjectId());
assertEquals(1, rc.getRef().getUpdateIndex());
assertEquals(1, rc.getUpdateIndex()); assertEquals(1, rc.getUpdateIndex());
assertTrue(rc.next()); assertTrue(rc.next());
assertEquals("refs/heads/b", rc.getRef().getName()); assertEquals("refs/heads/b", rc.getRef().getName());
assertEquals(id(2), rc.getRef().getObjectId()); assertEquals(id(2), rc.getRef().getObjectId());
assertEquals(2, rc.getRef().getUpdateIndex());
assertEquals(2, rc.getUpdateIndex()); assertEquals(2, rc.getUpdateIndex());
assertTrue(rc.next()); assertTrue(rc.next());
assertEquals("refs/heads/c", rc.getRef().getName()); assertEquals("refs/heads/c", rc.getRef().getName());
assertEquals(id(3), rc.getRef().getObjectId()); assertEquals(id(3), rc.getRef().getObjectId());
assertEquals(3, rc.getRef().getUpdateIndex());
assertEquals(3, rc.getUpdateIndex()); assertEquals(3, rc.getUpdateIndex());
} }
} }
@ -344,6 +359,63 @@ public class MergedReftableTest {
} }
} }
@Test
public void versioningSymbolicReftargetMoves() throws IOException {
Ref master = ref(MASTER, 100);
List<Ref> delta1 = Arrays.asList(master, sym(HEAD, MASTER));
List<Ref> delta2 = Arrays.asList(ref(MASTER, 200));
MergedReftable mr = merge(write(delta1, 1), write(delta2, 2));
Ref head = mr.exactRef(HEAD);
assertEquals(head.getUpdateIndex(), 1);
Ref masterRef = mr.exactRef(MASTER);
assertEquals(masterRef.getUpdateIndex(), 2);
}
@Test
public void versioningSymbolicRefMoves() throws IOException {
Ref branchX = ref("refs/heads/branchX", 200);
List<Ref> delta1 = Arrays.asList(ref(MASTER, 100), branchX,
sym(HEAD, MASTER));
List<Ref> delta2 = Arrays.asList(sym(HEAD, "refs/heads/branchX"));
List<Ref> delta3 = Arrays.asList(sym(HEAD, MASTER));
MergedReftable mr = merge(write(delta1, 1), write(delta2, 2),
write(delta3, 3));
Ref head = mr.exactRef(HEAD);
assertEquals(head.getUpdateIndex(), 3);
Ref masterRef = mr.exactRef(MASTER);
assertEquals(masterRef.getUpdateIndex(), 1);
Ref branchRef = mr.exactRef(MASTER);
assertEquals(branchRef.getUpdateIndex(), 1);
}
@Test
public void versioningResolveRef() throws IOException {
List<Ref> delta1 = Arrays.asList(sym(HEAD, "refs/heads/tmp"),
sym("refs/heads/tmp", MASTER), ref(MASTER, 100));
List<Ref> delta2 = Arrays.asList(ref(MASTER, 200));
List<Ref> delta3 = Arrays.asList(ref(MASTER, 300));
MergedReftable mr = merge(write(delta1, 1), write(delta2, 2),
write(delta3, 3));
Ref head = mr.exactRef(HEAD);
Ref resolvedHead = mr.resolve(head);
assertEquals(resolvedHead.getObjectId(), id(300));
assertEquals("HEAD has not moved", resolvedHead.getUpdateIndex(), 1);
Ref master = mr.exactRef(MASTER);
Ref resolvedMaster = mr.resolve(master);
assertEquals(resolvedMaster.getObjectId(), id(300));
assertEquals("master also has update index",
resolvedMaster.getUpdateIndex(), 3);
}
private static MergedReftable merge(byte[]... table) { private static MergedReftable merge(byte[]... table) {
List<Reftable> stack = new ArrayList<>(table.length); List<Reftable> stack = new ArrayList<>(table.length);
for (byte[] b : table) { for (byte[] b : table) {
@ -360,6 +432,14 @@ public class MergedReftableTest {
return new ObjectIdRef.PeeledNonTag(PACKED, name, id(id)); return new ObjectIdRef.PeeledNonTag(PACKED, name, id(id));
} }
private static Ref sym(String name, String target) {
return new SymbolicRef(name, newRef(target));
}
private static Ref newRef(String name) {
return new ObjectIdRef.Unpeeled(NEW, name, null);
}
private static Ref delete(String name) { private static Ref delete(String name) {
return new ObjectIdRef.Unpeeled(NEW, name, null); return new ObjectIdRef.Unpeeled(NEW, name, null);
} }

4
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableCompactorTest.java

@ -108,6 +108,7 @@ public class ReftableCompactorTest {
assertTrue(rc.next()); assertTrue(rc.next());
assertEquals(MASTER, rc.getRef().getName()); assertEquals(MASTER, rc.getRef().getName());
assertEquals(id(1), rc.getRef().getObjectId()); assertEquals(id(1), rc.getRef().getObjectId());
assertEquals(0, rc.getRef().getUpdateIndex());
assertEquals(0, rc.getUpdateIndex()); assertEquals(0, rc.getUpdateIndex());
} }
} }
@ -155,6 +156,7 @@ public class ReftableCompactorTest {
assertTrue(rc.next()); assertTrue(rc.next());
assertEquals(MASTER, rc.getRef().getName()); assertEquals(MASTER, rc.getRef().getName());
assertEquals(id(2), rc.getRef().getObjectId()); assertEquals(id(2), rc.getRef().getObjectId());
assertEquals(1, rc.getRef().getUpdateIndex());
assertEquals(1, rc.getUpdateIndex()); assertEquals(1, rc.getUpdateIndex());
} }
} }
@ -203,11 +205,13 @@ public class ReftableCompactorTest {
assertTrue(rc.next()); assertTrue(rc.next());
assertEquals(MASTER, rc.getRef().getName()); assertEquals(MASTER, rc.getRef().getName());
assertEquals(id(3), rc.getRef().getObjectId()); assertEquals(id(3), rc.getRef().getObjectId());
assertEquals(1, rc.getRef().getUpdateIndex());
assertEquals(1, rc.getUpdateIndex()); assertEquals(1, rc.getUpdateIndex());
assertTrue(rc.next()); assertTrue(rc.next());
assertEquals(NEXT, rc.getRef().getName()); assertEquals(NEXT, rc.getRef().getName());
assertEquals(id(2), rc.getRef().getObjectId()); assertEquals(id(2), rc.getRef().getObjectId());
assertEquals(0, rc.getRef().getUpdateIndex());
assertEquals(0, rc.getUpdateIndex()); assertEquals(0, rc.getUpdateIndex());
} }
} }

19
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java

@ -186,6 +186,7 @@ public class ReftableTest {
assertFalse(act.isSymbolic()); assertFalse(act.isSymbolic());
assertEquals(exp.getName(), act.getName()); assertEquals(exp.getName(), act.getName());
assertEquals(exp.getObjectId(), act.getObjectId()); assertEquals(exp.getObjectId(), act.getObjectId());
assertEquals(0, act.getUpdateIndex());
assertNull(act.getPeeledObjectId()); assertNull(act.getPeeledObjectId());
assertFalse(rc.wasDeleted()); assertFalse(rc.wasDeleted());
assertFalse(rc.next()); assertFalse(rc.next());
@ -195,6 +196,7 @@ public class ReftableTest {
Ref act = rc.getRef(); Ref act = rc.getRef();
assertNotNull(act); assertNotNull(act);
assertEquals(exp.getName(), act.getName()); assertEquals(exp.getName(), act.getName());
assertEquals(0, act.getUpdateIndex());
assertFalse(rc.next()); assertFalse(rc.next());
} }
} }
@ -216,6 +218,7 @@ public class ReftableTest {
assertEquals(exp.getName(), act.getName()); assertEquals(exp.getName(), act.getName());
assertEquals(exp.getObjectId(), act.getObjectId()); assertEquals(exp.getObjectId(), act.getObjectId());
assertEquals(exp.getPeeledObjectId(), act.getPeeledObjectId()); assertEquals(exp.getPeeledObjectId(), act.getPeeledObjectId());
assertEquals(0, act.getUpdateIndex());
} }
} }
@ -237,6 +240,7 @@ public class ReftableTest {
assertNotNull(act.getLeaf()); assertNotNull(act.getLeaf());
assertEquals(MASTER, act.getTarget().getName()); assertEquals(MASTER, act.getTarget().getName());
assertNull(act.getObjectId()); assertNull(act.getObjectId());
assertEquals(0, act.getUpdateIndex());
} }
} }
@ -250,14 +254,17 @@ public class ReftableTest {
Ref head = t.exactRef(HEAD); Ref head = t.exactRef(HEAD);
assertNull(head.getObjectId()); assertNull(head.getObjectId());
assertEquals("refs/heads/tmp", head.getTarget().getName()); assertEquals("refs/heads/tmp", head.getTarget().getName());
assertEquals(0, head.getUpdateIndex());
head = t.resolve(head); head = t.resolve(head);
assertNotNull(head); assertNotNull(head);
assertEquals(id(1), head.getObjectId()); assertEquals(id(1), head.getObjectId());
assertEquals(0, head.getUpdateIndex());
Ref master = t.exactRef(MASTER); Ref master = t.exactRef(MASTER);
assertNotNull(master); assertNotNull(master);
assertSame(master, t.resolve(master)); assertSame(master, t.resolve(master));
assertEquals(0, master.getUpdateIndex());
} }
@Test @Test
@ -335,14 +342,17 @@ public class ReftableTest {
try (RefCursor rc = t.seekRefsWithPrefix("refs/tags/")) { try (RefCursor rc = t.seekRefsWithPrefix("refs/tags/")) {
assertTrue(rc.next()); assertTrue(rc.next());
assertEquals(V1_0, rc.getRef().getName()); assertEquals(V1_0, rc.getRef().getName());
assertEquals(0, rc.getRef().getUpdateIndex());
assertFalse(rc.next()); assertFalse(rc.next());
} }
try (RefCursor rc = t.seekRefsWithPrefix("refs/heads/")) { try (RefCursor rc = t.seekRefsWithPrefix("refs/heads/")) {
assertTrue(rc.next()); assertTrue(rc.next());
assertEquals(MASTER, rc.getRef().getName()); assertEquals(MASTER, rc.getRef().getName());
assertEquals(0, rc.getRef().getUpdateIndex());
assertTrue(rc.next()); assertTrue(rc.next());
assertEquals(NEXT, rc.getRef().getName()); assertEquals(NEXT, rc.getRef().getName());
assertEquals(0, rc.getRef().getUpdateIndex());
assertFalse(rc.next()); assertFalse(rc.next());
} }
@ -432,11 +442,13 @@ public class ReftableTest {
assertTrue(rc.next()); assertTrue(rc.next());
assertEquals(MASTER, rc.getRef().getName()); assertEquals(MASTER, rc.getRef().getName());
assertEquals(id(1), rc.getRef().getObjectId()); assertEquals(id(1), rc.getRef().getObjectId());
assertEquals(1, rc.getRef().getUpdateIndex());
assertEquals(1, rc.getUpdateIndex()); assertEquals(1, rc.getUpdateIndex());
assertTrue(rc.next()); assertTrue(rc.next());
assertEquals(NEXT, rc.getRef().getName()); assertEquals(NEXT, rc.getRef().getName());
assertEquals(id(2), rc.getRef().getObjectId()); assertEquals(id(2), rc.getRef().getObjectId());
assertEquals(1, rc.getRef().getUpdateIndex());
assertFalse(rc.next()); assertFalse(rc.next());
} }
try (LogCursor lc = t.allLogs()) { try (LogCursor lc = t.allLogs()) {
@ -569,6 +581,7 @@ public class ReftableTest {
assertTrue("has 42", rc.next()); assertTrue("has 42", rc.next());
assertEquals("refs/heads/42", rc.getRef().getName()); assertEquals("refs/heads/42", rc.getRef().getName());
assertEquals(id(42), rc.getRef().getObjectId()); assertEquals(id(42), rc.getRef().getObjectId());
assertEquals(0, rc.getRef().getUpdateIndex());
assertFalse(rc.next()); assertFalse(rc.next());
} }
try (RefCursor rc = t.byObjectId(id(100))) { try (RefCursor rc = t.byObjectId(id(100))) {
@ -579,6 +592,7 @@ public class ReftableTest {
assertTrue("has master", rc.next()); assertTrue("has master", rc.next());
assertEquals("refs/heads/master", rc.getRef().getName()); assertEquals("refs/heads/master", rc.getRef().getName());
assertEquals(id(100), rc.getRef().getObjectId()); assertEquals(id(100), rc.getRef().getObjectId());
assertEquals(0, rc.getRef().getUpdateIndex());
assertFalse(rc.next()); assertFalse(rc.next());
} }
@ -600,6 +614,7 @@ public class ReftableTest {
assertTrue("has 42", rc.next()); assertTrue("has 42", rc.next());
assertEquals("refs/heads/42", rc.getRef().getName()); assertEquals("refs/heads/42", rc.getRef().getName());
assertEquals(id(42), rc.getRef().getObjectId()); assertEquals(id(42), rc.getRef().getObjectId());
assertEquals(0, rc.getRef().getUpdateIndex());
assertFalse(rc.next()); assertFalse(rc.next());
} }
try (RefCursor rc = t.byObjectId(id(100))) { try (RefCursor rc = t.byObjectId(id(100))) {
@ -610,6 +625,7 @@ public class ReftableTest {
assertTrue("has master", rc.next()); assertTrue("has master", rc.next());
assertEquals("refs/heads/master", rc.getRef().getName()); assertEquals("refs/heads/master", rc.getRef().getName());
assertEquals(id(100), rc.getRef().getObjectId()); assertEquals(id(100), rc.getRef().getObjectId());
assertEquals(0, rc.getRef().getUpdateIndex());
assertFalse(rc.next()); assertFalse(rc.next());
} }
@ -654,7 +670,6 @@ public class ReftableTest {
} }
} }
private static void assertScan(List<Ref> refs, Reftable t) private static void assertScan(List<Ref> refs, Reftable t)
throws IOException { throws IOException {
try (RefCursor rc = t.allRefs()) { try (RefCursor rc = t.allRefs()) {
@ -663,6 +678,7 @@ public class ReftableTest {
Ref act = rc.getRef(); Ref act = rc.getRef();
assertEquals(exp.getName(), act.getName()); assertEquals(exp.getName(), act.getName());
assertEquals(exp.getObjectId(), act.getObjectId()); assertEquals(exp.getObjectId(), act.getObjectId());
assertEquals(0, rc.getRef().getUpdateIndex());
} }
assertFalse(rc.next()); assertFalse(rc.next());
} }
@ -676,6 +692,7 @@ public class ReftableTest {
Ref act = rc.getRef(); Ref act = rc.getRef();
assertEquals(exp.getName(), act.getName()); assertEquals(exp.getName(), act.getName());
assertEquals(exp.getObjectId(), act.getObjectId()); assertEquals(exp.getObjectId(), act.getObjectId());
assertEquals(0, rc.getRef().getUpdateIndex());
assertFalse(rc.next()); assertFalse(rc.next());
} }
} }

39
org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdRefTest.java

@ -48,6 +48,10 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame; import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.Arrays;
import java.util.List;
import org.junit.Test; import org.junit.Test;
@ -114,11 +118,44 @@ public class ObjectIdRefTest {
assertSame(ID_B, r.getPeeledObjectId()); assertSame(ID_B, r.getPeeledObjectId());
} }
@Test
public void testUpdateIndex() {
ObjectIdRef r;
r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, name, ID_A, 3);
assertTrue(r.getUpdateIndex() == 3);
r = new ObjectIdRef.PeeledTag(Ref.Storage.LOOSE, name, ID_A, ID_B, 4);
assertTrue(r.getUpdateIndex() == 4);
r = new ObjectIdRef.PeeledNonTag(Ref.Storage.LOOSE, name, ID_A, 5);
assertTrue(r.getUpdateIndex() == 5);
}
@Test
public void testUpdateIndexNotSet() {
List<ObjectIdRef> r = Arrays.asList(
new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, name, ID_A),
new ObjectIdRef.PeeledTag(Ref.Storage.LOOSE, name, ID_A, ID_B),
new ObjectIdRef.PeeledNonTag(Ref.Storage.LOOSE, name, ID_A));
for (ObjectIdRef ref : r) {
try {
ref.getUpdateIndex();
fail("Update index wasn't set. It must throw");
} catch (UnsupportedOperationException u) {
// Ok
}
}
}
@Test @Test
public void testToString() { public void testToString() {
ObjectIdRef r; ObjectIdRef r;
r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, name, ID_A); r = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, name, ID_A);
assertEquals("Ref[" + name + "=" + ID_A.name() + "]", r.toString()); assertEquals("Ref[" + name + "=" + ID_A.name() + "(-1)]",
r.toString());
} }
} }

8
org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SymbolicRefTest.java

@ -68,7 +68,7 @@ public class SymbolicRefTest {
SymbolicRef r; SymbolicRef r;
t = new ObjectIdRef.Unpeeled(Ref.Storage.NEW, targetName, null); t = new ObjectIdRef.Unpeeled(Ref.Storage.NEW, targetName, null);
r = new SymbolicRef(name, t); r = new SymbolicRef(name, t, 1);
assertSame(Ref.Storage.LOOSE, r.getStorage()); assertSame(Ref.Storage.LOOSE, r.getStorage());
assertSame(name, r.getName()); assertSame(name, r.getName());
assertNull("no id on new ref", r.getObjectId()); assertNull("no id on new ref", r.getObjectId());
@ -77,9 +77,10 @@ public class SymbolicRefTest {
assertSame("leaf is t", t, r.getLeaf()); assertSame("leaf is t", t, r.getLeaf());
assertSame("target is t", t, r.getTarget()); assertSame("target is t", t, r.getTarget());
assertTrue("is symbolic", r.isSymbolic()); assertTrue("is symbolic", r.isSymbolic());
assertTrue("holds update index", r.getUpdateIndex() == 1);
t = new ObjectIdRef.Unpeeled(Ref.Storage.PACKED, targetName, ID_A); t = new ObjectIdRef.Unpeeled(Ref.Storage.PACKED, targetName, ID_A);
r = new SymbolicRef(name, t); r = new SymbolicRef(name, t, 2);
assertSame(Ref.Storage.LOOSE, r.getStorage()); assertSame(Ref.Storage.LOOSE, r.getStorage());
assertSame(name, r.getName()); assertSame(name, r.getName());
assertSame(ID_A, r.getObjectId()); assertSame(ID_A, r.getObjectId());
@ -88,6 +89,7 @@ public class SymbolicRefTest {
assertSame("leaf is t", t, r.getLeaf()); assertSame("leaf is t", t, r.getLeaf());
assertSame("target is t", t, r.getTarget()); assertSame("target is t", t, r.getTarget());
assertTrue("is symbolic", r.isSymbolic()); assertTrue("is symbolic", r.isSymbolic());
assertTrue("holds update index", r.getUpdateIndex() == 2);
} }
@Test @Test
@ -133,6 +135,6 @@ public class SymbolicRefTest {
d = new SymbolicRef("D", c); d = new SymbolicRef("D", c);
assertEquals("SymbolicRef[D -> C -> B -> " + targetName + "=" assertEquals("SymbolicRef[D -> C -> B -> " + targetName + "="
+ ID_A.name() + "]", d.toString()); + ID_A.name() + "(-1)]", d.toString());
} }
} }

6
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableDatabase.java

@ -97,6 +97,12 @@ public class DfsReftableDatabase extends DfsRefDatabase {
super(repo); super(repo);
} }
/** {@inheritDoc} */
@Override
public boolean hasVersioning() {
return true;
}
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override
public boolean performsAtomicTransactions() { public boolean performsAtomicTransactions() {

19
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockReader.java

@ -170,24 +170,27 @@ class BlockReader {
return readVarint64(); return readVarint64();
} }
Ref readRef() throws IOException { Ref readRef(long minUpdateIndex) throws IOException {
long updateIndex = minUpdateIndex + readUpdateIndexDelta();
String name = RawParseUtils.decode(UTF_8, nameBuf, 0, nameLen); String name = RawParseUtils.decode(UTF_8, nameBuf, 0, nameLen);
switch (valueType & VALUE_TYPE_MASK) { switch (valueType & VALUE_TYPE_MASK) {
case VALUE_NONE: // delete case VALUE_NONE: // delete
return newRef(name); return newRef(name, updateIndex);
case VALUE_1ID: case VALUE_1ID:
return new ObjectIdRef.PeeledNonTag(PACKED, name, readValueId()); return new ObjectIdRef.PeeledNonTag(PACKED, name, readValueId(),
updateIndex);
case VALUE_2ID: { // annotated tag case VALUE_2ID: { // annotated tag
ObjectId id1 = readValueId(); ObjectId id1 = readValueId();
ObjectId id2 = readValueId(); ObjectId id2 = readValueId();
return new ObjectIdRef.PeeledTag(PACKED, name, id1, id2); return new ObjectIdRef.PeeledTag(PACKED, name, id1, id2,
updateIndex);
} }
case VALUE_SYMREF: { case VALUE_SYMREF: {
String val = readValueString(); String val = readValueString();
return new SymbolicRef(name, newRef(val)); return new SymbolicRef(name, newRef(val, updateIndex), updateIndex);
} }
default: default:
@ -410,7 +413,7 @@ class BlockReader {
* <ul> * <ul>
* <li>{@link #name()} * <li>{@link #name()}
* <li>{@link #match(byte[], boolean)} * <li>{@link #match(byte[], boolean)}
* <li>{@link #readRef()} * <li>{@link #readRef(long)}
* <li>{@link #readLogUpdateIndex()} * <li>{@link #readLogUpdateIndex()}
* <li>{@link #readLogEntry()} * <li>{@link #readLogEntry()}
* <li>{@link #readBlockPositionList()} * <li>{@link #readBlockPositionList()}
@ -575,8 +578,8 @@ class BlockReader {
return val; return val;
} }
private static Ref newRef(String name) { private static Ref newRef(String name, long updateIndex) {
return new ObjectIdRef.Unpeeled(NEW, name, null); return new ObjectIdRef.Unpeeled(NEW, name, null, updateIndex);
} }
private static IOException invalidBlock() { private static IOException invalidBlock() {

2
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/Reftable.java

@ -280,7 +280,7 @@ public abstract class Reftable implements AutoCloseable {
if (dst == null) { if (dst == null) {
return null; // claim it doesn't exist return null; // claim it doesn't exist
} }
return new SymbolicRef(ref.getName(), dst); return new SymbolicRef(ref.getName(), dst, ref.getUpdateIndex());
} }
/** {@inheritDoc} */ /** {@inheritDoc} */

12
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReader.java

@ -479,7 +479,6 @@ public class ReftableReader extends Reftable {
private final boolean prefix; private final boolean prefix;
private Ref ref; private Ref ref;
private long updateIndex;
BlockReader block; BlockReader block;
RefCursorImpl(long scanEnd, byte[] match, boolean prefix) { RefCursorImpl(long scanEnd, byte[] match, boolean prefix) {
@ -508,8 +507,7 @@ public class ReftableReader extends Reftable {
return false; return false;
} }
updateIndex = minUpdateIndex + block.readUpdateIndexDelta(); ref = block.readRef(minUpdateIndex);
ref = block.readRef();
if (!includeDeletes && wasDeleted()) { if (!includeDeletes && wasDeleted()) {
continue; continue;
} }
@ -524,7 +522,7 @@ public class ReftableReader extends Reftable {
@Override @Override
public long getUpdateIndex() { public long getUpdateIndex() {
return updateIndex; return ref.getUpdateIndex();
} }
@Override @Override
@ -605,7 +603,6 @@ public class ReftableReader extends Reftable {
private final ObjectId match; private final ObjectId match;
private Ref ref; private Ref ref;
private long updateIndex;
private int listIdx; private int listIdx;
private LongList blockPos; private LongList blockPos;
@ -679,8 +676,7 @@ public class ReftableReader extends Reftable {
} }
block.parseKey(); block.parseKey();
updateIndex = minUpdateIndex + block.readUpdateIndexDelta(); ref = block.readRef(minUpdateIndex);
ref = block.readRef();
ObjectId id = ref.getObjectId(); ObjectId id = ref.getObjectId();
if (id != null && match.equals(id) if (id != null && match.equals(id)
&& (includeDeletes || !wasDeleted())) { && (includeDeletes || !wasDeleted())) {
@ -696,7 +692,7 @@ public class ReftableReader extends Reftable {
@Override @Override
public long getUpdateIndex() { public long getUpdateIndex() {
return updateIndex; return ref.getUpdateIndex();
} }
@Override @Override

84
org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java

@ -67,7 +67,25 @@ public abstract class ObjectIdRef implements Ref {
*/ */
public Unpeeled(@NonNull Storage st, @NonNull String name, public Unpeeled(@NonNull Storage st, @NonNull String name,
@Nullable ObjectId id) { @Nullable ObjectId id) {
super(st, name, id); super(st, name, id, -1);
}
/**
* Create a new ref pairing with update index.
*
* @param st
* method used to store this ref.
* @param name
* name of this ref.
* @param id
* current value of the ref. May be {@code null} to indicate
* a ref that does not exist yet.
* @param updateIndex
* number increasing with each update to the reference.
*/
public Unpeeled(@NonNull Storage st, @NonNull String name,
@Nullable ObjectId id, long updateIndex) {
super(st, name, id, updateIndex);
} }
@Override @Override
@ -100,7 +118,28 @@ public abstract class ObjectIdRef implements Ref {
*/ */
public PeeledTag(@NonNull Storage st, @NonNull String name, public PeeledTag(@NonNull Storage st, @NonNull String name,
@Nullable ObjectId id, @NonNull ObjectId p) { @Nullable ObjectId id, @NonNull ObjectId p) {
super(st, name, id); super(st, name, id, -1);
peeledObjectId = p;
}
/**
* Create a new ref pairing with update index.
*
* @param st
* method used to store this ref.
* @param name
* name of this ref.
* @param id
* current value of the ref. May be {@code null} to indicate
* a ref that does not exist yet.
* @param p
* the first non-tag object that tag {@code id} points to.
* @param updateIndex
* number increasing with each update to the reference.
*/
public PeeledTag(@NonNull Storage st, @NonNull String name,
@Nullable ObjectId id, @NonNull ObjectId p, long updateIndex) {
super(st, name, id, updateIndex);
peeledObjectId = p; peeledObjectId = p;
} }
@ -131,7 +170,25 @@ public abstract class ObjectIdRef implements Ref {
*/ */
public PeeledNonTag(@NonNull Storage st, @NonNull String name, public PeeledNonTag(@NonNull Storage st, @NonNull String name,
@Nullable ObjectId id) { @Nullable ObjectId id) {
super(st, name, id); super(st, name, id, -1);
}
/**
* Create a new ref pairing with update index.
*
* @param st
* method used to store this ref.
* @param name
* name of this ref.
* @param id
* current value of the ref. May be {@code null} to indicate
* a ref that does not exist yet.
* @param updateIndex
* number increasing with each update to the reference.
*/
public PeeledNonTag(@NonNull Storage st, @NonNull String name,
@Nullable ObjectId id, long updateIndex) {
super(st, name, id, updateIndex);
} }
@Override @Override
@ -152,6 +209,8 @@ public abstract class ObjectIdRef implements Ref {
private final ObjectId objectId; private final ObjectId objectId;
private final long updateIndex;
/** /**
* Create a new ref pairing. * Create a new ref pairing.
* *
@ -162,12 +221,16 @@ public abstract class ObjectIdRef implements Ref {
* @param id * @param id
* current value of the ref. May be {@code null} to indicate a * current value of the ref. May be {@code null} to indicate a
* ref that does not exist yet. * ref that does not exist yet.
* @param updateIndex
* number that increases with each ref update. Set to -1 if the
* storage doesn't support versioning.
*/ */
protected ObjectIdRef(@NonNull Storage st, @NonNull String name, protected ObjectIdRef(@NonNull Storage st, @NonNull String name,
@Nullable ObjectId id) { @Nullable ObjectId id, long updateIndex) {
this.name = name; this.name = name;
this.storage = st; this.storage = st;
this.objectId = id; this.objectId = id;
this.updateIndex = updateIndex;
} }
/** {@inheritDoc} */ /** {@inheritDoc} */
@ -211,6 +274,15 @@ public abstract class ObjectIdRef implements Ref {
return storage; return storage;
} }
/** {@inheritDoc} */
@Override
public long getUpdateIndex() {
if (updateIndex == -1) {
throw new UnsupportedOperationException();
}
return updateIndex;
}
/** {@inheritDoc} */ /** {@inheritDoc} */
@NonNull @NonNull
@Override @Override
@ -220,7 +292,9 @@ public abstract class ObjectIdRef implements Ref {
r.append(getName()); r.append(getName());
r.append('='); r.append('=');
r.append(ObjectId.toString(getObjectId())); r.append(ObjectId.toString(getObjectId()));
r.append(']'); r.append('(');
r.append(updateIndex); // Print value, even if -1
r.append(")]"); //$NON-NLS-1$
return r.toString(); return r.toString();
} }
} }

23
org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java

@ -217,4 +217,27 @@ public interface Ref {
*/ */
@NonNull @NonNull
Storage getStorage(); Storage getStorage();
/**
* Indicator of the relative order between updates of a specific reference
* name. A number that increases when a reference is updated.
* <p>
* With symbolic references, the update index refers to updates of the
* symbolic reference itself. For example, if HEAD points to
* refs/heads/master, then the update index for exactRef("HEAD") will only
* increase when HEAD changes to point to another ref, regardless of how
* many times refs/heads/master is updated.
* <p>
* Should not be used unless the {@code RefDatabase} that instantiated the
* ref supports versioning (see {@link RefDatabase#hasVersioning()})
*
* @return the update index (i.e. version) of this reference.
* @throws UnsupportedOperationException
* if the creator of the instance (e.g. {@link RefDatabase})
* doesn't support versioning and doesn't override this method
* @since 5.3
*/
default long getUpdateIndex() {
throw new UnsupportedOperationException();
}
} }

13
org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java

@ -110,6 +110,19 @@ public abstract class RefDatabase {
*/ */
public abstract void close(); public abstract void close();
/**
* With versioning, each reference has a version number that increases on
* update. See {@link Ref#getUpdateIndex()}.
*
* @implSpec This method returns false by default. Implementations
* supporting versioning must override it to return true.
* @return true if the implementation assigns update indices to references.
* @since 5.3
*/
public boolean hasVersioning() {
return false;
}
/** /**
* Determine if a proposed reference name overlaps with an existing one. * Determine if a proposed reference name overlaps with an existing one.
* <p> * <p>

33
org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java

@ -58,6 +58,8 @@ public class SymbolicRef implements Ref {
private final Ref target; private final Ref target;
private final long updateIndex;
/** /**
* Create a new ref pairing. * Create a new ref pairing.
* *
@ -69,6 +71,24 @@ public class SymbolicRef implements Ref {
public SymbolicRef(@NonNull String refName, @NonNull Ref target) { public SymbolicRef(@NonNull String refName, @NonNull Ref target) {
this.name = refName; this.name = refName;
this.target = target; this.target = target;
this.updateIndex = -1;
}
/**
* Create a new ref pairing.
*
* @param refName
* name of this ref.
* @param target
* the ref we reference and derive our value from.
* @param updateIndex
* index that increases with each update of the reference
*/
public SymbolicRef(@NonNull String refName, @NonNull Ref target,
long updateIndex) {
this.name = refName;
this.target = target;
this.updateIndex = updateIndex;
} }
/** {@inheritDoc} */ /** {@inheritDoc} */
@ -128,6 +148,15 @@ public class SymbolicRef implements Ref {
return getLeaf().isPeeled(); return getLeaf().isPeeled();
} }
/** {@inheritDoc} */
@Override
public long getUpdateIndex() {
if (updateIndex == -1) {
throw new UnsupportedOperationException();
}
return updateIndex;
}
/** {@inheritDoc} */ /** {@inheritDoc} */
@SuppressWarnings("nls") @SuppressWarnings("nls")
@Override @Override
@ -143,7 +172,9 @@ public class SymbolicRef implements Ref {
r.append(cur.getName()); r.append(cur.getName());
r.append('='); r.append('=');
r.append(ObjectId.toString(cur.getObjectId())); r.append(ObjectId.toString(cur.getObjectId()));
r.append("]"); r.append("(");
r.append(updateIndex); // Print value, even if -1
r.append(")]");
return r.toString(); return r.toString();
} }
} }

Loading…
Cancel
Save