refs/iterator: implement seeking for ref-cache iterators

Implement seeking of ref-cache iterators. This is done by splitting most of the logic to seek iterators out of `cache_ref_iterator_begin()` and putting it into `cache_ref_iterator_seek()` so that we can reuse the logic. Note that we cannot use the optimization anymore where we return an empty ref iterator when there aren't any references, as otherwise it wouldn't be possible to reseek the iterator to a different prefix that may exist. This shouldn't be much of a performance concern though as we now start to bail out early in case `advance()` sees that there are no more directories to be searched. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Mar 12, 2025 at 16:56 UTC 84e656919cb7237f1b11a948974d0591d9d3434f
1 file changed +51 -28
refs/ref-cache.c
+51 -28
@@ -362,9 +362,7 @@ struct cache_ref_iterator {
362 struct ref_iterator base;
363
364 /*
365 - * The number of levels currently on the stack. This is always
366 - * at least 1, because when it becomes zero the iteration is
367 - * ended and this struct is freed.
365 + * The number of levels currently on the stack.
366 */
367 size_t levels_nr;
368
@@ -376,7 +374,7 @@ struct cache_ref_iterator {
374 * The prefix is matched textually, without regard for path
375 * component boundaries.
376 */
379 - const char *prefix;
377 + char *prefix;
378
379 /*
380 * A stack of levels. levels[0] is the uppermost level that is
@@ -389,6 +387,9 @@ struct cache_ref_iterator {
387 struct cache_ref_iterator_level *levels;
388
389 struct repository *repo;
390 + struct ref_cache *cache;
391 +
392 + int prime_dir;
393 };
394
395 static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator)
@@ -396,6 +397,9 @@ static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator)
397 struct cache_ref_iterator *iter =
398 (struct cache_ref_iterator *)ref_iterator;
399
400 + if (!iter->levels_nr)
401 + return ITER_DONE;
402 +
403 while (1) {
404 struct cache_ref_iterator_level *level =
405 &iter->levels[iter->levels_nr - 1];
@@ -444,6 +448,41 @@ static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator)
448 }
449 }
450
451 +static int cache_ref_iterator_seek(struct ref_iterator *ref_iterator,
452 + const char *prefix)
453 +{
454 + struct cache_ref_iterator *iter =
455 + (struct cache_ref_iterator *)ref_iterator;
456 + struct cache_ref_iterator_level *level;
457 + struct ref_dir *dir;
458 +
459 + dir = get_ref_dir(iter->cache->root);
460 + if (prefix && *prefix)
461 + dir = find_containing_dir(dir, prefix);
462 + if (!dir) {
463 + iter->levels_nr = 0;
464 + return 0;
465 + }
466 +
467 + if (iter->prime_dir)
468 + prime_ref_dir(dir, prefix);
469 + iter->levels_nr = 1;
470 + level = &iter->levels[0];
471 + level->index = -1;
472 + level->dir = dir;
473 +
474 + if (prefix && *prefix) {
475 + free(iter->prefix);
476 + iter->prefix = xstrdup(prefix);
477 + level->prefix_state = PREFIX_WITHIN_DIR;
478 + } else {
479 + FREE_AND_NULL(iter->prefix);
480 + level->prefix_state = PREFIX_CONTAINS_DIR;
481 + }
482 +
483 + return 0;
484 +}
485 +
486 static int cache_ref_iterator_peel(struct ref_iterator *ref_iterator,
487 struct object_id *peeled)
488 {
@@ -456,12 +495,13 @@ static void cache_ref_iterator_release(struct ref_iterator *ref_iterator)
495 {
496 struct cache_ref_iterator *iter =
497 (struct cache_ref_iterator *)ref_iterator;
459 - free((char *)iter->prefix);
498 + free(iter->prefix);
499 free(iter->levels);
500 }
501
502 static struct ref_iterator_vtable cache_ref_iterator_vtable = {
503 .advance = cache_ref_iterator_advance,
504 + .seek = cache_ref_iterator_seek,
505 .peel = cache_ref_iterator_peel,
506 .release = cache_ref_iterator_release,
507 };
@@ -471,39 +511,22 @@ struct ref_iterator *cache_ref_iterator_begin(struct ref_cache *cache,
511 struct repository *repo,
512 int prime_dir)
513 {
474 - struct ref_dir *dir;
514 struct cache_ref_iterator *iter;
515 struct ref_iterator *ref_iterator;
477 - struct cache_ref_iterator_level *level;
478 -
479 - dir = get_ref_dir(cache->root);
480 - if (prefix && *prefix)
481 - dir = find_containing_dir(dir, prefix);
482 - if (!dir)
483 - /* There's nothing to iterate over. */
484 - return empty_ref_iterator_begin();
485 -
486 - if (prime_dir)
487 - prime_ref_dir(dir, prefix);
516
517 CALLOC_ARRAY(iter, 1);
518 ref_iterator = &iter->base;
519 base_ref_iterator_init(ref_iterator, &cache_ref_iterator_vtable);
520 ALLOC_GROW(iter->levels, 10, iter->levels_alloc);
521
494 - iter->levels_nr = 1;
495 - level = &iter->levels[0];
496 - level->index = -1;
497 - level->dir = dir;
522 + iter->repo = repo;
523 + iter->cache = cache;
524 + iter->prime_dir = prime_dir;
525
499 - if (prefix && *prefix) {
500 - iter->prefix = xstrdup(prefix);
501 - level->prefix_state = PREFIX_WITHIN_DIR;
502 - } else {
503 - level->prefix_state = PREFIX_CONTAINS_DIR;
526 + if (cache_ref_iterator_seek(&iter->base, prefix) < 0) {
527 + ref_iterator_free(&iter->base);
528 + return NULL;
529 }
530
506 - iter->repo = repo;
507 -
531 return ref_iterator;
532 }