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>
This commit is contained in:
Ivan Frade 2018-12-19 16:43:56 -08:00
parent d09388e156
commit 6ea888a036
14 changed files with 342 additions and 28 deletions

View File

@ -61,6 +61,7 @@
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 void testCreate() throws IOException {
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
public void testGetRefs_EmptyDatabase() throws IOException {
Map<String, Ref> all;

View File

@ -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.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 void oneTableScan() throws IOException {
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 void deleteIsHidden() throws IOException {
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 void twoTableSeek() throws IOException {
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 void twoTableById() throws IOException {
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 void fourTableScan() throws IOException {
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 void scanDuplicates() throws IOException {
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 void scanIncludeDeletes() throws IOException {
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 void oneTableSeek() throws IOException {
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 void missedUpdate() throws IOException {
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 void compaction() throws IOException {
}
}
@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) {
List<Reftable> stack = new ArrayList<>(table.length);
for (byte[] b : table) {
@ -360,6 +432,14 @@ private static Ref ref(String name, int 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) {
return new ObjectIdRef.Unpeeled(NEW, name, null);
}

View File

@ -108,6 +108,7 @@ public void oneTable() throws IOException {
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 void twoTablesOneRef() throws IOException {
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 void twoTablesTwoRefs() throws IOException {
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());
}
}

View File

@ -186,6 +186,7 @@ public void oneIdRef() throws IOException {
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 void oneIdRef() throws IOException {
Ref act = rc.getRef();
assertNotNull(act);
assertEquals(exp.getName(), act.getName());
assertEquals(0, act.getUpdateIndex());
assertFalse(rc.next());
}
}
@ -216,6 +218,7 @@ public void oneTagRef() throws IOException {
assertEquals(exp.getName(), act.getName());
assertEquals(exp.getObjectId(), act.getObjectId());
assertEquals(exp.getPeeledObjectId(), act.getPeeledObjectId());
assertEquals(0, act.getUpdateIndex());
}
}
@ -237,6 +240,7 @@ public void oneSymbolicRef() throws IOException {
assertNotNull(act.getLeaf());
assertEquals(MASTER, act.getTarget().getName());
assertNull(act.getObjectId());
assertEquals(0, act.getUpdateIndex());
}
}
@ -250,14 +254,17 @@ public void resolveSymbolicRef() throws IOException {
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 void namespaceHeads() throws IOException {
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 void withReflog() throws IOException {
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 void byObjectIdOneRefNoIndex() throws IOException {
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 void byObjectIdOneRefNoIndex() throws IOException {
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 void byObjectIdOneRefWithIndex() throws IOException {
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 void byObjectIdOneRefWithIndex() throws IOException {
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 void badCrc32() throws IOException {
}
}
private static void assertScan(List<Ref> refs, Reftable t)
throws IOException {
try (RefCursor rc = t.allRefs()) {
@ -663,6 +678,7 @@ private static void assertScan(List<Ref> refs, Reftable t)
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 @@ private static void assertSeek(List<Ref> refs, Reftable t)
Ref act = rc.getRef();
assertEquals(exp.getName(), act.getName());
assertEquals(exp.getObjectId(), act.getObjectId());
assertEquals(0, rc.getRef().getUpdateIndex());
assertFalse(rc.next());
}
}

View File

@ -48,6 +48,10 @@
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 void testConstructor_Peeled() {
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
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());
}
}

View File

@ -68,7 +68,7 @@ public void testConstructor() {
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 void testConstructor() {
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 void testConstructor() {
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 void testToString() {
d = new SymbolicRef("D", c);
assertEquals("SymbolicRef[D -> C -> B -> " + targetName + "="
+ ID_A.name() + "]", d.toString());
+ ID_A.name() + "(-1)]", d.toString());
}
}

View File

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

View File

@ -170,24 +170,27 @@ long readUpdateIndexDelta() {
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 @@ void verifyIndex() throws IOException {
* <ul>
* <li>{@link #name()}
* <li>{@link #match(byte[], boolean)}
* <li>{@link #readRef()}
* <li>{@link #readRef(long)}
* <li>{@link #readLogUpdateIndex()}
* <li>{@link #readLogEntry()}
* <li>{@link #readBlockPositionList()}
@ -575,8 +578,8 @@ private long readVarint64() {
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() {

View File

@ -280,7 +280,7 @@ private Ref resolve(Ref ref, int depth) throws IOException {
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} */

View File

@ -479,7 +479,6 @@ private class RefCursorImpl extends RefCursor {
private final boolean prefix;
private Ref ref;
private long updateIndex;
BlockReader block;
RefCursorImpl(long scanEnd, byte[] match, boolean prefix) {
@ -508,8 +507,7 @@ public boolean next() throws IOException {
return false;
}
updateIndex = minUpdateIndex + block.readUpdateIndexDelta();
ref = block.readRef();
ref = block.readRef(minUpdateIndex);
if (!includeDeletes && wasDeleted()) {
continue;
}
@ -524,7 +522,7 @@ public Ref getRef() {
@Override
public long getUpdateIndex() {
return updateIndex;
return ref.getUpdateIndex();
}
@Override
@ -605,7 +603,6 @@ private class ObjCursorImpl extends RefCursor {
private final ObjectId match;
private Ref ref;
private long updateIndex;
private int listIdx;
private LongList blockPos;
@ -679,8 +676,7 @@ public boolean next() throws IOException {
}
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 Ref getRef() {
@Override
public long getUpdateIndex() {
return updateIndex;
return ref.getUpdateIndex();
}
@Override

View File

@ -67,7 +67,25 @@ public static class Unpeeled extends ObjectIdRef {
*/
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 static class PeeledTag extends ObjectIdRef {
*/
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 static class PeeledNonTag extends ObjectIdRef {
*/
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 boolean isPeeled() {
private final ObjectId objectId;
private final long updateIndex;
/**
* Create a new ref pairing.
*
@ -162,12 +221,16 @@ public boolean isPeeled() {
* @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 Storage getStorage() {
return storage;
}
/** {@inheritDoc} */
@Override
public long getUpdateIndex() {
if (updateIndex == -1) {
throw new UnsupportedOperationException();
}
return updateIndex;
}
/** {@inheritDoc} */
@NonNull
@Override
@ -220,7 +292,9 @@ public String toString() {
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();
}
}

View File

@ -217,4 +217,27 @@ public boolean isPacked() {
*/
@NonNull
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();
}
}

View File

@ -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.
* <p>

View File

@ -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 boolean isPeeled() {
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 String toString() {
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();
}
}