packed_ref_iterator_begin(): iterate using `mmapped_ref_iterator`

Now that we have an efficient way to iterate, in order, over the mmapped contents of the `packed-refs` file, we can use that directly to implement reference iteration for the `packed_ref_store`, rather than iterating over the `ref_cache`. This is the next step towards getting rid of the `ref_cache` entirely. Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Michael Haggerty committed Sep 25, 2017 at 10:00 UTC d1cf15516feb024f34ae5fbbdad8f538b4e62fce
1 file changed +106 -3
refs/packed-backend.c
+106 -3
@@ -397,6 +397,27 @@ static int cmp_packed_ref_entries(const void *v1, const void *v2)
397 }
398 }
399
400 +/*
401 + * Compare a packed-refs record pointed to by `rec` to the specified
402 + * NUL-terminated refname.
403 + */
404 +static int cmp_entry_to_refname(const char *rec, const char *refname)
405 +{
406 + const char *r1 = rec + GIT_SHA1_HEXSZ + 1;
407 + const char *r2 = refname;
408 +
409 + while (1) {
410 + if (*r1 == '\n')
411 + return *r2 ? -1 : 0;
412 + if (!*r2)
413 + return 1;
414 + if (*r1 != *r2)
415 + return (unsigned char)*r1 < (unsigned char)*r2 ? -1 : +1;
416 + r1++;
417 + r2++;
418 + }
419 +}
420 +
421 /*
422 * `packed_refs->buf` is not known to be sorted. Check whether it is,
423 * and if not, sort it into new memory and munmap/free the old
@@ -503,6 +524,17 @@ static const char *find_start_of_record(const char *buf, const char *p)
524 return p;
525 }
526
527 +/*
528 + * Return a pointer to the start of the record following the record
529 + * that contains `*p`. If none is found before `end`, return `end`.
530 + */
531 +static const char *find_end_of_record(const char *p, const char *end)
532 +{
533 + while (++p < end && (p[-1] != '\n' || p[0] == '^'))
534 + ;
535 + return p;
536 +}
537 +
538 /*
539 * We want to be able to compare mmapped reference records quickly,
540 * without totally parsing them. We can do so because the records are
@@ -592,6 +624,65 @@ static int load_contents(struct packed_ref_cache *packed_refs)
624 return 1;
625 }
626
627 +/*
628 + * Find the place in `cache->buf` where the start of the record for
629 + * `refname` starts. If `mustexist` is true and the reference doesn't
630 + * exist, then return NULL. If `mustexist` is false and the reference
631 + * doesn't exist, then return the point where that reference would be
632 + * inserted. In the latter mode, `refname` doesn't have to be a proper
633 + * reference name; for example, one could search for "refs/replace/"
634 + * to find the start of any replace references.
635 + *
636 + * The record is sought using a binary search, so `cache->buf` must be
637 + * sorted.
638 + */
639 +static const char *find_reference_location(struct packed_ref_cache *cache,
640 + const char *refname, int mustexist)
641 +{
642 + /*
643 + * This is not *quite* a garden-variety binary search, because
644 + * the data we're searching is made up of records, and we
645 + * always need to find the beginning of a record to do a
646 + * comparison. A "record" here is one line for the reference
647 + * itself and zero or one peel lines that start with '^'. Our
648 + * loop invariant is described in the next two comments.
649 + */
650 +
651 + /*
652 + * A pointer to the character at the start of a record whose
653 + * preceding records all have reference names that come
654 + * *before* `refname`.
655 + */
656 + const char *lo = cache->buf + cache->header_len;
657 +
658 + /*
659 + * A pointer to a the first character of a record whose
660 + * reference name comes *after* `refname`.
661 + */
662 + const char *hi = cache->eof;
663 +
664 + while (lo < hi) {
665 + const char *mid, *rec;
666 + int cmp;
667 +
668 + mid = lo + (hi - lo) / 2;
669 + rec = find_start_of_record(lo, mid);
670 + cmp = cmp_entry_to_refname(rec, refname);
671 + if (cmp < 0) {
672 + lo = find_end_of_record(mid, hi);
673 + } else if (cmp > 0) {
674 + hi = rec;
675 + } else {
676 + return rec;
677 + }
678 + }
679 +
680 + if (mustexist)
681 + return NULL;
682 + else
683 + return lo;
684 +}
685 +
686 /*
687 * Read from the `packed-refs` file into a newly-allocated
688 * `packed_ref_cache` and return it. The return value will already
@@ -888,6 +979,8 @@ static struct ref_iterator *packed_ref_iterator_begin(
979 const char *prefix, unsigned int flags)
980 {
981 struct packed_ref_store *refs;
982 + struct packed_ref_cache *packed_refs;
983 + const char *start;
984 struct packed_ref_iterator *iter;
985 struct ref_iterator *ref_iterator;
986 unsigned int required_flags = REF_STORE_READ;
@@ -905,13 +998,23 @@ static struct ref_iterator *packed_ref_iterator_begin(
998 * the packed-ref cache is up to date with what is on disk,
999 * and re-reads it if not.
1000 */
1001 + iter->cache = packed_refs = get_packed_ref_cache(refs);
1002 + acquire_packed_ref_cache(packed_refs);
1003
909 - iter->cache = get_packed_ref_cache(refs);
910 - acquire_packed_ref_cache(iter->cache);
911 - iter->iter0 = cache_ref_iterator_begin(iter->cache->cache, prefix, 0);
1004 + if (prefix && *prefix)
1005 + start = find_reference_location(packed_refs, prefix, 0);
1006 + else
1007 + start = packed_refs->buf + packed_refs->header_len;
1008 +
1009 + iter->iter0 = mmapped_ref_iterator_begin(
1010 + packed_refs, start, packed_refs->eof);
1011
1012 iter->flags = flags;
1013
1014 + if (prefix && *prefix)
1015 + /* Stop iteration after we've gone *past* prefix: */
1016 + ref_iterator = prefix_ref_iterator_begin(ref_iterator, prefix, 0);
1017 +
1018 return ref_iterator;
1019 }
1020