refs: selectively set prefix in the seek functions

The ref iterator exposes a `ref_iterator_seek()` function. The name suggests that this would seek the iterator to a specific reference in some ways similar to how `fseek()` works for the filesystem. However, the function actually sets the prefix for refs iteration. So further iteration would only yield references which match the particular prefix. This is a bit confusing. Let's add a 'flags' field to the function, which when set with the 'REF_ITERATOR_SEEK_SET_PREFIX' flag, will set the prefix for the iteration in-line with the existing behavior. Otherwise, the reference backends will simply seek to the specified reference and clears any previously set prefix. This allows users to start iteration from a specific reference. In the packed and reftable backend, since references are available in a sorted list, the changes are simply setting the prefix if needed. The changes on the files-backend are a little more involved, since the files backend uses the 'ref-cache' mechanism. We move out the existing logic within `cache_ref_iterator_seek()` to `cache_ref_iterator_set_prefix()` which is called when the 'REF_ITERATOR_SEEK_SET_PREFIX' flag is set. We then parse the provided seek string and set the required levels and their indexes to ensure that seeking is possible. Helped-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Karthik Nayak <karthik.188@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Karthik Nayak committed Jul 15, 2025 at 13:28 UTC 2b4648b9190b552e942d93363482bd617a510fc1
9 files changed +152 -50
refs.c
+3 -3
@@ -2666,12 +2666,12 @@ enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs
2666 if (!initial_transaction) {
2667 int ok;
2668
2669 - if (!iter) {
2669 + if (!iter)
2670 iter = refs_ref_iterator_begin(refs, dirname.buf, NULL, 0,
2671 DO_FOR_EACH_INCLUDE_BROKEN);
2672 - } else if (ref_iterator_seek(iter, dirname.buf) < 0) {
2672 + else if (ref_iterator_seek(iter, dirname.buf,
2673 + REF_ITERATOR_SEEK_SET_PREFIX) < 0)
2674 goto cleanup;
2674 - }
2675
2676 while ((ok = ref_iterator_advance(iter)) == ITER_OK) {
2677 if (skip &&
refs.h
+17 -9
@@ -1299,21 +1299,29 @@ struct ref_iterator *refs_ref_iterator_begin(
1299 */
1300 int ref_iterator_advance(struct ref_iterator *ref_iterator);
1301
1302 +enum ref_iterator_seek_flag {
1303 + /*
1304 + * When the REF_ITERATOR_SEEK_SET_PREFIX flag is set, the iterator's prefix is
1305 + * updated to match the provided string, affecting all subsequent iterations. If
1306 + * not, the iterator seeks to the specified reference and clears any previously
1307 + * set prefix.
1308 + */
1309 + REF_ITERATOR_SEEK_SET_PREFIX = (1 << 0),
1310 +};
1311 +
1312 /*
1303 - * Seek the iterator to the first reference with the given prefix.
1304 - * The prefix is matched as a literal string, without regard for path
1305 - * separators. If prefix is NULL or the empty string, seek the iterator to the
1313 + * Seek the iterator to the first reference matching the given seek string.
1314 + * The seek string is matched as a literal string, without regard for path
1315 + * separators. If seek is NULL or the empty string, seek the iterator to the
1316 * first reference again.
1317 *
1308 - * This function is expected to behave as if a new ref iterator with the same
1309 - * prefix had been created, but allows reuse of iterators and thus may allow
1310 - * the backend to optimize. Parameters other than the prefix that have been
1311 - * passed when creating the iterator will remain unchanged.
1318 + * This function is expected to behave as if a new ref iterator has been
1319 + * created, but allows reuse of existing iterators for optimization.
1320 *
1321 * Returns 0 on success, a negative error code otherwise.
1322 */
1315 -int ref_iterator_seek(struct ref_iterator *ref_iterator,
1316 - const char *prefix);
1323 +int ref_iterator_seek(struct ref_iterator *ref_iterator, const char *refname,
1324 + unsigned int flags);
1325
1326 /*
1327 * If possible, peel the reference currently being viewed by the
refs/debug.c
+4 -3
@@ -170,12 +170,13 @@ static int debug_ref_iterator_advance(struct ref_iterator *ref_iterator)
170 }
171
172 static int debug_ref_iterator_seek(struct ref_iterator *ref_iterator,
173 - const char *prefix)
173 + const char *refname, unsigned int flags)
174 {
175 struct debug_ref_iterator *diter =
176 (struct debug_ref_iterator *)ref_iterator;
177 - int res = diter->iter->vtable->seek(diter->iter, prefix);
178 - trace_printf_key(&trace_refs, "iterator_seek: %s: %d\n", prefix ? prefix : "", res);
177 + int res = diter->iter->vtable->seek(diter->iter, refname, flags);
178 + trace_printf_key(&trace_refs, "iterator_seek: %s flags: %d: %d\n",
179 + refname ? refname : "", flags, res);
180 return res;
181 }
182
refs/files-backend.c
+4 -3
@@ -929,11 +929,11 @@ static int files_ref_iterator_advance(struct ref_iterator *ref_iterator)
929 }
930
931 static int files_ref_iterator_seek(struct ref_iterator *ref_iterator,
932 - const char *prefix)
932 + const char *refname, unsigned int flags)
933 {
934 struct files_ref_iterator *iter =
935 (struct files_ref_iterator *)ref_iterator;
936 - return ref_iterator_seek(iter->iter0, prefix);
936 + return ref_iterator_seek(iter->iter0, refname, flags);
937 }
938
939 static int files_ref_iterator_peel(struct ref_iterator *ref_iterator,
@@ -2316,7 +2316,8 @@ static int files_reflog_iterator_advance(struct ref_iterator *ref_iterator)
2316 }
2317
2318 static int files_reflog_iterator_seek(struct ref_iterator *ref_iterator UNUSED,
2319 - const char *prefix UNUSED)
2319 + const char *refname UNUSED,
2320 + unsigned int flags UNUSED)
2321 {
2322 BUG("ref_iterator_seek() called for reflog_iterator");
2323 }
refs/iterator.c
+15 -11
@@ -15,10 +15,10 @@ int ref_iterator_advance(struct ref_iterator *ref_iterator)
15 return ref_iterator->vtable->advance(ref_iterator);
16 }
17
18 -int ref_iterator_seek(struct ref_iterator *ref_iterator,
19 - const char *prefix)
18 +int ref_iterator_seek(struct ref_iterator *ref_iterator, const char *refname,
19 + unsigned int flags)
20 {
21 - return ref_iterator->vtable->seek(ref_iterator, prefix);
21 + return ref_iterator->vtable->seek(ref_iterator, refname, flags);
22 }
23
24 int ref_iterator_peel(struct ref_iterator *ref_iterator,
@@ -57,7 +57,8 @@ static int empty_ref_iterator_advance(struct ref_iterator *ref_iterator UNUSED)
57 }
58
59 static int empty_ref_iterator_seek(struct ref_iterator *ref_iterator UNUSED,
60 - const char *prefix UNUSED)
60 + const char *refname UNUSED,
61 + unsigned int flags UNUSED)
62 {
63 return 0;
64 }
@@ -224,7 +225,7 @@ error:
225 }
226
227 static int merge_ref_iterator_seek(struct ref_iterator *ref_iterator,
227 - const char *prefix)
228 + const char *refname, unsigned int flags)
229 {
230 struct merge_ref_iterator *iter =
231 (struct merge_ref_iterator *)ref_iterator;
@@ -234,11 +235,11 @@ static int merge_ref_iterator_seek(struct ref_iterator *ref_iterator,
235 iter->iter0 = iter->iter0_owned;
236 iter->iter1 = iter->iter1_owned;
237
237 - ret = ref_iterator_seek(iter->iter0, prefix);
238 + ret = ref_iterator_seek(iter->iter0, refname, flags);
239 if (ret < 0)
240 return ret;
241
241 - ret = ref_iterator_seek(iter->iter1, prefix);
242 + ret = ref_iterator_seek(iter->iter1, refname, flags);
243 if (ret < 0)
244 return ret;
245
@@ -407,13 +408,16 @@ static int prefix_ref_iterator_advance(struct ref_iterator *ref_iterator)
408 }
409
410 static int prefix_ref_iterator_seek(struct ref_iterator *ref_iterator,
410 - const char *prefix)
411 + const char *refname, unsigned int flags)
412 {
413 struct prefix_ref_iterator *iter =
414 (struct prefix_ref_iterator *)ref_iterator;
414 - free(iter->prefix);
415 - iter->prefix = xstrdup_or_null(prefix);
416 - return ref_iterator_seek(iter->iter0, prefix);
415 +
416 + if (flags & REF_ITERATOR_SEEK_SET_PREFIX) {
417 + free(iter->prefix);
418 + iter->prefix = xstrdup_or_null(refname);
419 + }
420 + return ref_iterator_seek(iter->iter0, refname, flags);
421 }
422
423 static int prefix_ref_iterator_peel(struct ref_iterator *ref_iterator,
refs/packed-backend.c
+11 -6
@@ -1004,19 +1004,23 @@ static int packed_ref_iterator_advance(struct ref_iterator *ref_iterator)
1004 }
1005
1006 static int packed_ref_iterator_seek(struct ref_iterator *ref_iterator,
1007 - const char *prefix)
1007 + const char *refname, unsigned int flags)
1008 {
1009 struct packed_ref_iterator *iter =
1010 (struct packed_ref_iterator *)ref_iterator;
1011 const char *start;
1012
1013 - if (prefix && *prefix)
1014 - start = find_reference_location(iter->snapshot, prefix, 0);
1013 + if (refname && *refname)
1014 + start = find_reference_location(iter->snapshot, refname, 0);
1015 else
1016 start = iter->snapshot->start;
1017
1018 - free(iter->prefix);
1019 - iter->prefix = xstrdup_or_null(prefix);
1018 + /* Unset any previously set prefix */
1019 + FREE_AND_NULL(iter->prefix);
1020 +
1021 + if (flags & REF_ITERATOR_SEEK_SET_PREFIX)
1022 + iter->prefix = xstrdup_or_null(refname);
1023 +
1024 iter->pos = start;
1025 iter->eof = iter->snapshot->eof;
1026
@@ -1194,7 +1198,8 @@ static struct ref_iterator *packed_ref_iterator_begin(
1198 iter->repo = ref_store->repo;
1199 iter->flags = flags;
1200
1197 - if (packed_ref_iterator_seek(&iter->base, prefix) < 0) {
1201 + if (packed_ref_iterator_seek(&iter->base, prefix,
1202 + REF_ITERATOR_SEEK_SET_PREFIX) < 0) {
1203 ref_iterator_free(&iter->base);
1204 return NULL;
1205 }
refs/ref-cache.c
+80 -5
@@ -434,11 +434,9 @@ static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator)
434 }
435 }
436
437 -static int cache_ref_iterator_seek(struct ref_iterator *ref_iterator,
438 - const char *prefix)
437 +static int cache_ref_iterator_set_prefix(struct cache_ref_iterator *iter,
438 + const char *prefix)
439 {
440 - struct cache_ref_iterator *iter =
441 - (struct cache_ref_iterator *)ref_iterator;
440 struct cache_ref_iterator_level *level;
441 struct ref_dir *dir;
442
@@ -469,6 +467,82 @@ static int cache_ref_iterator_seek(struct ref_iterator *ref_iterator,
467 return 0;
468 }
469
470 +static int cache_ref_iterator_seek(struct ref_iterator *ref_iterator,
471 + const char *refname, unsigned int flags)
472 +{
473 + struct cache_ref_iterator *iter =
474 + (struct cache_ref_iterator *)ref_iterator;
475 +
476 + if (flags & REF_ITERATOR_SEEK_SET_PREFIX) {
477 + return cache_ref_iterator_set_prefix(iter, refname);
478 + } else if (refname && *refname) {
479 + struct cache_ref_iterator_level *level;
480 + const char *slash = refname;
481 + struct ref_dir *dir;
482 +
483 + dir = get_ref_dir(iter->cache->root);
484 +
485 + if (iter->prime_dir)
486 + prime_ref_dir(dir, refname);
487 +
488 + iter->levels_nr = 1;
489 + level = &iter->levels[0];
490 + level->index = -1;
491 + level->dir = dir;
492 +
493 + /* Unset any previously set prefix */
494 + FREE_AND_NULL(iter->prefix);
495 +
496 + /*
497 + * Breakdown the provided seek path and assign the correct
498 + * indexing to each level as needed.
499 + */
500 + do {
501 + int len, idx;
502 + int cmp = 0;
503 +
504 + sort_ref_dir(dir);
505 +
506 + slash = strchr(slash, '/');
507 + len = slash ? slash - refname : (int)strlen(refname);
508 +
509 + for (idx = 0; idx < dir->nr; idx++) {
510 + cmp = strncmp(refname, dir->entries[idx]->name, len);
511 + if (cmp <= 0)
512 + break;
513 + }
514 + /* don't overflow the index */
515 + idx = idx >= dir->nr ? dir->nr - 1 : idx;
516 +
517 + if (slash)
518 + slash = slash + 1;
519 +
520 + level->index = idx;
521 + if (dir->entries[idx]->flag & REF_DIR) {
522 + /* push down a level */
523 + dir = get_ref_dir(dir->entries[idx]);
524 +
525 + ALLOC_GROW(iter->levels, iter->levels_nr + 1,
526 + iter->levels_alloc);
527 + level = &iter->levels[iter->levels_nr++];
528 + level->dir = dir;
529 + level->index = -1;
530 + } else {
531 + /* reduce the index so the leaf node is iterated over */
532 + if (cmp <= 0 && !slash)
533 + level->index = idx - 1;
534 + /*
535 + * while the seek path may not be exhausted, our
536 + * match is exhausted at a leaf node.
537 + */
538 + break;
539 + }
540 + } while (slash);
541 + }
542 +
543 + return 0;
544 +}
545 +
546 static int cache_ref_iterator_peel(struct ref_iterator *ref_iterator,
547 struct object_id *peeled)
548 {
@@ -509,7 +583,8 @@ struct ref_iterator *cache_ref_iterator_begin(struct ref_cache *cache,
583 iter->cache = cache;
584 iter->prime_dir = prime_dir;
585
512 - if (cache_ref_iterator_seek(&iter->base, prefix) < 0) {
586 + if (cache_ref_iterator_seek(&iter->base, prefix,
587 + REF_ITERATOR_SEEK_SET_PREFIX) < 0) {
588 ref_iterator_free(&iter->base);
589 return NULL;
590 }
refs/refs-internal.h
+4 -3
@@ -353,11 +353,12 @@ void base_ref_iterator_init(struct ref_iterator *iter,
353 typedef int ref_iterator_advance_fn(struct ref_iterator *ref_iterator);
354
355 /*
356 - * Seek the iterator to the first reference matching the given prefix. Should
357 - * behave the same as if a new iterator was created with the same prefix.
356 + * Seek the iterator to the first matching reference. If the
357 + * REF_ITERATOR_SEEK_SET_PREFIX flag is set, it would behave the same as if a
358 + * new iterator was created with the provided refname as prefix.
359 */
360 typedef int ref_iterator_seek_fn(struct ref_iterator *ref_iterator,
360 - const char *prefix);
361 + const char *refname, unsigned int flags);
362
363 /*
364 * Peels the current ref, returning 0 for success or -1 for failure.
refs/reftable-backend.c
+14 -7
@@ -719,15 +719,20 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)
719 }
720
721 static int reftable_ref_iterator_seek(struct ref_iterator *ref_iterator,
722 - const char *prefix)
722 + const char *refname, unsigned int flags)
723 {
724 struct reftable_ref_iterator *iter =
725 (struct reftable_ref_iterator *)ref_iterator;
726
727 - free(iter->prefix);
728 - iter->prefix = xstrdup_or_null(prefix);
729 - iter->prefix_len = prefix ? strlen(prefix) : 0;
730 - iter->err = reftable_iterator_seek_ref(&iter->iter, prefix);
727 + /* Unset any previously set prefix */
728 + FREE_AND_NULL(iter->prefix);
729 + iter->prefix_len = 0;
730 +
731 + if (flags & REF_ITERATOR_SEEK_SET_PREFIX) {
732 + iter->prefix = xstrdup_or_null(refname);
733 + iter->prefix_len = refname ? strlen(refname) : 0;
734 + }
735 + iter->err = reftable_iterator_seek_ref(&iter->iter, refname);
736
737 return iter->err;
738 }
@@ -839,7 +844,8 @@ static struct reftable_ref_iterator *ref_iterator_for_stack(struct reftable_ref_
844 if (ret)
845 goto done;
846
842 - ret = reftable_ref_iterator_seek(&iter->base, prefix);
847 + ret = reftable_ref_iterator_seek(&iter->base, prefix,
848 + REF_ITERATOR_SEEK_SET_PREFIX);
849 if (ret)
850 goto done;
851
@@ -2042,7 +2048,8 @@ static int reftable_reflog_iterator_advance(struct ref_iterator *ref_iterator)
2048 }
2049
2050 static int reftable_reflog_iterator_seek(struct ref_iterator *ref_iterator UNUSED,
2045 - const char *prefix UNUSED)
2051 + const char *refname UNUSED,
2052 + unsigned int flags UNUSED)
2053 {
2054 BUG("reftable reflog iterator cannot be seeked");
2055 return -1;