summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJonathan Tan <jonathantanmy@google.com>2021-01-27 13:36:43 -0500
committerGerrit Code Review @ Eclipse.org <gerrit@eclipse.org>2021-01-27 13:36:43 -0500
commitc29ec3447d3339e57a46e02423c44ba3638a197e (patch)
tree3588167bbd6b5f57400f9ffc2ad03a12a10e11b4
parentb79882586de256060076324cd988f3a1cbd92b38 (diff)
parent31e3cb4375f92e56f27b83c4583523c14a712b2d (diff)
downloadjgit-c29ec3447d3339e57a46e02423c44ba3638a197e.tar.gz
jgit-c29ec3447d3339e57a46e02423c44ba3638a197e.zip
Merge changes I36d9b63e,I8c5db581,I2c02e89c
* changes: Compare getting all refs except specific refs with seek and with filter Add getsRefsByPrefixWithSkips (excluding prefixes) to ReftableDatabase Add seekPastPrefix method to RefCursor
-rw-r--r--org.eclipse.jgit.http.test/src/org/eclipse/jgit/http/test/RefsUnreadableInMemoryRepository.java11
-rw-r--r--org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/BenchmarkReftable.java54
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileReftableTest.java72
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java20
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/MergedReftableTest.java112
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/reftable/ReftableTest.java158
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java59
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableDatabase.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableDatabase.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/MergedReftable.java18
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/RefCursor.java13
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableDatabase.java50
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReader.java26
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java28
14 files changed, 635 insertions, 1 deletions
diff --git a/org.eclipse.jgit.http.test/src/org/eclipse/jgit/http/test/RefsUnreadableInMemoryRepository.java b/org.eclipse.jgit.http.test/src/org/eclipse/jgit/http/test/RefsUnreadableInMemoryRepository.java
index 80cbe8738c..4167b038e1 100644
--- a/org.eclipse.jgit.http.test/src/org/eclipse/jgit/http/test/RefsUnreadableInMemoryRepository.java
+++ b/org.eclipse.jgit.http.test/src/org/eclipse/jgit/http/test/RefsUnreadableInMemoryRepository.java
@@ -85,6 +85,17 @@ class RefsUnreadableInMemoryRepository extends InMemoryRepository {
/** {@inheritDoc} */
@Override
+ public List<Ref> getRefsByPrefixWithExclusions(String include, Set<String> excludes)
+ throws IOException {
+ if (failing) {
+ throw new IOException("disk failed, no refs found");
+ }
+
+ return super.getRefsByPrefixWithExclusions(include, excludes);
+ }
+
+ /** {@inheritDoc} */
+ @Override
public Set<Ref> getTipsWithSha1(ObjectId id) throws IOException {
if (failing) {
throw new IOException("disk failed, no refs found");
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/BenchmarkReftable.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/BenchmarkReftable.java
index 630fac549e..f23f4cf0ea 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/BenchmarkReftable.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/BenchmarkReftable.java
@@ -23,7 +23,9 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
+import java.util.ArrayList;
import java.util.List;
+import java.util.stream.Collectors;
import org.eclipse.jgit.internal.storage.file.FileReftableStack;
import org.eclipse.jgit.internal.storage.io.BlockSource;
@@ -47,6 +49,7 @@ class BenchmarkReftable extends TextBuiltin {
SEEK_COLD, SEEK_HOT,
BY_ID_COLD, BY_ID_HOT,
WRITE_STACK,
+ GET_REFS_EXCLUDING_REF
}
@Option(name = "--tries")
@@ -91,7 +94,11 @@ class BenchmarkReftable extends TextBuiltin {
case WRITE_STACK:
writeStack();
break;
- }
+ case GET_REFS_EXCLUDING_REF :
+ getRefsExcludingWithSeekPast(ref);
+ getRefsExcludingWithFilter(ref);
+ break;
+ }
}
private void printf(String fmt, Object... args) throws IOException {
@@ -315,4 +322,49 @@ class BenchmarkReftable extends TextBuiltin {
printf("%12s %10d usec %9.1f usec/run %5d runs", "reftable",
tot / 1000, (((double) tot) / tries) / 1000, tries);
}
+
+ @SuppressWarnings({"nls", "boxing"})
+ private void getRefsExcludingWithFilter(String prefix) throws Exception {
+ long startTime = System.nanoTime();
+ List<Ref> allRefs = new ArrayList<>();
+ try (FileInputStream in = new FileInputStream(reftablePath);
+ BlockSource src = BlockSource.from(in);
+ ReftableReader reader = new ReftableReader(src)) {
+ try (RefCursor rc = reader.allRefs()) {
+ while (rc.next()) {
+ allRefs.add(rc.getRef());
+ }
+ }
+ }
+ int total = allRefs.size();
+ allRefs = allRefs.stream().filter(r -> r.getName().startsWith(prefix)).collect(Collectors.toList());
+ int notStartWithPrefix = allRefs.size();
+ int startWithPrefix = total - notStartWithPrefix;
+ long totalTime = System.nanoTime() - startTime;
+ printf("total time the action took using filter: %10d usec", totalTime / 1000);
+ printf("number of refs that start with prefix: %d", startWithPrefix);
+ printf("number of refs that don't start with prefix: %d", notStartWithPrefix);
+ }
+
+ @SuppressWarnings({"nls", "boxing"})
+ private void getRefsExcludingWithSeekPast(String prefix) throws Exception {
+ long start = System.nanoTime();
+ try (FileInputStream in = new FileInputStream(reftablePath);
+ BlockSource src = BlockSource.from(in);
+ ReftableReader reader = new ReftableReader(src)) {
+ try (RefCursor rc = reader.allRefs()) {
+ while (rc.next()) {
+ if (rc.getRef().getName().startsWith(prefix)) {
+ break;
+ }
+ }
+ rc.seekPastPrefix(prefix);
+ while (rc.next()) {
+ rc.getRef();
+ }
+ }
+ }
+ long tot = System.nanoTime() - start;
+ printf("total time the action took using seek: %10d usec", tot / 1000);
+ }
}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileReftableTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileReftableTest.java
index 33bacbe3e2..15c9109ca0 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileReftableTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/FileReftableTest.java
@@ -28,14 +28,18 @@ import java.io.File;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
import org.eclipse.jgit.lib.RefRename;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.RefUpdate.Result;
@@ -579,6 +583,64 @@ public class FileReftableTest extends SampleDataRepositoryTestCase {
assertEquals(Ref.Storage.PACKED, b.getStorage());
}
+ @Test
+ public void testGetRefsExcludingPrefix() throws IOException {
+ Set<String> prefixes = new HashSet<>();
+ prefixes.add("refs/tags");
+ // HEAD + 12 refs/heads are present here.
+ List<Ref> refs =
+ db.getRefDatabase().getRefsByPrefixWithExclusions(RefDatabase.ALL, prefixes);
+ assertEquals(13, refs.size());
+ checkContainsRef(refs, db.exactRef("HEAD"));
+ checkContainsRef(refs, db.exactRef("refs/heads/a"));
+ for (Ref notInResult : db.getRefDatabase().getRefsByPrefix("refs/tags")) {
+ assertFalse(refs.contains(notInResult));
+ }
+ }
+
+ @Test
+ public void testGetRefsExcludingPrefixes() throws IOException {
+ Set<String> exclude = new HashSet<>();
+ exclude.add("refs/tags/");
+ exclude.add("refs/heads/");
+ List<Ref> refs = db.getRefDatabase().getRefsByPrefixWithExclusions(RefDatabase.ALL, exclude);
+ assertEquals(1, refs.size());
+ checkContainsRef(refs, db.exactRef("HEAD"));
+ }
+
+ @Test
+ public void testGetRefsExcludingNonExistingPrefixes() throws IOException {
+ Set<String> exclude = new HashSet<>();
+ exclude.add("refs/tags/");
+ exclude.add("refs/heads/");
+ exclude.add("refs/nonexistent/");
+ List<Ref> refs = db.getRefDatabase().getRefsByPrefixWithExclusions(RefDatabase.ALL, exclude);
+ assertEquals(1, refs.size());
+ checkContainsRef(refs, db.exactRef("HEAD"));
+ }
+
+ @Test
+ public void testGetRefsWithPrefixExcludingPrefixes() throws IOException {
+ Set<String> exclude = new HashSet<>();
+ exclude.add("refs/heads/pa");
+ String include = "refs/heads/p";
+ List<Ref> refs = db.getRefDatabase().getRefsByPrefixWithExclusions(include, exclude);
+ assertEquals(1, refs.size());
+ checkContainsRef(refs, db.exactRef("refs/heads/prefix/a"));
+ }
+
+ @Test
+ public void testGetRefsWithPrefixExcludingOverlappingPrefixes() throws IOException {
+ Set<String> exclude = new HashSet<>();
+ exclude.add("refs/heads/pa");
+ exclude.add("refs/heads/");
+ exclude.add("refs/heads/p");
+ exclude.add("refs/tags/");
+ List<Ref> refs = db.getRefDatabase().getRefsByPrefixWithExclusions(RefDatabase.ALL, exclude);
+ assertEquals(1, refs.size());
+ checkContainsRef(refs, db.exactRef("HEAD"));
+ }
+
private RefUpdate updateRef(String name) throws IOException {
final RefUpdate ref = db.updateRef(name);
ref.setNewObjectId(db.resolve(Constants.HEAD));
@@ -596,4 +658,14 @@ public class FileReftableTest extends SampleDataRepositoryTestCase {
fail("link " + src + " to " + dst);
}
}
+
+ private static void checkContainsRef(Collection<Ref> haystack, Ref needle) {
+ for (Ref ref : haystack) {
+ if (ref.getName().equals(needle.getName()) &&
+ ref.getObjectId().equals(needle.getObjectId())) {
+ return;
+ }
+ }
+ fail("list " + haystack + " does not contain ref " + needle);
+ }
}
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 97ef5993b9..38c545ef57 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
@@ -30,8 +30,10 @@ import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
@@ -353,6 +355,24 @@ public class RefDirectoryTest extends LocalDiskRepositoryTestCase {
}
@Test
+ public void testGetRefs_ExcludingPrefixes() throws IOException {
+ writeLooseRef("refs/heads/A", A);
+ writeLooseRef("refs/heads/B", B);
+ writeLooseRef("refs/tags/tag", A);
+ writeLooseRef("refs/something/something", B);
+ writeLooseRef("refs/aaa/aaa", A);
+
+ Set<String> toExclude = new HashSet<>();
+ toExclude.add("refs/aaa/");
+ toExclude.add("refs/heads/");
+ List<Ref> refs = refdir.getRefsByPrefixWithExclusions(RefDatabase.ALL, toExclude);
+
+ assertEquals(2, refs.size());
+ assertTrue(refs.contains(refdir.exactRef("refs/tags/tag")));
+ assertTrue(refs.contains(refdir.exactRef("refs/something/something")));
+ }
+
+ @Test
public void testFirstExactRef_IgnoresGarbageRef() throws IOException {
writeLooseRef("refs/heads/A", A);
write(new File(diskRepo.getDirectory(), "refs/heads/bad"), "FAIL\n");
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 0a03fc3523..9aea3b4b25 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
@@ -138,6 +138,118 @@ public class MergedReftableTest {
}
@Test
+ public void twoTableSeekPastWithRefCursor() throws IOException {
+ List<Ref> delta1 = Arrays.asList(
+ ref("refs/heads/apple", 1),
+ ref("refs/heads/master", 2));
+ List<Ref> delta2 = Arrays.asList(
+ ref("refs/heads/banana", 3),
+ ref("refs/heads/zzlast", 4));
+
+ MergedReftable mr = merge(write(delta1), write(delta2));
+ try (RefCursor rc = mr.seekRefsWithPrefix("")) {
+ assertTrue(rc.next());
+ assertEquals("refs/heads/apple", rc.getRef().getName());
+ assertEquals(id(1), rc.getRef().getObjectId());
+
+ rc.seekPastPrefix("refs/heads/banana/");
+
+ assertTrue(rc.next());
+ assertEquals("refs/heads/master", rc.getRef().getName());
+ assertEquals(id(2), rc.getRef().getObjectId());
+
+ assertTrue(rc.next());
+ assertEquals("refs/heads/zzlast", rc.getRef().getName());
+ assertEquals(id(4), rc.getRef().getObjectId());
+
+ assertEquals(1, rc.getRef().getUpdateIndex());
+ }
+ }
+
+ @Test
+ public void oneTableSeekPastWithRefCursor() throws IOException {
+ List<Ref> delta1 = Arrays.asList(
+ ref("refs/heads/apple", 1),
+ ref("refs/heads/master", 2));
+
+ MergedReftable mr = merge(write(delta1));
+ try (RefCursor rc = mr.seekRefsWithPrefix("")) {
+ rc.seekPastPrefix("refs/heads/apple");
+
+ assertTrue(rc.next());
+ assertEquals("refs/heads/master", rc.getRef().getName());
+ assertEquals(id(2), rc.getRef().getObjectId());
+
+ assertEquals(1, rc.getRef().getUpdateIndex());
+ }
+ }
+
+ @Test
+ public void seekPastToNonExistentPrefixToTheMiddle() throws IOException {
+ List<Ref> delta1 = Arrays.asList(
+ ref("refs/heads/apple", 1),
+ ref("refs/heads/master", 2));
+ List<Ref> delta2 = Arrays.asList(
+ ref("refs/heads/banana", 3),
+ ref("refs/heads/zzlast", 4));
+
+ MergedReftable mr = merge(write(delta1), write(delta2));
+ try (RefCursor rc = mr.seekRefsWithPrefix("")) {
+ rc.seekPastPrefix("refs/heads/x");
+
+ assertTrue(rc.next());
+ assertEquals("refs/heads/zzlast", rc.getRef().getName());
+ assertEquals(id(4), rc.getRef().getObjectId());
+
+ assertEquals(1, rc.getRef().getUpdateIndex());
+ }
+ }
+
+ @Test
+ public void seekPastToNonExistentPrefixToTheEnd() throws IOException {
+ List<Ref> delta1 = Arrays.asList(
+ ref("refs/heads/apple", 1),
+ ref("refs/heads/master", 2));
+ List<Ref> delta2 = Arrays.asList(
+ ref("refs/heads/banana", 3),
+ ref("refs/heads/zzlast", 4));
+
+ MergedReftable mr = merge(write(delta1), write(delta2));
+ try (RefCursor rc = mr.seekRefsWithPrefix("")) {
+ rc.seekPastPrefix("refs/heads/zzz");
+ assertFalse(rc.next());
+ }
+ }
+
+ @Test
+ public void seekPastManyTimes() throws IOException {
+ List<Ref> delta1 = Arrays.asList(
+ ref("refs/heads/apple", 1),
+ ref("refs/heads/master", 2));
+ List<Ref> delta2 = Arrays.asList(
+ ref("refs/heads/banana", 3),
+ ref("refs/heads/zzlast", 4));
+
+ MergedReftable mr = merge(write(delta1), write(delta2));
+ try (RefCursor rc = mr.seekRefsWithPrefix("")) {
+ rc.seekPastPrefix("refs/heads/apple");
+ rc.seekPastPrefix("refs/heads/banana");
+ rc.seekPastPrefix("refs/heads/master");
+ rc.seekPastPrefix("refs/heads/zzlast");
+ assertFalse(rc.next());
+ }
+ }
+
+ @Test
+ public void seekPastOnEmptyTable() throws IOException {
+ MergedReftable mr = merge(write(), write());
+ try (RefCursor rc = mr.seekRefsWithPrefix("")) {
+ rc.seekPastPrefix("refs/");
+ assertFalse(rc.next());
+ }
+ }
+
+ @Test
public void twoTableById() throws IOException {
List<Ref> delta1 = Arrays.asList(
ref("refs/heads/apple", 1),
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 009914b35c..56f881ec53 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
@@ -10,6 +10,7 @@
package org.eclipse.jgit.internal.storage.reftable;
+import static java.nio.charset.StandardCharsets.UTF_8;
import static org.eclipse.jgit.lib.Constants.HEAD;
import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
import static org.eclipse.jgit.lib.Constants.R_HEADS;
@@ -49,8 +50,16 @@ import org.hamcrest.Matchers;
import org.junit.Test;
public class ReftableTest {
+ private static final byte[] LAST_UTF8_CHAR = new byte[] {
+ (byte)0x10,
+ (byte)0xFF,
+ (byte)0xFF};
+
private static final String MASTER = "refs/heads/master";
private static final String NEXT = "refs/heads/next";
+ private static final String AFTER_NEXT = "refs/heads/nextnext";
+ private static final String LAST = "refs/heads/nextnextnext";
+ private static final String NOT_REF_HEADS = "refs/zzz/zzz";
private static final String V1_0 = "refs/tags/v1.0";
private Stats stats;
@@ -396,6 +405,135 @@ public class ReftableTest {
}
@Test
+ public void seekPastRefWithRefCursor() throws IOException {
+ Ref exp = ref(MASTER, 1);
+ Ref next = ref(NEXT, 2);
+ Ref afterNext = ref(AFTER_NEXT, 3);
+ Ref afterNextNext = ref(LAST, 4);
+ ReftableReader t = read(write(exp, next, afterNext, afterNextNext));
+ try (RefCursor rc = t.seekRefsWithPrefix("")) {
+ assertTrue(rc.next());
+ assertEquals(MASTER, rc.getRef().getName());
+
+ rc.seekPastPrefix("refs/heads/next/");
+
+ assertTrue(rc.next());
+ assertEquals(AFTER_NEXT, rc.getRef().getName());
+ assertTrue(rc.next());
+ assertEquals(LAST, rc.getRef().getName());
+
+ assertFalse(rc.next());
+ }
+ }
+
+ @Test
+ public void seekPastToNonExistentPrefixToTheMiddle() throws IOException {
+ Ref exp = ref(MASTER, 1);
+ Ref next = ref(NEXT, 2);
+ Ref afterNext = ref(AFTER_NEXT, 3);
+ Ref afterNextNext = ref(LAST, 4);
+ ReftableReader t = read(write(exp, next, afterNext, afterNextNext));
+ try (RefCursor rc = t.seekRefsWithPrefix("")) {
+ rc.seekPastPrefix("refs/heads/master_non_existent");
+
+ assertTrue(rc.next());
+ assertEquals(NEXT, rc.getRef().getName());
+
+ assertTrue(rc.next());
+ assertEquals(AFTER_NEXT, rc.getRef().getName());
+
+ assertTrue(rc.next());
+ assertEquals(LAST, rc.getRef().getName());
+
+ assertFalse(rc.next());
+ }
+ }
+
+ @Test
+ public void seekPastToNonExistentPrefixToTheEnd() throws IOException {
+ Ref exp = ref(MASTER, 1);
+ Ref next = ref(NEXT, 2);
+ Ref afterNext = ref(AFTER_NEXT, 3);
+ Ref afterNextNext = ref(LAST, 4);
+ ReftableReader t = read(write(exp, next, afterNext, afterNextNext));
+ try (RefCursor rc = t.seekRefsWithPrefix("")) {
+ rc.seekPastPrefix("refs/heads/nextnon_existent_end");
+ assertFalse(rc.next());
+ }
+ }
+
+ @Test
+ public void seekPastWithSeekRefsWithPrefix() throws IOException {
+ Ref exp = ref(MASTER, 1);
+ Ref next = ref(NEXT, 2);
+ Ref afterNext = ref(AFTER_NEXT, 3);
+ Ref afterNextNext = ref(LAST, 4);
+ Ref notRefsHeads = ref(NOT_REF_HEADS, 5);
+ ReftableReader t = read(write(exp, next, afterNext, afterNextNext, notRefsHeads));
+ try (RefCursor rc = t.seekRefsWithPrefix("refs/heads/")) {
+ rc.seekPastPrefix("refs/heads/next/");
+ assertTrue(rc.next());
+ assertEquals(AFTER_NEXT, rc.getRef().getName());
+ assertTrue(rc.next());
+ assertEquals(LAST, rc.getRef().getName());
+
+ // NOT_REF_HEADS is next, but it's omitted because of
+ // seekRefsWithPrefix("refs/heads/").
+ assertFalse(rc.next());
+ }
+ }
+
+ @Test
+ public void seekPastWithLotsOfRefs() throws IOException {
+ Ref[] refs = new Ref[500];
+ for (int i = 1; i <= 500; i++) {
+ refs[i - 1] = ref(String.format("refs/%d", i), i);
+ }
+ ReftableReader t = read(write(refs));
+ try (RefCursor rc = t.allRefs()) {
+ rc.seekPastPrefix("refs/3");
+ assertTrue(rc.next());
+ assertEquals("refs/4", rc.getRef().getName());
+ assertTrue(rc.next());
+ assertEquals("refs/40", rc.getRef().getName());
+
+ rc.seekPastPrefix("refs/8");
+ assertTrue(rc.next());
+ assertEquals("refs/9", rc.getRef().getName());
+ assertTrue(rc.next());
+ assertEquals("refs/90", rc.getRef().getName());
+ assertTrue(rc.next());
+ assertEquals("refs/91", rc.getRef().getName());
+ }
+ }
+
+ @Test
+ public void seekPastManyTimes() throws IOException {
+ Ref exp = ref(MASTER, 1);
+ Ref next = ref(NEXT, 2);
+ Ref afterNext = ref(AFTER_NEXT, 3);
+ Ref afterNextNext = ref(LAST, 4);
+ ReftableReader t = read(write(exp, next, afterNext, afterNextNext));
+
+ try (RefCursor rc = t.seekRefsWithPrefix("")) {
+ rc.seekPastPrefix("refs/heads/master");
+ rc.seekPastPrefix("refs/heads/next");
+ rc.seekPastPrefix("refs/heads/nextnext");
+ rc.seekPastPrefix("refs/heads/nextnextnext");
+ assertFalse(rc.next());
+ }
+ }
+
+ @Test
+ public void seekPastOnEmptyTable() throws IOException {
+ ReftableReader t = read(write());
+ try (RefCursor rc = t.seekRefsWithPrefix("")) {
+ rc.seekPastPrefix("refs/");
+ assertFalse(rc.next());
+ }
+ }
+
+ @Test
public void indexScan() throws IOException {
List<Ref> refs = new ArrayList<>();
for (int i = 1; i <= 5670; i++) {
@@ -874,6 +1012,14 @@ public class ReftableTest {
}
@Test
+ public void byObjectIdSkipPastPrefix() throws IOException {
+ ReftableReader t = read(write());
+ try (RefCursor rc = t.byObjectId(id(2))) {
+ assertThrows(UnsupportedOperationException.class, () -> rc.seekPastPrefix("refs/heads/"));
+ }
+ }
+
+ @Test
public void unpeeledDoesNotWrite() {
try {
write(new ObjectIdRef.Unpeeled(PACKED, MASTER, id(1)));
@@ -884,6 +1030,18 @@ public class ReftableTest {
}
@Test
+ public void skipPastRefWithLastUTF8() throws IOException {
+ ReftableReader t = read(write(ref(String.format("refs/heads/%sbla", new String(LAST_UTF8_CHAR
+ , UTF_8)), 1)));
+
+ try (RefCursor rc = t.allRefs()) {
+ rc.seekPastPrefix("refs/heads/");
+ assertFalse(rc.next());
+ }
+ }
+
+
+ @Test
public void nameTooLongDoesNotWrite() throws IOException {
try {
ReftableConfig cfg = new ReftableConfig();
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java
index 88d17ec153..7590048a71 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java
@@ -26,6 +26,7 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collection;
+import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@@ -318,6 +319,64 @@ public class RefTest extends SampleDataRepositoryTestCase {
}
@Test
+ public void testGetRefsExcludingPrefix() throws IOException {
+ Set<String> exclude = new HashSet<>();
+ exclude.add("refs/tags");
+ // HEAD + 12 refs/heads are present here.
+ List<Ref> refs =
+ db.getRefDatabase().getRefsByPrefixWithExclusions(RefDatabase.ALL, exclude);
+ assertEquals(13, refs.size());
+ checkContainsRef(refs, db.exactRef("HEAD"));
+ checkContainsRef(refs, db.exactRef("refs/heads/a"));
+ for (Ref notInResult : db.getRefDatabase().getRefsByPrefix("refs/tags")) {
+ assertFalse(refs.contains(notInResult));
+ }
+ }
+
+ @Test
+ public void testGetRefsExcludingPrefixes() throws IOException {
+ Set<String> exclude = new HashSet<>();
+ exclude.add("refs/tags/");
+ exclude.add("refs/heads/");
+ List<Ref> refs = db.getRefDatabase().getRefsByPrefixWithExclusions(RefDatabase.ALL, exclude);
+ assertEquals(1, refs.size());
+ checkContainsRef(refs, db.exactRef("HEAD"));
+ }
+
+ @Test
+ public void testGetRefsExcludingNonExistingPrefixes() throws IOException {
+ Set<String> prefixes = new HashSet<>();
+ prefixes.add("refs/tags/");
+ prefixes.add("refs/heads/");
+ prefixes.add("refs/nonexistent/");
+ List<Ref> refs = db.getRefDatabase().getRefsByPrefixWithExclusions(RefDatabase.ALL, prefixes);
+ assertEquals(1, refs.size());
+ checkContainsRef(refs, db.exactRef("HEAD"));
+ }
+
+ @Test
+ public void testGetRefsWithPrefixExcludingPrefixes() throws IOException {
+ Set<String> exclude = new HashSet<>();
+ exclude.add("refs/heads/pa");
+ String include = "refs/heads/p";
+ List<Ref> refs = db.getRefDatabase().getRefsByPrefixWithExclusions(include, exclude);
+ assertEquals(1, refs.size());
+ checkContainsRef(refs, db.exactRef("refs/heads/prefix/a"));
+ }
+
+ @Test
+ public void testGetRefsWithPrefixExcludingOverlappingPrefixes() throws IOException {
+ Set<String> exclude = new HashSet<>();
+ exclude.add("refs/heads/pa");
+ exclude.add("refs/heads/");
+ exclude.add("refs/heads/p");
+ exclude.add("refs/tags/");
+ List<Ref> refs = db.getRefDatabase().getRefsByPrefixWithExclusions(RefDatabase.ALL, exclude);
+ assertEquals(1, refs.size());
+ checkContainsRef(refs, db.exactRef("HEAD"));
+ }
+
+ @Test
public void testResolveTipSha1() throws IOException {
ObjectId masterId = db.resolve("refs/heads/master");
Set<Ref> resolved = db.getRefDatabase().getTipsWithSha1(masterId);
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 5561dc6a27..6c3b056efd 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
@@ -178,6 +178,13 @@ public class DfsReftableDatabase extends DfsRefDatabase {
/** {@inheritDoc} */
@Override
+ public List<Ref> getRefsByPrefixWithExclusions(String include, Set<String> excludes)
+ throws IOException {
+ return reftableDatabase.getRefsByPrefixWithExclusions(include, excludes);
+ }
+
+ /** {@inheritDoc} */
+ @Override
public Set<Ref> getTipsWithSha1(ObjectId id) throws IOException {
if (!getReftableConfig().isIndexObjects()) {
return super.getTipsWithSha1(id);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableDatabase.java
index ad1e753128..a80fa837b7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableDatabase.java
@@ -21,6 +21,7 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@@ -181,6 +182,13 @@ public class FileReftableDatabase extends RefDatabase {
/** {@inheritDoc} */
@Override
+ public List<Ref> getRefsByPrefixWithExclusions(String include, Set<String> excludes)
+ throws IOException {
+ return reftableDatabase.getRefsByPrefixWithExclusions(include, excludes);
+ }
+
+ /** {@inheritDoc} */
+ @Override
public List<Ref> getAdditionalRefs() throws IOException {
return Collections.emptyList();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/MergedReftable.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/MergedReftable.java
index a78f4d24da..e210acf058 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/MergedReftable.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/MergedReftable.java
@@ -11,6 +11,7 @@
package org.eclipse.jgit.internal.storage.reftable;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.List;
import java.util.PriorityQueue;
@@ -215,6 +216,23 @@ public class MergedReftable extends Reftable {
}
}
+ @Override
+ public void seekPastPrefix(String prefixName) throws IOException {
+ List<RefQueueEntry> entriesToAdd = new ArrayList<>();
+ entriesToAdd.addAll(queue);
+ if (head != null) {
+ entriesToAdd.add(head);
+ }
+
+ head = null;
+ queue.clear();
+
+ for(RefQueueEntry entry : entriesToAdd){
+ entry.rc.seekPastPrefix(prefixName);
+ add(entry);
+ }
+ }
+
private RefQueueEntry poll() {
RefQueueEntry e = head;
if (e != null) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/RefCursor.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/RefCursor.java
index d96648eb50..5e2c350883 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/RefCursor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/RefCursor.java
@@ -29,6 +29,19 @@ public abstract class RefCursor implements AutoCloseable {
public abstract boolean next() throws IOException;
/**
+ * Seeks forward to the first ref record lexicographically beyond
+ * {@code prefixName} that doesn't start with {@code prefixName}. If there are
+ * no more results, skipping some refs won't add new results. E.g if we create a
+ * RefCursor that returns only results with a specific prefix, skipping that
+ * prefix won't give results that are not part of the original prefix.
+ *
+ * @param prefixName prefix that should be skipped. All previous refs before it
+ * will be skipped.
+ * @throws java.io.IOException references cannot be read.
+ */
+ public abstract void seekPastPrefix(String prefixName) throws IOException;
+
+ /**
* Get reference at the current position.
*
* @return reference at the current position.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableDatabase.java
index 4747be3544..0c16828617 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableDatabase.java
@@ -14,10 +14,12 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.locks.ReentrantLock;
+import java.util.stream.Collectors;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.lib.ObjectId;
@@ -266,6 +268,54 @@ public abstract class ReftableDatabase {
}
/**
+ * Returns refs whose names start with a given prefix excluding all refs that
+ * start with one of the given prefixes.
+ *
+ * @param include string that names of refs should start with; may be empty.
+ * @param excludes strings that names of refs can't start with; may be empty.
+ * @return immutable list of refs whose names start with {@code include} and
+ * none of the strings in {@code exclude}.
+ * @throws java.io.IOException the reference space cannot be accessed.
+ */
+ public List<Ref> getRefsByPrefixWithExclusions(String include, Set<String> excludes) throws IOException {
+ if (excludes.isEmpty()) {
+ return getRefsByPrefix(include);
+ }
+ List<Ref> results = new ArrayList<>();
+ lock.lock();
+ try {
+ Reftable table = reader();
+ Iterator<String> excludeIterator =
+ excludes.stream().sorted().collect(Collectors.toList()).iterator();
+ String currentExclusion = excludeIterator.hasNext() ? excludeIterator.next() : null;
+ try (RefCursor rc = RefDatabase.ALL.equals(include) ? table.allRefs() : table.seekRefsWithPrefix(include)) {
+ while (rc.next()) {
+ Ref ref = table.resolve(rc.getRef());
+ if (ref == null || ref.getObjectId() == null) {
+ continue;
+ }
+ // Skip prefixes that will never see since we are already further than those
+ // prefixes lexicographically.
+ while (excludeIterator.hasNext() && !ref.getName().startsWith(currentExclusion)
+ && ref.getName().compareTo(currentExclusion) > 0) {
+ currentExclusion = excludeIterator.next();
+ }
+
+ if (currentExclusion != null && ref.getName().startsWith(currentExclusion)) {
+ rc.seekPastPrefix(currentExclusion);
+ continue;
+ }
+ results.add(ref);
+ }
+ }
+ } finally {
+ lock.unlock();
+ }
+
+ return Collections.unmodifiableList(results);
+ }
+
+ /**
* @return whether there is a fast SHA1 to ref map.
* @throws IOException in case of I/O problems.
*/
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 095276f57b..9e2ae91608 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
@@ -509,6 +509,21 @@ public class ReftableReader extends Reftable implements AutoCloseable {
}
@Override
+ public void seekPastPrefix(String prefixName) throws IOException {
+ initRefIndex();
+ byte[] key = prefixName.getBytes(UTF_8);
+ ByteBuffer byteBuffer = ByteBuffer.allocate(key.length + 1);
+ byteBuffer.put(key);
+ // Add the representation of the last byte lexicographically. Based on how UTF_8
+ // representation works, this byte will be bigger lexicographically than any
+ // UTF_8 character when translated into bytes, since 0xFF can never be a part of
+ // a UTF_8 string.
+ byteBuffer.put((byte) 0xFF);
+
+ block = seek(REF_BLOCK_TYPE, byteBuffer.array(), refIndex, 0, refEnd);
+ }
+
+ @Override
public Ref getRef() {
return ref;
}
@@ -682,6 +697,17 @@ public class ReftableReader extends Reftable implements AutoCloseable {
}
@Override
+ /**
+ * The implementation here would not be efficient complexity-wise since it
+ * expected that there are a small number of refs that match the same object id.
+ * In such case it's better to not even use this method (as the caller might
+ * expect it to be efficient).
+ */
+ public void seekPastPrefix(String prefixName) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public Ref getRef() {
return ref;
}
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 6832c9cd80..7b7bdebac8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
@@ -21,6 +21,9 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.annotations.Nullable;
@@ -414,6 +417,31 @@ public abstract class RefDatabase {
}
/**
+ * Returns refs whose names start with a given prefix excluding all refs that
+ * start with one of the given prefixes.
+ *
+ * <p>
+ * The default implementation is not efficient. Implementors of {@link RefDatabase}
+ * should override this method directly if a better implementation is possible.
+ *
+ * @param include string that names of refs should start with; may be empty.
+ * @param excludes strings that names of refs can't start with; may be empty.
+ * @return immutable list of refs whose names start with {@code prefix} and none
+ * of the strings in {@code exclude}.
+ * @throws java.io.IOException the reference space cannot be accessed.
+ * @since 5.11
+ */
+ @NonNull
+ public List<Ref> getRefsByPrefixWithExclusions(String include, Set<String> excludes)
+ throws IOException {
+ Stream<Ref> refs = getRefs(include).values().stream();
+ for(String exclude: excludes) {
+ refs = refs.filter(r -> !r.getName().startsWith(exclude));
+ }
+ return Collections.unmodifiableList(refs.collect(Collectors.toList()));
+ }
+
+ /**
* Returns refs whose names start with one of the given prefixes.
* <p>
* The default implementation uses {@link #getRefsByPrefix(String)}.