* changes: Compare getting all refs except specific refs with seek and with filter Add getsRefsByPrefixWithSkips (excluding prefixes) to ReftableDatabase Add seekPastPrefix method to RefCursortags/v5.11.0.202102031030-m2
return super.getRefsByPrefix(prefix); | return super.getRefsByPrefix(prefix); | ||||
} | } | ||||
/** {@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} */ | /** {@inheritDoc} */ | ||||
@Override | @Override | ||||
public Set<Ref> getTipsWithSha1(ObjectId id) throws IOException { | public Set<Ref> getTipsWithSha1(ObjectId id) throws IOException { |
import java.io.FileNotFoundException; | import java.io.FileNotFoundException; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.InputStreamReader; | import java.io.InputStreamReader; | ||||
import java.util.ArrayList; | |||||
import java.util.List; | import java.util.List; | ||||
import java.util.stream.Collectors; | |||||
import org.eclipse.jgit.internal.storage.file.FileReftableStack; | import org.eclipse.jgit.internal.storage.file.FileReftableStack; | ||||
import org.eclipse.jgit.internal.storage.io.BlockSource; | import org.eclipse.jgit.internal.storage.io.BlockSource; | ||||
SEEK_COLD, SEEK_HOT, | SEEK_COLD, SEEK_HOT, | ||||
BY_ID_COLD, BY_ID_HOT, | BY_ID_COLD, BY_ID_HOT, | ||||
WRITE_STACK, | WRITE_STACK, | ||||
GET_REFS_EXCLUDING_REF | |||||
} | } | ||||
@Option(name = "--tries") | @Option(name = "--tries") | ||||
case WRITE_STACK: | case WRITE_STACK: | ||||
writeStack(); | writeStack(); | ||||
break; | break; | ||||
} | |||||
case GET_REFS_EXCLUDING_REF : | |||||
getRefsExcludingWithSeekPast(ref); | |||||
getRefsExcludingWithFilter(ref); | |||||
break; | |||||
} | |||||
} | } | ||||
private void printf(String fmt, Object... args) throws IOException { | private void printf(String fmt, Object... args) throws IOException { | ||||
printf("%12s %10d usec %9.1f usec/run %5d runs", "reftable", | printf("%12s %10d usec %9.1f usec/run %5d runs", "reftable", | ||||
tot / 1000, (((double) tot) / tries) / 1000, tries); | 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); | |||||
} | |||||
} | } |
import java.io.IOException; | import java.io.IOException; | ||||
import java.security.SecureRandom; | import java.security.SecureRandom; | ||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.Collection; | |||||
import java.util.HashSet; | |||||
import java.util.List; | import java.util.List; | ||||
import java.util.Set; | |||||
import org.eclipse.jgit.lib.AnyObjectId; | import org.eclipse.jgit.lib.AnyObjectId; | ||||
import org.eclipse.jgit.lib.Constants; | import org.eclipse.jgit.lib.Constants; | ||||
import org.eclipse.jgit.lib.NullProgressMonitor; | import org.eclipse.jgit.lib.NullProgressMonitor; | ||||
import org.eclipse.jgit.lib.ObjectId; | import org.eclipse.jgit.lib.ObjectId; | ||||
import org.eclipse.jgit.lib.PersonIdent; | import org.eclipse.jgit.lib.PersonIdent; | ||||
import org.eclipse.jgit.lib.Ref; | import org.eclipse.jgit.lib.Ref; | ||||
import org.eclipse.jgit.lib.RefDatabase; | |||||
import org.eclipse.jgit.lib.RefRename; | import org.eclipse.jgit.lib.RefRename; | ||||
import org.eclipse.jgit.lib.RefUpdate; | import org.eclipse.jgit.lib.RefUpdate; | ||||
import org.eclipse.jgit.lib.RefUpdate.Result; | import org.eclipse.jgit.lib.RefUpdate.Result; | ||||
assertEquals(Ref.Storage.PACKED, b.getStorage()); | 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 { | private RefUpdate updateRef(String name) throws IOException { | ||||
final RefUpdate ref = db.updateRef(name); | final RefUpdate ref = db.updateRef(name); | ||||
ref.setNewObjectId(db.resolve(Constants.HEAD)); | ref.setNewObjectId(db.resolve(Constants.HEAD)); | ||||
fail("link " + src + " to " + dst); | 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); | |||||
} | |||||
} | } |
import java.time.Instant; | import java.time.Instant; | ||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.Arrays; | import java.util.Arrays; | ||||
import java.util.HashSet; | |||||
import java.util.List; | import java.util.List; | ||||
import java.util.Map; | import java.util.Map; | ||||
import java.util.Set; | |||||
import java.util.concurrent.atomic.AtomicInteger; | import java.util.concurrent.atomic.AtomicInteger; | ||||
import java.util.concurrent.atomic.AtomicReference; | import java.util.concurrent.atomic.AtomicReference; | ||||
assertEquals(A, c.getObjectId()); | assertEquals(A, c.getObjectId()); | ||||
} | } | ||||
@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 | @Test | ||||
public void testFirstExactRef_IgnoresGarbageRef() throws IOException { | public void testFirstExactRef_IgnoresGarbageRef() throws IOException { | ||||
writeLooseRef("refs/heads/A", A); | writeLooseRef("refs/heads/A", A); |
} | } | ||||
} | } | ||||
@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 | @Test | ||||
public void twoTableById() throws IOException { | public void twoTableById() throws IOException { | ||||
List<Ref> delta1 = Arrays.asList( | List<Ref> delta1 = Arrays.asList( |
package org.eclipse.jgit.internal.storage.reftable; | 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.HEAD; | ||||
import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH; | import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH; | ||||
import static org.eclipse.jgit.lib.Constants.R_HEADS; | import static org.eclipse.jgit.lib.Constants.R_HEADS; | ||||
import org.junit.Test; | import org.junit.Test; | ||||
public class ReftableTest { | 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 MASTER = "refs/heads/master"; | ||||
private static final String NEXT = "refs/heads/next"; | 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 static final String V1_0 = "refs/tags/v1.0"; | ||||
private Stats stats; | private Stats stats; | ||||
} | } | ||||
} | } | ||||
@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 | @Test | ||||
public void indexScan() throws IOException { | public void indexScan() throws IOException { | ||||
List<Ref> refs = new ArrayList<>(); | List<Ref> refs = new ArrayList<>(); | ||||
} | } | ||||
} | } | ||||
@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 | @Test | ||||
public void unpeeledDoesNotWrite() { | public void unpeeledDoesNotWrite() { | ||||
try { | try { | ||||
} | } | ||||
} | } | ||||
@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 | @Test | ||||
public void nameTooLongDoesNotWrite() throws IOException { | public void nameTooLongDoesNotWrite() throws IOException { | ||||
try { | try { |
import java.io.FileOutputStream; | import java.io.FileOutputStream; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.util.Collection; | import java.util.Collection; | ||||
import java.util.HashSet; | |||||
import java.util.List; | import java.util.List; | ||||
import java.util.Optional; | import java.util.Optional; | ||||
import java.util.Set; | import java.util.Set; | ||||
checkContainsRef(refs, db.exactRef("refs/tags/A")); | checkContainsRef(refs, db.exactRef("refs/tags/A")); | ||||
} | } | ||||
@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 | @Test | ||||
public void testResolveTipSha1() throws IOException { | public void testResolveTipSha1() throws IOException { | ||||
ObjectId masterId = db.resolve("refs/heads/master"); | ObjectId masterId = db.resolve("refs/heads/master"); |
return reftableDatabase.getRefsByPrefix(prefix); | return reftableDatabase.getRefsByPrefix(prefix); | ||||
} | } | ||||
/** {@inheritDoc} */ | |||||
@Override | |||||
public List<Ref> getRefsByPrefixWithExclusions(String include, Set<String> excludes) | |||||
throws IOException { | |||||
return reftableDatabase.getRefsByPrefixWithExclusions(include, excludes); | |||||
} | |||||
/** {@inheritDoc} */ | /** {@inheritDoc} */ | ||||
@Override | @Override | ||||
public Set<Ref> getTipsWithSha1(ObjectId id) throws IOException { | public Set<Ref> getTipsWithSha1(ObjectId id) throws IOException { |
import java.util.HashSet; | import java.util.HashSet; | ||||
import java.util.List; | import java.util.List; | ||||
import java.util.Map; | import java.util.Map; | ||||
import java.util.Set; | |||||
import java.util.TreeSet; | import java.util.TreeSet; | ||||
import java.util.concurrent.locks.Lock; | import java.util.concurrent.locks.Lock; | ||||
import java.util.concurrent.locks.ReentrantLock; | import java.util.concurrent.locks.ReentrantLock; | ||||
RefList.emptyList()); | RefList.emptyList()); | ||||
} | } | ||||
/** {@inheritDoc} */ | |||||
@Override | |||||
public List<Ref> getRefsByPrefixWithExclusions(String include, Set<String> excludes) | |||||
throws IOException { | |||||
return reftableDatabase.getRefsByPrefixWithExclusions(include, excludes); | |||||
} | |||||
/** {@inheritDoc} */ | /** {@inheritDoc} */ | ||||
@Override | @Override | ||||
public List<Ref> getAdditionalRefs() throws IOException { | public List<Ref> getAdditionalRefs() throws IOException { |
package org.eclipse.jgit.internal.storage.reftable; | package org.eclipse.jgit.internal.storage.reftable; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.util.ArrayList; | |||||
import java.util.List; | import java.util.List; | ||||
import java.util.PriorityQueue; | import java.util.PriorityQueue; | ||||
} | } | ||||
} | } | ||||
@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() { | private RefQueueEntry poll() { | ||||
RefQueueEntry e = head; | RefQueueEntry e = head; | ||||
if (e != null) { | if (e != null) { |
*/ | */ | ||||
public abstract boolean next() throws IOException; | 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. | * Get reference at the current position. | ||||
* | * |
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.Collections; | import java.util.Collections; | ||||
import java.util.HashSet; | import java.util.HashSet; | ||||
import java.util.Iterator; | |||||
import java.util.List; | import java.util.List; | ||||
import java.util.Set; | import java.util.Set; | ||||
import java.util.TreeSet; | import java.util.TreeSet; | ||||
import java.util.concurrent.locks.ReentrantLock; | import java.util.concurrent.locks.ReentrantLock; | ||||
import java.util.stream.Collectors; | |||||
import org.eclipse.jgit.annotations.Nullable; | import org.eclipse.jgit.annotations.Nullable; | ||||
import org.eclipse.jgit.lib.ObjectId; | import org.eclipse.jgit.lib.ObjectId; | ||||
return Collections.unmodifiableList(all); | return Collections.unmodifiableList(all); | ||||
} | } | ||||
/** | |||||
* 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. | * @return whether there is a fast SHA1 to ref map. | ||||
* @throws IOException in case of I/O problems. | * @throws IOException in case of I/O problems. |
} | } | ||||
} | } | ||||
@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 | @Override | ||||
public Ref getRef() { | public Ref getRef() { | ||||
return ref; | return ref; | ||||
} | } | ||||
} | } | ||||
@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 | @Override | ||||
public Ref getRef() { | public Ref getRef() { | ||||
return ref; | return ref; |
import java.util.List; | import java.util.List; | ||||
import java.util.Map; | import java.util.Map; | ||||
import java.util.Set; | 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.NonNull; | ||||
import org.eclipse.jgit.annotations.Nullable; | import org.eclipse.jgit.annotations.Nullable; | ||||
return Collections.unmodifiableList(result); | return Collections.unmodifiableList(result); | ||||
} | } | ||||
/** | |||||
* 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. | * Returns refs whose names start with one of the given prefixes. | ||||
* <p> | * <p> |