On 03/20/2017 07:05 PM, Jeff King wrote:
> On Mon, Mar 20, 2017 at 05:33:23PM +0100, Michael Haggerty wrote:
> 
>> -/*
>> - * An each_ref_entry_fn that writes the entry to a packed-refs file.
>> - */
>> -static int write_packed_entry_fn(struct ref_entry *entry, void *cb_data)
>> -{
>> -    enum peel_status peel_status = peel_entry(entry, 0);
>> -
>> -    if (peel_status != PEEL_PEELED && peel_status != PEEL_NON_TAG)
>> -            error("internal error: %s is not a valid packed reference!",
>> -                  entry->name);
>> -    write_packed_entry(cb_data, entry->name, entry->u.value.oid.hash,
>> -                       peel_status == PEEL_PEELED ?
>> -                       entry->u.value.peeled.hash : NULL);
>> -    return 0;
>> -}
> 
> This assertion goes away. It can't be moved into write_packed_entry()
> because the peel status is only known in the caller.
> 
> But here:
> 
>> @@ -1376,8 +1362,18 @@ static int commit_packed_refs(struct files_ref_store 
>> *refs)
>>              die_errno("unable to fdopen packed-refs descriptor");
>>  
>>      fprintf_or_die(out, "%s", PACKED_REFS_HEADER);
>> -    do_for_each_entry_in_dir(get_packed_ref_dir(packed_ref_cache),
>> -                             write_packed_entry_fn, out);
>> +
>> +    iter = cache_ref_iterator_begin(packed_ref_cache->cache, NULL, 0);
>> +    while ((ok = ref_iterator_advance(iter)) == ITER_OK) {
>> +            struct object_id peeled;
>> +            int peel_error = ref_iterator_peel(iter, &peeled);
>> +
>> +            write_packed_entry(out, iter->refname, iter->oid->hash,
>> +                               peel_error ? NULL : peeled.hash);
>> +    }
> 
> Should we be checking that peel_error is only PEELED or NON_TAG?

This is a good question, and it took me a while to figure out the whole
answer. At first I deleted this check without much thought because it
was just an internal consistency check that should never trigger. But
actually the story is more complicated than that.

Tl;dr: the old code is slightly wrong and I think the new code is correct.

First the superficial answer: we can't `peel_error` in
`commit_packed_refs()` as you suggested, because `ref_iterator_peel()`
doesn't return an `enum peel_status`. It only returns 0 on OK /  nonzero
for problems. A legitimate reference should never have a status
`PEEL_INVALID`. That status is meant for stale packed refs that are
hidden by more recent loose refs, which *can* legitimately point at
objects that have since been GCed. Since `ref_iterator_peel()` is
someday meant to be an exposed part of the API, I didn't want it to give
out more information than pass/fail [1]. Also, the reason for a peel
failure is not necessarily known accurately (information is lost when a
reference is packed then the packed-refs file is read).

So, when could the old error message have been emitted? It is when there
is an entry in the packed-ref cache that is not `REF_KNOWS_PEELED`, and
when the peel is attempted, the result is `PEEL_INVALID`,
`PEEL_IS_SYMREF`, or `PEEL_BROKEN`. Since a packed ref cannot be a
symref, `PEEL_IS_SYMREF` and `PEEL_BROKEN` can be ruled out. So we're
left with `PEEL_INVALID`.

How could an entry get into the packed-refs cache without
`REF_KNOWS_PEELED`? One of the following:

* It was read from a `packed-refs` file that didn't have the
`fully-peeled` attribute. In that case, we *don't want* to emit an
error, because the broken value is presumably masked by a loose version
of the same reference (which we just don't happen to be packing this
time). The old code incorrectly emits the error message in this case. It
was probably never reported as a bug because this scenario is rare.

* It was a loose reference that was just added to the packed ref cache
by `files_packed_refs()` via `pack_if_possible_fn()` in preparation for
being packed. The latter function refuses to pack a reference for which
`entry_resolves_to_object()` returns false, and otherwise calls
`peel_entry()` itself and checks the return value. So a reference added
this way should always be `REF_KNOWS_PEELED`.

Therefore, I think it is a good thing to remove this check. I'll improve
the commit message to make this story clearer.

Michael

[1] We could change this policy, for example by only documenting the
pass/fail return value externally, while distinguishing between the
types of failure when iterating internal to the module.

Reply via email to