From 6ea888a036467649fb4d6be9182599bbef879a6d Mon Sep 17 00:00:00 2001 From: Ivan Frade Date: Wed, 19 Dec 2018 16:43:56 -0800 Subject: [PATCH] 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 --- .../storage/file/RefDirectoryTest.java | 28 +++++++ .../storage/reftable/MergedReftableTest.java | 80 ++++++++++++++++++ .../reftable/ReftableCompactorTest.java | 4 + .../storage/reftable/ReftableTest.java | 19 ++++- .../org/eclipse/jgit/lib/ObjectIdRefTest.java | 39 ++++++++- .../org/eclipse/jgit/lib/SymbolicRefTest.java | 8 +- .../storage/dfs/DfsReftableDatabase.java | 6 ++ .../storage/reftable/BlockReader.java | 19 +++-- .../internal/storage/reftable/Reftable.java | 2 +- .../storage/reftable/ReftableReader.java | 12 +-- .../src/org/eclipse/jgit/lib/ObjectIdRef.java | 84 +++++++++++++++++-- .../src/org/eclipse/jgit/lib/Ref.java | 23 +++++ .../src/org/eclipse/jgit/lib/RefDatabase.java | 13 +++ .../src/org/eclipse/jgit/lib/SymbolicRef.java | 33 +++++++- 14 files changed, 342 insertions(+), 28 deletions(-) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java index 5a2bd9c333..56346e1d85 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java +++ b/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.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; 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))); } + @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 refs = refdir.getRefs(); + + for (Ref ref : refs) { + try { + ref.getUpdateIndex(); + fail("FS doesn't implement ref versioning"); + } catch (UnsupportedOperationException e) { + // ok + } + } + } + @Test public void testGetRefs_EmptyDatabase() throws IOException { Map all; diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/MergedReftableTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/MergedReftableTest.java index 1d11573b99..9f7e7331e5 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/MergedReftableTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/MergedReftableTest.java @@ -44,6 +44,7 @@ package org.eclipse.jgit.internal.storage.reftable; 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.R_HEADS; 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.Ref; import org.eclipse.jgit.lib.RefComparator; +import org.eclipse.jgit.lib.SymbolicRef; import org.junit.Test; public class MergedReftableTest { @@ -128,6 +130,7 @@ public class MergedReftableTest { Ref act = rc.getRef(); assertEquals(exp.getName(), act.getName()); assertEquals(exp.getObjectId(), act.getObjectId()); + assertEquals(1, act.getUpdateIndex()); } assertFalse(rc.next()); } @@ -145,6 +148,7 @@ public class MergedReftableTest { assertTrue(rc.next()); assertEquals("refs/heads/master", rc.getRef().getName()); assertEquals(id(2), rc.getRef().getObjectId()); + assertEquals(1, rc.getRef().getUpdateIndex()); assertFalse(rc.next()); } } @@ -162,6 +166,7 @@ public class MergedReftableTest { assertEquals("refs/heads/master", rc.getRef().getName()); assertEquals(id(2), rc.getRef().getObjectId()); assertFalse(rc.next()); + assertEquals(1, rc.getRef().getUpdateIndex()); } } @@ -177,6 +182,7 @@ public class MergedReftableTest { assertTrue(rc.next()); assertEquals("refs/heads/master", rc.getRef().getName()); assertEquals(id(2), rc.getRef().getObjectId()); + assertEquals(1, rc.getRef().getUpdateIndex()); assertFalse(rc.next()); } } @@ -212,6 +218,7 @@ public class MergedReftableTest { Ref act = rc.getRef(); assertEquals(exp.getName(), act.getName()); assertEquals(exp.getObjectId(), act.getObjectId()); + assertEquals(1, rc.getRef().getUpdateIndex()); } assertFalse(rc.next()); } @@ -231,9 +238,11 @@ public class MergedReftableTest { assertTrue(rc.next()); assertEquals("refs/heads/apple", rc.getRef().getName()); assertEquals(id(3), rc.getRef().getObjectId()); + assertEquals(2000, rc.getRef().getUpdateIndex()); assertTrue(rc.next()); assertEquals("refs/heads/banana", rc.getRef().getName()); assertEquals(id(2), rc.getRef().getObjectId()); + assertEquals(1000, rc.getRef().getUpdateIndex()); assertFalse(rc.next()); } } @@ -251,12 +260,14 @@ public class MergedReftableTest { Ref r = rc.getRef(); assertEquals("refs/heads/master", r.getName()); assertEquals(id(8), r.getObjectId()); + assertEquals(1, rc.getRef().getUpdateIndex()); assertTrue(rc.next()); r = rc.getRef(); assertEquals("refs/heads/next", r.getName()); assertEquals(NEW, r.getStorage()); assertNull(r.getObjectId()); + assertEquals(1, rc.getRef().getUpdateIndex()); assertFalse(rc.next()); } @@ -277,6 +288,7 @@ public class MergedReftableTest { Ref act = rc.getRef(); assertEquals(exp.getName(), act.getName()); assertEquals(exp.getObjectId(), act.getObjectId()); + assertEquals(1, act.getUpdateIndex()); assertFalse(rc.next()); } } @@ -303,16 +315,19 @@ public class MergedReftableTest { assertTrue(rc.next()); assertEquals("refs/heads/a", rc.getRef().getName()); assertEquals(id(1), rc.getRef().getObjectId()); + assertEquals(1, rc.getRef().getUpdateIndex()); assertEquals(1, rc.getUpdateIndex()); assertTrue(rc.next()); assertEquals("refs/heads/b", rc.getRef().getName()); assertEquals(id(2), rc.getRef().getObjectId()); + assertEquals(2, rc.getRef().getUpdateIndex()); assertEquals(2, rc.getUpdateIndex()); assertTrue(rc.next()); assertEquals("refs/heads/c", rc.getRef().getName()); assertEquals(id(3), rc.getRef().getObjectId()); + assertEquals(3, rc.getRef().getUpdateIndex()); assertEquals(3, rc.getUpdateIndex()); } } @@ -344,6 +359,63 @@ public class MergedReftableTest { } } + @Test + public void versioningSymbolicReftargetMoves() throws IOException { + Ref master = ref(MASTER, 100); + + List delta1 = Arrays.asList(master, sym(HEAD, MASTER)); + List 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 delta1 = Arrays.asList(ref(MASTER, 100), branchX, + sym(HEAD, MASTER)); + List delta2 = Arrays.asList(sym(HEAD, "refs/heads/branchX")); + List 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 delta1 = Arrays.asList(sym(HEAD, "refs/heads/tmp"), + sym("refs/heads/tmp", MASTER), ref(MASTER, 100)); + List delta2 = Arrays.asList(ref(MASTER, 200)); + List 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) { List stack = new ArrayList<>(table.length); for (byte[] b : table) { @@ -360,6 +432,14 @@ public class MergedReftableTest { 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) { return new ObjectIdRef.Unpeeled(NEW, name, null); } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableCompactorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableCompactorTest.java index 46a37ff603..20228283ba 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableCompactorTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableCompactorTest.java @@ -108,6 +108,7 @@ public class ReftableCompactorTest { assertTrue(rc.next()); assertEquals(MASTER, rc.getRef().getName()); assertEquals(id(1), rc.getRef().getObjectId()); + assertEquals(0, rc.getRef().getUpdateIndex()); assertEquals(0, rc.getUpdateIndex()); } } @@ -155,6 +156,7 @@ public class ReftableCompactorTest { assertTrue(rc.next()); assertEquals(MASTER, rc.getRef().getName()); assertEquals(id(2), rc.getRef().getObjectId()); + assertEquals(1, rc.getRef().getUpdateIndex()); assertEquals(1, rc.getUpdateIndex()); } } @@ -203,11 +205,13 @@ public class ReftableCompactorTest { assertTrue(rc.next()); assertEquals(MASTER, rc.getRef().getName()); assertEquals(id(3), rc.getRef().getObjectId()); + assertEquals(1, rc.getRef().getUpdateIndex()); assertEquals(1, rc.getUpdateIndex()); assertTrue(rc.next()); assertEquals(NEXT, rc.getRef().getName()); assertEquals(id(2), rc.getRef().getObjectId()); + assertEquals(0, rc.getRef().getUpdateIndex()); assertEquals(0, rc.getUpdateIndex()); } } 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 0ee785c60c..d72d6d424a 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 @@ -186,6 +186,7 @@ public class ReftableTest { assertFalse(act.isSymbolic()); assertEquals(exp.getName(), act.getName()); assertEquals(exp.getObjectId(), act.getObjectId()); + assertEquals(0, act.getUpdateIndex()); assertNull(act.getPeeledObjectId()); assertFalse(rc.wasDeleted()); assertFalse(rc.next()); @@ -195,6 +196,7 @@ public class ReftableTest { Ref act = rc.getRef(); assertNotNull(act); assertEquals(exp.getName(), act.getName()); + assertEquals(0, act.getUpdateIndex()); assertFalse(rc.next()); } } @@ -216,6 +218,7 @@ public class ReftableTest { assertEquals(exp.getName(), act.getName()); assertEquals(exp.getObjectId(), act.getObjectId()); assertEquals(exp.getPeeledObjectId(), act.getPeeledObjectId()); + assertEquals(0, act.getUpdateIndex()); } } @@ -237,6 +240,7 @@ public class ReftableTest { assertNotNull(act.getLeaf()); assertEquals(MASTER, act.getTarget().getName()); assertNull(act.getObjectId()); + assertEquals(0, act.getUpdateIndex()); } } @@ -250,14 +254,17 @@ public class ReftableTest { Ref head = t.exactRef(HEAD); assertNull(head.getObjectId()); assertEquals("refs/heads/tmp", head.getTarget().getName()); + assertEquals(0, head.getUpdateIndex()); head = t.resolve(head); assertNotNull(head); assertEquals(id(1), head.getObjectId()); + assertEquals(0, head.getUpdateIndex()); Ref master = t.exactRef(MASTER); assertNotNull(master); assertSame(master, t.resolve(master)); + assertEquals(0, master.getUpdateIndex()); } @Test @@ -335,14 +342,17 @@ public class ReftableTest { try (RefCursor rc = t.seekRefsWithPrefix("refs/tags/")) { assertTrue(rc.next()); assertEquals(V1_0, rc.getRef().getName()); + assertEquals(0, rc.getRef().getUpdateIndex()); assertFalse(rc.next()); } try (RefCursor rc = t.seekRefsWithPrefix("refs/heads/")) { assertTrue(rc.next()); assertEquals(MASTER, rc.getRef().getName()); + assertEquals(0, rc.getRef().getUpdateIndex()); assertTrue(rc.next()); assertEquals(NEXT, rc.getRef().getName()); + assertEquals(0, rc.getRef().getUpdateIndex()); assertFalse(rc.next()); } @@ -432,11 +442,13 @@ public class ReftableTest { assertTrue(rc.next()); assertEquals(MASTER, rc.getRef().getName()); assertEquals(id(1), rc.getRef().getObjectId()); + assertEquals(1, rc.getRef().getUpdateIndex()); assertEquals(1, rc.getUpdateIndex()); assertTrue(rc.next()); assertEquals(NEXT, rc.getRef().getName()); assertEquals(id(2), rc.getRef().getObjectId()); + assertEquals(1, rc.getRef().getUpdateIndex()); assertFalse(rc.next()); } try (LogCursor lc = t.allLogs()) { @@ -569,6 +581,7 @@ public class ReftableTest { assertTrue("has 42", rc.next()); assertEquals("refs/heads/42", rc.getRef().getName()); assertEquals(id(42), rc.getRef().getObjectId()); + assertEquals(0, rc.getRef().getUpdateIndex()); assertFalse(rc.next()); } try (RefCursor rc = t.byObjectId(id(100))) { @@ -579,6 +592,7 @@ public class ReftableTest { assertTrue("has master", rc.next()); assertEquals("refs/heads/master", rc.getRef().getName()); assertEquals(id(100), rc.getRef().getObjectId()); + assertEquals(0, rc.getRef().getUpdateIndex()); assertFalse(rc.next()); } @@ -600,6 +614,7 @@ public class ReftableTest { assertTrue("has 42", rc.next()); assertEquals("refs/heads/42", rc.getRef().getName()); assertEquals(id(42), rc.getRef().getObjectId()); + assertEquals(0, rc.getRef().getUpdateIndex()); assertFalse(rc.next()); } try (RefCursor rc = t.byObjectId(id(100))) { @@ -610,6 +625,7 @@ public class ReftableTest { assertTrue("has master", rc.next()); assertEquals("refs/heads/master", rc.getRef().getName()); assertEquals(id(100), rc.getRef().getObjectId()); + assertEquals(0, rc.getRef().getUpdateIndex()); assertFalse(rc.next()); } @@ -654,7 +670,6 @@ public class ReftableTest { } } - private static void assertScan(List refs, Reftable t) throws IOException { try (RefCursor rc = t.allRefs()) { @@ -663,6 +678,7 @@ public class ReftableTest { Ref act = rc.getRef(); assertEquals(exp.getName(), act.getName()); assertEquals(exp.getObjectId(), act.getObjectId()); + assertEquals(0, rc.getRef().getUpdateIndex()); } assertFalse(rc.next()); } @@ -676,6 +692,7 @@ public class ReftableTest { Ref act = rc.getRef(); assertEquals(exp.getName(), act.getName()); assertEquals(exp.getObjectId(), act.getObjectId()); + assertEquals(0, rc.getRef().getUpdateIndex()); assertFalse(rc.next()); } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdRefTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdRefTest.java index fb16c6baca..6553bfa830 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectIdRefTest.java +++ b/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.assertSame; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.Arrays; +import java.util.List; import org.junit.Test; @@ -114,11 +118,44 @@ public class ObjectIdRefTest { 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 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 public void testToString() { ObjectIdRef r; 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()); } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SymbolicRefTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SymbolicRefTest.java index 1342253c05..99b2211e69 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SymbolicRefTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/SymbolicRefTest.java @@ -68,7 +68,7 @@ public class SymbolicRefTest { SymbolicRef r; 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(name, r.getName()); assertNull("no id on new ref", r.getObjectId()); @@ -77,9 +77,10 @@ public class SymbolicRefTest { assertSame("leaf is t", t, r.getLeaf()); assertSame("target is t", t, r.getTarget()); assertTrue("is symbolic", r.isSymbolic()); + assertTrue("holds update index", r.getUpdateIndex() == 1); 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(name, r.getName()); assertSame(ID_A, r.getObjectId()); @@ -88,6 +89,7 @@ public class SymbolicRefTest { assertSame("leaf is t", t, r.getLeaf()); assertSame("target is t", t, r.getTarget()); assertTrue("is symbolic", r.isSymbolic()); + assertTrue("holds update index", r.getUpdateIndex() == 2); } @Test @@ -133,6 +135,6 @@ public class SymbolicRefTest { d = new SymbolicRef("D", c); assertEquals("SymbolicRef[D -> C -> B -> " + targetName + "=" - + ID_A.name() + "]", d.toString()); + + ID_A.name() + "(-1)]", d.toString()); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableDatabase.java index 70816307f5..0e0a6ef5e4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableDatabase.java @@ -97,6 +97,12 @@ public class DfsReftableDatabase extends DfsRefDatabase { super(repo); } + /** {@inheritDoc} */ + @Override + public boolean hasVersioning() { + return true; + } + /** {@inheritDoc} */ @Override public boolean performsAtomicTransactions() { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockReader.java index ce2ba4a2e1..44529bfff2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockReader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockReader.java @@ -170,24 +170,27 @@ class BlockReader { 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); switch (valueType & VALUE_TYPE_MASK) { case VALUE_NONE: // delete - return newRef(name); + return newRef(name, updateIndex); case VALUE_1ID: - return new ObjectIdRef.PeeledNonTag(PACKED, name, readValueId()); + return new ObjectIdRef.PeeledNonTag(PACKED, name, readValueId(), + updateIndex); case VALUE_2ID: { // annotated tag ObjectId id1 = readValueId(); ObjectId id2 = readValueId(); - return new ObjectIdRef.PeeledTag(PACKED, name, id1, id2); + return new ObjectIdRef.PeeledTag(PACKED, name, id1, id2, + updateIndex); } case VALUE_SYMREF: { String val = readValueString(); - return new SymbolicRef(name, newRef(val)); + return new SymbolicRef(name, newRef(val, updateIndex), updateIndex); } default: @@ -410,7 +413,7 @@ class BlockReader { *
    *
  • {@link #name()} *
  • {@link #match(byte[], boolean)} - *
  • {@link #readRef()} + *
  • {@link #readRef(long)} *
  • {@link #readLogUpdateIndex()} *
  • {@link #readLogEntry()} *
  • {@link #readBlockPositionList()} @@ -575,8 +578,8 @@ class BlockReader { return val; } - private static Ref newRef(String name) { - return new ObjectIdRef.Unpeeled(NEW, name, null); + private static Ref newRef(String name, long updateIndex) { + return new ObjectIdRef.Unpeeled(NEW, name, null, updateIndex); } private static IOException invalidBlock() { 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 a1087e2023..cb02628e8d 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 @@ -280,7 +280,7 @@ public abstract class Reftable implements AutoCloseable { if (dst == null) { return null; // claim it doesn't exist } - return new SymbolicRef(ref.getName(), dst); + return new SymbolicRef(ref.getName(), dst, ref.getUpdateIndex()); } /** {@inheritDoc} */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReader.java index 81b30e4cb9..8fde7e3b2e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReader.java +++ b/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 Ref ref; - private long updateIndex; BlockReader block; RefCursorImpl(long scanEnd, byte[] match, boolean prefix) { @@ -508,8 +507,7 @@ public class ReftableReader extends Reftable { return false; } - updateIndex = minUpdateIndex + block.readUpdateIndexDelta(); - ref = block.readRef(); + ref = block.readRef(minUpdateIndex); if (!includeDeletes && wasDeleted()) { continue; } @@ -524,7 +522,7 @@ public class ReftableReader extends Reftable { @Override public long getUpdateIndex() { - return updateIndex; + return ref.getUpdateIndex(); } @Override @@ -605,7 +603,6 @@ public class ReftableReader extends Reftable { private final ObjectId match; private Ref ref; - private long updateIndex; private int listIdx; private LongList blockPos; @@ -679,8 +676,7 @@ public class ReftableReader extends Reftable { } block.parseKey(); - updateIndex = minUpdateIndex + block.readUpdateIndexDelta(); - ref = block.readRef(); + ref = block.readRef(minUpdateIndex); ObjectId id = ref.getObjectId(); if (id != null && match.equals(id) && (includeDeletes || !wasDeleted())) { @@ -696,7 +692,7 @@ public class ReftableReader extends Reftable { @Override public long getUpdateIndex() { - return updateIndex; + return ref.getUpdateIndex(); } @Override diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java index 22aaa3ad73..747318170a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java +++ b/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, @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 @@ -100,7 +118,28 @@ public abstract class ObjectIdRef implements Ref { */ public PeeledTag(@NonNull Storage st, @NonNull String name, @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; } @@ -131,7 +170,25 @@ public abstract class ObjectIdRef implements Ref { */ public PeeledNonTag(@NonNull Storage st, @NonNull String name, @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 @@ -152,6 +209,8 @@ public abstract class ObjectIdRef implements Ref { private final ObjectId objectId; + private final long updateIndex; + /** * Create a new ref pairing. * @@ -162,12 +221,16 @@ public abstract class ObjectIdRef implements Ref { * @param id * current value of the ref. May be {@code null} to indicate a * 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, - @Nullable ObjectId id) { + @Nullable ObjectId id, long updateIndex) { this.name = name; this.storage = st; this.objectId = id; + this.updateIndex = updateIndex; } /** {@inheritDoc} */ @@ -211,6 +274,15 @@ public abstract class ObjectIdRef implements Ref { return storage; } + /** {@inheritDoc} */ + @Override + public long getUpdateIndex() { + if (updateIndex == -1) { + throw new UnsupportedOperationException(); + } + return updateIndex; + } + /** {@inheritDoc} */ @NonNull @Override @@ -220,7 +292,9 @@ public abstract class ObjectIdRef implements Ref { r.append(getName()); r.append('='); 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(); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java index faabbf892f..32c8b06c91 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java @@ -217,4 +217,27 @@ public interface Ref { */ @NonNull Storage getStorage(); + + /** + * Indicator of the relative order between updates of a specific reference + * name. A number that increases when a reference is updated. + *

    + * 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. + *

    + * 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(); + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java index 68929b4220..5010a89ed5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java @@ -110,6 +110,19 @@ public abstract class RefDatabase { */ 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. *

    diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java index d4b83b0128..0e9a23fe15 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java +++ b/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 long updateIndex; + /** * Create a new ref pairing. * @@ -69,6 +71,24 @@ public class SymbolicRef implements Ref { public SymbolicRef(@NonNull String refName, @NonNull Ref target) { this.name = refName; 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} */ @@ -128,6 +148,15 @@ public class SymbolicRef implements Ref { return getLeaf().isPeeled(); } + /** {@inheritDoc} */ + @Override + public long getUpdateIndex() { + if (updateIndex == -1) { + throw new UnsupportedOperationException(); + } + return updateIndex; + } + /** {@inheritDoc} */ @SuppressWarnings("nls") @Override @@ -143,7 +172,9 @@ public class SymbolicRef implements Ref { r.append(cur.getName()); r.append('='); r.append(ObjectId.toString(cur.getObjectId())); - r.append("]"); + r.append("("); + r.append(updateIndex); // Print value, even if -1 + r.append(")]"); return r.toString(); } } -- 2.39.5