ref-filter.c: use peeled tag for '*' format fields

In most builtins ('rev-parse <revision>^{}', 'show-ref --dereference'), "dereferencing" a tag refers to a recursive peel of the tag object. Unlike these cases, the dereferencing prefix ('*') in 'for-each-ref' format specifiers triggers only a single, non-recursive dereference of a given tag object. For most annotated tags, a single dereference is all that is needed to access the tag's associated commit or tree; "recursive" and "non-recursive" dereferencing are functionally equivalent in these cases. However, nested tags (annotated tags whose target is another annotated tag) dereferenced once return another tag, where a recursive dereference would return the commit or tree. Currently, if a user wants to filter & format refs and include information about a recursively-dereferenced tag, they can do so with something like 'cat-file --batch-check': git for-each-ref --format="%(objectname)^{} %(refname)" <pattern> | git cat-file --batch-check="%(objectname) %(rest)" But the combination of commands is inefficient. So, to improve the performance of this use case and align the defererencing behavior of 'for-each-ref' with that of other commands, update the ref formatting code to use the peeled tag (from 'peel_iterated_oid()') to populate '*' fields rather than the tag's immediate target object (from 'get_tagged_oid()'). Additionally, add a test to 't6300-for-each-ref' to verify new nested tag behavior and update 't6302-for-each-ref-filter.sh' to print the correct value for nested dereferenced fields. Signed-off-by: Victoria Dye <vdye@github.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Victoria Dye committed Nov 14, 2023 at 19:53 UTC 188782ecb1d326ff724e41a77ea8ccb72f21ad44
4 files changed +30 -13
Documentation/git-for-each-ref.txt
+2 -2
@@ -296,8 +296,8 @@ from the `committer` or `tagger` fields depending on the object type.
296 These are intended for working on a mix of annotated and lightweight tags.
297
298 For tag objects, a `fieldname` prefixed with an asterisk (`*`) expands to
299 -the `fieldname` value of object the tag points at, rather than that of the
300 -tag object itself.
299 +the `fieldname` value of the peeled object, rather than that of the tag
300 +object itself.
301
302 Fields that have name-email-date tuple as its value (`author`,
303 `committer`, and `tagger`) can be suffixed with `name`, `email`,
ref-filter.c
+4 -9
@@ -2424,17 +2424,12 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)
2424 return 0;
2425
2426 /*
2427 - * If it is a tag object, see if we use a value that derefs
2428 - * the object, and if we do grab the object it refers to.
2427 + * If it is a tag object, see if we use the peeled value. If we do,
2428 + * grab the peeled OID.
2429 */
2430 - oi_deref.oid = *get_tagged_oid((struct tag *)obj);
2430 + if (need_tagged && peel_iterated_oid(&obj->oid, &oi_deref.oid))
2431 + die("bad tag");
2432
2432 - /*
2433 - * NEEDSWORK: This derefs tag only once, which
2434 - * is good to deal with chains of trust, but
2435 - * is not consistent with what deref_tag() does
2436 - * which peels the onion to the core.
2437 - */
2433 return get_object(ref, 1, &obj, &oi_deref, err);
2434 }
2435
t/t6300-for-each-ref.sh
+22
@@ -1728,6 +1728,28 @@ test_expect_success 'git for-each-ref with non-existing refs' '
1728 test_must_be_empty actual
1729 '
1730
1731 +test_expect_success 'git for-each-ref with nested tags' '
1732 + git tag -am "Normal tag" nested/base HEAD &&
1733 + git tag -am "Nested tag" nested/nest1 refs/tags/nested/base &&
1734 + git tag -am "Double nested tag" nested/nest2 refs/tags/nested/nest1 &&
1735 +
1736 + head_oid="$(git rev-parse HEAD)" &&
1737 + base_tag_oid="$(git rev-parse refs/tags/nested/base)" &&
1738 + nest1_tag_oid="$(git rev-parse refs/tags/nested/nest1)" &&
1739 + nest2_tag_oid="$(git rev-parse refs/tags/nested/nest2)" &&
1740 +
1741 + cat >expect <<-EOF &&
1742 + refs/tags/nested/base $base_tag_oid tag $head_oid commit
1743 + refs/tags/nested/nest1 $nest1_tag_oid tag $head_oid commit
1744 + refs/tags/nested/nest2 $nest2_tag_oid tag $head_oid commit
1745 + EOF
1746 +
1747 + git for-each-ref \
1748 + --format="%(refname) %(objectname) %(objecttype) %(*objectname) %(*objecttype)" \
1749 + refs/tags/nested/ >actual &&
1750 + test_cmp expect actual
1751 +'
1752 +
1753 GRADE_FORMAT="%(signature:grade)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)"
1754 TRUSTLEVEL_FORMAT="%(signature:trustlevel)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)"
1755
t/t6302-for-each-ref-filter.sh
+2 -2
@@ -45,8 +45,8 @@ test_expect_success 'check signed tags with --points-at' '
45 sed -e "s/Z$//" >expect <<-\EOF &&
46 refs/heads/side Z
47 refs/tags/annotated-tag four
48 - refs/tags/doubly-annotated-tag An annotated tag
49 - refs/tags/doubly-signed-tag A signed tag
48 + refs/tags/doubly-annotated-tag four
49 + refs/tags/doubly-signed-tag four
50 refs/tags/four Z
51 refs/tags/signed-tag four
52 EOF