Seek references by prefix in reftable

Reftable implementation of RefDatabase.getRefsByPrefix() should be
more performant, as references are filtered directly by prefix;
instead of fetching the whole subtree then filter by prefix.

Change-Id: If4f5f8c08285ea1eaec9efb83c3d864cea7a1321
Signed-off-by: Minh Thai <mthai@google.com>
This commit is contained in:
Minh Thai 2018-07-10 12:41:23 -07:00 committed by Jonathan Nieder
parent 04b9f44367
commit e04d96e3fa
8 changed files with 81 additions and 28 deletions

View File

@ -70,7 +70,7 @@ protected void run() throws Exception {
BlockSource src = BlockSource.from(in);
ReftableReader reader = new ReftableReader(src)) {
try (RefCursor rc = ref != null
? reader.seekRef(ref)
? reader.seekPrefix(ref)
: reader.allRefs()) {
while (rc.next()) {
write(rc.getRef());

View File

@ -80,7 +80,7 @@ public void noTables() throws IOException {
try (RefCursor rc = mr.seekRef(HEAD)) {
assertFalse(rc.next());
}
try (RefCursor rc = mr.seekRef(R_HEADS)) {
try (RefCursor rc = mr.seekPrefix(R_HEADS)) {
assertFalse(rc.next());
}
}
@ -94,7 +94,7 @@ public void oneEmptyTable() throws IOException {
try (RefCursor rc = mr.seekRef(HEAD)) {
assertFalse(rc.next());
}
try (RefCursor rc = mr.seekRef(R_HEADS)) {
try (RefCursor rc = mr.seekPrefix(R_HEADS)) {
assertFalse(rc.next());
}
}
@ -108,7 +108,7 @@ public void twoEmptyTables() throws IOException {
try (RefCursor rc = mr.seekRef(HEAD)) {
assertFalse(rc.next());
}
try (RefCursor rc = mr.seekRef(R_HEADS)) {
try (RefCursor rc = mr.seekPrefix(R_HEADS)) {
assertFalse(rc.next());
}
}

View File

@ -101,7 +101,7 @@ public void emptyTable() throws IOException {
try (RefCursor rc = t.seekRef(HEAD)) {
assertFalse(rc.next());
}
try (RefCursor rc = t.seekRef(R_HEADS)) {
try (RefCursor rc = t.seekPrefix(R_HEADS)) {
assertFalse(rc.next());
}
try (LogCursor rc = t.allLogs()) {
@ -317,10 +317,10 @@ public void seekNotFound() throws IOException {
public void namespaceNotFound() throws IOException {
Ref exp = ref(MASTER, 1);
ReftableReader t = read(write(exp));
try (RefCursor rc = t.seekRef("refs/changes/")) {
try (RefCursor rc = t.seekPrefix("refs/changes/")) {
assertFalse(rc.next());
}
try (RefCursor rc = t.seekRef("refs/tags/")) {
try (RefCursor rc = t.seekPrefix("refs/tags/")) {
assertFalse(rc.next());
}
}
@ -332,12 +332,12 @@ public void namespaceHeads() throws IOException {
Ref v1 = tag(V1_0, 3, 4);
ReftableReader t = read(write(master, next, v1));
try (RefCursor rc = t.seekRef("refs/tags/")) {
try (RefCursor rc = t.seekPrefix("refs/tags/")) {
assertTrue(rc.next());
assertEquals(V1_0, rc.getRef().getName());
assertFalse(rc.next());
}
try (RefCursor rc = t.seekRef("refs/heads/")) {
try (RefCursor rc = t.seekPrefix("refs/heads/")) {
assertTrue(rc.next());
assertEquals(MASTER, rc.getRef().getName());
@ -484,7 +484,7 @@ public void onlyReflog() throws IOException {
try (RefCursor rc = t.allRefs()) {
assertFalse(rc.next());
}
try (RefCursor rc = t.seekRef("refs/heads/")) {
try (RefCursor rc = t.seekPrefix("refs/heads/")) {
assertFalse(rc.next());
}
try (LogCursor lc = t.allLogs()) {

View File

@ -45,6 +45,9 @@
import java.io.IOException;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
@ -238,7 +241,8 @@ public Map<String, Ref> getRefs(String prefix) throws IOException {
try {
Reftable table = reader();
try (RefCursor rc = ALL.equals(prefix) ? table.allRefs()
: table.seekRef(prefix)) {
: (prefix.endsWith("/") ? table.seekPrefix(prefix) //$NON-NLS-1$
: table.seekRef(prefix))) {
while (rc.next()) {
Ref ref = table.resolve(rc.getRef());
if (ref != null && ref.getObjectId() != null) {
@ -254,6 +258,29 @@ public Map<String, Ref> getRefs(String prefix) throws IOException {
return new RefMap(prefix, all.toRefList(), none, none);
}
/** {@inheritDoc} */
@Override
public List<Ref> getRefsByPrefix(String prefix) throws IOException {
List<Ref> all = new ArrayList<>();
lock.lock();
try {
Reftable table = reader();
try (RefCursor rc = ALL.equals(prefix) ? table.allRefs()
: table.seekPrefix(prefix)) {
while (rc.next()) {
Ref ref = table.resolve(rc.getRef());
if (ref != null && ref.getObjectId() != null) {
all.add(ref);
}
}
}
} finally {
lock.unlock();
}
return Collections.unmodifiableList(all);
}
/** {@inheritDoc} */
@Override
public Ref peel(Ref ref) throws IOException {

View File

@ -70,7 +70,6 @@
import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
import org.eclipse.jgit.internal.storage.io.BlockSource;
import org.eclipse.jgit.internal.storage.pack.PackExt;
import org.eclipse.jgit.internal.storage.reftable.RefCursor;
import org.eclipse.jgit.internal.storage.reftable.Reftable;
import org.eclipse.jgit.internal.storage.reftable.ReftableCompactor;
import org.eclipse.jgit.internal.storage.reftable.ReftableConfig;
@ -240,11 +239,7 @@ private boolean checkConflicting(List<ReceiveCommand> pending)
private boolean checkExpected(Reftable table, List<ReceiveCommand> pending)
throws IOException {
for (ReceiveCommand cmd : pending) {
Ref ref;
try (RefCursor rc = table.seekRef(cmd.getRefName())) {
ref = rc.next() ? rc.getRef() : null;
}
if (!matchOld(cmd, ref)) {
if (!matchOld(cmd, table.exactRef(cmd.getRefName()))) {
cmd.setResult(LOCK_FAILURE);
if (isAtomic()) {
ReceiveCommand.abort(pending);

View File

@ -111,6 +111,16 @@ public RefCursor seekRef(String name) throws IOException {
return m;
}
/** {@inheritDoc} */
@Override
public RefCursor seekPrefix(String prefix) throws IOException {
MergedRefCursor m = new MergedRefCursor();
for (int i = 0; i < tables.length; i++) {
m.add(new RefQueueEntry(tables[i].seekPrefix(prefix), i));
}
return m;
}
/** {@inheritDoc} */
@Override
public RefCursor byObjectId(AnyObjectId name) throws IOException {

View File

@ -108,24 +108,35 @@ public void setIncludeDeletes(boolean deletes) {
public abstract RefCursor allRefs() throws IOException;
/**
* Seek either to a reference, or a reference subtree.
* Seek to a reference.
* <p>
* If {@code refName} ends with {@code "/"} the method will seek to the
* subtree of all references starting with {@code refName} as a prefix. If
* no references start with this prefix, an empty cursor is returned.
* <p>
* Otherwise exactly {@code refName} will be looked for. If present, the
* This method will seek to the reference {@code refName}. If present, the
* returned cursor will iterate exactly one entry. If not found, an empty
* cursor is returned.
*
* @param refName
* reference name or subtree to find.
* reference name.
* @return cursor to iterate; empty cursor if no references match.
* @throws java.io.IOException
* if references cannot be read.
*/
public abstract RefCursor seekRef(String refName) throws IOException;
/**
* Seek references with prefix.
* <p>
* The method will seek all the references starting with {@code prefix} as a
* prefix. If no references start with this prefix, an empty cursor is
* returned.
*
* @param prefix
* prefix to find.
* @return cursor to iterate; empty cursor if no references match.
* @throws java.io.IOException
* if references cannot be read.
*/
public abstract RefCursor seekPrefix(String prefix) throws IOException;
/**
* Match references pointing to a specific object.
*
@ -206,8 +217,9 @@ public Ref exactRef(String refName) throws IOException {
* if references cannot be read.
*/
public boolean hasRef(String refName) throws IOException {
try (RefCursor rc = seekRef(refName)) {
return rc.next();
try (RefCursor rc = seekPrefix(refName)) {
return rc.next() && (refName.endsWith("/") //$NON-NLS-1$
|| refName.equals(rc.getRef().getName()));
}
}

View File

@ -183,9 +183,18 @@ public RefCursor seekRef(String refName) throws IOException {
initRefIndex();
byte[] key = refName.getBytes(CHARSET);
boolean prefix = key[key.length - 1] == '/';
RefCursorImpl i = new RefCursorImpl(refEnd, key, false);
i.block = seek(REF_BLOCK_TYPE, key, refIndex, 0, refEnd);
return i;
}
RefCursorImpl i = new RefCursorImpl(refEnd, key, prefix);
/** {@inheritDoc} */
@Override
public RefCursor seekPrefix(String prefix) throws IOException {
initRefIndex();
byte[] key = prefix.getBytes(CHARSET);
RefCursorImpl i = new RefCursorImpl(refEnd, key, true);
i.block = seek(REF_BLOCK_TYPE, key, refIndex, 0, refEnd);
return i;
}