git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [PATCH 18/20] commit_packed_refs(): use reference iteration

From
Michael Haggerty <mhagger@alum.mit.edu>
Date
Mar 22, 2017, 08:42 UTC
Message-ID
<b49b4ae0-bcef-ce1d-62d5-f76a11e84766@alum.mit.edu>
In-Reply-To
<20170320180532.vxzra6tpf3t7qjks@sigill.intra.peff.net>
On 03/20/2017 07:05 PM, Jeff King wrote:
Show 40 quoted lines
> 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.

Previous: Jeff KingNext: Jeff King
Message 11 of 38 in “Separate `ref_cache` into a separate module”
  1. 00/20 Separate `ref_cache` into a separate moduleMichael Haggerty, Mar 20, 2017
  2. 01/20 get_ref_dir(): don't call read_loose_refs() for "refs/bisect"Michael Haggerty, Mar 20, 2017
  3. 10/20 ref-cache: introduce a new type, ref_cacheMichael Haggerty, Mar 20, 2017
  4. 13/20 refs: handle "refs/bisect/" in `loose_fill_ref_dir()`Michael Haggerty, Mar 20, 2017
  5. 11/20 refs: record the ref_store in ref_cache, not ref_dirMichael Haggerty, Mar 20, 2017
  6. Jeff KingMar 20, 2017
  7. Michael HaggertyMar 20, 2017
  8. 08/20 Rename `remove_entry()` to `remove_entry_from_dir()`Michael Haggerty, Mar 20, 2017
  9. 18/20 commit_packed_refs(): use reference iterationMichael Haggerty, Mar 20, 2017
  10. Jeff KingMar 20, 2017
  11. Michael HaggertyMar 22, 2017
  12. Jeff KingMar 22, 2017
  13. 20/20 do_for_each_entry_in_dir(): delete functionMichael Haggerty, Mar 20, 2017
  14. 16/20 get_loose_ref_cache(): new functionMichael Haggerty, Mar 20, 2017
  15. 15/20 get_loose_ref_dir(): function renamed from get_loose_refs()Michael Haggerty, Mar 20, 2017
  16. 19/20 files_pack_refs(): use reference iterationMichael Haggerty, Mar 20, 2017
  17. 14/20 do_for_each_entry_in_dir(): eliminate `offset` argumentMichael Haggerty, Mar 20, 2017
  18. 03/20 refs_ref_iterator_begin(): new functionMichael Haggerty, Mar 20, 2017
  19. 17/20 cache_ref_iterator_begin(): make function smarterMichael Haggerty, Mar 20, 2017
  20. 07/20 Rename `find_ref()` to `find_ref_entry()`Michael Haggerty, Mar 20, 2017
  21. 12/20 ref-cache: use a callback function to fill the cacheMichael Haggerty, Mar 20, 2017
  22. 09/20 refs: split `ref_cache` code into separate filesMichael Haggerty, Mar 20, 2017
  23. Jeff KingMar 20, 2017
  24. Junio C HamanoMar 20, 2017
  25. Stefan BellerMar 20, 2017
  26. Junio C HamanoMar 20, 2017
  27. 04/20 refs_verify_refname_available(): implement once for all backendsMichael Haggerty, Mar 20, 2017
  28. Jeff KingMar 20, 2017
  29. Michael HaggertyMar 20, 2017
  30. 02/20 refs_read_raw_ref(): new functionMichael Haggerty, Mar 20, 2017
  31. 06/20 Rename `add_ref()` to `add_ref_entry()`Michael Haggerty, Mar 20, 2017
  32. 05/20 refs_verify_refname_available(): use function in more placesMichael Haggerty, Mar 20, 2017
  33. Junio C HamanoMar 20, 2017
  34. Jeff KingMar 20, 2017
  35. Ævar Arnfjörð BjarmasonMar 20, 2017
  36. Jeff KingMar 20, 2017
  37. Junio C HamanoMar 20, 2017
  38. Michael HaggertyMar 20, 2017

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.