{"thread":{"id":"64260","subject":"[PATCH 00/13] refs: improvements and fixes for peeling tags","startedAt":"2025-10-07T10:58:59Z","lastAt":"2025-11-04T23:54:48Z","messageCount":106,"participants":["Patrick Steinhardt","Karthik Nayak","Justin Tobler","Junio C Hamano","Taylor Blau","Kristoffer Haugsbakk","shejialuo","Jeff King","Toon Claes"],"isPatch":true,"patchVersion":1,"patchTotal":13},"messages":[{"id":"528065","messageId":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-0-916cc7c6886b@pks.im","threadId":"64260","inReplyTo":null,"subject":"[PATCH 00/13] refs: improvements and fixes for peeling tags","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-07T10:58:37Z","receivedAt":"2025-10-07T10:58:59Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\noriginally, all I wanted to do was the last patch: a small performance\noptimization that stops parsing objects in git-for-each-ref(1) unless we\nreally need to parse them. But that fix cause one specific test to fail,\nand only with the reftable backend. So this led me down the rabbit hole\nof tag peeling, ending up with this patch series.\n\nThe series is structured like follows:\n\n  - Patches 1 to 7 refactor our codebase so that we don't have the\n    `peel_iterated_object()` hack anymore. I just found it hard to\n    follow and thought it shouldn't be too hard to get rid of it.\n\n  - Patches 8 and 9 remove infrastructure that we don't need anymore\n    after the first couple of patches.\n\n  - Patches 10 to 12 fix a couple of issues with peeled tags that I\n    found. The underlying issue is that tags store both the tagged\n    object and their type, but this information may not match. We never\n    verify the actual object type though when allocating the tagged\n    object, so this only blows up much later.\n\n  - Patch 13 was my original motivation, a small performance\n    optimization.\n\nI'm not particularly fond of the patches 10 to 12. It feels more like\nplaying whack-a-mole, and I very much assume that there still are edge\ncases where we should properly verify the tagged object type. But\nchanging it in `parse_tag_buffer()` itself causes a bunch of tests to\nfail where we intentionally create such corrupted tags. So I didn't\nreally dare to touch that part, to be honest.\n\nIf anybody has suggestions for an alternative approach I'd be very open\nto it.\n\nThe topic is built on top of 45547b60ac (Merge branch 'master' of\nhttps://github.com/j6t/gitk, 2025-10-05). There is a merge conflict with\ntb/incremental-midx-part-3.1, which moves code from \"builtin/repack.c\"\ninto \"repack-*.c\".\n\nThe conflict can be solved by accepting \"builtin/repack.c\" from\ntb/incremental-midx-part-3.1 and adding the below patch to\n\"repack-midx.c\". I can also rebase on top of that series, but given that\nit is rather huge it may take a while before it lands.\n\nThanks!\n\nPatrick\n\ndiff --cc builtin/repack.c\nindex 873e21c35d,ad60c4290d..0000000000\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\ndiff --git a/repack-midx.c b/repack-midx.c\nindex 6f6202c5bc..74bdfa3a6e 100644\n--- a/repack-midx.c\n+++ b/repack-midx.c\n@@ -16,25 +16,23 @@ struct midx_snapshot_ref_data {\n \tint preferred;\n };\n \n-static int midx_snapshot_ref_one(const char *refname UNUSED,\n-\t\t\t\t const char *referent UNUSED,\n-\t\t\t\t const struct object_id *oid,\n-\t\t\t\t int flag UNUSED, void *_data)\n+static int midx_snapshot_ref_one(const struct reference *ref, void *_data)\n {\n \tstruct midx_snapshot_ref_data *data = _data;\n+\tconst struct object_id *maybe_peeled = ref->oid;\n \tstruct object_id peeled;\n \n-\tif (!peel_iterated_oid(data->repo, oid, &peeled))\n-\t\toid = &peeled;\n+\tif (!reference_get_peeled_oid(data->repo, ref, &peeled))\n+\t\tmaybe_peeled = &peeled;\n \n-\tif (oidset_insert(&data->seen, oid))\n+\tif (oidset_insert(&data->seen, maybe_peeled))\n \t\treturn 0; /* already seen */\n \n-\tif (odb_read_object_info(data->repo->objects, oid, NULL) != OBJ_COMMIT)\n+\tif (odb_read_object_info(data->repo->objects, maybe_peeled, NULL) != OBJ_COMMIT)\n \t\treturn 0;\n \n \tfprintf(data->f->fp, \"%s%s\\n\", data->preferred ? \"+\" : \"\",\n-\t\toid_to_hex(oid));\n+\t\toid_to_hex(maybe_peeled));\n \n \treturn 0;\n }\n\n---\nPatrick Steinhardt (13):\n      refs: introduce wrapper struct for `each_ref_fn`\n      refs: introduce `.ref` field for the base iterator\n      refs: refactor reference status flags\n      refs: expose peeled object ID via the iterator\n      upload-pack: convert to use `reference_get_peeled_oid()`\n      ref-filter: propagate peeled object ID\n      builtin/show-ref: convert to use `reference_get_peeled_oid()`\n      refs: drop `current_ref_iter` hack\n      refs: drop infrastructure to peel via iterators\n      object: add flag to `peel_object()` to verify object type\n      refs: don't store peeled object IDs for invalid tags\n      ref-filter: detect broken tags when dereferencing them\n      ref-filter: parse objects on demand\n\n bisect.c                    |  24 ++---\n builtin/bisect.c            |  17 +---\n builtin/checkout.c          |   6 +-\n builtin/describe.c          |  18 ++--\n builtin/fetch.c             |  13 +--\n builtin/fsck.c              |  33 +++---\n builtin/gc.c                |  15 ++-\n builtin/ls-remote.c         |   2 +-\n builtin/name-rev.c          |  17 ++--\n builtin/pack-objects.c      |  28 +++---\n builtin/receive-pack.c      |  13 ++-\n builtin/remote.c            |  44 ++++----\n builtin/repack.c            |  16 ++-\n builtin/replace.c           |  21 ++--\n builtin/rev-parse.c         |  12 +--\n builtin/show-branch.c       |  35 +++----\n builtin/show-ref.c          |  50 ++++-----\n builtin/submodule--helper.c |  10 +-\n builtin/tag.c               |   2 +-\n builtin/verify-tag.c        |   2 +-\n builtin/worktree.c          |   6 +-\n commit-graph.c              |  14 ++-\n delta-islands.c             |   9 +-\n fetch-pack.c                |  16 +--\n help.c                      |  10 +-\n http-backend.c              |  20 ++--\n log-tree.c                  |  24 ++---\n ls-refs.c                   |  36 ++++---\n midx-write.c                |  17 ++--\n negotiator/default.c        |   7 +-\n negotiator/skipping.c       |   7 +-\n notes.c                     |   8 +-\n object-name.c               |  10 +-\n object.c                    |  20 +++-\n object.h                    |  15 ++-\n pseudo-merge.c              |  21 ++--\n reachable.c                 |   9 +-\n ref-filter.c                | 239 ++++++++++++++++++++++++++++++--------------\n ref-filter.h                |   5 +-\n reflog.c                    |   9 +-\n refs.c                      |  85 +++++++++-------\n refs.h                      |  84 ++++++++++------\n refs/debug.c                |  17 +---\n refs/files-backend.c        |  71 +++++--------\n refs/iterator.c             |  73 +++-----------\n refs/packed-backend.c       |  72 +++++--------\n refs/ref-cache.c            |  18 +---\n refs/refs-internal.h        |  25 +----\n refs/reftable-backend.c     |  48 +++------\n remote.c                    |  27 +++--\n replace-object.c            |  16 ++-\n revision.c                  |  12 +--\n server-info.c               |  12 +--\n shallow.c                   |  16 +--\n submodule.c                 |  12 +--\n t/for-each-ref-tests.sh     |   4 +-\n t/helper/test-reach.c       |   2 +-\n t/helper/test-ref-store.c   |   5 +-\n t/pack-refs-tests.sh        |  32 ++++++\n t/t0610-reftable-basics.sh  |  28 ++++++\n tag.c                       |  12 ---\n tag.h                       |   1 -\n upload-pack.c               |  49 ++++-----\n walker.c                    |   8 +-\n worktree.c                  |  11 +-\n 65 files changed, 791 insertions(+), 829 deletions(-)\n\n\n---\nbase-commit: 45547b60aca32b45d2f1ef93462cf9df28637c13\nchange-id: 20250918-b4-pks-ref-filter-skip-parsing-objects-f0d1f6af4a9f\n\n"},{"id":"528066","messageId":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-1-916cc7c6886b@pks.im","threadId":"64260","inReplyTo":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-0-916cc7c6886b@pks.im","subject":"[PATCH 01/13] refs: introduce wrapper struct for `each_ref_fn`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-07T10:58:38Z","receivedAt":"2025-10-07T10:59:01Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `each_ref_fn` callback function type is used across our code base\nfor several different functions that iterate through reference. There's\na bunch of callbacks implementing this type, which makes any changes to\nthe callback signature extremely noisy. An example of the required churn\nis e8207717f1 (refs: add referent to each_ref_fn, 2024-08-09): adding a\nsingle argument required us to change 48 files.\n\nIt was already proposed back then [1] that we might want to introduce a\nwrapper structure to alleviate the pain going forward. While this of\ncourse requires the same kind of global refactoring as just introducing\na new parameter, it at least allows us to more change the callback type\nafterwards by just extending the wrapper structure.\n\nOne counterargument to this refactoring is that it makes the structure\nmore opaque. While it is obvious which callsites need to be fixed up\nwhen we change the function type, it's not obvious anymore once we use\na structure. That being said, we only have a handful of sites that\nactually need to populate this wrapper structure: our ref backends and\n\"refs/iterator.c\".\n\nIntroduce this wrapper structure so that we can adapt the iterator\ninterfaces more readily.\n\n[1]: <ZmarVcF5JjsZx0dl@tanuki>\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n bisect.c                    | 24 +++++++---------\n builtin/bisect.c            | 17 ++++--------\n builtin/checkout.c          |  6 ++--\n builtin/describe.c          | 18 ++++++------\n builtin/fetch.c             | 13 +++------\n builtin/fsck.c              | 33 ++++++++++++----------\n builtin/gc.c                | 15 ++++------\n builtin/name-rev.c          | 17 ++++++------\n builtin/pack-objects.c      | 27 ++++++++----------\n builtin/receive-pack.c      | 13 ++++-----\n builtin/remote.c            | 44 +++++++++++++----------------\n builtin/repack.c            | 16 +++++------\n builtin/replace.c           | 21 ++++++--------\n builtin/rev-parse.c         | 12 ++++----\n builtin/show-branch.c       | 35 +++++++++++------------\n builtin/show-ref.c          | 20 ++++++--------\n builtin/submodule--helper.c | 10 ++-----\n builtin/worktree.c          |  6 +---\n commit-graph.c              | 14 ++++------\n delta-islands.c             |  9 +++---\n fetch-pack.c                | 16 +++--------\n help.c                      | 10 +++----\n http-backend.c              | 20 ++++++--------\n log-tree.c                  | 24 ++++++++--------\n ls-refs.c                   | 36 ++++++++++++++----------\n midx-write.c                | 17 ++++++------\n negotiator/default.c        |  7 ++---\n negotiator/skipping.c       |  7 ++---\n notes.c                     |  8 ++----\n object-name.c               | 10 +++----\n pseudo-merge.c              | 21 ++++++--------\n reachable.c                 |  9 +++---\n ref-filter.c                | 24 ++++++++--------\n reflog.c                    |  9 ++----\n refs.c                      | 67 +++++++++++++++++++++++++--------------------\n refs.h                      | 22 +++++++++++++--\n refs/files-backend.c        |  7 ++---\n refs/iterator.c             |  9 +++++-\n remote.c                    | 27 ++++++++----------\n replace-object.c            | 16 ++++-------\n revision.c                  | 12 ++++----\n server-info.c               | 12 ++++----\n shallow.c                   | 16 +++--------\n submodule.c                 | 12 ++------\n t/helper/test-ref-store.c   |  5 ++--\n upload-pack.c               | 29 +++++++++-----------\n walker.c                    |  8 ++----\n worktree.c                  | 11 ++++++--\n 48 files changed, 387 insertions(+), 454 deletions(-)\n\ndiff --git a/bisect.c b/bisect.c\nindex a6dc76b15c9..326b59c0dc7 100644\n--- a/bisect.c\n+++ b/bisect.c\n@@ -450,21 +450,20 @@ void find_bisection(struct commit_list **commit_list, int *reaches,\n \tclear_commit_weight(&commit_weight);\n }\n \n-static int register_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\tint flags UNUSED, void *cb_data UNUSED)\n+static int register_ref(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct strbuf good_prefix = STRBUF_INIT;\n \tstrbuf_addstr(&good_prefix, term_good);\n \tstrbuf_addstr(&good_prefix, \"-\");\n \n-\tif (!strcmp(refname, term_bad)) {\n+\tif (!strcmp(ref->name, term_bad)) {\n \t\tfree(current_bad_oid);\n \t\tcurrent_bad_oid = xmalloc(sizeof(*current_bad_oid));\n-\t\toidcpy(current_bad_oid, oid);\n-\t} else if (starts_with(refname, good_prefix.buf)) {\n-\t\toid_array_append(&good_revs, oid);\n-\t} else if (starts_with(refname, \"skip-\")) {\n-\t\toid_array_append(&skipped_revs, oid);\n+\t\toidcpy(current_bad_oid, ref->oid);\n+\t} else if (starts_with(ref->name, good_prefix.buf)) {\n+\t\toid_array_append(&good_revs, ref->oid);\n+\t} else if (starts_with(ref->name, \"skip-\")) {\n+\t\toid_array_append(&skipped_revs, ref->oid);\n \t}\n \n \tstrbuf_release(&good_prefix);\n@@ -1178,14 +1177,11 @@ int estimate_bisect_steps(int all)\n \treturn (e < 3 * x) ? n : n - 1;\n }\n \n-static int mark_for_removal(const char *refname,\n-\t\t\t    const char *referent UNUSED,\n-\t\t\t    const struct object_id *oid UNUSED,\n-\t\t\t    int flag UNUSED, void *cb_data)\n+static int mark_for_removal(const struct reference *ref, void *cb_data)\n {\n \tstruct string_list *refs = cb_data;\n-\tchar *ref = xstrfmt(\"refs/bisect%s\", refname);\n-\tstring_list_append(refs, ref);\n+\tchar *bisect_ref = xstrfmt(\"refs/bisect%s\", ref->name);\n+\tstring_list_append(refs, bisect_ref);\n \treturn 0;\n }\n \ndiff --git a/builtin/bisect.c b/builtin/bisect.c\nindex 8b8d870cd1e..5b2024be62d 100644\n--- a/builtin/bisect.c\n+++ b/builtin/bisect.c\n@@ -358,10 +358,7 @@ static int check_and_set_terms(struct bisect_terms *terms, const char *cmd)\n \treturn 0;\n }\n \n-static int inc_nr(const char *refname UNUSED,\n-\t\t  const char *referent UNUSED,\n-\t\t  const struct object_id *oid UNUSED,\n-\t\t  int flag UNUSED, void *cb_data)\n+static int inc_nr(const struct reference *ref UNUSED, void *cb_data)\n {\n \tunsigned int *nr = (unsigned int *)cb_data;\n \t(*nr)++;\n@@ -549,12 +546,11 @@ static int bisect_append_log_quoted(const char **argv)\n \treturn res;\n }\n \n-static int add_bisect_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t  int flags UNUSED, void *cb)\n+static int add_bisect_ref(const struct reference *ref, void *cb)\n {\n \tstruct add_bisect_ref_data *data = cb;\n \n-\tadd_pending_oid(data->revs, refname, oid, data->object_flags);\n+\tadd_pending_oid(data->revs, ref->name, ref->oid, data->object_flags);\n \n \treturn 0;\n }\n@@ -1165,12 +1161,9 @@ static int bisect_visualize(struct bisect_terms *terms, int argc,\n \treturn run_command(&cmd);\n }\n \n-static int get_first_good(const char *refname UNUSED,\n-\t\t\t  const char *referent UNUSED,\n-\t\t\t  const struct object_id *oid,\n-\t\t\t  int flag UNUSED, void *cb_data)\n+static int get_first_good(const struct reference *ref, void *cb_data)\n {\n-\toidcpy(cb_data, oid);\n+\toidcpy(cb_data, ref->oid);\n \treturn 1;\n }\n \ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex f9453473fe2..66b69df6e67 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1063,11 +1063,9 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\treport_tracking(new_branch_info);\n }\n \n-static int add_pending_uninteresting_ref(const char *refname, const char *referent UNUSED,\n-\t\t\t\t\t const struct object_id *oid,\n-\t\t\t\t\t int flags UNUSED, void *cb_data)\n+static int add_pending_uninteresting_ref(const struct reference *ref, void *cb_data)\n {\n-\tadd_pending_oid(cb_data, refname, oid, UNINTERESTING);\n+\tadd_pending_oid(cb_data, ref->name, ref->oid, UNINTERESTING);\n \treturn 0;\n }\n \ndiff --git a/builtin/describe.c b/builtin/describe.c\nindex ffaf8d9f0aa..79545350443 100644\n--- a/builtin/describe.c\n+++ b/builtin/describe.c\n@@ -154,20 +154,19 @@ static void add_to_known_names(const char *path,\n \t}\n }\n \n-static int get_name(const char *path, const char *referent UNUSED, const struct object_id *oid,\n-\t\t    int flag UNUSED, void *cb_data UNUSED)\n+static int get_name(const struct reference *ref, void *cb_data UNUSED)\n {\n \tint is_tag = 0;\n \tstruct object_id peeled;\n \tint is_annotated, prio;\n \tconst char *path_to_match = NULL;\n \n-\tif (skip_prefix(path, \"refs/tags/\", &path_to_match)) {\n+\tif (skip_prefix(ref->name, \"refs/tags/\", &path_to_match)) {\n \t\tis_tag = 1;\n \t} else if (all) {\n \t\tif ((exclude_patterns.nr || patterns.nr) &&\n-\t\t    !skip_prefix(path, \"refs/heads/\", &path_to_match) &&\n-\t\t    !skip_prefix(path, \"refs/remotes/\", &path_to_match)) {\n+\t\t    !skip_prefix(ref->name, \"refs/heads/\", &path_to_match) &&\n+\t\t    !skip_prefix(ref->name, \"refs/remotes/\", &path_to_match)) {\n \t\t\t/* Only accept reference of known type if there are match/exclude patterns */\n \t\t\treturn 0;\n \t\t}\n@@ -209,10 +208,10 @@ static int get_name(const char *path, const char *referent UNUSED, const struct\n \t}\n \n \t/* Is it annotated? */\n-\tif (!peel_iterated_oid(the_repository, oid, &peeled)) {\n-\t\tis_annotated = !oideq(oid, &peeled);\n+\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled)) {\n+\t\tis_annotated = !oideq(ref->oid, &peeled);\n \t} else {\n-\t\toidcpy(&peeled, oid);\n+\t\toidcpy(&peeled, ref->oid);\n \t\tis_annotated = 0;\n \t}\n \n@@ -229,7 +228,8 @@ static int get_name(const char *path, const char *referent UNUSED, const struct\n \telse\n \t\tprio = 0;\n \n-\tadd_to_known_names(all ? path + 5 : path + 10, &peeled, prio, oid);\n+\tadd_to_known_names(all ? ref->name + 5 : ref->name + 10,\n+\t\t\t   &peeled, prio, ref->oid);\n \treturn 0;\n }\n \ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex c7ff3480fb1..7052e6ff215 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -289,13 +289,11 @@ static struct refname_hash_entry *refname_hash_add(struct hashmap *map,\n \treturn ent;\n }\n \n-static int add_one_refname(const char *refname, const char *referent UNUSED,\n-\t\t\t   const struct object_id *oid,\n-\t\t\t   int flag UNUSED, void *cbdata)\n+static int add_one_refname(const struct reference *ref, void *cbdata)\n {\n \tstruct hashmap *refname_map = cbdata;\n \n-\t(void) refname_hash_add(refname_map, refname, oid);\n+\t(void) refname_hash_add(refname_map, ref->name, ref->oid);\n \treturn 0;\n }\n \n@@ -1416,14 +1414,11 @@ static void set_option(struct transport *transport, const char *name, const char\n }\n \n \n-static int add_oid(const char *refname UNUSED,\n-\t\t   const char *referent UNUSED,\n-\t\t   const struct object_id *oid,\n-\t\t   int flags UNUSED, void *cb_data)\n+static int add_oid(const struct reference *ref, void *cb_data)\n {\n \tstruct oid_array *oids = cb_data;\n \n-\toid_array_append(oids, oid);\n+\toid_array_append(oids, ref->oid);\n \treturn 0;\n }\n \ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex d2eb9d4fbe9..2b8fa22cccb 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -530,14 +530,13 @@ static int fsck_handle_reflog(const char *logname, void *cb_data)\n \treturn 0;\n }\n \n-static int fsck_handle_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t   int flag UNUSED, void *cb_data UNUSED)\n+static int fsck_handle_ref(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct object *obj;\n \n-\tobj = parse_object(the_repository, oid);\n+\tobj = parse_object(the_repository, ref->oid);\n \tif (!obj) {\n-\t\tif (is_promisor_object(the_repository, oid)) {\n+\t\tif (is_promisor_object(the_repository, ref->oid)) {\n \t\t\t/*\n \t\t\t * Increment default_refs anyway, because this is a\n \t\t\t * valid ref.\n@@ -546,19 +545,19 @@ static int fsck_handle_ref(const char *refname, const char *referent UNUSED, con\n \t\t\t return 0;\n \t\t}\n \t\terror(_(\"%s: invalid sha1 pointer %s\"),\n-\t\t      refname, oid_to_hex(oid));\n+\t\t      ref->name, oid_to_hex(ref->oid));\n \t\terrors_found |= ERROR_REACHABLE;\n \t\t/* We'll continue with the rest despite the error.. */\n \t\treturn 0;\n \t}\n-\tif (obj->type != OBJ_COMMIT && is_branch(refname)) {\n-\t\terror(_(\"%s: not a commit\"), refname);\n+\tif (obj->type != OBJ_COMMIT && is_branch(ref->name)) {\n+\t\terror(_(\"%s: not a commit\"), ref->name);\n \t\terrors_found |= ERROR_REFS;\n \t}\n \tdefault_refs++;\n \tobj->flags |= USED;\n \tfsck_put_object_name(&fsck_walk_options,\n-\t\t\t     oid, \"%s\", refname);\n+\t\t\t     ref->oid, \"%s\", ref->name);\n \tmark_object_reachable(obj);\n \n \treturn 0;\n@@ -580,13 +579,19 @@ static void get_default_heads(void)\n \tworktrees = get_worktrees();\n \tfor (p = worktrees; *p; p++) {\n \t\tstruct worktree *wt = *p;\n-\t\tstruct strbuf ref = STRBUF_INIT;\n+\t\tstruct strbuf refname = STRBUF_INIT;\n \n-\t\tstrbuf_worktree_ref(wt, &ref, \"HEAD\");\n-\t\tfsck_head_link(ref.buf, &head_points_at, &head_oid);\n-\t\tif (head_points_at && !is_null_oid(&head_oid))\n-\t\t\tfsck_handle_ref(ref.buf, NULL, &head_oid, 0, NULL);\n-\t\tstrbuf_release(&ref);\n+\t\tstrbuf_worktree_ref(wt, &refname, \"HEAD\");\n+\t\tfsck_head_link(refname.buf, &head_points_at, &head_oid);\n+\t\tif (head_points_at && !is_null_oid(&head_oid)) {\n+\t\t\tstruct reference ref = {\n+\t\t\t\t.name = refname.buf,\n+\t\t\t\t.oid = &head_oid,\n+\t\t\t};\n+\n+\t\t\tfsck_handle_ref(&ref, NULL);\n+\t\t}\n+\t\tstrbuf_release(&refname);\n \n \t\tif (include_reflogs)\n \t\t\trefs_for_each_reflog(get_worktree_ref_store(wt),\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 03ae4926b20..a104c1cb782 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -1098,24 +1098,21 @@ struct cg_auto_data {\n \tint limit;\n };\n \n-static int dfs_on_ref(const char *refname UNUSED,\n-\t\t      const char *referent UNUSED,\n-\t\t      const struct object_id *oid,\n-\t\t      int flags UNUSED,\n-\t\t      void *cb_data)\n+static int dfs_on_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct cg_auto_data *data = (struct cg_auto_data *)cb_data;\n \tint result = 0;\n+\tconst struct object_id *maybe_peeled = ref->oid;\n \tstruct object_id peeled;\n \tstruct commit_list *stack = NULL;\n \tstruct commit *commit;\n \n-\tif (!peel_iterated_oid(the_repository, oid, &peeled))\n-\t\toid = &peeled;\n-\tif (odb_read_object_info(the_repository->objects, oid, NULL) != OBJ_COMMIT)\n+\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n+\t\tmaybe_peeled = &peeled;\n+\tif (odb_read_object_info(the_repository->objects, maybe_peeled, NULL) != OBJ_COMMIT)\n \t\treturn 0;\n \n-\tcommit = lookup_commit(the_repository, oid);\n+\tcommit = lookup_commit(the_repository, maybe_peeled);\n \tif (!commit)\n \t\treturn 0;\n \tif (repo_parse_commit(the_repository, commit) ||\ndiff --git a/builtin/name-rev.c b/builtin/name-rev.c\nindex 74512e54a38..615f7d1aae4 100644\n--- a/builtin/name-rev.c\n+++ b/builtin/name-rev.c\n@@ -339,10 +339,9 @@ static int cmp_by_tag_and_age(const void *a_, const void *b_)\n \treturn a->taggerdate != b->taggerdate;\n }\n \n-static int name_ref(const char *path, const char *referent UNUSED, const struct object_id *oid,\n-\t\t    int flags UNUSED, void *cb_data)\n+static int name_ref(const struct reference *ref, void *cb_data)\n {\n-\tstruct object *o = parse_object(the_repository, oid);\n+\tstruct object *o = parse_object(the_repository, ref->oid);\n \tstruct name_ref_data *data = cb_data;\n \tint can_abbreviate_output = data->tags_only && data->name_only;\n \tint deref = 0;\n@@ -350,14 +349,14 @@ static int name_ref(const char *path, const char *referent UNUSED, const struct\n \tstruct commit *commit = NULL;\n \ttimestamp_t taggerdate = TIME_MAX;\n \n-\tif (data->tags_only && !starts_with(path, \"refs/tags/\"))\n+\tif (data->tags_only && !starts_with(ref->name, \"refs/tags/\"))\n \t\treturn 0;\n \n \tif (data->exclude_filters.nr) {\n \t\tstruct string_list_item *item;\n \n \t\tfor_each_string_list_item(item, &data->exclude_filters) {\n-\t\t\tif (subpath_matches(path, item->string) >= 0)\n+\t\t\tif (subpath_matches(ref->name, item->string) >= 0)\n \t\t\t\treturn 0;\n \t\t}\n \t}\n@@ -378,7 +377,7 @@ static int name_ref(const char *path, const char *referent UNUSED, const struct\n \t\t\t * shouldn't stop when seeing 'refs/tags/v1.4' matches\n \t\t\t * 'refs/tags/v*'.  We should show it as 'v1.4'.\n \t\t\t */\n-\t\t\tswitch (subpath_matches(path, item->string)) {\n+\t\t\tswitch (subpath_matches(ref->name, item->string)) {\n \t\t\tcase -1: /* did not match */\n \t\t\t\tbreak;\n \t\t\tcase 0: /* matched fully */\n@@ -406,13 +405,13 @@ static int name_ref(const char *path, const char *referent UNUSED, const struct\n \t}\n \tif (o && o->type == OBJ_COMMIT) {\n \t\tcommit = (struct commit *)o;\n-\t\tfrom_tag = starts_with(path, \"refs/tags/\");\n+\t\tfrom_tag = starts_with(ref->name, \"refs/tags/\");\n \t\tif (taggerdate == TIME_MAX)\n \t\t\ttaggerdate = commit->date;\n \t}\n \n-\tadd_to_tip_table(oid, path, can_abbreviate_output, commit, taggerdate,\n-\t\t\t from_tag, deref);\n+\tadd_to_tip_table(ref->oid, ref->name, can_abbreviate_output,\n+\t\t\t commit, taggerdate, from_tag, deref);\n \treturn 0;\n }\n \ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 5856b5f6bfb..288ba199811 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -831,15 +831,14 @@ static enum write_one_status write_one(struct hashfile *f,\n \treturn WRITE_ONE_WRITTEN;\n }\n \n-static int mark_tagged(const char *path UNUSED, const char *referent UNUSED, const struct object_id *oid,\n-\t\t       int flag UNUSED, void *cb_data UNUSED)\n+static int mark_tagged(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct object_id peeled;\n-\tstruct object_entry *entry = packlist_find(&to_pack, oid);\n+\tstruct object_entry *entry = packlist_find(&to_pack, ref->oid);\n \n \tif (entry)\n \t\tentry->tagged = 1;\n-\tif (!peel_iterated_oid(the_repository, oid, &peeled)) {\n+\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled)) {\n \t\tentry = packlist_find(&to_pack, &peeled);\n \t\tif (entry)\n \t\t\tentry->tagged = 1;\n@@ -3306,13 +3305,12 @@ static void add_tag_chain(const struct object_id *oid)\n \t}\n }\n \n-static int add_ref_tag(const char *tag UNUSED, const char *referent UNUSED, const struct object_id *oid,\n-\t\t       int flag UNUSED, void *cb_data UNUSED)\n+static int add_ref_tag(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct object_id peeled;\n \n-\tif (!peel_iterated_oid(the_repository, oid, &peeled) && obj_is_packed(&peeled))\n-\t\tadd_tag_chain(oid);\n+\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled) && obj_is_packed(&peeled))\n+\t\tadd_tag_chain(ref->oid);\n \treturn 0;\n }\n \n@@ -4528,19 +4526,16 @@ static void record_recent_commit(struct commit *commit, void *data UNUSED)\n \toid_array_append(&recent_objects, &commit->object.oid);\n }\n \n-static int mark_bitmap_preferred_tip(const char *refname,\n-\t\t\t\t     const char *referent UNUSED,\n-\t\t\t\t     const struct object_id *oid,\n-\t\t\t\t     int flags UNUSED,\n-\t\t\t\t     void *data UNUSED)\n+static int mark_bitmap_preferred_tip(const struct reference *ref, void *data UNUSED)\n {\n+\tconst struct object_id *maybe_peeled = ref->oid;\n \tstruct object_id peeled;\n \tstruct object *object;\n \n-\tif (!peel_iterated_oid(the_repository, oid, &peeled))\n-\t\toid = &peeled;\n+\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n+\t\tmaybe_peeled = &peeled;\n \n-\tobject = parse_object_or_die(the_repository, oid, refname);\n+\tobject = parse_object_or_die(the_repository, maybe_peeled, ref->name);\n \tif (object->type == OBJ_COMMIT)\n \t\tobject->flags |= NEEDS_BITMAP;\n \ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 1113137a6f0..2a45c0867e3 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -305,13 +305,12 @@ static void show_ref(const char *path, const struct object_id *oid)\n \t}\n }\n \n-static int show_ref_cb(const char *path_full, const char *referent UNUSED, const struct object_id *oid,\n-\t\t       int flag UNUSED, void *data)\n+static int show_ref_cb(const struct reference *ref, void *data)\n {\n \tstruct oidset *seen = data;\n-\tconst char *path = strip_namespace(path_full);\n+\tconst char *path = strip_namespace(ref->name);\n \n-\tif (ref_is_hidden(path, path_full, &hidden_refs))\n+\tif (ref_is_hidden(path, ref->name, &hidden_refs))\n \t\treturn 0;\n \n \t/*\n@@ -320,13 +319,13 @@ static int show_ref_cb(const char *path_full, const char *referent UNUSED, const\n \t * transfer but will otherwise ignore them.\n \t */\n \tif (!path) {\n-\t\tif (oidset_insert(seen, oid))\n+\t\tif (oidset_insert(seen, ref->oid))\n \t\t\treturn 0;\n \t\tpath = \".have\";\n \t} else {\n-\t\toidset_insert(seen, oid);\n+\t\toidset_insert(seen, ref->oid);\n \t}\n-\tshow_ref(path, oid);\n+\tshow_ref(path, ref->oid);\n \treturn 0;\n }\n \ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 8a7ed4299a4..7ffc14ba157 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -570,17 +570,14 @@ struct branches_for_remote {\n \tstruct known_remotes *keep;\n };\n \n-static int add_branch_for_removal(const char *refname,\n-\t\t\t\t  const char *referent UNUSED,\n-\t\t\t\t  const struct object_id *oid UNUSED,\n-\t\t\t\t  int flags UNUSED, void *cb_data)\n+static int add_branch_for_removal(const struct reference *ref, void *cb_data)\n {\n \tstruct branches_for_remote *branches = cb_data;\n \tstruct refspec_item refspec;\n \tstruct known_remote *kr;\n \n \tmemset(&refspec, 0, sizeof(refspec));\n-\trefspec.dst = (char *)refname;\n+\trefspec.dst = (char *)ref->name;\n \tif (remote_find_tracking(branches->remote, &refspec))\n \t\treturn 0;\n \tfree(refspec.src);\n@@ -588,7 +585,7 @@ static int add_branch_for_removal(const char *refname,\n \t/* don't delete a branch if another remote also uses it */\n \tfor (kr = branches->keep->list; kr; kr = kr->next) {\n \t\tmemset(&refspec, 0, sizeof(refspec));\n-\t\trefspec.dst = (char *)refname;\n+\t\trefspec.dst = (char *)ref->name;\n \t\tif (!remote_find_tracking(kr->remote, &refspec)) {\n \t\t\tfree(refspec.src);\n \t\t\treturn 0;\n@@ -596,16 +593,16 @@ static int add_branch_for_removal(const char *refname,\n \t}\n \n \t/* don't delete non-remote-tracking refs */\n-\tif (!starts_with(refname, \"refs/remotes/\")) {\n+\tif (!starts_with(ref->name, \"refs/remotes/\")) {\n \t\t/* advise user how to delete local branches */\n-\t\tif (starts_with(refname, \"refs/heads/\"))\n+\t\tif (starts_with(ref->name, \"refs/heads/\"))\n \t\t\tstring_list_append(branches->skipped,\n-\t\t\t\t\t   abbrev_branch(refname));\n+\t\t\t\t\t   abbrev_branch(ref->name));\n \t\t/* silently skip over other non-remote refs */\n \t\treturn 0;\n \t}\n \n-\tstring_list_append(branches->branches, refname);\n+\tstring_list_append(branches->branches, ref->name);\n \n \treturn 0;\n }\n@@ -713,18 +710,18 @@ static int rename_one_reflog(const char *old_refname,\n \treturn error;\n }\n \n-static int rename_one_ref(const char *old_refname, const char *referent,\n-\t\t\t  const struct object_id *oid,\n-\t\t\t  int flags, void *cb_data)\n+static int rename_one_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct strbuf new_referent = STRBUF_INIT;\n \tstruct strbuf new_refname = STRBUF_INIT;\n \tstruct rename_info *rename = cb_data;\n+\tconst struct object_id *oid = ref->oid;\n+\tconst char *referent = ref->target;\n \tint error;\n \n-\tcompute_renamed_ref(rename, old_refname, &new_refname);\n+\tcompute_renamed_ref(rename, ref->name, &new_refname);\n \n-\tif (flags & REF_ISSYMREF) {\n+\tif (ref->flags & REF_ISSYMREF) {\n \t\t/*\n \t\t * Stupidly enough `referent` is not pointing to the immediate\n \t\t * target of a symref, but it's the recursively resolved value.\n@@ -732,25 +729,25 @@ static int rename_one_ref(const char *old_refname, const char *referent,\n \t\t * unborn symrefs don't have any value for the `referent` at all.\n \t\t */\n \t\treferent = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),\n-\t\t\t\t\t\t   old_refname, RESOLVE_REF_NO_RECURSE,\n+\t\t\t\t\t\t   ref->name, RESOLVE_REF_NO_RECURSE,\n \t\t\t\t\t\t   NULL, NULL);\n \t\tcompute_renamed_ref(rename, referent, &new_referent);\n \t\toid = NULL;\n \t}\n \n-\terror = ref_transaction_delete(rename->transaction, old_refname,\n+\terror = ref_transaction_delete(rename->transaction, ref->name,\n \t\t\t\t       oid, referent, REF_NO_DEREF, NULL, rename->err);\n \tif (error < 0)\n \t\tgoto out;\n \n \terror = ref_transaction_update(rename->transaction, new_refname.buf, oid, null_oid(the_hash_algo),\n-\t\t\t\t       (flags & REF_ISSYMREF) ? new_referent.buf : NULL, NULL,\n+\t\t\t\t       (ref->flags & REF_ISSYMREF) ? new_referent.buf : NULL, NULL,\n \t\t\t\t       REF_SKIP_CREATE_REFLOG | REF_NO_DEREF | REF_SKIP_OID_VERIFICATION,\n \t\t\t\t       NULL, rename->err);\n \tif (error < 0)\n \t\tgoto out;\n \n-\terror = rename_one_reflog(old_refname, oid, rename);\n+\terror = rename_one_reflog(ref->name, oid, rename);\n \tif (error < 0)\n \t\tgoto out;\n \n@@ -1125,19 +1122,16 @@ static void free_remote_ref_states(struct ref_states *states)\n \tstring_list_clear_func(&states->push, clear_push_info);\n }\n \n-static int append_ref_to_tracked_list(const char *refname,\n-\t\t\t\t      const char *referent UNUSED,\n-\t\t\t\t      const struct object_id *oid UNUSED,\n-\t\t\t\t      int flags, void *cb_data)\n+static int append_ref_to_tracked_list(const struct reference *ref, void *cb_data)\n {\n \tstruct ref_states *states = cb_data;\n \tstruct refspec_item refspec;\n \n-\tif (flags & REF_ISSYMREF)\n+\tif (ref->flags & REF_ISSYMREF)\n \t\treturn 0;\n \n \tmemset(&refspec, 0, sizeof(refspec));\n-\trefspec.dst = (char *)refname;\n+\trefspec.dst = (char *)ref->name;\n \tif (!remote_find_tracking(states->remote, &refspec)) {\n \t\tstring_list_append(&states->tracked, abbrev_branch(refspec.src));\n \t\tfree(refspec.src);\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex c490a51e919..893c105c81f 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -773,25 +773,23 @@ struct midx_snapshot_ref_data {\n \tint preferred;\n };\n \n-static int midx_snapshot_ref_one(const char *refname UNUSED,\n-\t\t\t\t const char *referent UNUSED,\n-\t\t\t\t const struct object_id *oid,\n-\t\t\t\t int flag UNUSED, void *_data)\n+static int midx_snapshot_ref_one(const struct reference *ref, void *_data)\n {\n \tstruct midx_snapshot_ref_data *data = _data;\n+\tconst struct object_id *maybe_peeled = ref->oid;\n \tstruct object_id peeled;\n \n-\tif (!peel_iterated_oid(the_repository, oid, &peeled))\n-\t\toid = &peeled;\n+\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n+\t\tmaybe_peeled = &peeled;\n \n-\tif (oidset_insert(&data->seen, oid))\n+\tif (oidset_insert(&data->seen, maybe_peeled))\n \t\treturn 0; /* already seen */\n \n-\tif (odb_read_object_info(the_repository->objects, oid, NULL) != OBJ_COMMIT)\n+\tif (odb_read_object_info(the_repository->objects, maybe_peeled, NULL) != OBJ_COMMIT)\n \t\treturn 0;\n \n \tfprintf(data->f->fp, \"%s%s\\n\", data->preferred ? \"+\" : \"\",\n-\t\toid_to_hex(oid));\n+\t\toid_to_hex(maybe_peeled));\n \n \treturn 0;\n }\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 900b560a77d..4c62c5ab58b 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -47,30 +47,27 @@ struct show_data {\n \tenum replace_format format;\n };\n \n-static int show_reference(const char *refname,\n-\t\t\t  const char *referent UNUSED,\n-\t\t\t  const struct object_id *oid,\n-\t\t\t  int flag UNUSED, void *cb_data)\n+static int show_reference(const struct reference *ref, void *cb_data)\n {\n \tstruct show_data *data = cb_data;\n \n-\tif (!wildmatch(data->pattern, refname, 0)) {\n+\tif (!wildmatch(data->pattern, ref->name, 0)) {\n \t\tif (data->format == REPLACE_FORMAT_SHORT)\n-\t\t\tprintf(\"%s\\n\", refname);\n+\t\t\tprintf(\"%s\\n\", ref->name);\n \t\telse if (data->format == REPLACE_FORMAT_MEDIUM)\n-\t\t\tprintf(\"%s -> %s\\n\", refname, oid_to_hex(oid));\n+\t\t\tprintf(\"%s -> %s\\n\", ref->name, oid_to_hex(ref->oid));\n \t\telse { /* data->format == REPLACE_FORMAT_LONG */\n \t\t\tstruct object_id object;\n \t\t\tenum object_type obj_type, repl_type;\n \n-\t\t\tif (repo_get_oid(data->repo, refname, &object))\n-\t\t\t\treturn error(_(\"failed to resolve '%s' as a valid ref\"), refname);\n+\t\t\tif (repo_get_oid(data->repo, ref->name, &object))\n+\t\t\t\treturn error(_(\"failed to resolve '%s' as a valid ref\"), ref->name);\n \n \t\t\tobj_type = odb_read_object_info(data->repo->objects, &object, NULL);\n-\t\t\trepl_type = odb_read_object_info(data->repo->objects, oid, NULL);\n+\t\t\trepl_type = odb_read_object_info(data->repo->objects, ref->oid, NULL);\n \n-\t\t\tprintf(\"%s (%s) -> %s (%s)\\n\", refname, type_name(obj_type),\n-\t\t\t       oid_to_hex(oid), type_name(repl_type));\n+\t\t\tprintf(\"%s (%s) -> %s (%s)\\n\", ref->name, type_name(obj_type),\n+\t\t\t       oid_to_hex(ref->oid), type_name(repl_type));\n \t\t}\n \t}\n \ndiff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\nindex 9da92b990d0..3578591b4f2 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -217,19 +217,17 @@ static int show_default(void)\n \treturn 0;\n }\n \n-static int show_reference(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t  int flag UNUSED, void *cb_data UNUSED)\n+static int show_reference(const struct reference *ref, void *cb_data UNUSED)\n {\n-\tif (ref_excluded(&ref_excludes, refname))\n+\tif (ref_excluded(&ref_excludes, ref->name))\n \t\treturn 0;\n-\tshow_rev(NORMAL, oid, refname);\n+\tshow_rev(NORMAL, ref->oid, ref->name);\n \treturn 0;\n }\n \n-static int anti_reference(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t  int flag UNUSED, void *cb_data UNUSED)\n+static int anti_reference(const struct reference *ref, void *cb_data UNUSED)\n {\n-\tshow_rev(REVERSED, oid, refname);\n+\tshow_rev(REVERSED, ref->oid, ref->name);\n \treturn 0;\n }\n \ndiff --git a/builtin/show-branch.c b/builtin/show-branch.c\nindex 441babf2e35..10475a6b5ed 100644\n--- a/builtin/show-branch.c\n+++ b/builtin/show-branch.c\n@@ -413,34 +413,32 @@ static int append_ref(const char *refname, const struct object_id *oid,\n \treturn 0;\n }\n \n-static int append_head_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t   int flag UNUSED, void *cb_data UNUSED)\n+static int append_head_ref(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct object_id tmp;\n \tint ofs = 11;\n-\tif (!starts_with(refname, \"refs/heads/\"))\n+\tif (!starts_with(ref->name, \"refs/heads/\"))\n \t\treturn 0;\n \t/* If both heads/foo and tags/foo exists, get_sha1 would\n \t * get confused.\n \t */\n-\tif (repo_get_oid(the_repository, refname + ofs, &tmp) || !oideq(&tmp, oid))\n+\tif (repo_get_oid(the_repository, ref->name + ofs, &tmp) || !oideq(&tmp, ref->oid))\n \t\tofs = 5;\n-\treturn append_ref(refname + ofs, oid, 0);\n+\treturn append_ref(ref->name + ofs, ref->oid, 0);\n }\n \n-static int append_remote_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t     int flag UNUSED, void *cb_data UNUSED)\n+static int append_remote_ref(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct object_id tmp;\n \tint ofs = 13;\n-\tif (!starts_with(refname, \"refs/remotes/\"))\n+\tif (!starts_with(ref->name, \"refs/remotes/\"))\n \t\treturn 0;\n \t/* If both heads/foo and tags/foo exists, get_sha1 would\n \t * get confused.\n \t */\n-\tif (repo_get_oid(the_repository, refname + ofs, &tmp) || !oideq(&tmp, oid))\n+\tif (repo_get_oid(the_repository, ref->name + ofs, &tmp) || !oideq(&tmp, ref->oid))\n \t\tofs = 5;\n-\treturn append_ref(refname + ofs, oid, 0);\n+\treturn append_ref(ref->name + ofs, ref->oid, 0);\n }\n \n static int append_tag_ref(const char *refname, const struct object_id *oid,\n@@ -454,27 +452,26 @@ static int append_tag_ref(const char *refname, const struct object_id *oid,\n static const char *match_ref_pattern = NULL;\n static int match_ref_slash = 0;\n \n-static int append_matching_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t       int flag, void *cb_data)\n+static int append_matching_ref(const struct reference *ref, void *cb_data)\n {\n \t/* we want to allow pattern hold/<asterisk> to show all\n \t * branches under refs/heads/hold/, and v0.99.9? to show\n \t * refs/tags/v0.99.9a and friends.\n \t */\n \tconst char *tail;\n-\tint slash = count_slashes(refname);\n-\tfor (tail = refname; *tail && match_ref_slash < slash; )\n+\tint slash = count_slashes(ref->name);\n+\tfor (tail = ref->name; *tail && match_ref_slash < slash; )\n \t\tif (*tail++ == '/')\n \t\t\tslash--;\n \tif (!*tail)\n \t\treturn 0;\n \tif (wildmatch(match_ref_pattern, tail, 0))\n \t\treturn 0;\n-\tif (starts_with(refname, \"refs/heads/\"))\n-\t\treturn append_head_ref(refname, NULL, oid, flag, cb_data);\n-\tif (starts_with(refname, \"refs/tags/\"))\n-\t\treturn append_tag_ref(refname, oid, flag, cb_data);\n-\treturn append_ref(refname, oid, 0);\n+\tif (starts_with(ref->name, \"refs/heads/\"))\n+\t\treturn append_head_ref(ref, cb_data);\n+\tif (starts_with(ref->name, \"refs/tags/\"))\n+\t\treturn append_tag_ref(ref->name, ref->oid, ref->flags, cb_data);\n+\treturn append_ref(ref->name, ref->oid, 0);\n }\n \n static void snarf_refs(int head, int remotes)\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex 0b6f9edf86c..4803b5e5986 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -66,26 +66,25 @@ struct show_ref_data {\n \tint show_head;\n };\n \n-static int show_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t    int flag UNUSED, void *cbdata)\n+static int show_ref(const struct reference *ref, void *cbdata)\n {\n \tstruct show_ref_data *data = cbdata;\n \n-\tif (data->show_head && !strcmp(refname, \"HEAD\"))\n+\tif (data->show_head && !strcmp(ref->name, \"HEAD\"))\n \t\tgoto match;\n \n \tif (data->patterns) {\n-\t\tint reflen = strlen(refname);\n+\t\tint reflen = strlen(ref->name);\n \t\tconst char **p = data->patterns, *m;\n \t\twhile ((m = *p++) != NULL) {\n \t\t\tint len = strlen(m);\n \t\t\tif (len > reflen)\n \t\t\t\tcontinue;\n-\t\t\tif (memcmp(m, refname + reflen - len, len))\n+\t\t\tif (memcmp(m, ref->name + reflen - len, len))\n \t\t\t\tcontinue;\n \t\t\tif (len == reflen)\n \t\t\t\tgoto match;\n-\t\t\tif (refname[reflen - len - 1] == '/')\n+\t\t\tif (ref->name[reflen - len - 1] == '/')\n \t\t\t\tgoto match;\n \t\t}\n \t\treturn 0;\n@@ -94,18 +93,15 @@ static int show_ref(const char *refname, const char *referent UNUSED, const stru\n match:\n \tdata->found_match++;\n \n-\tshow_one(data->show_one_opts, refname, oid);\n+\tshow_one(data->show_one_opts, ref->name, ref->oid);\n \n \treturn 0;\n }\n \n-static int add_existing(const char *refname,\n-\t\t\tconst char *referent UNUSED,\n-\t\t\tconst struct object_id *oid UNUSED,\n-\t\t\tint flag UNUSED, void *cbdata)\n+static int add_existing(const struct reference *ref, void *cbdata)\n {\n \tstruct string_list *list = (struct string_list *)cbdata;\n-\tstring_list_insert(list, refname);\n+\tstring_list_insert(list, ref->name);\n \treturn 0;\n }\n \ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex fcd73abe533..35f6cf735e5 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -593,16 +593,12 @@ static void print_status(unsigned int flags, char state, const char *path,\n \tprintf(\"\\n\");\n }\n \n-static int handle_submodule_head_ref(const char *refname UNUSED,\n-\t\t\t\t     const char *referent UNUSED,\n-\t\t\t\t     const struct object_id *oid,\n-\t\t\t\t     int flags UNUSED,\n-\t\t\t\t     void *cb_data)\n+static int handle_submodule_head_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct object_id *output = cb_data;\n \n-\tif (oid)\n-\t\toidcpy(output, oid);\n+\tif (ref->oid)\n+\t\toidcpy(output, ref->oid);\n \n \treturn 0;\n }\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 812774a5ca9..b7f323b5e4d 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -635,11 +635,7 @@ static void print_preparing_worktree_line(int detach,\n  *\n  * Returns 0 on failure and non-zero on success.\n  */\n-static int first_valid_ref(const char *refname UNUSED,\n-\t\t\t   const char *referent UNUSED,\n-\t\t\t   const struct object_id *oid UNUSED,\n-\t\t\t   int flags UNUSED,\n-\t\t\t   void *cb_data UNUSED)\n+static int first_valid_ref(const struct reference *ref UNUSED, void *cb_data UNUSED)\n {\n \treturn 1;\n }\ndiff --git a/commit-graph.c b/commit-graph.c\nindex 2f20f66cfdd..0cfe16ad082 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -1846,18 +1846,16 @@ struct refs_cb_data {\n \tstruct progress *progress;\n };\n \n-static int add_ref_to_set(const char *refname UNUSED,\n-\t\t\t  const char *referent UNUSED,\n-\t\t\t  const struct object_id *oid,\n-\t\t\t  int flags UNUSED, void *cb_data)\n+static int add_ref_to_set(const struct reference *ref, void *cb_data)\n {\n+\tconst struct object_id *maybe_peeled = ref->oid;\n \tstruct object_id peeled;\n \tstruct refs_cb_data *data = (struct refs_cb_data *)cb_data;\n \n-\tif (!peel_iterated_oid(data->repo, oid, &peeled))\n-\t\toid = &peeled;\n-\tif (odb_read_object_info(data->repo->objects, oid, NULL) == OBJ_COMMIT)\n-\t\toidset_insert(data->commits, oid);\n+\tif (!peel_iterated_oid(data->repo, ref->oid, &peeled))\n+\t\tmaybe_peeled = &peeled;\n+\tif (odb_read_object_info(data->repo->objects, maybe_peeled, NULL) == OBJ_COMMIT)\n+\t\toidset_insert(data->commits, maybe_peeled);\n \n \tdisplay_progress(data->progress, oidset_size(data->commits));\n \ndiff --git a/delta-islands.c b/delta-islands.c\nindex 36c94799d69..7cfebc4162b 100644\n--- a/delta-islands.c\n+++ b/delta-islands.c\n@@ -390,8 +390,7 @@ static void add_ref_to_island(kh_str_t *remote_islands, const char *island_name,\n \trl->hash += sha_core;\n }\n \n-static int find_island_for_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t       int flags UNUSED, void *cb)\n+static int find_island_for_ref(const struct reference *ref, void *cb)\n {\n \tstruct island_load_data *ild = cb;\n \n@@ -406,7 +405,7 @@ static int find_island_for_ref(const char *refname, const char *referent UNUSED,\n \n \t/* walk backwards to get last-one-wins ordering */\n \tfor (i = ild->nr - 1; i >= 0; i--) {\n-\t\tif (!regexec(&ild->rx[i], refname,\n+\t\tif (!regexec(&ild->rx[i], ref->name,\n \t\t\t     ARRAY_SIZE(matches), matches, 0))\n \t\t\tbreak;\n \t}\n@@ -428,10 +427,10 @@ static int find_island_for_ref(const char *refname, const char *referent UNUSED,\n \t\tif (island_name.len)\n \t\t\tstrbuf_addch(&island_name, '-');\n \n-\t\tstrbuf_add(&island_name, refname + match->rm_so, match->rm_eo - match->rm_so);\n+\t\tstrbuf_add(&island_name, ref->name + match->rm_so, match->rm_eo - match->rm_so);\n \t}\n \n-\tadd_ref_to_island(ild->remote_islands, island_name.buf, oid);\n+\tadd_ref_to_island(ild->remote_islands, island_name.buf, ref->oid);\n \tstrbuf_release(&island_name);\n \treturn 0;\n }\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 6ed56629518..f576e593b9f 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -188,13 +188,9 @@ static int rev_list_insert_ref(struct fetch_negotiator *negotiator,\n \treturn 0;\n }\n \n-static int rev_list_insert_ref_oid(const char *refname UNUSED,\n-\t\t\t\t   const char *referent UNUSED,\n-\t\t\t\t   const struct object_id *oid,\n-\t\t\t\t   int flag UNUSED,\n-\t\t\t\t   void *cb_data)\n+static int rev_list_insert_ref_oid(const struct reference *ref, void *cb_data)\n {\n-\treturn rev_list_insert_ref(cb_data, oid);\n+\treturn rev_list_insert_ref(cb_data, ref->oid);\n }\n \n enum ack_type {\n@@ -616,13 +612,9 @@ static int mark_complete(const struct object_id *oid)\n \treturn 0;\n }\n \n-static int mark_complete_oid(const char *refname UNUSED,\n-\t\t\t     const char *referent UNUSED,\n-\t\t\t     const struct object_id *oid,\n-\t\t\t     int flag UNUSED,\n-\t\t\t     void *cb_data UNUSED)\n+static int mark_complete_oid(const struct reference *ref, void *cb_data UNUSED)\n {\n-\treturn mark_complete(oid);\n+\treturn mark_complete(ref->oid);\n }\n \n static void mark_recent_complete_commits(struct fetch_pack_args *args,\ndiff --git a/help.c b/help.c\nindex bb20498cfd0..d8c056fc5d1 100644\n--- a/help.c\n+++ b/help.c\n@@ -845,18 +845,16 @@ struct similar_ref_cb {\n \tstruct string_list *similar_refs;\n };\n \n-static int append_similar_ref(const char *refname, const char *referent UNUSED,\n-\t\t\t      const struct object_id *oid UNUSED,\n-\t\t\t      int flags UNUSED, void *cb_data)\n+static int append_similar_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct similar_ref_cb *cb = (struct similar_ref_cb *)(cb_data);\n-\tchar *branch = strrchr(refname, '/') + 1;\n+\tchar *branch = strrchr(ref->name, '/') + 1;\n \n \t/* A remote branch of the same name is deemed similar */\n-\tif (starts_with(refname, \"refs/remotes/\") &&\n+\tif (starts_with(ref->name, \"refs/remotes/\") &&\n \t    !strcmp(branch, cb->base_ref))\n \t\tstring_list_append_nodup(cb->similar_refs,\n-\t\t\t\t\t refs_shorten_unambiguous_ref(get_main_ref_store(the_repository), refname, 1));\n+\t\t\t\t\t refs_shorten_unambiguous_ref(get_main_ref_store(the_repository), ref->name, 1));\n \treturn 0;\n }\n \ndiff --git a/http-backend.c b/http-backend.c\nindex d5dfe762bb5..992be9dc70c 100644\n--- a/http-backend.c\n+++ b/http-backend.c\n@@ -513,18 +513,17 @@ static void run_service(const char **argv, int buffer_input)\n \t\texit(1);\n }\n \n-static int show_text_ref(const char *name, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t int flag UNUSED, void *cb_data)\n+static int show_text_ref(const struct reference *ref, void *cb_data)\n {\n-\tconst char *name_nons = strip_namespace(name);\n+\tconst char *name_nons = strip_namespace(ref->name);\n \tstruct strbuf *buf = cb_data;\n-\tstruct object *o = parse_object(the_repository, oid);\n+\tstruct object *o = parse_object(the_repository, ref->oid);\n \tif (!o)\n \t\treturn 0;\n \n-\tstrbuf_addf(buf, \"%s\\t%s\\n\", oid_to_hex(oid), name_nons);\n+\tstrbuf_addf(buf, \"%s\\t%s\\n\", oid_to_hex(ref->oid), name_nons);\n \tif (o->type == OBJ_TAG) {\n-\t\to = deref_tag(the_repository, o, name, 0);\n+\t\to = deref_tag(the_repository, o, ref->name, 0);\n \t\tif (!o)\n \t\t\treturn 0;\n \t\tstrbuf_addf(buf, \"%s\\t%s^{}\\n\", oid_to_hex(&o->oid),\n@@ -569,21 +568,20 @@ static void get_info_refs(struct strbuf *hdr, char *arg UNUSED)\n \tstrbuf_release(&buf);\n }\n \n-static int show_head_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t int flag, void *cb_data)\n+static int show_head_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct strbuf *buf = cb_data;\n \n-\tif (flag & REF_ISSYMREF) {\n+\tif (ref->flags & REF_ISSYMREF) {\n \t\tconst char *target = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),\n-\t\t\t\t\t\t\t     refname,\n+\t\t\t\t\t\t\t     ref->name,\n \t\t\t\t\t\t\t     RESOLVE_REF_READING,\n \t\t\t\t\t\t\t     NULL, NULL);\n \n \t\tif (target)\n \t\t\tstrbuf_addf(buf, \"ref: %s\\n\", strip_namespace(target));\n \t} else {\n-\t\tstrbuf_addf(buf, \"%s\\n\", oid_to_hex(oid));\n+\t\tstrbuf_addf(buf, \"%s\\n\", oid_to_hex(ref->oid));\n \t}\n \n \treturn 0;\ndiff --git a/log-tree.c b/log-tree.c\nindex 67d9ace5964..95f1d12c722 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -147,9 +147,7 @@ static int ref_filter_match(const char *refname,\n \treturn 1;\n }\n \n-static int add_ref_decoration(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t      int flags UNUSED,\n-\t\t\t      void *cb_data)\n+static int add_ref_decoration(const struct reference *ref, void *cb_data)\n {\n \tint i;\n \tstruct object *obj;\n@@ -158,16 +156,16 @@ static int add_ref_decoration(const char *refname, const char *referent UNUSED,\n \tstruct decoration_filter *filter = (struct decoration_filter *)cb_data;\n \tconst char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref;\n \n-\tif (filter && !ref_filter_match(refname, filter))\n+\tif (filter && !ref_filter_match(ref->name, filter))\n \t\treturn 0;\n \n-\tif (starts_with(refname, git_replace_ref_base)) {\n+\tif (starts_with(ref->name, git_replace_ref_base)) {\n \t\tstruct object_id original_oid;\n \t\tif (!replace_refs_enabled(the_repository))\n \t\t\treturn 0;\n-\t\tif (get_oid_hex(refname + strlen(git_replace_ref_base),\n+\t\tif (get_oid_hex(ref->name + strlen(git_replace_ref_base),\n \t\t\t\t&original_oid)) {\n-\t\t\twarning(\"invalid replace ref %s\", refname);\n+\t\t\twarning(\"invalid replace ref %s\", ref->name);\n \t\t\treturn 0;\n \t\t}\n \t\tobj = parse_object(the_repository, &original_oid);\n@@ -176,10 +174,10 @@ static int add_ref_decoration(const char *refname, const char *referent UNUSED,\n \t\treturn 0;\n \t}\n \n-\tobjtype = odb_read_object_info(the_repository->objects, oid, NULL);\n+\tobjtype = odb_read_object_info(the_repository->objects, ref->oid, NULL);\n \tif (objtype < 0)\n \t\treturn 0;\n-\tobj = lookup_object_by_type(the_repository, oid, objtype);\n+\tobj = lookup_object_by_type(the_repository, ref->oid, objtype);\n \n \tfor (i = 0; i < ARRAY_SIZE(ref_namespace); i++) {\n \t\tstruct ref_namespace_info *info = &ref_namespace[i];\n@@ -187,24 +185,24 @@ static int add_ref_decoration(const char *refname, const char *referent UNUSED,\n \t\tif (!info->decoration)\n \t\t\tcontinue;\n \t\tif (info->exact) {\n-\t\t\tif (!strcmp(refname, info->ref)) {\n+\t\t\tif (!strcmp(ref->name, info->ref)) {\n \t\t\t\tdeco_type = info->decoration;\n \t\t\t\tbreak;\n \t\t\t}\n-\t\t} else if (starts_with(refname, info->ref)) {\n+\t\t} else if (starts_with(ref->name, info->ref)) {\n \t\t\tdeco_type = info->decoration;\n \t\t\tbreak;\n \t\t}\n \t}\n \n-\tadd_name_decoration(deco_type, refname, obj);\n+\tadd_name_decoration(deco_type, ref->name, obj);\n \twhile (obj->type == OBJ_TAG) {\n \t\tif (!obj->parsed)\n \t\t\tparse_object(the_repository, &obj->oid);\n \t\tobj = ((struct tag *)obj)->tagged;\n \t\tif (!obj)\n \t\t\tbreak;\n-\t\tadd_name_decoration(DECORATION_REF_TAG, refname, obj);\n+\t\tadd_name_decoration(DECORATION_REF_TAG, ref->name, obj);\n \t}\n \treturn 0;\n }\ndiff --git a/ls-refs.c b/ls-refs.c\nindex c47acde07f3..64d02723691 100644\n--- a/ls-refs.c\n+++ b/ls-refs.c\n@@ -75,42 +75,42 @@ struct ls_refs_data {\n \tunsigned unborn : 1;\n };\n \n-static int send_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t    int flag, void *cb_data)\n+static int send_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct ls_refs_data *data = cb_data;\n-\tconst char *refname_nons = strip_namespace(refname);\n+\tconst char *refname_nons = strip_namespace(ref->name);\n \n \tstrbuf_reset(&data->buf);\n \n-\tif (ref_is_hidden(refname_nons, refname, &data->hidden_refs))\n+\tif (ref_is_hidden(refname_nons, ref->name, &data->hidden_refs))\n \t\treturn 0;\n \n \tif (!ref_match(&data->prefixes, refname_nons))\n \t\treturn 0;\n \n-\tif (oid)\n-\t\tstrbuf_addf(&data->buf, \"%s %s\", oid_to_hex(oid), refname_nons);\n+\tif (ref->oid)\n+\t\tstrbuf_addf(&data->buf, \"%s %s\", oid_to_hex(ref->oid), refname_nons);\n \telse\n \t\tstrbuf_addf(&data->buf, \"unborn %s\", refname_nons);\n-\tif (data->symrefs && flag & REF_ISSYMREF) {\n+\tif (data->symrefs && ref->flags & REF_ISSYMREF) {\n+\t\tint unused_flag;\n \t\tstruct object_id unused;\n \t\tconst char *symref_target = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),\n-\t\t\t\t\t\t\t\t    refname,\n+\t\t\t\t\t\t\t\t    ref->name,\n \t\t\t\t\t\t\t\t    0,\n \t\t\t\t\t\t\t\t    &unused,\n-\t\t\t\t\t\t\t\t    &flag);\n+\t\t\t\t\t\t\t\t    &unused_flag);\n \n \t\tif (!symref_target)\n-\t\t\tdie(\"'%s' is a symref but it is not?\", refname);\n+\t\t\tdie(\"'%s' is a symref but it is not?\", ref->name);\n \n \t\tstrbuf_addf(&data->buf, \" symref-target:%s\",\n \t\t\t    strip_namespace(symref_target));\n \t}\n \n-\tif (data->peel && oid) {\n+\tif (data->peel && ref->oid) {\n \t\tstruct object_id peeled;\n-\t\tif (!peel_iterated_oid(the_repository, oid, &peeled))\n+\t\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n \t\t\tstrbuf_addf(&data->buf, \" peeled:%s\", oid_to_hex(&peeled));\n \t}\n \n@@ -131,9 +131,17 @@ static void send_possibly_unborn_head(struct ls_refs_data *data)\n \tif (!refs_resolve_ref_unsafe(get_main_ref_store(the_repository), namespaced.buf, 0, &oid, &flag))\n \t\treturn; /* bad ref */\n \toid_is_null = is_null_oid(&oid);\n+\n \tif (!oid_is_null ||\n-\t    (data->unborn && data->symrefs && (flag & REF_ISSYMREF)))\n-\t\tsend_ref(namespaced.buf, NULL, oid_is_null ? NULL : &oid, flag, data);\n+\t    (data->unborn && data->symrefs && (flag & REF_ISSYMREF))) {\n+\t\tstruct reference ref = {\n+\t\t\t.name = namespaced.buf,\n+\t\t\t.oid = oid_is_null ? NULL : &oid,\n+\t\t\t.flags = flag,\n+\t\t};\n+\n+\t\tsend_ref(&ref, data);\n+\t}\n \tstrbuf_release(&namespaced);\n }\n \ndiff --git a/midx-write.c b/midx-write.c\nindex c73010df6d3..f4dd875747a 100644\n--- a/midx-write.c\n+++ b/midx-write.c\n@@ -697,28 +697,27 @@ static void prepare_midx_packing_data(struct packing_data *pdata,\n \ttrace2_region_leave(\"midx\", \"prepare_midx_packing_data\", ctx->repo);\n }\n \n-static int add_ref_to_pending(const char *refname, const char *referent UNUSED,\n-\t\t\t      const struct object_id *oid,\n-\t\t\t      int flag, void *cb_data)\n+static int add_ref_to_pending(const struct reference *ref, void *cb_data)\n {\n \tstruct rev_info *revs = (struct rev_info*)cb_data;\n+\tconst struct object_id *maybe_peeled = ref->oid;\n \tstruct object_id peeled;\n \tstruct object *object;\n \n-\tif ((flag & REF_ISSYMREF) && (flag & REF_ISBROKEN)) {\n-\t\twarning(\"symbolic ref is dangling: %s\", refname);\n+\tif ((ref->flags & REF_ISSYMREF) && (ref->flags & REF_ISBROKEN)) {\n+\t\twarning(\"symbolic ref is dangling: %s\", ref->name);\n \t\treturn 0;\n \t}\n \n-\tif (!peel_iterated_oid(revs->repo, oid, &peeled))\n-\t\toid = &peeled;\n+\tif (!peel_iterated_oid(revs->repo, ref->oid, &peeled))\n+\t\tmaybe_peeled = &peeled;\n \n-\tobject = parse_object_or_die(revs->repo, oid, refname);\n+\tobject = parse_object_or_die(revs->repo, maybe_peeled, ref->name);\n \tif (object->type != OBJ_COMMIT)\n \t\treturn 0;\n \n \tadd_pending_object(revs, object, \"\");\n-\tif (bitmap_is_preferred_refname(revs->repo, refname))\n+\tif (bitmap_is_preferred_refname(revs->repo, ref->name))\n \t\tobject->flags |= NEEDS_BITMAP;\n \treturn 0;\n }\ndiff --git a/negotiator/default.c b/negotiator/default.c\nindex c479da9b091..116dedcf830 100644\n--- a/negotiator/default.c\n+++ b/negotiator/default.c\n@@ -38,11 +38,10 @@ static void rev_list_push(struct negotiation_state *ns,\n \t}\n }\n \n-static int clear_marks(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t       int flag UNUSED,\n-\t\t       void *cb_data UNUSED)\n+static int clear_marks(const struct reference *ref, void *cb_data UNUSED)\n {\n-\tstruct object *o = deref_tag(the_repository, parse_object(the_repository, oid), refname, 0);\n+\tstruct object *o = deref_tag(the_repository, parse_object(the_repository, ref->oid),\n+\t\t\t\t     ref->name, 0);\n \n \tif (o && o->type == OBJ_COMMIT)\n \t\tclear_commit_marks((struct commit *)o,\ndiff --git a/negotiator/skipping.c b/negotiator/skipping.c\nindex 616df6bf3af..0a272130fb1 100644\n--- a/negotiator/skipping.c\n+++ b/negotiator/skipping.c\n@@ -75,11 +75,10 @@ static struct entry *rev_list_push(struct data *data, struct commit *commit, int\n \treturn entry;\n }\n \n-static int clear_marks(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t       int flag UNUSED,\n-\t\t       void *cb_data UNUSED)\n+static int clear_marks(const struct reference *ref, void *cb_data UNUSED)\n {\n-\tstruct object *o = deref_tag(the_repository, parse_object(the_repository, oid), refname, 0);\n+\tstruct object *o = deref_tag(the_repository, parse_object(the_repository, ref->oid),\n+\t\t\t\t     ref->name, 0);\n \n \tif (o && o->type == OBJ_COMMIT)\n \t\tclear_commit_marks((struct commit *)o,\ndiff --git a/notes.c b/notes.c\nindex 9a2e9181fe6..8e00fd8c470 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -938,13 +938,11 @@ int combine_notes_cat_sort_uniq(struct object_id *cur_oid,\n \treturn ret;\n }\n \n-static int string_list_add_one_ref(const char *refname, const char *referent UNUSED,\n-\t\t\t\t   const struct object_id *oid UNUSED,\n-\t\t\t\t   int flag UNUSED, void *cb)\n+static int string_list_add_one_ref(const struct reference *ref, void *cb)\n {\n \tstruct string_list *refs = cb;\n-\tif (!unsorted_string_list_has_string(refs, refname))\n-\t\tstring_list_append(refs, refname);\n+\tif (!unsorted_string_list_has_string(refs, ref->name))\n+\t\tstring_list_append(refs, ref->name);\n \treturn 0;\n }\n \ndiff --git a/object-name.c b/object-name.c\nindex 7774991d281..43c7518f995 100644\n--- a/object-name.c\n+++ b/object-name.c\n@@ -1444,18 +1444,16 @@ struct handle_one_ref_cb {\n \tstruct commit_list **list;\n };\n \n-static int handle_one_ref(const char *path, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t  int flag UNUSED,\n-\t\t\t  void *cb_data)\n+static int handle_one_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct handle_one_ref_cb *cb = cb_data;\n \tstruct commit_list **list = cb->list;\n-\tstruct object *object = parse_object(cb->repo, oid);\n+\tstruct object *object = parse_object(cb->repo, ref->oid);\n \tif (!object)\n \t\treturn 0;\n \tif (object->type == OBJ_TAG) {\n-\t\tobject = deref_tag(cb->repo, object, path,\n-\t\t\t\t   strlen(path));\n+\t\tobject = deref_tag(cb->repo, object, ref->name,\n+\t\t\t\t   strlen(ref->name));\n \t\tif (!object)\n \t\t\treturn 0;\n \t}\ndiff --git a/pseudo-merge.c b/pseudo-merge.c\nindex 893b763fe45..0abd51b42c1 100644\n--- a/pseudo-merge.c\n+++ b/pseudo-merge.c\n@@ -221,28 +221,25 @@ void load_pseudo_merges_from_config(struct repository *r,\n \t}\n }\n \n-static int find_pseudo_merge_group_for_ref(const char *refname,\n-\t\t\t\t\t   const char *referent UNUSED,\n-\t\t\t\t\t   const struct object_id *oid,\n-\t\t\t\t\t   int flags UNUSED,\n-\t\t\t\t\t   void *_data)\n+static int find_pseudo_merge_group_for_ref(const struct reference *ref, void *_data)\n {\n \tstruct bitmap_writer *writer = _data;\n+\tconst struct object_id *maybe_peeled = ref->oid;\n \tstruct object_id peeled;\n \tstruct commit *c;\n \tuint32_t i;\n \tint has_bitmap;\n \n-\tif (!peel_iterated_oid(the_repository, oid, &peeled))\n-\t\toid = &peeled;\n+\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n+\t\tmaybe_peeled = &peeled;\n \n-\tc = lookup_commit(the_repository, oid);\n+\tc = lookup_commit(the_repository, maybe_peeled);\n \tif (!c)\n \t\treturn 0;\n-\tif (!packlist_find(writer->to_pack, oid))\n+\tif (!packlist_find(writer->to_pack, maybe_peeled))\n \t\treturn 0;\n \n-\thas_bitmap = bitmap_writer_has_bitmapped_object_id(writer, oid);\n+\thas_bitmap = bitmap_writer_has_bitmapped_object_id(writer, maybe_peeled);\n \n \tfor (i = 0; i < writer->pseudo_merge_groups.nr; i++) {\n \t\tstruct pseudo_merge_group *group;\n@@ -252,7 +249,7 @@ static int find_pseudo_merge_group_for_ref(const char *refname,\n \t\tsize_t j;\n \n \t\tgroup = writer->pseudo_merge_groups.items[i].util;\n-\t\tif (regexec(group->pattern, refname, ARRAY_SIZE(captures),\n+\t\tif (regexec(group->pattern, ref->name, ARRAY_SIZE(captures),\n \t\t\t    captures, 0))\n \t\t\tcontinue;\n \n@@ -269,7 +266,7 @@ static int find_pseudo_merge_group_for_ref(const char *refname,\n \t\t\tif (group_name.len)\n \t\t\t\tstrbuf_addch(&group_name, '-');\n \n-\t\t\tstrbuf_add(&group_name, refname + match->rm_so,\n+\t\t\tstrbuf_add(&group_name, ref->name + match->rm_so,\n \t\t\t\t   match->rm_eo - match->rm_so);\n \t\t}\n \ndiff --git a/reachable.c b/reachable.c\nindex 22266db5233..b753c395530 100644\n--- a/reachable.c\n+++ b/reachable.c\n@@ -83,18 +83,17 @@ static void add_rebase_files(struct rev_info *revs)\n \tfree_worktrees(worktrees);\n }\n \n-static int add_one_ref(const char *path, const char *referent UNUSED, const struct object_id *oid,\n-\t\t       int flag, void *cb_data)\n+static int add_one_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct rev_info *revs = (struct rev_info *)cb_data;\n \tstruct object *object;\n \n-\tif ((flag & REF_ISSYMREF) && (flag & REF_ISBROKEN)) {\n-\t\twarning(\"symbolic ref is dangling: %s\", path);\n+\tif ((ref->flags & REF_ISSYMREF) && (ref->flags & REF_ISBROKEN)) {\n+\t\twarning(\"symbolic ref is dangling: %s\", ref->name);\n \t\treturn 0;\n \t}\n \n-\tobject = parse_object_or_die(the_repository, oid, path);\n+\tobject = parse_object_or_die(the_repository, ref->oid, ref->name);\n \tadd_pending_object(revs, object, \"\");\n \n \treturn 0;\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 520d2539c9e..7740f35e937 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -2954,14 +2954,15 @@ struct ref_filter_cbdata {\n  * A call-back given to for_each_ref().  Filter refs and keep them for\n  * later object processing.\n  */\n-static int filter_one(const char *refname, const char *referent, const struct object_id *oid, int flag, void *cb_data)\n+static int filter_one(const struct reference *ref, void *cb_data)\n {\n \tstruct ref_filter_cbdata *ref_cbdata = cb_data;\n-\tstruct ref_array_item *ref;\n+\tstruct ref_array_item *item;\n \n-\tref = apply_ref_filter(refname, referent, oid, flag, ref_cbdata->filter);\n-\tif (ref)\n-\t\tref_array_append(ref_cbdata->array, ref);\n+\titem = apply_ref_filter(ref->name, ref->target, ref->oid,\n+\t\t\t\tref->flags, ref_cbdata->filter);\n+\tif (item)\n+\t\tref_array_append(ref_cbdata->array, item);\n \n \treturn 0;\n }\n@@ -2990,17 +2991,18 @@ struct ref_filter_and_format_cbdata {\n \t} internal;\n };\n \n-static int filter_and_format_one(const char *refname, const char *referent, const struct object_id *oid, int flag, void *cb_data)\n+static int filter_and_format_one(const struct reference *ref, void *cb_data)\n {\n \tstruct ref_filter_and_format_cbdata *ref_cbdata = cb_data;\n-\tstruct ref_array_item *ref;\n+\tstruct ref_array_item *item;\n \tstruct strbuf output = STRBUF_INIT, err = STRBUF_INIT;\n \n-\tref = apply_ref_filter(refname, referent, oid, flag, ref_cbdata->filter);\n-\tif (!ref)\n+\titem = apply_ref_filter(ref->name, ref->target, ref->oid,\n+\t\t\t\tref->flags, ref_cbdata->filter);\n+\tif (!item)\n \t\treturn 0;\n \n-\tif (format_ref_array_item(ref, ref_cbdata->format, &output, &err))\n+\tif (format_ref_array_item(item, ref_cbdata->format, &output, &err))\n \t\tdie(\"%s\", err.buf);\n \n \tif (output.len || !ref_cbdata->format->array_opts.omit_empty) {\n@@ -3010,7 +3012,7 @@ static int filter_and_format_one(const char *refname, const char *referent, cons\n \n \tstrbuf_release(&output);\n \tstrbuf_release(&err);\n-\tfree_array_item(ref);\n+\tfree_array_item(item);\n \n \t/*\n \t * Increment the running count of refs that match the filter. If\ndiff --git a/reflog.c b/reflog.c\nindex 65ef259b4f5..ac87e20c4f9 100644\n--- a/reflog.c\n+++ b/reflog.c\n@@ -423,16 +423,13 @@ int should_expire_reflog_ent_verbose(struct object_id *ooid,\n \treturn expire;\n }\n \n-static int push_tip_to_list(const char *refname UNUSED,\n-\t\t\t    const char *referent UNUSED,\n-\t\t\t    const struct object_id *oid,\n-\t\t\t    int flags, void *cb_data)\n+static int push_tip_to_list(const struct reference *ref, void *cb_data)\n {\n \tstruct commit_list **list = cb_data;\n \tstruct commit *tip_commit;\n-\tif (flags & REF_ISSYMREF)\n+\tif (ref->flags & REF_ISSYMREF)\n \t\treturn 0;\n-\ttip_commit = lookup_commit_reference_gently(the_repository, oid, 1);\n+\ttip_commit = lookup_commit_reference_gently(the_repository, ref->oid, 1);\n \tif (!tip_commit)\n \t\treturn 0;\n \tcommit_list_insert(tip_commit, list);\ndiff --git a/refs.c b/refs.c\nindex 750e5db0771..262849301d0 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -423,17 +423,19 @@ int refs_ref_exists(struct ref_store *refs, const char *refname)\n \t\t\t\t\t NULL, NULL);\n }\n \n-static int for_each_filter_refs(const char *refname, const char *referent,\n-\t\t\t\tconst struct object_id *oid,\n-\t\t\t\tint flags, void *data)\n+static int for_each_filter_refs(const struct reference *ref, void *data)\n {\n \tstruct for_each_ref_filter *filter = data;\n \n-\tif (wildmatch(filter->pattern, refname, 0))\n+\tif (wildmatch(filter->pattern, ref->name, 0))\n \t\treturn 0;\n-\tif (filter->prefix)\n-\t\tskip_prefix(refname, filter->prefix, &refname);\n-\treturn filter->fn(refname, referent, oid, flags, filter->cb_data);\n+\tif (filter->prefix) {\n+\t\tstruct reference skipped = *ref;\n+\t\tskip_prefix(skipped.name, filter->prefix, &skipped.name);\n+\t\treturn filter->fn(&skipped, filter->cb_data);\n+\t} else {\n+\t\treturn filter->fn(ref, filter->cb_data);\n+\t}\n }\n \n struct warn_if_dangling_data {\n@@ -444,17 +446,15 @@ struct warn_if_dangling_data {\n \tint dry_run;\n };\n \n-static int warn_if_dangling_symref(const char *refname, const char *referent UNUSED,\n-\t\t\t\t   const struct object_id *oid UNUSED,\n-\t\t\t\t   int flags, void *cb_data)\n+static int warn_if_dangling_symref(const struct reference *ref, void *cb_data)\n {\n \tstruct warn_if_dangling_data *d = cb_data;\n \tconst char *resolves_to, *msg;\n \n-\tif (!(flags & REF_ISSYMREF))\n+\tif (!(ref->flags & REF_ISSYMREF))\n \t\treturn 0;\n \n-\tresolves_to = refs_resolve_ref_unsafe(d->refs, refname, 0, NULL, NULL);\n+\tresolves_to = refs_resolve_ref_unsafe(d->refs, ref->name, 0, NULL, NULL);\n \tif (!resolves_to\n \t    || !string_list_has_string(d->refnames, resolves_to)) {\n \t\treturn 0;\n@@ -463,7 +463,7 @@ static int warn_if_dangling_symref(const char *refname, const char *referent UNU\n \tmsg = d->dry_run\n \t\t? _(\"%s%s will become dangling after %s is deleted\\n\")\n \t\t: _(\"%s%s has become dangling after %s was deleted\\n\");\n-\tfprintf(d->fp, msg, d->indent, refname, resolves_to);\n+\tfprintf(d->fp, msg, d->indent, ref->name, resolves_to);\n \treturn 0;\n }\n \n@@ -504,8 +504,15 @@ int refs_head_ref_namespaced(struct ref_store *refs, each_ref_fn fn, void *cb_da\n \tint flag;\n \n \tstrbuf_addf(&buf, \"%sHEAD\", get_git_namespace());\n-\tif (!refs_read_ref_full(refs, buf.buf, RESOLVE_REF_READING, &oid, &flag))\n-\t\tret = fn(buf.buf, NULL, &oid, flag, cb_data);\n+\tif (!refs_read_ref_full(refs, buf.buf, RESOLVE_REF_READING, &oid, &flag)) {\n+\t\tstruct reference ref = {\n+\t\t\t.name = buf.buf,\n+\t\t\t.oid = &oid,\n+\t\t\t.flags = flag,\n+\t\t};\n+\n+\t\tret = fn(&ref, cb_data);\n+\t}\n \tstrbuf_release(&buf);\n \n \treturn ret;\n@@ -1740,8 +1747,15 @@ int refs_head_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)\n \tint flag;\n \n \tif (refs_resolve_ref_unsafe(refs, \"HEAD\", RESOLVE_REF_READING,\n-\t\t\t\t    &oid, &flag))\n-\t\treturn fn(\"HEAD\", NULL, &oid, flag, cb_data);\n+\t\t\t\t    &oid, &flag)) {\n+\t\tstruct reference ref = {\n+\t\t\t.name = \"HEAD\",\n+\t\t\t.oid = &oid,\n+\t\t\t.flags = flag,\n+\t\t};\n+\n+\t\treturn fn(&ref, cb_data);\n+\t}\n \n \treturn 0;\n }\n@@ -2752,14 +2766,10 @@ struct do_for_each_reflog_help {\n \tvoid *cb_data;\n };\n \n-static int do_for_each_reflog_helper(const char *refname,\n-\t\t\t\t     const char *referent UNUSED,\n-\t\t\t\t     const struct object_id *oid UNUSED,\n-\t\t\t\t     int flags UNUSED,\n-\t\t\t\t     void *cb_data)\n+static int do_for_each_reflog_helper(const struct reference *ref, void *cb_data)\n {\n \tstruct do_for_each_reflog_help *hp = cb_data;\n-\treturn hp->fn(refname, hp->cb_data);\n+\treturn hp->fn(ref->name, hp->cb_data);\n }\n \n int refs_for_each_reflog(struct ref_store *refs, each_reflog_fn fn, void *cb_data)\n@@ -2977,25 +2987,24 @@ struct migration_data {\n \tuint64_t index;\n };\n \n-static int migrate_one_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t   int flags, void *cb_data)\n+static int migrate_one_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct migration_data *data = cb_data;\n \tstruct strbuf symref_target = STRBUF_INIT;\n \tint ret;\n \n-\tif (flags & REF_ISSYMREF) {\n-\t\tret = refs_read_symbolic_ref(data->old_refs, refname, &symref_target);\n+\tif (ref->flags & REF_ISSYMREF) {\n+\t\tret = refs_read_symbolic_ref(data->old_refs, ref->name, &symref_target);\n \t\tif (ret < 0)\n \t\t\tgoto done;\n \n-\t\tret = ref_transaction_update(data->transaction, refname, NULL, null_oid(the_hash_algo),\n+\t\tret = ref_transaction_update(data->transaction, ref->name, NULL, null_oid(the_hash_algo),\n \t\t\t\t\t     symref_target.buf, NULL,\n \t\t\t\t\t     REF_SKIP_CREATE_REFLOG | REF_NO_DEREF, NULL, data->errbuf);\n \t\tif (ret < 0)\n \t\t\tgoto done;\n \t} else {\n-\t\tret = ref_transaction_create(data->transaction, refname, oid, NULL,\n+\t\tret = ref_transaction_create(data->transaction, ref->name, ref->oid, NULL,\n \t\t\t\t\t     REF_SKIP_CREATE_REFLOG | REF_SKIP_OID_VERIFICATION,\n \t\t\t\t\t     NULL, data->errbuf);\n \t\tif (ret < 0)\ndiff --git a/refs.h b/refs.h\nindex 4e6bd63aa86..2b24a3d9974 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -355,14 +355,32 @@ struct ref_transaction;\n  */\n #define REF_BAD_NAME 0x08\n \n+/* A reference passed to `for_each_ref()`-style callbacks. */\n+struct reference {\n+\t/* The fully-qualified name of the reference. */\n+\tconst char *name;\n+\n+\t/* The target of a symbolic ref. `NULL` for direct references. */\n+\tconst char *target;\n+\n+\t/*\n+\t * The object ID of a reference. Either the direct object ID or the\n+\t * resolved object ID in the case of a symbolic ref. May be the zero\n+\t * object ID in case the symbolic ref cannot be resolved.\n+\t */\n+\tconst struct object_id *oid;\n+\n+\t/* A bitfield of `REF_` flags. */\n+\tint flags;\n+};\n+\n /*\n  * The signature for the callback function for the for_each_*()\n  * functions below.  The memory pointed to by the refname and oid\n  * arguments is only guaranteed to be valid for the duration of a\n  * single callback invocation.\n  */\n-typedef int each_ref_fn(const char *refname, const char *referent,\n-\t\t\tconst struct object_id *oid, int flags, void *cb_data);\n+typedef int each_ref_fn(const struct reference *ref, void *cb_data);\n \n /*\n  * The following functions invoke the specified callback function for\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex bb2bec38076..0ddcf22aed7 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -3151,14 +3151,11 @@ static int parse_and_write_reflog(struct files_ref_store *refs,\n \treturn 0;\n }\n \n-static int ref_present(const char *refname, const char *referent UNUSED,\n-\t\t       const struct object_id *oid UNUSED,\n-\t\t       int flags UNUSED,\n-\t\t       void *cb_data)\n+static int ref_present(const struct reference *ref, void *cb_data)\n {\n \tstruct string_list *affected_refnames = cb_data;\n \n-\treturn string_list_has_string(affected_refnames, refname);\n+\treturn string_list_has_string(affected_refnames, ref->name);\n }\n \n static int files_transaction_finish_initial(struct files_ref_store *refs,\ndiff --git a/refs/iterator.c b/refs/iterator.c\nindex 17ef841d8a3..7f2e718f1c9 100644\n--- a/refs/iterator.c\n+++ b/refs/iterator.c\n@@ -476,7 +476,14 @@ int do_for_each_ref_iterator(struct ref_iterator *iter,\n \n \tcurrent_ref_iter = iter;\n \twhile ((ok = ref_iterator_advance(iter)) == ITER_OK) {\n-\t\tretval = fn(iter->refname, iter->referent, iter->oid, iter->flags, cb_data);\n+\t\tstruct reference ref = {\n+\t\t\t.name = iter->refname,\n+\t\t\t.target = iter->referent,\n+\t\t\t.oid = iter->oid,\n+\t\t\t.flags = iter->flags,\n+\t\t};\n+\n+\t\tretval = fn(&ref, cb_data);\n \t\tif (retval)\n \t\t\tgoto out;\n \t}\ndiff --git a/remote.c b/remote.c\nindex df9675cd330..59b37151208 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -2315,21 +2315,19 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,\n \treturn 1;\n }\n \n-static int one_local_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t int flag UNUSED,\n-\t\t\t void *cb_data)\n+static int one_local_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct ref ***local_tail = cb_data;\n-\tstruct ref *ref;\n+\tstruct ref *local_ref;\n \n \t/* we already know it starts with refs/ to get here */\n-\tif (check_refname_format(refname + 5, 0))\n+\tif (check_refname_format(ref->name + 5, 0))\n \t\treturn 0;\n \n-\tref = alloc_ref(refname);\n-\toidcpy(&ref->new_oid, oid);\n-\t**local_tail = ref;\n-\t*local_tail = &ref->next;\n+\tlocal_ref = alloc_ref(ref->name);\n+\toidcpy(&local_ref->new_oid, ref->oid);\n+\t**local_tail = local_ref;\n+\t*local_tail = &local_ref->next;\n \treturn 0;\n }\n \n@@ -2402,15 +2400,14 @@ struct stale_heads_info {\n \tstruct refspec *rs;\n };\n \n-static int get_stale_heads_cb(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t      int flags, void *cb_data)\n+static int get_stale_heads_cb(const struct reference *ref, void *cb_data)\n {\n \tstruct stale_heads_info *info = cb_data;\n \tstruct string_list matches = STRING_LIST_INIT_DUP;\n \tstruct refspec_item query;\n \tint i, stale = 1;\n \tmemset(&query, 0, sizeof(struct refspec_item));\n-\tquery.dst = (char *)refname;\n+\tquery.dst = (char *)ref->name;\n \n \trefspec_find_all_matches(info->rs, &query, &matches);\n \tif (matches.nr == 0)\n@@ -2423,7 +2420,7 @@ static int get_stale_heads_cb(const char *refname, const char *referent UNUSED,\n \t * overlapping refspecs, we need to go over all of the\n \t * matching refs.\n \t */\n-\tif (flags & REF_ISSYMREF)\n+\tif (ref->flags & REF_ISSYMREF)\n \t\tgoto clean_exit;\n \n \tfor (i = 0; stale && i < matches.nr; i++)\n@@ -2431,8 +2428,8 @@ static int get_stale_heads_cb(const char *refname, const char *referent UNUSED,\n \t\t\tstale = 0;\n \n \tif (stale) {\n-\t\tstruct ref *ref = make_linked_ref(refname, &info->stale_refs_tail);\n-\t\toidcpy(&ref->new_oid, oid);\n+\t\tstruct ref *linked_ref = make_linked_ref(ref->name, &info->stale_refs_tail);\n+\t\toidcpy(&linked_ref->new_oid, ref->oid);\n \t}\n \n clean_exit:\ndiff --git a/replace-object.c b/replace-object.c\nindex 3eae0510745..03d0f1f083b 100644\n--- a/replace-object.c\n+++ b/replace-object.c\n@@ -8,31 +8,27 @@\n #include \"repository.h\"\n #include \"commit.h\"\n \n-static int register_replace_ref(const char *refname,\n-\t\t\t\tconst char *referent UNUSED,\n-\t\t\t\tconst struct object_id *oid,\n-\t\t\t\tint flag UNUSED,\n-\t\t\t\tvoid *cb_data)\n+static int register_replace_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct repository *r = cb_data;\n \n \t/* Get sha1 from refname */\n-\tconst char *slash = strrchr(refname, '/');\n-\tconst char *hash = slash ? slash + 1 : refname;\n+\tconst char *slash = strrchr(ref->name, '/');\n+\tconst char *hash = slash ? slash + 1 : ref->name;\n \tstruct replace_object *repl_obj = xmalloc(sizeof(*repl_obj));\n \n \tif (get_oid_hex_algop(hash, &repl_obj->original.oid, r->hash_algo)) {\n \t\tfree(repl_obj);\n-\t\twarning(_(\"bad replace ref name: %s\"), refname);\n+\t\twarning(_(\"bad replace ref name: %s\"), ref->name);\n \t\treturn 0;\n \t}\n \n \t/* Copy sha1 from the read ref */\n-\toidcpy(&repl_obj->replacement, oid);\n+\toidcpy(&repl_obj->replacement, ref->oid);\n \n \t/* Register new object */\n \tif (oidmap_put(&r->objects->replace_map, repl_obj))\n-\t\tdie(_(\"duplicate replace ref: %s\"), refname);\n+\t\tdie(_(\"duplicate replace ref: %s\"), ref->name);\n \n \treturn 0;\n }\ndiff --git a/revision.c b/revision.c\nindex 806a1c4c24d..a195c0ded45 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -1647,19 +1647,17 @@ struct all_refs_cb {\n \tstruct worktree *wt;\n };\n \n-static int handle_one_ref(const char *path, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t  int flag UNUSED,\n-\t\t\t  void *cb_data)\n+static int handle_one_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct all_refs_cb *cb = cb_data;\n \tstruct object *object;\n \n-\tif (ref_excluded(&cb->all_revs->ref_excludes, path))\n+\tif (ref_excluded(&cb->all_revs->ref_excludes, ref->name))\n \t    return 0;\n \n-\tobject = get_reference(cb->all_revs, path, oid, cb->all_flags);\n-\tadd_rev_cmdline(cb->all_revs, object, path, REV_CMD_REF, cb->all_flags);\n-\tadd_pending_object(cb->all_revs, object, path);\n+\tobject = get_reference(cb->all_revs, ref->name, ref->oid, cb->all_flags);\n+\tadd_rev_cmdline(cb->all_revs, object, ref->name, REV_CMD_REF, cb->all_flags);\n+\tadd_pending_object(cb->all_revs, object, ref->name);\n \treturn 0;\n }\n \ndiff --git a/server-info.c b/server-info.c\nindex 9bb30d9ab71..aea4df80416 100644\n--- a/server-info.c\n+++ b/server-info.c\n@@ -148,23 +148,21 @@ static int update_info_file(struct repository *r, char *path,\n \treturn ret;\n }\n \n-static int add_info_ref(const char *path, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\tint flag UNUSED,\n-\t\t\tvoid *cb_data)\n+static int add_info_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct update_info_ctx *uic = cb_data;\n-\tstruct object *o = parse_object(uic->repo, oid);\n+\tstruct object *o = parse_object(uic->repo, ref->oid);\n \tif (!o)\n \t\treturn -1;\n \n-\tif (uic_printf(uic, \"%s\t%s\\n\", oid_to_hex(oid), path) < 0)\n+\tif (uic_printf(uic, \"%s\t%s\\n\", oid_to_hex(ref->oid), ref->name) < 0)\n \t\treturn -1;\n \n \tif (o->type == OBJ_TAG) {\n-\t\to = deref_tag(uic->repo, o, path, 0);\n+\t\to = deref_tag(uic->repo, o, ref->name, 0);\n \t\tif (o)\n \t\t\tif (uic_printf(uic, \"%s\t%s^{}\\n\",\n-\t\t\t\toid_to_hex(&o->oid), path) < 0)\n+\t\t\t\toid_to_hex(&o->oid), ref->name) < 0)\n \t\t\t\treturn -1;\n \t}\n \treturn 0;\ndiff --git a/shallow.c b/shallow.c\nindex d9cd4e219cb..55b9cd9d3f2 100644\n--- a/shallow.c\n+++ b/shallow.c\n@@ -626,14 +626,10 @@ static void paint_down(struct paint_info *info, const struct object_id *oid,\n \tfree(tmp);\n }\n \n-static int mark_uninteresting(const char *refname UNUSED,\n-\t\t\t      const char *referent UNUSED,\n-\t\t\t      const struct object_id *oid,\n-\t\t\t      int flags UNUSED,\n-\t\t\t      void *cb_data UNUSED)\n+static int mark_uninteresting(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct commit *commit = lookup_commit_reference_gently(the_repository,\n-\t\t\t\t\t\t\t       oid, 1);\n+\t\t\t\t\t\t\t       ref->oid, 1);\n \tif (!commit)\n \t\treturn 0;\n \tcommit->object.flags |= UNINTERESTING;\n@@ -742,16 +738,12 @@ struct commit_array {\n \tsize_t nr, alloc;\n };\n \n-static int add_ref(const char *refname UNUSED,\n-\t\t  const char *referent UNUSED,\n-\t\t   const struct object_id *oid,\n-\t\t   int flags UNUSED,\n-\t\t   void *cb_data)\n+static int add_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct commit_array *ca = cb_data;\n \tALLOC_GROW(ca->commits, ca->nr + 1, ca->alloc);\n \tca->commits[ca->nr] = lookup_commit_reference_gently(the_repository,\n-\t\t\t\t\t\t\t     oid, 1);\n+\t\t\t\t\t\t\t     ref->oid, 1);\n \tif (ca->commits[ca->nr])\n \t\tca->nr++;\n \treturn 0;\ndiff --git a/submodule.c b/submodule.c\nindex 35c55155f7b..40a5c6fb9d1 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -934,10 +934,7 @@ static void free_submodules_data(struct string_list *submodules)\n \tstring_list_clear(submodules, 1);\n }\n \n-static int has_remote(const char *refname UNUSED,\n-\t\t      const char *referent UNUSED,\n-\t\t      const struct object_id *oid UNUSED,\n-\t\t      int flags UNUSED, void *cb_data UNUSED)\n+static int has_remote(const struct reference *ref UNUSED, void *cb_data UNUSED)\n {\n \treturn 1;\n }\n@@ -1255,13 +1252,10 @@ int push_unpushed_submodules(struct repository *r,\n \treturn ret;\n }\n \n-static int append_oid_to_array(const char *ref UNUSED,\n-\t\t\t       const char *referent UNUSED,\n-\t\t\t       const struct object_id *oid,\n-\t\t\t       int flags UNUSED, void *data)\n+static int append_oid_to_array(const struct reference *ref, void *data)\n {\n \tstruct oid_array *array = data;\n-\toid_array_append(array, oid);\n+\toid_array_append(array, ref->oid);\n \treturn 0;\n }\n \ndiff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c\nindex 83b06d39a36..b1215947c5e 100644\n--- a/t/helper/test-ref-store.c\n+++ b/t/helper/test-ref-store.c\n@@ -154,10 +154,9 @@ static int cmd_rename_ref(struct ref_store *refs, const char **argv)\n \treturn refs_rename_ref(refs, oldref, newref, logmsg);\n }\n \n-static int each_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t    int flags, void *cb_data UNUSED)\n+static int each_ref(const struct reference *ref, void *cb_data UNUSED)\n {\n-\tprintf(\"%s %s 0x%x\\n\", oid_to_hex(oid), refname, flags);\n+\tprintf(\"%s %s 0x%x\\n\", oid_to_hex(ref->oid), ref->name, ref->flags);\n \treturn 0;\n }\n \ndiff --git a/upload-pack.c b/upload-pack.c\nindex 1e87ae95593..0d563ae74e9 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -870,8 +870,8 @@ static void send_unshallow(struct upload_pack_data *data)\n \t}\n }\n \n-static int check_ref(const char *refname_full, const char *referent UNUSED, const struct object_id *oid,\n-\t\t     int flag, void *cb_data);\n+static int check_ref(const struct reference *ref, void *cb_data);\n+\n static void deepen(struct upload_pack_data *data, int depth)\n {\n \tif (depth == INFINITE_DEPTH && !is_repository_shallow(the_repository)) {\n@@ -1224,13 +1224,12 @@ static int mark_our_ref(const char *refname, const char *refname_full,\n \treturn 0;\n }\n \n-static int check_ref(const char *refname_full, const char *referent UNUSED,const struct object_id *oid,\n-\t\t     int flag UNUSED, void *cb_data)\n+static int check_ref(const struct reference *ref, void *cb_data)\n {\n-\tconst char *refname = strip_namespace(refname_full);\n+\tconst char *refname = strip_namespace(ref->name);\n \tstruct upload_pack_data *data = cb_data;\n \n-\tmark_our_ref(refname, refname_full, oid, &data->hidden_refs);\n+\tmark_our_ref(refname, ref->name, ref->oid, &data->hidden_refs);\n \treturn 0;\n }\n \n@@ -1292,27 +1291,25 @@ static void write_v0_ref(struct upload_pack_data *data,\n \treturn;\n }\n \n-static int send_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t    int flag UNUSED, void *cb_data)\n+static int send_ref(const struct reference *ref, void *cb_data)\n {\n-\twrite_v0_ref(cb_data, refname, strip_namespace(refname), oid);\n+\twrite_v0_ref(cb_data, ref->name, strip_namespace(ref->name), ref->oid);\n \treturn 0;\n }\n \n-static int find_symref(const char *refname, const char *referent UNUSED,\n-\t\t       const struct object_id *oid UNUSED,\n-\t\t       int flag, void *cb_data)\n+static int find_symref(const struct reference *ref, void *cb_data)\n {\n \tconst char *symref_target;\n \tstruct string_list_item *item;\n+\tint flag;\n \n-\tif ((flag & REF_ISSYMREF) == 0)\n+\tif ((ref->flags & REF_ISSYMREF) == 0)\n \t\treturn 0;\n \tsymref_target = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),\n-\t\t\t\t\t\trefname, 0, NULL, &flag);\n+\t\t\t\t\t\tref->name, 0, NULL, &flag);\n \tif (!symref_target || (flag & REF_ISSYMREF) == 0)\n-\t\tdie(\"'%s' is a symref but it is not?\", refname);\n-\titem = string_list_append(cb_data, strip_namespace(refname));\n+\t\tdie(\"'%s' is a symref but it is not?\", ref->name);\n+\titem = string_list_append(cb_data, strip_namespace(ref->name));\n \titem->util = xstrdup(strip_namespace(symref_target));\n \treturn 0;\n }\ndiff --git a/walker.c b/walker.c\nindex 80737545172..409b646578a 100644\n--- a/walker.c\n+++ b/walker.c\n@@ -226,14 +226,10 @@ static int interpret_target(struct walker *walker, char *target, struct object_i\n \treturn -1;\n }\n \n-static int mark_complete(const char *path UNUSED,\n-\t\t\tconst char *referent UNUSED,\n-\t\t\t const struct object_id *oid,\n-\t\t\t int flag UNUSED,\n-\t\t\t void *cb_data UNUSED)\n+static int mark_complete(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct commit *commit = lookup_commit_reference_gently(the_repository,\n-\t\t\t\t\t\t\t       oid, 1);\n+\t\t\t\t\t\t\t       ref->oid, 1);\n \n \tif (commit) {\n \t\tcommit->object.flags |= COMPLETE;\ndiff --git a/worktree.c b/worktree.c\nindex a2a5f51f29f..9308389cb6f 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -595,8 +595,15 @@ int other_head_refs(each_ref_fn fn, void *cb_data)\n \t\tif (refs_resolve_ref_unsafe(get_main_ref_store(the_repository),\n \t\t\t\t\t    refname.buf,\n \t\t\t\t\t    RESOLVE_REF_READING,\n-\t\t\t\t\t    &oid, &flag))\n-\t\t\tret = fn(refname.buf, NULL, &oid, flag, cb_data);\n+\t\t\t\t\t    &oid, &flag)) {\n+\t\t\tstruct reference ref = {\n+\t\t\t\t.name = refname.buf,\n+\t\t\t\t.oid = &oid,\n+\t\t\t\t.flags = flag,\n+\t\t\t};\n+\n+\t\t\tret = fn(&ref, cb_data);\n+\t\t}\n \t\tif (ret)\n \t\t\tbreak;\n \t}\n\n-- \n2.51.0.764.g787ff6f08a.dirty\n\n"},{"id":"528067","messageId":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-2-916cc7c6886b@pks.im","threadId":"64260","inReplyTo":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-0-916cc7c6886b@pks.im","subject":"[PATCH 02/13] refs: introduce `.ref` field for the base iterator","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-07T10:58:39Z","receivedAt":"2025-10-07T10:59:04Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The base iterator has a couple of fields that tracks the name, target,\nobject ID and flags for the current reference. Due do this design we\nhave to create a new `struct reference` whenever we want to hand over\nthat reference to the callback function, which is tedious and not very\nefficient.\n\nConvert the structure to instead contain a `stuct reference` as member.\nThis member is expected to be populated by the implementations of the\niterator and is handed over to the callback directly.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c                  |  8 ++++----\n refs/debug.c            |  8 +++-----\n refs/files-backend.c    | 47 +++++++++++++++++++++--------------------------\n refs/iterator.c         | 39 ++++++++++++---------------------------\n refs/packed-backend.c   | 46 +++++++++++++++++++++++-----------------------\n refs/ref-cache.c        | 10 +++++-----\n refs/refs-internal.h    |  5 +----\n refs/reftable-backend.c | 12 ++++++------\n 8 files changed, 75 insertions(+), 100 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 262849301d..15ad0ef7a8 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2326,8 +2326,8 @@ int refs_optimize(struct ref_store *refs, struct pack_refs_opts *opts)\n int peel_iterated_oid(struct repository *r, const struct object_id *base, struct object_id *peeled)\n {\n \tif (current_ref_iter &&\n-\t    (current_ref_iter->oid == base ||\n-\t     oideq(current_ref_iter->oid, base)))\n+\t    (current_ref_iter->ref.oid == base ||\n+\t     oideq(current_ref_iter->ref.oid, base)))\n \t\treturn ref_iterator_peel(current_ref_iter, peeled);\n \n \treturn peel_object(r, base, peeled) ? -1 : 0;\n@@ -2702,7 +2702,7 @@ enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs\n \n \t\t\twhile ((ok = ref_iterator_advance(iter)) == ITER_OK) {\n \t\t\t\tif (skip &&\n-\t\t\t\t    string_list_has_string(skip, iter->refname))\n+\t\t\t\t    string_list_has_string(skip, iter->ref.name))\n \t\t\t\t\tcontinue;\n \n \t\t\t\tif (transaction && ref_transaction_maybe_set_rejected(\n@@ -2711,7 +2711,7 @@ enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs\n \t\t\t\t\tcontinue;\n \n \t\t\t\tstrbuf_addf(err, _(\"'%s' exists; cannot create '%s'\"),\n-\t\t\t\t\t    iter->refname, refname);\n+\t\t\t\t\t    iter->ref.name, refname);\n \t\t\t\tgoto cleanup;\n \t\t\t}\n \ndiff --git a/refs/debug.c b/refs/debug.c\nindex 1cb955961e..7a26035617 100644\n--- a/refs/debug.c\n+++ b/refs/debug.c\n@@ -161,11 +161,9 @@ static int debug_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\ttrace_printf_key(&trace_refs, \"iterator_advance: (%d)\\n\", res);\n \telse\n \t\ttrace_printf_key(&trace_refs, \"iterator_advance: %s (0)\\n\",\n-\t\t\tditer->iter->refname);\n+\t\t\tditer->iter->ref.name);\n \n-\tditer->base.refname = diter->iter->refname;\n-\tditer->base.oid = diter->iter->oid;\n-\tditer->base.flags = diter->iter->flags;\n+\tditer->base.ref = diter->iter->ref;\n \treturn res;\n }\n \n@@ -186,7 +184,7 @@ static int debug_ref_iterator_peel(struct ref_iterator *ref_iterator,\n \tstruct debug_ref_iterator *diter =\n \t\t(struct debug_ref_iterator *)ref_iterator;\n \tint res = diter->iter->vtable->peel(diter->iter, peeled);\n-\ttrace_printf_key(&trace_refs, \"iterator_peel: %s: %d\\n\", diter->iter->refname, res);\n+\ttrace_printf_key(&trace_refs, \"iterator_peel: %s: %d\\n\", diter->iter->ref.name, res);\n \treturn res;\n }\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 0ddcf22aed..d34fbe55d6 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -962,26 +962,23 @@ static int files_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \n \twhile ((ok = ref_iterator_advance(iter->iter0)) == ITER_OK) {\n \t\tif (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY &&\n-\t\t    parse_worktree_ref(iter->iter0->refname, NULL, NULL,\n+\t\t    parse_worktree_ref(iter->iter0->ref.name, NULL, NULL,\n \t\t\t\t       NULL) != REF_WORKTREE_CURRENT)\n \t\t\tcontinue;\n \n \t\tif ((iter->flags & DO_FOR_EACH_OMIT_DANGLING_SYMREFS) &&\n-\t\t    (iter->iter0->flags & REF_ISSYMREF) &&\n-\t\t    (iter->iter0->flags & REF_ISBROKEN))\n+\t\t    (iter->iter0->ref.flags & REF_ISSYMREF) &&\n+\t\t    (iter->iter0->ref.flags & REF_ISBROKEN))\n \t\t\tcontinue;\n \n \t\tif (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&\n-\t\t    !ref_resolves_to_object(iter->iter0->refname,\n+\t\t    !ref_resolves_to_object(iter->iter0->ref.name,\n \t\t\t\t\t    iter->repo,\n-\t\t\t\t\t    iter->iter0->oid,\n-\t\t\t\t\t    iter->iter0->flags))\n+\t\t\t\t\t    iter->iter0->ref.oid,\n+\t\t\t\t\t    iter->iter0->ref.flags))\n \t\t\tcontinue;\n \n-\t\titer->base.refname = iter->iter0->refname;\n-\t\titer->base.oid = iter->iter0->oid;\n-\t\titer->base.flags = iter->iter0->flags;\n-\t\titer->base.referent = iter->iter0->referent;\n+\t\titer->base.ref = iter->iter0->ref;\n \n \t\treturn ITER_OK;\n \t}\n@@ -1368,30 +1365,29 @@ static void prune_refs(struct files_ref_store *refs, struct ref_to_prune **refs_\n  * Return true if the specified reference should be packed.\n  */\n static int should_pack_ref(struct files_ref_store *refs,\n-\t\t\t   const char *refname,\n-\t\t\t   const struct object_id *oid, unsigned int ref_flags,\n+\t\t\t   const struct reference *ref,\n \t\t\t   struct pack_refs_opts *opts)\n {\n \tstruct string_list_item *item;\n \n \t/* Do not pack per-worktree refs: */\n-\tif (parse_worktree_ref(refname, NULL, NULL, NULL) !=\n+\tif (parse_worktree_ref(ref->name, NULL, NULL, NULL) !=\n \t    REF_WORKTREE_SHARED)\n \t\treturn 0;\n \n \t/* Do not pack symbolic refs: */\n-\tif (ref_flags & REF_ISSYMREF)\n+\tif (ref->flags & REF_ISSYMREF)\n \t\treturn 0;\n \n \t/* Do not pack broken refs: */\n-\tif (!ref_resolves_to_object(refname, refs->base.repo, oid, ref_flags))\n+\tif (!ref_resolves_to_object(ref->name, refs->base.repo, ref->oid, ref->flags))\n \t\treturn 0;\n \n-\tif (ref_excluded(opts->exclusions, refname))\n+\tif (ref_excluded(opts->exclusions, ref->name))\n \t\treturn 0;\n \n \tfor_each_string_list_item(item, opts->includes)\n-\t\tif (!wildmatch(item->string, refname, 0))\n+\t\tif (!wildmatch(item->string, ref->name, 0))\n \t\t\treturn 1;\n \n \treturn 0;\n@@ -1444,8 +1440,7 @@ static int should_pack_refs(struct files_ref_store *refs,\n \titer = cache_ref_iterator_begin(get_loose_ref_cache(refs, 0), NULL,\n \t\t\t\t\trefs->base.repo, 0);\n \twhile ((ret = ref_iterator_advance(iter)) == ITER_OK) {\n-\t\tif (should_pack_ref(refs, iter->refname, iter->oid,\n-\t\t\t\t    iter->flags, opts))\n+\t\tif (should_pack_ref(refs, &iter->ref, opts))\n \t\t\trefcount++;\n \t\tif (refcount >= limit) {\n \t\t\tref_iterator_free(iter);\n@@ -1490,24 +1485,24 @@ static int files_pack_refs(struct ref_store *ref_store,\n \t\t * in the packed ref cache. If the reference should be\n \t\t * pruned, also add it to refs_to_prune.\n \t\t */\n-\t\tif (!should_pack_ref(refs, iter->refname, iter->oid, iter->flags, opts))\n+\t\tif (!should_pack_ref(refs, &iter->ref, opts))\n \t\t\tcontinue;\n \n \t\t/*\n \t\t * Add a reference creation for this reference to the\n \t\t * packed-refs transaction:\n \t\t */\n-\t\tif (ref_transaction_update(transaction, iter->refname,\n-\t\t\t\t\t   iter->oid, NULL, NULL, NULL,\n+\t\tif (ref_transaction_update(transaction, iter->ref.name,\n+\t\t\t\t\t   iter->ref.oid, NULL, NULL, NULL,\n \t\t\t\t\t   REF_NO_DEREF, NULL, &err))\n \t\t\tdie(\"failure preparing to create packed reference %s: %s\",\n-\t\t\t    iter->refname, err.buf);\n+\t\t\t    iter->ref.name, err.buf);\n \n \t\t/* Schedule the loose reference for pruning if requested. */\n \t\tif ((opts->flags & PACK_REFS_PRUNE)) {\n \t\t\tstruct ref_to_prune *n;\n-\t\t\tFLEX_ALLOC_STR(n, name, iter->refname);\n-\t\t\toidcpy(&n->oid, iter->oid);\n+\t\t\tFLEX_ALLOC_STR(n, name, iter->ref.name);\n+\t\t\toidcpy(&n->oid, iter->ref.oid);\n \t\t\tn->next = refs_to_prune;\n \t\t\trefs_to_prune = n;\n \t\t}\n@@ -2380,7 +2375,7 @@ static int files_reflog_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t\t\t\t REFNAME_ALLOW_ONELEVEL))\n \t\t\tcontinue;\n \n-\t\titer->base.refname = diter->relative_path;\n+\t\titer->base.ref.name = diter->relative_path;\n \t\treturn ITER_OK;\n \t}\n \ndiff --git a/refs/iterator.c b/refs/iterator.c\nindex 7f2e718f1c..fe5980e1b6 100644\n--- a/refs/iterator.c\n+++ b/refs/iterator.c\n@@ -41,10 +41,7 @@ void base_ref_iterator_init(struct ref_iterator *iter,\n \t\t\t    struct ref_iterator_vtable *vtable)\n {\n \titer->vtable = vtable;\n-\titer->refname = NULL;\n-\titer->referent = NULL;\n-\titer->oid = NULL;\n-\titer->flags = 0;\n+\tmemset(&iter->ref, 0, sizeof(iter->ref));\n }\n \n struct empty_ref_iterator {\n@@ -127,8 +124,8 @@ enum iterator_selection ref_iterator_select(struct ref_iterator *iter_worktree,\n \t\t * latter.\n \t\t */\n \t\tif (iter_worktree) {\n-\t\t\tint cmp = strcmp(iter_worktree->refname,\n-\t\t\t\t\t iter_common->refname);\n+\t\t\tint cmp = strcmp(iter_worktree->ref.name,\n+\t\t\t\t\t iter_common->ref.name);\n \t\t\tif (cmp < 0)\n \t\t\t\treturn ITER_SELECT_0;\n \t\t\telse if (!cmp)\n@@ -139,7 +136,7 @@ enum iterator_selection ref_iterator_select(struct ref_iterator *iter_worktree,\n \t\t  * We now know that the lexicographically-next ref is a common\n \t\t  * ref. When the common ref is a shared one we return it.\n \t\t  */\n-\t\tif (parse_worktree_ref(iter_common->refname, NULL, NULL,\n+\t\tif (parse_worktree_ref(iter_common->ref.name, NULL, NULL,\n \t\t\t\t       NULL) == REF_WORKTREE_SHARED)\n \t\t\treturn ITER_SELECT_1;\n \n@@ -212,10 +209,7 @@ static int merge_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t}\n \n \t\tif (selection & ITER_YIELD_CURRENT) {\n-\t\t\titer->base.referent = (*iter->current)->referent;\n-\t\t\titer->base.refname = (*iter->current)->refname;\n-\t\t\titer->base.oid = (*iter->current)->oid;\n-\t\t\titer->base.flags = (*iter->current)->flags;\n+\t\t\titer->base.ref = (*iter->current)->ref;\n \t\t\treturn ITER_OK;\n \t\t}\n \t}\n@@ -313,7 +307,7 @@ static enum iterator_selection overlay_iterator_select(\n \telse if (!front)\n \t\treturn ITER_SELECT_1;\n \n-\tcmp = strcmp(front->refname, back->refname);\n+\tcmp = strcmp(front->ref.name, back->ref.name);\n \n \tif (cmp < 0)\n \t\treturn ITER_SELECT_0;\n@@ -371,7 +365,7 @@ static int prefix_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \tint ok;\n \n \twhile ((ok = ref_iterator_advance(iter->iter0)) == ITER_OK) {\n-\t\tint cmp = compare_prefix(iter->iter0->refname, iter->prefix);\n+\t\tint cmp = compare_prefix(iter->iter0->ref.name, iter->prefix);\n \t\tif (cmp < 0)\n \t\t\tcontinue;\n \t\t/*\n@@ -382,6 +376,8 @@ static int prefix_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\tif (cmp > 0)\n \t\t\treturn ITER_DONE;\n \n+\t\titer->base.ref = iter->iter0->ref;\n+\n \t\tif (iter->trim) {\n \t\t\t/*\n \t\t\t * It is nonsense to trim off characters that\n@@ -392,15 +388,11 @@ static int prefix_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t\t * one character left in the refname after\n \t\t\t * trimming, report it as a bug:\n \t\t\t */\n-\t\t\tif (strlen(iter->iter0->refname) <= iter->trim)\n+\t\t\tif (strlen(iter->base.ref.name) <= iter->trim)\n \t\t\t\tBUG(\"attempt to trim too many characters\");\n-\t\t\titer->base.refname = iter->iter0->refname + iter->trim;\n-\t\t} else {\n-\t\t\titer->base.refname = iter->iter0->refname;\n+\t\t\titer->base.ref.name += iter->trim;\n \t\t}\n \n-\t\titer->base.oid = iter->iter0->oid;\n-\t\titer->base.flags = iter->iter0->flags;\n \t\treturn ITER_OK;\n \t}\n \n@@ -476,14 +468,7 @@ int do_for_each_ref_iterator(struct ref_iterator *iter,\n \n \tcurrent_ref_iter = iter;\n \twhile ((ok = ref_iterator_advance(iter)) == ITER_OK) {\n-\t\tstruct reference ref = {\n-\t\t\t.name = iter->refname,\n-\t\t\t.target = iter->referent,\n-\t\t\t.oid = iter->oid,\n-\t\t\t.flags = iter->flags,\n-\t\t};\n-\n-\t\tretval = fn(&ref, cb_data);\n+\t\tretval = fn(&iter->ref, cb_data);\n \t\tif (retval)\n \t\t\tgoto out;\n \t}\ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex a8c22a0a7f..7987acdc96 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -908,7 +908,7 @@ static int next_record(struct packed_ref_iterator *iter)\n \tif (iter->pos == iter->eof)\n \t\treturn ITER_DONE;\n \n-\titer->base.flags = REF_ISPACKED;\n+\titer->base.ref.flags = REF_ISPACKED;\n \tp = iter->pos;\n \n \tif (iter->eof - p < snapshot_hexsz(iter->snapshot) + 2 ||\n@@ -923,22 +923,22 @@ static int next_record(struct packed_ref_iterator *iter)\n \t\t\t\t      iter->pos, iter->eof - iter->pos);\n \n \tstrbuf_add(&iter->refname_buf, p, eol - p);\n-\titer->base.refname = iter->refname_buf.buf;\n+\titer->base.ref.name = iter->refname_buf.buf;\n \n \tif (refname_contains_nul(&iter->refname_buf))\n-\t\tdie(\"packed refname contains embedded NULL: %s\", iter->base.refname);\n+\t\tdie(\"packed refname contains embedded NULL: %s\", iter->base.ref.name);\n \n-\tif (check_refname_format(iter->base.refname, REFNAME_ALLOW_ONELEVEL)) {\n-\t\tif (!refname_is_safe(iter->base.refname))\n+\tif (check_refname_format(iter->base.ref.name, REFNAME_ALLOW_ONELEVEL)) {\n+\t\tif (!refname_is_safe(iter->base.ref.name))\n \t\t\tdie(\"packed refname is dangerous: %s\",\n-\t\t\t    iter->base.refname);\n+\t\t\t    iter->base.ref.name);\n \t\toidclr(&iter->oid, iter->repo->hash_algo);\n-\t\titer->base.flags |= REF_BAD_NAME | REF_ISBROKEN;\n+\t\titer->base.ref.flags |= REF_BAD_NAME | REF_ISBROKEN;\n \t}\n \tif (iter->snapshot->peeled == PEELED_FULLY ||\n \t    (iter->snapshot->peeled == PEELED_TAGS &&\n-\t     starts_with(iter->base.refname, \"refs/tags/\")))\n-\t\titer->base.flags |= REF_KNOWS_PEELED;\n+\t     starts_with(iter->base.ref.name, \"refs/tags/\")))\n+\t\titer->base.ref.flags |= REF_KNOWS_PEELED;\n \n \titer->pos = eol + 1;\n \n@@ -956,11 +956,11 @@ static int next_record(struct packed_ref_iterator *iter)\n \t\t * definitely know the value of *this* reference. But\n \t\t * we suppress it if the reference is broken:\n \t\t */\n-\t\tif ((iter->base.flags & REF_ISBROKEN)) {\n+\t\tif ((iter->base.ref.flags & REF_ISBROKEN)) {\n \t\t\toidclr(&iter->peeled, iter->repo->hash_algo);\n-\t\t\titer->base.flags &= ~REF_KNOWS_PEELED;\n+\t\t\titer->base.ref.flags &= ~REF_KNOWS_PEELED;\n \t\t} else {\n-\t\t\titer->base.flags |= REF_KNOWS_PEELED;\n+\t\t\titer->base.ref.flags |= REF_KNOWS_PEELED;\n \t\t}\n \t} else {\n \t\toidclr(&iter->peeled, iter->repo->hash_algo);\n@@ -976,15 +976,15 @@ static int packed_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \tint ok;\n \n \twhile ((ok = next_record(iter)) == ITER_OK) {\n-\t\tconst char *refname = iter->base.refname;\n+\t\tconst char *refname = iter->base.ref.name;\n \t\tconst char *prefix = iter->prefix;\n \n \t\tif (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY &&\n-\t\t    !is_per_worktree_ref(iter->base.refname))\n+\t\t    !is_per_worktree_ref(iter->base.ref.name))\n \t\t\tcontinue;\n \n \t\tif (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&\n-\t\t    !ref_resolves_to_object(iter->base.refname, iter->repo,\n+\t\t    !ref_resolves_to_object(iter->base.ref.name, iter->repo,\n \t\t\t\t\t    &iter->oid, iter->flags))\n \t\t\tcontinue;\n \n@@ -1033,10 +1033,10 @@ static int packed_ref_iterator_peel(struct ref_iterator *ref_iterator,\n \tstruct packed_ref_iterator *iter =\n \t\t(struct packed_ref_iterator *)ref_iterator;\n \n-\tif ((iter->base.flags & REF_KNOWS_PEELED)) {\n+\tif ((iter->base.ref.flags & REF_KNOWS_PEELED)) {\n \t\toidcpy(peeled, &iter->peeled);\n \t\treturn is_null_oid(&iter->peeled) ? -1 : 0;\n-\t} else if ((iter->base.flags & (REF_ISBROKEN | REF_ISSYMREF))) {\n+\t} else if ((iter->base.ref.flags & (REF_ISBROKEN | REF_ISSYMREF))) {\n \t\treturn -1;\n \t} else {\n \t\treturn peel_object(iter->repo, &iter->oid, peeled) ? -1 : 0;\n@@ -1194,7 +1194,7 @@ static struct ref_iterator *packed_ref_iterator_begin(\n \titer->snapshot = snapshot;\n \tacquire_snapshot(snapshot);\n \tstrbuf_init(&iter->refname_buf, 0);\n-\titer->base.oid = &iter->oid;\n+\titer->base.ref.oid = &iter->oid;\n \titer->repo = ref_store->repo;\n \titer->flags = flags;\n \n@@ -1436,7 +1436,7 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re\n \t\t\tif (!iter)\n \t\t\t\tcmp = +1;\n \t\t\telse\n-\t\t\t\tcmp = strcmp(iter->refname, update->refname);\n+\t\t\t\tcmp = strcmp(iter->ref.name, update->refname);\n \t\t}\n \n \t\tif (!cmp) {\n@@ -1459,11 +1459,11 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re\n \t\t\t\t\t}\n \n \t\t\t\t\tgoto error;\n-\t\t\t\t} else if (!oideq(&update->old_oid, iter->oid)) {\n+\t\t\t\t} else if (!oideq(&update->old_oid, iter->ref.oid)) {\n \t\t\t\t\tstrbuf_addf(err, \"cannot update ref '%s': \"\n \t\t\t\t\t\t    \"is at %s but expected %s\",\n \t\t\t\t\t\t    update->refname,\n-\t\t\t\t\t\t    oid_to_hex(iter->oid),\n+\t\t\t\t\t\t    oid_to_hex(iter->ref.oid),\n \t\t\t\t\t\t    oid_to_hex(&update->old_oid));\n \t\t\t\t\tret = REF_TRANSACTION_ERROR_INCORRECT_OLD_VALUE;\n \n@@ -1527,8 +1527,8 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re\n \t\t\tstruct object_id peeled;\n \t\t\tint peel_error = ref_iterator_peel(iter, &peeled);\n \n-\t\t\tif (write_packed_entry(out, iter->refname,\n-\t\t\t\t\t       iter->oid,\n+\t\t\tif (write_packed_entry(out, iter->ref.name,\n+\t\t\t\t\t       iter->ref.oid,\n \t\t\t\t\t       peel_error ? NULL : &peeled))\n \t\t\t\tgoto write_error;\n \ndiff --git a/refs/ref-cache.c b/refs/ref-cache.c\nindex c180e0aad7..97555fa118 100644\n--- a/refs/ref-cache.c\n+++ b/refs/ref-cache.c\n@@ -425,10 +425,10 @@ static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t\tlevel->prefix_state = entry_prefix_state;\n \t\t\tlevel->index = -1;\n \t\t} else {\n-\t\t\titer->base.refname = entry->name;\n-\t\t\titer->base.referent = entry->u.value.referent;\n-\t\t\titer->base.oid = &entry->u.value.oid;\n-\t\t\titer->base.flags = entry->flag;\n+\t\t\titer->base.ref.name = entry->name;\n+\t\t\titer->base.ref.target = entry->u.value.referent;\n+\t\t\titer->base.ref.oid = &entry->u.value.oid;\n+\t\t\titer->base.ref.flags = entry->flag;\n \t\t\treturn ITER_OK;\n \t\t}\n \t}\n@@ -550,7 +550,7 @@ static int cache_ref_iterator_peel(struct ref_iterator *ref_iterator,\n {\n \tstruct cache_ref_iterator *iter =\n \t\t(struct cache_ref_iterator *)ref_iterator;\n-\treturn peel_object(iter->repo, ref_iterator->oid, peeled) ? -1 : 0;\n+\treturn peel_object(iter->repo, ref_iterator->ref.oid, peeled) ? -1 : 0;\n }\n \n static void cache_ref_iterator_release(struct ref_iterator *ref_iterator)\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 4ef3bd75c6..ed749d1657 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -249,10 +249,7 @@ const char *find_descendant_ref(const char *dirname,\n  */\n struct ref_iterator {\n \tstruct ref_iterator_vtable *vtable;\n-\tconst char *refname;\n-\tconst char *referent;\n-\tconst struct object_id *oid;\n-\tunsigned int flags;\n+\tstruct reference ref;\n };\n \n /*\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 9884b876c1..7fbc77492e 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -703,10 +703,10 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t\t\t\t    &iter->oid, flags))\n \t\t\t\tcontinue;\n \n-\t\titer->base.refname = iter->ref.refname;\n-\t\titer->base.referent = referent;\n-\t\titer->base.oid = &iter->oid;\n-\t\titer->base.flags = flags;\n+\t\titer->base.ref.name = iter->ref.refname;\n+\t\titer->base.ref.target = referent;\n+\t\titer->base.ref.oid = &iter->oid;\n+\t\titer->base.ref.flags = flags;\n \n \t\tbreak;\n \t}\n@@ -827,7 +827,7 @@ static struct reftable_ref_iterator *ref_iterator_for_stack(struct reftable_ref_\n \n \titer = xcalloc(1, sizeof(*iter));\n \tbase_ref_iterator_init(&iter->base, &reftable_ref_iterator_vtable);\n-\titer->base.oid = &iter->oid;\n+\titer->base.ref.oid = &iter->oid;\n \titer->flags = flags;\n \titer->refs = refs;\n \titer->exclude_patterns = filter_exclude_patterns(exclude_patterns);\n@@ -2071,7 +2071,7 @@ static int reftable_reflog_iterator_advance(struct ref_iterator *ref_iterator)\n \n \t\tstrbuf_reset(&iter->last_name);\n \t\tstrbuf_addstr(&iter->last_name, iter->log.refname);\n-\t\titer->base.refname = iter->log.refname;\n+\t\titer->base.ref.name = iter->log.refname;\n \n \t\tbreak;\n \t}\n\n-- \n2.51.0.764.g787ff6f08a.dirty\n\n"},{"id":"528068","messageId":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-3-916cc7c6886b@pks.im","threadId":"64260","inReplyTo":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-0-916cc7c6886b@pks.im","subject":"[PATCH 03/13] refs: refactor reference status flags","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-07T10:58:40Z","receivedAt":"2025-10-07T10:59:07Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The reference flags encode information like whether or not a reference\nis a symbolic reference or whether it may be broken. This information is\nstored in a `int flags` bitfield, which is in conflict with our modern\nbest practices; we tend to use an unsigned integer to store flags.\n\nChange the type of the field to be `unsigned`. While at it, refactor the\nindividual flags to be part of an `enum` instead of using preprocessor\ndefines.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.h | 41 +++++++++++++++++++++--------------------\n 1 file changed, 21 insertions(+), 20 deletions(-)\n\ndiff --git a/refs.h b/refs.h\nindex 2b24a3d997..8f484e2ffc 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -333,27 +333,28 @@ struct ref_transaction;\n  * stored in ref_iterator::flags. Other bits are for internal use\n  * only:\n  */\n+enum reference_status {\n+\t/* Reference is a symbolic reference. */\n+\tREF_ISSYMREF = (1 << 0),\n \n-/* Reference is a symbolic reference. */\n-#define REF_ISSYMREF 0x01\n+\t/* Reference is a packed reference. */\n+\tREF_ISPACKED = (1 << 1),\n \n-/* Reference is a packed reference. */\n-#define REF_ISPACKED 0x02\n-\n-/*\n- * Reference cannot be resolved to an object name: dangling symbolic\n- * reference (directly or indirectly), corrupt reference file,\n- * reference exists but name is bad, or symbolic reference refers to\n- * ill-formatted reference name.\n- */\n-#define REF_ISBROKEN 0x04\n+\t/*\n+\t * Reference cannot be resolved to an object name: dangling symbolic\n+\t * reference (directly or indirectly), corrupt reference file,\n+\t * reference exists but name is bad, or symbolic reference refers to\n+\t * ill-formatted reference name.\n+\t */\n+\tREF_ISBROKEN = (1 << 2),\n \n-/*\n- * Reference name is not well formed.\n- *\n- * See git-check-ref-format(1) for the definition of well formed ref names.\n- */\n-#define REF_BAD_NAME 0x08\n+\t/*\n+\t * Reference name is not well formed.\n+\t *\n+\t * See git-check-ref-format(1) for the definition of well formed ref names.\n+\t */\n+\tREF_BAD_NAME = (1 << 3),\n+};\n \n /* A reference passed to `for_each_ref()`-style callbacks. */\n struct reference {\n@@ -370,8 +371,8 @@ struct reference {\n \t */\n \tconst struct object_id *oid;\n \n-\t/* A bitfield of `REF_` flags. */\n-\tint flags;\n+\t/* A bitfield of `enum reference_status` flags. */\n+\tunsigned flags;\n };\n \n /*\n\n-- \n2.51.0.764.g787ff6f08a.dirty\n\n"},{"id":"528069","messageId":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-4-916cc7c6886b@pks.im","threadId":"64260","inReplyTo":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-0-916cc7c6886b@pks.im","subject":"[PATCH 04/13] refs: expose peeled object ID via the iterator","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-07T10:58:41Z","receivedAt":"2025-10-07T10:59:10Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Both the \"files\" and \"reftable\" backend are able to store peeled values\nfor tags in the respective formats. This allows for a more efficient\nlookup of the target object of such a tag without having to manually\npeel via the object database.\n\nThe infrastructure to access these peeled object IDs is somewhat funky\nthough. When iterating through objects, we store a pointer reference to\nthe current iterator in a global variable. The callbacks invoked by that\niterator are then expected to call `peel_iterated_oid()`, which checks\nwhether the globally-stored iterator's current reference refers to the\none handed into that function. If so, we ask the iterator to peel the\nobject, otherwise we manually peel the object via the object database.\nDepending on global state like this is somewhat weird and also quite\nfragile.\n\nIntroduce a new `struct reference::peeled_oid` field that can be\npopulated by the reference backends. This field can be accessed via a\nnew function `reference_get_peeled_oid()` that either uses that value,\nif set, or alternatively peels via the ODB. With this change we don't\nhave to rely on global state anymore, but make the peeled object ID\navailable to the callback functions directly.\n\nAdjust trivial callers that already have a `struct reference` available.\nRemaining callers will be adjusted in subsequent commits.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/describe.c      |  2 +-\n builtin/gc.c            |  2 +-\n builtin/pack-objects.c  |  7 ++++---\n builtin/repack.c        |  2 +-\n commit-graph.c          |  2 +-\n ls-refs.c               |  2 +-\n midx-write.c            |  2 +-\n pseudo-merge.c          |  2 +-\n refs.c                  | 12 ++++++++++++\n refs.h                  | 19 +++++++++++++++++++\n refs/packed-backend.c   |  3 +++\n refs/ref-cache.c        |  1 +\n refs/reftable-backend.c |  7 +++++++\n 13 files changed, 53 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/describe.c b/builtin/describe.c\nindex 7954535044..443546aaac 100644\n--- a/builtin/describe.c\n+++ b/builtin/describe.c\n@@ -208,7 +208,7 @@ static int get_name(const struct reference *ref, void *cb_data UNUSED)\n \t}\n \n \t/* Is it annotated? */\n-\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled)) {\n+\tif (!reference_get_peeled_oid(the_repository, ref, &peeled)) {\n \t\tis_annotated = !oideq(ref->oid, &peeled);\n \t} else {\n \t\toidcpy(&peeled, ref->oid);\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex a104c1cb78..814be8d1a9 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -1107,7 +1107,7 @@ static int dfs_on_ref(const struct reference *ref, void *cb_data)\n \tstruct commit_list *stack = NULL;\n \tstruct commit *commit;\n \n-\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n+\tif (!reference_get_peeled_oid(the_repository, ref, &peeled))\n \t\tmaybe_peeled = &peeled;\n \tif (odb_read_object_info(the_repository->objects, maybe_peeled, NULL) != OBJ_COMMIT)\n \t\treturn 0;\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 288ba19981..7fd457779d 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -838,7 +838,7 @@ static int mark_tagged(const struct reference *ref, void *cb_data UNUSED)\n \n \tif (entry)\n \t\tentry->tagged = 1;\n-\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled)) {\n+\tif (!reference_get_peeled_oid(the_repository, ref, &peeled)) {\n \t\tentry = packlist_find(&to_pack, &peeled);\n \t\tif (entry)\n \t\t\tentry->tagged = 1;\n@@ -3309,7 +3309,8 @@ static int add_ref_tag(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct object_id peeled;\n \n-\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled) && obj_is_packed(&peeled))\n+\tif (!reference_get_peeled_oid(the_repository, ref, &peeled) &&\n+\t    obj_is_packed(&peeled))\n \t\tadd_tag_chain(ref->oid);\n \treturn 0;\n }\n@@ -4532,7 +4533,7 @@ static int mark_bitmap_preferred_tip(const struct reference *ref, void *data UNU\n \tstruct object_id peeled;\n \tstruct object *object;\n \n-\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n+\tif (!reference_get_peeled_oid(the_repository, ref, &peeled))\n \t\tmaybe_peeled = &peeled;\n \n \tobject = parse_object_or_die(the_repository, maybe_peeled, ref->name);\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 893c105c81..873e21c35d 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -779,7 +779,7 @@ static int midx_snapshot_ref_one(const struct reference *ref, void *_data)\n \tconst struct object_id *maybe_peeled = ref->oid;\n \tstruct object_id peeled;\n \n-\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n+\tif (!reference_get_peeled_oid(the_repository, ref, &peeled))\n \t\tmaybe_peeled = &peeled;\n \n \tif (oidset_insert(&data->seen, maybe_peeled))\ndiff --git a/commit-graph.c b/commit-graph.c\nindex 0cfe16ad08..bfd8b187f0 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -1852,7 +1852,7 @@ static int add_ref_to_set(const struct reference *ref, void *cb_data)\n \tstruct object_id peeled;\n \tstruct refs_cb_data *data = (struct refs_cb_data *)cb_data;\n \n-\tif (!peel_iterated_oid(data->repo, ref->oid, &peeled))\n+\tif (!reference_get_peeled_oid(data->repo, ref, &peeled))\n \t\tmaybe_peeled = &peeled;\n \tif (odb_read_object_info(data->repo->objects, maybe_peeled, NULL) == OBJ_COMMIT)\n \t\toidset_insert(data->commits, maybe_peeled);\ndiff --git a/ls-refs.c b/ls-refs.c\nindex 64d0272369..8641281b86 100644\n--- a/ls-refs.c\n+++ b/ls-refs.c\n@@ -110,7 +110,7 @@ static int send_ref(const struct reference *ref, void *cb_data)\n \n \tif (data->peel && ref->oid) {\n \t\tstruct object_id peeled;\n-\t\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n+\t\tif (!reference_get_peeled_oid(the_repository, ref, &peeled))\n \t\t\tstrbuf_addf(&data->buf, \" peeled:%s\", oid_to_hex(&peeled));\n \t}\n \ndiff --git a/midx-write.c b/midx-write.c\nindex f4dd875747..23e61cb000 100644\n--- a/midx-write.c\n+++ b/midx-write.c\n@@ -709,7 +709,7 @@ static int add_ref_to_pending(const struct reference *ref, void *cb_data)\n \t\treturn 0;\n \t}\n \n-\tif (!peel_iterated_oid(revs->repo, ref->oid, &peeled))\n+\tif (!reference_get_peeled_oid(revs->repo, ref, &peeled))\n \t\tmaybe_peeled = &peeled;\n \n \tobject = parse_object_or_die(revs->repo, maybe_peeled, ref->name);\ndiff --git a/pseudo-merge.c b/pseudo-merge.c\nindex 0abd51b42c..a2d5bd85f9 100644\n--- a/pseudo-merge.c\n+++ b/pseudo-merge.c\n@@ -230,7 +230,7 @@ static int find_pseudo_merge_group_for_ref(const struct reference *ref, void *_d\n \tuint32_t i;\n \tint has_bitmap;\n \n-\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n+\tif (!reference_get_peeled_oid(the_repository, ref, &peeled))\n \t\tmaybe_peeled = &peeled;\n \n \tc = lookup_commit(the_repository, maybe_peeled);\ndiff --git a/refs.c b/refs.c\nindex 15ad0ef7a8..5002e56435 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2333,6 +2333,18 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct\n \treturn peel_object(r, base, peeled) ? -1 : 0;\n }\n \n+int reference_get_peeled_oid(struct repository *repo,\n+\t\t\t     const struct reference *ref,\n+\t\t\t     struct object_id *peeled_oid)\n+{\n+\tif (ref->peeled_oid) {\n+\t\toidcpy(peeled_oid, ref->peeled_oid);\n+\t\treturn 0;\n+\t}\n+\n+\treturn peel_object(repo, ref->oid, peeled_oid) ? -1 : 0;\n+}\n+\n int refs_update_symref(struct ref_store *refs, const char *ref,\n \t\t       const char *target, const char *logmsg)\n {\ndiff --git a/refs.h b/refs.h\nindex 8f484e2ffc..f1e023df35 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -371,10 +371,29 @@ struct reference {\n \t */\n \tconst struct object_id *oid;\n \n+\t/*\n+\t * An optional peeled object ID. This field _may_ be set for tags in\n+\t * case the peeled value is present in the backend. Please refer to\n+\t * `reference_get_peeled_oid()`.\n+\t */\n+\tconst struct object_id *peeled_oid;\n+\n \t/* A bitfield of `enum reference_status` flags. */\n \tunsigned flags;\n };\n \n+/*\n+ * Peel the tag to a non-tag commit. If present, this uses the peeled object ID\n+ * exposed by the reference backend. Otherwise, the object is peeled via the\n+ * object database, which is less efficient.\n+ *\n+ * Return `0` if the reference could be peeled, a negative error code\n+ * otherwise.\n+ */\n+int reference_get_peeled_oid(struct repository *repo,\n+\t\t\t     const struct reference *ref,\n+\t\t\t     struct object_id *peeled_oid);\n+\n /*\n  * The signature for the callback function for the for_each_*()\n  * functions below.  The memory pointed to by the refname and oid\ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 7987acdc96..7922d63acc 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -959,11 +959,14 @@ static int next_record(struct packed_ref_iterator *iter)\n \t\tif ((iter->base.ref.flags & REF_ISBROKEN)) {\n \t\t\toidclr(&iter->peeled, iter->repo->hash_algo);\n \t\t\titer->base.ref.flags &= ~REF_KNOWS_PEELED;\n+\t\t\titer->base.ref.peeled_oid = NULL;\n \t\t} else {\n \t\t\titer->base.ref.flags |= REF_KNOWS_PEELED;\n+\t\t\titer->base.ref.peeled_oid = &iter->peeled;\n \t\t}\n \t} else {\n \t\toidclr(&iter->peeled, iter->repo->hash_algo);\n+\t\titer->base.ref.peeled_oid = NULL;\n \t}\n \n \treturn ITER_OK;\ndiff --git a/refs/ref-cache.c b/refs/ref-cache.c\nindex 97555fa118..2f46f650a6 100644\n--- a/refs/ref-cache.c\n+++ b/refs/ref-cache.c\n@@ -428,6 +428,7 @@ static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t\titer->base.ref.name = entry->name;\n \t\t\titer->base.ref.target = entry->u.value.referent;\n \t\t\titer->base.ref.oid = &entry->u.value.oid;\n+\t\t\titer->base.ref.peeled_oid = NULL;\n \t\t\titer->base.ref.flags = entry->flag;\n \t\t\treturn ITER_OK;\n \t\t}\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 7fbc77492e..f93ab96358 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -546,6 +546,7 @@ struct reftable_ref_iterator {\n \tstruct reftable_iterator iter;\n \tstruct reftable_ref_record ref;\n \tstruct object_id oid;\n+\tstruct object_id peeled_oid;\n \n \tchar *prefix;\n \tsize_t prefix_len;\n@@ -670,6 +671,8 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\tcase REFTABLE_REF_VAL2:\n \t\t\toidread(&iter->oid, iter->ref.value.val2.value,\n \t\t\t\trefs->base.repo->hash_algo);\n+\t\t\toidread(&iter->peeled_oid, iter->ref.value.val2.target_value,\n+\t\t\t\trefs->base.repo->hash_algo);\n \t\t\tbreak;\n \t\tcase REFTABLE_REF_SYMREF:\n \t\t\treferent = refs_resolve_ref_unsafe(&iter->refs->base,\n@@ -706,6 +709,10 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\titer->base.ref.name = iter->ref.refname;\n \t\titer->base.ref.target = referent;\n \t\titer->base.ref.oid = &iter->oid;\n+\t\tif (iter->ref.value_type == REFTABLE_REF_VAL2)\n+\t\t\titer->base.ref.peeled_oid = &iter->peeled_oid;\n+\t\telse\n+\t\t\titer->base.ref.peeled_oid = NULL;\n \t\titer->base.ref.flags = flags;\n \n \t\tbreak;\n\n-- \n2.51.0.764.g787ff6f08a.dirty\n\n"},{"id":"528070","messageId":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-5-916cc7c6886b@pks.im","threadId":"64260","inReplyTo":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-0-916cc7c6886b@pks.im","subject":"[PATCH 05/13] upload-pack: convert to use `reference_get_peeled_oid()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-07T10:58:42Z","receivedAt":"2025-10-07T10:59:13Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `write_v0_ref()` callback is invoked from two callsites:\n\n  - Once via `send_ref()` which is a callback passed to\n    `for_each_namespaced_ref_1()`.\n\n  - Once manually to announce capabilities.\n\nWhen sending references to the client we also send the peeled value of\ntags. As we don't have a `struct reference` available in the second\ncase, we cannot easily peel by calling `reference_get_peeled_oid()`, but\nwe instead have to depend on on global state via `peel_iterated_oid()`.\n\nWe do have a reference available though in the first case, it's only the\nsecond case that keeps us from using `reference_get_peeled_oid()`. But\nthat second case only announces capabilities anyway, so we're not really\nhandling a reference at all here.\n\nAdapt that case to construct a reference manually and pass that to\n`write_v0_ref()`. Start to use `reference_get_peeled_oid()` now that we\nalways have a `struct reference` available.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n upload-pack.c | 22 +++++++++++++---------\n 1 file changed, 13 insertions(+), 9 deletions(-)\n\ndiff --git a/upload-pack.c b/upload-pack.c\nindex 0d563ae74e..2d2b70cbf2 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -1249,15 +1249,15 @@ static void format_session_id(struct strbuf *buf, struct upload_pack_data *d) {\n }\n \n static void write_v0_ref(struct upload_pack_data *data,\n-\t\t\tconst char *refname, const char *refname_nons,\n-\t\t\tconst struct object_id *oid)\n+\t\t\t const struct reference *ref,\n+\t\t\t const char *refname_nons)\n {\n \tstatic const char *capabilities = \"multi_ack thin-pack side-band\"\n \t\t\" side-band-64k ofs-delta shallow deepen-since deepen-not\"\n \t\t\" deepen-relative no-progress include-tag multi_ack_detailed\";\n \tstruct object_id peeled;\n \n-\tif (mark_our_ref(refname_nons, refname, oid, &data->hidden_refs))\n+\tif (mark_our_ref(refname_nons, ref->name, ref->oid, &data->hidden_refs))\n \t\treturn;\n \n \tif (capabilities) {\n@@ -1267,7 +1267,7 @@ static void write_v0_ref(struct upload_pack_data *data,\n \t\tformat_symref_info(&symref_info, &data->symref);\n \t\tformat_session_id(&session_id, data);\n \t\tpacket_fwrite_fmt(stdout, \"%s %s%c%s%s%s%s%s%s%s object-format=%s agent=%s\\n\",\n-\t\t\t     oid_to_hex(oid), refname_nons,\n+\t\t\t     oid_to_hex(ref->oid), refname_nons,\n \t\t\t     0, capabilities,\n \t\t\t     (data->allow_uor & ALLOW_TIP_SHA1) ?\n \t\t\t\t     \" allow-tip-sha1-in-want\" : \"\",\n@@ -1283,17 +1283,17 @@ static void write_v0_ref(struct upload_pack_data *data,\n \t\tstrbuf_release(&session_id);\n \t\tdata->sent_capabilities = 1;\n \t} else {\n-\t\tpacket_fwrite_fmt(stdout, \"%s %s\\n\", oid_to_hex(oid), refname_nons);\n+\t\tpacket_fwrite_fmt(stdout, \"%s %s\\n\", oid_to_hex(ref->oid), refname_nons);\n \t}\n \tcapabilities = NULL;\n-\tif (!peel_iterated_oid(the_repository, oid, &peeled))\n+\tif (!reference_get_peeled_oid(the_repository, ref, &peeled))\n \t\tpacket_fwrite_fmt(stdout, \"%s %s^{}\\n\", oid_to_hex(&peeled), refname_nons);\n \treturn;\n }\n \n static int send_ref(const struct reference *ref, void *cb_data)\n {\n-\twrite_v0_ref(cb_data, ref->name, strip_namespace(ref->name), ref->oid);\n+\twrite_v0_ref(cb_data, ref, strip_namespace(ref->name));\n \treturn 0;\n }\n \n@@ -1442,8 +1442,12 @@ void upload_pack(const int advertise_refs, const int stateless_rpc,\n \t\t\t\t\t send_ref, &data);\n \t\tfor_each_namespaced_ref_1(send_ref, &data);\n \t\tif (!data.sent_capabilities) {\n-\t\t\tconst char *refname = \"capabilities^{}\";\n-\t\t\twrite_v0_ref(&data, refname, refname, null_oid(the_hash_algo));\n+\t\t\tstruct reference ref = {\n+\t\t\t\t.name = \"capabilities^{}\",\n+\t\t\t\t.oid = null_oid(the_hash_algo),\n+\t\t\t};\n+\n+\t\t\twrite_v0_ref(&data, &ref, ref.name);\n \t\t}\n \t\t/*\n \t\t * fflush stdout before calling advertise_shallow_grafts because send_ref\n\n-- \n2.51.0.764.g787ff6f08a.dirty\n\n"},{"id":"528071","messageId":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-6-916cc7c6886b@pks.im","threadId":"64260","inReplyTo":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-0-916cc7c6886b@pks.im","subject":"[PATCH 06/13] ref-filter: propagate peeled object ID","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-07T10:58:43Z","receivedAt":"2025-10-07T10:59:16Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When queueing a reference in the \"ref-filter\" subsystem we end up\ncreating a new ref array item that contains the reference's info. One\nbit of info that we always discard though is the peeled object ID, and\nbecause of that we are forced to use `peel_iterated_oid()`.\n\nRefactor the code to propagate the peeled object ID via the ref array,\nif available. This allows us to manually peel tags without having to go\nthrough the object database.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/ls-remote.c  |  2 +-\n builtin/tag.c        |  2 +-\n builtin/verify-tag.c |  2 +-\n ref-filter.c         | 66 ++++++++++++++++++++++++++++++----------------------\n ref-filter.h         |  5 +++-\n 5 files changed, 45 insertions(+), 32 deletions(-)\n\ndiff --git a/builtin/ls-remote.c b/builtin/ls-remote.c\nindex df09000b30..fe77829557 100644\n--- a/builtin/ls-remote.c\n+++ b/builtin/ls-remote.c\n@@ -156,7 +156,7 @@ int cmd_ls_remote(int argc,\n \t\t\tcontinue;\n \t\tif (!tail_match(&pattern, ref->name))\n \t\t\tcontinue;\n-\t\titem = ref_array_push(&ref_array, ref->name, &ref->old_oid);\n+\t\titem = ref_array_push(&ref_array, ref->name, &ref->old_oid, NULL);\n \t\titem->symref = xstrdup_or_null(ref->symref);\n \t}\n \ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex f0665af3ac..01eba90c5c 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -153,7 +153,7 @@ static int verify_tag(const char *name, const char *ref UNUSED,\n \t\treturn -1;\n \n \tif (format->format)\n-\t\tpretty_print_ref(name, oid, format);\n+\t\tpretty_print_ref(name, oid, NULL, format);\n \n \treturn 0;\n }\ndiff --git a/builtin/verify-tag.c b/builtin/verify-tag.c\nindex cd6bc11095..558121eaa1 100644\n--- a/builtin/verify-tag.c\n+++ b/builtin/verify-tag.c\n@@ -67,7 +67,7 @@ int cmd_verify_tag(int argc,\n \t\t}\n \n \t\tif (format.format)\n-\t\t\tpretty_print_ref(name, &oid, &format);\n+\t\t\tpretty_print_ref(name, &oid, NULL, &format);\n \t}\n \treturn had_error;\n }\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 7740f35e93..b18a032e57 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -2578,8 +2578,15 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)\n \t * If it is a tag object, see if we use the peeled value. If we do,\n \t * grab the peeled OID.\n \t */\n-\tif (need_tagged && peel_iterated_oid(the_repository, &obj->oid, &oi_deref.oid))\n-\t\tdie(\"bad tag\");\n+\tif (need_tagged) {\n+\t\tif (!is_null_oid(&ref->peeled_oid)) {\n+\t\t\toidcpy(&oi_deref.oid, &ref->peeled_oid);\n+\t\t} else if (!peel_object(the_repository, &obj->oid, &oi_deref.oid)) {\n+\t\t\t/* We managed to peel the object ourselves. */\n+\t\t} else {\n+\t\t\tdie(\"bad tag\");\n+\t\t}\n+\t}\n \n \treturn get_object(ref, 1, &obj, &oi_deref, err);\n }\n@@ -2807,12 +2814,15 @@ static int match_points_at(struct oid_array *points_at,\n  * Callers can then fill in other struct members at their leisure.\n  */\n static struct ref_array_item *new_ref_array_item(const char *refname,\n-\t\t\t\t\t\t const struct object_id *oid)\n+\t\t\t\t\t\t const struct object_id *oid,\n+\t\t\t\t\t\t const struct object_id *peeled_oid)\n {\n \tstruct ref_array_item *ref;\n \n \tFLEX_ALLOC_STR(ref, refname, refname);\n \toidcpy(&ref->objectname, oid);\n+\tif (peeled_oid)\n+\t\toidcpy(&ref->peeled_oid, peeled_oid);\n \tref->rest = NULL;\n \n \treturn ref;\n@@ -2826,9 +2836,10 @@ static void ref_array_append(struct ref_array *array, struct ref_array_item *ref\n \n struct ref_array_item *ref_array_push(struct ref_array *array,\n \t\t\t\t      const char *refname,\n-\t\t\t\t      const struct object_id *oid)\n+\t\t\t\t      const struct object_id *oid,\n+\t\t\t\t      const struct object_id *peeled_oid)\n {\n-\tstruct ref_array_item *ref = new_ref_array_item(refname, oid);\n+\tstruct ref_array_item *ref = new_ref_array_item(refname, oid, peeled_oid);\n \tref_array_append(array, ref);\n \treturn ref;\n }\n@@ -2871,25 +2882,25 @@ static int filter_ref_kind(struct ref_filter *filter, const char *refname)\n \treturn ref_kind_from_refname(refname);\n }\n \n-static struct ref_array_item *apply_ref_filter(const char *refname, const char *referent, const struct object_id *oid,\n-\t\t\t    int flag, struct ref_filter *filter)\n+static struct ref_array_item *apply_ref_filter(const struct reference *ref,\n+\t\t\t\t\t       struct ref_filter *filter)\n {\n-\tstruct ref_array_item *ref;\n+\tstruct ref_array_item *item;\n \tstruct commit *commit = NULL;\n \tunsigned int kind;\n \n-\tif (flag & REF_BAD_NAME) {\n-\t\twarning(_(\"ignoring ref with broken name %s\"), refname);\n+\tif (ref->flags & REF_BAD_NAME) {\n+\t\twarning(_(\"ignoring ref with broken name %s\"), ref->name);\n \t\treturn NULL;\n \t}\n \n-\tif (flag & REF_ISBROKEN) {\n-\t\twarning(_(\"ignoring broken ref %s\"), refname);\n+\tif (ref->flags & REF_ISBROKEN) {\n+\t\twarning(_(\"ignoring broken ref %s\"), ref->name);\n \t\treturn NULL;\n \t}\n \n \t/* Obtain the current ref kind from filter_ref_kind() and ignore unwanted refs. */\n-\tkind = filter_ref_kind(filter, refname);\n+\tkind = filter_ref_kind(filter, ref->name);\n \n \t/*\n \t * Generally HEAD refs are printed with special description denoting a rebase,\n@@ -2902,13 +2913,13 @@ static struct ref_array_item *apply_ref_filter(const char *refname, const char *\n \telse if (!(kind & filter->kind))\n \t\treturn NULL;\n \n-\tif (!filter_pattern_match(filter, refname))\n+\tif (!filter_pattern_match(filter, ref->name))\n \t\treturn NULL;\n \n-\tif (filter_exclude_match(filter, refname))\n+\tif (filter_exclude_match(filter, ref->name))\n \t\treturn NULL;\n \n-\tif (filter->points_at.nr && !match_points_at(&filter->points_at, oid, refname))\n+\tif (filter->points_at.nr && !match_points_at(&filter->points_at, ref->oid, ref->name))\n \t\treturn NULL;\n \n \t/*\n@@ -2918,7 +2929,7 @@ static struct ref_array_item *apply_ref_filter(const char *refname, const char *\n \t */\n \tif (filter->reachable_from || filter->unreachable_from ||\n \t    filter->with_commit || filter->no_commit || filter->verbose) {\n-\t\tcommit = lookup_commit_reference_gently(the_repository, oid, 1);\n+\t\tcommit = lookup_commit_reference_gently(the_repository, ref->oid, 1);\n \t\tif (!commit)\n \t\t\treturn NULL;\n \t\t/* We perform the filtering for the '--contains' option... */\n@@ -2936,13 +2947,13 @@ static struct ref_array_item *apply_ref_filter(const char *refname, const char *\n \t * to do its job and the resulting list may yet to be pruned\n \t * by maxcount logic.\n \t */\n-\tref = new_ref_array_item(refname, oid);\n-\tref->commit = commit;\n-\tref->flag = flag;\n-\tref->kind = kind;\n-\tref->symref = xstrdup_or_null(referent);\n+\titem = new_ref_array_item(ref->name, ref->oid, ref->peeled_oid);\n+\titem->commit = commit;\n+\titem->flag = ref->flags;\n+\titem->kind = kind;\n+\titem->symref = xstrdup_or_null(ref->target);\n \n-\treturn ref;\n+\treturn item;\n }\n \n struct ref_filter_cbdata {\n@@ -2959,8 +2970,7 @@ static int filter_one(const struct reference *ref, void *cb_data)\n \tstruct ref_filter_cbdata *ref_cbdata = cb_data;\n \tstruct ref_array_item *item;\n \n-\titem = apply_ref_filter(ref->name, ref->target, ref->oid,\n-\t\t\t\tref->flags, ref_cbdata->filter);\n+\titem = apply_ref_filter(ref, ref_cbdata->filter);\n \tif (item)\n \t\tref_array_append(ref_cbdata->array, item);\n \n@@ -2997,8 +3007,7 @@ static int filter_and_format_one(const struct reference *ref, void *cb_data)\n \tstruct ref_array_item *item;\n \tstruct strbuf output = STRBUF_INIT, err = STRBUF_INIT;\n \n-\titem = apply_ref_filter(ref->name, ref->target, ref->oid,\n-\t\t\t\tref->flags, ref_cbdata->filter);\n+\titem = apply_ref_filter(ref, ref_cbdata->filter);\n \tif (!item)\n \t\treturn 0;\n \n@@ -3585,13 +3594,14 @@ void print_formatted_ref_array(struct ref_array *array, struct ref_format *forma\n }\n \n void pretty_print_ref(const char *name, const struct object_id *oid,\n+\t\t      const struct object_id *peeled_oid,\n \t\t      struct ref_format *format)\n {\n \tstruct ref_array_item *ref_item;\n \tstruct strbuf output = STRBUF_INIT;\n \tstruct strbuf err = STRBUF_INIT;\n \n-\tref_item = new_ref_array_item(name, oid);\n+\tref_item = new_ref_array_item(name, oid, peeled_oid);\n \tref_item->kind = ref_kind_from_refname(name);\n \tif (format_ref_array_item(ref_item, format, &output, &err))\n \t\tdie(\"%s\", err.buf);\ndiff --git a/ref-filter.h b/ref-filter.h\nindex 81f2c229a9..1126826201 100644\n--- a/ref-filter.h\n+++ b/ref-filter.h\n@@ -41,6 +41,7 @@ enum ref_sorting_order {\n \n struct ref_array_item {\n \tstruct object_id objectname;\n+\tstruct object_id peeled_oid;\n \tconst char *rest;\n \tint flag;\n \tunsigned int kind;\n@@ -185,6 +186,7 @@ void print_formatted_ref_array(struct ref_array *array, struct ref_format *forma\n  * name must be a fully qualified refname.\n  */\n void pretty_print_ref(const char *name, const struct object_id *oid,\n+\t\t      const struct object_id *peeled_oid,\n \t\t      struct ref_format *format);\n \n /*\n@@ -193,7 +195,8 @@ void pretty_print_ref(const char *name, const struct object_id *oid,\n  */\n struct ref_array_item *ref_array_push(struct ref_array *array,\n \t\t\t\t      const char *refname,\n-\t\t\t\t      const struct object_id *oid);\n+\t\t\t\t      const struct object_id *oid,\n+\t\t\t\t      const struct object_id *peeled_oid);\n \n /*\n  * If the provided format includes ahead-behind atoms, then compute the\n\n-- \n2.51.0.764.g787ff6f08a.dirty\n\n"},{"id":"528072","messageId":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-7-916cc7c6886b@pks.im","threadId":"64260","inReplyTo":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-0-916cc7c6886b@pks.im","subject":"[PATCH 07/13] builtin/show-ref: convert to use `reference_get_peeled_oid()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-07T10:58:44Z","receivedAt":"2025-10-07T10:59:19Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The git-show-ref(1) command has multiple different modes:\n\n  - It knows to show all references matching a pattern.\n\n  - It knows to list all references that are an exact match to whatever\n    the user has provided.\n\n  - It knows to check for reference existence.\n\nThe first two commands use mostly the same infrastructure to print the\nreferences via `show_one()`. But while the former mode uses a proper\niterator and thus has a `struct reference` available in its context, the\nlatter calls `refs_read_ref()` and thus doesn't. Consequently, we cannot\neasily use `reference_get_peeled_oid()` to print the peeled value.\n\nAdapt the code so that we manually construct a `struct reference` when\nverifying refs. We wouldn't ever have the peeled value available anyway\nas we're not using an iterator here, so we can simply plug in the values\nwe _do_ have.\n\nWith this change we now have a `struct reference` available at both\ncallsites of `show_one()` and can thus pass it, which allows us to use\n`reference_get_peeled_oid()` instead of `peel_iterated_oid()`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/show-ref.c | 32 +++++++++++++++++++-------------\n 1 file changed, 19 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex 4803b5e598..4d4984e4e0 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -31,31 +31,31 @@ struct show_one_options {\n };\n \n static void show_one(const struct show_one_options *opts,\n-\t\t     const char *refname, const struct object_id *oid)\n+\t\t     const struct reference *ref)\n {\n \tconst char *hex;\n \tstruct object_id peeled;\n \n-\tif (!odb_has_object(the_repository->objects, oid,\n+\tif (!odb_has_object(the_repository->objects, ref->oid,\n \t\t\t    HAS_OBJECT_RECHECK_PACKED | HAS_OBJECT_FETCH_PROMISOR))\n-\t\tdie(\"git show-ref: bad ref %s (%s)\", refname,\n-\t\t    oid_to_hex(oid));\n+\t\tdie(\"git show-ref: bad ref %s (%s)\", ref->name,\n+\t\t    oid_to_hex(ref->oid));\n \n \tif (opts->quiet)\n \t\treturn;\n \n-\thex = repo_find_unique_abbrev(the_repository, oid, opts->abbrev);\n+\thex = repo_find_unique_abbrev(the_repository, ref->oid, opts->abbrev);\n \tif (opts->hash_only)\n \t\tprintf(\"%s\\n\", hex);\n \telse\n-\t\tprintf(\"%s %s\\n\", hex, refname);\n+\t\tprintf(\"%s %s\\n\", hex, ref->name);\n \n \tif (!opts->deref_tags)\n \t\treturn;\n \n-\tif (!peel_iterated_oid(the_repository, oid, &peeled)) {\n+\tif (!reference_get_peeled_oid(the_repository, ref, &peeled)) {\n \t\thex = repo_find_unique_abbrev(the_repository, &peeled, opts->abbrev);\n-\t\tprintf(\"%s %s^{}\\n\", hex, refname);\n+\t\tprintf(\"%s %s^{}\\n\", hex, ref->name);\n \t}\n }\n \n@@ -93,7 +93,7 @@ static int show_ref(const struct reference *ref, void *cbdata)\n match:\n \tdata->found_match++;\n \n-\tshow_one(data->show_one_opts, ref->name, ref->oid);\n+\tshow_one(data->show_one_opts, ref);\n \n \treturn 0;\n }\n@@ -175,12 +175,18 @@ static int cmd_show_ref__verify(const struct show_one_options *show_one_opts,\n \n \t\tif ((starts_with(*refs, \"refs/\") || refname_is_safe(*refs)) &&\n \t\t    !refs_read_ref(get_main_ref_store(the_repository), *refs, &oid)) {\n-\t\t\tshow_one(show_one_opts, *refs, &oid);\n-\t\t}\n-\t\telse if (!show_one_opts->quiet)\n+\t\t\tstruct reference ref = {\n+\t\t\t\t.name = *refs,\n+\t\t\t\t.oid = &oid,\n+\t\t\t};\n+\n+\t\t\tshow_one(show_one_opts, &ref);\n+\t\t} else if (!show_one_opts->quiet) {\n \t\t\tdie(\"'%s' - not a valid ref\", *refs);\n-\t\telse\n+\t\t} else {\n \t\t\treturn 1;\n+\t\t}\n+\n \t\trefs++;\n \t}\n \n\n-- \n2.51.0.764.g787ff6f08a.dirty\n\n"},{"id":"528073","messageId":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-8-916cc7c6886b@pks.im","threadId":"64260","inReplyTo":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-0-916cc7c6886b@pks.im","subject":"[PATCH 08/13] refs: drop `current_ref_iter` hack","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-07T10:58:45Z","receivedAt":"2025-10-07T10:59:22Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In preceding commits we have refactored all callers of\n`peel_iterated_oid()` to instead use `reference_get_peeled_oid()`. This\nallows us to thus get rid of the former function.\n\nGetting rid of that function is nice, but even nicer is that this also\nallows us to get rid of the `current_ref_iter` hack. This global\nvariable tracked the currently-active ref iterator so that we can use it\nto peel an object ID. Now that the peeled object ID is propagated via\n`struct reference` though we don't have to depend on this hack anymore,\nwhich makes for a more robust and easier-to-understand infrastructure.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c               | 10 ----------\n refs/iterator.c      |  5 -----\n refs/refs-internal.h | 13 -------------\n 3 files changed, 28 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 5002e56435..b0ceba8bc3 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2323,16 +2323,6 @@ int refs_optimize(struct ref_store *refs, struct pack_refs_opts *opts)\n \treturn refs->be->optimize(refs, opts);\n }\n \n-int peel_iterated_oid(struct repository *r, const struct object_id *base, struct object_id *peeled)\n-{\n-\tif (current_ref_iter &&\n-\t    (current_ref_iter->ref.oid == base ||\n-\t     oideq(current_ref_iter->ref.oid, base)))\n-\t\treturn ref_iterator_peel(current_ref_iter, peeled);\n-\n-\treturn peel_object(r, base, peeled) ? -1 : 0;\n-}\n-\n int reference_get_peeled_oid(struct repository *repo,\n \t\t\t     const struct reference *ref,\n \t\t\t     struct object_id *peeled_oid)\ndiff --git a/refs/iterator.c b/refs/iterator.c\nindex fe5980e1b6..072c6aacdb 100644\n--- a/refs/iterator.c\n+++ b/refs/iterator.c\n@@ -458,15 +458,11 @@ struct ref_iterator *prefix_ref_iterator_begin(struct ref_iterator *iter0,\n \treturn ref_iterator;\n }\n \n-struct ref_iterator *current_ref_iter = NULL;\n-\n int do_for_each_ref_iterator(struct ref_iterator *iter,\n \t\t\t     each_ref_fn fn, void *cb_data)\n {\n \tint retval = 0, ok;\n-\tstruct ref_iterator *old_ref_iter = current_ref_iter;\n \n-\tcurrent_ref_iter = iter;\n \twhile ((ok = ref_iterator_advance(iter)) == ITER_OK) {\n \t\tretval = fn(&iter->ref, cb_data);\n \t\tif (retval)\n@@ -474,7 +470,6 @@ int do_for_each_ref_iterator(struct ref_iterator *iter,\n \t}\n \n out:\n-\tcurrent_ref_iter = old_ref_iter;\n \tif (ok == ITER_ERROR)\n \t\tretval = -1;\n \tref_iterator_free(iter);\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex ed749d1657..f4f845bbea 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -376,19 +376,6 @@ struct ref_iterator_vtable {\n \tref_iterator_release_fn *release;\n };\n \n-/*\n- * current_ref_iter is a performance hack: when iterating over\n- * references using the for_each_ref*() functions, current_ref_iter is\n- * set to the reference iterator before calling the callback function.\n- * If the callback function calls peel_ref(), then peel_ref() first\n- * checks whether the reference to be peeled is the one referred to by\n- * the iterator (it usually is) and if so, asks the iterator for the\n- * peeled version of the reference if it is available. This avoids a\n- * refname lookup in a common case. current_ref_iter is set to NULL\n- * when the iteration is over.\n- */\n-extern struct ref_iterator *current_ref_iter;\n-\n struct ref_store;\n \n /* refs backends */\n\n-- \n2.51.0.764.g787ff6f08a.dirty\n\n"},{"id":"528074","messageId":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-9-916cc7c6886b@pks.im","threadId":"64260","inReplyTo":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-0-916cc7c6886b@pks.im","subject":"[PATCH 09/13] refs: drop infrastructure to peel via iterators","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-07T10:58:46Z","receivedAt":"2025-10-07T10:59:25Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Now that the peeled object ID gets propagated via the `struct reference`\nthere is no need anymore to call into the reference iterator itself to\ndereference an object. Remove this infrastructure.\n\nMost of the changes are straight-forward deletions of code. There is one\nexception though in `refs/packed-backend.c::write_with_updates()`. Here\nwe stop peeling the iterator and instead just pass the peeled object ID\nof that iterator directly.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.h                  | 14 --------------\n refs/debug.c            | 11 -----------\n refs/files-backend.c    | 17 -----------------\n refs/iterator.c         | 36 ------------------------------------\n refs/packed-backend.c   | 24 +-----------------------\n refs/ref-cache.c        |  9 ---------\n refs/refs-internal.h    |  7 -------\n refs/reftable-backend.c | 24 ------------------------\n 8 files changed, 1 insertion(+), 141 deletions(-)\n\ndiff --git a/refs.h b/refs.h\nindex f1e023df35..1f76000055 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -1289,10 +1289,6 @@ int repo_migrate_ref_storage_format(struct repository *repo,\n  * to the next entry, ref_iterator_advance() aborts the iteration,\n  * frees the ref_iterator, and returns ITER_ERROR.\n  *\n- * The reference currently being looked at can be peeled by calling\n- * ref_iterator_peel(). This function is often faster than peel_ref(),\n- * so it should be preferred when iterating over references.\n- *\n  * Putting it all together, a typical iteration looks like this:\n  *\n  *     int ok;\n@@ -1307,9 +1303,6 @@ int repo_migrate_ref_storage_format(struct repository *repo,\n  *             // Access information about the current reference:\n  *             if (!(iter->flags & REF_ISSYMREF))\n  *                     printf(\"%s is %s\\n\", iter->refname, oid_to_hex(iter->oid));\n- *\n- *             // If you need to peel the reference:\n- *             ref_iterator_peel(iter, &oid);\n  *     }\n  *\n  *     if (ok != ITER_DONE)\n@@ -1400,13 +1393,6 @@ enum ref_iterator_seek_flag {\n int ref_iterator_seek(struct ref_iterator *ref_iterator, const char *refname,\n \t\t      unsigned int flags);\n \n-/*\n- * If possible, peel the reference currently being viewed by the\n- * iterator. Return 0 on success.\n- */\n-int ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t      struct object_id *peeled);\n-\n /* Free the reference iterator and any associated resources. */\n void ref_iterator_free(struct ref_iterator *ref_iterator);\n \ndiff --git a/refs/debug.c b/refs/debug.c\nindex 7a26035617..162c24e5cc 100644\n--- a/refs/debug.c\n+++ b/refs/debug.c\n@@ -178,16 +178,6 @@ static int debug_ref_iterator_seek(struct ref_iterator *ref_iterator,\n \treturn res;\n }\n \n-static int debug_ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t\t\t   struct object_id *peeled)\n-{\n-\tstruct debug_ref_iterator *diter =\n-\t\t(struct debug_ref_iterator *)ref_iterator;\n-\tint res = diter->iter->vtable->peel(diter->iter, peeled);\n-\ttrace_printf_key(&trace_refs, \"iterator_peel: %s: %d\\n\", diter->iter->ref.name, res);\n-\treturn res;\n-}\n-\n static void debug_ref_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct debug_ref_iterator *diter =\n@@ -199,7 +189,6 @@ static void debug_ref_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable debug_ref_iterator_vtable = {\n \t.advance = debug_ref_iterator_advance,\n \t.seek = debug_ref_iterator_seek,\n-\t.peel = debug_ref_iterator_peel,\n \t.release = debug_ref_iterator_release,\n };\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex d34fbe55d6..a4cda57981 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -994,15 +994,6 @@ static int files_ref_iterator_seek(struct ref_iterator *ref_iterator,\n \treturn ref_iterator_seek(iter->iter0, refname, flags);\n }\n \n-static int files_ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t\t\t   struct object_id *peeled)\n-{\n-\tstruct files_ref_iterator *iter =\n-\t\t(struct files_ref_iterator *)ref_iterator;\n-\n-\treturn ref_iterator_peel(iter->iter0, peeled);\n-}\n-\n static void files_ref_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct files_ref_iterator *iter =\n@@ -1013,7 +1004,6 @@ static void files_ref_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable files_ref_iterator_vtable = {\n \t.advance = files_ref_iterator_advance,\n \t.seek = files_ref_iterator_seek,\n-\t.peel = files_ref_iterator_peel,\n \t.release = files_ref_iterator_release,\n };\n \n@@ -2389,12 +2379,6 @@ static int files_reflog_iterator_seek(struct ref_iterator *ref_iterator UNUSED,\n \tBUG(\"ref_iterator_seek() called for reflog_iterator\");\n }\n \n-static int files_reflog_iterator_peel(struct ref_iterator *ref_iterator UNUSED,\n-\t\t\t\t      struct object_id *peeled UNUSED)\n-{\n-\tBUG(\"ref_iterator_peel() called for reflog_iterator\");\n-}\n-\n static void files_reflog_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct files_reflog_iterator *iter =\n@@ -2405,7 +2389,6 @@ static void files_reflog_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable files_reflog_iterator_vtable = {\n \t.advance = files_reflog_iterator_advance,\n \t.seek = files_reflog_iterator_seek,\n-\t.peel = files_reflog_iterator_peel,\n \t.release = files_reflog_iterator_release,\n };\n \ndiff --git a/refs/iterator.c b/refs/iterator.c\nindex 072c6aacdb..d79aa5ec82 100644\n--- a/refs/iterator.c\n+++ b/refs/iterator.c\n@@ -21,12 +21,6 @@ int ref_iterator_seek(struct ref_iterator *ref_iterator, const char *refname,\n \treturn ref_iterator->vtable->seek(ref_iterator, refname, flags);\n }\n \n-int ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t      struct object_id *peeled)\n-{\n-\treturn ref_iterator->vtable->peel(ref_iterator, peeled);\n-}\n-\n void ref_iterator_free(struct ref_iterator *ref_iterator)\n {\n \tif (ref_iterator) {\n@@ -60,12 +54,6 @@ static int empty_ref_iterator_seek(struct ref_iterator *ref_iterator UNUSED,\n \treturn 0;\n }\n \n-static int empty_ref_iterator_peel(struct ref_iterator *ref_iterator UNUSED,\n-\t\t\t\t   struct object_id *peeled UNUSED)\n-{\n-\tBUG(\"peel called for empty iterator\");\n-}\n-\n static void empty_ref_iterator_release(struct ref_iterator *ref_iterator UNUSED)\n {\n }\n@@ -73,7 +61,6 @@ static void empty_ref_iterator_release(struct ref_iterator *ref_iterator UNUSED)\n static struct ref_iterator_vtable empty_ref_iterator_vtable = {\n \t.advance = empty_ref_iterator_advance,\n \t.seek = empty_ref_iterator_seek,\n-\t.peel = empty_ref_iterator_peel,\n \t.release = empty_ref_iterator_release,\n };\n \n@@ -240,18 +227,6 @@ static int merge_ref_iterator_seek(struct ref_iterator *ref_iterator,\n \treturn 0;\n }\n \n-static int merge_ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t\t\t   struct object_id *peeled)\n-{\n-\tstruct merge_ref_iterator *iter =\n-\t\t(struct merge_ref_iterator *)ref_iterator;\n-\n-\tif (!iter->current) {\n-\t\tBUG(\"peel called before advance for merge iterator\");\n-\t}\n-\treturn ref_iterator_peel(*iter->current, peeled);\n-}\n-\n static void merge_ref_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct merge_ref_iterator *iter =\n@@ -263,7 +238,6 @@ static void merge_ref_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable merge_ref_iterator_vtable = {\n \t.advance = merge_ref_iterator_advance,\n \t.seek = merge_ref_iterator_seek,\n-\t.peel = merge_ref_iterator_peel,\n \t.release = merge_ref_iterator_release,\n };\n \n@@ -412,15 +386,6 @@ static int prefix_ref_iterator_seek(struct ref_iterator *ref_iterator,\n \treturn ref_iterator_seek(iter->iter0, refname, flags);\n }\n \n-static int prefix_ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t\t\t    struct object_id *peeled)\n-{\n-\tstruct prefix_ref_iterator *iter =\n-\t\t(struct prefix_ref_iterator *)ref_iterator;\n-\n-\treturn ref_iterator_peel(iter->iter0, peeled);\n-}\n-\n static void prefix_ref_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct prefix_ref_iterator *iter =\n@@ -432,7 +397,6 @@ static void prefix_ref_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable prefix_ref_iterator_vtable = {\n \t.advance = prefix_ref_iterator_advance,\n \t.seek = prefix_ref_iterator_seek,\n-\t.peel = prefix_ref_iterator_peel,\n \t.release = prefix_ref_iterator_release,\n };\n \ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 7922d63acc..274c2f23aa 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -1030,22 +1030,6 @@ static int packed_ref_iterator_seek(struct ref_iterator *ref_iterator,\n \treturn 0;\n }\n \n-static int packed_ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t\t\t   struct object_id *peeled)\n-{\n-\tstruct packed_ref_iterator *iter =\n-\t\t(struct packed_ref_iterator *)ref_iterator;\n-\n-\tif ((iter->base.ref.flags & REF_KNOWS_PEELED)) {\n-\t\toidcpy(peeled, &iter->peeled);\n-\t\treturn is_null_oid(&iter->peeled) ? -1 : 0;\n-\t} else if ((iter->base.ref.flags & (REF_ISBROKEN | REF_ISSYMREF))) {\n-\t\treturn -1;\n-\t} else {\n-\t\treturn peel_object(iter->repo, &iter->oid, peeled) ? -1 : 0;\n-\t}\n-}\n-\n static void packed_ref_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct packed_ref_iterator *iter =\n@@ -1059,7 +1043,6 @@ static void packed_ref_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable packed_ref_iterator_vtable = {\n \t.advance = packed_ref_iterator_advance,\n \t.seek = packed_ref_iterator_seek,\n-\t.peel = packed_ref_iterator_peel,\n \t.release = packed_ref_iterator_release,\n };\n \n@@ -1526,13 +1509,8 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re\n \n \t\tif (cmp < 0) {\n \t\t\t/* Pass the old reference through. */\n-\n-\t\t\tstruct object_id peeled;\n-\t\t\tint peel_error = ref_iterator_peel(iter, &peeled);\n-\n \t\t\tif (write_packed_entry(out, iter->ref.name,\n-\t\t\t\t\t       iter->ref.oid,\n-\t\t\t\t\t       peel_error ? NULL : &peeled))\n+\t\t\t\t\t       iter->ref.oid, iter->ref.peeled_oid))\n \t\t\t\tgoto write_error;\n \n \t\t\tif ((ok = ref_iterator_advance(iter)) != ITER_OK) {\ndiff --git a/refs/ref-cache.c b/refs/ref-cache.c\nindex 2f46f650a6..7d515fc527 100644\n--- a/refs/ref-cache.c\n+++ b/refs/ref-cache.c\n@@ -546,14 +546,6 @@ static int cache_ref_iterator_seek(struct ref_iterator *ref_iterator,\n \treturn 0;\n }\n \n-static int cache_ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t\t\t   struct object_id *peeled)\n-{\n-\tstruct cache_ref_iterator *iter =\n-\t\t(struct cache_ref_iterator *)ref_iterator;\n-\treturn peel_object(iter->repo, ref_iterator->ref.oid, peeled) ? -1 : 0;\n-}\n-\n static void cache_ref_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct cache_ref_iterator *iter =\n@@ -565,7 +557,6 @@ static void cache_ref_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable cache_ref_iterator_vtable = {\n \t.advance = cache_ref_iterator_advance,\n \t.seek = cache_ref_iterator_seek,\n-\t.peel = cache_ref_iterator_peel,\n \t.release = cache_ref_iterator_release,\n };\n \ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex f4f845bbea..4671517dad 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -357,12 +357,6 @@ typedef int ref_iterator_advance_fn(struct ref_iterator *ref_iterator);\n typedef int ref_iterator_seek_fn(struct ref_iterator *ref_iterator,\n \t\t\t\t const char *refname, unsigned int flags);\n \n-/*\n- * Peels the current ref, returning 0 for success or -1 for failure.\n- */\n-typedef int ref_iterator_peel_fn(struct ref_iterator *ref_iterator,\n-\t\t\t\t struct object_id *peeled);\n-\n /*\n  * Implementations of this function should free any resources specific\n  * to the derived class.\n@@ -372,7 +366,6 @@ typedef void ref_iterator_release_fn(struct ref_iterator *ref_iterator);\n struct ref_iterator_vtable {\n \tref_iterator_advance_fn *advance;\n \tref_iterator_seek_fn *seek;\n-\tref_iterator_peel_fn *peel;\n \tref_iterator_release_fn *release;\n };\n \ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex f93ab96358..14160023e5 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -744,21 +744,6 @@ static int reftable_ref_iterator_seek(struct ref_iterator *ref_iterator,\n \treturn iter->err;\n }\n \n-static int reftable_ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t\t\t      struct object_id *peeled)\n-{\n-\tstruct reftable_ref_iterator *iter =\n-\t\t(struct reftable_ref_iterator *)ref_iterator;\n-\n-\tif (iter->ref.value_type == REFTABLE_REF_VAL2) {\n-\t\toidread(peeled, iter->ref.value.val2.target_value,\n-\t\t\titer->refs->base.repo->hash_algo);\n-\t\treturn 0;\n-\t}\n-\n-\treturn -1;\n-}\n-\n static void reftable_ref_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct reftable_ref_iterator *iter =\n@@ -776,7 +761,6 @@ static void reftable_ref_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable reftable_ref_iterator_vtable = {\n \t.advance = reftable_ref_iterator_advance,\n \t.seek = reftable_ref_iterator_seek,\n-\t.peel = reftable_ref_iterator_peel,\n \t.release = reftable_ref_iterator_release,\n };\n \n@@ -2098,13 +2082,6 @@ static int reftable_reflog_iterator_seek(struct ref_iterator *ref_iterator UNUSE\n \treturn -1;\n }\n \n-static int reftable_reflog_iterator_peel(struct ref_iterator *ref_iterator UNUSED,\n-\t\t\t\t\t struct object_id *peeled UNUSED)\n-{\n-\tBUG(\"reftable reflog iterator cannot be peeled\");\n-\treturn -1;\n-}\n-\n static void reftable_reflog_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct reftable_reflog_iterator *iter =\n@@ -2117,7 +2094,6 @@ static void reftable_reflog_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable reftable_reflog_iterator_vtable = {\n \t.advance = reftable_reflog_iterator_advance,\n \t.seek = reftable_reflog_iterator_seek,\n-\t.peel = reftable_reflog_iterator_peel,\n \t.release = reftable_reflog_iterator_release,\n };\n \n\n-- \n2.51.0.764.g787ff6f08a.dirty\n\n"},{"id":"528075","messageId":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-10-916cc7c6886b@pks.im","threadId":"64260","inReplyTo":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-0-916cc7c6886b@pks.im","subject":"[PATCH 10/13] object: add flag to `peel_object()` to verify object type","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-07T10:58:47Z","receivedAt":"2025-10-07T10:59:28Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When peeling a tag to a non-tag object we repeatedly call\n`parse_object()` on the tagged object until we find the first object\nthat isn't a tag. While this feels sensible at first, there is a big\ncatch here: `parse_object()` doesn't actually verify the type of the\ntagged object.\n\nThe relevant code path here eventually ends up in `parse_tag_buffer()`.\nHere, we parset he various fields of the tag, including the \"type\". Once\nwe've figured out the type and the tagged object ID, we call one of the\n`lookup_${type}()` functions for whatever type we have found. There is\ntwo possible outcomes in the successful case:\n\n  1. The object is already part of our cached objects. In that case we\n     double-check whether the type we're trying to look up matches the\n     type that was cached.\n\n  2. The object is _not_ part of our cached objects. In that case, we\n     simply create a new object with the expected type, but we don't\n     parse that object.\n\nIn the first case we might notice type mismatches, but only in the case\nwhere our cache has the object with the correct type. In the second\ncase, we'll blindly assume that the type is correct and then go with it.\nWe'll only notice that the type might be wrong when we try to parse the\nobject at a later point.\n\nNow arguably, we could change `parse_tag_buffer()` to verify the tagged\nobject's type for us. But that would have the effect that such a tag\ncannot be parsed at all anymore, and we have a small bunch of tests for\nexactly this case that assert we still can open such tags. So this\nchange does not feel like something we can retroactively tighten, even\nthough one shouldn't ever hit such corrupted tags.\n\nInstead, add a new `flags` field to `peel_object()` that allows the\ncaller to opt in to strict object verification. This will be wired up at\na subset of callsites over the next few commits.\n\nNote that this change also inlines `deref_tag_noverify()`. There's only\nbeen two callsites of that function, the one we're changing and one in\nour test helpers. The latter callsite can trivially use `deref_tag()`\ninstead, so by inlining the function we avoid having to pass down the\nflag.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object.c                | 20 +++++++++++++++++---\n object.h                | 15 ++++++++++++++-\n ref-filter.c            |  2 +-\n refs.c                  |  2 +-\n refs/packed-backend.c   |  5 ++---\n refs/reftable-backend.c |  4 ++--\n t/helper/test-reach.c   |  2 +-\n tag.c                   | 12 ------------\n tag.h                   |  1 -\n 9 files changed, 38 insertions(+), 25 deletions(-)\n\ndiff --git a/object.c b/object.c\nindex 986114a6dba..e72b0ed4360 100644\n--- a/object.c\n+++ b/object.c\n@@ -209,11 +209,12 @@ struct object *lookup_object_by_type(struct repository *r,\n \n enum peel_status peel_object(struct repository *r,\n \t\t\t     const struct object_id *name,\n-\t\t\t     struct object_id *oid)\n+\t\t\t     struct object_id *oid,\n+\t\t\t     unsigned flags)\n {\n \tstruct object *o = lookup_unknown_object(r, name);\n \n-\tif (o->type == OBJ_NONE) {\n+\tif (o->type == OBJ_NONE || flags & PEEL_OBJECT_VERIFY_OBJECT_TYPE) {\n \t\tint type = odb_read_object_info(r->objects, name, NULL);\n \t\tif (type < 0 || !object_as_type(o, type, 0))\n \t\t\treturn PEEL_INVALID;\n@@ -222,7 +223,20 @@ enum peel_status peel_object(struct repository *r,\n \tif (o->type != OBJ_TAG)\n \t\treturn PEEL_NON_TAG;\n \n-\to = deref_tag_noverify(r, o);\n+\twhile (o && o->type == OBJ_TAG) {\n+\t\to = parse_object(r, &o->oid);\n+\t\tif (o && o->type == OBJ_TAG && ((struct tag *)o)->tagged) {\n+\t\t\to = ((struct tag *)o)->tagged;\n+\n+\t\t\tif (flags & PEEL_OBJECT_VERIFY_OBJECT_TYPE) {\n+\t\t\t\tint type = odb_read_object_info(r->objects, &o->oid, NULL);\n+\t\t\t\tif (type < 0 || !object_as_type(o, type, 0))\n+\t\t\t\t\treturn PEEL_INVALID;\n+\t\t\t}\n+\t\t} else {\n+\t\t\to = NULL;\n+\t\t}\n+\t}\n \tif (!o)\n \t\treturn PEEL_INVALID;\n \ndiff --git a/object.h b/object.h\nindex 8c3c1c46e1b..1499f63d507 100644\n--- a/object.h\n+++ b/object.h\n@@ -287,6 +287,17 @@ enum peel_status {\n \tPEEL_BROKEN = -4\n };\n \n+enum peel_object_flags {\n+\t/*\n+\t * Always verify the object type, even in the case where the looked-up\n+\t * object already has an object type. This can be useful when the\n+\t * stored object type may be invalid. One such case is when looking up\n+\t * objects via tags, where we blindly trust the object type declared by\n+\t * the tag.\n+\t */\n+\tPEEL_OBJECT_VERIFY_OBJECT_TYPE = (1 << 0),\n+};\n+\n /*\n  * Peel the named object; i.e., if the object is a tag, resolve the\n  * tag recursively until a non-tag is found.  If successful, store the\n@@ -295,7 +306,9 @@ enum peel_status {\n  * and leave oid unchanged.\n  */\n enum peel_status peel_object(struct repository *r,\n-\t\t\t     const struct object_id *name, struct object_id *oid);\n+\t\t\t     const struct object_id *name,\n+\t\t\t     struct object_id *oid,\n+\t\t\t     unsigned flags);\n \n struct object_list *object_list_insert(struct object *item,\n \t\t\t\t       struct object_list **list_p);\ndiff --git a/ref-filter.c b/ref-filter.c\nindex b18a032e575..72e5a221ff3 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -2581,7 +2581,7 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)\n \tif (need_tagged) {\n \t\tif (!is_null_oid(&ref->peeled_oid)) {\n \t\t\toidcpy(&oi_deref.oid, &ref->peeled_oid);\n-\t\t} else if (!peel_object(the_repository, &obj->oid, &oi_deref.oid)) {\n+\t\t} else if (!peel_object(the_repository, &oi.oid, &oi_deref.oid, 0)) {\n \t\t\t/* We managed to peel the object ourselves. */\n \t\t} else {\n \t\t\tdie(\"bad tag\");\ndiff --git a/refs.c b/refs.c\nindex b0ceba8bc38..40acaa3f42f 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2332,7 +2332,7 @@ int reference_get_peeled_oid(struct repository *repo,\n \t\treturn 0;\n \t}\n \n-\treturn peel_object(repo, ref->oid, peeled_oid) ? -1 : 0;\n+\treturn peel_object(repo, ref->oid, peeled_oid, 0) ? -1 : 0;\n }\n \n int refs_update_symref(struct ref_store *refs, const char *ref,\ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 274c2f23aa4..6b8e5247a93 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -1528,9 +1528,8 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re\n \t\t\ti++;\n \t\t} else {\n \t\t\tstruct object_id peeled;\n-\t\t\tint peel_error = peel_object(refs->base.repo,\n-\t\t\t\t\t\t     &update->new_oid,\n-\t\t\t\t\t\t     &peeled);\n+\t\t\tint peel_error = peel_object(refs->base.repo, &update->new_oid,\n+\t\t\t\t\t\t     &peeled, 0);\n \n \t\t\tif (write_packed_entry(out, update->refname,\n \t\t\t\t\t       &update->new_oid,\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 14160023e54..fff17f9ef4e 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1632,7 +1632,7 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n \t\t\tref.refname = (char *)u->refname;\n \t\t\tref.update_index = ts;\n \n-\t\t\tpeel_error = peel_object(arg->refs->base.repo, &u->new_oid, &peeled);\n+\t\t\tpeel_error = peel_object(arg->refs->base.repo, &u->new_oid, &peeled, 0);\n \t\t\tif (!peel_error) {\n \t\t\t\tref.value_type = REFTABLE_REF_VAL2;\n \t\t\t\tmemcpy(ref.value.val2.target_value, peeled.hash, GIT_MAX_RAWSZ);\n@@ -2497,7 +2497,7 @@ static int write_reflog_expiry_table(struct reftable_writer *writer, void *cb_da\n \t\tref.refname = (char *)arg->refname;\n \t\tref.update_index = ts;\n \n-\t\tif (!peel_object(arg->refs->base.repo, &arg->update_oid, &peeled)) {\n+\t\tif (!peel_object(arg->refs->base.repo, &arg->update_oid, &peeled, 0)) {\n \t\t\tref.value_type = REFTABLE_REF_VAL2;\n \t\t\tmemcpy(ref.value.val2.target_value, peeled.hash, GIT_MAX_RAWSZ);\n \t\t\tmemcpy(ref.value.val2.value, arg->update_oid.hash, GIT_MAX_RAWSZ);\ndiff --git a/t/helper/test-reach.c b/t/helper/test-reach.c\nindex 028ec003067..c58c93800f3 100644\n--- a/t/helper/test-reach.c\n+++ b/t/helper/test-reach.c\n@@ -63,7 +63,7 @@ int cmd__reach(int ac, const char **av)\n \t\t\tdie(\"failed to resolve %s\", buf.buf + 2);\n \n \t\torig = parse_object(r, &oid);\n-\t\tpeeled = deref_tag_noverify(the_repository, orig);\n+\t\tpeeled = deref_tag(the_repository, orig, NULL, 0);\n \n \t\tif (!peeled)\n \t\t\tdie(\"failed to load commit for input %s resulting in oid %s\",\ndiff --git a/tag.c b/tag.c\nindex 1d52686ee10..f5c232d2f1f 100644\n--- a/tag.c\n+++ b/tag.c\n@@ -94,18 +94,6 @@ struct object *deref_tag(struct repository *r, struct object *o, const char *war\n \treturn o;\n }\n \n-struct object *deref_tag_noverify(struct repository *r, struct object *o)\n-{\n-\twhile (o && o->type == OBJ_TAG) {\n-\t\to = parse_object(r, &o->oid);\n-\t\tif (o && o->type == OBJ_TAG && ((struct tag *)o)->tagged)\n-\t\t\to = ((struct tag *)o)->tagged;\n-\t\telse\n-\t\t\to = NULL;\n-\t}\n-\treturn o;\n-}\n-\n struct tag *lookup_tag(struct repository *r, const struct object_id *oid)\n {\n \tstruct object *obj = lookup_object(r, oid);\ndiff --git a/tag.h b/tag.h\nindex c49d7c19ad3..ef12a610372 100644\n--- a/tag.h\n+++ b/tag.h\n@@ -16,7 +16,6 @@ int parse_tag_buffer(struct repository *r, struct tag *item, const void *data, u\n int parse_tag(struct tag *item);\n void release_tag_memory(struct tag *t);\n struct object *deref_tag(struct repository *r, struct object *, const char *, int);\n-struct object *deref_tag_noverify(struct repository *r, struct object *);\n int gpg_verify_tag(const struct object_id *oid,\n \t\t   const char *name_to_report, unsigned flags);\n struct object_id *get_tagged_oid(struct tag *tag);\n\n-- \n2.51.0.764.g787ff6f08a.dirty\n\n"},{"id":"528076","messageId":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-11-916cc7c6886b@pks.im","threadId":"64260","inReplyTo":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-0-916cc7c6886b@pks.im","subject":"[PATCH 11/13] refs: don't store peeled object IDs for invalid tags","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-07T10:58:48Z","receivedAt":"2025-10-07T10:59:31Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Both the \"files\" and \"reftable\" backend store peeled object IDs for\nreferences that point to tags:\n\n  - The \"files\" backend stores the value when packing refs, where each\n    peeled object ID is prefixed with \"^\".\n\n  - The \"reftable\" backend stores the value whenever writing a new\n    reference that points to a tag via a special ref record type.\n\nBoth of these backends use `peel_object()` to find the peeled object ID.\nBut as explained in the preceding commit, that function does not detect\nthe case where the tag's tagged object and its claimed type mismatch.\n\nThe consequence of storing these bogus peeled object IDs is that we're\nless likely to detect such corruption in other parts of Git.\ngit-for-each-ref(1) for example does not notice anymore that the tag is\nbroken when using \"--format=%(*objectname)\" to dereference tags.\n\nOne could claim that this is good, because it still allows us to mostly\nuse the tag as intended. But the biggest problem here is that we now\nhave different behaviour for such a broken tag depending on whether or\nnot we have its peeled value in the refdb.\n\nFix the issue by verifying the object type when peeling the object. If\nthat verification fails we simply skip storing the peeled value in\neither of the reference formats.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs/packed-backend.c      |  2 +-\n refs/reftable-backend.c    |  3 ++-\n t/pack-refs-tests.sh       | 32 ++++++++++++++++++++++++++++++++\n t/t0610-reftable-basics.sh | 28 ++++++++++++++++++++++++++++\n 4 files changed, 63 insertions(+), 2 deletions(-)\n\ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 6b8e5247a9..acf87a61b1 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -1529,7 +1529,7 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re\n \t\t} else {\n \t\t\tstruct object_id peeled;\n \t\t\tint peel_error = peel_object(refs->base.repo, &update->new_oid,\n-\t\t\t\t\t\t     &peeled, 0);\n+\t\t\t\t\t\t     &peeled, PEEL_OBJECT_VERIFY_OBJECT_TYPE);\n \n \t\t\tif (write_packed_entry(out, update->refname,\n \t\t\t\t\t       &update->new_oid,\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex fff17f9ef4..5f4c903b3f 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1632,7 +1632,8 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n \t\t\tref.refname = (char *)u->refname;\n \t\t\tref.update_index = ts;\n \n-\t\t\tpeel_error = peel_object(arg->refs->base.repo, &u->new_oid, &peeled, 0);\n+\t\t\tpeel_error = peel_object(arg->refs->base.repo, &u->new_oid, &peeled,\n+\t\t\t\t\t\t PEEL_OBJECT_VERIFY_OBJECT_TYPE);\n \t\t\tif (!peel_error) {\n \t\t\t\tref.value_type = REFTABLE_REF_VAL2;\n \t\t\t\tmemcpy(ref.value.val2.target_value, peeled.hash, GIT_MAX_RAWSZ);\ndiff --git a/t/pack-refs-tests.sh b/t/pack-refs-tests.sh\nindex 3dbcc01718..095823d915 100644\n--- a/t/pack-refs-tests.sh\n+++ b/t/pack-refs-tests.sh\n@@ -428,4 +428,36 @@ do\n \t'\n done\n \n+test_expect_success 'pack-refs does not store invalid peeled tag value' '\n+\ttest_when_finished rm -rf repo &&\n+\tgit init repo &&\n+\t(\n+\t\tcd repo &&\n+\t\tgit commit --allow-empty --message initial &&\n+\n+\t\techo garbage >blob-content &&\n+\t\tblob_id=$(git hash-object -w -t blob blob-content) &&\n+\n+\t\t# Write an invalid tag into the object database. The tag itself\n+\t\t# is well-formed, but the tagged object is a blob while we\n+\t\t# claim that it is a commit.\n+\t\tcat >tag-content <<-EOF &&\n+\t\tobject $blob_id\n+\t\ttype commit\n+\t\ttag bad-tag\n+\t\ttagger C O Mitter <committer@example.com> 1112354055 +0200\n+\n+\t\tannotated\n+\t\tEOF\n+\t\ttag_id=$(git hash-object -w -t tag tag-content) &&\n+\t\tgit update-ref refs/tags/bad-tag \"$tag_id\" &&\n+\n+\t\t# The packed-refs file should not contain the peeled object ID.\n+\t\t# If it did this would cause commands that use the peeled value\n+\t\t# to not notice this corrupted tag.\n+\t\tgit pack-refs --all &&\n+\t\ttest_grep ! \"^\\^\" .git/packed-refs\n+\t)\n+'\n+\n test_done\ndiff --git a/t/t0610-reftable-basics.sh b/t/t0610-reftable-basics.sh\nindex 3ea5d51532..6575528f21 100755\n--- a/t/t0610-reftable-basics.sh\n+++ b/t/t0610-reftable-basics.sh\n@@ -1135,4 +1135,32 @@ test_expect_success 'fetch: accessing FETCH_HEAD special ref works' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'writes do not persist peeled value for invalid tags' '\n+\ttest_when_finished rm -rf repo &&\n+\tgit init repo &&\n+\t(\n+\t\tcd repo &&\n+\t\tgit commit --allow-empty --message initial &&\n+\n+\t\t# We cannot easily verify that the peeled value is not stored\n+\t\t# in the tables. Instead, we test this indirectly: we create\n+\t\t# two tags that both point to the same object, but they claim\n+\t\t# different object types. If we parse both tags we notice that\n+\t\t# the parsed tagged object has a mismatch between the two tags\n+\t\t# and bail out.\n+\t\t#\n+\t\t# If we instead use the persisted peeled value we would not\n+\t\t# even parse the tags. As such, we would not notice the\n+\t\t# discrepancy either and thus listing these tags would succeed.\n+\t\tgit tag tag-1 -m \"tag 1\" &&\n+\t\tgit cat-file tag tag-1 >raw-tag &&\n+\t\tsed \"s/^type commit$/type blob/\" <raw-tag >broken-tag &&\n+\t\tbroken_tag_id=$(git hash-object -w -t tag broken-tag) &&\n+\t\tgit update-ref refs/tags/tag-2 $broken_tag_id &&\n+\n+\t\ttest_must_fail git for-each-ref --format=\"%(*objectname)\" refs/tags/ 2>err &&\n+\t\ttest_grep \"bad tag pointer\" err\n+\t)\n+'\n+\n test_done\n\n-- \n2.51.0.764.g787ff6f08a.dirty\n\n"},{"id":"528077","messageId":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-12-916cc7c6886b@pks.im","threadId":"64260","inReplyTo":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-0-916cc7c6886b@pks.im","subject":"[PATCH 12/13] ref-filter: detect broken tags when dereferencing them","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-07T10:58:49Z","receivedAt":"2025-10-07T10:59:34Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Users can ask git-for-each-ref(1) to peel tags and return information of\nthe tagged object by adding an asterisk to the format, like for example\n\"%(*$objectname)\". If so, git-for-each-ref(1) peels that object to the\nfirst non-tag object and then returns its values.\n\nAs mentioned in preceding commits, it can happen that the tagged object\ntype and the claimed object type differ, effectively resulting in a\ncorrupt tag. git-for-each-ref(1) would notice this mismatch, print an\nerror and then bail out when trying to peel the tag.\n\nBut we only notice this corruption in some very specific edge cases!\nWhile we have a test in \"t/for-each-ref-tests.sh\" that verifies the\nabove scenario, this test is specifically crafted to detect the issue at\nhand. Namely, we create two tags:\n\n  - One tag points to a specific object with the correct type.\n\n  - The other tag points to the *same* object with a different type.\n\nThe fact that both tags point to the same object is important here:\n`peel_object()` wouldn't notice the corruption if the tagged objects\nwere different.\n\nThe root cause is that `peel_object()` calls `lookup_${type}()`\neventually, where the type is the same type declared in the tag object.\nConsequently, when we have two tags pointing to the same object but with\ndifferent declared types we'll call two different lookup functions. The\nfirst lookup will store the object with an unverified type A, whereas\nthe second lookup will try to look up the object with a different\nunverified type B. And it is only now that we notice the discrepancy in\nobject types, even though type A could've already been the wrong type.\n\nFix the issue by verifying the object type in `populate_value()`. With\nthis change we'll also notice type mismatches when only dereferencing a\ntag once.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n ref-filter.c            | 3 ++-\n t/for-each-ref-tests.sh | 4 +++-\n 2 files changed, 5 insertions(+), 2 deletions(-)\n\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 72e5a221ff..72cf85c8c6 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -2581,7 +2581,8 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)\n \tif (need_tagged) {\n \t\tif (!is_null_oid(&ref->peeled_oid)) {\n \t\t\toidcpy(&oi_deref.oid, &ref->peeled_oid);\n-\t\t} else if (!peel_object(the_repository, &oi.oid, &oi_deref.oid, 0)) {\n+\t\t} else if (!peel_object(the_repository, &oi.oid, &oi_deref.oid,\n+\t\t\t\t\tPEEL_OBJECT_VERIFY_OBJECT_TYPE)) {\n \t\t\t/* We managed to peel the object ourselves. */\n \t\t} else {\n \t\t\tdie(\"bad tag\");\ndiff --git a/t/for-each-ref-tests.sh b/t/for-each-ref-tests.sh\nindex e3ad19298a..4593be5fd5 100644\n--- a/t/for-each-ref-tests.sh\n+++ b/t/for-each-ref-tests.sh\n@@ -1809,7 +1809,9 @@ test_expect_success \"${git_for_each_ref} reports broken tags\" '\n \tbad=$(git hash-object -w -t tag bad) &&\n \tgit update-ref refs/tags/broken-tag-bad $bad &&\n \ttest_must_fail ${git_for_each_ref} --format=\"%(*objectname)\" \\\n-\t\trefs/tags/broken-tag-*\n+\t\trefs/tags/broken-tag-* &&\n+\ttest_must_fail ${git_for_each_ref} --format=\"%(*objectname)\" \\\n+\t\trefs/tags/broken-tag-bad\n '\n \n test_expect_success 'set up tag with signature and no blank lines' '\n\n-- \n2.51.0.764.g787ff6f08a.dirty\n\n"},{"id":"528078","messageId":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-13-916cc7c6886b@pks.im","threadId":"64260","inReplyTo":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-0-916cc7c6886b@pks.im","subject":"[PATCH 13/13] ref-filter: parse objects on demand","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-07T10:58:50Z","receivedAt":"2025-10-07T10:59:37Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When formatting an arbitray object we parse that object regardless of\nwhether or not we actually need any parsed data. In fact, many of the\natoms we have don't require any.\n\nRefactor the code so that we parse the data on demand when we see an\natom that wants to access the objects. This leads to a small speedup,\nfor example in the Chromium repository with around 40000 refs:\n\n    Benchmark 1: for-each-ref --format='%(raw)' (HEAD~)\n      Time (mean ± σ):     388.7 ms ±   1.1 ms    [User: 322.2 ms, System: 65.0 ms]\n      Range (min … max):   387.3 ms … 390.8 ms    10 runs\n\n    Benchmark 2: for-each-ref --format='%(raw)' (HEAD)\n      Time (mean ± σ):     344.7 ms ±   0.7 ms    [User: 287.8 ms, System: 55.1 ms]\n      Range (min … max):   343.9 ms … 345.7 ms    10 runs\n\n    Summary\n      for-each-ref --format='%(raw)' (HEAD) ran\n        1.13 ± 0.00 times faster than for-each-ref --format='%(raw)' (HEAD~)\n\nWith this change, we now spend ~90% of the time decompressing objects,\nwhich is almost as good as it gets regarding git-for-each-ref(1)'s own\ninfrastructure.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n ref-filter.c | 156 ++++++++++++++++++++++++++++++++++++++++++++---------------\n 1 file changed, 117 insertions(+), 39 deletions(-)\n\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 72cf85c8c6..537c7babac 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -91,6 +91,7 @@ static struct expand_data {\n \tstruct object_id delta_base_oid;\n \tvoid *content;\n \n+\tstruct object *maybe_object;\n \tstruct object_info info;\n } oi, oi_deref;\n \n@@ -1475,11 +1476,28 @@ static void grab_common_values(struct atom_value *val, int deref, struct expand_\n \t}\n }\n \n+static int get_or_parse_object(struct expand_data *data, const char *refname,\n+\t\t\t       struct object **object, struct strbuf *err, int *eaten)\n+{\n+\tif (!data->maybe_object) {\n+\t\tdata->maybe_object = parse_object_buffer(the_repository, &data->oid, data->type,\n+\t\t\t\t\t\t\t data->size, data->content, eaten);\n+\t\tif (!data->maybe_object)\n+\t\t\treturn strbuf_addf_ret(err, -1, _(\"parse_object_buffer failed on %s for %s\"),\n+\t\t\t\t\t       oid_to_hex(&data->oid), refname);\n+\t}\n+\n+\t*object = data->maybe_object;\n+\treturn 0;\n+}\n+\n /* See grab_values */\n-static void grab_tag_values(struct atom_value *val, int deref, struct object *obj)\n+static int grab_tag_values(struct atom_value *val, int deref,\n+\t\t\t   struct expand_data *data, const char *refname,\n+\t\t\t   struct strbuf *err, int *eaten)\n {\n-\tint i;\n-\tstruct tag *tag = (struct tag *) obj;\n+\tstruct tag *tag = NULL;\n+\tint i, ret;\n \n \tfor (i = 0; i < used_atom_cnt; i++) {\n \t\tconst char *name = used_atom[i].name;\n@@ -1487,6 +1505,17 @@ static void grab_tag_values(struct atom_value *val, int deref, struct object *ob\n \t\tstruct atom_value *v = &val[i];\n \t\tif (!!deref != (*name == '*'))\n \t\t\tcontinue;\n+\n+\t\tif (!tag) {\n+\t\t\tstruct object *object;\n+\n+\t\t\tret = get_or_parse_object(data, refname, &object, err, eaten);\n+\t\t\tif (ret < 0)\n+\t\t\t\treturn ret;\n+\n+\t\t\ttag = (struct tag *) object;\n+\t\t}\n+\n \t\tif (deref)\n \t\t\tname++;\n \t\tif (atom_type == ATOM_TAG)\n@@ -1496,22 +1525,38 @@ static void grab_tag_values(struct atom_value *val, int deref, struct object *ob\n \t\telse if (atom_type == ATOM_OBJECT && tag->tagged)\n \t\t\tv->s = xstrdup(oid_to_hex(&tag->tagged->oid));\n \t}\n+\n+\treturn 0;\n }\n \n /* See grab_values */\n-static void grab_commit_values(struct atom_value *val, int deref, struct object *obj)\n+static int grab_commit_values(struct atom_value *val, int deref,\n+\t\t\t      struct expand_data *data, const char *refname,\n+\t\t\t      struct strbuf *err, int *eaten)\n {\n-\tint i;\n-\tstruct commit *commit = (struct commit *) obj;\n+\tint i, ret;\n+\tstruct commit *commit = NULL;\n \n \tfor (i = 0; i < used_atom_cnt; i++) {\n \t\tconst char *name = used_atom[i].name;\n \t\tenum atom_type atom_type = used_atom[i].atom_type;\n \t\tstruct atom_value *v = &val[i];\n+\n \t\tif (!!deref != (*name == '*'))\n \t\t\tcontinue;\n \t\tif (deref)\n \t\t\tname++;\n+\n+\t\tif (!commit) {\n+\t\t\tstruct object *object;\n+\n+\t\t\tret = get_or_parse_object(data, refname, &object, err, eaten);\n+\t\t\tif (ret < 0)\n+\t\t\t\treturn ret;\n+\n+\t\t\tcommit = (struct commit *) object;\n+\t\t}\n+\n \t\tif (atom_type == ATOM_TREE &&\n \t\t    grab_oid(name, \"tree\", get_commit_tree_oid(commit), v, &used_atom[i]))\n \t\t\tcontinue;\n@@ -1531,6 +1576,8 @@ static void grab_commit_values(struct atom_value *val, int deref, struct object\n \t\t\tv->s = strbuf_detach(&s, NULL);\n \t\t}\n \t}\n+\n+\treturn 0;\n }\n \n static const char *find_wholine(const char *who, int wholen, const char *buf)\n@@ -1759,10 +1806,12 @@ static void grab_person(const char *who, struct atom_value *val, int deref, void\n \t}\n }\n \n-static void grab_signature(struct atom_value *val, int deref, struct object *obj)\n+static int grab_signature(struct atom_value *val, int deref,\n+\t\t\t  struct expand_data *data, const char *refname,\n+\t\t\t  struct strbuf *err, int *eaten)\n {\n-\tint i;\n-\tstruct commit *commit = (struct commit *) obj;\n+\tint i, ret;\n+\tstruct commit *commit = NULL;\n \tstruct signature_check sigc = { 0 };\n \tint signature_checked = 0;\n \n@@ -1790,6 +1839,16 @@ static void grab_signature(struct atom_value *val, int deref, struct object *obj\n \t\t\tcontinue;\n \n \t\tif (!signature_checked) {\n+\t\t\tif (!commit) {\n+\t\t\t\tstruct object *object;\n+\n+\t\t\t\tret = get_or_parse_object(data, refname, &object, err, eaten);\n+\t\t\t\tif (ret < 0)\n+\t\t\t\t\treturn ret;\n+\n+\t\t\t\tcommit = (struct commit *) object;\n+\t\t\t}\n+\n \t\t\tcheck_commit_signature(commit, &sigc);\n \t\t\tsignature_checked = 1;\n \t\t}\n@@ -1843,6 +1902,8 @@ static void grab_signature(struct atom_value *val, int deref, struct object *obj\n \n \tif (signature_checked)\n \t\tsignature_check_clear(&sigc);\n+\n+\treturn 0;\n }\n \n static void find_subpos(const char *buf,\n@@ -1920,9 +1981,8 @@ static void append_lines(struct strbuf *out, const char *buf, unsigned long size\n }\n \n static void grab_describe_values(struct atom_value *val, int deref,\n-\t\t\t\t struct object *obj)\n+\t\t\t\t struct expand_data *data)\n {\n-\tstruct commit *commit = (struct commit *)obj;\n \tint i;\n \n \tfor (i = 0; i < used_atom_cnt; i++) {\n@@ -1944,7 +2004,7 @@ static void grab_describe_values(struct atom_value *val, int deref,\n \t\tcmd.git_cmd = 1;\n \t\tstrvec_push(&cmd.args, \"describe\");\n \t\tstrvec_pushv(&cmd.args, atom->u.describe_args.v);\n-\t\tstrvec_push(&cmd.args, oid_to_hex(&commit->object.oid));\n+\t\tstrvec_push(&cmd.args, oid_to_hex(&data->oid));\n \t\tif (pipe_command(&cmd, NULL, 0, &out, 0, &err, 0) < 0) {\n \t\t\terror(_(\"failed to run 'describe'\"));\n \t\t\tv->s = xstrdup(\"\");\n@@ -2066,24 +2126,36 @@ static void fill_missing_values(struct atom_value *val)\n  * pointed at by the ref itself; otherwise it is the object the\n  * ref (which is a tag) refers to.\n  */\n-static void grab_values(struct atom_value *val, int deref, struct object *obj, struct expand_data *data)\n+static int grab_values(struct atom_value *val, int deref, struct expand_data *data,\n+\t\t       const char *refname, struct strbuf *err, int *eaten)\n {\n \tvoid *buf = data->content;\n+\tint ret;\n \n-\tswitch (obj->type) {\n+\tswitch (data->type) {\n \tcase OBJ_TAG:\n-\t\tgrab_tag_values(val, deref, obj);\n+\t\tret = grab_tag_values(val, deref, data, refname, err, eaten);\n+\t\tif (ret < 0)\n+\t\t\tgoto out;\n+\n \t\tgrab_sub_body_contents(val, deref, data);\n \t\tgrab_person(\"tagger\", val, deref, buf);\n-\t\tgrab_describe_values(val, deref, obj);\n+\t\tgrab_describe_values(val, deref, data);\n \t\tbreak;\n \tcase OBJ_COMMIT:\n-\t\tgrab_commit_values(val, deref, obj);\n+\t\tret = grab_commit_values(val, deref, data, refname, err, eaten);\n+\t\tif (ret < 0)\n+\t\t\tgoto out;\n+\n \t\tgrab_sub_body_contents(val, deref, data);\n \t\tgrab_person(\"author\", val, deref, buf);\n \t\tgrab_person(\"committer\", val, deref, buf);\n-\t\tgrab_signature(val, deref, obj);\n-\t\tgrab_describe_values(val, deref, obj);\n+\n+\t\tret = grab_signature(val, deref, data, refname, err, eaten);\n+\t\tif (ret < 0)\n+\t\t\tgoto out;\n+\n+\t\tgrab_describe_values(val, deref, data);\n \t\tbreak;\n \tcase OBJ_TREE:\n \t\t/* grab_tree_values(val, deref, obj, buf, sz); */\n@@ -2094,8 +2166,12 @@ static void grab_values(struct atom_value *val, int deref, struct object *obj, s\n \t\tgrab_sub_body_contents(val, deref, data);\n \t\tbreak;\n \tdefault:\n-\t\tdie(\"Eh?  Object of type %d?\", obj->type);\n+\t\tdie(\"Eh?  Object of type %d?\", data->type);\n \t}\n+\n+\tret = 0;\n+out:\n+\treturn ret;\n }\n \n static inline char *copy_advance(char *dst, const char *src)\n@@ -2292,38 +2368,41 @@ static const char *get_refname(struct used_atom *atom, struct ref_array_item *re\n \treturn show_ref(&atom->u.refname, ref->refname);\n }\n \n-static int get_object(struct ref_array_item *ref, int deref, struct object **obj,\n+static int get_object(struct ref_array_item *ref, int deref,\n \t\t      struct expand_data *oi, struct strbuf *err)\n {\n-\t/* parse_object_buffer() will set eaten to 0 if free() will be needed */\n-\tint eaten = 1;\n+\t/* parse_object_buffer() will set eaten to 1 if free() will be needed */\n+\tint eaten = 0;\n+\tint ret;\n+\n \tif (oi->info.contentp) {\n \t\t/* We need to know that to use parse_object_buffer properly */\n \t\toi->info.sizep = &oi->size;\n \t\toi->info.typep = &oi->type;\n \t}\n+\n \tif (odb_read_object_info_extended(the_repository->objects, &oi->oid, &oi->info,\n-\t\t\t\t\t  OBJECT_INFO_LOOKUP_REPLACE))\n-\t\treturn strbuf_addf_ret(err, -1, _(\"missing object %s for %s\"),\n-\t\t\t\t       oid_to_hex(&oi->oid), ref->refname);\n+\t\t\t\t\t  OBJECT_INFO_LOOKUP_REPLACE)) {\n+\t\tret = strbuf_addf_ret(err, -1, _(\"missing object %s for %s\"),\n+\t\t\t\t      oid_to_hex(&oi->oid), ref->refname);\n+\t\tgoto out;\n+\t}\n \tif (oi->info.disk_sizep && oi->disk_size < 0)\n \t\tBUG(\"Object size is less than zero.\");\n \n \tif (oi->info.contentp) {\n-\t\t*obj = parse_object_buffer(the_repository, &oi->oid, oi->type, oi->size, oi->content, &eaten);\n-\t\tif (!*obj) {\n-\t\t\tif (!eaten)\n-\t\t\t\tfree(oi->content);\n-\t\t\treturn strbuf_addf_ret(err, -1, _(\"parse_object_buffer failed on %s for %s\"),\n-\t\t\t\t\t       oid_to_hex(&oi->oid), ref->refname);\n-\t\t}\n-\t\tgrab_values(ref->value, deref, *obj, oi);\n+\t\tret = grab_values(ref->value, deref, oi, ref->refname, err, &eaten);\n+\t\tif (ret < 0)\n+\t\t\tgoto out;\n \t}\n \n \tgrab_common_values(ref->value, deref, oi);\n+\tret = 0;\n+\n+out:\n \tif (!eaten)\n \t\tfree(oi->content);\n-\treturn 0;\n+\treturn ret;\n }\n \n static void populate_worktree_map(struct hashmap *map, struct worktree **worktrees)\n@@ -2376,7 +2455,6 @@ static char *get_worktree_path(const struct ref_array_item *ref)\n  */\n static int populate_value(struct ref_array_item *ref, struct strbuf *err)\n {\n-\tstruct object *obj;\n \tint i;\n \tstruct object_info empty = OBJECT_INFO_INIT;\n \tint ahead_behind_atoms = 0;\n@@ -2564,14 +2642,14 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)\n \n \n \toi.oid = ref->objectname;\n-\tif (get_object(ref, 0, &obj, &oi, err))\n+\tif (get_object(ref, 0, &oi, err))\n \t\treturn -1;\n \n \t/*\n \t * If there is no atom that wants to know about tagged\n \t * object, we are done.\n \t */\n-\tif (!need_tagged || (obj->type != OBJ_TAG))\n+\tif (!need_tagged || (oi.type != OBJ_TAG))\n \t\treturn 0;\n \n \t/*\n@@ -2589,7 +2667,7 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)\n \t\t}\n \t}\n \n-\treturn get_object(ref, 1, &obj, &oi_deref, err);\n+\treturn get_object(ref, 1, &oi_deref, err);\n }\n \n /*\n\n-- \n2.51.0.764.g787ff6f08a.dirty\n\n"},{"id":"528120","messageId":"CAOLa=ZR0KWkpB4kjFm2g8F-PK_LWpE20P21rmJGOLONCkfN7yA@mail.gmail.com","threadId":"64260","inReplyTo":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-2-916cc7c6886b@pks.im","subject":"Re: [PATCH 02/13] refs: introduce `.ref` field for the base iterator","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2025-10-07T14:24:01Z","receivedAt":"2025-10-07T14:24:03Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> The base iterator has a couple of fields that tracks the name, target,\n> object ID and flags for the current reference. Due do this design we\n> have to create a new `struct reference` whenever we want to hand over\n> that reference to the callback function, which is tedious and not very\n> efficient.\n>\n> Convert the structure to instead contain a `stuct reference` as member.\n\ns/stuct/struct\n\n> This member is expected to be populated by the implementations of the\n> iterator and is handed over to the callback directly.\n>\n\nWouldn't this also add the burden on each backend to ensure we don't\nserve stale data for each '_advance()' call?\n\nWould it make sense to reset this data in `ref_iterator_advance()`?\n\nKarthik\n"},{"id":"528122","messageId":"CAOLa=ZTL7kY6_2AqjYJGrjnKgwWVBbjrtgha-dBR-ijL7C87tg@mail.gmail.com","threadId":"64260","inReplyTo":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-3-916cc7c6886b@pks.im","subject":"Re: [PATCH 03/13] refs: refactor reference status flags","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2025-10-07T14:27:55Z","receivedAt":"2025-10-07T14:27:57Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> The reference flags encode information like whether or not a reference\n> is a symbolic reference or whether it may be broken. This information is\n> stored in a `int flags` bitfield, which is in conflict with our modern\n> best practices; we tend to use an unsigned integer to store flags.\n>\n> Change the type of the field to be `unsigned`. While at it, refactor the\n> individual flags to be part of an `enum` instead of using preprocessor\n> defines.\n>\n\nUsing an enum is much nicer.\n\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  refs.h | 41 +++++++++++++++++++++--------------------\n>  1 file changed, 21 insertions(+), 20 deletions(-)\n>\n> diff --git a/refs.h b/refs.h\n> index 2b24a3d997..8f484e2ffc 100644\n> --- a/refs.h\n> +++ b/refs.h\n> @@ -333,27 +333,28 @@ struct ref_transaction;\n>   * stored in ref_iterator::flags. Other bits are for internal use\n>   * only:\n>   */\n> +enum reference_status {\n> +\t/* Reference is a symbolic reference. */\n> +\tREF_ISSYMREF = (1 << 0),\n>\n\nNit: It would also be nice to rename these flags to\n'REFERENCE_STATUS_*', since that seems to be the pattern we generally\nfollow. Doesn't have to be in your series though :)\n\nRest looks good.\n"},{"id":"528124","messageId":"CAOLa=ZRQuLa_xD8GzynHNmNZuyoJeK9dCBOKbUfkCES4ejG0OA@mail.gmail.com","threadId":"64260","inReplyTo":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-4-916cc7c6886b@pks.im","subject":"Re: [PATCH 04/13] refs: expose peeled object ID via the iterator","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2025-10-07T14:52:43Z","receivedAt":"2025-10-07T14:52:46Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Both the \"files\" and \"reftable\" backend are able to store peeled values\n> for tags in the respective formats. This allows for a more efficient\n> lookup of the target object of such a tag without having to manually\n> peel via the object database.\n>\n\nIn the 'files' backend, I thought only packed-refs store peeled values?\n\n> The infrastructure to access these peeled object IDs is somewhat funky\n> though. When iterating through objects, we store a pointer reference to\n> the current iterator in a global variable. The callbacks invoked by that\n> iterator are then expected to call `peel_iterated_oid()`, which checks\n> whether the globally-stored iterator's current reference refers to the\n> one handed into that function. If so, we ask the iterator to peel the\n> object, otherwise we manually peel the object via the object database.\n> Depending on global state like this is somewhat weird and also quite\n> fragile.\n>\n> Introduce a new `struct reference::peeled_oid` field that can be\n> populated by the reference backends. This field can be accessed via a\n> new function `reference_get_peeled_oid()` that either uses that value,\n> if set, or alternatively peels via the ODB. With this change we don't\n> have to rely on global state anymore, but make the peeled object ID\n> available to the callback functions directly.\n>\n> Adjust trivial callers that already have a `struct reference` available.\n> Remaining callers will be adjusted in subsequent commits.\n\n[snip]\n\n> diff --git a/refs.c b/refs.c\n> index 15ad0ef7a8..5002e56435 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -2333,6 +2333,18 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct\n>  \treturn peel_object(r, base, peeled) ? -1 : 0;\n>  }\n>\n> +int reference_get_peeled_oid(struct repository *repo,\n> +\t\t\t     const struct reference *ref,\n> +\t\t\t     struct object_id *peeled_oid)\n> +{\n> +\tif (ref->peeled_oid) {\n> +\t\toidcpy(peeled_oid, ref->peeled_oid);\n> +\t\treturn 0;\n> +\t}\n> +\n> +\treturn peel_object(repo, ref->oid, peeled_oid) ? -1 : 0;\n> +}\n> +\n>\n\nSo similar to `peel_iterated_oid()` but instead of relying on the\nreference backend to actually provide us the information, we simply rely\non the value if present. This avoids the round-trip. Makes sense.\n\nThe last resource is to look into the object database. Which should only\nhappen with loose refs in the files backend.\n\n> diff --git a/refs/packed-backend.c b/refs/packed-backend.c\n> index 7987acdc96..7922d63acc 100644\n> --- a/refs/packed-backend.c\n> +++ b/refs/packed-backend.c\n> @@ -959,11 +959,14 @@ static int next_record(struct packed_ref_iterator *iter)\n>  \t\tif ((iter->base.ref.flags & REF_ISBROKEN)) {\n>  \t\t\toidclr(&iter->peeled, iter->repo->hash_algo);\n>  \t\t\titer->base.ref.flags &= ~REF_KNOWS_PEELED;\n> +\t\t\titer->base.ref.peeled_oid = NULL;\n>  \t\t} else {\n>  \t\t\titer->base.ref.flags |= REF_KNOWS_PEELED;\n> +\t\t\titer->base.ref.peeled_oid = &iter->peeled;\n>  \t\t}\n>  \t} else {\n>  \t\toidclr(&iter->peeled, iter->repo->hash_algo);\n> +\t\titer->base.ref.peeled_oid = NULL;\n>\n\nSo my comment on the previous commit holds. We have to manually ensure\nwe reset the fields.\n\n>  \t}\n>\n>  \treturn ITER_OK;\n> diff --git a/refs/ref-cache.c b/refs/ref-cache.c\n> index 97555fa118..2f46f650a6 100644\n> --- a/refs/ref-cache.c\n> +++ b/refs/ref-cache.c\n> @@ -428,6 +428,7 @@ static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator)\n>  \t\t\titer->base.ref.name = entry->name;\n>  \t\t\titer->base.ref.target = entry->u.value.referent;\n>  \t\t\titer->base.ref.oid = &entry->u.value.oid;\n> +\t\t\titer->base.ref.peeled_oid = NULL;\n>  \t\t\titer->base.ref.flags = entry->flag;\n>  \t\t\treturn ITER_OK;\n>  \t\t}\n> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\n> index 7fbc77492e..f93ab96358 100644\n> --- a/refs/reftable-backend.c\n> +++ b/refs/reftable-backend.c\n> @@ -546,6 +546,7 @@ struct reftable_ref_iterator {\n>  \tstruct reftable_iterator iter;\n>  \tstruct reftable_ref_record ref;\n>  \tstruct object_id oid;\n> +\tstruct object_id peeled_oid;\n>\n>  \tchar *prefix;\n>  \tsize_t prefix_len;\n> @@ -670,6 +671,8 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)\n>  \t\tcase REFTABLE_REF_VAL2:\n>  \t\t\toidread(&iter->oid, iter->ref.value.val2.value,\n>  \t\t\t\trefs->base.repo->hash_algo);\n> +\t\t\toidread(&iter->peeled_oid, iter->ref.value.val2.target_value,\n> +\t\t\t\trefs->base.repo->hash_algo);\n>  \t\t\tbreak;\n>  \t\tcase REFTABLE_REF_SYMREF:\n>  \t\t\treferent = refs_resolve_ref_unsafe(&iter->refs->base,\n> @@ -706,6 +709,10 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)\n>  \t\titer->base.ref.name = iter->ref.refname;\n>  \t\titer->base.ref.target = referent;\n>  \t\titer->base.ref.oid = &iter->oid;\n> +\t\tif (iter->ref.value_type == REFTABLE_REF_VAL2)\n> +\t\t\titer->base.ref.peeled_oid = &iter->peeled_oid;\n> +\t\telse\n> +\t\t\titer->base.ref.peeled_oid = NULL;\n>  \t\titer->base.ref.flags = flags;\n>\n>  \t\tbreak;\n>\n> --\n> 2.51.0.764.g787ff6f08a.dirty\n\nAll of this looks good and as expected!\n"},{"id":"528129","messageId":"CAOLa=ZRdcXUQLXK1s1JLgZAcEYx=kT-eS6CMzCocJ9Oenia_Jw@mail.gmail.com","threadId":"64260","inReplyTo":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-5-916cc7c6886b@pks.im","subject":"Re: [PATCH 05/13] upload-pack: convert to use `reference_get_peeled_oid()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2025-10-07T16:18:52Z","receivedAt":"2025-10-07T16:18:55Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> The `write_v0_ref()` callback is invoked from two callsites:\n>\n\nOkay so this function does multiple things based on whether the\ncapabilities are already advertised or not.\n\n- If not, we propagate the capabilities and set the static variable\n  `capabilities` to NULL and also set `data->sent_capabilities = 1;`.\n  - We receive `ref->oid` as a zero oid for the hash algorithm being\n    used, we convert it to the hex format with `oid_to_hex()`.\n- If already advertised, we simply propagate the reference and if it can\n  be peeled, also propagate the peeled reference.\n\nNot for your series: but this feels like the capabilities should be an\nindependent function.\n\n>   - Once via `send_ref()` which is a callback passed to\n>     `for_each_namespaced_ref_1()`.\n>\n\nand passed to `refs_head_ref_namespaced()`\n\n>   - Once manually to announce capabilities.\n>\n> When sending references to the client we also send the peeled value of\n> tags. As we don't have a `struct reference` available in the second\n> case, we cannot easily peel by calling `reference_get_peeled_oid()`, but\n> we instead have to depend on on global state via `peel_iterated_oid()`.\n>\n> We do have a reference available though in the first case, it's only the\n> second case that keeps us from using `reference_get_peeled_oid()`. But\n> that second case only announces capabilities anyway, so we're not really\n> handling a reference at all here.\n>\n\nYup, this was my inference above as well.\n\n> Adapt that case to construct a reference manually and pass that to\n> `write_v0_ref()`. Start to use `reference_get_peeled_oid()` now that we\n> always have a `struct reference` available.\n>\n\nThis is a fair solution for now. I think this also shows that these two\noperation modes should definitely be separated out.\n\n[snip]\n"},{"id":"528148","messageId":"jrst5hft3o7ee72hrmswhrnz46rgvjihdxgfsougg5u5vs6os4@2prgx3uw6qp7","threadId":"64260","inReplyTo":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-1-916cc7c6886b@pks.im","subject":"Re: [PATCH 01/13] refs: introduce wrapper struct for `each_ref_fn`","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2025-10-07T18:05:15Z","receivedAt":"2025-10-07T18:05:18Z","isPatch":true,"sender":{"key":"jltobler@gmail.com","avatar":"https://avatars.githubusercontent.com/u/53454972?v=4"},"body":"On 25/10/07 12:58PM, Patrick Steinhardt wrote:\n> The `each_ref_fn` callback function type is used across our code base\n> for several different functions that iterate through reference. There's\n> a bunch of callbacks implementing this type, which makes any changes to\n> the callback signature extremely noisy. An example of the required churn\n> is e8207717f1 (refs: add referent to each_ref_fn, 2024-08-09): adding a\n> single argument required us to change 48 files.\n> \n> It was already proposed back then [1] that we might want to introduce a\n> wrapper structure to alleviate the pain going forward. While this of\n> course requires the same kind of global refactoring as just introducing\n> a new parameter, it at least allows us to more change the callback type\n> afterwards by just extending the wrapper structure.\n> \n> One counterargument to this refactoring is that it makes the structure\n> more opaque. While it is obvious which callsites need to be fixed up\n> when we change the function type, it's not obvious anymore once we use\n> a structure. That being said, we only have a handful of sites that\n> actually need to populate this wrapper structure: our ref backends and\n> \"refs/iterator.c\".\n\nIt looks like we also populate `stuct reference` in a couple other spots\nwhere we invoke the callback explicitly.\n\n> Introduce this wrapper structure so that we can adapt the iterator\n> interfaces more readily.\n> \n> [1]: <ZmarVcF5JjsZx0dl@tanuki>\n> \n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n[snip]\n> diff --git a/refs.h b/refs.h\n> index 4e6bd63aa86..2b24a3d9974 100644\n> --- a/refs.h\n> +++ b/refs.h\n> @@ -355,14 +355,32 @@ struct ref_transaction;\n>   */\n>  #define REF_BAD_NAME 0x08\n>  \n> +/* A reference passed to `for_each_ref()`-style callbacks. */\n> +struct reference {\n> +\t/* The fully-qualified name of the reference. */\n> +\tconst char *name;\n> +\n> +\t/* The target of a symbolic ref. `NULL` for direct references. */\n> +\tconst char *target;\n> +\n> +\t/*\n> +\t * The object ID of a reference. Either the direct object ID or the\n> +\t * resolved object ID in the case of a symbolic ref. May be the zero\n> +\t * object ID in case the symbolic ref cannot be resolved.\n> +\t */\n> +\tconst struct object_id *oid;\n> +\n> +\t/* A bitfield of `REF_` flags. */\n> +\tint flags;\n\nI was considering for a little while whether it would make sense for all\nthe arguments to be moved here, or if ones such as flags should remain.\nSince all these fields directly relate to the reference though, I think\nit does make sense to relocate all of them.\n\n> +};\n\nOk, so now all the explicit callback arguments are contained in `struct\nreference` here. Going forward this certainly would reduce churn if need\nneed to add additional fields here. Overall, this seems sensible to me.\n\n> +\n>  /*\n>   * The signature for the callback function for the for_each_*()\n>   * functions below.  The memory pointed to by the refname and oid\n>   * arguments is only guaranteed to be valid for the duration of a\n>   * single callback invocation.\n>   */\n\nShould we update this comment now that these fields are contained the\nwrapper struct?\n\n[snip]\n> diff --git a/refs/iterator.c b/refs/iterator.c\n> index 17ef841d8a3..7f2e718f1c9 100644\n> --- a/refs/iterator.c\n> +++ b/refs/iterator.c\n> @@ -476,7 +476,14 @@ int do_for_each_ref_iterator(struct ref_iterator *iter,\n>  \n>  \tcurrent_ref_iter = iter;\n>  \twhile ((ok = ref_iterator_advance(iter)) == ITER_OK) {\n> -\t\tretval = fn(iter->refname, iter->referent, iter->oid, iter->flags, cb_data);\n> +\t\tstruct reference ref = {\n> +\t\t\t.name = iter->refname,\n> +\t\t\t.target = iter->referent,\n> +\t\t\t.oid = iter->oid,\n> +\t\t\t.flags = iter->flags,\n> +\t\t};\n\nNow we wire up the new wrapper struct instead of passing explicit\narguments. Looks good.\n\n> +\n> +\t\tretval = fn(&ref, cb_data);\n>  \t\tif (retval)\n>  \t\t\tgoto out;\n>  \t}\n\n-Justin\n"},{"id":"528156","messageId":"q6ti6bevxr4kbsi7pe7slwmvyqhc2sslma3tk3xshohnqadtuv@canofgr644do","threadId":"64260","inReplyTo":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-2-916cc7c6886b@pks.im","subject":"Re: [PATCH 02/13] refs: introduce `.ref` field for the base iterator","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2025-10-07T20:19:58Z","receivedAt":"2025-10-07T20:20:00Z","isPatch":true,"sender":{"key":"jltobler@gmail.com","avatar":"https://avatars.githubusercontent.com/u/53454972?v=4"},"body":"On 25/10/07 12:58PM, Patrick Steinhardt wrote:\n> The base iterator has a couple of fields that tracks the name, target,\n> object ID and flags for the current reference. Due do this design we\n\ns/Due do/Due to/\n\n> have to create a new `struct reference` whenever we want to hand over\n> that reference to the callback function, which is tedious and not very\n> efficient.\n> \n> Convert the structure to instead contain a `stuct reference` as member.\n> This member is expected to be populated by the implementations of the\n> iterator and is handed over to the callback directly.\n> \n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n[snip]\n> diff --git a/refs/files-backend.c b/refs/files-backend.c\n> index 0ddcf22aed..d34fbe55d6 100644\n> --- a/refs/files-backend.c\n> +++ b/refs/files-backend.c\n> @@ -962,26 +962,23 @@ static int files_ref_iterator_advance(struct ref_iterator *ref_iterator)\n>  \n>  \twhile ((ok = ref_iterator_advance(iter->iter0)) == ITER_OK) {\n>  \t\tif (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY &&\n> -\t\t    parse_worktree_ref(iter->iter0->refname, NULL, NULL,\n> +\t\t    parse_worktree_ref(iter->iter0->ref.name, NULL, NULL,\n>  \t\t\t\t       NULL) != REF_WORKTREE_CURRENT)\n>  \t\t\tcontinue;\n>  \n>  \t\tif ((iter->flags & DO_FOR_EACH_OMIT_DANGLING_SYMREFS) &&\n> -\t\t    (iter->iter0->flags & REF_ISSYMREF) &&\n> -\t\t    (iter->iter0->flags & REF_ISBROKEN))\n> +\t\t    (iter->iter0->ref.flags & REF_ISSYMREF) &&\n> +\t\t    (iter->iter0->ref.flags & REF_ISBROKEN))\n>  \t\t\tcontinue;\n>  \n>  \t\tif (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&\n> -\t\t    !ref_resolves_to_object(iter->iter0->refname,\n> +\t\t    !ref_resolves_to_object(iter->iter0->ref.name,\n>  \t\t\t\t\t    iter->repo,\n> -\t\t\t\t\t    iter->iter0->oid,\n> -\t\t\t\t\t    iter->iter0->flags))\n> +\t\t\t\t\t    iter->iter0->ref.oid,\n> +\t\t\t\t\t    iter->iter0->ref.flags))\n>  \t\t\tcontinue;\n>  \n> -\t\titer->base.refname = iter->iter0->refname;\n> -\t\titer->base.oid = iter->iter0->oid;\n> -\t\titer->base.flags = iter->iter0->flags;\n> -\t\titer->base.referent = iter->iter0->referent;\n> +\t\titer->base.ref = iter->iter0->ref;\n\nOk, so here we already have a `struct reference` setup and thus we\ndirectly propagate to the base when advacing the iterator. Makes sense.\n\n>  \n>  \t\treturn ITER_OK;\n>  \t}\n> @@ -1368,30 +1365,29 @@ static void prune_refs(struct files_ref_store *refs, struct ref_to_prune **refs_\n>   * Return true if the specified reference should be packed.\n>   */\n>  static int should_pack_ref(struct files_ref_store *refs,\n> -\t\t\t   const char *refname,\n> -\t\t\t   const struct object_id *oid, unsigned int ref_flags,\n> +\t\t\t   const struct reference *ref,\n>  \t\t\t   struct pack_refs_opts *opts)\n\nThis hunk is simplifies the arguments required by should_pack_ref() by\nusing `struct reference`. The change seems sensible, it might be worth\nmentioning in the commit message though.\n\n>  {\n>  \tstruct string_list_item *item;\n>  \n>  \t/* Do not pack per-worktree refs: */\n> -\tif (parse_worktree_ref(refname, NULL, NULL, NULL) !=\n> +\tif (parse_worktree_ref(ref->name, NULL, NULL, NULL) !=\n>  \t    REF_WORKTREE_SHARED)\n>  \t\treturn 0;\n>  \n>  \t/* Do not pack symbolic refs: */\n> -\tif (ref_flags & REF_ISSYMREF)\n> +\tif (ref->flags & REF_ISSYMREF)\n>  \t\treturn 0;\n>  \n>  \t/* Do not pack broken refs: */\n> -\tif (!ref_resolves_to_object(refname, refs->base.repo, oid, ref_flags))\n> +\tif (!ref_resolves_to_object(ref->name, refs->base.repo, ref->oid, ref->flags))\n>  \t\treturn 0;\n>  \n> -\tif (ref_excluded(opts->exclusions, refname))\n> +\tif (ref_excluded(opts->exclusions, ref->name))\n>  \t\treturn 0;\n>  \n>  \tfor_each_string_list_item(item, opts->includes)\n> -\t\tif (!wildmatch(item->string, refname, 0))\n> +\t\tif (!wildmatch(item->string, ref->name, 0))\n>  \t\t\treturn 1;\n>  \n>  \treturn 0;\n[snip]\n> @@ -476,14 +468,7 @@ int do_for_each_ref_iterator(struct ref_iterator *iter,\n>  \n>  \tcurrent_ref_iter = iter;\n>  \twhile ((ok = ref_iterator_advance(iter)) == ITER_OK) {\n> -\t\tstruct reference ref = {\n> -\t\t\t.name = iter->refname,\n> -\t\t\t.target = iter->referent,\n> -\t\t\t.oid = iter->oid,\n> -\t\t\t.flags = iter->flags,\n> -\t\t};\n> -\n> -\t\tretval = fn(&ref, cb_data);\n> +\t\tretval = fn(&iter->ref, cb_data);\n\nNow we propagate `struct reference` directly when invoking the for each\ncallback. Makes sense.\n\n>  \t\tif (retval)\n>  \t\t\tgoto out;\n>  \t}\n\n-Justin\n"},{"id":"528165","messageId":"xmqqsefuig0n.fsf@gitster.g","threadId":"64260","inReplyTo":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-0-916cc7c6886b@pks.im","subject":"Re: [PATCH 00/13] refs: improvements and fixes for peeling tags","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-10-07T21:00:24Z","receivedAt":"2025-10-07T21:00:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> diff --cc builtin/repack.c\n> index 873e21c35d,ad60c4290d..0000000000\n> --- a/builtin/repack.c\n> +++ b/builtin/repack.c\n> diff --git a/repack-midx.c b/repack-midx.c\n> index 6f6202c5bc..74bdfa3a6e 100644\n> --- a/repack-midx.c\n> +++ b/repack-midx.c\n> @@ -16,25 +16,23 @@ struct midx_snapshot_ref_data {\n>  \tint preferred;\n>  };\n>  \n> -static int midx_snapshot_ref_one(const char *refname UNUSED,\n> -\t\t\t\t const char *referent UNUSED,\n> -\t\t\t\t const struct object_id *oid,\n> -\t\t\t\t int flag UNUSED, void *_data)\n> +static int midx_snapshot_ref_one(const struct reference *ref, void *_data)\n>  {\n>  \tstruct midx_snapshot_ref_data *data = _data;\n> +\tconst struct object_id *maybe_peeled = ref->oid;\n>  \tstruct object_id peeled;\n>  \n> -\tif (!peel_iterated_oid(data->repo, oid, &peeled))\n> -\t\toid = &peeled;\n> +\tif (!reference_get_peeled_oid(data->repo, ref, &peeled))\n> +\t\tmaybe_peeled = &peeled;\n>  \n> -\tif (oidset_insert(&data->seen, oid))\n> +\tif (oidset_insert(&data->seen, maybe_peeled))\n>  \t\treturn 0; /* already seen */\n>  \n> -\tif (odb_read_object_info(data->repo->objects, oid, NULL) != OBJ_COMMIT)\n> +\tif (odb_read_object_info(data->repo->objects, maybe_peeled, NULL) != OBJ_COMMIT)\n>  \t\treturn 0;\n>  \n>  \tfprintf(data->f->fp, \"%s%s\\n\", data->preferred ? \"+\" : \"\",\n> -\t\toid_to_hex(oid));\n> +\t\toid_to_hex(maybe_peeled));\n>  \n>  \treturn 0;\n>  }\n\nThis seems to match the manual resolution I just made, except that I\nline wrapped overly wide line ;-).  \n\nQueued.\n"},{"id":"528172","messageId":"aOWK+aWlGvzEqpRf@nand.local","threadId":"64260","inReplyTo":"xmqqsefuig0n.fsf@gitster.g","subject":"Re: [PATCH 00/13] refs: improvements and fixes for peeling tags","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-07T21:49:45Z","receivedAt":"2025-10-07T21:49:48Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Tue, Oct 07, 2025 at 02:00:24PM -0700, Junio C Hamano wrote:\n> This seems to match the manual resolution I just made, except that I\n> line wrapped overly wide line ;-).\n\nThe resolution looks good to me as well. Thanks, both.\n\nThanks,\nTaylor\n"},{"id":"528173","messageId":"aOWMdG/vxXy6bLKZ@nand.local","threadId":"64260","inReplyTo":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-1-916cc7c6886b@pks.im","subject":"Re: [PATCH 01/13] refs: introduce wrapper struct for `each_ref_fn`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-07T21:56:04Z","receivedAt":"2025-10-07T21:56:06Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Tue, Oct 07, 2025 at 12:58:38PM +0200, Patrick Steinhardt wrote:\n> [1]: <ZmarVcF5JjsZx0dl@tanuki>\n\nI agree with Junio's follow-up[2] there that the benefit of dropping\npeel_iterated_oid() is worth the one-time blast radius of this change.\n\nI reviewed it as closely as I could, and everything here looks good to\nme. I'm excited about the direction that this patch is taking us in :-).\n\nThanks,\nTaylor\n\n[2]: https://lore.kernel.org/git/xmqqy17c3c1v.fsf@gitster.g/\n"},{"id":"528174","messageId":"aOWM48xFTql8OkNR@nand.local","threadId":"64260","inReplyTo":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-2-916cc7c6886b@pks.im","subject":"Re: [PATCH 02/13] refs: introduce `.ref` field for the base iterator","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2025-10-07T21:57:55Z","receivedAt":"2025-10-07T21:57:57Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Tue, Oct 07, 2025 at 12:58:39PM +0200, Patrick Steinhardt wrote:\n> ---\n>  refs.c                  |  8 ++++----\n>  refs/debug.c            |  8 +++-----\n>  refs/files-backend.c    | 47 +++++++++++++++++++++--------------------------\n>  refs/iterator.c         | 39 ++++++++++++---------------------------\n>  refs/packed-backend.c   | 46 +++++++++++++++++++++++-----------------------\n>  refs/ref-cache.c        | 10 +++++-----\n>  refs/refs-internal.h    |  5 +----\n>  refs/reftable-backend.c | 12 ++++++------\n>  8 files changed, 75 insertions(+), 100 deletions(-)\n\nBesides the couple of typos that Justin and Karthik mentioned in their\nrespective replies to this message, this change makes sense and looks\ngood to me.\n\nThanks,\nTaylor\n"},{"id":"528182","messageId":"xmqqzfa2gvu9.fsf@gitster.g","threadId":"64260","inReplyTo":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-0-916cc7c6886b@pks.im","subject":"Re: [PATCH 00/13] refs: improvements and fixes for peeling tags","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-10-07T23:01:34Z","receivedAt":"2025-10-07T23:01:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Hi,\n>\n> originally, all I wanted to do was the last patch: a small performance\n> optimization that stops parsing objects in git-for-each-ref(1) unless we\n> really need to parse them. But that fix cause one specific test to fail,\n> and only with the reftable backend. So this led me down the rabbit hole\n> of tag peeling, ending up with this patch series.\n>\n> The series is structured like follows:\n>\n>   - Patches 1 to 7 refactor our codebase so that we don't have the\n>     `peel_iterated_object()` hack anymore. I just found it hard to\n>     follow and thought it shouldn't be too hard to get rid of it.\n\nI've only read up to here, and found all of them welcome\nimprovements.  Will continue reading later.\n\n>   - Patches 8 and 9 remove infrastructure that we don't need anymore\n>     after the first couple of patches.\n>   - Patches 10 to 12 fix a couple of issues with peeled tags that I\n>     found. The underlying issue is that tags store both the tagged\n>     object and their type, but this information may not match. We never\n>     verify the actual object type though when allocating the tagged\n>     object, so this only blows up much later.\n>\n>   - Patch 13 was my original motivation, a small performance\n>     optimization.\n"},{"id":"528234","messageId":"e4de781c-406e-406c-a166-5a48b6de42ea@app.fastmail.com","threadId":"64260","inReplyTo":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-10-916cc7c6886b@pks.im","subject":"Re: [PATCH 10/13] object: add flag to `peel_object()` to verify object type","fromName":"Kristoffer Haugsbakk","fromEmail":"kristofferhaugsbakk@fastmail.com","sentAt":"2025-10-08T11:04:43Z","receivedAt":"2025-10-08T11:05:07Z","isPatch":true,"sender":{"key":"kristofferhaugsbakk@fastmail.com","avatar":null},"body":"On Tue, Oct 7, 2025, at 12:58, Patrick Steinhardt wrote:\n> When peeling a tag to a non-tag object we repeatedly call\n> `parse_object()` on the tagged object until we find the first object\n> that isn't a tag. While this feels sensible at first, there is a big\n> catch here: `parse_object()` doesn't actually verify the type of the\n> tagged object.\n>\n> The relevant code path here eventually ends up in `parse_tag_buffer()`.\n> Here, we parset he various fields of the tag, including the \"type\". Once\n\ns/parset he/parse the/\n\n> we've figured out the type and the tagged object ID, we call one of the\n> `lookup_${type}()` functions for whatever type we have found. There is\n> two possible outcomes in the successful case:\n>\n>[snip]\n"},{"id":"528235","messageId":"6e622432-12bd-42ab-95c9-6360fde75377@app.fastmail.com","threadId":"64260","inReplyTo":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-13-916cc7c6886b@pks.im","subject":"Re: [PATCH 13/13] ref-filter: parse objects on demand","fromName":"Kristoffer Haugsbakk","fromEmail":"kristofferhaugsbakk@fastmail.com","sentAt":"2025-10-08T11:05:13Z","receivedAt":"2025-10-08T11:05:35Z","isPatch":true,"sender":{"key":"kristofferhaugsbakk@fastmail.com","avatar":null},"body":"On Tue, Oct 7, 2025, at 12:58, Patrick Steinhardt wrote:\n> When formatting an arbitray object we parse that object regardless of\n\ns/arbitray/arbitrary/\n\n> whether or not we actually need any parsed data. In fact, many of the\n> atoms we have don't require any.\n>\n>[snip]\n"},{"id":"528243","messageId":"aOZqVAOjVboM3sm3@pks.im","threadId":"64260","inReplyTo":"jrst5hft3o7ee72hrmswhrnz46rgvjihdxgfsougg5u5vs6os4@2prgx3uw6qp7","subject":"Re: [PATCH 01/13] refs: introduce wrapper struct for `each_ref_fn`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-08T13:42:44Z","receivedAt":"2025-10-08T13:42:50Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Oct 07, 2025 at 01:05:15PM -0500, Justin Tobler wrote:\n> On 25/10/07 12:58PM, Patrick Steinhardt wrote:\n> > The `each_ref_fn` callback function type is used across our code base\n> > for several different functions that iterate through reference. There's\n> > a bunch of callbacks implementing this type, which makes any changes to\n> > the callback signature extremely noisy. An example of the required churn\n> > is e8207717f1 (refs: add referent to each_ref_fn, 2024-08-09): adding a\n> > single argument required us to change 48 files.\n> > \n> > It was already proposed back then [1] that we might want to introduce a\n> > wrapper structure to alleviate the pain going forward. While this of\n> > course requires the same kind of global refactoring as just introducing\n> > a new parameter, it at least allows us to more change the callback type\n> > afterwards by just extending the wrapper structure.\n> > \n> > One counterargument to this refactoring is that it makes the structure\n> > more opaque. While it is obvious which callsites need to be fixed up\n> > when we change the function type, it's not obvious anymore once we use\n> > a structure. That being said, we only have a handful of sites that\n> > actually need to populate this wrapper structure: our ref backends and\n> > \"refs/iterator.c\".\n> \n> It looks like we also populate `stuct reference` in a couple other spots\n> where we invoke the callback explicitly.\n\nFair, let me mention that.\n\n[snip]\n> >  /*\n> >   * The signature for the callback function for the for_each_*()\n> >   * functions below.  The memory pointed to by the refname and oid\n> >   * arguments is only guaranteed to be valid for the duration of a\n> >   * single callback invocation.\n> >   */\n> \n> Should we update this comment now that these fields are contained the\n> wrapper struct?\n\nYeah, will do.\n\nPatrick\n"},{"id":"528244","messageId":"aOZqsM2TKv3g8lJ3@pks.im","threadId":"64260","inReplyTo":"CAOLa=ZR0KWkpB4kjFm2g8F-PK_LWpE20P21rmJGOLONCkfN7yA@mail.gmail.com","subject":"Re: [PATCH 02/13] refs: introduce `.ref` field for the base iterator","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-08T13:44:16Z","receivedAt":"2025-10-08T13:44:21Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Oct 07, 2025 at 07:24:01AM -0700, Karthik Nayak wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > The base iterator has a couple of fields that tracks the name, target,\n> > object ID and flags for the current reference. Due do this design we\n> > have to create a new `struct reference` whenever we want to hand over\n> > that reference to the callback function, which is tedious and not very\n> > efficient.\n> >\n> > Convert the structure to instead contain a `stuct reference` as member.\n> \n> s/stuct/struct\n> \n> > This member is expected to be populated by the implementations of the\n> > iterator and is handed over to the callback directly.\n> >\n> \n> Wouldn't this also add the burden on each backend to ensure we don't\n> serve stale data for each '_advance()' call?\n> \n> Would it make sense to reset this data in `ref_iterator_advance()`?\n\nYeah, that's true indeed. I was a bit hesitant to do such a change\nthough because the ref iterator is part of a bunch of hot loops. I'll do\nsome benchmarking here to figure out whether a memset(3p) would have a\nnegative impact on performance.\n\nPatrick\n"},{"id":"528245","messageId":"aOZqt7s3wWtIK8ev@pks.im","threadId":"64260","inReplyTo":"CAOLa=ZTL7kY6_2AqjYJGrjnKgwWVBbjrtgha-dBR-ijL7C87tg@mail.gmail.com","subject":"Re: [PATCH 03/13] refs: refactor reference status flags","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-08T13:44:23Z","receivedAt":"2025-10-08T13:44:28Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Oct 07, 2025 at 07:27:55AM -0700, Karthik Nayak wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> > diff --git a/refs.h b/refs.h\n> > index 2b24a3d997..8f484e2ffc 100644\n> > --- a/refs.h\n> > +++ b/refs.h\n> > @@ -333,27 +333,28 @@ struct ref_transaction;\n> >   * stored in ref_iterator::flags. Other bits are for internal use\n> >   * only:\n> >   */\n> > +enum reference_status {\n> > +\t/* Reference is a symbolic reference. */\n> > +\tREF_ISSYMREF = (1 << 0),\n> >\n> \n> Nit: It would also be nice to rename these flags to\n> 'REFERENCE_STATUS_*', since that seems to be the pattern we generally\n> follow. Doesn't have to be in your series though :)\n\nYeah, let's rather punt this to another series. There's hundreds of uses\nof these, and I feel like the series is already quite big.\n\nPatrick\n"},{"id":"528246","messageId":"aOZq3HlVV7tF4VSJ@pks.im","threadId":"64260","inReplyTo":"CAOLa=ZRQuLa_xD8GzynHNmNZuyoJeK9dCBOKbUfkCES4ejG0OA@mail.gmail.com","subject":"Re: [PATCH 04/13] refs: expose peeled object ID via the iterator","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-08T13:45:00Z","receivedAt":"2025-10-08T13:45:06Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Oct 07, 2025 at 07:52:43AM -0700, Karthik Nayak wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > Both the \"files\" and \"reftable\" backend are able to store peeled values\n> > for tags in the respective formats. This allows for a more efficient\n> > lookup of the target object of such a tag without having to manually\n> > peel via the object database.\n> >\n> \n> In the 'files' backend, I thought only packed-refs store peeled values?\n\nYup, peeled values may or may not be available with the \"files\" backend,\ndepending on whether or not a reference is packed. But the paragraph\ndoesn't really contradict this, does it?\n\nPatrick\n"},{"id":"528247","messageId":"aOZq5UtJglYQhD26@pks.im","threadId":"64260","inReplyTo":"CAOLa=ZRdcXUQLXK1s1JLgZAcEYx=kT-eS6CMzCocJ9Oenia_Jw@mail.gmail.com","subject":"Re: [PATCH 05/13] upload-pack: convert to use `reference_get_peeled_oid()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-08T13:45:09Z","receivedAt":"2025-10-08T13:45:14Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Oct 07, 2025 at 09:18:52AM -0700, Karthik Nayak wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > The `write_v0_ref()` callback is invoked from two callsites:\n> >\n> \n> Okay so this function does multiple things based on whether the\n> capabilities are already advertised or not.\n> \n> - If not, we propagate the capabilities and set the static variable\n>   `capabilities` to NULL and also set `data->sent_capabilities = 1;`.\n>   - We receive `ref->oid` as a zero oid for the hash algorithm being\n>     used, we convert it to the hex format with `oid_to_hex()`.\n> - If already advertised, we simply propagate the reference and if it can\n>   be peeled, also propagate the peeled reference.\n> \n> Not for your series: but this feels like the capabilities should be an\n> independent function.\n\nYeah, agreed, it's a bit of a weird design choice that should be cleaned\nup eventually.\n\nPatrick\n"},{"id":"528248","messageId":"aOZrBHSnkBWPw9ma@pks.im","threadId":"64260","inReplyTo":"6e622432-12bd-42ab-95c9-6360fde75377@app.fastmail.com","subject":"Re: [PATCH 13/13] ref-filter: parse objects on demand","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-08T13:45:40Z","receivedAt":"2025-10-08T13:45:45Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Oct 08, 2025 at 01:05:13PM +0200, Kristoffer Haugsbakk wrote:\n> On Tue, Oct 7, 2025, at 12:58, Patrick Steinhardt wrote:\n> > When formatting an arbitray object we parse that object regardless of\n> \n> s/arbitray/arbitrary/\n\nThanks, I've addressed both of your typo fixes locally.\n\nPatrick\n"},{"id":"528255","messageId":"aOZ9Lhjo1n3B70kF@pks.im","threadId":"64260","inReplyTo":"aOZqsM2TKv3g8lJ3@pks.im","subject":"Re: [PATCH 02/13] refs: introduce `.ref` field for the base iterator","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-08T15:03:10Z","receivedAt":"2025-10-08T15:03:17Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Oct 08, 2025 at 03:44:16PM +0200, Patrick Steinhardt wrote:\n> On Tue, Oct 07, 2025 at 07:24:01AM -0700, Karthik Nayak wrote:\n> > Patrick Steinhardt <ps@pks.im> writes:\n> > \n> > > The base iterator has a couple of fields that tracks the name, target,\n> > > object ID and flags for the current reference. Due do this design we\n> > > have to create a new `struct reference` whenever we want to hand over\n> > > that reference to the callback function, which is tedious and not very\n> > > efficient.\n> > >\n> > > Convert the structure to instead contain a `stuct reference` as member.\n> > \n> > s/stuct/struct\n> > \n> > > This member is expected to be populated by the implementations of the\n> > > iterator and is handed over to the callback directly.\n> > >\n> > \n> > Wouldn't this also add the burden on each backend to ensure we don't\n> > serve stale data for each '_advance()' call?\n> > \n> > Would it make sense to reset this data in `ref_iterator_advance()`?\n> \n> Yeah, that's true indeed. I was a bit hesitant to do such a change\n> though because the ref iterator is part of a bunch of hot loops. I'll do\n> some benchmarking here to figure out whether a memset(3p) would have a\n> negative impact on performance.\n\nOkay, I've measured this and it doesn't make any difference. I'll add\nanother patch to do this.\n\nPatrick\n"},{"id":"528258","messageId":"20251008-b4-pks-ref-filter-skip-parsing-objects-v2-0-76e30d5c9542@pks.im","threadId":"64260","inReplyTo":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-0-916cc7c6886b@pks.im","subject":"[PATCH v2 00/14] refs: improvements and fixes for peeling tags","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-08T15:50:15Z","receivedAt":"2025-10-08T15:50:29Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\noriginally, all I wanted to do was the last patch: a small performance\noptimization that stops parsing objects in git-for-each-ref(1) unless we\nreally need to parse them. But that fix cause one specific test to fail,\nand only with the reftable backend. So this led me down the rabbit hole\nof tag peeling, ending up with this patch series.\n\nThe series is structured like follows:\n\n  - Patches 1 to 8 refactor our codebase so that we don't have the\n    `peel_iterated_object()` hack anymore. I just found it hard to\n    follow and thought it shouldn't be too hard to get rid of it.\n\n  - Patches 9 and 10 remove infrastructure that we don't need anymore\n    after the first couple of patches.\n\n  - Patches 11 to 13 fix a couple of issues with peeled tags that I\n    found. The underlying issue is that tags store both the tagged\n    object and their type, but this information may not match. We never\n    verify the actual object type though when allocating the tagged\n    object, so this only blows up much later.\n\n  - Patch 14 was my original motivation, a small performance\n    optimization.\n\nI'm not particularly fond of the patches 11 to 13. It feels more like\nplaying whack-a-mole, and I very much assume that there still are edge\ncases where we should properly verify the tagged object type. But\nchanging it in `parse_tag_buffer()` itself causes a bunch of tests to\nfail where we intentionally create such corrupted tags. So I didn't\nreally dare to touch that part, to be honest.\n\nIf anybody has suggestions for an alternative approach I'd be very open\nto it.\n\nThe topic is built on top of 45547b60ac (Merge branch 'master' of\nhttps://github.com/j6t/gitk, 2025-10-05). There is a merge conflict with\ntb/incremental-midx-part-3.1, which moves code from \"builtin/repack.c\"\ninto \"repack-*.c\".\n\nChanges in v2:\n  - A couple of improvements to commit messages.\n  - A new commit that ensures that `struct ref_iterator::ref` is always\n    zeroed out to protect against stale state.\n  - Link to v1: https://lore.kernel.org/r/20251007-b4-pks-ref-filter-skip-parsing-objects-v1-0-916cc7c6886b@pks.im\n\nThanks!\n\nPatrick\n\n---\nPatrick Steinhardt (14):\n      refs: introduce wrapper struct for `each_ref_fn`\n      refs: introduce `.ref` field for the base iterator\n      refs: fully reset `struct ref_iterator::ref` on iteration\n      refs: refactor reference status flags\n      refs: expose peeled object ID via the iterator\n      upload-pack: convert to use `reference_get_peeled_oid()`\n      ref-filter: propagate peeled object ID\n      builtin/show-ref: convert to use `reference_get_peeled_oid()`\n      refs: drop `current_ref_iter` hack\n      refs: drop infrastructure to peel via iterators\n      object: add flag to `peel_object()` to verify object type\n      refs: don't store peeled object IDs for invalid tags\n      ref-filter: detect broken tags when dereferencing them\n      ref-filter: parse objects on demand\n\n bisect.c                    |  24 ++---\n builtin/bisect.c            |  17 +---\n builtin/checkout.c          |   6 +-\n builtin/describe.c          |  18 ++--\n builtin/fetch.c             |  13 +--\n builtin/fsck.c              |  33 +++---\n builtin/gc.c                |  15 ++-\n builtin/ls-remote.c         |   2 +-\n builtin/name-rev.c          |  17 ++--\n builtin/pack-objects.c      |  28 +++---\n builtin/receive-pack.c      |  13 ++-\n builtin/remote.c            |  44 ++++----\n builtin/repack.c            |  16 ++-\n builtin/replace.c           |  21 ++--\n builtin/rev-parse.c         |  12 +--\n builtin/show-branch.c       |  35 +++----\n builtin/show-ref.c          |  50 ++++-----\n builtin/submodule--helper.c |  10 +-\n builtin/tag.c               |   2 +-\n builtin/verify-tag.c        |   2 +-\n builtin/worktree.c          |   6 +-\n commit-graph.c              |  14 ++-\n delta-islands.c             |   9 +-\n fetch-pack.c                |  16 +--\n help.c                      |  10 +-\n http-backend.c              |  20 ++--\n log-tree.c                  |  24 ++---\n ls-refs.c                   |  36 ++++---\n midx-write.c                |  17 ++--\n negotiator/default.c        |   7 +-\n negotiator/skipping.c       |   7 +-\n notes.c                     |   8 +-\n object-name.c               |  10 +-\n object.c                    |  20 +++-\n object.h                    |  15 ++-\n pseudo-merge.c              |  21 ++--\n reachable.c                 |   9 +-\n ref-filter.c                | 239 ++++++++++++++++++++++++++++++--------------\n ref-filter.h                |   5 +-\n reflog.c                    |   9 +-\n refs.c                      |  85 +++++++++-------\n refs.h                      |  88 ++++++++++------\n refs/debug.c                |  17 +---\n refs/files-backend.c        |  71 +++++--------\n refs/iterator.c             |  73 +++-----------\n refs/packed-backend.c       |  71 +++++--------\n refs/ref-cache.c            |  18 +---\n refs/refs-internal.h        |  25 +----\n refs/reftable-backend.c     |  47 +++------\n remote.c                    |  27 +++--\n replace-object.c            |  16 ++-\n revision.c                  |  12 +--\n server-info.c               |  12 +--\n shallow.c                   |  16 +--\n submodule.c                 |  12 +--\n t/for-each-ref-tests.sh     |   4 +-\n t/helper/test-reach.c       |   2 +-\n t/helper/test-ref-store.c   |   5 +-\n t/pack-refs-tests.sh        |  32 ++++++\n t/t0610-reftable-basics.sh  |  28 ++++++\n tag.c                       |  12 ---\n tag.h                       |   1 -\n upload-pack.c               |  49 ++++-----\n walker.c                    |   8 +-\n worktree.c                  |  11 +-\n 65 files changed, 791 insertions(+), 831 deletions(-)\n\nRange-diff versus v1:\n\n 1:  4f4de68657 !  1:  3d0c5110b3 refs: introduce wrapper struct for `each_ref_fn`\n    @@ Commit message\n         more opaque. While it is obvious which callsites need to be fixed up\n         when we change the function type, it's not obvious anymore once we use\n         a structure. That being said, we only have a handful of sites that\n    -    actually need to populate this wrapper structure: our ref backends and\n    -    \"refs/iterator.c\".\n    +    actually need to populate this wrapper structure: our ref backends,\n    +    \"refs/iterator.c\" as well as very few sites that invoke the iterator\n    +    callback functions directly.\n     \n         Introduce this wrapper structure so that we can adapt the iterator\n         interfaces more readily.\n    @@ refs.h: struct ref_transaction;\n     +\n      /*\n       * The signature for the callback function for the for_each_*()\n    -  * functions below.  The memory pointed to by the refname and oid\n    -  * arguments is only guaranteed to be valid for the duration of a\n    +- * functions below.  The memory pointed to by the refname and oid\n    +- * arguments is only guaranteed to be valid for the duration of a\n    ++ * functions below.  The memory pointed to by the `struct reference`\n    ++ * argument is only guaranteed to be valid for the duration of a\n       * single callback invocation.\n       */\n     -typedef int each_ref_fn(const char *refname, const char *referent,\n 2:  e90c26f12c !  2:  412818b8d1 refs: introduce `.ref` field for the base iterator\n    @@ Commit message\n         refs: introduce `.ref` field for the base iterator\n     \n         The base iterator has a couple of fields that tracks the name, target,\n    -    object ID and flags for the current reference. Due do this design we\n    +    object ID and flags for the current reference. Due to this design we\n         have to create a new `struct reference` whenever we want to hand over\n         that reference to the callback function, which is tedious and not very\n         efficient.\n     \n    -    Convert the structure to instead contain a `stuct reference` as member.\n    +    Convert the structure to instead contain a `struct reference` as member.\n         This member is expected to be populated by the implementations of the\n         iterator and is handed over to the callback directly.\n     \n    +    While at it, simplify `should_pack_ref()` to take a `struct reference`\n    +    directly instead of passing its respective fields.\n    +\n         Signed-off-by: Patrick Steinhardt <ps@pks.im>\n     \n      ## refs.c ##\n -:  ---------- >  3:  231adc4960 refs: fully reset `struct ref_iterator::ref` on iteration\n 3:  bc0432dc35 =  4:  1d2189e7a9 refs: refactor reference status flags\n 4:  aba078d59c !  5:  1f975f1079 refs: expose peeled object ID via the iterator\n    @@ refs.h: struct reference {\n     +\n      /*\n       * The signature for the callback function for the for_each_*()\n    -  * functions below.  The memory pointed to by the refname and oid\n    +  * functions below.  The memory pointed to by the `struct reference`\n     \n      ## refs/packed-backend.c ##\n     @@ refs/packed-backend.c: static int next_record(struct packed_ref_iterator *iter)\n    - \t\tif ((iter->base.ref.flags & REF_ISBROKEN)) {\n    - \t\t\toidclr(&iter->peeled, iter->repo->hash_algo);\n      \t\t\titer->base.ref.flags &= ~REF_KNOWS_PEELED;\n    -+\t\t\titer->base.ref.peeled_oid = NULL;\n      \t\t} else {\n      \t\t\titer->base.ref.flags |= REF_KNOWS_PEELED;\n     +\t\t\titer->base.ref.peeled_oid = &iter->peeled;\n      \t\t}\n      \t} else {\n      \t\toidclr(&iter->peeled, iter->repo->hash_algo);\n    -+\t\titer->base.ref.peeled_oid = NULL;\n    - \t}\n    - \n    - \treturn ITER_OK;\n    -\n    - ## refs/ref-cache.c ##\n    -@@ refs/ref-cache.c: static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator)\n    - \t\t\titer->base.ref.name = entry->name;\n    - \t\t\titer->base.ref.target = entry->u.value.referent;\n    - \t\t\titer->base.ref.oid = &entry->u.value.oid;\n    -+\t\t\titer->base.ref.peeled_oid = NULL;\n    - \t\t\titer->base.ref.flags = entry->flag;\n    - \t\t\treturn ITER_OK;\n    - \t\t}\n     \n      ## refs/reftable-backend.c ##\n     @@ refs/reftable-backend.c: struct reftable_ref_iterator {\n    @@ refs/reftable-backend.c: static int reftable_ref_iterator_advance(struct ref_ite\n      \t\titer->base.ref.oid = &iter->oid;\n     +\t\tif (iter->ref.value_type == REFTABLE_REF_VAL2)\n     +\t\t\titer->base.ref.peeled_oid = &iter->peeled_oid;\n    -+\t\telse\n    -+\t\t\titer->base.ref.peeled_oid = NULL;\n      \t\titer->base.ref.flags = flags;\n      \n      \t\tbreak;\n 5:  664775be02 !  6:  8f3b757726 upload-pack: convert to use `reference_get_peeled_oid()`\n    @@ Commit message\n         The `write_v0_ref()` callback is invoked from two callsites:\n     \n           - Once via `send_ref()` which is a callback passed to\n    -        `for_each_namespaced_ref_1()`.\n    +        `for_each_namespaced_ref_1()` and `refs_head_ref_namespaced()`.\n     \n           - Once manually to announce capabilities.\n     \n 6:  e0c95a3df2 =  7:  603213919f ref-filter: propagate peeled object ID\n 7:  9ef21d25d1 =  8:  8d3cae65f8 builtin/show-ref: convert to use `reference_get_peeled_oid()`\n 8:  2d32a2410d =  9:  35226e7a7d refs: drop `current_ref_iter` hack\n 9:  a9fbbe6b1a = 10:  2e73f3b227 refs: drop infrastructure to peel via iterators\n10:  68b3a85ade ! 11:  ade4d5875a object: add flag to `peel_object()` to verify object type\n    @@ Commit message\n         tagged object.\n     \n         The relevant code path here eventually ends up in `parse_tag_buffer()`.\n    -    Here, we parset he various fields of the tag, including the \"type\". Once\n    +    Here, we parse the various fields of the tag, including the \"type\". Once\n         we've figured out the type and the tagged object ID, we call one of the\n         `lookup_${type}()` functions for whatever type we have found. There is\n         two possible outcomes in the successful case:\n11:  80ec80883e = 12:  c1ccb8e6e1 refs: don't store peeled object IDs for invalid tags\n12:  8056c68337 = 13:  e5db08d3fa ref-filter: detect broken tags when dereferencing them\n13:  40cc049093 ! 14:  617d03d8c1 ref-filter: parse objects on demand\n    @@ Metadata\n      ## Commit message ##\n         ref-filter: parse objects on demand\n     \n    -    When formatting an arbitray object we parse that object regardless of\n    +    When formatting an arbitrary object we parse that object regardless of\n         whether or not we actually need any parsed data. In fact, many of the\n         atoms we have don't require any.\n     \n\n---\nbase-commit: 45547b60aca32b45d2f1ef93462cf9df28637c13\nchange-id: 20250918-b4-pks-ref-filter-skip-parsing-objects-f0d1f6af4a9f\n\n"},{"id":"528260","messageId":"20251008-b4-pks-ref-filter-skip-parsing-objects-v2-1-76e30d5c9542@pks.im","threadId":"64260","inReplyTo":"20251008-b4-pks-ref-filter-skip-parsing-objects-v2-0-76e30d5c9542@pks.im","subject":"[PATCH v2 01/14] refs: introduce wrapper struct for `each_ref_fn`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-08T15:50:16Z","receivedAt":"2025-10-08T15:50:31Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `each_ref_fn` callback function type is used across our code base\nfor several different functions that iterate through reference. There's\na bunch of callbacks implementing this type, which makes any changes to\nthe callback signature extremely noisy. An example of the required churn\nis e8207717f1 (refs: add referent to each_ref_fn, 2024-08-09): adding a\nsingle argument required us to change 48 files.\n\nIt was already proposed back then [1] that we might want to introduce a\nwrapper structure to alleviate the pain going forward. While this of\ncourse requires the same kind of global refactoring as just introducing\na new parameter, it at least allows us to more change the callback type\nafterwards by just extending the wrapper structure.\n\nOne counterargument to this refactoring is that it makes the structure\nmore opaque. While it is obvious which callsites need to be fixed up\nwhen we change the function type, it's not obvious anymore once we use\na structure. That being said, we only have a handful of sites that\nactually need to populate this wrapper structure: our ref backends,\n\"refs/iterator.c\" as well as very few sites that invoke the iterator\ncallback functions directly.\n\nIntroduce this wrapper structure so that we can adapt the iterator\ninterfaces more readily.\n\n[1]: <ZmarVcF5JjsZx0dl@tanuki>\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n bisect.c                    | 24 +++++++---------\n builtin/bisect.c            | 17 ++++--------\n builtin/checkout.c          |  6 ++--\n builtin/describe.c          | 18 ++++++------\n builtin/fetch.c             | 13 +++------\n builtin/fsck.c              | 33 ++++++++++++----------\n builtin/gc.c                | 15 ++++------\n builtin/name-rev.c          | 17 ++++++------\n builtin/pack-objects.c      | 27 ++++++++----------\n builtin/receive-pack.c      | 13 ++++-----\n builtin/remote.c            | 44 +++++++++++++----------------\n builtin/repack.c            | 16 +++++------\n builtin/replace.c           | 21 ++++++--------\n builtin/rev-parse.c         | 12 ++++----\n builtin/show-branch.c       | 35 +++++++++++------------\n builtin/show-ref.c          | 20 ++++++--------\n builtin/submodule--helper.c | 10 ++-----\n builtin/worktree.c          |  6 +---\n commit-graph.c              | 14 ++++------\n delta-islands.c             |  9 +++---\n fetch-pack.c                | 16 +++--------\n help.c                      | 10 +++----\n http-backend.c              | 20 ++++++--------\n log-tree.c                  | 24 ++++++++--------\n ls-refs.c                   | 36 ++++++++++++++----------\n midx-write.c                | 17 ++++++------\n negotiator/default.c        |  7 ++---\n negotiator/skipping.c       |  7 ++---\n notes.c                     |  8 ++----\n object-name.c               | 10 +++----\n pseudo-merge.c              | 21 ++++++--------\n reachable.c                 |  9 +++---\n ref-filter.c                | 24 ++++++++--------\n reflog.c                    |  9 ++----\n refs.c                      | 67 +++++++++++++++++++++++++--------------------\n refs.h                      | 26 +++++++++++++++---\n refs/files-backend.c        |  7 ++---\n refs/iterator.c             |  9 +++++-\n remote.c                    | 27 ++++++++----------\n replace-object.c            | 16 ++++-------\n revision.c                  | 12 ++++----\n server-info.c               | 12 ++++----\n shallow.c                   | 16 +++--------\n submodule.c                 | 12 ++------\n t/helper/test-ref-store.c   |  5 ++--\n upload-pack.c               | 29 +++++++++-----------\n walker.c                    |  8 ++----\n worktree.c                  | 11 ++++++--\n 48 files changed, 389 insertions(+), 456 deletions(-)\n\ndiff --git a/bisect.c b/bisect.c\nindex a6dc76b15c9..326b59c0dc7 100644\n--- a/bisect.c\n+++ b/bisect.c\n@@ -450,21 +450,20 @@ void find_bisection(struct commit_list **commit_list, int *reaches,\n \tclear_commit_weight(&commit_weight);\n }\n \n-static int register_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\tint flags UNUSED, void *cb_data UNUSED)\n+static int register_ref(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct strbuf good_prefix = STRBUF_INIT;\n \tstrbuf_addstr(&good_prefix, term_good);\n \tstrbuf_addstr(&good_prefix, \"-\");\n \n-\tif (!strcmp(refname, term_bad)) {\n+\tif (!strcmp(ref->name, term_bad)) {\n \t\tfree(current_bad_oid);\n \t\tcurrent_bad_oid = xmalloc(sizeof(*current_bad_oid));\n-\t\toidcpy(current_bad_oid, oid);\n-\t} else if (starts_with(refname, good_prefix.buf)) {\n-\t\toid_array_append(&good_revs, oid);\n-\t} else if (starts_with(refname, \"skip-\")) {\n-\t\toid_array_append(&skipped_revs, oid);\n+\t\toidcpy(current_bad_oid, ref->oid);\n+\t} else if (starts_with(ref->name, good_prefix.buf)) {\n+\t\toid_array_append(&good_revs, ref->oid);\n+\t} else if (starts_with(ref->name, \"skip-\")) {\n+\t\toid_array_append(&skipped_revs, ref->oid);\n \t}\n \n \tstrbuf_release(&good_prefix);\n@@ -1178,14 +1177,11 @@ int estimate_bisect_steps(int all)\n \treturn (e < 3 * x) ? n : n - 1;\n }\n \n-static int mark_for_removal(const char *refname,\n-\t\t\t    const char *referent UNUSED,\n-\t\t\t    const struct object_id *oid UNUSED,\n-\t\t\t    int flag UNUSED, void *cb_data)\n+static int mark_for_removal(const struct reference *ref, void *cb_data)\n {\n \tstruct string_list *refs = cb_data;\n-\tchar *ref = xstrfmt(\"refs/bisect%s\", refname);\n-\tstring_list_append(refs, ref);\n+\tchar *bisect_ref = xstrfmt(\"refs/bisect%s\", ref->name);\n+\tstring_list_append(refs, bisect_ref);\n \treturn 0;\n }\n \ndiff --git a/builtin/bisect.c b/builtin/bisect.c\nindex 8b8d870cd1e..5b2024be62d 100644\n--- a/builtin/bisect.c\n+++ b/builtin/bisect.c\n@@ -358,10 +358,7 @@ static int check_and_set_terms(struct bisect_terms *terms, const char *cmd)\n \treturn 0;\n }\n \n-static int inc_nr(const char *refname UNUSED,\n-\t\t  const char *referent UNUSED,\n-\t\t  const struct object_id *oid UNUSED,\n-\t\t  int flag UNUSED, void *cb_data)\n+static int inc_nr(const struct reference *ref UNUSED, void *cb_data)\n {\n \tunsigned int *nr = (unsigned int *)cb_data;\n \t(*nr)++;\n@@ -549,12 +546,11 @@ static int bisect_append_log_quoted(const char **argv)\n \treturn res;\n }\n \n-static int add_bisect_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t  int flags UNUSED, void *cb)\n+static int add_bisect_ref(const struct reference *ref, void *cb)\n {\n \tstruct add_bisect_ref_data *data = cb;\n \n-\tadd_pending_oid(data->revs, refname, oid, data->object_flags);\n+\tadd_pending_oid(data->revs, ref->name, ref->oid, data->object_flags);\n \n \treturn 0;\n }\n@@ -1165,12 +1161,9 @@ static int bisect_visualize(struct bisect_terms *terms, int argc,\n \treturn run_command(&cmd);\n }\n \n-static int get_first_good(const char *refname UNUSED,\n-\t\t\t  const char *referent UNUSED,\n-\t\t\t  const struct object_id *oid,\n-\t\t\t  int flag UNUSED, void *cb_data)\n+static int get_first_good(const struct reference *ref, void *cb_data)\n {\n-\toidcpy(cb_data, oid);\n+\toidcpy(cb_data, ref->oid);\n \treturn 1;\n }\n \ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex f9453473fe2..66b69df6e67 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1063,11 +1063,9 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\treport_tracking(new_branch_info);\n }\n \n-static int add_pending_uninteresting_ref(const char *refname, const char *referent UNUSED,\n-\t\t\t\t\t const struct object_id *oid,\n-\t\t\t\t\t int flags UNUSED, void *cb_data)\n+static int add_pending_uninteresting_ref(const struct reference *ref, void *cb_data)\n {\n-\tadd_pending_oid(cb_data, refname, oid, UNINTERESTING);\n+\tadd_pending_oid(cb_data, ref->name, ref->oid, UNINTERESTING);\n \treturn 0;\n }\n \ndiff --git a/builtin/describe.c b/builtin/describe.c\nindex ffaf8d9f0aa..79545350443 100644\n--- a/builtin/describe.c\n+++ b/builtin/describe.c\n@@ -154,20 +154,19 @@ static void add_to_known_names(const char *path,\n \t}\n }\n \n-static int get_name(const char *path, const char *referent UNUSED, const struct object_id *oid,\n-\t\t    int flag UNUSED, void *cb_data UNUSED)\n+static int get_name(const struct reference *ref, void *cb_data UNUSED)\n {\n \tint is_tag = 0;\n \tstruct object_id peeled;\n \tint is_annotated, prio;\n \tconst char *path_to_match = NULL;\n \n-\tif (skip_prefix(path, \"refs/tags/\", &path_to_match)) {\n+\tif (skip_prefix(ref->name, \"refs/tags/\", &path_to_match)) {\n \t\tis_tag = 1;\n \t} else if (all) {\n \t\tif ((exclude_patterns.nr || patterns.nr) &&\n-\t\t    !skip_prefix(path, \"refs/heads/\", &path_to_match) &&\n-\t\t    !skip_prefix(path, \"refs/remotes/\", &path_to_match)) {\n+\t\t    !skip_prefix(ref->name, \"refs/heads/\", &path_to_match) &&\n+\t\t    !skip_prefix(ref->name, \"refs/remotes/\", &path_to_match)) {\n \t\t\t/* Only accept reference of known type if there are match/exclude patterns */\n \t\t\treturn 0;\n \t\t}\n@@ -209,10 +208,10 @@ static int get_name(const char *path, const char *referent UNUSED, const struct\n \t}\n \n \t/* Is it annotated? */\n-\tif (!peel_iterated_oid(the_repository, oid, &peeled)) {\n-\t\tis_annotated = !oideq(oid, &peeled);\n+\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled)) {\n+\t\tis_annotated = !oideq(ref->oid, &peeled);\n \t} else {\n-\t\toidcpy(&peeled, oid);\n+\t\toidcpy(&peeled, ref->oid);\n \t\tis_annotated = 0;\n \t}\n \n@@ -229,7 +228,8 @@ static int get_name(const char *path, const char *referent UNUSED, const struct\n \telse\n \t\tprio = 0;\n \n-\tadd_to_known_names(all ? path + 5 : path + 10, &peeled, prio, oid);\n+\tadd_to_known_names(all ? ref->name + 5 : ref->name + 10,\n+\t\t\t   &peeled, prio, ref->oid);\n \treturn 0;\n }\n \ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex c7ff3480fb1..7052e6ff215 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -289,13 +289,11 @@ static struct refname_hash_entry *refname_hash_add(struct hashmap *map,\n \treturn ent;\n }\n \n-static int add_one_refname(const char *refname, const char *referent UNUSED,\n-\t\t\t   const struct object_id *oid,\n-\t\t\t   int flag UNUSED, void *cbdata)\n+static int add_one_refname(const struct reference *ref, void *cbdata)\n {\n \tstruct hashmap *refname_map = cbdata;\n \n-\t(void) refname_hash_add(refname_map, refname, oid);\n+\t(void) refname_hash_add(refname_map, ref->name, ref->oid);\n \treturn 0;\n }\n \n@@ -1416,14 +1414,11 @@ static void set_option(struct transport *transport, const char *name, const char\n }\n \n \n-static int add_oid(const char *refname UNUSED,\n-\t\t   const char *referent UNUSED,\n-\t\t   const struct object_id *oid,\n-\t\t   int flags UNUSED, void *cb_data)\n+static int add_oid(const struct reference *ref, void *cb_data)\n {\n \tstruct oid_array *oids = cb_data;\n \n-\toid_array_append(oids, oid);\n+\toid_array_append(oids, ref->oid);\n \treturn 0;\n }\n \ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex d2eb9d4fbe9..2b8fa22cccb 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -530,14 +530,13 @@ static int fsck_handle_reflog(const char *logname, void *cb_data)\n \treturn 0;\n }\n \n-static int fsck_handle_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t   int flag UNUSED, void *cb_data UNUSED)\n+static int fsck_handle_ref(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct object *obj;\n \n-\tobj = parse_object(the_repository, oid);\n+\tobj = parse_object(the_repository, ref->oid);\n \tif (!obj) {\n-\t\tif (is_promisor_object(the_repository, oid)) {\n+\t\tif (is_promisor_object(the_repository, ref->oid)) {\n \t\t\t/*\n \t\t\t * Increment default_refs anyway, because this is a\n \t\t\t * valid ref.\n@@ -546,19 +545,19 @@ static int fsck_handle_ref(const char *refname, const char *referent UNUSED, con\n \t\t\t return 0;\n \t\t}\n \t\terror(_(\"%s: invalid sha1 pointer %s\"),\n-\t\t      refname, oid_to_hex(oid));\n+\t\t      ref->name, oid_to_hex(ref->oid));\n \t\terrors_found |= ERROR_REACHABLE;\n \t\t/* We'll continue with the rest despite the error.. */\n \t\treturn 0;\n \t}\n-\tif (obj->type != OBJ_COMMIT && is_branch(refname)) {\n-\t\terror(_(\"%s: not a commit\"), refname);\n+\tif (obj->type != OBJ_COMMIT && is_branch(ref->name)) {\n+\t\terror(_(\"%s: not a commit\"), ref->name);\n \t\terrors_found |= ERROR_REFS;\n \t}\n \tdefault_refs++;\n \tobj->flags |= USED;\n \tfsck_put_object_name(&fsck_walk_options,\n-\t\t\t     oid, \"%s\", refname);\n+\t\t\t     ref->oid, \"%s\", ref->name);\n \tmark_object_reachable(obj);\n \n \treturn 0;\n@@ -580,13 +579,19 @@ static void get_default_heads(void)\n \tworktrees = get_worktrees();\n \tfor (p = worktrees; *p; p++) {\n \t\tstruct worktree *wt = *p;\n-\t\tstruct strbuf ref = STRBUF_INIT;\n+\t\tstruct strbuf refname = STRBUF_INIT;\n \n-\t\tstrbuf_worktree_ref(wt, &ref, \"HEAD\");\n-\t\tfsck_head_link(ref.buf, &head_points_at, &head_oid);\n-\t\tif (head_points_at && !is_null_oid(&head_oid))\n-\t\t\tfsck_handle_ref(ref.buf, NULL, &head_oid, 0, NULL);\n-\t\tstrbuf_release(&ref);\n+\t\tstrbuf_worktree_ref(wt, &refname, \"HEAD\");\n+\t\tfsck_head_link(refname.buf, &head_points_at, &head_oid);\n+\t\tif (head_points_at && !is_null_oid(&head_oid)) {\n+\t\t\tstruct reference ref = {\n+\t\t\t\t.name = refname.buf,\n+\t\t\t\t.oid = &head_oid,\n+\t\t\t};\n+\n+\t\t\tfsck_handle_ref(&ref, NULL);\n+\t\t}\n+\t\tstrbuf_release(&refname);\n \n \t\tif (include_reflogs)\n \t\t\trefs_for_each_reflog(get_worktree_ref_store(wt),\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 03ae4926b20..a104c1cb782 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -1098,24 +1098,21 @@ struct cg_auto_data {\n \tint limit;\n };\n \n-static int dfs_on_ref(const char *refname UNUSED,\n-\t\t      const char *referent UNUSED,\n-\t\t      const struct object_id *oid,\n-\t\t      int flags UNUSED,\n-\t\t      void *cb_data)\n+static int dfs_on_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct cg_auto_data *data = (struct cg_auto_data *)cb_data;\n \tint result = 0;\n+\tconst struct object_id *maybe_peeled = ref->oid;\n \tstruct object_id peeled;\n \tstruct commit_list *stack = NULL;\n \tstruct commit *commit;\n \n-\tif (!peel_iterated_oid(the_repository, oid, &peeled))\n-\t\toid = &peeled;\n-\tif (odb_read_object_info(the_repository->objects, oid, NULL) != OBJ_COMMIT)\n+\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n+\t\tmaybe_peeled = &peeled;\n+\tif (odb_read_object_info(the_repository->objects, maybe_peeled, NULL) != OBJ_COMMIT)\n \t\treturn 0;\n \n-\tcommit = lookup_commit(the_repository, oid);\n+\tcommit = lookup_commit(the_repository, maybe_peeled);\n \tif (!commit)\n \t\treturn 0;\n \tif (repo_parse_commit(the_repository, commit) ||\ndiff --git a/builtin/name-rev.c b/builtin/name-rev.c\nindex 74512e54a38..615f7d1aae4 100644\n--- a/builtin/name-rev.c\n+++ b/builtin/name-rev.c\n@@ -339,10 +339,9 @@ static int cmp_by_tag_and_age(const void *a_, const void *b_)\n \treturn a->taggerdate != b->taggerdate;\n }\n \n-static int name_ref(const char *path, const char *referent UNUSED, const struct object_id *oid,\n-\t\t    int flags UNUSED, void *cb_data)\n+static int name_ref(const struct reference *ref, void *cb_data)\n {\n-\tstruct object *o = parse_object(the_repository, oid);\n+\tstruct object *o = parse_object(the_repository, ref->oid);\n \tstruct name_ref_data *data = cb_data;\n \tint can_abbreviate_output = data->tags_only && data->name_only;\n \tint deref = 0;\n@@ -350,14 +349,14 @@ static int name_ref(const char *path, const char *referent UNUSED, const struct\n \tstruct commit *commit = NULL;\n \ttimestamp_t taggerdate = TIME_MAX;\n \n-\tif (data->tags_only && !starts_with(path, \"refs/tags/\"))\n+\tif (data->tags_only && !starts_with(ref->name, \"refs/tags/\"))\n \t\treturn 0;\n \n \tif (data->exclude_filters.nr) {\n \t\tstruct string_list_item *item;\n \n \t\tfor_each_string_list_item(item, &data->exclude_filters) {\n-\t\t\tif (subpath_matches(path, item->string) >= 0)\n+\t\t\tif (subpath_matches(ref->name, item->string) >= 0)\n \t\t\t\treturn 0;\n \t\t}\n \t}\n@@ -378,7 +377,7 @@ static int name_ref(const char *path, const char *referent UNUSED, const struct\n \t\t\t * shouldn't stop when seeing 'refs/tags/v1.4' matches\n \t\t\t * 'refs/tags/v*'.  We should show it as 'v1.4'.\n \t\t\t */\n-\t\t\tswitch (subpath_matches(path, item->string)) {\n+\t\t\tswitch (subpath_matches(ref->name, item->string)) {\n \t\t\tcase -1: /* did not match */\n \t\t\t\tbreak;\n \t\t\tcase 0: /* matched fully */\n@@ -406,13 +405,13 @@ static int name_ref(const char *path, const char *referent UNUSED, const struct\n \t}\n \tif (o && o->type == OBJ_COMMIT) {\n \t\tcommit = (struct commit *)o;\n-\t\tfrom_tag = starts_with(path, \"refs/tags/\");\n+\t\tfrom_tag = starts_with(ref->name, \"refs/tags/\");\n \t\tif (taggerdate == TIME_MAX)\n \t\t\ttaggerdate = commit->date;\n \t}\n \n-\tadd_to_tip_table(oid, path, can_abbreviate_output, commit, taggerdate,\n-\t\t\t from_tag, deref);\n+\tadd_to_tip_table(ref->oid, ref->name, can_abbreviate_output,\n+\t\t\t commit, taggerdate, from_tag, deref);\n \treturn 0;\n }\n \ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 5856b5f6bfb..288ba199811 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -831,15 +831,14 @@ static enum write_one_status write_one(struct hashfile *f,\n \treturn WRITE_ONE_WRITTEN;\n }\n \n-static int mark_tagged(const char *path UNUSED, const char *referent UNUSED, const struct object_id *oid,\n-\t\t       int flag UNUSED, void *cb_data UNUSED)\n+static int mark_tagged(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct object_id peeled;\n-\tstruct object_entry *entry = packlist_find(&to_pack, oid);\n+\tstruct object_entry *entry = packlist_find(&to_pack, ref->oid);\n \n \tif (entry)\n \t\tentry->tagged = 1;\n-\tif (!peel_iterated_oid(the_repository, oid, &peeled)) {\n+\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled)) {\n \t\tentry = packlist_find(&to_pack, &peeled);\n \t\tif (entry)\n \t\t\tentry->tagged = 1;\n@@ -3306,13 +3305,12 @@ static void add_tag_chain(const struct object_id *oid)\n \t}\n }\n \n-static int add_ref_tag(const char *tag UNUSED, const char *referent UNUSED, const struct object_id *oid,\n-\t\t       int flag UNUSED, void *cb_data UNUSED)\n+static int add_ref_tag(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct object_id peeled;\n \n-\tif (!peel_iterated_oid(the_repository, oid, &peeled) && obj_is_packed(&peeled))\n-\t\tadd_tag_chain(oid);\n+\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled) && obj_is_packed(&peeled))\n+\t\tadd_tag_chain(ref->oid);\n \treturn 0;\n }\n \n@@ -4528,19 +4526,16 @@ static void record_recent_commit(struct commit *commit, void *data UNUSED)\n \toid_array_append(&recent_objects, &commit->object.oid);\n }\n \n-static int mark_bitmap_preferred_tip(const char *refname,\n-\t\t\t\t     const char *referent UNUSED,\n-\t\t\t\t     const struct object_id *oid,\n-\t\t\t\t     int flags UNUSED,\n-\t\t\t\t     void *data UNUSED)\n+static int mark_bitmap_preferred_tip(const struct reference *ref, void *data UNUSED)\n {\n+\tconst struct object_id *maybe_peeled = ref->oid;\n \tstruct object_id peeled;\n \tstruct object *object;\n \n-\tif (!peel_iterated_oid(the_repository, oid, &peeled))\n-\t\toid = &peeled;\n+\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n+\t\tmaybe_peeled = &peeled;\n \n-\tobject = parse_object_or_die(the_repository, oid, refname);\n+\tobject = parse_object_or_die(the_repository, maybe_peeled, ref->name);\n \tif (object->type == OBJ_COMMIT)\n \t\tobject->flags |= NEEDS_BITMAP;\n \ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 1113137a6f0..2a45c0867e3 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -305,13 +305,12 @@ static void show_ref(const char *path, const struct object_id *oid)\n \t}\n }\n \n-static int show_ref_cb(const char *path_full, const char *referent UNUSED, const struct object_id *oid,\n-\t\t       int flag UNUSED, void *data)\n+static int show_ref_cb(const struct reference *ref, void *data)\n {\n \tstruct oidset *seen = data;\n-\tconst char *path = strip_namespace(path_full);\n+\tconst char *path = strip_namespace(ref->name);\n \n-\tif (ref_is_hidden(path, path_full, &hidden_refs))\n+\tif (ref_is_hidden(path, ref->name, &hidden_refs))\n \t\treturn 0;\n \n \t/*\n@@ -320,13 +319,13 @@ static int show_ref_cb(const char *path_full, const char *referent UNUSED, const\n \t * transfer but will otherwise ignore them.\n \t */\n \tif (!path) {\n-\t\tif (oidset_insert(seen, oid))\n+\t\tif (oidset_insert(seen, ref->oid))\n \t\t\treturn 0;\n \t\tpath = \".have\";\n \t} else {\n-\t\toidset_insert(seen, oid);\n+\t\toidset_insert(seen, ref->oid);\n \t}\n-\tshow_ref(path, oid);\n+\tshow_ref(path, ref->oid);\n \treturn 0;\n }\n \ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 8a7ed4299a4..7ffc14ba157 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -570,17 +570,14 @@ struct branches_for_remote {\n \tstruct known_remotes *keep;\n };\n \n-static int add_branch_for_removal(const char *refname,\n-\t\t\t\t  const char *referent UNUSED,\n-\t\t\t\t  const struct object_id *oid UNUSED,\n-\t\t\t\t  int flags UNUSED, void *cb_data)\n+static int add_branch_for_removal(const struct reference *ref, void *cb_data)\n {\n \tstruct branches_for_remote *branches = cb_data;\n \tstruct refspec_item refspec;\n \tstruct known_remote *kr;\n \n \tmemset(&refspec, 0, sizeof(refspec));\n-\trefspec.dst = (char *)refname;\n+\trefspec.dst = (char *)ref->name;\n \tif (remote_find_tracking(branches->remote, &refspec))\n \t\treturn 0;\n \tfree(refspec.src);\n@@ -588,7 +585,7 @@ static int add_branch_for_removal(const char *refname,\n \t/* don't delete a branch if another remote also uses it */\n \tfor (kr = branches->keep->list; kr; kr = kr->next) {\n \t\tmemset(&refspec, 0, sizeof(refspec));\n-\t\trefspec.dst = (char *)refname;\n+\t\trefspec.dst = (char *)ref->name;\n \t\tif (!remote_find_tracking(kr->remote, &refspec)) {\n \t\t\tfree(refspec.src);\n \t\t\treturn 0;\n@@ -596,16 +593,16 @@ static int add_branch_for_removal(const char *refname,\n \t}\n \n \t/* don't delete non-remote-tracking refs */\n-\tif (!starts_with(refname, \"refs/remotes/\")) {\n+\tif (!starts_with(ref->name, \"refs/remotes/\")) {\n \t\t/* advise user how to delete local branches */\n-\t\tif (starts_with(refname, \"refs/heads/\"))\n+\t\tif (starts_with(ref->name, \"refs/heads/\"))\n \t\t\tstring_list_append(branches->skipped,\n-\t\t\t\t\t   abbrev_branch(refname));\n+\t\t\t\t\t   abbrev_branch(ref->name));\n \t\t/* silently skip over other non-remote refs */\n \t\treturn 0;\n \t}\n \n-\tstring_list_append(branches->branches, refname);\n+\tstring_list_append(branches->branches, ref->name);\n \n \treturn 0;\n }\n@@ -713,18 +710,18 @@ static int rename_one_reflog(const char *old_refname,\n \treturn error;\n }\n \n-static int rename_one_ref(const char *old_refname, const char *referent,\n-\t\t\t  const struct object_id *oid,\n-\t\t\t  int flags, void *cb_data)\n+static int rename_one_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct strbuf new_referent = STRBUF_INIT;\n \tstruct strbuf new_refname = STRBUF_INIT;\n \tstruct rename_info *rename = cb_data;\n+\tconst struct object_id *oid = ref->oid;\n+\tconst char *referent = ref->target;\n \tint error;\n \n-\tcompute_renamed_ref(rename, old_refname, &new_refname);\n+\tcompute_renamed_ref(rename, ref->name, &new_refname);\n \n-\tif (flags & REF_ISSYMREF) {\n+\tif (ref->flags & REF_ISSYMREF) {\n \t\t/*\n \t\t * Stupidly enough `referent` is not pointing to the immediate\n \t\t * target of a symref, but it's the recursively resolved value.\n@@ -732,25 +729,25 @@ static int rename_one_ref(const char *old_refname, const char *referent,\n \t\t * unborn symrefs don't have any value for the `referent` at all.\n \t\t */\n \t\treferent = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),\n-\t\t\t\t\t\t   old_refname, RESOLVE_REF_NO_RECURSE,\n+\t\t\t\t\t\t   ref->name, RESOLVE_REF_NO_RECURSE,\n \t\t\t\t\t\t   NULL, NULL);\n \t\tcompute_renamed_ref(rename, referent, &new_referent);\n \t\toid = NULL;\n \t}\n \n-\terror = ref_transaction_delete(rename->transaction, old_refname,\n+\terror = ref_transaction_delete(rename->transaction, ref->name,\n \t\t\t\t       oid, referent, REF_NO_DEREF, NULL, rename->err);\n \tif (error < 0)\n \t\tgoto out;\n \n \terror = ref_transaction_update(rename->transaction, new_refname.buf, oid, null_oid(the_hash_algo),\n-\t\t\t\t       (flags & REF_ISSYMREF) ? new_referent.buf : NULL, NULL,\n+\t\t\t\t       (ref->flags & REF_ISSYMREF) ? new_referent.buf : NULL, NULL,\n \t\t\t\t       REF_SKIP_CREATE_REFLOG | REF_NO_DEREF | REF_SKIP_OID_VERIFICATION,\n \t\t\t\t       NULL, rename->err);\n \tif (error < 0)\n \t\tgoto out;\n \n-\terror = rename_one_reflog(old_refname, oid, rename);\n+\terror = rename_one_reflog(ref->name, oid, rename);\n \tif (error < 0)\n \t\tgoto out;\n \n@@ -1125,19 +1122,16 @@ static void free_remote_ref_states(struct ref_states *states)\n \tstring_list_clear_func(&states->push, clear_push_info);\n }\n \n-static int append_ref_to_tracked_list(const char *refname,\n-\t\t\t\t      const char *referent UNUSED,\n-\t\t\t\t      const struct object_id *oid UNUSED,\n-\t\t\t\t      int flags, void *cb_data)\n+static int append_ref_to_tracked_list(const struct reference *ref, void *cb_data)\n {\n \tstruct ref_states *states = cb_data;\n \tstruct refspec_item refspec;\n \n-\tif (flags & REF_ISSYMREF)\n+\tif (ref->flags & REF_ISSYMREF)\n \t\treturn 0;\n \n \tmemset(&refspec, 0, sizeof(refspec));\n-\trefspec.dst = (char *)refname;\n+\trefspec.dst = (char *)ref->name;\n \tif (!remote_find_tracking(states->remote, &refspec)) {\n \t\tstring_list_append(&states->tracked, abbrev_branch(refspec.src));\n \t\tfree(refspec.src);\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex c490a51e919..893c105c81f 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -773,25 +773,23 @@ struct midx_snapshot_ref_data {\n \tint preferred;\n };\n \n-static int midx_snapshot_ref_one(const char *refname UNUSED,\n-\t\t\t\t const char *referent UNUSED,\n-\t\t\t\t const struct object_id *oid,\n-\t\t\t\t int flag UNUSED, void *_data)\n+static int midx_snapshot_ref_one(const struct reference *ref, void *_data)\n {\n \tstruct midx_snapshot_ref_data *data = _data;\n+\tconst struct object_id *maybe_peeled = ref->oid;\n \tstruct object_id peeled;\n \n-\tif (!peel_iterated_oid(the_repository, oid, &peeled))\n-\t\toid = &peeled;\n+\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n+\t\tmaybe_peeled = &peeled;\n \n-\tif (oidset_insert(&data->seen, oid))\n+\tif (oidset_insert(&data->seen, maybe_peeled))\n \t\treturn 0; /* already seen */\n \n-\tif (odb_read_object_info(the_repository->objects, oid, NULL) != OBJ_COMMIT)\n+\tif (odb_read_object_info(the_repository->objects, maybe_peeled, NULL) != OBJ_COMMIT)\n \t\treturn 0;\n \n \tfprintf(data->f->fp, \"%s%s\\n\", data->preferred ? \"+\" : \"\",\n-\t\toid_to_hex(oid));\n+\t\toid_to_hex(maybe_peeled));\n \n \treturn 0;\n }\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 900b560a77d..4c62c5ab58b 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -47,30 +47,27 @@ struct show_data {\n \tenum replace_format format;\n };\n \n-static int show_reference(const char *refname,\n-\t\t\t  const char *referent UNUSED,\n-\t\t\t  const struct object_id *oid,\n-\t\t\t  int flag UNUSED, void *cb_data)\n+static int show_reference(const struct reference *ref, void *cb_data)\n {\n \tstruct show_data *data = cb_data;\n \n-\tif (!wildmatch(data->pattern, refname, 0)) {\n+\tif (!wildmatch(data->pattern, ref->name, 0)) {\n \t\tif (data->format == REPLACE_FORMAT_SHORT)\n-\t\t\tprintf(\"%s\\n\", refname);\n+\t\t\tprintf(\"%s\\n\", ref->name);\n \t\telse if (data->format == REPLACE_FORMAT_MEDIUM)\n-\t\t\tprintf(\"%s -> %s\\n\", refname, oid_to_hex(oid));\n+\t\t\tprintf(\"%s -> %s\\n\", ref->name, oid_to_hex(ref->oid));\n \t\telse { /* data->format == REPLACE_FORMAT_LONG */\n \t\t\tstruct object_id object;\n \t\t\tenum object_type obj_type, repl_type;\n \n-\t\t\tif (repo_get_oid(data->repo, refname, &object))\n-\t\t\t\treturn error(_(\"failed to resolve '%s' as a valid ref\"), refname);\n+\t\t\tif (repo_get_oid(data->repo, ref->name, &object))\n+\t\t\t\treturn error(_(\"failed to resolve '%s' as a valid ref\"), ref->name);\n \n \t\t\tobj_type = odb_read_object_info(data->repo->objects, &object, NULL);\n-\t\t\trepl_type = odb_read_object_info(data->repo->objects, oid, NULL);\n+\t\t\trepl_type = odb_read_object_info(data->repo->objects, ref->oid, NULL);\n \n-\t\t\tprintf(\"%s (%s) -> %s (%s)\\n\", refname, type_name(obj_type),\n-\t\t\t       oid_to_hex(oid), type_name(repl_type));\n+\t\t\tprintf(\"%s (%s) -> %s (%s)\\n\", ref->name, type_name(obj_type),\n+\t\t\t       oid_to_hex(ref->oid), type_name(repl_type));\n \t\t}\n \t}\n \ndiff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\nindex 9da92b990d0..3578591b4f2 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -217,19 +217,17 @@ static int show_default(void)\n \treturn 0;\n }\n \n-static int show_reference(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t  int flag UNUSED, void *cb_data UNUSED)\n+static int show_reference(const struct reference *ref, void *cb_data UNUSED)\n {\n-\tif (ref_excluded(&ref_excludes, refname))\n+\tif (ref_excluded(&ref_excludes, ref->name))\n \t\treturn 0;\n-\tshow_rev(NORMAL, oid, refname);\n+\tshow_rev(NORMAL, ref->oid, ref->name);\n \treturn 0;\n }\n \n-static int anti_reference(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t  int flag UNUSED, void *cb_data UNUSED)\n+static int anti_reference(const struct reference *ref, void *cb_data UNUSED)\n {\n-\tshow_rev(REVERSED, oid, refname);\n+\tshow_rev(REVERSED, ref->oid, ref->name);\n \treturn 0;\n }\n \ndiff --git a/builtin/show-branch.c b/builtin/show-branch.c\nindex 441babf2e35..10475a6b5ed 100644\n--- a/builtin/show-branch.c\n+++ b/builtin/show-branch.c\n@@ -413,34 +413,32 @@ static int append_ref(const char *refname, const struct object_id *oid,\n \treturn 0;\n }\n \n-static int append_head_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t   int flag UNUSED, void *cb_data UNUSED)\n+static int append_head_ref(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct object_id tmp;\n \tint ofs = 11;\n-\tif (!starts_with(refname, \"refs/heads/\"))\n+\tif (!starts_with(ref->name, \"refs/heads/\"))\n \t\treturn 0;\n \t/* If both heads/foo and tags/foo exists, get_sha1 would\n \t * get confused.\n \t */\n-\tif (repo_get_oid(the_repository, refname + ofs, &tmp) || !oideq(&tmp, oid))\n+\tif (repo_get_oid(the_repository, ref->name + ofs, &tmp) || !oideq(&tmp, ref->oid))\n \t\tofs = 5;\n-\treturn append_ref(refname + ofs, oid, 0);\n+\treturn append_ref(ref->name + ofs, ref->oid, 0);\n }\n \n-static int append_remote_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t     int flag UNUSED, void *cb_data UNUSED)\n+static int append_remote_ref(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct object_id tmp;\n \tint ofs = 13;\n-\tif (!starts_with(refname, \"refs/remotes/\"))\n+\tif (!starts_with(ref->name, \"refs/remotes/\"))\n \t\treturn 0;\n \t/* If both heads/foo and tags/foo exists, get_sha1 would\n \t * get confused.\n \t */\n-\tif (repo_get_oid(the_repository, refname + ofs, &tmp) || !oideq(&tmp, oid))\n+\tif (repo_get_oid(the_repository, ref->name + ofs, &tmp) || !oideq(&tmp, ref->oid))\n \t\tofs = 5;\n-\treturn append_ref(refname + ofs, oid, 0);\n+\treturn append_ref(ref->name + ofs, ref->oid, 0);\n }\n \n static int append_tag_ref(const char *refname, const struct object_id *oid,\n@@ -454,27 +452,26 @@ static int append_tag_ref(const char *refname, const struct object_id *oid,\n static const char *match_ref_pattern = NULL;\n static int match_ref_slash = 0;\n \n-static int append_matching_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t       int flag, void *cb_data)\n+static int append_matching_ref(const struct reference *ref, void *cb_data)\n {\n \t/* we want to allow pattern hold/<asterisk> to show all\n \t * branches under refs/heads/hold/, and v0.99.9? to show\n \t * refs/tags/v0.99.9a and friends.\n \t */\n \tconst char *tail;\n-\tint slash = count_slashes(refname);\n-\tfor (tail = refname; *tail && match_ref_slash < slash; )\n+\tint slash = count_slashes(ref->name);\n+\tfor (tail = ref->name; *tail && match_ref_slash < slash; )\n \t\tif (*tail++ == '/')\n \t\t\tslash--;\n \tif (!*tail)\n \t\treturn 0;\n \tif (wildmatch(match_ref_pattern, tail, 0))\n \t\treturn 0;\n-\tif (starts_with(refname, \"refs/heads/\"))\n-\t\treturn append_head_ref(refname, NULL, oid, flag, cb_data);\n-\tif (starts_with(refname, \"refs/tags/\"))\n-\t\treturn append_tag_ref(refname, oid, flag, cb_data);\n-\treturn append_ref(refname, oid, 0);\n+\tif (starts_with(ref->name, \"refs/heads/\"))\n+\t\treturn append_head_ref(ref, cb_data);\n+\tif (starts_with(ref->name, \"refs/tags/\"))\n+\t\treturn append_tag_ref(ref->name, ref->oid, ref->flags, cb_data);\n+\treturn append_ref(ref->name, ref->oid, 0);\n }\n \n static void snarf_refs(int head, int remotes)\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex 0b6f9edf86c..4803b5e5986 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -66,26 +66,25 @@ struct show_ref_data {\n \tint show_head;\n };\n \n-static int show_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t    int flag UNUSED, void *cbdata)\n+static int show_ref(const struct reference *ref, void *cbdata)\n {\n \tstruct show_ref_data *data = cbdata;\n \n-\tif (data->show_head && !strcmp(refname, \"HEAD\"))\n+\tif (data->show_head && !strcmp(ref->name, \"HEAD\"))\n \t\tgoto match;\n \n \tif (data->patterns) {\n-\t\tint reflen = strlen(refname);\n+\t\tint reflen = strlen(ref->name);\n \t\tconst char **p = data->patterns, *m;\n \t\twhile ((m = *p++) != NULL) {\n \t\t\tint len = strlen(m);\n \t\t\tif (len > reflen)\n \t\t\t\tcontinue;\n-\t\t\tif (memcmp(m, refname + reflen - len, len))\n+\t\t\tif (memcmp(m, ref->name + reflen - len, len))\n \t\t\t\tcontinue;\n \t\t\tif (len == reflen)\n \t\t\t\tgoto match;\n-\t\t\tif (refname[reflen - len - 1] == '/')\n+\t\t\tif (ref->name[reflen - len - 1] == '/')\n \t\t\t\tgoto match;\n \t\t}\n \t\treturn 0;\n@@ -94,18 +93,15 @@ static int show_ref(const char *refname, const char *referent UNUSED, const stru\n match:\n \tdata->found_match++;\n \n-\tshow_one(data->show_one_opts, refname, oid);\n+\tshow_one(data->show_one_opts, ref->name, ref->oid);\n \n \treturn 0;\n }\n \n-static int add_existing(const char *refname,\n-\t\t\tconst char *referent UNUSED,\n-\t\t\tconst struct object_id *oid UNUSED,\n-\t\t\tint flag UNUSED, void *cbdata)\n+static int add_existing(const struct reference *ref, void *cbdata)\n {\n \tstruct string_list *list = (struct string_list *)cbdata;\n-\tstring_list_insert(list, refname);\n+\tstring_list_insert(list, ref->name);\n \treturn 0;\n }\n \ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex fcd73abe533..35f6cf735e5 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -593,16 +593,12 @@ static void print_status(unsigned int flags, char state, const char *path,\n \tprintf(\"\\n\");\n }\n \n-static int handle_submodule_head_ref(const char *refname UNUSED,\n-\t\t\t\t     const char *referent UNUSED,\n-\t\t\t\t     const struct object_id *oid,\n-\t\t\t\t     int flags UNUSED,\n-\t\t\t\t     void *cb_data)\n+static int handle_submodule_head_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct object_id *output = cb_data;\n \n-\tif (oid)\n-\t\toidcpy(output, oid);\n+\tif (ref->oid)\n+\t\toidcpy(output, ref->oid);\n \n \treturn 0;\n }\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 812774a5ca9..b7f323b5e4d 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -635,11 +635,7 @@ static void print_preparing_worktree_line(int detach,\n  *\n  * Returns 0 on failure and non-zero on success.\n  */\n-static int first_valid_ref(const char *refname UNUSED,\n-\t\t\t   const char *referent UNUSED,\n-\t\t\t   const struct object_id *oid UNUSED,\n-\t\t\t   int flags UNUSED,\n-\t\t\t   void *cb_data UNUSED)\n+static int first_valid_ref(const struct reference *ref UNUSED, void *cb_data UNUSED)\n {\n \treturn 1;\n }\ndiff --git a/commit-graph.c b/commit-graph.c\nindex 2f20f66cfdd..0cfe16ad082 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -1846,18 +1846,16 @@ struct refs_cb_data {\n \tstruct progress *progress;\n };\n \n-static int add_ref_to_set(const char *refname UNUSED,\n-\t\t\t  const char *referent UNUSED,\n-\t\t\t  const struct object_id *oid,\n-\t\t\t  int flags UNUSED, void *cb_data)\n+static int add_ref_to_set(const struct reference *ref, void *cb_data)\n {\n+\tconst struct object_id *maybe_peeled = ref->oid;\n \tstruct object_id peeled;\n \tstruct refs_cb_data *data = (struct refs_cb_data *)cb_data;\n \n-\tif (!peel_iterated_oid(data->repo, oid, &peeled))\n-\t\toid = &peeled;\n-\tif (odb_read_object_info(data->repo->objects, oid, NULL) == OBJ_COMMIT)\n-\t\toidset_insert(data->commits, oid);\n+\tif (!peel_iterated_oid(data->repo, ref->oid, &peeled))\n+\t\tmaybe_peeled = &peeled;\n+\tif (odb_read_object_info(data->repo->objects, maybe_peeled, NULL) == OBJ_COMMIT)\n+\t\toidset_insert(data->commits, maybe_peeled);\n \n \tdisplay_progress(data->progress, oidset_size(data->commits));\n \ndiff --git a/delta-islands.c b/delta-islands.c\nindex 36c94799d69..7cfebc4162b 100644\n--- a/delta-islands.c\n+++ b/delta-islands.c\n@@ -390,8 +390,7 @@ static void add_ref_to_island(kh_str_t *remote_islands, const char *island_name,\n \trl->hash += sha_core;\n }\n \n-static int find_island_for_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t       int flags UNUSED, void *cb)\n+static int find_island_for_ref(const struct reference *ref, void *cb)\n {\n \tstruct island_load_data *ild = cb;\n \n@@ -406,7 +405,7 @@ static int find_island_for_ref(const char *refname, const char *referent UNUSED,\n \n \t/* walk backwards to get last-one-wins ordering */\n \tfor (i = ild->nr - 1; i >= 0; i--) {\n-\t\tif (!regexec(&ild->rx[i], refname,\n+\t\tif (!regexec(&ild->rx[i], ref->name,\n \t\t\t     ARRAY_SIZE(matches), matches, 0))\n \t\t\tbreak;\n \t}\n@@ -428,10 +427,10 @@ static int find_island_for_ref(const char *refname, const char *referent UNUSED,\n \t\tif (island_name.len)\n \t\t\tstrbuf_addch(&island_name, '-');\n \n-\t\tstrbuf_add(&island_name, refname + match->rm_so, match->rm_eo - match->rm_so);\n+\t\tstrbuf_add(&island_name, ref->name + match->rm_so, match->rm_eo - match->rm_so);\n \t}\n \n-\tadd_ref_to_island(ild->remote_islands, island_name.buf, oid);\n+\tadd_ref_to_island(ild->remote_islands, island_name.buf, ref->oid);\n \tstrbuf_release(&island_name);\n \treturn 0;\n }\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 6ed56629518..f576e593b9f 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -188,13 +188,9 @@ static int rev_list_insert_ref(struct fetch_negotiator *negotiator,\n \treturn 0;\n }\n \n-static int rev_list_insert_ref_oid(const char *refname UNUSED,\n-\t\t\t\t   const char *referent UNUSED,\n-\t\t\t\t   const struct object_id *oid,\n-\t\t\t\t   int flag UNUSED,\n-\t\t\t\t   void *cb_data)\n+static int rev_list_insert_ref_oid(const struct reference *ref, void *cb_data)\n {\n-\treturn rev_list_insert_ref(cb_data, oid);\n+\treturn rev_list_insert_ref(cb_data, ref->oid);\n }\n \n enum ack_type {\n@@ -616,13 +612,9 @@ static int mark_complete(const struct object_id *oid)\n \treturn 0;\n }\n \n-static int mark_complete_oid(const char *refname UNUSED,\n-\t\t\t     const char *referent UNUSED,\n-\t\t\t     const struct object_id *oid,\n-\t\t\t     int flag UNUSED,\n-\t\t\t     void *cb_data UNUSED)\n+static int mark_complete_oid(const struct reference *ref, void *cb_data UNUSED)\n {\n-\treturn mark_complete(oid);\n+\treturn mark_complete(ref->oid);\n }\n \n static void mark_recent_complete_commits(struct fetch_pack_args *args,\ndiff --git a/help.c b/help.c\nindex bb20498cfd0..d8c056fc5d1 100644\n--- a/help.c\n+++ b/help.c\n@@ -845,18 +845,16 @@ struct similar_ref_cb {\n \tstruct string_list *similar_refs;\n };\n \n-static int append_similar_ref(const char *refname, const char *referent UNUSED,\n-\t\t\t      const struct object_id *oid UNUSED,\n-\t\t\t      int flags UNUSED, void *cb_data)\n+static int append_similar_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct similar_ref_cb *cb = (struct similar_ref_cb *)(cb_data);\n-\tchar *branch = strrchr(refname, '/') + 1;\n+\tchar *branch = strrchr(ref->name, '/') + 1;\n \n \t/* A remote branch of the same name is deemed similar */\n-\tif (starts_with(refname, \"refs/remotes/\") &&\n+\tif (starts_with(ref->name, \"refs/remotes/\") &&\n \t    !strcmp(branch, cb->base_ref))\n \t\tstring_list_append_nodup(cb->similar_refs,\n-\t\t\t\t\t refs_shorten_unambiguous_ref(get_main_ref_store(the_repository), refname, 1));\n+\t\t\t\t\t refs_shorten_unambiguous_ref(get_main_ref_store(the_repository), ref->name, 1));\n \treturn 0;\n }\n \ndiff --git a/http-backend.c b/http-backend.c\nindex d5dfe762bb5..992be9dc70c 100644\n--- a/http-backend.c\n+++ b/http-backend.c\n@@ -513,18 +513,17 @@ static void run_service(const char **argv, int buffer_input)\n \t\texit(1);\n }\n \n-static int show_text_ref(const char *name, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t int flag UNUSED, void *cb_data)\n+static int show_text_ref(const struct reference *ref, void *cb_data)\n {\n-\tconst char *name_nons = strip_namespace(name);\n+\tconst char *name_nons = strip_namespace(ref->name);\n \tstruct strbuf *buf = cb_data;\n-\tstruct object *o = parse_object(the_repository, oid);\n+\tstruct object *o = parse_object(the_repository, ref->oid);\n \tif (!o)\n \t\treturn 0;\n \n-\tstrbuf_addf(buf, \"%s\\t%s\\n\", oid_to_hex(oid), name_nons);\n+\tstrbuf_addf(buf, \"%s\\t%s\\n\", oid_to_hex(ref->oid), name_nons);\n \tif (o->type == OBJ_TAG) {\n-\t\to = deref_tag(the_repository, o, name, 0);\n+\t\to = deref_tag(the_repository, o, ref->name, 0);\n \t\tif (!o)\n \t\t\treturn 0;\n \t\tstrbuf_addf(buf, \"%s\\t%s^{}\\n\", oid_to_hex(&o->oid),\n@@ -569,21 +568,20 @@ static void get_info_refs(struct strbuf *hdr, char *arg UNUSED)\n \tstrbuf_release(&buf);\n }\n \n-static int show_head_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t int flag, void *cb_data)\n+static int show_head_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct strbuf *buf = cb_data;\n \n-\tif (flag & REF_ISSYMREF) {\n+\tif (ref->flags & REF_ISSYMREF) {\n \t\tconst char *target = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),\n-\t\t\t\t\t\t\t     refname,\n+\t\t\t\t\t\t\t     ref->name,\n \t\t\t\t\t\t\t     RESOLVE_REF_READING,\n \t\t\t\t\t\t\t     NULL, NULL);\n \n \t\tif (target)\n \t\t\tstrbuf_addf(buf, \"ref: %s\\n\", strip_namespace(target));\n \t} else {\n-\t\tstrbuf_addf(buf, \"%s\\n\", oid_to_hex(oid));\n+\t\tstrbuf_addf(buf, \"%s\\n\", oid_to_hex(ref->oid));\n \t}\n \n \treturn 0;\ndiff --git a/log-tree.c b/log-tree.c\nindex 67d9ace5964..95f1d12c722 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -147,9 +147,7 @@ static int ref_filter_match(const char *refname,\n \treturn 1;\n }\n \n-static int add_ref_decoration(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t      int flags UNUSED,\n-\t\t\t      void *cb_data)\n+static int add_ref_decoration(const struct reference *ref, void *cb_data)\n {\n \tint i;\n \tstruct object *obj;\n@@ -158,16 +156,16 @@ static int add_ref_decoration(const char *refname, const char *referent UNUSED,\n \tstruct decoration_filter *filter = (struct decoration_filter *)cb_data;\n \tconst char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref;\n \n-\tif (filter && !ref_filter_match(refname, filter))\n+\tif (filter && !ref_filter_match(ref->name, filter))\n \t\treturn 0;\n \n-\tif (starts_with(refname, git_replace_ref_base)) {\n+\tif (starts_with(ref->name, git_replace_ref_base)) {\n \t\tstruct object_id original_oid;\n \t\tif (!replace_refs_enabled(the_repository))\n \t\t\treturn 0;\n-\t\tif (get_oid_hex(refname + strlen(git_replace_ref_base),\n+\t\tif (get_oid_hex(ref->name + strlen(git_replace_ref_base),\n \t\t\t\t&original_oid)) {\n-\t\t\twarning(\"invalid replace ref %s\", refname);\n+\t\t\twarning(\"invalid replace ref %s\", ref->name);\n \t\t\treturn 0;\n \t\t}\n \t\tobj = parse_object(the_repository, &original_oid);\n@@ -176,10 +174,10 @@ static int add_ref_decoration(const char *refname, const char *referent UNUSED,\n \t\treturn 0;\n \t}\n \n-\tobjtype = odb_read_object_info(the_repository->objects, oid, NULL);\n+\tobjtype = odb_read_object_info(the_repository->objects, ref->oid, NULL);\n \tif (objtype < 0)\n \t\treturn 0;\n-\tobj = lookup_object_by_type(the_repository, oid, objtype);\n+\tobj = lookup_object_by_type(the_repository, ref->oid, objtype);\n \n \tfor (i = 0; i < ARRAY_SIZE(ref_namespace); i++) {\n \t\tstruct ref_namespace_info *info = &ref_namespace[i];\n@@ -187,24 +185,24 @@ static int add_ref_decoration(const char *refname, const char *referent UNUSED,\n \t\tif (!info->decoration)\n \t\t\tcontinue;\n \t\tif (info->exact) {\n-\t\t\tif (!strcmp(refname, info->ref)) {\n+\t\t\tif (!strcmp(ref->name, info->ref)) {\n \t\t\t\tdeco_type = info->decoration;\n \t\t\t\tbreak;\n \t\t\t}\n-\t\t} else if (starts_with(refname, info->ref)) {\n+\t\t} else if (starts_with(ref->name, info->ref)) {\n \t\t\tdeco_type = info->decoration;\n \t\t\tbreak;\n \t\t}\n \t}\n \n-\tadd_name_decoration(deco_type, refname, obj);\n+\tadd_name_decoration(deco_type, ref->name, obj);\n \twhile (obj->type == OBJ_TAG) {\n \t\tif (!obj->parsed)\n \t\t\tparse_object(the_repository, &obj->oid);\n \t\tobj = ((struct tag *)obj)->tagged;\n \t\tif (!obj)\n \t\t\tbreak;\n-\t\tadd_name_decoration(DECORATION_REF_TAG, refname, obj);\n+\t\tadd_name_decoration(DECORATION_REF_TAG, ref->name, obj);\n \t}\n \treturn 0;\n }\ndiff --git a/ls-refs.c b/ls-refs.c\nindex c47acde07f3..64d02723691 100644\n--- a/ls-refs.c\n+++ b/ls-refs.c\n@@ -75,42 +75,42 @@ struct ls_refs_data {\n \tunsigned unborn : 1;\n };\n \n-static int send_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t    int flag, void *cb_data)\n+static int send_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct ls_refs_data *data = cb_data;\n-\tconst char *refname_nons = strip_namespace(refname);\n+\tconst char *refname_nons = strip_namespace(ref->name);\n \n \tstrbuf_reset(&data->buf);\n \n-\tif (ref_is_hidden(refname_nons, refname, &data->hidden_refs))\n+\tif (ref_is_hidden(refname_nons, ref->name, &data->hidden_refs))\n \t\treturn 0;\n \n \tif (!ref_match(&data->prefixes, refname_nons))\n \t\treturn 0;\n \n-\tif (oid)\n-\t\tstrbuf_addf(&data->buf, \"%s %s\", oid_to_hex(oid), refname_nons);\n+\tif (ref->oid)\n+\t\tstrbuf_addf(&data->buf, \"%s %s\", oid_to_hex(ref->oid), refname_nons);\n \telse\n \t\tstrbuf_addf(&data->buf, \"unborn %s\", refname_nons);\n-\tif (data->symrefs && flag & REF_ISSYMREF) {\n+\tif (data->symrefs && ref->flags & REF_ISSYMREF) {\n+\t\tint unused_flag;\n \t\tstruct object_id unused;\n \t\tconst char *symref_target = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),\n-\t\t\t\t\t\t\t\t    refname,\n+\t\t\t\t\t\t\t\t    ref->name,\n \t\t\t\t\t\t\t\t    0,\n \t\t\t\t\t\t\t\t    &unused,\n-\t\t\t\t\t\t\t\t    &flag);\n+\t\t\t\t\t\t\t\t    &unused_flag);\n \n \t\tif (!symref_target)\n-\t\t\tdie(\"'%s' is a symref but it is not?\", refname);\n+\t\t\tdie(\"'%s' is a symref but it is not?\", ref->name);\n \n \t\tstrbuf_addf(&data->buf, \" symref-target:%s\",\n \t\t\t    strip_namespace(symref_target));\n \t}\n \n-\tif (data->peel && oid) {\n+\tif (data->peel && ref->oid) {\n \t\tstruct object_id peeled;\n-\t\tif (!peel_iterated_oid(the_repository, oid, &peeled))\n+\t\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n \t\t\tstrbuf_addf(&data->buf, \" peeled:%s\", oid_to_hex(&peeled));\n \t}\n \n@@ -131,9 +131,17 @@ static void send_possibly_unborn_head(struct ls_refs_data *data)\n \tif (!refs_resolve_ref_unsafe(get_main_ref_store(the_repository), namespaced.buf, 0, &oid, &flag))\n \t\treturn; /* bad ref */\n \toid_is_null = is_null_oid(&oid);\n+\n \tif (!oid_is_null ||\n-\t    (data->unborn && data->symrefs && (flag & REF_ISSYMREF)))\n-\t\tsend_ref(namespaced.buf, NULL, oid_is_null ? NULL : &oid, flag, data);\n+\t    (data->unborn && data->symrefs && (flag & REF_ISSYMREF))) {\n+\t\tstruct reference ref = {\n+\t\t\t.name = namespaced.buf,\n+\t\t\t.oid = oid_is_null ? NULL : &oid,\n+\t\t\t.flags = flag,\n+\t\t};\n+\n+\t\tsend_ref(&ref, data);\n+\t}\n \tstrbuf_release(&namespaced);\n }\n \ndiff --git a/midx-write.c b/midx-write.c\nindex c73010df6d3..f4dd875747a 100644\n--- a/midx-write.c\n+++ b/midx-write.c\n@@ -697,28 +697,27 @@ static void prepare_midx_packing_data(struct packing_data *pdata,\n \ttrace2_region_leave(\"midx\", \"prepare_midx_packing_data\", ctx->repo);\n }\n \n-static int add_ref_to_pending(const char *refname, const char *referent UNUSED,\n-\t\t\t      const struct object_id *oid,\n-\t\t\t      int flag, void *cb_data)\n+static int add_ref_to_pending(const struct reference *ref, void *cb_data)\n {\n \tstruct rev_info *revs = (struct rev_info*)cb_data;\n+\tconst struct object_id *maybe_peeled = ref->oid;\n \tstruct object_id peeled;\n \tstruct object *object;\n \n-\tif ((flag & REF_ISSYMREF) && (flag & REF_ISBROKEN)) {\n-\t\twarning(\"symbolic ref is dangling: %s\", refname);\n+\tif ((ref->flags & REF_ISSYMREF) && (ref->flags & REF_ISBROKEN)) {\n+\t\twarning(\"symbolic ref is dangling: %s\", ref->name);\n \t\treturn 0;\n \t}\n \n-\tif (!peel_iterated_oid(revs->repo, oid, &peeled))\n-\t\toid = &peeled;\n+\tif (!peel_iterated_oid(revs->repo, ref->oid, &peeled))\n+\t\tmaybe_peeled = &peeled;\n \n-\tobject = parse_object_or_die(revs->repo, oid, refname);\n+\tobject = parse_object_or_die(revs->repo, maybe_peeled, ref->name);\n \tif (object->type != OBJ_COMMIT)\n \t\treturn 0;\n \n \tadd_pending_object(revs, object, \"\");\n-\tif (bitmap_is_preferred_refname(revs->repo, refname))\n+\tif (bitmap_is_preferred_refname(revs->repo, ref->name))\n \t\tobject->flags |= NEEDS_BITMAP;\n \treturn 0;\n }\ndiff --git a/negotiator/default.c b/negotiator/default.c\nindex c479da9b091..116dedcf830 100644\n--- a/negotiator/default.c\n+++ b/negotiator/default.c\n@@ -38,11 +38,10 @@ static void rev_list_push(struct negotiation_state *ns,\n \t}\n }\n \n-static int clear_marks(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t       int flag UNUSED,\n-\t\t       void *cb_data UNUSED)\n+static int clear_marks(const struct reference *ref, void *cb_data UNUSED)\n {\n-\tstruct object *o = deref_tag(the_repository, parse_object(the_repository, oid), refname, 0);\n+\tstruct object *o = deref_tag(the_repository, parse_object(the_repository, ref->oid),\n+\t\t\t\t     ref->name, 0);\n \n \tif (o && o->type == OBJ_COMMIT)\n \t\tclear_commit_marks((struct commit *)o,\ndiff --git a/negotiator/skipping.c b/negotiator/skipping.c\nindex 616df6bf3af..0a272130fb1 100644\n--- a/negotiator/skipping.c\n+++ b/negotiator/skipping.c\n@@ -75,11 +75,10 @@ static struct entry *rev_list_push(struct data *data, struct commit *commit, int\n \treturn entry;\n }\n \n-static int clear_marks(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t       int flag UNUSED,\n-\t\t       void *cb_data UNUSED)\n+static int clear_marks(const struct reference *ref, void *cb_data UNUSED)\n {\n-\tstruct object *o = deref_tag(the_repository, parse_object(the_repository, oid), refname, 0);\n+\tstruct object *o = deref_tag(the_repository, parse_object(the_repository, ref->oid),\n+\t\t\t\t     ref->name, 0);\n \n \tif (o && o->type == OBJ_COMMIT)\n \t\tclear_commit_marks((struct commit *)o,\ndiff --git a/notes.c b/notes.c\nindex 9a2e9181fe6..8e00fd8c470 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -938,13 +938,11 @@ int combine_notes_cat_sort_uniq(struct object_id *cur_oid,\n \treturn ret;\n }\n \n-static int string_list_add_one_ref(const char *refname, const char *referent UNUSED,\n-\t\t\t\t   const struct object_id *oid UNUSED,\n-\t\t\t\t   int flag UNUSED, void *cb)\n+static int string_list_add_one_ref(const struct reference *ref, void *cb)\n {\n \tstruct string_list *refs = cb;\n-\tif (!unsorted_string_list_has_string(refs, refname))\n-\t\tstring_list_append(refs, refname);\n+\tif (!unsorted_string_list_has_string(refs, ref->name))\n+\t\tstring_list_append(refs, ref->name);\n \treturn 0;\n }\n \ndiff --git a/object-name.c b/object-name.c\nindex 7774991d281..43c7518f995 100644\n--- a/object-name.c\n+++ b/object-name.c\n@@ -1444,18 +1444,16 @@ struct handle_one_ref_cb {\n \tstruct commit_list **list;\n };\n \n-static int handle_one_ref(const char *path, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t  int flag UNUSED,\n-\t\t\t  void *cb_data)\n+static int handle_one_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct handle_one_ref_cb *cb = cb_data;\n \tstruct commit_list **list = cb->list;\n-\tstruct object *object = parse_object(cb->repo, oid);\n+\tstruct object *object = parse_object(cb->repo, ref->oid);\n \tif (!object)\n \t\treturn 0;\n \tif (object->type == OBJ_TAG) {\n-\t\tobject = deref_tag(cb->repo, object, path,\n-\t\t\t\t   strlen(path));\n+\t\tobject = deref_tag(cb->repo, object, ref->name,\n+\t\t\t\t   strlen(ref->name));\n \t\tif (!object)\n \t\t\treturn 0;\n \t}\ndiff --git a/pseudo-merge.c b/pseudo-merge.c\nindex 893b763fe45..0abd51b42c1 100644\n--- a/pseudo-merge.c\n+++ b/pseudo-merge.c\n@@ -221,28 +221,25 @@ void load_pseudo_merges_from_config(struct repository *r,\n \t}\n }\n \n-static int find_pseudo_merge_group_for_ref(const char *refname,\n-\t\t\t\t\t   const char *referent UNUSED,\n-\t\t\t\t\t   const struct object_id *oid,\n-\t\t\t\t\t   int flags UNUSED,\n-\t\t\t\t\t   void *_data)\n+static int find_pseudo_merge_group_for_ref(const struct reference *ref, void *_data)\n {\n \tstruct bitmap_writer *writer = _data;\n+\tconst struct object_id *maybe_peeled = ref->oid;\n \tstruct object_id peeled;\n \tstruct commit *c;\n \tuint32_t i;\n \tint has_bitmap;\n \n-\tif (!peel_iterated_oid(the_repository, oid, &peeled))\n-\t\toid = &peeled;\n+\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n+\t\tmaybe_peeled = &peeled;\n \n-\tc = lookup_commit(the_repository, oid);\n+\tc = lookup_commit(the_repository, maybe_peeled);\n \tif (!c)\n \t\treturn 0;\n-\tif (!packlist_find(writer->to_pack, oid))\n+\tif (!packlist_find(writer->to_pack, maybe_peeled))\n \t\treturn 0;\n \n-\thas_bitmap = bitmap_writer_has_bitmapped_object_id(writer, oid);\n+\thas_bitmap = bitmap_writer_has_bitmapped_object_id(writer, maybe_peeled);\n \n \tfor (i = 0; i < writer->pseudo_merge_groups.nr; i++) {\n \t\tstruct pseudo_merge_group *group;\n@@ -252,7 +249,7 @@ static int find_pseudo_merge_group_for_ref(const char *refname,\n \t\tsize_t j;\n \n \t\tgroup = writer->pseudo_merge_groups.items[i].util;\n-\t\tif (regexec(group->pattern, refname, ARRAY_SIZE(captures),\n+\t\tif (regexec(group->pattern, ref->name, ARRAY_SIZE(captures),\n \t\t\t    captures, 0))\n \t\t\tcontinue;\n \n@@ -269,7 +266,7 @@ static int find_pseudo_merge_group_for_ref(const char *refname,\n \t\t\tif (group_name.len)\n \t\t\t\tstrbuf_addch(&group_name, '-');\n \n-\t\t\tstrbuf_add(&group_name, refname + match->rm_so,\n+\t\t\tstrbuf_add(&group_name, ref->name + match->rm_so,\n \t\t\t\t   match->rm_eo - match->rm_so);\n \t\t}\n \ndiff --git a/reachable.c b/reachable.c\nindex 22266db5233..b753c395530 100644\n--- a/reachable.c\n+++ b/reachable.c\n@@ -83,18 +83,17 @@ static void add_rebase_files(struct rev_info *revs)\n \tfree_worktrees(worktrees);\n }\n \n-static int add_one_ref(const char *path, const char *referent UNUSED, const struct object_id *oid,\n-\t\t       int flag, void *cb_data)\n+static int add_one_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct rev_info *revs = (struct rev_info *)cb_data;\n \tstruct object *object;\n \n-\tif ((flag & REF_ISSYMREF) && (flag & REF_ISBROKEN)) {\n-\t\twarning(\"symbolic ref is dangling: %s\", path);\n+\tif ((ref->flags & REF_ISSYMREF) && (ref->flags & REF_ISBROKEN)) {\n+\t\twarning(\"symbolic ref is dangling: %s\", ref->name);\n \t\treturn 0;\n \t}\n \n-\tobject = parse_object_or_die(the_repository, oid, path);\n+\tobject = parse_object_or_die(the_repository, ref->oid, ref->name);\n \tadd_pending_object(revs, object, \"\");\n \n \treturn 0;\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 520d2539c9e..7740f35e937 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -2954,14 +2954,15 @@ struct ref_filter_cbdata {\n  * A call-back given to for_each_ref().  Filter refs and keep them for\n  * later object processing.\n  */\n-static int filter_one(const char *refname, const char *referent, const struct object_id *oid, int flag, void *cb_data)\n+static int filter_one(const struct reference *ref, void *cb_data)\n {\n \tstruct ref_filter_cbdata *ref_cbdata = cb_data;\n-\tstruct ref_array_item *ref;\n+\tstruct ref_array_item *item;\n \n-\tref = apply_ref_filter(refname, referent, oid, flag, ref_cbdata->filter);\n-\tif (ref)\n-\t\tref_array_append(ref_cbdata->array, ref);\n+\titem = apply_ref_filter(ref->name, ref->target, ref->oid,\n+\t\t\t\tref->flags, ref_cbdata->filter);\n+\tif (item)\n+\t\tref_array_append(ref_cbdata->array, item);\n \n \treturn 0;\n }\n@@ -2990,17 +2991,18 @@ struct ref_filter_and_format_cbdata {\n \t} internal;\n };\n \n-static int filter_and_format_one(const char *refname, const char *referent, const struct object_id *oid, int flag, void *cb_data)\n+static int filter_and_format_one(const struct reference *ref, void *cb_data)\n {\n \tstruct ref_filter_and_format_cbdata *ref_cbdata = cb_data;\n-\tstruct ref_array_item *ref;\n+\tstruct ref_array_item *item;\n \tstruct strbuf output = STRBUF_INIT, err = STRBUF_INIT;\n \n-\tref = apply_ref_filter(refname, referent, oid, flag, ref_cbdata->filter);\n-\tif (!ref)\n+\titem = apply_ref_filter(ref->name, ref->target, ref->oid,\n+\t\t\t\tref->flags, ref_cbdata->filter);\n+\tif (!item)\n \t\treturn 0;\n \n-\tif (format_ref_array_item(ref, ref_cbdata->format, &output, &err))\n+\tif (format_ref_array_item(item, ref_cbdata->format, &output, &err))\n \t\tdie(\"%s\", err.buf);\n \n \tif (output.len || !ref_cbdata->format->array_opts.omit_empty) {\n@@ -3010,7 +3012,7 @@ static int filter_and_format_one(const char *refname, const char *referent, cons\n \n \tstrbuf_release(&output);\n \tstrbuf_release(&err);\n-\tfree_array_item(ref);\n+\tfree_array_item(item);\n \n \t/*\n \t * Increment the running count of refs that match the filter. If\ndiff --git a/reflog.c b/reflog.c\nindex 65ef259b4f5..ac87e20c4f9 100644\n--- a/reflog.c\n+++ b/reflog.c\n@@ -423,16 +423,13 @@ int should_expire_reflog_ent_verbose(struct object_id *ooid,\n \treturn expire;\n }\n \n-static int push_tip_to_list(const char *refname UNUSED,\n-\t\t\t    const char *referent UNUSED,\n-\t\t\t    const struct object_id *oid,\n-\t\t\t    int flags, void *cb_data)\n+static int push_tip_to_list(const struct reference *ref, void *cb_data)\n {\n \tstruct commit_list **list = cb_data;\n \tstruct commit *tip_commit;\n-\tif (flags & REF_ISSYMREF)\n+\tif (ref->flags & REF_ISSYMREF)\n \t\treturn 0;\n-\ttip_commit = lookup_commit_reference_gently(the_repository, oid, 1);\n+\ttip_commit = lookup_commit_reference_gently(the_repository, ref->oid, 1);\n \tif (!tip_commit)\n \t\treturn 0;\n \tcommit_list_insert(tip_commit, list);\ndiff --git a/refs.c b/refs.c\nindex 750e5db0771..262849301d0 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -423,17 +423,19 @@ int refs_ref_exists(struct ref_store *refs, const char *refname)\n \t\t\t\t\t NULL, NULL);\n }\n \n-static int for_each_filter_refs(const char *refname, const char *referent,\n-\t\t\t\tconst struct object_id *oid,\n-\t\t\t\tint flags, void *data)\n+static int for_each_filter_refs(const struct reference *ref, void *data)\n {\n \tstruct for_each_ref_filter *filter = data;\n \n-\tif (wildmatch(filter->pattern, refname, 0))\n+\tif (wildmatch(filter->pattern, ref->name, 0))\n \t\treturn 0;\n-\tif (filter->prefix)\n-\t\tskip_prefix(refname, filter->prefix, &refname);\n-\treturn filter->fn(refname, referent, oid, flags, filter->cb_data);\n+\tif (filter->prefix) {\n+\t\tstruct reference skipped = *ref;\n+\t\tskip_prefix(skipped.name, filter->prefix, &skipped.name);\n+\t\treturn filter->fn(&skipped, filter->cb_data);\n+\t} else {\n+\t\treturn filter->fn(ref, filter->cb_data);\n+\t}\n }\n \n struct warn_if_dangling_data {\n@@ -444,17 +446,15 @@ struct warn_if_dangling_data {\n \tint dry_run;\n };\n \n-static int warn_if_dangling_symref(const char *refname, const char *referent UNUSED,\n-\t\t\t\t   const struct object_id *oid UNUSED,\n-\t\t\t\t   int flags, void *cb_data)\n+static int warn_if_dangling_symref(const struct reference *ref, void *cb_data)\n {\n \tstruct warn_if_dangling_data *d = cb_data;\n \tconst char *resolves_to, *msg;\n \n-\tif (!(flags & REF_ISSYMREF))\n+\tif (!(ref->flags & REF_ISSYMREF))\n \t\treturn 0;\n \n-\tresolves_to = refs_resolve_ref_unsafe(d->refs, refname, 0, NULL, NULL);\n+\tresolves_to = refs_resolve_ref_unsafe(d->refs, ref->name, 0, NULL, NULL);\n \tif (!resolves_to\n \t    || !string_list_has_string(d->refnames, resolves_to)) {\n \t\treturn 0;\n@@ -463,7 +463,7 @@ static int warn_if_dangling_symref(const char *refname, const char *referent UNU\n \tmsg = d->dry_run\n \t\t? _(\"%s%s will become dangling after %s is deleted\\n\")\n \t\t: _(\"%s%s has become dangling after %s was deleted\\n\");\n-\tfprintf(d->fp, msg, d->indent, refname, resolves_to);\n+\tfprintf(d->fp, msg, d->indent, ref->name, resolves_to);\n \treturn 0;\n }\n \n@@ -504,8 +504,15 @@ int refs_head_ref_namespaced(struct ref_store *refs, each_ref_fn fn, void *cb_da\n \tint flag;\n \n \tstrbuf_addf(&buf, \"%sHEAD\", get_git_namespace());\n-\tif (!refs_read_ref_full(refs, buf.buf, RESOLVE_REF_READING, &oid, &flag))\n-\t\tret = fn(buf.buf, NULL, &oid, flag, cb_data);\n+\tif (!refs_read_ref_full(refs, buf.buf, RESOLVE_REF_READING, &oid, &flag)) {\n+\t\tstruct reference ref = {\n+\t\t\t.name = buf.buf,\n+\t\t\t.oid = &oid,\n+\t\t\t.flags = flag,\n+\t\t};\n+\n+\t\tret = fn(&ref, cb_data);\n+\t}\n \tstrbuf_release(&buf);\n \n \treturn ret;\n@@ -1740,8 +1747,15 @@ int refs_head_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)\n \tint flag;\n \n \tif (refs_resolve_ref_unsafe(refs, \"HEAD\", RESOLVE_REF_READING,\n-\t\t\t\t    &oid, &flag))\n-\t\treturn fn(\"HEAD\", NULL, &oid, flag, cb_data);\n+\t\t\t\t    &oid, &flag)) {\n+\t\tstruct reference ref = {\n+\t\t\t.name = \"HEAD\",\n+\t\t\t.oid = &oid,\n+\t\t\t.flags = flag,\n+\t\t};\n+\n+\t\treturn fn(&ref, cb_data);\n+\t}\n \n \treturn 0;\n }\n@@ -2752,14 +2766,10 @@ struct do_for_each_reflog_help {\n \tvoid *cb_data;\n };\n \n-static int do_for_each_reflog_helper(const char *refname,\n-\t\t\t\t     const char *referent UNUSED,\n-\t\t\t\t     const struct object_id *oid UNUSED,\n-\t\t\t\t     int flags UNUSED,\n-\t\t\t\t     void *cb_data)\n+static int do_for_each_reflog_helper(const struct reference *ref, void *cb_data)\n {\n \tstruct do_for_each_reflog_help *hp = cb_data;\n-\treturn hp->fn(refname, hp->cb_data);\n+\treturn hp->fn(ref->name, hp->cb_data);\n }\n \n int refs_for_each_reflog(struct ref_store *refs, each_reflog_fn fn, void *cb_data)\n@@ -2977,25 +2987,24 @@ struct migration_data {\n \tuint64_t index;\n };\n \n-static int migrate_one_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t   int flags, void *cb_data)\n+static int migrate_one_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct migration_data *data = cb_data;\n \tstruct strbuf symref_target = STRBUF_INIT;\n \tint ret;\n \n-\tif (flags & REF_ISSYMREF) {\n-\t\tret = refs_read_symbolic_ref(data->old_refs, refname, &symref_target);\n+\tif (ref->flags & REF_ISSYMREF) {\n+\t\tret = refs_read_symbolic_ref(data->old_refs, ref->name, &symref_target);\n \t\tif (ret < 0)\n \t\t\tgoto done;\n \n-\t\tret = ref_transaction_update(data->transaction, refname, NULL, null_oid(the_hash_algo),\n+\t\tret = ref_transaction_update(data->transaction, ref->name, NULL, null_oid(the_hash_algo),\n \t\t\t\t\t     symref_target.buf, NULL,\n \t\t\t\t\t     REF_SKIP_CREATE_REFLOG | REF_NO_DEREF, NULL, data->errbuf);\n \t\tif (ret < 0)\n \t\t\tgoto done;\n \t} else {\n-\t\tret = ref_transaction_create(data->transaction, refname, oid, NULL,\n+\t\tret = ref_transaction_create(data->transaction, ref->name, ref->oid, NULL,\n \t\t\t\t\t     REF_SKIP_CREATE_REFLOG | REF_SKIP_OID_VERIFICATION,\n \t\t\t\t\t     NULL, data->errbuf);\n \t\tif (ret < 0)\ndiff --git a/refs.h b/refs.h\nindex 4e6bd63aa86..68d235438c2 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -355,14 +355,32 @@ struct ref_transaction;\n  */\n #define REF_BAD_NAME 0x08\n \n+/* A reference passed to `for_each_ref()`-style callbacks. */\n+struct reference {\n+\t/* The fully-qualified name of the reference. */\n+\tconst char *name;\n+\n+\t/* The target of a symbolic ref. `NULL` for direct references. */\n+\tconst char *target;\n+\n+\t/*\n+\t * The object ID of a reference. Either the direct object ID or the\n+\t * resolved object ID in the case of a symbolic ref. May be the zero\n+\t * object ID in case the symbolic ref cannot be resolved.\n+\t */\n+\tconst struct object_id *oid;\n+\n+\t/* A bitfield of `REF_` flags. */\n+\tint flags;\n+};\n+\n /*\n  * The signature for the callback function for the for_each_*()\n- * functions below.  The memory pointed to by the refname and oid\n- * arguments is only guaranteed to be valid for the duration of a\n+ * functions below.  The memory pointed to by the `struct reference`\n+ * argument is only guaranteed to be valid for the duration of a\n  * single callback invocation.\n  */\n-typedef int each_ref_fn(const char *refname, const char *referent,\n-\t\t\tconst struct object_id *oid, int flags, void *cb_data);\n+typedef int each_ref_fn(const struct reference *ref, void *cb_data);\n \n /*\n  * The following functions invoke the specified callback function for\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex bb2bec38076..0ddcf22aed7 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -3151,14 +3151,11 @@ static int parse_and_write_reflog(struct files_ref_store *refs,\n \treturn 0;\n }\n \n-static int ref_present(const char *refname, const char *referent UNUSED,\n-\t\t       const struct object_id *oid UNUSED,\n-\t\t       int flags UNUSED,\n-\t\t       void *cb_data)\n+static int ref_present(const struct reference *ref, void *cb_data)\n {\n \tstruct string_list *affected_refnames = cb_data;\n \n-\treturn string_list_has_string(affected_refnames, refname);\n+\treturn string_list_has_string(affected_refnames, ref->name);\n }\n \n static int files_transaction_finish_initial(struct files_ref_store *refs,\ndiff --git a/refs/iterator.c b/refs/iterator.c\nindex 17ef841d8a3..7f2e718f1c9 100644\n--- a/refs/iterator.c\n+++ b/refs/iterator.c\n@@ -476,7 +476,14 @@ int do_for_each_ref_iterator(struct ref_iterator *iter,\n \n \tcurrent_ref_iter = iter;\n \twhile ((ok = ref_iterator_advance(iter)) == ITER_OK) {\n-\t\tretval = fn(iter->refname, iter->referent, iter->oid, iter->flags, cb_data);\n+\t\tstruct reference ref = {\n+\t\t\t.name = iter->refname,\n+\t\t\t.target = iter->referent,\n+\t\t\t.oid = iter->oid,\n+\t\t\t.flags = iter->flags,\n+\t\t};\n+\n+\t\tretval = fn(&ref, cb_data);\n \t\tif (retval)\n \t\t\tgoto out;\n \t}\ndiff --git a/remote.c b/remote.c\nindex df9675cd330..59b37151208 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -2315,21 +2315,19 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,\n \treturn 1;\n }\n \n-static int one_local_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t int flag UNUSED,\n-\t\t\t void *cb_data)\n+static int one_local_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct ref ***local_tail = cb_data;\n-\tstruct ref *ref;\n+\tstruct ref *local_ref;\n \n \t/* we already know it starts with refs/ to get here */\n-\tif (check_refname_format(refname + 5, 0))\n+\tif (check_refname_format(ref->name + 5, 0))\n \t\treturn 0;\n \n-\tref = alloc_ref(refname);\n-\toidcpy(&ref->new_oid, oid);\n-\t**local_tail = ref;\n-\t*local_tail = &ref->next;\n+\tlocal_ref = alloc_ref(ref->name);\n+\toidcpy(&local_ref->new_oid, ref->oid);\n+\t**local_tail = local_ref;\n+\t*local_tail = &local_ref->next;\n \treturn 0;\n }\n \n@@ -2402,15 +2400,14 @@ struct stale_heads_info {\n \tstruct refspec *rs;\n };\n \n-static int get_stale_heads_cb(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t      int flags, void *cb_data)\n+static int get_stale_heads_cb(const struct reference *ref, void *cb_data)\n {\n \tstruct stale_heads_info *info = cb_data;\n \tstruct string_list matches = STRING_LIST_INIT_DUP;\n \tstruct refspec_item query;\n \tint i, stale = 1;\n \tmemset(&query, 0, sizeof(struct refspec_item));\n-\tquery.dst = (char *)refname;\n+\tquery.dst = (char *)ref->name;\n \n \trefspec_find_all_matches(info->rs, &query, &matches);\n \tif (matches.nr == 0)\n@@ -2423,7 +2420,7 @@ static int get_stale_heads_cb(const char *refname, const char *referent UNUSED,\n \t * overlapping refspecs, we need to go over all of the\n \t * matching refs.\n \t */\n-\tif (flags & REF_ISSYMREF)\n+\tif (ref->flags & REF_ISSYMREF)\n \t\tgoto clean_exit;\n \n \tfor (i = 0; stale && i < matches.nr; i++)\n@@ -2431,8 +2428,8 @@ static int get_stale_heads_cb(const char *refname, const char *referent UNUSED,\n \t\t\tstale = 0;\n \n \tif (stale) {\n-\t\tstruct ref *ref = make_linked_ref(refname, &info->stale_refs_tail);\n-\t\toidcpy(&ref->new_oid, oid);\n+\t\tstruct ref *linked_ref = make_linked_ref(ref->name, &info->stale_refs_tail);\n+\t\toidcpy(&linked_ref->new_oid, ref->oid);\n \t}\n \n clean_exit:\ndiff --git a/replace-object.c b/replace-object.c\nindex 3eae0510745..03d0f1f083b 100644\n--- a/replace-object.c\n+++ b/replace-object.c\n@@ -8,31 +8,27 @@\n #include \"repository.h\"\n #include \"commit.h\"\n \n-static int register_replace_ref(const char *refname,\n-\t\t\t\tconst char *referent UNUSED,\n-\t\t\t\tconst struct object_id *oid,\n-\t\t\t\tint flag UNUSED,\n-\t\t\t\tvoid *cb_data)\n+static int register_replace_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct repository *r = cb_data;\n \n \t/* Get sha1 from refname */\n-\tconst char *slash = strrchr(refname, '/');\n-\tconst char *hash = slash ? slash + 1 : refname;\n+\tconst char *slash = strrchr(ref->name, '/');\n+\tconst char *hash = slash ? slash + 1 : ref->name;\n \tstruct replace_object *repl_obj = xmalloc(sizeof(*repl_obj));\n \n \tif (get_oid_hex_algop(hash, &repl_obj->original.oid, r->hash_algo)) {\n \t\tfree(repl_obj);\n-\t\twarning(_(\"bad replace ref name: %s\"), refname);\n+\t\twarning(_(\"bad replace ref name: %s\"), ref->name);\n \t\treturn 0;\n \t}\n \n \t/* Copy sha1 from the read ref */\n-\toidcpy(&repl_obj->replacement, oid);\n+\toidcpy(&repl_obj->replacement, ref->oid);\n \n \t/* Register new object */\n \tif (oidmap_put(&r->objects->replace_map, repl_obj))\n-\t\tdie(_(\"duplicate replace ref: %s\"), refname);\n+\t\tdie(_(\"duplicate replace ref: %s\"), ref->name);\n \n \treturn 0;\n }\ndiff --git a/revision.c b/revision.c\nindex 806a1c4c24d..a195c0ded45 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -1647,19 +1647,17 @@ struct all_refs_cb {\n \tstruct worktree *wt;\n };\n \n-static int handle_one_ref(const char *path, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t  int flag UNUSED,\n-\t\t\t  void *cb_data)\n+static int handle_one_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct all_refs_cb *cb = cb_data;\n \tstruct object *object;\n \n-\tif (ref_excluded(&cb->all_revs->ref_excludes, path))\n+\tif (ref_excluded(&cb->all_revs->ref_excludes, ref->name))\n \t    return 0;\n \n-\tobject = get_reference(cb->all_revs, path, oid, cb->all_flags);\n-\tadd_rev_cmdline(cb->all_revs, object, path, REV_CMD_REF, cb->all_flags);\n-\tadd_pending_object(cb->all_revs, object, path);\n+\tobject = get_reference(cb->all_revs, ref->name, ref->oid, cb->all_flags);\n+\tadd_rev_cmdline(cb->all_revs, object, ref->name, REV_CMD_REF, cb->all_flags);\n+\tadd_pending_object(cb->all_revs, object, ref->name);\n \treturn 0;\n }\n \ndiff --git a/server-info.c b/server-info.c\nindex 9bb30d9ab71..aea4df80416 100644\n--- a/server-info.c\n+++ b/server-info.c\n@@ -148,23 +148,21 @@ static int update_info_file(struct repository *r, char *path,\n \treturn ret;\n }\n \n-static int add_info_ref(const char *path, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\tint flag UNUSED,\n-\t\t\tvoid *cb_data)\n+static int add_info_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct update_info_ctx *uic = cb_data;\n-\tstruct object *o = parse_object(uic->repo, oid);\n+\tstruct object *o = parse_object(uic->repo, ref->oid);\n \tif (!o)\n \t\treturn -1;\n \n-\tif (uic_printf(uic, \"%s\t%s\\n\", oid_to_hex(oid), path) < 0)\n+\tif (uic_printf(uic, \"%s\t%s\\n\", oid_to_hex(ref->oid), ref->name) < 0)\n \t\treturn -1;\n \n \tif (o->type == OBJ_TAG) {\n-\t\to = deref_tag(uic->repo, o, path, 0);\n+\t\to = deref_tag(uic->repo, o, ref->name, 0);\n \t\tif (o)\n \t\t\tif (uic_printf(uic, \"%s\t%s^{}\\n\",\n-\t\t\t\toid_to_hex(&o->oid), path) < 0)\n+\t\t\t\toid_to_hex(&o->oid), ref->name) < 0)\n \t\t\t\treturn -1;\n \t}\n \treturn 0;\ndiff --git a/shallow.c b/shallow.c\nindex d9cd4e219cb..55b9cd9d3f2 100644\n--- a/shallow.c\n+++ b/shallow.c\n@@ -626,14 +626,10 @@ static void paint_down(struct paint_info *info, const struct object_id *oid,\n \tfree(tmp);\n }\n \n-static int mark_uninteresting(const char *refname UNUSED,\n-\t\t\t      const char *referent UNUSED,\n-\t\t\t      const struct object_id *oid,\n-\t\t\t      int flags UNUSED,\n-\t\t\t      void *cb_data UNUSED)\n+static int mark_uninteresting(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct commit *commit = lookup_commit_reference_gently(the_repository,\n-\t\t\t\t\t\t\t       oid, 1);\n+\t\t\t\t\t\t\t       ref->oid, 1);\n \tif (!commit)\n \t\treturn 0;\n \tcommit->object.flags |= UNINTERESTING;\n@@ -742,16 +738,12 @@ struct commit_array {\n \tsize_t nr, alloc;\n };\n \n-static int add_ref(const char *refname UNUSED,\n-\t\t  const char *referent UNUSED,\n-\t\t   const struct object_id *oid,\n-\t\t   int flags UNUSED,\n-\t\t   void *cb_data)\n+static int add_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct commit_array *ca = cb_data;\n \tALLOC_GROW(ca->commits, ca->nr + 1, ca->alloc);\n \tca->commits[ca->nr] = lookup_commit_reference_gently(the_repository,\n-\t\t\t\t\t\t\t     oid, 1);\n+\t\t\t\t\t\t\t     ref->oid, 1);\n \tif (ca->commits[ca->nr])\n \t\tca->nr++;\n \treturn 0;\ndiff --git a/submodule.c b/submodule.c\nindex 35c55155f7b..40a5c6fb9d1 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -934,10 +934,7 @@ static void free_submodules_data(struct string_list *submodules)\n \tstring_list_clear(submodules, 1);\n }\n \n-static int has_remote(const char *refname UNUSED,\n-\t\t      const char *referent UNUSED,\n-\t\t      const struct object_id *oid UNUSED,\n-\t\t      int flags UNUSED, void *cb_data UNUSED)\n+static int has_remote(const struct reference *ref UNUSED, void *cb_data UNUSED)\n {\n \treturn 1;\n }\n@@ -1255,13 +1252,10 @@ int push_unpushed_submodules(struct repository *r,\n \treturn ret;\n }\n \n-static int append_oid_to_array(const char *ref UNUSED,\n-\t\t\t       const char *referent UNUSED,\n-\t\t\t       const struct object_id *oid,\n-\t\t\t       int flags UNUSED, void *data)\n+static int append_oid_to_array(const struct reference *ref, void *data)\n {\n \tstruct oid_array *array = data;\n-\toid_array_append(array, oid);\n+\toid_array_append(array, ref->oid);\n \treturn 0;\n }\n \ndiff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c\nindex 83b06d39a36..b1215947c5e 100644\n--- a/t/helper/test-ref-store.c\n+++ b/t/helper/test-ref-store.c\n@@ -154,10 +154,9 @@ static int cmd_rename_ref(struct ref_store *refs, const char **argv)\n \treturn refs_rename_ref(refs, oldref, newref, logmsg);\n }\n \n-static int each_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t    int flags, void *cb_data UNUSED)\n+static int each_ref(const struct reference *ref, void *cb_data UNUSED)\n {\n-\tprintf(\"%s %s 0x%x\\n\", oid_to_hex(oid), refname, flags);\n+\tprintf(\"%s %s 0x%x\\n\", oid_to_hex(ref->oid), ref->name, ref->flags);\n \treturn 0;\n }\n \ndiff --git a/upload-pack.c b/upload-pack.c\nindex 1e87ae95593..0d563ae74e9 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -870,8 +870,8 @@ static void send_unshallow(struct upload_pack_data *data)\n \t}\n }\n \n-static int check_ref(const char *refname_full, const char *referent UNUSED, const struct object_id *oid,\n-\t\t     int flag, void *cb_data);\n+static int check_ref(const struct reference *ref, void *cb_data);\n+\n static void deepen(struct upload_pack_data *data, int depth)\n {\n \tif (depth == INFINITE_DEPTH && !is_repository_shallow(the_repository)) {\n@@ -1224,13 +1224,12 @@ static int mark_our_ref(const char *refname, const char *refname_full,\n \treturn 0;\n }\n \n-static int check_ref(const char *refname_full, const char *referent UNUSED,const struct object_id *oid,\n-\t\t     int flag UNUSED, void *cb_data)\n+static int check_ref(const struct reference *ref, void *cb_data)\n {\n-\tconst char *refname = strip_namespace(refname_full);\n+\tconst char *refname = strip_namespace(ref->name);\n \tstruct upload_pack_data *data = cb_data;\n \n-\tmark_our_ref(refname, refname_full, oid, &data->hidden_refs);\n+\tmark_our_ref(refname, ref->name, ref->oid, &data->hidden_refs);\n \treturn 0;\n }\n \n@@ -1292,27 +1291,25 @@ static void write_v0_ref(struct upload_pack_data *data,\n \treturn;\n }\n \n-static int send_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t    int flag UNUSED, void *cb_data)\n+static int send_ref(const struct reference *ref, void *cb_data)\n {\n-\twrite_v0_ref(cb_data, refname, strip_namespace(refname), oid);\n+\twrite_v0_ref(cb_data, ref->name, strip_namespace(ref->name), ref->oid);\n \treturn 0;\n }\n \n-static int find_symref(const char *refname, const char *referent UNUSED,\n-\t\t       const struct object_id *oid UNUSED,\n-\t\t       int flag, void *cb_data)\n+static int find_symref(const struct reference *ref, void *cb_data)\n {\n \tconst char *symref_target;\n \tstruct string_list_item *item;\n+\tint flag;\n \n-\tif ((flag & REF_ISSYMREF) == 0)\n+\tif ((ref->flags & REF_ISSYMREF) == 0)\n \t\treturn 0;\n \tsymref_target = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),\n-\t\t\t\t\t\trefname, 0, NULL, &flag);\n+\t\t\t\t\t\tref->name, 0, NULL, &flag);\n \tif (!symref_target || (flag & REF_ISSYMREF) == 0)\n-\t\tdie(\"'%s' is a symref but it is not?\", refname);\n-\titem = string_list_append(cb_data, strip_namespace(refname));\n+\t\tdie(\"'%s' is a symref but it is not?\", ref->name);\n+\titem = string_list_append(cb_data, strip_namespace(ref->name));\n \titem->util = xstrdup(strip_namespace(symref_target));\n \treturn 0;\n }\ndiff --git a/walker.c b/walker.c\nindex 80737545172..409b646578a 100644\n--- a/walker.c\n+++ b/walker.c\n@@ -226,14 +226,10 @@ static int interpret_target(struct walker *walker, char *target, struct object_i\n \treturn -1;\n }\n \n-static int mark_complete(const char *path UNUSED,\n-\t\t\tconst char *referent UNUSED,\n-\t\t\t const struct object_id *oid,\n-\t\t\t int flag UNUSED,\n-\t\t\t void *cb_data UNUSED)\n+static int mark_complete(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct commit *commit = lookup_commit_reference_gently(the_repository,\n-\t\t\t\t\t\t\t       oid, 1);\n+\t\t\t\t\t\t\t       ref->oid, 1);\n \n \tif (commit) {\n \t\tcommit->object.flags |= COMPLETE;\ndiff --git a/worktree.c b/worktree.c\nindex a2a5f51f29f..9308389cb6f 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -595,8 +595,15 @@ int other_head_refs(each_ref_fn fn, void *cb_data)\n \t\tif (refs_resolve_ref_unsafe(get_main_ref_store(the_repository),\n \t\t\t\t\t    refname.buf,\n \t\t\t\t\t    RESOLVE_REF_READING,\n-\t\t\t\t\t    &oid, &flag))\n-\t\t\tret = fn(refname.buf, NULL, &oid, flag, cb_data);\n+\t\t\t\t\t    &oid, &flag)) {\n+\t\t\tstruct reference ref = {\n+\t\t\t\t.name = refname.buf,\n+\t\t\t\t.oid = &oid,\n+\t\t\t\t.flags = flag,\n+\t\t\t};\n+\n+\t\t\tret = fn(&ref, cb_data);\n+\t\t}\n \t\tif (ret)\n \t\t\tbreak;\n \t}\n\n-- \n2.51.0.764.g787ff6f08a.dirty\n\n"},{"id":"528259","messageId":"20251008-b4-pks-ref-filter-skip-parsing-objects-v2-2-76e30d5c9542@pks.im","threadId":"64260","inReplyTo":"20251008-b4-pks-ref-filter-skip-parsing-objects-v2-0-76e30d5c9542@pks.im","subject":"[PATCH v2 02/14] refs: introduce `.ref` field for the base iterator","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-08T15:50:17Z","receivedAt":"2025-10-08T15:50:34Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The base iterator has a couple of fields that tracks the name, target,\nobject ID and flags for the current reference. Due to this design we\nhave to create a new `struct reference` whenever we want to hand over\nthat reference to the callback function, which is tedious and not very\nefficient.\n\nConvert the structure to instead contain a `struct reference` as member.\nThis member is expected to be populated by the implementations of the\niterator and is handed over to the callback directly.\n\nWhile at it, simplify `should_pack_ref()` to take a `struct reference`\ndirectly instead of passing its respective fields.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c                  |  8 ++++----\n refs/debug.c            |  8 +++-----\n refs/files-backend.c    | 47 +++++++++++++++++++++--------------------------\n refs/iterator.c         | 39 ++++++++++++---------------------------\n refs/packed-backend.c   | 46 +++++++++++++++++++++++-----------------------\n refs/ref-cache.c        | 10 +++++-----\n refs/refs-internal.h    |  5 +----\n refs/reftable-backend.c | 12 ++++++------\n 8 files changed, 75 insertions(+), 100 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 262849301d..15ad0ef7a8 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2326,8 +2326,8 @@ int refs_optimize(struct ref_store *refs, struct pack_refs_opts *opts)\n int peel_iterated_oid(struct repository *r, const struct object_id *base, struct object_id *peeled)\n {\n \tif (current_ref_iter &&\n-\t    (current_ref_iter->oid == base ||\n-\t     oideq(current_ref_iter->oid, base)))\n+\t    (current_ref_iter->ref.oid == base ||\n+\t     oideq(current_ref_iter->ref.oid, base)))\n \t\treturn ref_iterator_peel(current_ref_iter, peeled);\n \n \treturn peel_object(r, base, peeled) ? -1 : 0;\n@@ -2702,7 +2702,7 @@ enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs\n \n \t\t\twhile ((ok = ref_iterator_advance(iter)) == ITER_OK) {\n \t\t\t\tif (skip &&\n-\t\t\t\t    string_list_has_string(skip, iter->refname))\n+\t\t\t\t    string_list_has_string(skip, iter->ref.name))\n \t\t\t\t\tcontinue;\n \n \t\t\t\tif (transaction && ref_transaction_maybe_set_rejected(\n@@ -2711,7 +2711,7 @@ enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs\n \t\t\t\t\tcontinue;\n \n \t\t\t\tstrbuf_addf(err, _(\"'%s' exists; cannot create '%s'\"),\n-\t\t\t\t\t    iter->refname, refname);\n+\t\t\t\t\t    iter->ref.name, refname);\n \t\t\t\tgoto cleanup;\n \t\t\t}\n \ndiff --git a/refs/debug.c b/refs/debug.c\nindex 1cb955961e..7a26035617 100644\n--- a/refs/debug.c\n+++ b/refs/debug.c\n@@ -161,11 +161,9 @@ static int debug_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\ttrace_printf_key(&trace_refs, \"iterator_advance: (%d)\\n\", res);\n \telse\n \t\ttrace_printf_key(&trace_refs, \"iterator_advance: %s (0)\\n\",\n-\t\t\tditer->iter->refname);\n+\t\t\tditer->iter->ref.name);\n \n-\tditer->base.refname = diter->iter->refname;\n-\tditer->base.oid = diter->iter->oid;\n-\tditer->base.flags = diter->iter->flags;\n+\tditer->base.ref = diter->iter->ref;\n \treturn res;\n }\n \n@@ -186,7 +184,7 @@ static int debug_ref_iterator_peel(struct ref_iterator *ref_iterator,\n \tstruct debug_ref_iterator *diter =\n \t\t(struct debug_ref_iterator *)ref_iterator;\n \tint res = diter->iter->vtable->peel(diter->iter, peeled);\n-\ttrace_printf_key(&trace_refs, \"iterator_peel: %s: %d\\n\", diter->iter->refname, res);\n+\ttrace_printf_key(&trace_refs, \"iterator_peel: %s: %d\\n\", diter->iter->ref.name, res);\n \treturn res;\n }\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 0ddcf22aed..d34fbe55d6 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -962,26 +962,23 @@ static int files_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \n \twhile ((ok = ref_iterator_advance(iter->iter0)) == ITER_OK) {\n \t\tif (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY &&\n-\t\t    parse_worktree_ref(iter->iter0->refname, NULL, NULL,\n+\t\t    parse_worktree_ref(iter->iter0->ref.name, NULL, NULL,\n \t\t\t\t       NULL) != REF_WORKTREE_CURRENT)\n \t\t\tcontinue;\n \n \t\tif ((iter->flags & DO_FOR_EACH_OMIT_DANGLING_SYMREFS) &&\n-\t\t    (iter->iter0->flags & REF_ISSYMREF) &&\n-\t\t    (iter->iter0->flags & REF_ISBROKEN))\n+\t\t    (iter->iter0->ref.flags & REF_ISSYMREF) &&\n+\t\t    (iter->iter0->ref.flags & REF_ISBROKEN))\n \t\t\tcontinue;\n \n \t\tif (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&\n-\t\t    !ref_resolves_to_object(iter->iter0->refname,\n+\t\t    !ref_resolves_to_object(iter->iter0->ref.name,\n \t\t\t\t\t    iter->repo,\n-\t\t\t\t\t    iter->iter0->oid,\n-\t\t\t\t\t    iter->iter0->flags))\n+\t\t\t\t\t    iter->iter0->ref.oid,\n+\t\t\t\t\t    iter->iter0->ref.flags))\n \t\t\tcontinue;\n \n-\t\titer->base.refname = iter->iter0->refname;\n-\t\titer->base.oid = iter->iter0->oid;\n-\t\titer->base.flags = iter->iter0->flags;\n-\t\titer->base.referent = iter->iter0->referent;\n+\t\titer->base.ref = iter->iter0->ref;\n \n \t\treturn ITER_OK;\n \t}\n@@ -1368,30 +1365,29 @@ static void prune_refs(struct files_ref_store *refs, struct ref_to_prune **refs_\n  * Return true if the specified reference should be packed.\n  */\n static int should_pack_ref(struct files_ref_store *refs,\n-\t\t\t   const char *refname,\n-\t\t\t   const struct object_id *oid, unsigned int ref_flags,\n+\t\t\t   const struct reference *ref,\n \t\t\t   struct pack_refs_opts *opts)\n {\n \tstruct string_list_item *item;\n \n \t/* Do not pack per-worktree refs: */\n-\tif (parse_worktree_ref(refname, NULL, NULL, NULL) !=\n+\tif (parse_worktree_ref(ref->name, NULL, NULL, NULL) !=\n \t    REF_WORKTREE_SHARED)\n \t\treturn 0;\n \n \t/* Do not pack symbolic refs: */\n-\tif (ref_flags & REF_ISSYMREF)\n+\tif (ref->flags & REF_ISSYMREF)\n \t\treturn 0;\n \n \t/* Do not pack broken refs: */\n-\tif (!ref_resolves_to_object(refname, refs->base.repo, oid, ref_flags))\n+\tif (!ref_resolves_to_object(ref->name, refs->base.repo, ref->oid, ref->flags))\n \t\treturn 0;\n \n-\tif (ref_excluded(opts->exclusions, refname))\n+\tif (ref_excluded(opts->exclusions, ref->name))\n \t\treturn 0;\n \n \tfor_each_string_list_item(item, opts->includes)\n-\t\tif (!wildmatch(item->string, refname, 0))\n+\t\tif (!wildmatch(item->string, ref->name, 0))\n \t\t\treturn 1;\n \n \treturn 0;\n@@ -1444,8 +1440,7 @@ static int should_pack_refs(struct files_ref_store *refs,\n \titer = cache_ref_iterator_begin(get_loose_ref_cache(refs, 0), NULL,\n \t\t\t\t\trefs->base.repo, 0);\n \twhile ((ret = ref_iterator_advance(iter)) == ITER_OK) {\n-\t\tif (should_pack_ref(refs, iter->refname, iter->oid,\n-\t\t\t\t    iter->flags, opts))\n+\t\tif (should_pack_ref(refs, &iter->ref, opts))\n \t\t\trefcount++;\n \t\tif (refcount >= limit) {\n \t\t\tref_iterator_free(iter);\n@@ -1490,24 +1485,24 @@ static int files_pack_refs(struct ref_store *ref_store,\n \t\t * in the packed ref cache. If the reference should be\n \t\t * pruned, also add it to refs_to_prune.\n \t\t */\n-\t\tif (!should_pack_ref(refs, iter->refname, iter->oid, iter->flags, opts))\n+\t\tif (!should_pack_ref(refs, &iter->ref, opts))\n \t\t\tcontinue;\n \n \t\t/*\n \t\t * Add a reference creation for this reference to the\n \t\t * packed-refs transaction:\n \t\t */\n-\t\tif (ref_transaction_update(transaction, iter->refname,\n-\t\t\t\t\t   iter->oid, NULL, NULL, NULL,\n+\t\tif (ref_transaction_update(transaction, iter->ref.name,\n+\t\t\t\t\t   iter->ref.oid, NULL, NULL, NULL,\n \t\t\t\t\t   REF_NO_DEREF, NULL, &err))\n \t\t\tdie(\"failure preparing to create packed reference %s: %s\",\n-\t\t\t    iter->refname, err.buf);\n+\t\t\t    iter->ref.name, err.buf);\n \n \t\t/* Schedule the loose reference for pruning if requested. */\n \t\tif ((opts->flags & PACK_REFS_PRUNE)) {\n \t\t\tstruct ref_to_prune *n;\n-\t\t\tFLEX_ALLOC_STR(n, name, iter->refname);\n-\t\t\toidcpy(&n->oid, iter->oid);\n+\t\t\tFLEX_ALLOC_STR(n, name, iter->ref.name);\n+\t\t\toidcpy(&n->oid, iter->ref.oid);\n \t\t\tn->next = refs_to_prune;\n \t\t\trefs_to_prune = n;\n \t\t}\n@@ -2380,7 +2375,7 @@ static int files_reflog_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t\t\t\t REFNAME_ALLOW_ONELEVEL))\n \t\t\tcontinue;\n \n-\t\titer->base.refname = diter->relative_path;\n+\t\titer->base.ref.name = diter->relative_path;\n \t\treturn ITER_OK;\n \t}\n \ndiff --git a/refs/iterator.c b/refs/iterator.c\nindex 7f2e718f1c..fe5980e1b6 100644\n--- a/refs/iterator.c\n+++ b/refs/iterator.c\n@@ -41,10 +41,7 @@ void base_ref_iterator_init(struct ref_iterator *iter,\n \t\t\t    struct ref_iterator_vtable *vtable)\n {\n \titer->vtable = vtable;\n-\titer->refname = NULL;\n-\titer->referent = NULL;\n-\titer->oid = NULL;\n-\titer->flags = 0;\n+\tmemset(&iter->ref, 0, sizeof(iter->ref));\n }\n \n struct empty_ref_iterator {\n@@ -127,8 +124,8 @@ enum iterator_selection ref_iterator_select(struct ref_iterator *iter_worktree,\n \t\t * latter.\n \t\t */\n \t\tif (iter_worktree) {\n-\t\t\tint cmp = strcmp(iter_worktree->refname,\n-\t\t\t\t\t iter_common->refname);\n+\t\t\tint cmp = strcmp(iter_worktree->ref.name,\n+\t\t\t\t\t iter_common->ref.name);\n \t\t\tif (cmp < 0)\n \t\t\t\treturn ITER_SELECT_0;\n \t\t\telse if (!cmp)\n@@ -139,7 +136,7 @@ enum iterator_selection ref_iterator_select(struct ref_iterator *iter_worktree,\n \t\t  * We now know that the lexicographically-next ref is a common\n \t\t  * ref. When the common ref is a shared one we return it.\n \t\t  */\n-\t\tif (parse_worktree_ref(iter_common->refname, NULL, NULL,\n+\t\tif (parse_worktree_ref(iter_common->ref.name, NULL, NULL,\n \t\t\t\t       NULL) == REF_WORKTREE_SHARED)\n \t\t\treturn ITER_SELECT_1;\n \n@@ -212,10 +209,7 @@ static int merge_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t}\n \n \t\tif (selection & ITER_YIELD_CURRENT) {\n-\t\t\titer->base.referent = (*iter->current)->referent;\n-\t\t\titer->base.refname = (*iter->current)->refname;\n-\t\t\titer->base.oid = (*iter->current)->oid;\n-\t\t\titer->base.flags = (*iter->current)->flags;\n+\t\t\titer->base.ref = (*iter->current)->ref;\n \t\t\treturn ITER_OK;\n \t\t}\n \t}\n@@ -313,7 +307,7 @@ static enum iterator_selection overlay_iterator_select(\n \telse if (!front)\n \t\treturn ITER_SELECT_1;\n \n-\tcmp = strcmp(front->refname, back->refname);\n+\tcmp = strcmp(front->ref.name, back->ref.name);\n \n \tif (cmp < 0)\n \t\treturn ITER_SELECT_0;\n@@ -371,7 +365,7 @@ static int prefix_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \tint ok;\n \n \twhile ((ok = ref_iterator_advance(iter->iter0)) == ITER_OK) {\n-\t\tint cmp = compare_prefix(iter->iter0->refname, iter->prefix);\n+\t\tint cmp = compare_prefix(iter->iter0->ref.name, iter->prefix);\n \t\tif (cmp < 0)\n \t\t\tcontinue;\n \t\t/*\n@@ -382,6 +376,8 @@ static int prefix_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\tif (cmp > 0)\n \t\t\treturn ITER_DONE;\n \n+\t\titer->base.ref = iter->iter0->ref;\n+\n \t\tif (iter->trim) {\n \t\t\t/*\n \t\t\t * It is nonsense to trim off characters that\n@@ -392,15 +388,11 @@ static int prefix_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t\t * one character left in the refname after\n \t\t\t * trimming, report it as a bug:\n \t\t\t */\n-\t\t\tif (strlen(iter->iter0->refname) <= iter->trim)\n+\t\t\tif (strlen(iter->base.ref.name) <= iter->trim)\n \t\t\t\tBUG(\"attempt to trim too many characters\");\n-\t\t\titer->base.refname = iter->iter0->refname + iter->trim;\n-\t\t} else {\n-\t\t\titer->base.refname = iter->iter0->refname;\n+\t\t\titer->base.ref.name += iter->trim;\n \t\t}\n \n-\t\titer->base.oid = iter->iter0->oid;\n-\t\titer->base.flags = iter->iter0->flags;\n \t\treturn ITER_OK;\n \t}\n \n@@ -476,14 +468,7 @@ int do_for_each_ref_iterator(struct ref_iterator *iter,\n \n \tcurrent_ref_iter = iter;\n \twhile ((ok = ref_iterator_advance(iter)) == ITER_OK) {\n-\t\tstruct reference ref = {\n-\t\t\t.name = iter->refname,\n-\t\t\t.target = iter->referent,\n-\t\t\t.oid = iter->oid,\n-\t\t\t.flags = iter->flags,\n-\t\t};\n-\n-\t\tretval = fn(&ref, cb_data);\n+\t\tretval = fn(&iter->ref, cb_data);\n \t\tif (retval)\n \t\t\tgoto out;\n \t}\ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex a8c22a0a7f..7987acdc96 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -908,7 +908,7 @@ static int next_record(struct packed_ref_iterator *iter)\n \tif (iter->pos == iter->eof)\n \t\treturn ITER_DONE;\n \n-\titer->base.flags = REF_ISPACKED;\n+\titer->base.ref.flags = REF_ISPACKED;\n \tp = iter->pos;\n \n \tif (iter->eof - p < snapshot_hexsz(iter->snapshot) + 2 ||\n@@ -923,22 +923,22 @@ static int next_record(struct packed_ref_iterator *iter)\n \t\t\t\t      iter->pos, iter->eof - iter->pos);\n \n \tstrbuf_add(&iter->refname_buf, p, eol - p);\n-\titer->base.refname = iter->refname_buf.buf;\n+\titer->base.ref.name = iter->refname_buf.buf;\n \n \tif (refname_contains_nul(&iter->refname_buf))\n-\t\tdie(\"packed refname contains embedded NULL: %s\", iter->base.refname);\n+\t\tdie(\"packed refname contains embedded NULL: %s\", iter->base.ref.name);\n \n-\tif (check_refname_format(iter->base.refname, REFNAME_ALLOW_ONELEVEL)) {\n-\t\tif (!refname_is_safe(iter->base.refname))\n+\tif (check_refname_format(iter->base.ref.name, REFNAME_ALLOW_ONELEVEL)) {\n+\t\tif (!refname_is_safe(iter->base.ref.name))\n \t\t\tdie(\"packed refname is dangerous: %s\",\n-\t\t\t    iter->base.refname);\n+\t\t\t    iter->base.ref.name);\n \t\toidclr(&iter->oid, iter->repo->hash_algo);\n-\t\titer->base.flags |= REF_BAD_NAME | REF_ISBROKEN;\n+\t\titer->base.ref.flags |= REF_BAD_NAME | REF_ISBROKEN;\n \t}\n \tif (iter->snapshot->peeled == PEELED_FULLY ||\n \t    (iter->snapshot->peeled == PEELED_TAGS &&\n-\t     starts_with(iter->base.refname, \"refs/tags/\")))\n-\t\titer->base.flags |= REF_KNOWS_PEELED;\n+\t     starts_with(iter->base.ref.name, \"refs/tags/\")))\n+\t\titer->base.ref.flags |= REF_KNOWS_PEELED;\n \n \titer->pos = eol + 1;\n \n@@ -956,11 +956,11 @@ static int next_record(struct packed_ref_iterator *iter)\n \t\t * definitely know the value of *this* reference. But\n \t\t * we suppress it if the reference is broken:\n \t\t */\n-\t\tif ((iter->base.flags & REF_ISBROKEN)) {\n+\t\tif ((iter->base.ref.flags & REF_ISBROKEN)) {\n \t\t\toidclr(&iter->peeled, iter->repo->hash_algo);\n-\t\t\titer->base.flags &= ~REF_KNOWS_PEELED;\n+\t\t\titer->base.ref.flags &= ~REF_KNOWS_PEELED;\n \t\t} else {\n-\t\t\titer->base.flags |= REF_KNOWS_PEELED;\n+\t\t\titer->base.ref.flags |= REF_KNOWS_PEELED;\n \t\t}\n \t} else {\n \t\toidclr(&iter->peeled, iter->repo->hash_algo);\n@@ -976,15 +976,15 @@ static int packed_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \tint ok;\n \n \twhile ((ok = next_record(iter)) == ITER_OK) {\n-\t\tconst char *refname = iter->base.refname;\n+\t\tconst char *refname = iter->base.ref.name;\n \t\tconst char *prefix = iter->prefix;\n \n \t\tif (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY &&\n-\t\t    !is_per_worktree_ref(iter->base.refname))\n+\t\t    !is_per_worktree_ref(iter->base.ref.name))\n \t\t\tcontinue;\n \n \t\tif (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&\n-\t\t    !ref_resolves_to_object(iter->base.refname, iter->repo,\n+\t\t    !ref_resolves_to_object(iter->base.ref.name, iter->repo,\n \t\t\t\t\t    &iter->oid, iter->flags))\n \t\t\tcontinue;\n \n@@ -1033,10 +1033,10 @@ static int packed_ref_iterator_peel(struct ref_iterator *ref_iterator,\n \tstruct packed_ref_iterator *iter =\n \t\t(struct packed_ref_iterator *)ref_iterator;\n \n-\tif ((iter->base.flags & REF_KNOWS_PEELED)) {\n+\tif ((iter->base.ref.flags & REF_KNOWS_PEELED)) {\n \t\toidcpy(peeled, &iter->peeled);\n \t\treturn is_null_oid(&iter->peeled) ? -1 : 0;\n-\t} else if ((iter->base.flags & (REF_ISBROKEN | REF_ISSYMREF))) {\n+\t} else if ((iter->base.ref.flags & (REF_ISBROKEN | REF_ISSYMREF))) {\n \t\treturn -1;\n \t} else {\n \t\treturn peel_object(iter->repo, &iter->oid, peeled) ? -1 : 0;\n@@ -1194,7 +1194,7 @@ static struct ref_iterator *packed_ref_iterator_begin(\n \titer->snapshot = snapshot;\n \tacquire_snapshot(snapshot);\n \tstrbuf_init(&iter->refname_buf, 0);\n-\titer->base.oid = &iter->oid;\n+\titer->base.ref.oid = &iter->oid;\n \titer->repo = ref_store->repo;\n \titer->flags = flags;\n \n@@ -1436,7 +1436,7 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re\n \t\t\tif (!iter)\n \t\t\t\tcmp = +1;\n \t\t\telse\n-\t\t\t\tcmp = strcmp(iter->refname, update->refname);\n+\t\t\t\tcmp = strcmp(iter->ref.name, update->refname);\n \t\t}\n \n \t\tif (!cmp) {\n@@ -1459,11 +1459,11 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re\n \t\t\t\t\t}\n \n \t\t\t\t\tgoto error;\n-\t\t\t\t} else if (!oideq(&update->old_oid, iter->oid)) {\n+\t\t\t\t} else if (!oideq(&update->old_oid, iter->ref.oid)) {\n \t\t\t\t\tstrbuf_addf(err, \"cannot update ref '%s': \"\n \t\t\t\t\t\t    \"is at %s but expected %s\",\n \t\t\t\t\t\t    update->refname,\n-\t\t\t\t\t\t    oid_to_hex(iter->oid),\n+\t\t\t\t\t\t    oid_to_hex(iter->ref.oid),\n \t\t\t\t\t\t    oid_to_hex(&update->old_oid));\n \t\t\t\t\tret = REF_TRANSACTION_ERROR_INCORRECT_OLD_VALUE;\n \n@@ -1527,8 +1527,8 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re\n \t\t\tstruct object_id peeled;\n \t\t\tint peel_error = ref_iterator_peel(iter, &peeled);\n \n-\t\t\tif (write_packed_entry(out, iter->refname,\n-\t\t\t\t\t       iter->oid,\n+\t\t\tif (write_packed_entry(out, iter->ref.name,\n+\t\t\t\t\t       iter->ref.oid,\n \t\t\t\t\t       peel_error ? NULL : &peeled))\n \t\t\t\tgoto write_error;\n \ndiff --git a/refs/ref-cache.c b/refs/ref-cache.c\nindex c180e0aad7..97555fa118 100644\n--- a/refs/ref-cache.c\n+++ b/refs/ref-cache.c\n@@ -425,10 +425,10 @@ static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t\tlevel->prefix_state = entry_prefix_state;\n \t\t\tlevel->index = -1;\n \t\t} else {\n-\t\t\titer->base.refname = entry->name;\n-\t\t\titer->base.referent = entry->u.value.referent;\n-\t\t\titer->base.oid = &entry->u.value.oid;\n-\t\t\titer->base.flags = entry->flag;\n+\t\t\titer->base.ref.name = entry->name;\n+\t\t\titer->base.ref.target = entry->u.value.referent;\n+\t\t\titer->base.ref.oid = &entry->u.value.oid;\n+\t\t\titer->base.ref.flags = entry->flag;\n \t\t\treturn ITER_OK;\n \t\t}\n \t}\n@@ -550,7 +550,7 @@ static int cache_ref_iterator_peel(struct ref_iterator *ref_iterator,\n {\n \tstruct cache_ref_iterator *iter =\n \t\t(struct cache_ref_iterator *)ref_iterator;\n-\treturn peel_object(iter->repo, ref_iterator->oid, peeled) ? -1 : 0;\n+\treturn peel_object(iter->repo, ref_iterator->ref.oid, peeled) ? -1 : 0;\n }\n \n static void cache_ref_iterator_release(struct ref_iterator *ref_iterator)\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 4ef3bd75c6..ed749d1657 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -249,10 +249,7 @@ const char *find_descendant_ref(const char *dirname,\n  */\n struct ref_iterator {\n \tstruct ref_iterator_vtable *vtable;\n-\tconst char *refname;\n-\tconst char *referent;\n-\tconst struct object_id *oid;\n-\tunsigned int flags;\n+\tstruct reference ref;\n };\n \n /*\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 9884b876c1..7fbc77492e 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -703,10 +703,10 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t\t\t\t    &iter->oid, flags))\n \t\t\t\tcontinue;\n \n-\t\titer->base.refname = iter->ref.refname;\n-\t\titer->base.referent = referent;\n-\t\titer->base.oid = &iter->oid;\n-\t\titer->base.flags = flags;\n+\t\titer->base.ref.name = iter->ref.refname;\n+\t\titer->base.ref.target = referent;\n+\t\titer->base.ref.oid = &iter->oid;\n+\t\titer->base.ref.flags = flags;\n \n \t\tbreak;\n \t}\n@@ -827,7 +827,7 @@ static struct reftable_ref_iterator *ref_iterator_for_stack(struct reftable_ref_\n \n \titer = xcalloc(1, sizeof(*iter));\n \tbase_ref_iterator_init(&iter->base, &reftable_ref_iterator_vtable);\n-\titer->base.oid = &iter->oid;\n+\titer->base.ref.oid = &iter->oid;\n \titer->flags = flags;\n \titer->refs = refs;\n \titer->exclude_patterns = filter_exclude_patterns(exclude_patterns);\n@@ -2071,7 +2071,7 @@ static int reftable_reflog_iterator_advance(struct ref_iterator *ref_iterator)\n \n \t\tstrbuf_reset(&iter->last_name);\n \t\tstrbuf_addstr(&iter->last_name, iter->log.refname);\n-\t\titer->base.refname = iter->log.refname;\n+\t\titer->base.ref.name = iter->log.refname;\n \n \t\tbreak;\n \t}\n\n-- \n2.51.0.764.g787ff6f08a.dirty\n\n"},{"id":"528261","messageId":"20251008-b4-pks-ref-filter-skip-parsing-objects-v2-3-76e30d5c9542@pks.im","threadId":"64260","inReplyTo":"20251008-b4-pks-ref-filter-skip-parsing-objects-v2-0-76e30d5c9542@pks.im","subject":"[PATCH v2 03/14] refs: fully reset `struct ref_iterator::ref` on iteration","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-08T15:50:18Z","receivedAt":"2025-10-08T15:50:36Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"With the introduction of the `struct ref_iterator::ref` field it now is\na whole lot easier to introduce new fields that become accessible to the\ncaller without having to adapt every single callsite. But there's a\ndownside: when a new field is introduced we always have to adapt all\nbackends to set that field.\n\nThis isn't something we can avoid in the general case: when the new\nfield is expected to be populated by all backends we of course cannot\navoid doing so. But new fields may be entirely optional, in which case\nwe'd still have such churn. And furthermore, it is very easy right now\nto leak state from a previous iteration into the next iteration.\n\nAddress this issue by ensuring that the reference backends all fully\nreset the field on every single iteration. This ensures that no state\nfrom previous iterations can leak into the next one. And it ensures that\nany newly introduced fields will be zeroed out by default.\n\nNote that we don't have to explicitly adapt the \"files\" backend, as it\nuses the `cache_ref_iterator` internally. Furthermore, other \"wrapping\"\niterators like for example the `prefix_ref_iterator` copy around the\nwhole reference, so these don't need to be adapted either.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs/packed-backend.c   | 3 ++-\n refs/ref-cache.c        | 1 +\n refs/reftable-backend.c | 1 +\n 3 files changed, 4 insertions(+), 1 deletion(-)\n\ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 7987acdc96..711e07f832 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -882,6 +882,7 @@ static int next_record(struct packed_ref_iterator *iter)\n {\n \tconst char *p, *eol;\n \n+\tmemset(&iter->base.ref, 0, sizeof(iter->base.ref));\n \tstrbuf_reset(&iter->refname_buf);\n \n \t/*\n@@ -916,6 +917,7 @@ static int next_record(struct packed_ref_iterator *iter)\n \t    !isspace(*p++))\n \t\tdie_invalid_line(iter->snapshot->refs->path,\n \t\t\t\t iter->pos, iter->eof - iter->pos);\n+\titer->base.ref.oid = &iter->oid;\n \n \teol = memchr(p, '\\n', iter->eof - p);\n \tif (!eol)\n@@ -1194,7 +1196,6 @@ static struct ref_iterator *packed_ref_iterator_begin(\n \titer->snapshot = snapshot;\n \tacquire_snapshot(snapshot);\n \tstrbuf_init(&iter->refname_buf, 0);\n-\titer->base.ref.oid = &iter->oid;\n \titer->repo = ref_store->repo;\n \titer->flags = flags;\n \ndiff --git a/refs/ref-cache.c b/refs/ref-cache.c\nindex 97555fa118..4726de430d 100644\n--- a/refs/ref-cache.c\n+++ b/refs/ref-cache.c\n@@ -425,6 +425,7 @@ static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t\tlevel->prefix_state = entry_prefix_state;\n \t\t\tlevel->index = -1;\n \t\t} else {\n+\t\t\tmemset(&iter->base.ref, 0, sizeof(iter->base.ref));\n \t\t\titer->base.ref.name = entry->name;\n \t\t\titer->base.ref.target = entry->u.value.referent;\n \t\t\titer->base.ref.oid = &entry->u.value.oid;\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 7fbc77492e..1e047fddae 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -703,6 +703,7 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t\t\t\t    &iter->oid, flags))\n \t\t\t\tcontinue;\n \n+\t\tmemset(&iter->base.ref, 0, sizeof(iter->base.ref));\n \t\titer->base.ref.name = iter->ref.refname;\n \t\titer->base.ref.target = referent;\n \t\titer->base.ref.oid = &iter->oid;\n\n-- \n2.51.0.764.g787ff6f08a.dirty\n\n"},{"id":"528262","messageId":"20251008-b4-pks-ref-filter-skip-parsing-objects-v2-4-76e30d5c9542@pks.im","threadId":"64260","inReplyTo":"20251008-b4-pks-ref-filter-skip-parsing-objects-v2-0-76e30d5c9542@pks.im","subject":"[PATCH v2 04/14] refs: refactor reference status flags","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-08T15:50:19Z","receivedAt":"2025-10-08T15:50:39Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The reference flags encode information like whether or not a reference\nis a symbolic reference or whether it may be broken. This information is\nstored in a `int flags` bitfield, which is in conflict with our modern\nbest practices; we tend to use an unsigned integer to store flags.\n\nChange the type of the field to be `unsigned`. While at it, refactor the\nindividual flags to be part of an `enum` instead of using preprocessor\ndefines.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.h | 41 +++++++++++++++++++++--------------------\n 1 file changed, 21 insertions(+), 20 deletions(-)\n\ndiff --git a/refs.h b/refs.h\nindex 68d235438c..4f0a685714 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -333,27 +333,28 @@ struct ref_transaction;\n  * stored in ref_iterator::flags. Other bits are for internal use\n  * only:\n  */\n+enum reference_status {\n+\t/* Reference is a symbolic reference. */\n+\tREF_ISSYMREF = (1 << 0),\n \n-/* Reference is a symbolic reference. */\n-#define REF_ISSYMREF 0x01\n+\t/* Reference is a packed reference. */\n+\tREF_ISPACKED = (1 << 1),\n \n-/* Reference is a packed reference. */\n-#define REF_ISPACKED 0x02\n-\n-/*\n- * Reference cannot be resolved to an object name: dangling symbolic\n- * reference (directly or indirectly), corrupt reference file,\n- * reference exists but name is bad, or symbolic reference refers to\n- * ill-formatted reference name.\n- */\n-#define REF_ISBROKEN 0x04\n+\t/*\n+\t * Reference cannot be resolved to an object name: dangling symbolic\n+\t * reference (directly or indirectly), corrupt reference file,\n+\t * reference exists but name is bad, or symbolic reference refers to\n+\t * ill-formatted reference name.\n+\t */\n+\tREF_ISBROKEN = (1 << 2),\n \n-/*\n- * Reference name is not well formed.\n- *\n- * See git-check-ref-format(1) for the definition of well formed ref names.\n- */\n-#define REF_BAD_NAME 0x08\n+\t/*\n+\t * Reference name is not well formed.\n+\t *\n+\t * See git-check-ref-format(1) for the definition of well formed ref names.\n+\t */\n+\tREF_BAD_NAME = (1 << 3),\n+};\n \n /* A reference passed to `for_each_ref()`-style callbacks. */\n struct reference {\n@@ -370,8 +371,8 @@ struct reference {\n \t */\n \tconst struct object_id *oid;\n \n-\t/* A bitfield of `REF_` flags. */\n-\tint flags;\n+\t/* A bitfield of `enum reference_status` flags. */\n+\tunsigned flags;\n };\n \n /*\n\n-- \n2.51.0.764.g787ff6f08a.dirty\n\n"},{"id":"528263","messageId":"20251008-b4-pks-ref-filter-skip-parsing-objects-v2-5-76e30d5c9542@pks.im","threadId":"64260","inReplyTo":"20251008-b4-pks-ref-filter-skip-parsing-objects-v2-0-76e30d5c9542@pks.im","subject":"[PATCH v2 05/14] refs: expose peeled object ID via the iterator","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-08T15:50:20Z","receivedAt":"2025-10-08T15:50:42Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Both the \"files\" and \"reftable\" backend are able to store peeled values\nfor tags in the respective formats. This allows for a more efficient\nlookup of the target object of such a tag without having to manually\npeel via the object database.\n\nThe infrastructure to access these peeled object IDs is somewhat funky\nthough. When iterating through objects, we store a pointer reference to\nthe current iterator in a global variable. The callbacks invoked by that\niterator are then expected to call `peel_iterated_oid()`, which checks\nwhether the globally-stored iterator's current reference refers to the\none handed into that function. If so, we ask the iterator to peel the\nobject, otherwise we manually peel the object via the object database.\nDepending on global state like this is somewhat weird and also quite\nfragile.\n\nIntroduce a new `struct reference::peeled_oid` field that can be\npopulated by the reference backends. This field can be accessed via a\nnew function `reference_get_peeled_oid()` that either uses that value,\nif set, or alternatively peels via the ODB. With this change we don't\nhave to rely on global state anymore, but make the peeled object ID\navailable to the callback functions directly.\n\nAdjust trivial callers that already have a `struct reference` available.\nRemaining callers will be adjusted in subsequent commits.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/describe.c      |  2 +-\n builtin/gc.c            |  2 +-\n builtin/pack-objects.c  |  7 ++++---\n builtin/repack.c        |  2 +-\n commit-graph.c          |  2 +-\n ls-refs.c               |  2 +-\n midx-write.c            |  2 +-\n pseudo-merge.c          |  2 +-\n refs.c                  | 12 ++++++++++++\n refs.h                  | 19 +++++++++++++++++++\n refs/packed-backend.c   |  1 +\n refs/reftable-backend.c |  5 +++++\n 12 files changed, 48 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/describe.c b/builtin/describe.c\nindex 7954535044..443546aaac 100644\n--- a/builtin/describe.c\n+++ b/builtin/describe.c\n@@ -208,7 +208,7 @@ static int get_name(const struct reference *ref, void *cb_data UNUSED)\n \t}\n \n \t/* Is it annotated? */\n-\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled)) {\n+\tif (!reference_get_peeled_oid(the_repository, ref, &peeled)) {\n \t\tis_annotated = !oideq(ref->oid, &peeled);\n \t} else {\n \t\toidcpy(&peeled, ref->oid);\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex a104c1cb78..814be8d1a9 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -1107,7 +1107,7 @@ static int dfs_on_ref(const struct reference *ref, void *cb_data)\n \tstruct commit_list *stack = NULL;\n \tstruct commit *commit;\n \n-\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n+\tif (!reference_get_peeled_oid(the_repository, ref, &peeled))\n \t\tmaybe_peeled = &peeled;\n \tif (odb_read_object_info(the_repository->objects, maybe_peeled, NULL) != OBJ_COMMIT)\n \t\treturn 0;\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 288ba19981..7fd457779d 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -838,7 +838,7 @@ static int mark_tagged(const struct reference *ref, void *cb_data UNUSED)\n \n \tif (entry)\n \t\tentry->tagged = 1;\n-\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled)) {\n+\tif (!reference_get_peeled_oid(the_repository, ref, &peeled)) {\n \t\tentry = packlist_find(&to_pack, &peeled);\n \t\tif (entry)\n \t\t\tentry->tagged = 1;\n@@ -3309,7 +3309,8 @@ static int add_ref_tag(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct object_id peeled;\n \n-\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled) && obj_is_packed(&peeled))\n+\tif (!reference_get_peeled_oid(the_repository, ref, &peeled) &&\n+\t    obj_is_packed(&peeled))\n \t\tadd_tag_chain(ref->oid);\n \treturn 0;\n }\n@@ -4532,7 +4533,7 @@ static int mark_bitmap_preferred_tip(const struct reference *ref, void *data UNU\n \tstruct object_id peeled;\n \tstruct object *object;\n \n-\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n+\tif (!reference_get_peeled_oid(the_repository, ref, &peeled))\n \t\tmaybe_peeled = &peeled;\n \n \tobject = parse_object_or_die(the_repository, maybe_peeled, ref->name);\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 893c105c81..873e21c35d 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -779,7 +779,7 @@ static int midx_snapshot_ref_one(const struct reference *ref, void *_data)\n \tconst struct object_id *maybe_peeled = ref->oid;\n \tstruct object_id peeled;\n \n-\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n+\tif (!reference_get_peeled_oid(the_repository, ref, &peeled))\n \t\tmaybe_peeled = &peeled;\n \n \tif (oidset_insert(&data->seen, maybe_peeled))\ndiff --git a/commit-graph.c b/commit-graph.c\nindex 0cfe16ad08..bfd8b187f0 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -1852,7 +1852,7 @@ static int add_ref_to_set(const struct reference *ref, void *cb_data)\n \tstruct object_id peeled;\n \tstruct refs_cb_data *data = (struct refs_cb_data *)cb_data;\n \n-\tif (!peel_iterated_oid(data->repo, ref->oid, &peeled))\n+\tif (!reference_get_peeled_oid(data->repo, ref, &peeled))\n \t\tmaybe_peeled = &peeled;\n \tif (odb_read_object_info(data->repo->objects, maybe_peeled, NULL) == OBJ_COMMIT)\n \t\toidset_insert(data->commits, maybe_peeled);\ndiff --git a/ls-refs.c b/ls-refs.c\nindex 64d0272369..8641281b86 100644\n--- a/ls-refs.c\n+++ b/ls-refs.c\n@@ -110,7 +110,7 @@ static int send_ref(const struct reference *ref, void *cb_data)\n \n \tif (data->peel && ref->oid) {\n \t\tstruct object_id peeled;\n-\t\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n+\t\tif (!reference_get_peeled_oid(the_repository, ref, &peeled))\n \t\t\tstrbuf_addf(&data->buf, \" peeled:%s\", oid_to_hex(&peeled));\n \t}\n \ndiff --git a/midx-write.c b/midx-write.c\nindex f4dd875747..23e61cb000 100644\n--- a/midx-write.c\n+++ b/midx-write.c\n@@ -709,7 +709,7 @@ static int add_ref_to_pending(const struct reference *ref, void *cb_data)\n \t\treturn 0;\n \t}\n \n-\tif (!peel_iterated_oid(revs->repo, ref->oid, &peeled))\n+\tif (!reference_get_peeled_oid(revs->repo, ref, &peeled))\n \t\tmaybe_peeled = &peeled;\n \n \tobject = parse_object_or_die(revs->repo, maybe_peeled, ref->name);\ndiff --git a/pseudo-merge.c b/pseudo-merge.c\nindex 0abd51b42c..a2d5bd85f9 100644\n--- a/pseudo-merge.c\n+++ b/pseudo-merge.c\n@@ -230,7 +230,7 @@ static int find_pseudo_merge_group_for_ref(const struct reference *ref, void *_d\n \tuint32_t i;\n \tint has_bitmap;\n \n-\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n+\tif (!reference_get_peeled_oid(the_repository, ref, &peeled))\n \t\tmaybe_peeled = &peeled;\n \n \tc = lookup_commit(the_repository, maybe_peeled);\ndiff --git a/refs.c b/refs.c\nindex 15ad0ef7a8..5002e56435 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2333,6 +2333,18 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct\n \treturn peel_object(r, base, peeled) ? -1 : 0;\n }\n \n+int reference_get_peeled_oid(struct repository *repo,\n+\t\t\t     const struct reference *ref,\n+\t\t\t     struct object_id *peeled_oid)\n+{\n+\tif (ref->peeled_oid) {\n+\t\toidcpy(peeled_oid, ref->peeled_oid);\n+\t\treturn 0;\n+\t}\n+\n+\treturn peel_object(repo, ref->oid, peeled_oid) ? -1 : 0;\n+}\n+\n int refs_update_symref(struct ref_store *refs, const char *ref,\n \t\t       const char *target, const char *logmsg)\n {\ndiff --git a/refs.h b/refs.h\nindex 4f0a685714..886ed2c0f4 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -371,10 +371,29 @@ struct reference {\n \t */\n \tconst struct object_id *oid;\n \n+\t/*\n+\t * An optional peeled object ID. This field _may_ be set for tags in\n+\t * case the peeled value is present in the backend. Please refer to\n+\t * `reference_get_peeled_oid()`.\n+\t */\n+\tconst struct object_id *peeled_oid;\n+\n \t/* A bitfield of `enum reference_status` flags. */\n \tunsigned flags;\n };\n \n+/*\n+ * Peel the tag to a non-tag commit. If present, this uses the peeled object ID\n+ * exposed by the reference backend. Otherwise, the object is peeled via the\n+ * object database, which is less efficient.\n+ *\n+ * Return `0` if the reference could be peeled, a negative error code\n+ * otherwise.\n+ */\n+int reference_get_peeled_oid(struct repository *repo,\n+\t\t\t     const struct reference *ref,\n+\t\t\t     struct object_id *peeled_oid);\n+\n /*\n  * The signature for the callback function for the for_each_*()\n  * functions below.  The memory pointed to by the `struct reference`\ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 711e07f832..1fefefd54e 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -963,6 +963,7 @@ static int next_record(struct packed_ref_iterator *iter)\n \t\t\titer->base.ref.flags &= ~REF_KNOWS_PEELED;\n \t\t} else {\n \t\t\titer->base.ref.flags |= REF_KNOWS_PEELED;\n+\t\t\titer->base.ref.peeled_oid = &iter->peeled;\n \t\t}\n \t} else {\n \t\toidclr(&iter->peeled, iter->repo->hash_algo);\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 1e047fddae..0468f62a7e 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -546,6 +546,7 @@ struct reftable_ref_iterator {\n \tstruct reftable_iterator iter;\n \tstruct reftable_ref_record ref;\n \tstruct object_id oid;\n+\tstruct object_id peeled_oid;\n \n \tchar *prefix;\n \tsize_t prefix_len;\n@@ -670,6 +671,8 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\tcase REFTABLE_REF_VAL2:\n \t\t\toidread(&iter->oid, iter->ref.value.val2.value,\n \t\t\t\trefs->base.repo->hash_algo);\n+\t\t\toidread(&iter->peeled_oid, iter->ref.value.val2.target_value,\n+\t\t\t\trefs->base.repo->hash_algo);\n \t\t\tbreak;\n \t\tcase REFTABLE_REF_SYMREF:\n \t\t\treferent = refs_resolve_ref_unsafe(&iter->refs->base,\n@@ -707,6 +710,8 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\titer->base.ref.name = iter->ref.refname;\n \t\titer->base.ref.target = referent;\n \t\titer->base.ref.oid = &iter->oid;\n+\t\tif (iter->ref.value_type == REFTABLE_REF_VAL2)\n+\t\t\titer->base.ref.peeled_oid = &iter->peeled_oid;\n \t\titer->base.ref.flags = flags;\n \n \t\tbreak;\n\n-- \n2.51.0.764.g787ff6f08a.dirty\n\n"},{"id":"528264","messageId":"20251008-b4-pks-ref-filter-skip-parsing-objects-v2-6-76e30d5c9542@pks.im","threadId":"64260","inReplyTo":"20251008-b4-pks-ref-filter-skip-parsing-objects-v2-0-76e30d5c9542@pks.im","subject":"[PATCH v2 06/14] upload-pack: convert to use `reference_get_peeled_oid()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-08T15:50:21Z","receivedAt":"2025-10-08T15:50:45Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `write_v0_ref()` callback is invoked from two callsites:\n\n  - Once via `send_ref()` which is a callback passed to\n    `for_each_namespaced_ref_1()` and `refs_head_ref_namespaced()`.\n\n  - Once manually to announce capabilities.\n\nWhen sending references to the client we also send the peeled value of\ntags. As we don't have a `struct reference` available in the second\ncase, we cannot easily peel by calling `reference_get_peeled_oid()`, but\nwe instead have to depend on on global state via `peel_iterated_oid()`.\n\nWe do have a reference available though in the first case, it's only the\nsecond case that keeps us from using `reference_get_peeled_oid()`. But\nthat second case only announces capabilities anyway, so we're not really\nhandling a reference at all here.\n\nAdapt that case to construct a reference manually and pass that to\n`write_v0_ref()`. Start to use `reference_get_peeled_oid()` now that we\nalways have a `struct reference` available.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n upload-pack.c | 22 +++++++++++++---------\n 1 file changed, 13 insertions(+), 9 deletions(-)\n\ndiff --git a/upload-pack.c b/upload-pack.c\nindex 0d563ae74e..2d2b70cbf2 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -1249,15 +1249,15 @@ static void format_session_id(struct strbuf *buf, struct upload_pack_data *d) {\n }\n \n static void write_v0_ref(struct upload_pack_data *data,\n-\t\t\tconst char *refname, const char *refname_nons,\n-\t\t\tconst struct object_id *oid)\n+\t\t\t const struct reference *ref,\n+\t\t\t const char *refname_nons)\n {\n \tstatic const char *capabilities = \"multi_ack thin-pack side-band\"\n \t\t\" side-band-64k ofs-delta shallow deepen-since deepen-not\"\n \t\t\" deepen-relative no-progress include-tag multi_ack_detailed\";\n \tstruct object_id peeled;\n \n-\tif (mark_our_ref(refname_nons, refname, oid, &data->hidden_refs))\n+\tif (mark_our_ref(refname_nons, ref->name, ref->oid, &data->hidden_refs))\n \t\treturn;\n \n \tif (capabilities) {\n@@ -1267,7 +1267,7 @@ static void write_v0_ref(struct upload_pack_data *data,\n \t\tformat_symref_info(&symref_info, &data->symref);\n \t\tformat_session_id(&session_id, data);\n \t\tpacket_fwrite_fmt(stdout, \"%s %s%c%s%s%s%s%s%s%s object-format=%s agent=%s\\n\",\n-\t\t\t     oid_to_hex(oid), refname_nons,\n+\t\t\t     oid_to_hex(ref->oid), refname_nons,\n \t\t\t     0, capabilities,\n \t\t\t     (data->allow_uor & ALLOW_TIP_SHA1) ?\n \t\t\t\t     \" allow-tip-sha1-in-want\" : \"\",\n@@ -1283,17 +1283,17 @@ static void write_v0_ref(struct upload_pack_data *data,\n \t\tstrbuf_release(&session_id);\n \t\tdata->sent_capabilities = 1;\n \t} else {\n-\t\tpacket_fwrite_fmt(stdout, \"%s %s\\n\", oid_to_hex(oid), refname_nons);\n+\t\tpacket_fwrite_fmt(stdout, \"%s %s\\n\", oid_to_hex(ref->oid), refname_nons);\n \t}\n \tcapabilities = NULL;\n-\tif (!peel_iterated_oid(the_repository, oid, &peeled))\n+\tif (!reference_get_peeled_oid(the_repository, ref, &peeled))\n \t\tpacket_fwrite_fmt(stdout, \"%s %s^{}\\n\", oid_to_hex(&peeled), refname_nons);\n \treturn;\n }\n \n static int send_ref(const struct reference *ref, void *cb_data)\n {\n-\twrite_v0_ref(cb_data, ref->name, strip_namespace(ref->name), ref->oid);\n+\twrite_v0_ref(cb_data, ref, strip_namespace(ref->name));\n \treturn 0;\n }\n \n@@ -1442,8 +1442,12 @@ void upload_pack(const int advertise_refs, const int stateless_rpc,\n \t\t\t\t\t send_ref, &data);\n \t\tfor_each_namespaced_ref_1(send_ref, &data);\n \t\tif (!data.sent_capabilities) {\n-\t\t\tconst char *refname = \"capabilities^{}\";\n-\t\t\twrite_v0_ref(&data, refname, refname, null_oid(the_hash_algo));\n+\t\t\tstruct reference ref = {\n+\t\t\t\t.name = \"capabilities^{}\",\n+\t\t\t\t.oid = null_oid(the_hash_algo),\n+\t\t\t};\n+\n+\t\t\twrite_v0_ref(&data, &ref, ref.name);\n \t\t}\n \t\t/*\n \t\t * fflush stdout before calling advertise_shallow_grafts because send_ref\n\n-- \n2.51.0.764.g787ff6f08a.dirty\n\n"},{"id":"528265","messageId":"20251008-b4-pks-ref-filter-skip-parsing-objects-v2-7-76e30d5c9542@pks.im","threadId":"64260","inReplyTo":"20251008-b4-pks-ref-filter-skip-parsing-objects-v2-0-76e30d5c9542@pks.im","subject":"[PATCH v2 07/14] ref-filter: propagate peeled object ID","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-08T15:50:22Z","receivedAt":"2025-10-08T15:50:48Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When queueing a reference in the \"ref-filter\" subsystem we end up\ncreating a new ref array item that contains the reference's info. One\nbit of info that we always discard though is the peeled object ID, and\nbecause of that we are forced to use `peel_iterated_oid()`.\n\nRefactor the code to propagate the peeled object ID via the ref array,\nif available. This allows us to manually peel tags without having to go\nthrough the object database.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/ls-remote.c  |  2 +-\n builtin/tag.c        |  2 +-\n builtin/verify-tag.c |  2 +-\n ref-filter.c         | 66 ++++++++++++++++++++++++++++++----------------------\n ref-filter.h         |  5 +++-\n 5 files changed, 45 insertions(+), 32 deletions(-)\n\ndiff --git a/builtin/ls-remote.c b/builtin/ls-remote.c\nindex df09000b30..fe77829557 100644\n--- a/builtin/ls-remote.c\n+++ b/builtin/ls-remote.c\n@@ -156,7 +156,7 @@ int cmd_ls_remote(int argc,\n \t\t\tcontinue;\n \t\tif (!tail_match(&pattern, ref->name))\n \t\t\tcontinue;\n-\t\titem = ref_array_push(&ref_array, ref->name, &ref->old_oid);\n+\t\titem = ref_array_push(&ref_array, ref->name, &ref->old_oid, NULL);\n \t\titem->symref = xstrdup_or_null(ref->symref);\n \t}\n \ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex f0665af3ac..01eba90c5c 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -153,7 +153,7 @@ static int verify_tag(const char *name, const char *ref UNUSED,\n \t\treturn -1;\n \n \tif (format->format)\n-\t\tpretty_print_ref(name, oid, format);\n+\t\tpretty_print_ref(name, oid, NULL, format);\n \n \treturn 0;\n }\ndiff --git a/builtin/verify-tag.c b/builtin/verify-tag.c\nindex cd6bc11095..558121eaa1 100644\n--- a/builtin/verify-tag.c\n+++ b/builtin/verify-tag.c\n@@ -67,7 +67,7 @@ int cmd_verify_tag(int argc,\n \t\t}\n \n \t\tif (format.format)\n-\t\t\tpretty_print_ref(name, &oid, &format);\n+\t\t\tpretty_print_ref(name, &oid, NULL, &format);\n \t}\n \treturn had_error;\n }\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 7740f35e93..b18a032e57 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -2578,8 +2578,15 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)\n \t * If it is a tag object, see if we use the peeled value. If we do,\n \t * grab the peeled OID.\n \t */\n-\tif (need_tagged && peel_iterated_oid(the_repository, &obj->oid, &oi_deref.oid))\n-\t\tdie(\"bad tag\");\n+\tif (need_tagged) {\n+\t\tif (!is_null_oid(&ref->peeled_oid)) {\n+\t\t\toidcpy(&oi_deref.oid, &ref->peeled_oid);\n+\t\t} else if (!peel_object(the_repository, &obj->oid, &oi_deref.oid)) {\n+\t\t\t/* We managed to peel the object ourselves. */\n+\t\t} else {\n+\t\t\tdie(\"bad tag\");\n+\t\t}\n+\t}\n \n \treturn get_object(ref, 1, &obj, &oi_deref, err);\n }\n@@ -2807,12 +2814,15 @@ static int match_points_at(struct oid_array *points_at,\n  * Callers can then fill in other struct members at their leisure.\n  */\n static struct ref_array_item *new_ref_array_item(const char *refname,\n-\t\t\t\t\t\t const struct object_id *oid)\n+\t\t\t\t\t\t const struct object_id *oid,\n+\t\t\t\t\t\t const struct object_id *peeled_oid)\n {\n \tstruct ref_array_item *ref;\n \n \tFLEX_ALLOC_STR(ref, refname, refname);\n \toidcpy(&ref->objectname, oid);\n+\tif (peeled_oid)\n+\t\toidcpy(&ref->peeled_oid, peeled_oid);\n \tref->rest = NULL;\n \n \treturn ref;\n@@ -2826,9 +2836,10 @@ static void ref_array_append(struct ref_array *array, struct ref_array_item *ref\n \n struct ref_array_item *ref_array_push(struct ref_array *array,\n \t\t\t\t      const char *refname,\n-\t\t\t\t      const struct object_id *oid)\n+\t\t\t\t      const struct object_id *oid,\n+\t\t\t\t      const struct object_id *peeled_oid)\n {\n-\tstruct ref_array_item *ref = new_ref_array_item(refname, oid);\n+\tstruct ref_array_item *ref = new_ref_array_item(refname, oid, peeled_oid);\n \tref_array_append(array, ref);\n \treturn ref;\n }\n@@ -2871,25 +2882,25 @@ static int filter_ref_kind(struct ref_filter *filter, const char *refname)\n \treturn ref_kind_from_refname(refname);\n }\n \n-static struct ref_array_item *apply_ref_filter(const char *refname, const char *referent, const struct object_id *oid,\n-\t\t\t    int flag, struct ref_filter *filter)\n+static struct ref_array_item *apply_ref_filter(const struct reference *ref,\n+\t\t\t\t\t       struct ref_filter *filter)\n {\n-\tstruct ref_array_item *ref;\n+\tstruct ref_array_item *item;\n \tstruct commit *commit = NULL;\n \tunsigned int kind;\n \n-\tif (flag & REF_BAD_NAME) {\n-\t\twarning(_(\"ignoring ref with broken name %s\"), refname);\n+\tif (ref->flags & REF_BAD_NAME) {\n+\t\twarning(_(\"ignoring ref with broken name %s\"), ref->name);\n \t\treturn NULL;\n \t}\n \n-\tif (flag & REF_ISBROKEN) {\n-\t\twarning(_(\"ignoring broken ref %s\"), refname);\n+\tif (ref->flags & REF_ISBROKEN) {\n+\t\twarning(_(\"ignoring broken ref %s\"), ref->name);\n \t\treturn NULL;\n \t}\n \n \t/* Obtain the current ref kind from filter_ref_kind() and ignore unwanted refs. */\n-\tkind = filter_ref_kind(filter, refname);\n+\tkind = filter_ref_kind(filter, ref->name);\n \n \t/*\n \t * Generally HEAD refs are printed with special description denoting a rebase,\n@@ -2902,13 +2913,13 @@ static struct ref_array_item *apply_ref_filter(const char *refname, const char *\n \telse if (!(kind & filter->kind))\n \t\treturn NULL;\n \n-\tif (!filter_pattern_match(filter, refname))\n+\tif (!filter_pattern_match(filter, ref->name))\n \t\treturn NULL;\n \n-\tif (filter_exclude_match(filter, refname))\n+\tif (filter_exclude_match(filter, ref->name))\n \t\treturn NULL;\n \n-\tif (filter->points_at.nr && !match_points_at(&filter->points_at, oid, refname))\n+\tif (filter->points_at.nr && !match_points_at(&filter->points_at, ref->oid, ref->name))\n \t\treturn NULL;\n \n \t/*\n@@ -2918,7 +2929,7 @@ static struct ref_array_item *apply_ref_filter(const char *refname, const char *\n \t */\n \tif (filter->reachable_from || filter->unreachable_from ||\n \t    filter->with_commit || filter->no_commit || filter->verbose) {\n-\t\tcommit = lookup_commit_reference_gently(the_repository, oid, 1);\n+\t\tcommit = lookup_commit_reference_gently(the_repository, ref->oid, 1);\n \t\tif (!commit)\n \t\t\treturn NULL;\n \t\t/* We perform the filtering for the '--contains' option... */\n@@ -2936,13 +2947,13 @@ static struct ref_array_item *apply_ref_filter(const char *refname, const char *\n \t * to do its job and the resulting list may yet to be pruned\n \t * by maxcount logic.\n \t */\n-\tref = new_ref_array_item(refname, oid);\n-\tref->commit = commit;\n-\tref->flag = flag;\n-\tref->kind = kind;\n-\tref->symref = xstrdup_or_null(referent);\n+\titem = new_ref_array_item(ref->name, ref->oid, ref->peeled_oid);\n+\titem->commit = commit;\n+\titem->flag = ref->flags;\n+\titem->kind = kind;\n+\titem->symref = xstrdup_or_null(ref->target);\n \n-\treturn ref;\n+\treturn item;\n }\n \n struct ref_filter_cbdata {\n@@ -2959,8 +2970,7 @@ static int filter_one(const struct reference *ref, void *cb_data)\n \tstruct ref_filter_cbdata *ref_cbdata = cb_data;\n \tstruct ref_array_item *item;\n \n-\titem = apply_ref_filter(ref->name, ref->target, ref->oid,\n-\t\t\t\tref->flags, ref_cbdata->filter);\n+\titem = apply_ref_filter(ref, ref_cbdata->filter);\n \tif (item)\n \t\tref_array_append(ref_cbdata->array, item);\n \n@@ -2997,8 +3007,7 @@ static int filter_and_format_one(const struct reference *ref, void *cb_data)\n \tstruct ref_array_item *item;\n \tstruct strbuf output = STRBUF_INIT, err = STRBUF_INIT;\n \n-\titem = apply_ref_filter(ref->name, ref->target, ref->oid,\n-\t\t\t\tref->flags, ref_cbdata->filter);\n+\titem = apply_ref_filter(ref, ref_cbdata->filter);\n \tif (!item)\n \t\treturn 0;\n \n@@ -3585,13 +3594,14 @@ void print_formatted_ref_array(struct ref_array *array, struct ref_format *forma\n }\n \n void pretty_print_ref(const char *name, const struct object_id *oid,\n+\t\t      const struct object_id *peeled_oid,\n \t\t      struct ref_format *format)\n {\n \tstruct ref_array_item *ref_item;\n \tstruct strbuf output = STRBUF_INIT;\n \tstruct strbuf err = STRBUF_INIT;\n \n-\tref_item = new_ref_array_item(name, oid);\n+\tref_item = new_ref_array_item(name, oid, peeled_oid);\n \tref_item->kind = ref_kind_from_refname(name);\n \tif (format_ref_array_item(ref_item, format, &output, &err))\n \t\tdie(\"%s\", err.buf);\ndiff --git a/ref-filter.h b/ref-filter.h\nindex 81f2c229a9..1126826201 100644\n--- a/ref-filter.h\n+++ b/ref-filter.h\n@@ -41,6 +41,7 @@ enum ref_sorting_order {\n \n struct ref_array_item {\n \tstruct object_id objectname;\n+\tstruct object_id peeled_oid;\n \tconst char *rest;\n \tint flag;\n \tunsigned int kind;\n@@ -185,6 +186,7 @@ void print_formatted_ref_array(struct ref_array *array, struct ref_format *forma\n  * name must be a fully qualified refname.\n  */\n void pretty_print_ref(const char *name, const struct object_id *oid,\n+\t\t      const struct object_id *peeled_oid,\n \t\t      struct ref_format *format);\n \n /*\n@@ -193,7 +195,8 @@ void pretty_print_ref(const char *name, const struct object_id *oid,\n  */\n struct ref_array_item *ref_array_push(struct ref_array *array,\n \t\t\t\t      const char *refname,\n-\t\t\t\t      const struct object_id *oid);\n+\t\t\t\t      const struct object_id *oid,\n+\t\t\t\t      const struct object_id *peeled_oid);\n \n /*\n  * If the provided format includes ahead-behind atoms, then compute the\n\n-- \n2.51.0.764.g787ff6f08a.dirty\n\n"},{"id":"528266","messageId":"20251008-b4-pks-ref-filter-skip-parsing-objects-v2-8-76e30d5c9542@pks.im","threadId":"64260","inReplyTo":"20251008-b4-pks-ref-filter-skip-parsing-objects-v2-0-76e30d5c9542@pks.im","subject":"[PATCH v2 08/14] builtin/show-ref: convert to use `reference_get_peeled_oid()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-08T15:50:23Z","receivedAt":"2025-10-08T15:50:51Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The git-show-ref(1) command has multiple different modes:\n\n  - It knows to show all references matching a pattern.\n\n  - It knows to list all references that are an exact match to whatever\n    the user has provided.\n\n  - It knows to check for reference existence.\n\nThe first two commands use mostly the same infrastructure to print the\nreferences via `show_one()`. But while the former mode uses a proper\niterator and thus has a `struct reference` available in its context, the\nlatter calls `refs_read_ref()` and thus doesn't. Consequently, we cannot\neasily use `reference_get_peeled_oid()` to print the peeled value.\n\nAdapt the code so that we manually construct a `struct reference` when\nverifying refs. We wouldn't ever have the peeled value available anyway\nas we're not using an iterator here, so we can simply plug in the values\nwe _do_ have.\n\nWith this change we now have a `struct reference` available at both\ncallsites of `show_one()` and can thus pass it, which allows us to use\n`reference_get_peeled_oid()` instead of `peel_iterated_oid()`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/show-ref.c | 32 +++++++++++++++++++-------------\n 1 file changed, 19 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex 4803b5e598..4d4984e4e0 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -31,31 +31,31 @@ struct show_one_options {\n };\n \n static void show_one(const struct show_one_options *opts,\n-\t\t     const char *refname, const struct object_id *oid)\n+\t\t     const struct reference *ref)\n {\n \tconst char *hex;\n \tstruct object_id peeled;\n \n-\tif (!odb_has_object(the_repository->objects, oid,\n+\tif (!odb_has_object(the_repository->objects, ref->oid,\n \t\t\t    HAS_OBJECT_RECHECK_PACKED | HAS_OBJECT_FETCH_PROMISOR))\n-\t\tdie(\"git show-ref: bad ref %s (%s)\", refname,\n-\t\t    oid_to_hex(oid));\n+\t\tdie(\"git show-ref: bad ref %s (%s)\", ref->name,\n+\t\t    oid_to_hex(ref->oid));\n \n \tif (opts->quiet)\n \t\treturn;\n \n-\thex = repo_find_unique_abbrev(the_repository, oid, opts->abbrev);\n+\thex = repo_find_unique_abbrev(the_repository, ref->oid, opts->abbrev);\n \tif (opts->hash_only)\n \t\tprintf(\"%s\\n\", hex);\n \telse\n-\t\tprintf(\"%s %s\\n\", hex, refname);\n+\t\tprintf(\"%s %s\\n\", hex, ref->name);\n \n \tif (!opts->deref_tags)\n \t\treturn;\n \n-\tif (!peel_iterated_oid(the_repository, oid, &peeled)) {\n+\tif (!reference_get_peeled_oid(the_repository, ref, &peeled)) {\n \t\thex = repo_find_unique_abbrev(the_repository, &peeled, opts->abbrev);\n-\t\tprintf(\"%s %s^{}\\n\", hex, refname);\n+\t\tprintf(\"%s %s^{}\\n\", hex, ref->name);\n \t}\n }\n \n@@ -93,7 +93,7 @@ static int show_ref(const struct reference *ref, void *cbdata)\n match:\n \tdata->found_match++;\n \n-\tshow_one(data->show_one_opts, ref->name, ref->oid);\n+\tshow_one(data->show_one_opts, ref);\n \n \treturn 0;\n }\n@@ -175,12 +175,18 @@ static int cmd_show_ref__verify(const struct show_one_options *show_one_opts,\n \n \t\tif ((starts_with(*refs, \"refs/\") || refname_is_safe(*refs)) &&\n \t\t    !refs_read_ref(get_main_ref_store(the_repository), *refs, &oid)) {\n-\t\t\tshow_one(show_one_opts, *refs, &oid);\n-\t\t}\n-\t\telse if (!show_one_opts->quiet)\n+\t\t\tstruct reference ref = {\n+\t\t\t\t.name = *refs,\n+\t\t\t\t.oid = &oid,\n+\t\t\t};\n+\n+\t\t\tshow_one(show_one_opts, &ref);\n+\t\t} else if (!show_one_opts->quiet) {\n \t\t\tdie(\"'%s' - not a valid ref\", *refs);\n-\t\telse\n+\t\t} else {\n \t\t\treturn 1;\n+\t\t}\n+\n \t\trefs++;\n \t}\n \n\n-- \n2.51.0.764.g787ff6f08a.dirty\n\n"},{"id":"528267","messageId":"20251008-b4-pks-ref-filter-skip-parsing-objects-v2-9-76e30d5c9542@pks.im","threadId":"64260","inReplyTo":"20251008-b4-pks-ref-filter-skip-parsing-objects-v2-0-76e30d5c9542@pks.im","subject":"[PATCH v2 09/14] refs: drop `current_ref_iter` hack","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-08T15:50:24Z","receivedAt":"2025-10-08T15:50:53Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In preceding commits we have refactored all callers of\n`peel_iterated_oid()` to instead use `reference_get_peeled_oid()`. This\nallows us to thus get rid of the former function.\n\nGetting rid of that function is nice, but even nicer is that this also\nallows us to get rid of the `current_ref_iter` hack. This global\nvariable tracked the currently-active ref iterator so that we can use it\nto peel an object ID. Now that the peeled object ID is propagated via\n`struct reference` though we don't have to depend on this hack anymore,\nwhich makes for a more robust and easier-to-understand infrastructure.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c               | 10 ----------\n refs/iterator.c      |  5 -----\n refs/refs-internal.h | 13 -------------\n 3 files changed, 28 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 5002e56435..b0ceba8bc3 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2323,16 +2323,6 @@ int refs_optimize(struct ref_store *refs, struct pack_refs_opts *opts)\n \treturn refs->be->optimize(refs, opts);\n }\n \n-int peel_iterated_oid(struct repository *r, const struct object_id *base, struct object_id *peeled)\n-{\n-\tif (current_ref_iter &&\n-\t    (current_ref_iter->ref.oid == base ||\n-\t     oideq(current_ref_iter->ref.oid, base)))\n-\t\treturn ref_iterator_peel(current_ref_iter, peeled);\n-\n-\treturn peel_object(r, base, peeled) ? -1 : 0;\n-}\n-\n int reference_get_peeled_oid(struct repository *repo,\n \t\t\t     const struct reference *ref,\n \t\t\t     struct object_id *peeled_oid)\ndiff --git a/refs/iterator.c b/refs/iterator.c\nindex fe5980e1b6..072c6aacdb 100644\n--- a/refs/iterator.c\n+++ b/refs/iterator.c\n@@ -458,15 +458,11 @@ struct ref_iterator *prefix_ref_iterator_begin(struct ref_iterator *iter0,\n \treturn ref_iterator;\n }\n \n-struct ref_iterator *current_ref_iter = NULL;\n-\n int do_for_each_ref_iterator(struct ref_iterator *iter,\n \t\t\t     each_ref_fn fn, void *cb_data)\n {\n \tint retval = 0, ok;\n-\tstruct ref_iterator *old_ref_iter = current_ref_iter;\n \n-\tcurrent_ref_iter = iter;\n \twhile ((ok = ref_iterator_advance(iter)) == ITER_OK) {\n \t\tretval = fn(&iter->ref, cb_data);\n \t\tif (retval)\n@@ -474,7 +470,6 @@ int do_for_each_ref_iterator(struct ref_iterator *iter,\n \t}\n \n out:\n-\tcurrent_ref_iter = old_ref_iter;\n \tif (ok == ITER_ERROR)\n \t\tretval = -1;\n \tref_iterator_free(iter);\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex ed749d1657..f4f845bbea 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -376,19 +376,6 @@ struct ref_iterator_vtable {\n \tref_iterator_release_fn *release;\n };\n \n-/*\n- * current_ref_iter is a performance hack: when iterating over\n- * references using the for_each_ref*() functions, current_ref_iter is\n- * set to the reference iterator before calling the callback function.\n- * If the callback function calls peel_ref(), then peel_ref() first\n- * checks whether the reference to be peeled is the one referred to by\n- * the iterator (it usually is) and if so, asks the iterator for the\n- * peeled version of the reference if it is available. This avoids a\n- * refname lookup in a common case. current_ref_iter is set to NULL\n- * when the iteration is over.\n- */\n-extern struct ref_iterator *current_ref_iter;\n-\n struct ref_store;\n \n /* refs backends */\n\n-- \n2.51.0.764.g787ff6f08a.dirty\n\n"},{"id":"528268","messageId":"20251008-b4-pks-ref-filter-skip-parsing-objects-v2-10-76e30d5c9542@pks.im","threadId":"64260","inReplyTo":"20251008-b4-pks-ref-filter-skip-parsing-objects-v2-0-76e30d5c9542@pks.im","subject":"[PATCH v2 10/14] refs: drop infrastructure to peel via iterators","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-08T15:50:25Z","receivedAt":"2025-10-08T15:50:56Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Now that the peeled object ID gets propagated via the `struct reference`\nthere is no need anymore to call into the reference iterator itself to\ndereference an object. Remove this infrastructure.\n\nMost of the changes are straight-forward deletions of code. There is one\nexception though in `refs/packed-backend.c::write_with_updates()`. Here\nwe stop peeling the iterator and instead just pass the peeled object ID\nof that iterator directly.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.h                  | 14 --------------\n refs/debug.c            | 11 -----------\n refs/files-backend.c    | 17 -----------------\n refs/iterator.c         | 36 ------------------------------------\n refs/packed-backend.c   | 24 +-----------------------\n refs/ref-cache.c        |  9 ---------\n refs/refs-internal.h    |  7 -------\n refs/reftable-backend.c | 24 ------------------------\n 8 files changed, 1 insertion(+), 141 deletions(-)\n\ndiff --git a/refs.h b/refs.h\nindex 886ed2c0f4..2dd7ac1a16 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -1289,10 +1289,6 @@ int repo_migrate_ref_storage_format(struct repository *repo,\n  * to the next entry, ref_iterator_advance() aborts the iteration,\n  * frees the ref_iterator, and returns ITER_ERROR.\n  *\n- * The reference currently being looked at can be peeled by calling\n- * ref_iterator_peel(). This function is often faster than peel_ref(),\n- * so it should be preferred when iterating over references.\n- *\n  * Putting it all together, a typical iteration looks like this:\n  *\n  *     int ok;\n@@ -1307,9 +1303,6 @@ int repo_migrate_ref_storage_format(struct repository *repo,\n  *             // Access information about the current reference:\n  *             if (!(iter->flags & REF_ISSYMREF))\n  *                     printf(\"%s is %s\\n\", iter->refname, oid_to_hex(iter->oid));\n- *\n- *             // If you need to peel the reference:\n- *             ref_iterator_peel(iter, &oid);\n  *     }\n  *\n  *     if (ok != ITER_DONE)\n@@ -1400,13 +1393,6 @@ enum ref_iterator_seek_flag {\n int ref_iterator_seek(struct ref_iterator *ref_iterator, const char *refname,\n \t\t      unsigned int flags);\n \n-/*\n- * If possible, peel the reference currently being viewed by the\n- * iterator. Return 0 on success.\n- */\n-int ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t      struct object_id *peeled);\n-\n /* Free the reference iterator and any associated resources. */\n void ref_iterator_free(struct ref_iterator *ref_iterator);\n \ndiff --git a/refs/debug.c b/refs/debug.c\nindex 7a26035617..162c24e5cc 100644\n--- a/refs/debug.c\n+++ b/refs/debug.c\n@@ -178,16 +178,6 @@ static int debug_ref_iterator_seek(struct ref_iterator *ref_iterator,\n \treturn res;\n }\n \n-static int debug_ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t\t\t   struct object_id *peeled)\n-{\n-\tstruct debug_ref_iterator *diter =\n-\t\t(struct debug_ref_iterator *)ref_iterator;\n-\tint res = diter->iter->vtable->peel(diter->iter, peeled);\n-\ttrace_printf_key(&trace_refs, \"iterator_peel: %s: %d\\n\", diter->iter->ref.name, res);\n-\treturn res;\n-}\n-\n static void debug_ref_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct debug_ref_iterator *diter =\n@@ -199,7 +189,6 @@ static void debug_ref_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable debug_ref_iterator_vtable = {\n \t.advance = debug_ref_iterator_advance,\n \t.seek = debug_ref_iterator_seek,\n-\t.peel = debug_ref_iterator_peel,\n \t.release = debug_ref_iterator_release,\n };\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex d34fbe55d6..a4cda57981 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -994,15 +994,6 @@ static int files_ref_iterator_seek(struct ref_iterator *ref_iterator,\n \treturn ref_iterator_seek(iter->iter0, refname, flags);\n }\n \n-static int files_ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t\t\t   struct object_id *peeled)\n-{\n-\tstruct files_ref_iterator *iter =\n-\t\t(struct files_ref_iterator *)ref_iterator;\n-\n-\treturn ref_iterator_peel(iter->iter0, peeled);\n-}\n-\n static void files_ref_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct files_ref_iterator *iter =\n@@ -1013,7 +1004,6 @@ static void files_ref_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable files_ref_iterator_vtable = {\n \t.advance = files_ref_iterator_advance,\n \t.seek = files_ref_iterator_seek,\n-\t.peel = files_ref_iterator_peel,\n \t.release = files_ref_iterator_release,\n };\n \n@@ -2389,12 +2379,6 @@ static int files_reflog_iterator_seek(struct ref_iterator *ref_iterator UNUSED,\n \tBUG(\"ref_iterator_seek() called for reflog_iterator\");\n }\n \n-static int files_reflog_iterator_peel(struct ref_iterator *ref_iterator UNUSED,\n-\t\t\t\t      struct object_id *peeled UNUSED)\n-{\n-\tBUG(\"ref_iterator_peel() called for reflog_iterator\");\n-}\n-\n static void files_reflog_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct files_reflog_iterator *iter =\n@@ -2405,7 +2389,6 @@ static void files_reflog_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable files_reflog_iterator_vtable = {\n \t.advance = files_reflog_iterator_advance,\n \t.seek = files_reflog_iterator_seek,\n-\t.peel = files_reflog_iterator_peel,\n \t.release = files_reflog_iterator_release,\n };\n \ndiff --git a/refs/iterator.c b/refs/iterator.c\nindex 072c6aacdb..d79aa5ec82 100644\n--- a/refs/iterator.c\n+++ b/refs/iterator.c\n@@ -21,12 +21,6 @@ int ref_iterator_seek(struct ref_iterator *ref_iterator, const char *refname,\n \treturn ref_iterator->vtable->seek(ref_iterator, refname, flags);\n }\n \n-int ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t      struct object_id *peeled)\n-{\n-\treturn ref_iterator->vtable->peel(ref_iterator, peeled);\n-}\n-\n void ref_iterator_free(struct ref_iterator *ref_iterator)\n {\n \tif (ref_iterator) {\n@@ -60,12 +54,6 @@ static int empty_ref_iterator_seek(struct ref_iterator *ref_iterator UNUSED,\n \treturn 0;\n }\n \n-static int empty_ref_iterator_peel(struct ref_iterator *ref_iterator UNUSED,\n-\t\t\t\t   struct object_id *peeled UNUSED)\n-{\n-\tBUG(\"peel called for empty iterator\");\n-}\n-\n static void empty_ref_iterator_release(struct ref_iterator *ref_iterator UNUSED)\n {\n }\n@@ -73,7 +61,6 @@ static void empty_ref_iterator_release(struct ref_iterator *ref_iterator UNUSED)\n static struct ref_iterator_vtable empty_ref_iterator_vtable = {\n \t.advance = empty_ref_iterator_advance,\n \t.seek = empty_ref_iterator_seek,\n-\t.peel = empty_ref_iterator_peel,\n \t.release = empty_ref_iterator_release,\n };\n \n@@ -240,18 +227,6 @@ static int merge_ref_iterator_seek(struct ref_iterator *ref_iterator,\n \treturn 0;\n }\n \n-static int merge_ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t\t\t   struct object_id *peeled)\n-{\n-\tstruct merge_ref_iterator *iter =\n-\t\t(struct merge_ref_iterator *)ref_iterator;\n-\n-\tif (!iter->current) {\n-\t\tBUG(\"peel called before advance for merge iterator\");\n-\t}\n-\treturn ref_iterator_peel(*iter->current, peeled);\n-}\n-\n static void merge_ref_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct merge_ref_iterator *iter =\n@@ -263,7 +238,6 @@ static void merge_ref_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable merge_ref_iterator_vtable = {\n \t.advance = merge_ref_iterator_advance,\n \t.seek = merge_ref_iterator_seek,\n-\t.peel = merge_ref_iterator_peel,\n \t.release = merge_ref_iterator_release,\n };\n \n@@ -412,15 +386,6 @@ static int prefix_ref_iterator_seek(struct ref_iterator *ref_iterator,\n \treturn ref_iterator_seek(iter->iter0, refname, flags);\n }\n \n-static int prefix_ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t\t\t    struct object_id *peeled)\n-{\n-\tstruct prefix_ref_iterator *iter =\n-\t\t(struct prefix_ref_iterator *)ref_iterator;\n-\n-\treturn ref_iterator_peel(iter->iter0, peeled);\n-}\n-\n static void prefix_ref_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct prefix_ref_iterator *iter =\n@@ -432,7 +397,6 @@ static void prefix_ref_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable prefix_ref_iterator_vtable = {\n \t.advance = prefix_ref_iterator_advance,\n \t.seek = prefix_ref_iterator_seek,\n-\t.peel = prefix_ref_iterator_peel,\n \t.release = prefix_ref_iterator_release,\n };\n \ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 1fefefd54e..6fa229edd0 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -1030,22 +1030,6 @@ static int packed_ref_iterator_seek(struct ref_iterator *ref_iterator,\n \treturn 0;\n }\n \n-static int packed_ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t\t\t   struct object_id *peeled)\n-{\n-\tstruct packed_ref_iterator *iter =\n-\t\t(struct packed_ref_iterator *)ref_iterator;\n-\n-\tif ((iter->base.ref.flags & REF_KNOWS_PEELED)) {\n-\t\toidcpy(peeled, &iter->peeled);\n-\t\treturn is_null_oid(&iter->peeled) ? -1 : 0;\n-\t} else if ((iter->base.ref.flags & (REF_ISBROKEN | REF_ISSYMREF))) {\n-\t\treturn -1;\n-\t} else {\n-\t\treturn peel_object(iter->repo, &iter->oid, peeled) ? -1 : 0;\n-\t}\n-}\n-\n static void packed_ref_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct packed_ref_iterator *iter =\n@@ -1059,7 +1043,6 @@ static void packed_ref_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable packed_ref_iterator_vtable = {\n \t.advance = packed_ref_iterator_advance,\n \t.seek = packed_ref_iterator_seek,\n-\t.peel = packed_ref_iterator_peel,\n \t.release = packed_ref_iterator_release,\n };\n \n@@ -1525,13 +1508,8 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re\n \n \t\tif (cmp < 0) {\n \t\t\t/* Pass the old reference through. */\n-\n-\t\t\tstruct object_id peeled;\n-\t\t\tint peel_error = ref_iterator_peel(iter, &peeled);\n-\n \t\t\tif (write_packed_entry(out, iter->ref.name,\n-\t\t\t\t\t       iter->ref.oid,\n-\t\t\t\t\t       peel_error ? NULL : &peeled))\n+\t\t\t\t\t       iter->ref.oid, iter->ref.peeled_oid))\n \t\t\t\tgoto write_error;\n \n \t\t\tif ((ok = ref_iterator_advance(iter)) != ITER_OK) {\ndiff --git a/refs/ref-cache.c b/refs/ref-cache.c\nindex 4726de430d..9d0cae10b5 100644\n--- a/refs/ref-cache.c\n+++ b/refs/ref-cache.c\n@@ -546,14 +546,6 @@ static int cache_ref_iterator_seek(struct ref_iterator *ref_iterator,\n \treturn 0;\n }\n \n-static int cache_ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t\t\t   struct object_id *peeled)\n-{\n-\tstruct cache_ref_iterator *iter =\n-\t\t(struct cache_ref_iterator *)ref_iterator;\n-\treturn peel_object(iter->repo, ref_iterator->ref.oid, peeled) ? -1 : 0;\n-}\n-\n static void cache_ref_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct cache_ref_iterator *iter =\n@@ -565,7 +557,6 @@ static void cache_ref_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable cache_ref_iterator_vtable = {\n \t.advance = cache_ref_iterator_advance,\n \t.seek = cache_ref_iterator_seek,\n-\t.peel = cache_ref_iterator_peel,\n \t.release = cache_ref_iterator_release,\n };\n \ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex f4f845bbea..4671517dad 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -357,12 +357,6 @@ typedef int ref_iterator_advance_fn(struct ref_iterator *ref_iterator);\n typedef int ref_iterator_seek_fn(struct ref_iterator *ref_iterator,\n \t\t\t\t const char *refname, unsigned int flags);\n \n-/*\n- * Peels the current ref, returning 0 for success or -1 for failure.\n- */\n-typedef int ref_iterator_peel_fn(struct ref_iterator *ref_iterator,\n-\t\t\t\t struct object_id *peeled);\n-\n /*\n  * Implementations of this function should free any resources specific\n  * to the derived class.\n@@ -372,7 +366,6 @@ typedef void ref_iterator_release_fn(struct ref_iterator *ref_iterator);\n struct ref_iterator_vtable {\n \tref_iterator_advance_fn *advance;\n \tref_iterator_seek_fn *seek;\n-\tref_iterator_peel_fn *peel;\n \tref_iterator_release_fn *release;\n };\n \ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 0468f62a7e..d9447f8fa7 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -743,21 +743,6 @@ static int reftable_ref_iterator_seek(struct ref_iterator *ref_iterator,\n \treturn iter->err;\n }\n \n-static int reftable_ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t\t\t      struct object_id *peeled)\n-{\n-\tstruct reftable_ref_iterator *iter =\n-\t\t(struct reftable_ref_iterator *)ref_iterator;\n-\n-\tif (iter->ref.value_type == REFTABLE_REF_VAL2) {\n-\t\toidread(peeled, iter->ref.value.val2.target_value,\n-\t\t\titer->refs->base.repo->hash_algo);\n-\t\treturn 0;\n-\t}\n-\n-\treturn -1;\n-}\n-\n static void reftable_ref_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct reftable_ref_iterator *iter =\n@@ -775,7 +760,6 @@ static void reftable_ref_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable reftable_ref_iterator_vtable = {\n \t.advance = reftable_ref_iterator_advance,\n \t.seek = reftable_ref_iterator_seek,\n-\t.peel = reftable_ref_iterator_peel,\n \t.release = reftable_ref_iterator_release,\n };\n \n@@ -2097,13 +2081,6 @@ static int reftable_reflog_iterator_seek(struct ref_iterator *ref_iterator UNUSE\n \treturn -1;\n }\n \n-static int reftable_reflog_iterator_peel(struct ref_iterator *ref_iterator UNUSED,\n-\t\t\t\t\t struct object_id *peeled UNUSED)\n-{\n-\tBUG(\"reftable reflog iterator cannot be peeled\");\n-\treturn -1;\n-}\n-\n static void reftable_reflog_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct reftable_reflog_iterator *iter =\n@@ -2116,7 +2093,6 @@ static void reftable_reflog_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable reftable_reflog_iterator_vtable = {\n \t.advance = reftable_reflog_iterator_advance,\n \t.seek = reftable_reflog_iterator_seek,\n-\t.peel = reftable_reflog_iterator_peel,\n \t.release = reftable_reflog_iterator_release,\n };\n \n\n-- \n2.51.0.764.g787ff6f08a.dirty\n\n"},{"id":"528269","messageId":"20251008-b4-pks-ref-filter-skip-parsing-objects-v2-11-76e30d5c9542@pks.im","threadId":"64260","inReplyTo":"20251008-b4-pks-ref-filter-skip-parsing-objects-v2-0-76e30d5c9542@pks.im","subject":"[PATCH v2 11/14] object: add flag to `peel_object()` to verify object type","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-08T15:50:26Z","receivedAt":"2025-10-08T15:50:59Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When peeling a tag to a non-tag object we repeatedly call\n`parse_object()` on the tagged object until we find the first object\nthat isn't a tag. While this feels sensible at first, there is a big\ncatch here: `parse_object()` doesn't actually verify the type of the\ntagged object.\n\nThe relevant code path here eventually ends up in `parse_tag_buffer()`.\nHere, we parse the various fields of the tag, including the \"type\". Once\nwe've figured out the type and the tagged object ID, we call one of the\n`lookup_${type}()` functions for whatever type we have found. There is\ntwo possible outcomes in the successful case:\n\n  1. The object is already part of our cached objects. In that case we\n     double-check whether the type we're trying to look up matches the\n     type that was cached.\n\n  2. The object is _not_ part of our cached objects. In that case, we\n     simply create a new object with the expected type, but we don't\n     parse that object.\n\nIn the first case we might notice type mismatches, but only in the case\nwhere our cache has the object with the correct type. In the second\ncase, we'll blindly assume that the type is correct and then go with it.\nWe'll only notice that the type might be wrong when we try to parse the\nobject at a later point.\n\nNow arguably, we could change `parse_tag_buffer()` to verify the tagged\nobject's type for us. But that would have the effect that such a tag\ncannot be parsed at all anymore, and we have a small bunch of tests for\nexactly this case that assert we still can open such tags. So this\nchange does not feel like something we can retroactively tighten, even\nthough one shouldn't ever hit such corrupted tags.\n\nInstead, add a new `flags` field to `peel_object()` that allows the\ncaller to opt in to strict object verification. This will be wired up at\na subset of callsites over the next few commits.\n\nNote that this change also inlines `deref_tag_noverify()`. There's only\nbeen two callsites of that function, the one we're changing and one in\nour test helpers. The latter callsite can trivially use `deref_tag()`\ninstead, so by inlining the function we avoid having to pass down the\nflag.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object.c                | 20 +++++++++++++++++---\n object.h                | 15 ++++++++++++++-\n ref-filter.c            |  2 +-\n refs.c                  |  2 +-\n refs/packed-backend.c   |  5 ++---\n refs/reftable-backend.c |  4 ++--\n t/helper/test-reach.c   |  2 +-\n tag.c                   | 12 ------------\n tag.h                   |  1 -\n 9 files changed, 38 insertions(+), 25 deletions(-)\n\ndiff --git a/object.c b/object.c\nindex 986114a6dba..e72b0ed4360 100644\n--- a/object.c\n+++ b/object.c\n@@ -209,11 +209,12 @@ struct object *lookup_object_by_type(struct repository *r,\n \n enum peel_status peel_object(struct repository *r,\n \t\t\t     const struct object_id *name,\n-\t\t\t     struct object_id *oid)\n+\t\t\t     struct object_id *oid,\n+\t\t\t     unsigned flags)\n {\n \tstruct object *o = lookup_unknown_object(r, name);\n \n-\tif (o->type == OBJ_NONE) {\n+\tif (o->type == OBJ_NONE || flags & PEEL_OBJECT_VERIFY_OBJECT_TYPE) {\n \t\tint type = odb_read_object_info(r->objects, name, NULL);\n \t\tif (type < 0 || !object_as_type(o, type, 0))\n \t\t\treturn PEEL_INVALID;\n@@ -222,7 +223,20 @@ enum peel_status peel_object(struct repository *r,\n \tif (o->type != OBJ_TAG)\n \t\treturn PEEL_NON_TAG;\n \n-\to = deref_tag_noverify(r, o);\n+\twhile (o && o->type == OBJ_TAG) {\n+\t\to = parse_object(r, &o->oid);\n+\t\tif (o && o->type == OBJ_TAG && ((struct tag *)o)->tagged) {\n+\t\t\to = ((struct tag *)o)->tagged;\n+\n+\t\t\tif (flags & PEEL_OBJECT_VERIFY_OBJECT_TYPE) {\n+\t\t\t\tint type = odb_read_object_info(r->objects, &o->oid, NULL);\n+\t\t\t\tif (type < 0 || !object_as_type(o, type, 0))\n+\t\t\t\t\treturn PEEL_INVALID;\n+\t\t\t}\n+\t\t} else {\n+\t\t\to = NULL;\n+\t\t}\n+\t}\n \tif (!o)\n \t\treturn PEEL_INVALID;\n \ndiff --git a/object.h b/object.h\nindex 8c3c1c46e1b..1499f63d507 100644\n--- a/object.h\n+++ b/object.h\n@@ -287,6 +287,17 @@ enum peel_status {\n \tPEEL_BROKEN = -4\n };\n \n+enum peel_object_flags {\n+\t/*\n+\t * Always verify the object type, even in the case where the looked-up\n+\t * object already has an object type. This can be useful when the\n+\t * stored object type may be invalid. One such case is when looking up\n+\t * objects via tags, where we blindly trust the object type declared by\n+\t * the tag.\n+\t */\n+\tPEEL_OBJECT_VERIFY_OBJECT_TYPE = (1 << 0),\n+};\n+\n /*\n  * Peel the named object; i.e., if the object is a tag, resolve the\n  * tag recursively until a non-tag is found.  If successful, store the\n@@ -295,7 +306,9 @@ enum peel_status {\n  * and leave oid unchanged.\n  */\n enum peel_status peel_object(struct repository *r,\n-\t\t\t     const struct object_id *name, struct object_id *oid);\n+\t\t\t     const struct object_id *name,\n+\t\t\t     struct object_id *oid,\n+\t\t\t     unsigned flags);\n \n struct object_list *object_list_insert(struct object *item,\n \t\t\t\t       struct object_list **list_p);\ndiff --git a/ref-filter.c b/ref-filter.c\nindex b18a032e575..72e5a221ff3 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -2581,7 +2581,7 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)\n \tif (need_tagged) {\n \t\tif (!is_null_oid(&ref->peeled_oid)) {\n \t\t\toidcpy(&oi_deref.oid, &ref->peeled_oid);\n-\t\t} else if (!peel_object(the_repository, &obj->oid, &oi_deref.oid)) {\n+\t\t} else if (!peel_object(the_repository, &oi.oid, &oi_deref.oid, 0)) {\n \t\t\t/* We managed to peel the object ourselves. */\n \t\t} else {\n \t\t\tdie(\"bad tag\");\ndiff --git a/refs.c b/refs.c\nindex b0ceba8bc38..40acaa3f42f 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2332,7 +2332,7 @@ int reference_get_peeled_oid(struct repository *repo,\n \t\treturn 0;\n \t}\n \n-\treturn peel_object(repo, ref->oid, peeled_oid) ? -1 : 0;\n+\treturn peel_object(repo, ref->oid, peeled_oid, 0) ? -1 : 0;\n }\n \n int refs_update_symref(struct ref_store *refs, const char *ref,\ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 6fa229edd0f..4752d3f3981 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -1527,9 +1527,8 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re\n \t\t\ti++;\n \t\t} else {\n \t\t\tstruct object_id peeled;\n-\t\t\tint peel_error = peel_object(refs->base.repo,\n-\t\t\t\t\t\t     &update->new_oid,\n-\t\t\t\t\t\t     &peeled);\n+\t\t\tint peel_error = peel_object(refs->base.repo, &update->new_oid,\n+\t\t\t\t\t\t     &peeled, 0);\n \n \t\t\tif (write_packed_entry(out, update->refname,\n \t\t\t\t\t       &update->new_oid,\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex d9447f8fa72..32ee2ce22a1 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1631,7 +1631,7 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n \t\t\tref.refname = (char *)u->refname;\n \t\t\tref.update_index = ts;\n \n-\t\t\tpeel_error = peel_object(arg->refs->base.repo, &u->new_oid, &peeled);\n+\t\t\tpeel_error = peel_object(arg->refs->base.repo, &u->new_oid, &peeled, 0);\n \t\t\tif (!peel_error) {\n \t\t\t\tref.value_type = REFTABLE_REF_VAL2;\n \t\t\t\tmemcpy(ref.value.val2.target_value, peeled.hash, GIT_MAX_RAWSZ);\n@@ -2496,7 +2496,7 @@ static int write_reflog_expiry_table(struct reftable_writer *writer, void *cb_da\n \t\tref.refname = (char *)arg->refname;\n \t\tref.update_index = ts;\n \n-\t\tif (!peel_object(arg->refs->base.repo, &arg->update_oid, &peeled)) {\n+\t\tif (!peel_object(arg->refs->base.repo, &arg->update_oid, &peeled, 0)) {\n \t\t\tref.value_type = REFTABLE_REF_VAL2;\n \t\t\tmemcpy(ref.value.val2.target_value, peeled.hash, GIT_MAX_RAWSZ);\n \t\t\tmemcpy(ref.value.val2.value, arg->update_oid.hash, GIT_MAX_RAWSZ);\ndiff --git a/t/helper/test-reach.c b/t/helper/test-reach.c\nindex 028ec003067..c58c93800f3 100644\n--- a/t/helper/test-reach.c\n+++ b/t/helper/test-reach.c\n@@ -63,7 +63,7 @@ int cmd__reach(int ac, const char **av)\n \t\t\tdie(\"failed to resolve %s\", buf.buf + 2);\n \n \t\torig = parse_object(r, &oid);\n-\t\tpeeled = deref_tag_noverify(the_repository, orig);\n+\t\tpeeled = deref_tag(the_repository, orig, NULL, 0);\n \n \t\tif (!peeled)\n \t\t\tdie(\"failed to load commit for input %s resulting in oid %s\",\ndiff --git a/tag.c b/tag.c\nindex 1d52686ee10..f5c232d2f1f 100644\n--- a/tag.c\n+++ b/tag.c\n@@ -94,18 +94,6 @@ struct object *deref_tag(struct repository *r, struct object *o, const char *war\n \treturn o;\n }\n \n-struct object *deref_tag_noverify(struct repository *r, struct object *o)\n-{\n-\twhile (o && o->type == OBJ_TAG) {\n-\t\to = parse_object(r, &o->oid);\n-\t\tif (o && o->type == OBJ_TAG && ((struct tag *)o)->tagged)\n-\t\t\to = ((struct tag *)o)->tagged;\n-\t\telse\n-\t\t\to = NULL;\n-\t}\n-\treturn o;\n-}\n-\n struct tag *lookup_tag(struct repository *r, const struct object_id *oid)\n {\n \tstruct object *obj = lookup_object(r, oid);\ndiff --git a/tag.h b/tag.h\nindex c49d7c19ad3..ef12a610372 100644\n--- a/tag.h\n+++ b/tag.h\n@@ -16,7 +16,6 @@ int parse_tag_buffer(struct repository *r, struct tag *item, const void *data, u\n int parse_tag(struct tag *item);\n void release_tag_memory(struct tag *t);\n struct object *deref_tag(struct repository *r, struct object *, const char *, int);\n-struct object *deref_tag_noverify(struct repository *r, struct object *);\n int gpg_verify_tag(const struct object_id *oid,\n \t\t   const char *name_to_report, unsigned flags);\n struct object_id *get_tagged_oid(struct tag *tag);\n\n-- \n2.51.0.764.g787ff6f08a.dirty\n\n"},{"id":"528271","messageId":"20251008-b4-pks-ref-filter-skip-parsing-objects-v2-12-76e30d5c9542@pks.im","threadId":"64260","inReplyTo":"20251008-b4-pks-ref-filter-skip-parsing-objects-v2-0-76e30d5c9542@pks.im","subject":"[PATCH v2 12/14] refs: don't store peeled object IDs for invalid tags","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-08T15:50:27Z","receivedAt":"2025-10-08T15:51:02Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Both the \"files\" and \"reftable\" backend store peeled object IDs for\nreferences that point to tags:\n\n  - The \"files\" backend stores the value when packing refs, where each\n    peeled object ID is prefixed with \"^\".\n\n  - The \"reftable\" backend stores the value whenever writing a new\n    reference that points to a tag via a special ref record type.\n\nBoth of these backends use `peel_object()` to find the peeled object ID.\nBut as explained in the preceding commit, that function does not detect\nthe case where the tag's tagged object and its claimed type mismatch.\n\nThe consequence of storing these bogus peeled object IDs is that we're\nless likely to detect such corruption in other parts of Git.\ngit-for-each-ref(1) for example does not notice anymore that the tag is\nbroken when using \"--format=%(*objectname)\" to dereference tags.\n\nOne could claim that this is good, because it still allows us to mostly\nuse the tag as intended. But the biggest problem here is that we now\nhave different behaviour for such a broken tag depending on whether or\nnot we have its peeled value in the refdb.\n\nFix the issue by verifying the object type when peeling the object. If\nthat verification fails we simply skip storing the peeled value in\neither of the reference formats.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs/packed-backend.c      |  2 +-\n refs/reftable-backend.c    |  3 ++-\n t/pack-refs-tests.sh       | 32 ++++++++++++++++++++++++++++++++\n t/t0610-reftable-basics.sh | 28 ++++++++++++++++++++++++++++\n 4 files changed, 63 insertions(+), 2 deletions(-)\n\ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 4752d3f398..1ab0c50393 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -1528,7 +1528,7 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re\n \t\t} else {\n \t\t\tstruct object_id peeled;\n \t\t\tint peel_error = peel_object(refs->base.repo, &update->new_oid,\n-\t\t\t\t\t\t     &peeled, 0);\n+\t\t\t\t\t\t     &peeled, PEEL_OBJECT_VERIFY_OBJECT_TYPE);\n \n \t\t\tif (write_packed_entry(out, update->refname,\n \t\t\t\t\t       &update->new_oid,\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 32ee2ce22a..0b7ec3ae15 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1631,7 +1631,8 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n \t\t\tref.refname = (char *)u->refname;\n \t\t\tref.update_index = ts;\n \n-\t\t\tpeel_error = peel_object(arg->refs->base.repo, &u->new_oid, &peeled, 0);\n+\t\t\tpeel_error = peel_object(arg->refs->base.repo, &u->new_oid, &peeled,\n+\t\t\t\t\t\t PEEL_OBJECT_VERIFY_OBJECT_TYPE);\n \t\t\tif (!peel_error) {\n \t\t\t\tref.value_type = REFTABLE_REF_VAL2;\n \t\t\t\tmemcpy(ref.value.val2.target_value, peeled.hash, GIT_MAX_RAWSZ);\ndiff --git a/t/pack-refs-tests.sh b/t/pack-refs-tests.sh\nindex 3dbcc01718..095823d915 100644\n--- a/t/pack-refs-tests.sh\n+++ b/t/pack-refs-tests.sh\n@@ -428,4 +428,36 @@ do\n \t'\n done\n \n+test_expect_success 'pack-refs does not store invalid peeled tag value' '\n+\ttest_when_finished rm -rf repo &&\n+\tgit init repo &&\n+\t(\n+\t\tcd repo &&\n+\t\tgit commit --allow-empty --message initial &&\n+\n+\t\techo garbage >blob-content &&\n+\t\tblob_id=$(git hash-object -w -t blob blob-content) &&\n+\n+\t\t# Write an invalid tag into the object database. The tag itself\n+\t\t# is well-formed, but the tagged object is a blob while we\n+\t\t# claim that it is a commit.\n+\t\tcat >tag-content <<-EOF &&\n+\t\tobject $blob_id\n+\t\ttype commit\n+\t\ttag bad-tag\n+\t\ttagger C O Mitter <committer@example.com> 1112354055 +0200\n+\n+\t\tannotated\n+\t\tEOF\n+\t\ttag_id=$(git hash-object -w -t tag tag-content) &&\n+\t\tgit update-ref refs/tags/bad-tag \"$tag_id\" &&\n+\n+\t\t# The packed-refs file should not contain the peeled object ID.\n+\t\t# If it did this would cause commands that use the peeled value\n+\t\t# to not notice this corrupted tag.\n+\t\tgit pack-refs --all &&\n+\t\ttest_grep ! \"^\\^\" .git/packed-refs\n+\t)\n+'\n+\n test_done\ndiff --git a/t/t0610-reftable-basics.sh b/t/t0610-reftable-basics.sh\nindex 3ea5d51532..6575528f21 100755\n--- a/t/t0610-reftable-basics.sh\n+++ b/t/t0610-reftable-basics.sh\n@@ -1135,4 +1135,32 @@ test_expect_success 'fetch: accessing FETCH_HEAD special ref works' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'writes do not persist peeled value for invalid tags' '\n+\ttest_when_finished rm -rf repo &&\n+\tgit init repo &&\n+\t(\n+\t\tcd repo &&\n+\t\tgit commit --allow-empty --message initial &&\n+\n+\t\t# We cannot easily verify that the peeled value is not stored\n+\t\t# in the tables. Instead, we test this indirectly: we create\n+\t\t# two tags that both point to the same object, but they claim\n+\t\t# different object types. If we parse both tags we notice that\n+\t\t# the parsed tagged object has a mismatch between the two tags\n+\t\t# and bail out.\n+\t\t#\n+\t\t# If we instead use the persisted peeled value we would not\n+\t\t# even parse the tags. As such, we would not notice the\n+\t\t# discrepancy either and thus listing these tags would succeed.\n+\t\tgit tag tag-1 -m \"tag 1\" &&\n+\t\tgit cat-file tag tag-1 >raw-tag &&\n+\t\tsed \"s/^type commit$/type blob/\" <raw-tag >broken-tag &&\n+\t\tbroken_tag_id=$(git hash-object -w -t tag broken-tag) &&\n+\t\tgit update-ref refs/tags/tag-2 $broken_tag_id &&\n+\n+\t\ttest_must_fail git for-each-ref --format=\"%(*objectname)\" refs/tags/ 2>err &&\n+\t\ttest_grep \"bad tag pointer\" err\n+\t)\n+'\n+\n test_done\n\n-- \n2.51.0.764.g787ff6f08a.dirty\n\n"},{"id":"528272","messageId":"20251008-b4-pks-ref-filter-skip-parsing-objects-v2-13-76e30d5c9542@pks.im","threadId":"64260","inReplyTo":"20251008-b4-pks-ref-filter-skip-parsing-objects-v2-0-76e30d5c9542@pks.im","subject":"[PATCH v2 13/14] ref-filter: detect broken tags when dereferencing them","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-08T15:50:28Z","receivedAt":"2025-10-08T15:51:05Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Users can ask git-for-each-ref(1) to peel tags and return information of\nthe tagged object by adding an asterisk to the format, like for example\n\"%(*$objectname)\". If so, git-for-each-ref(1) peels that object to the\nfirst non-tag object and then returns its values.\n\nAs mentioned in preceding commits, it can happen that the tagged object\ntype and the claimed object type differ, effectively resulting in a\ncorrupt tag. git-for-each-ref(1) would notice this mismatch, print an\nerror and then bail out when trying to peel the tag.\n\nBut we only notice this corruption in some very specific edge cases!\nWhile we have a test in \"t/for-each-ref-tests.sh\" that verifies the\nabove scenario, this test is specifically crafted to detect the issue at\nhand. Namely, we create two tags:\n\n  - One tag points to a specific object with the correct type.\n\n  - The other tag points to the *same* object with a different type.\n\nThe fact that both tags point to the same object is important here:\n`peel_object()` wouldn't notice the corruption if the tagged objects\nwere different.\n\nThe root cause is that `peel_object()` calls `lookup_${type}()`\neventually, where the type is the same type declared in the tag object.\nConsequently, when we have two tags pointing to the same object but with\ndifferent declared types we'll call two different lookup functions. The\nfirst lookup will store the object with an unverified type A, whereas\nthe second lookup will try to look up the object with a different\nunverified type B. And it is only now that we notice the discrepancy in\nobject types, even though type A could've already been the wrong type.\n\nFix the issue by verifying the object type in `populate_value()`. With\nthis change we'll also notice type mismatches when only dereferencing a\ntag once.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n ref-filter.c            | 3 ++-\n t/for-each-ref-tests.sh | 4 +++-\n 2 files changed, 5 insertions(+), 2 deletions(-)\n\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 72e5a221ff..72cf85c8c6 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -2581,7 +2581,8 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)\n \tif (need_tagged) {\n \t\tif (!is_null_oid(&ref->peeled_oid)) {\n \t\t\toidcpy(&oi_deref.oid, &ref->peeled_oid);\n-\t\t} else if (!peel_object(the_repository, &oi.oid, &oi_deref.oid, 0)) {\n+\t\t} else if (!peel_object(the_repository, &oi.oid, &oi_deref.oid,\n+\t\t\t\t\tPEEL_OBJECT_VERIFY_OBJECT_TYPE)) {\n \t\t\t/* We managed to peel the object ourselves. */\n \t\t} else {\n \t\t\tdie(\"bad tag\");\ndiff --git a/t/for-each-ref-tests.sh b/t/for-each-ref-tests.sh\nindex e3ad19298a..4593be5fd5 100644\n--- a/t/for-each-ref-tests.sh\n+++ b/t/for-each-ref-tests.sh\n@@ -1809,7 +1809,9 @@ test_expect_success \"${git_for_each_ref} reports broken tags\" '\n \tbad=$(git hash-object -w -t tag bad) &&\n \tgit update-ref refs/tags/broken-tag-bad $bad &&\n \ttest_must_fail ${git_for_each_ref} --format=\"%(*objectname)\" \\\n-\t\trefs/tags/broken-tag-*\n+\t\trefs/tags/broken-tag-* &&\n+\ttest_must_fail ${git_for_each_ref} --format=\"%(*objectname)\" \\\n+\t\trefs/tags/broken-tag-bad\n '\n \n test_expect_success 'set up tag with signature and no blank lines' '\n\n-- \n2.51.0.764.g787ff6f08a.dirty\n\n"},{"id":"528273","messageId":"20251008-b4-pks-ref-filter-skip-parsing-objects-v2-14-76e30d5c9542@pks.im","threadId":"64260","inReplyTo":"20251008-b4-pks-ref-filter-skip-parsing-objects-v2-0-76e30d5c9542@pks.im","subject":"[PATCH v2 14/14] ref-filter: parse objects on demand","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-08T15:50:29Z","receivedAt":"2025-10-08T15:51:08Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When formatting an arbitrary object we parse that object regardless of\nwhether or not we actually need any parsed data. In fact, many of the\natoms we have don't require any.\n\nRefactor the code so that we parse the data on demand when we see an\natom that wants to access the objects. This leads to a small speedup,\nfor example in the Chromium repository with around 40000 refs:\n\n    Benchmark 1: for-each-ref --format='%(raw)' (HEAD~)\n      Time (mean ± σ):     388.7 ms ±   1.1 ms    [User: 322.2 ms, System: 65.0 ms]\n      Range (min … max):   387.3 ms … 390.8 ms    10 runs\n\n    Benchmark 2: for-each-ref --format='%(raw)' (HEAD)\n      Time (mean ± σ):     344.7 ms ±   0.7 ms    [User: 287.8 ms, System: 55.1 ms]\n      Range (min … max):   343.9 ms … 345.7 ms    10 runs\n\n    Summary\n      for-each-ref --format='%(raw)' (HEAD) ran\n        1.13 ± 0.00 times faster than for-each-ref --format='%(raw)' (HEAD~)\n\nWith this change, we now spend ~90% of the time decompressing objects,\nwhich is almost as good as it gets regarding git-for-each-ref(1)'s own\ninfrastructure.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n ref-filter.c | 156 ++++++++++++++++++++++++++++++++++++++++++++---------------\n 1 file changed, 117 insertions(+), 39 deletions(-)\n\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 72cf85c8c6..537c7babac 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -91,6 +91,7 @@ static struct expand_data {\n \tstruct object_id delta_base_oid;\n \tvoid *content;\n \n+\tstruct object *maybe_object;\n \tstruct object_info info;\n } oi, oi_deref;\n \n@@ -1475,11 +1476,28 @@ static void grab_common_values(struct atom_value *val, int deref, struct expand_\n \t}\n }\n \n+static int get_or_parse_object(struct expand_data *data, const char *refname,\n+\t\t\t       struct object **object, struct strbuf *err, int *eaten)\n+{\n+\tif (!data->maybe_object) {\n+\t\tdata->maybe_object = parse_object_buffer(the_repository, &data->oid, data->type,\n+\t\t\t\t\t\t\t data->size, data->content, eaten);\n+\t\tif (!data->maybe_object)\n+\t\t\treturn strbuf_addf_ret(err, -1, _(\"parse_object_buffer failed on %s for %s\"),\n+\t\t\t\t\t       oid_to_hex(&data->oid), refname);\n+\t}\n+\n+\t*object = data->maybe_object;\n+\treturn 0;\n+}\n+\n /* See grab_values */\n-static void grab_tag_values(struct atom_value *val, int deref, struct object *obj)\n+static int grab_tag_values(struct atom_value *val, int deref,\n+\t\t\t   struct expand_data *data, const char *refname,\n+\t\t\t   struct strbuf *err, int *eaten)\n {\n-\tint i;\n-\tstruct tag *tag = (struct tag *) obj;\n+\tstruct tag *tag = NULL;\n+\tint i, ret;\n \n \tfor (i = 0; i < used_atom_cnt; i++) {\n \t\tconst char *name = used_atom[i].name;\n@@ -1487,6 +1505,17 @@ static void grab_tag_values(struct atom_value *val, int deref, struct object *ob\n \t\tstruct atom_value *v = &val[i];\n \t\tif (!!deref != (*name == '*'))\n \t\t\tcontinue;\n+\n+\t\tif (!tag) {\n+\t\t\tstruct object *object;\n+\n+\t\t\tret = get_or_parse_object(data, refname, &object, err, eaten);\n+\t\t\tif (ret < 0)\n+\t\t\t\treturn ret;\n+\n+\t\t\ttag = (struct tag *) object;\n+\t\t}\n+\n \t\tif (deref)\n \t\t\tname++;\n \t\tif (atom_type == ATOM_TAG)\n@@ -1496,22 +1525,38 @@ static void grab_tag_values(struct atom_value *val, int deref, struct object *ob\n \t\telse if (atom_type == ATOM_OBJECT && tag->tagged)\n \t\t\tv->s = xstrdup(oid_to_hex(&tag->tagged->oid));\n \t}\n+\n+\treturn 0;\n }\n \n /* See grab_values */\n-static void grab_commit_values(struct atom_value *val, int deref, struct object *obj)\n+static int grab_commit_values(struct atom_value *val, int deref,\n+\t\t\t      struct expand_data *data, const char *refname,\n+\t\t\t      struct strbuf *err, int *eaten)\n {\n-\tint i;\n-\tstruct commit *commit = (struct commit *) obj;\n+\tint i, ret;\n+\tstruct commit *commit = NULL;\n \n \tfor (i = 0; i < used_atom_cnt; i++) {\n \t\tconst char *name = used_atom[i].name;\n \t\tenum atom_type atom_type = used_atom[i].atom_type;\n \t\tstruct atom_value *v = &val[i];\n+\n \t\tif (!!deref != (*name == '*'))\n \t\t\tcontinue;\n \t\tif (deref)\n \t\t\tname++;\n+\n+\t\tif (!commit) {\n+\t\t\tstruct object *object;\n+\n+\t\t\tret = get_or_parse_object(data, refname, &object, err, eaten);\n+\t\t\tif (ret < 0)\n+\t\t\t\treturn ret;\n+\n+\t\t\tcommit = (struct commit *) object;\n+\t\t}\n+\n \t\tif (atom_type == ATOM_TREE &&\n \t\t    grab_oid(name, \"tree\", get_commit_tree_oid(commit), v, &used_atom[i]))\n \t\t\tcontinue;\n@@ -1531,6 +1576,8 @@ static void grab_commit_values(struct atom_value *val, int deref, struct object\n \t\t\tv->s = strbuf_detach(&s, NULL);\n \t\t}\n \t}\n+\n+\treturn 0;\n }\n \n static const char *find_wholine(const char *who, int wholen, const char *buf)\n@@ -1759,10 +1806,12 @@ static void grab_person(const char *who, struct atom_value *val, int deref, void\n \t}\n }\n \n-static void grab_signature(struct atom_value *val, int deref, struct object *obj)\n+static int grab_signature(struct atom_value *val, int deref,\n+\t\t\t  struct expand_data *data, const char *refname,\n+\t\t\t  struct strbuf *err, int *eaten)\n {\n-\tint i;\n-\tstruct commit *commit = (struct commit *) obj;\n+\tint i, ret;\n+\tstruct commit *commit = NULL;\n \tstruct signature_check sigc = { 0 };\n \tint signature_checked = 0;\n \n@@ -1790,6 +1839,16 @@ static void grab_signature(struct atom_value *val, int deref, struct object *obj\n \t\t\tcontinue;\n \n \t\tif (!signature_checked) {\n+\t\t\tif (!commit) {\n+\t\t\t\tstruct object *object;\n+\n+\t\t\t\tret = get_or_parse_object(data, refname, &object, err, eaten);\n+\t\t\t\tif (ret < 0)\n+\t\t\t\t\treturn ret;\n+\n+\t\t\t\tcommit = (struct commit *) object;\n+\t\t\t}\n+\n \t\t\tcheck_commit_signature(commit, &sigc);\n \t\t\tsignature_checked = 1;\n \t\t}\n@@ -1843,6 +1902,8 @@ static void grab_signature(struct atom_value *val, int deref, struct object *obj\n \n \tif (signature_checked)\n \t\tsignature_check_clear(&sigc);\n+\n+\treturn 0;\n }\n \n static void find_subpos(const char *buf,\n@@ -1920,9 +1981,8 @@ static void append_lines(struct strbuf *out, const char *buf, unsigned long size\n }\n \n static void grab_describe_values(struct atom_value *val, int deref,\n-\t\t\t\t struct object *obj)\n+\t\t\t\t struct expand_data *data)\n {\n-\tstruct commit *commit = (struct commit *)obj;\n \tint i;\n \n \tfor (i = 0; i < used_atom_cnt; i++) {\n@@ -1944,7 +2004,7 @@ static void grab_describe_values(struct atom_value *val, int deref,\n \t\tcmd.git_cmd = 1;\n \t\tstrvec_push(&cmd.args, \"describe\");\n \t\tstrvec_pushv(&cmd.args, atom->u.describe_args.v);\n-\t\tstrvec_push(&cmd.args, oid_to_hex(&commit->object.oid));\n+\t\tstrvec_push(&cmd.args, oid_to_hex(&data->oid));\n \t\tif (pipe_command(&cmd, NULL, 0, &out, 0, &err, 0) < 0) {\n \t\t\terror(_(\"failed to run 'describe'\"));\n \t\t\tv->s = xstrdup(\"\");\n@@ -2066,24 +2126,36 @@ static void fill_missing_values(struct atom_value *val)\n  * pointed at by the ref itself; otherwise it is the object the\n  * ref (which is a tag) refers to.\n  */\n-static void grab_values(struct atom_value *val, int deref, struct object *obj, struct expand_data *data)\n+static int grab_values(struct atom_value *val, int deref, struct expand_data *data,\n+\t\t       const char *refname, struct strbuf *err, int *eaten)\n {\n \tvoid *buf = data->content;\n+\tint ret;\n \n-\tswitch (obj->type) {\n+\tswitch (data->type) {\n \tcase OBJ_TAG:\n-\t\tgrab_tag_values(val, deref, obj);\n+\t\tret = grab_tag_values(val, deref, data, refname, err, eaten);\n+\t\tif (ret < 0)\n+\t\t\tgoto out;\n+\n \t\tgrab_sub_body_contents(val, deref, data);\n \t\tgrab_person(\"tagger\", val, deref, buf);\n-\t\tgrab_describe_values(val, deref, obj);\n+\t\tgrab_describe_values(val, deref, data);\n \t\tbreak;\n \tcase OBJ_COMMIT:\n-\t\tgrab_commit_values(val, deref, obj);\n+\t\tret = grab_commit_values(val, deref, data, refname, err, eaten);\n+\t\tif (ret < 0)\n+\t\t\tgoto out;\n+\n \t\tgrab_sub_body_contents(val, deref, data);\n \t\tgrab_person(\"author\", val, deref, buf);\n \t\tgrab_person(\"committer\", val, deref, buf);\n-\t\tgrab_signature(val, deref, obj);\n-\t\tgrab_describe_values(val, deref, obj);\n+\n+\t\tret = grab_signature(val, deref, data, refname, err, eaten);\n+\t\tif (ret < 0)\n+\t\t\tgoto out;\n+\n+\t\tgrab_describe_values(val, deref, data);\n \t\tbreak;\n \tcase OBJ_TREE:\n \t\t/* grab_tree_values(val, deref, obj, buf, sz); */\n@@ -2094,8 +2166,12 @@ static void grab_values(struct atom_value *val, int deref, struct object *obj, s\n \t\tgrab_sub_body_contents(val, deref, data);\n \t\tbreak;\n \tdefault:\n-\t\tdie(\"Eh?  Object of type %d?\", obj->type);\n+\t\tdie(\"Eh?  Object of type %d?\", data->type);\n \t}\n+\n+\tret = 0;\n+out:\n+\treturn ret;\n }\n \n static inline char *copy_advance(char *dst, const char *src)\n@@ -2292,38 +2368,41 @@ static const char *get_refname(struct used_atom *atom, struct ref_array_item *re\n \treturn show_ref(&atom->u.refname, ref->refname);\n }\n \n-static int get_object(struct ref_array_item *ref, int deref, struct object **obj,\n+static int get_object(struct ref_array_item *ref, int deref,\n \t\t      struct expand_data *oi, struct strbuf *err)\n {\n-\t/* parse_object_buffer() will set eaten to 0 if free() will be needed */\n-\tint eaten = 1;\n+\t/* parse_object_buffer() will set eaten to 1 if free() will be needed */\n+\tint eaten = 0;\n+\tint ret;\n+\n \tif (oi->info.contentp) {\n \t\t/* We need to know that to use parse_object_buffer properly */\n \t\toi->info.sizep = &oi->size;\n \t\toi->info.typep = &oi->type;\n \t}\n+\n \tif (odb_read_object_info_extended(the_repository->objects, &oi->oid, &oi->info,\n-\t\t\t\t\t  OBJECT_INFO_LOOKUP_REPLACE))\n-\t\treturn strbuf_addf_ret(err, -1, _(\"missing object %s for %s\"),\n-\t\t\t\t       oid_to_hex(&oi->oid), ref->refname);\n+\t\t\t\t\t  OBJECT_INFO_LOOKUP_REPLACE)) {\n+\t\tret = strbuf_addf_ret(err, -1, _(\"missing object %s for %s\"),\n+\t\t\t\t      oid_to_hex(&oi->oid), ref->refname);\n+\t\tgoto out;\n+\t}\n \tif (oi->info.disk_sizep && oi->disk_size < 0)\n \t\tBUG(\"Object size is less than zero.\");\n \n \tif (oi->info.contentp) {\n-\t\t*obj = parse_object_buffer(the_repository, &oi->oid, oi->type, oi->size, oi->content, &eaten);\n-\t\tif (!*obj) {\n-\t\t\tif (!eaten)\n-\t\t\t\tfree(oi->content);\n-\t\t\treturn strbuf_addf_ret(err, -1, _(\"parse_object_buffer failed on %s for %s\"),\n-\t\t\t\t\t       oid_to_hex(&oi->oid), ref->refname);\n-\t\t}\n-\t\tgrab_values(ref->value, deref, *obj, oi);\n+\t\tret = grab_values(ref->value, deref, oi, ref->refname, err, &eaten);\n+\t\tif (ret < 0)\n+\t\t\tgoto out;\n \t}\n \n \tgrab_common_values(ref->value, deref, oi);\n+\tret = 0;\n+\n+out:\n \tif (!eaten)\n \t\tfree(oi->content);\n-\treturn 0;\n+\treturn ret;\n }\n \n static void populate_worktree_map(struct hashmap *map, struct worktree **worktrees)\n@@ -2376,7 +2455,6 @@ static char *get_worktree_path(const struct ref_array_item *ref)\n  */\n static int populate_value(struct ref_array_item *ref, struct strbuf *err)\n {\n-\tstruct object *obj;\n \tint i;\n \tstruct object_info empty = OBJECT_INFO_INIT;\n \tint ahead_behind_atoms = 0;\n@@ -2564,14 +2642,14 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)\n \n \n \toi.oid = ref->objectname;\n-\tif (get_object(ref, 0, &obj, &oi, err))\n+\tif (get_object(ref, 0, &oi, err))\n \t\treturn -1;\n \n \t/*\n \t * If there is no atom that wants to know about tagged\n \t * object, we are done.\n \t */\n-\tif (!need_tagged || (obj->type != OBJ_TAG))\n+\tif (!need_tagged || (oi.type != OBJ_TAG))\n \t\treturn 0;\n \n \t/*\n@@ -2589,7 +2667,7 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)\n \t\t}\n \t}\n \n-\treturn get_object(ref, 1, &obj, &oi_deref, err);\n+\treturn get_object(ref, 1, &oi_deref, err);\n }\n \n /*\n\n-- \n2.51.0.764.g787ff6f08a.dirty\n\n"},{"id":"528274","messageId":"aOaIuji6EDFi-Yg5@ArchLinux","threadId":"64260","inReplyTo":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-1-916cc7c6886b@pks.im","subject":"Re: [PATCH 01/13] refs: introduce wrapper struct for `each_ref_fn`","fromName":"shejialuo","fromEmail":"shejialuo@gmail.com","sentAt":"2025-10-08T15:52:26Z","receivedAt":"2025-10-08T15:52:25Z","isPatch":true,"sender":{"key":"shejialuo@gmail.com","avatar":"https://avatars.githubusercontent.com/u/56911263?v=4"},"body":"On Tue, Oct 07, 2025 at 12:58:38PM +0200, Patrick Steinhardt wrote:\n> diff --git a/refs.h b/refs.h\n> index 4e6bd63aa86..2b24a3d9974 100644\n> --- a/refs.h\n> +++ b/refs.h\n> @@ -355,14 +355,32 @@ struct ref_transaction;\n>   */\n>  #define REF_BAD_NAME 0x08\n>  \n> +/* A reference passed to `for_each_ref()`-style callbacks. */\n> +struct reference {\n> +\t/* The fully-qualified name of the reference. */\n> +\tconst char *name;\n> +\n> +\t/* The target of a symbolic ref. `NULL` for direct references. */\n> +\tconst char *target;\n> +\n\nI have a question here, why do we name this `target` instead of\n`referent`? Oh, I somehow know your motivation as we already create a\nstructure `struct reference`, it is redudant that we use `referent` as\nthe name.\n\n> +\t/*\n> +\t * The object ID of a reference. Either the direct object ID or the\n> +\t * resolved object ID in the case of a symbolic ref. May be the zero\n> +\t * object ID in case the symbolic ref cannot be resolved.\n> +\t */\n> +\tconst struct object_id *oid;\n> +\n> +\t/* A bitfield of `REF_` flags. */\n> +\tint flags;\n> +};\n> +\n\nThanks,\nJialuo\n"},{"id":"528279","messageId":"aOaRDhwZ0YkPdjkd@ArchLinux","threadId":"64260","inReplyTo":"20251008-b4-pks-ref-filter-skip-parsing-objects-v2-12-76e30d5c9542@pks.im","subject":"Re: [PATCH v2 12/14] refs: don't store peeled object IDs for invalid tags","fromName":"shejialuo","fromEmail":"shejialuo@gmail.com","sentAt":"2025-10-08T16:27:58Z","receivedAt":"2025-10-08T16:27:56Z","isPatch":true,"sender":{"key":"shejialuo@gmail.com","avatar":"https://avatars.githubusercontent.com/u/56911263?v=4"},"body":"On Wed, Oct 08, 2025 at 05:50:27PM +0200, Patrick Steinhardt wrote:\n> Both the \"files\" and \"reftable\" backend store peeled object IDs for\n> references that point to tags:\n> \n>   - The \"files\" backend stores the value when packing refs, where each\n>     peeled object ID is prefixed with \"^\".\n> \n>   - The \"reftable\" backend stores the value whenever writing a new\n>     reference that points to a tag via a special ref record type.\n> \n> Both of these backends use `peel_object()` to find the peeled object ID.\n> But as explained in the preceding commit, that function does not detect\n> the case where the tag's tagged object and its claimed type mismatch.\n> \n> The consequence of storing these bogus peeled object IDs is that we're\n> less likely to detect such corruption in other parts of Git.\n> git-for-each-ref(1) for example does not notice anymore that the tag is\n> broken when using \"--format=%(*objectname)\" to dereference tags.\n> \n> One could claim that this is good, because it still allows us to mostly\n> use the tag as intended. But the biggest problem here is that we now\n> have different behaviour for such a broken tag depending on whether or\n> not we have its peeled value in the refdb.\n> \n> Fix the issue by verifying the object type when peeling the object. If\n> that verification fails we simply skip storing the peeled value in\n> either of the reference formats.\n> \n\nI have a design question here: should we just report an error to the\nuser or just die instead of skipping storing the peeled value? If the\nannotated tag is corrupted in the first place, it means the refdb is\nalso corrupted. And \"git-fsck(1)\" would definitely report an error to\nthe user. But here we just ignore the problem and give an illusion that\neverything is fine.\n\nThanks,\nJialuo\n"},{"id":"528349","messageId":"aOdGsqgyy5K6Lynu@pks.im","threadId":"64260","inReplyTo":"aOaRDhwZ0YkPdjkd@ArchLinux","subject":"Re: [PATCH v2 12/14] refs: don't store peeled object IDs for invalid tags","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-09T05:22:58Z","receivedAt":"2025-10-09T05:23:05Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Oct 09, 2025 at 12:27:58AM +0800, shejialuo wrote:\n> On Wed, Oct 08, 2025 at 05:50:27PM +0200, Patrick Steinhardt wrote:\n> > Both the \"files\" and \"reftable\" backend store peeled object IDs for\n> > references that point to tags:\n> > \n> >   - The \"files\" backend stores the value when packing refs, where each\n> >     peeled object ID is prefixed with \"^\".\n> > \n> >   - The \"reftable\" backend stores the value whenever writing a new\n> >     reference that points to a tag via a special ref record type.\n> > \n> > Both of these backends use `peel_object()` to find the peeled object ID.\n> > But as explained in the preceding commit, that function does not detect\n> > the case where the tag's tagged object and its claimed type mismatch.\n> > \n> > The consequence of storing these bogus peeled object IDs is that we're\n> > less likely to detect such corruption in other parts of Git.\n> > git-for-each-ref(1) for example does not notice anymore that the tag is\n> > broken when using \"--format=%(*objectname)\" to dereference tags.\n> > \n> > One could claim that this is good, because it still allows us to mostly\n> > use the tag as intended. But the biggest problem here is that we now\n> > have different behaviour for such a broken tag depending on whether or\n> > not we have its peeled value in the refdb.\n> > \n> > Fix the issue by verifying the object type when peeling the object. If\n> > that verification fails we simply skip storing the peeled value in\n> > either of the reference formats.\n> > \n> \n> I have a design question here: should we just report an error to the\n> user or just die instead of skipping storing the peeled value? If the\n> annotated tag is corrupted in the first place, it means the refdb is\n> also corrupted. And \"git-fsck(1)\" would definitely report an error to\n> the user. But here we just ignore the problem and give an illusion that\n> everything is fine.\n\nThe question is whether the user can do anything about it. The tag may\nexist due to whatever reason, and it may not be prunable from the repo's\nreferences.\n\nTools like git-fsck(1) should definitely complain about this, and they\nin fact already do:\n\n    $ git fsck\n    Checking ref database: 100% (1/1), done.\n    error: object d10476e1da82e779f64cfa12bd655b579c3fddbe is a commit, not a blob\n    error: bad tag pointer to d10476e1da82e779f64cfa12bd655b579c3fddbe in ef5b01be3c1ad24fae2181040ced5776456a197a\n    error: ef5b01be3c1ad24fae2181040ced5776456a197a: object could not be parsed: .git/objects/ef/5b01be3c1ad24fae2181040ced5776456a197a\n    Checking object directories: 100% (256/256), done.\n    error: object d10476e1da82e779f64cfa12bd655b579c3fddbe is a commit, not a blob\n    error: bad tag pointer to d10476e1da82e779f64cfa12bd655b579c3fddbe in ef5b01be3c1ad24fae2181040ced5776456a197a\n    error: refs/tags/tag-2: invalid sha1 pointer ef5b01be3c1ad24fae2181040ced5776456a197a\n\nBut for operations like optimizing references it is not as clean-cut\nfrom my perspective. We definitely don't want to error out, as it would\nmean that the user cannot have their reference optimized as long as such\na broken reference exist. And other operations should make sure that\nthey don't return invalid data in face of such a corrupted repository,\ntoo.\n\nWe may want to add a warning in such cases though? I'd like to have some\nmore opinions on this.\n\nPatrick\n"},{"id":"528354","messageId":"20251009053825.GB1614343@coredump.intra.peff.net","threadId":"64260","inReplyTo":"20251008-b4-pks-ref-filter-skip-parsing-objects-v2-0-76e30d5c9542@pks.im","subject":"Re: [PATCH v2 00/14] refs: improvements and fixes for peeling tags","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-10-09T05:38:25Z","receivedAt":"2025-10-09T05:38:27Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Oct 08, 2025 at 05:50:15PM +0200, Patrick Steinhardt wrote:\n\n>   - Patches 1 to 8 refactor our codebase so that we don't have the\n>     `peel_iterated_object()` hack anymore. I just found it hard to\n>     follow and thought it shouldn't be too hard to get rid of it.\n\nI'm really happy to see this hack go away. I've wanted to fix it for\nages, but didn't want to bite the bullet on changing all of the\neach_ref_fn callbacks. The approach you used here to pass through a\nstruct looks good to me.\n\nI do have one minor complaint, though: the name of that struct. I have a\nfeeling that the name \"struct reference\" may cause confusion down the\nroad because it's so generic, and because \"references\" and \"refs\" are so\ncommon in the code. From the names, when would I know when to use\n\"struct reference\" and when \"struct ref\"?\n\nCould we give it a name that ties it to the iteration interface?\nSomething like iterated_ref, each_ref_data, etc?\n\nI know this is minor (and will be annoying to adjust your series), but\nI'd rather raise the point now than realize later that it's confusing\nand try to change it then.\n\n-Peff\n"},{"id":"528360","messageId":"aOdQTcH_ik3si29C@pks.im","threadId":"64260","inReplyTo":"aOaIuji6EDFi-Yg5@ArchLinux","subject":"Re: [PATCH 01/13] refs: introduce wrapper struct for `each_ref_fn`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-09T06:03:57Z","receivedAt":"2025-10-09T06:04:04Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Oct 08, 2025 at 11:52:26PM +0800, shejialuo wrote:\n> On Tue, Oct 07, 2025 at 12:58:38PM +0200, Patrick Steinhardt wrote:\n> > diff --git a/refs.h b/refs.h\n> > index 4e6bd63aa86..2b24a3d9974 100644\n> > --- a/refs.h\n> > +++ b/refs.h\n> > @@ -355,14 +355,32 @@ struct ref_transaction;\n> >   */\n> >  #define REF_BAD_NAME 0x08\n> >  \n> > +/* A reference passed to `for_each_ref()`-style callbacks. */\n> > +struct reference {\n> > +\t/* The fully-qualified name of the reference. */\n> > +\tconst char *name;\n> > +\n> > +\t/* The target of a symbolic ref. `NULL` for direct references. */\n> > +\tconst char *target;\n> > +\n> \n> I have a question here, why do we name this `target` instead of\n> `referent`? Oh, I somehow know your motivation as we already create a\n> structure `struct reference`, it is redudant that we use `referent` as\n> the name.\n\nI don't really have a strong reason to be honest, I think both \"target\"\nand \"referent\" would be fine.\n\nPatrick\n"},{"id":"528361","messageId":"aOdRsR-k77uTWJRb@pks.im","threadId":"64260","inReplyTo":"20251009053825.GB1614343@coredump.intra.peff.net","subject":"Re: [PATCH v2 00/14] refs: improvements and fixes for peeling tags","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-09T06:09:53Z","receivedAt":"2025-10-09T06:10:01Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Oct 09, 2025 at 01:38:25AM -0400, Jeff King wrote:\n> On Wed, Oct 08, 2025 at 05:50:15PM +0200, Patrick Steinhardt wrote:\n> \n> >   - Patches 1 to 8 refactor our codebase so that we don't have the\n> >     `peel_iterated_object()` hack anymore. I just found it hard to\n> >     follow and thought it shouldn't be too hard to get rid of it.\n> \n> I'm really happy to see this hack go away. I've wanted to fix it for\n> ages, but didn't want to bite the bullet on changing all of the\n> each_ref_fn callbacks. The approach you used here to pass through a\n> struct looks good to me.\n> \n> I do have one minor complaint, though: the name of that struct. I have a\n> feeling that the name \"struct reference\" may cause confusion down the\n> road because it's so generic, and because \"references\" and \"refs\" are so\n> common in the code. From the names, when would I know when to use\n> \"struct reference\" and when \"struct ref\"?\n> \n> Could we give it a name that ties it to the iteration interface?\n> Something like iterated_ref, each_ref_data, etc?\n> \n> I know this is minor (and will be annoying to adjust your series), but\n> I'd rather raise the point now than realize later that it's confusing\n> and try to change it then.\n\nIt is puzzling indeed. I would claim that in this case it is not `struct\nreference` that is misnamed: what it contains is as close as you get to\na representation of a reference. It's rather `struct ref` that is\nmisnamed, as it carries a lot of data that is only valid in the context\nof a remote.\n\nAnother approach could thus be to rename `struct ref` to `struct\nremote_ref`, which I would claim would be a clear win for better semantics.\nIt's used in lots of places though, which is a valid counter argument.\n\nWDYT?\n\nPatrick\n"},{"id":"528365","messageId":"20251009063956.GA1622884@coredump.intra.peff.net","threadId":"64260","inReplyTo":"aOdRsR-k77uTWJRb@pks.im","subject":"Re: [PATCH v2 00/14] refs: improvements and fixes for peeling tags","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-10-09T06:39:56Z","receivedAt":"2025-10-09T06:39:58Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Oct 09, 2025 at 08:09:53AM +0200, Patrick Steinhardt wrote:\n\n> > I do have one minor complaint, though: the name of that struct. I have a\n> > feeling that the name \"struct reference\" may cause confusion down the\n> > road because it's so generic, and because \"references\" and \"refs\" are so\n> > common in the code. From the names, when would I know when to use\n> > \"struct reference\" and when \"struct ref\"?\n> > \n> > Could we give it a name that ties it to the iteration interface?\n> > Something like iterated_ref, each_ref_data, etc?\n> > \n> > I know this is minor (and will be annoying to adjust your series), but\n> > I'd rather raise the point now than realize later that it's confusing\n> > and try to change it then.\n> \n> It is puzzling indeed. I would claim that in this case it is not `struct\n> reference` that is misnamed: what it contains is as close as you get to\n> a representation of a reference. It's rather `struct ref` that is\n> misnamed, as it carries a lot of data that is only valid in the context\n> of a remote.\n> \n> Another approach could thus be to rename `struct ref` to `struct\n> remote_ref`, which I would claim would be a clear win for better semantics.\n> It's used in lots of places though, which is a valid counter argument.\n\nI am not so much arguing that \"struct reference\" is misnamed, as that it\nis sufficiently generic that people will reach for it when it is not the\nappropriate tool. It is for passing the ref data to the iterator\ncallback, but it probably doesn't make sense in other contexts. Would we\never expect anybody to declare their own \"struct reference\" in a local\nfunction? I don't think so.\n\nAnd yes, \"struct ref\" suffers somewhat from the same problem. It is\nmostly about using refs in one specific space, but the name does not\nreally help clarify that. I wouldn't mind seeing that improved, but yes,\nit would be a noisy patch. I don't know if remote_ref is the right name,\nthough (the \"peer_ref\" links mean we store both local and remote refs in\nit, IIRC).\n\n-Peff\n"},{"id":"528366","messageId":"aOdjM8F6WvTEBIo_@pks.im","threadId":"64260","inReplyTo":"20251009063956.GA1622884@coredump.intra.peff.net","subject":"Re: [PATCH v2 00/14] refs: improvements and fixes for peeling tags","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-09T07:24:35Z","receivedAt":"2025-10-09T07:24:43Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Oct 09, 2025 at 02:39:56AM -0400, Jeff King wrote:\n> On Thu, Oct 09, 2025 at 08:09:53AM +0200, Patrick Steinhardt wrote:\n> \n> > > I do have one minor complaint, though: the name of that struct. I have a\n> > > feeling that the name \"struct reference\" may cause confusion down the\n> > > road because it's so generic, and because \"references\" and \"refs\" are so\n> > > common in the code. From the names, when would I know when to use\n> > > \"struct reference\" and when \"struct ref\"?\n> > > \n> > > Could we give it a name that ties it to the iteration interface?\n> > > Something like iterated_ref, each_ref_data, etc?\n> > > \n> > > I know this is minor (and will be annoying to adjust your series), but\n> > > I'd rather raise the point now than realize later that it's confusing\n> > > and try to change it then.\n> > \n> > It is puzzling indeed. I would claim that in this case it is not `struct\n> > reference` that is misnamed: what it contains is as close as you get to\n> > a representation of a reference. It's rather `struct ref` that is\n> > misnamed, as it carries a lot of data that is only valid in the context\n> > of a remote.\n> > \n> > Another approach could thus be to rename `struct ref` to `struct\n> > remote_ref`, which I would claim would be a clear win for better semantics.\n> > It's used in lots of places though, which is a valid counter argument.\n> \n> I am not so much arguing that \"struct reference\" is misnamed, as that it\n> is sufficiently generic that people will reach for it when it is not the\n> appropriate tool. It is for passing the ref data to the iterator\n> callback, but it probably doesn't make sense in other contexts. Would we\n> ever expect anybody to declare their own \"struct reference\" in a local\n> function? I don't think so.\n\nHm. The thing is: if `struct reference` is established in our code base,\nand if it is a simple representation of a reference, then I think it\nmight even be a good thing to having it. I could for example see that we\ngain more interfaces over time that use it.\n\nFor example, functions like `refs_read_ref()` could totally be adapted\nto use the same struct, and I would claim that this is a good thing.\nWe'd basically have a single central structure that allows us to get a\nreference out of the \"refs\" subsystem with metadata.\n\nThis would for example allow us to make the peeling infrastructure\navailable in contexts where we don't use an iterator, which is something\nthat we cannot do right now. In the end, what this would enable is to\nhave higher-level interfaces for references.\n\nSo yes, `struct reference` is rather generic. But if it's the ref system\nowning it I think it's sensible, because references are basically what\nthis subsystem is all about. This is somewhat equivalent to `struct\nobject`: the struct name is generic, but it's the basic building block\nfor our object subsystem.\n\n> And yes, \"struct ref\" suffers somewhat from the same problem. It is\n> mostly about using refs in one specific space, but the name does not\n> really help clarify that. I wouldn't mind seeing that improved, but yes,\n> it would be a noisy patch. I don't know if remote_ref is the right name,\n> though (the \"peer_ref\" links mean we store both local and remote refs in\n> it, IIRC).\n\nIf we decide to do it it should definitely be a standalone patch (or\npatch series). I'm also open for different naming suggestions, but for\nnow this is the best I could come up with.\n\nThanks!\n\nPatrick\n"},{"id":"528379","messageId":"87ecrcbd0s.fsf@iotcl.com","threadId":"64260","inReplyTo":"20251009063956.GA1622884@coredump.intra.peff.net","subject":"Re: [PATCH v2 00/14] refs: improvements and fixes for peeling tags","fromName":"Toon Claes","fromEmail":"toon@iotcl.com","sentAt":"2025-10-09T10:11:31Z","receivedAt":"2025-10-09T10:11:43Z","isPatch":true,"sender":{"key":"toon@iotcl.com","avatar":"https://avatars.githubusercontent.com/u/121621?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n>> > I do have one minor complaint, though: the name of that struct. I have a\n>> > feeling that the name \"struct reference\" may cause confusion down the\n>> > road because it's so generic, and because \"references\" and \"refs\" are so\n>> > common in the code. From the names, when would I know when to use\n>> > \"struct reference\" and when \"struct ref\"?\n>> > \n>> > Could we give it a name that ties it to the iteration interface?\n>> > Something like iterated_ref, each_ref_data, etc?\n>> > \n>> > I know this is minor (and will be annoying to adjust your series), but\n>> > I'd rather raise the point now than realize later that it's confusing\n>> > and try to change it then.\n\nWhen I was reviewing these patches, I also had some doubts whether\n`reference` was the best name for this struct. Especially because the\ncomment above says:\n\n    /* A reference passed to `for_each_ref()`-style callbacks. */\n\nBut on the other hand, I agree with Patrick:\n\n>> what it contains is as close as you get to a representation of a\n>> reference.\n\nSo, I'd be fine if this comment was rewritten a little bit, into\nsomething like:\n\n    /* A representation of a reference, for example to be passed to\n     * `for_each_ref()`-style callbacks. */\n\n> I am not so much arguing that \"struct reference\" is misnamed, as that it\n> is sufficiently generic that people will reach for it when it is not the\n> appropriate tool. It is for passing the ref data to the iterator\n> callback, but it probably doesn't make sense in other contexts. Would we\n> ever expect anybody to declare their own \"struct reference\" in a local\n> function? I don't think so.\n\nPersonally I'd say it's hard to predict something like that. Usually I\ntend to lean toward naming that doesn't assume anything that might\nhappen in the future. And because I think this struct contains what a\nreference _is_, I think this name is fine. And whenever we run into the\nissue that we need something else to be named `reference`, we revisit\nthis decision.\n\n> And yes, \"struct ref\" suffers somewhat from the same problem. It is\n> mostly about using refs in one specific space, but the name does not\n> really help clarify that. I wouldn't mind seeing that improved, but yes,\n> it would be a noisy patch. I don't know if remote_ref is the right name,\n> though (the \"peer_ref\" links mean we store both local and remote refs in\n> it, IIRC).\n\n\n\n\n\n-- \nCheers,\nToon\n"},{"id":"528398","messageId":"xmqq5xcneuiu.fsf@gitster.g","threadId":"64260","inReplyTo":"20251009053825.GB1614343@coredump.intra.peff.net","subject":"Re: [PATCH v2 00/14] refs: improvements and fixes for peeling tags","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-10-09T19:37:29Z","receivedAt":"2025-10-09T19:37:32Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> On Wed, Oct 08, 2025 at 05:50:15PM +0200, Patrick Steinhardt wrote:\n>\n>>   - Patches 1 to 8 refactor our codebase so that we don't have the\n>>     `peel_iterated_object()` hack anymore. I just found it hard to\n>>     follow and thought it shouldn't be too hard to get rid of it.\n>\n> I'm really happy to see this hack go away. I've wanted to fix it for\n> ages, but didn't want to bite the bullet on changing all of the\n> each_ref_fn callbacks. The approach you used here to pass through a\n> struct looks good to me.\n>\n> I do have one minor complaint, though: the name of that struct. I have a\n> feeling that the name \"struct reference\" may cause confusion down the\n> road because it's so generic, and because \"references\" and \"refs\" are so\n> common in the code. From the names, when would I know when to use\n> \"struct reference\" and when \"struct ref\"?\n>\n> Could we give it a name that ties it to the iteration interface?\n> Something like iterated_ref, each_ref_data, etc?\n>\n> I know this is minor (and will be annoying to adjust your series), but\n> I'd rather raise the point now than realize later that it's confusing\n> and try to change it then.\n\nThanks for saving me from saying the above ;-) My feeling exactly,\nboth on the happiness of finally see the hack go, and also the\nnaming.\n\n"},{"id":"528453","messageId":"20251010051242.GC1897715@coredump.intra.peff.net","threadId":"64260","inReplyTo":"aOdjM8F6WvTEBIo_@pks.im","subject":"Re: [PATCH v2 00/14] refs: improvements and fixes for peeling tags","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-10-10T05:12:42Z","receivedAt":"2025-10-10T05:12:43Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Oct 09, 2025 at 09:24:35AM +0200, Patrick Steinhardt wrote:\n\n> > I am not so much arguing that \"struct reference\" is misnamed, as that it\n> > is sufficiently generic that people will reach for it when it is not the\n> > appropriate tool. It is for passing the ref data to the iterator\n> > callback, but it probably doesn't make sense in other contexts. Would we\n> > ever expect anybody to declare their own \"struct reference\" in a local\n> > function? I don't think so.\n> \n> Hm. The thing is: if `struct reference` is established in our code base,\n> and if it is a simple representation of a reference, then I think it\n> might even be a good thing to having it. I could for example see that we\n> gain more interfaces over time that use it.\n> \n> For example, functions like `refs_read_ref()` could totally be adapted\n> to use the same struct, and I would claim that this is a good thing.\n> We'd basically have a single central structure that allows us to get a\n> reference out of the \"refs\" subsystem with metadata.\n\nI agree that if it became the standard representation, then having the\ngeneric name would be good. I guess I'm just skeptical that it will\nbecome/remain that, and not grow gross appendages like the fetch/push\nstatus fields of \"struct ref\". But maybe I am just too pessimistic. ;)\n\nI do agree that it would be nice for refs_read_ref() and other refstore\nfunctions to use this as a common type for returning results. Even if it\nlater gained more fields that were specific to the ref subsystem, they'd\nstill make sense in that context.\n\nSo I dunno. I could go either way (keeping your series as-is, or using a\nnew name).\n\n> > And yes, \"struct ref\" suffers somewhat from the same problem. It is\n> > mostly about using refs in one specific space, but the name does not\n> > really help clarify that. I wouldn't mind seeing that improved, but yes,\n> > it would be a noisy patch. I don't know if remote_ref is the right name,\n> > though (the \"peer_ref\" links mean we store both local and remote refs in\n> > it, IIRC).\n> \n> If we decide to do it it should definitely be a standalone patch (or\n> patch series). I'm also open for different naming suggestions, but for\n> now this is the best I could come up with.\n\nThat makes sense. The only reason to touch it here would be if we wanted\nto free up the name \"struct ref\" to use right now. I think that is a\nbetter name than \"struct reference\", but given all of the existing uses\nof \"struct ref\", it is probably not worth the hassle to switch now.\n\n-Peff\n"},{"id":"528455","messageId":"aOiYFPTNLL1Fgz5V@pks.im","threadId":"64260","inReplyTo":"20251010051242.GC1897715@coredump.intra.peff.net","subject":"Re: [PATCH v2 00/14] refs: improvements and fixes for peeling tags","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-10T05:22:28Z","receivedAt":"2025-10-10T05:22:36Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, Oct 10, 2025 at 01:12:42AM -0400, Jeff King wrote:\n> On Thu, Oct 09, 2025 at 09:24:35AM +0200, Patrick Steinhardt wrote:\n> \n> > > I am not so much arguing that \"struct reference\" is misnamed, as that it\n> > > is sufficiently generic that people will reach for it when it is not the\n> > > appropriate tool. It is for passing the ref data to the iterator\n> > > callback, but it probably doesn't make sense in other contexts. Would we\n> > > ever expect anybody to declare their own \"struct reference\" in a local\n> > > function? I don't think so.\n> > \n> > Hm. The thing is: if `struct reference` is established in our code base,\n> > and if it is a simple representation of a reference, then I think it\n> > might even be a good thing to having it. I could for example see that we\n> > gain more interfaces over time that use it.\n> > \n> > For example, functions like `refs_read_ref()` could totally be adapted\n> > to use the same struct, and I would claim that this is a good thing.\n> > We'd basically have a single central structure that allows us to get a\n> > reference out of the \"refs\" subsystem with metadata.\n> \n> I agree that if it became the standard representation, then having the\n> generic name would be good. I guess I'm just skeptical that it will\n> become/remain that, and not grow gross appendages like the fetch/push\n> status fields of \"struct ref\". But maybe I am just too pessimistic. ;)\n> \n> I do agree that it would be nice for refs_read_ref() and other refstore\n> functions to use this as a common type for returning results. Even if it\n> later gained more fields that were specific to the ref subsystem, they'd\n> still make sense in that context.\n> \n> So I dunno. I could go either way (keeping your series as-is, or using a\n> new name).\n> \n> > > And yes, \"struct ref\" suffers somewhat from the same problem. It is\n> > > mostly about using refs in one specific space, but the name does not\n> > > really help clarify that. I wouldn't mind seeing that improved, but yes,\n> > > it would be a noisy patch. I don't know if remote_ref is the right name,\n> > > though (the \"peer_ref\" links mean we store both local and remote refs in\n> > > it, IIRC).\n> > \n> > If we decide to do it it should definitely be a standalone patch (or\n> > patch series). I'm also open for different naming suggestions, but for\n> > now this is the best I could come up with.\n> \n> That makes sense. The only reason to touch it here would be if we wanted\n> to free up the name \"struct ref\" to use right now. I think that is a\n> better name than \"struct reference\", but given all of the existing uses\n> of \"struct ref\", it is probably not worth the hassle to switch now.\n\nI think I'd like to avoid doing such a swap in this series, as it may\neasily cause confusion and create problems for any in-flight series.\nFrom my point of view I also think that neither of these names is\nclearly superior over the other.\n\nSo to move forward, how about we land this as-is and I promise to follow\nup with another series that:\n\n  - Renames `struct ref` as proposed.\n\n  - Introduces `struct reference` into more of our APIs?\n\nThanks!\n\nPatrick\n"},{"id":"528467","messageId":"20251010062643.GI1965904@coredump.intra.peff.net","threadId":"64260","inReplyTo":"aOiYFPTNLL1Fgz5V@pks.im","subject":"Re: [PATCH v2 00/14] refs: improvements and fixes for peeling tags","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-10-10T06:26:43Z","receivedAt":"2025-10-10T06:26:45Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Oct 10, 2025 at 07:22:28AM +0200, Patrick Steinhardt wrote:\n\n> I think I'd like to avoid doing such a swap in this series, as it may\n> easily cause confusion and create problems for any in-flight series.\n> From my point of view I also think that neither of these names is\n> clearly superior over the other.\n> \n> So to move forward, how about we land this as-is and I promise to follow\n> up with another series that:\n> \n>   - Renames `struct ref` as proposed.\n> \n>   - Introduces `struct reference` into more of our APIs?\n\nOK by me. I am also OK if we just leave \"struct ref\" as-is. I like the\nname \"ref\" more than \"reference\" (since that is the name we usually use,\nand what the API is called!). But changing \"struct ref\" now may be a lot\nof churn. So I will not hold you to your promise. :)\n\n-Peff\n"},{"id":"528514","messageId":"xmqq8qhibwrn.fsf@gitster.g","threadId":"64260","inReplyTo":"aOiYFPTNLL1Fgz5V@pks.im","subject":"Re: [PATCH v2 00/14] refs: improvements and fixes for peeling tags","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-10-10T15:29:32Z","receivedAt":"2025-10-10T15:29:35Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> So to move forward, how about we land this as-is and I promise to follow\n> up with another series that:\n>\n>   - Renames `struct ref` as proposed.\n>\n>   - Introduces `struct reference` into more of our APIs?\n\nSorry, but I am not quite sure why we would want to do so.\n\nDoes \"struct reference\" sufficiently cover the things we want to do\nwith references and \"struct ref\" is not sufficient for that?\n\nComparing what 'struct ref' caters to its users and what 'struct\nreference' offers to its users and declaring that one set of needs\nis more generic to the 'reference API' than the other set risks\ngetting blinded by the area we happened to have been focusing on\nrecently.\n\nApparently, the above proposal is not claiming that what one wants\nto do is a subset of what the other wants to do (if so, you'd rather\nnot be introducing a new \"struct reference\" but extending \"struct\nref\" to be usable for more things).\n\nOr would we add more to it than what we see in this series, such\nthat it would no longer be \"a subset\" of needs various code paths\nwould have around the reference API?  If so, is the longer-term plan\nto have callers that use \"struct ref\" to eventually use \"struct\nreference\"?\n\nIf not, they are serving different subset of the problem space, and\nthey will continue to do so.  In that case, why wouldn't we rename\n\"struct reference\" to something that is more focused on what it is\nfor?  In the context of this topic, would that be \"reference found\nduring iteration\" or something?\n"},{"id":"528687","messageId":"aO3uSz-idPWahgw7@pks.im","threadId":"64260","inReplyTo":"xmqq8qhibwrn.fsf@gitster.g","subject":"Re: [PATCH v2 00/14] refs: improvements and fixes for peeling tags","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-14T06:31:39Z","receivedAt":"2025-10-14T06:31:53Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, Oct 10, 2025 at 08:29:32AM -0700, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > So to move forward, how about we land this as-is and I promise to follow\n> > up with another series that:\n> >\n> >   - Renames `struct ref` as proposed.\n> >\n> >   - Introduces `struct reference` into more of our APIs?\n> \n> Sorry, but I am not quite sure why we would want to do so.\n> \n> Does \"struct reference\" sufficiently cover the things we want to do\n> with references and \"struct ref\" is not sufficient for that?\n> \n> Comparing what 'struct ref' caters to its users and what 'struct\n> reference' offers to its users and declaring that one set of needs\n> is more generic to the 'reference API' than the other set risks\n> getting blinded by the area we happened to have been focusing on\n> recently.\n> \n> Apparently, the above proposal is not claiming that what one wants\n> to do is a subset of what the other wants to do (if so, you'd rather\n> not be introducing a new \"struct reference\" but extending \"struct\n> ref\" to be usable for more things).\n> \n> Or would we add more to it than what we see in this series, such\n> that it would no longer be \"a subset\" of needs various code paths\n> would have around the reference API?  If so, is the longer-term plan\n> to have callers that use \"struct ref\" to eventually use \"struct\n> reference\"?\n> \n> If not, they are serving different subset of the problem space, and\n> they will continue to do so.  In that case, why wouldn't we rename\n> \"struct reference\" to something that is more focused on what it is\n> for?  In the context of this topic, would that be \"reference found\n> during iteration\" or something?\n\nI think that `struct ref` and `struct reference` serve quite distinct\nuse cases. `struct ref` is all around references in the context of a\nremote: they are only in our code that interacts with them like for\nexample \"transport.c\", \"walker.c\", \"fetch-pack.c\" and so on. As such,\nthis structure naturally contains a ton of fields that are relevant in\nthis context:\n\n  - It's a linked list that identifies all refs part of such a push.\n\n  - It contains new_old object IDs.\n\n  - It contains information whether or not such a reference should be\n    force-updated.\n\n  - It contains information whether the remote side has such a ref in\n    the first place.\n\n  - It encodes the FETCH_HEAD status.\n\nThere's much more, and nothing of this has anything to do with a plain\nreference. As such, this type would be a very bad fit for use in the\n\"refs.c\" subsystem, as the basic concepts are mismatching.\n\nSo what I'm proposing here is to introduce a `struct reference` that\nreally only cares about the specific concept of a plain reference. That\nstruct would thus only carry information that can be yielded by the ref\nbackends standalone.\n\nThe benefit is that it allows us to have a better abstraction boundary,\nand it allows us to achieve things we cannot easily do right now. For\nexample, if functions like `refs_read_ref()` returned such a structure,\nthen we can now also peel such a ref with `reference_get_peeled_oid()`,\nwhich is currently only possible when iterating over references. Other\nhigher-level functions like `reference_is_branch()` or similar things\ncan also be introduced. Last but not least, it also makes it obvious\nwhat kind of \"thing\" we're working with in many cases, as a `struct\nreference` will always be the fully-qualified and \"raw\" reference.\n\nHope that sheds some light on my thinking :)\n\nThanks!\n\nPatrick\n"},{"id":"528746","messageId":"xmqqy0pdxw7a.fsf@gitster.g","threadId":"64260","inReplyTo":"aO3uSz-idPWahgw7@pks.im","subject":"Re: [PATCH v2 00/14] refs: improvements and fixes for peeling tags","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-10-14T16:52:09Z","receivedAt":"2025-10-14T16:52:12Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> I think that `struct ref` and `struct reference` serve quite distinct\n> use cases. `struct ref` is all around references in the context of a\n> remote: they are only in our code that interacts with them like for\n> example \"transport.c\", \"walker.c\", \"fetch-pack.c\" and so on. As such,\n> this structure naturally contains a ton of fields that are relevant in\n> this context:\n>\n>   - It's a linked list that identifies all refs part of such a push.\n>\n>   - It contains new_old object IDs.\n>\n>   - It contains information whether or not such a reference should be\n>     force-updated.\n>\n>   - It contains information whether the remote side has such a ref in\n>     the first place.\n>\n>   - It encodes the FETCH_HEAD status.\n>\n> There's much more, and nothing of this has anything to do with a plain\n> reference. As such, this type would be a very bad fit for use in the\n> \"refs.c\" subsystem, as the basic concepts are mismatching.\n\nOK.  The plain reference structure then would not be involved in\nlocal ref update transactions, for example.  It is just \"here are\nthe refs we have\", \"this ref is of this type, at this path, with\nthis value\", etc.  Makes sense.\n\n> So what I'm proposing here is to introduce a `struct reference` that\n> really only cares about the specific concept of a plain reference. That\n> struct would thus only carry information that can be yielded by the ref\n> backends standalone.\n\nOK.  Makes sense.  Thanks.\n"},{"id":"528793","messageId":"CAOLa=ZSF25OG2T5cXtYArVxEqr-jRcMnha_yENTMnsG8sZ+6=A@mail.gmail.com","threadId":"64260","inReplyTo":"aOZq3HlVV7tF4VSJ@pks.im","subject":"Re: [PATCH 04/13] refs: expose peeled object ID via the iterator","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2025-10-15T08:28:21Z","receivedAt":"2025-10-15T08:28:25Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Tue, Oct 07, 2025 at 07:52:43AM -0700, Karthik Nayak wrote:\n>> Patrick Steinhardt <ps@pks.im> writes:\n>>\n>> > Both the \"files\" and \"reftable\" backend are able to store peeled values\n>> > for tags in the respective formats. This allows for a more efficient\n>> > lookup of the target object of such a tag without having to manually\n>> > peel via the object database.\n>> >\n>>\n>> In the 'files' backend, I thought only packed-refs store peeled values?\n>\n> Yup, peeled values may or may not be available with the \"files\" backend,\n> depending on whether or not a reference is packed. But the paragraph\n> doesn't really contradict this, does it?\n>\n> Patrick\n\nNo it doesn't, was more rhetorical and self thought :)\n"},{"id":"529382","messageId":"20251022-b4-pks-ref-filter-skip-parsing-objects-v3-0-eb9f71985ef0@pks.im","threadId":"64260","inReplyTo":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-0-916cc7c6886b@pks.im","subject":"[PATCH v3 00/14] refs: improvements and fixes for peeling tags","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-22T06:41:00Z","receivedAt":"2025-10-22T06:41:11Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\noriginally, all I wanted to do was the last patch: a small performance\noptimization that stops parsing objects in git-for-each-ref(1) unless we\nreally need to parse them. But that fix cause one specific test to fail,\nand only with the reftable backend. So this led me down the rabbit hole\nof tag peeling, ending up with this patch series.\n\nThe series is structured like follows:\n\n  - Patches 1 to 8 refactor our codebase so that we don't have the\n    `peel_iterated_object()` hack anymore. I just found it hard to\n    follow and thought it shouldn't be too hard to get rid of it.\n\n  - Patches 9 and 10 remove infrastructure that we don't need anymore\n    after the first couple of patches.\n\n  - Patches 11 to 13 fix a couple of issues with peeled tags that I\n    found. The underlying issue is that tags store both the tagged\n    object and their type, but this information may not match. We never\n    verify the actual object type though when allocating the tagged\n    object, so this only blows up much later.\n\n  - Patch 14 was my original motivation, a small performance\n    optimization.\n\nI'm not particularly fond of the patches 11 to 13. It feels more like\nplaying whack-a-mole, and I very much assume that there still are edge\ncases where we should properly verify the tagged object type. But\nchanging it in `parse_tag_buffer()` itself causes a bunch of tests to\nfail where we intentionally create such corrupted tags. So I didn't\nreally dare to touch that part, to be honest.\n\nIf anybody has suggestions for an alternative approach I'd be very open\nto it.\n\nChanges in v3:\n  - I've rebuilt the topic on 133d151831 (The twenty-first batch, 2025-10-20) with\n        - tb/incremental-midx-part-3.1 at 935ab44a0a (builtin/repack.c:\n          clean up unused `#include`s, 2025-10-15)\n        - jt/16a93c03c7 at (builtin/repo: add progress meter for\n          structure stats, 2025-10-21)\n    merged into it. This is done to fix a couple of merge conflicts with\n    \"seen\". Both of the topics are only in \"seen\" right now, but they\n    are close to be merged.\n  - Link to v2: https://lore.kernel.org/r/20251008-b4-pks-ref-filter-skip-parsing-objects-v2-0-76e30d5c9542@pks.im\n\nChanges in v2:\n  - A couple of improvements to commit messages.\n  - A new commit that ensures that `struct ref_iterator::ref` is always\n    zeroed out to protect against stale state.\n  - Link to v1: https://lore.kernel.org/r/20251007-b4-pks-ref-filter-skip-parsing-objects-v1-0-916cc7c6886b@pks.im\n\nThanks!\n\nPatrick\n\n---\nPatrick Steinhardt (14):\n      refs: introduce wrapper struct for `each_ref_fn`\n      refs: introduce `.ref` field for the base iterator\n      refs: fully reset `struct ref_iterator::ref` on iteration\n      refs: refactor reference status flags\n      refs: expose peeled object ID via the iterator\n      upload-pack: convert to use `reference_get_peeled_oid()`\n      ref-filter: propagate peeled object ID\n      builtin/show-ref: convert to use `reference_get_peeled_oid()`\n      refs: drop `current_ref_iter` hack\n      refs: drop infrastructure to peel via iterators\n      object: add flag to `peel_object()` to verify object type\n      refs: don't store peeled object IDs for invalid tags\n      ref-filter: detect broken tags when dereferencing them\n      ref-filter: parse objects on demand\n\n bisect.c                    |  24 ++---\n builtin/bisect.c            |  17 +---\n builtin/checkout.c          |   6 +-\n builtin/describe.c          |  18 ++--\n builtin/fetch.c             |  13 +--\n builtin/fsck.c              |  33 +++---\n builtin/gc.c                |  15 ++-\n builtin/ls-remote.c         |   2 +-\n builtin/name-rev.c          |  17 ++--\n builtin/pack-objects.c      |  28 +++---\n builtin/receive-pack.c      |  13 ++-\n builtin/remote.c            |  44 ++++----\n builtin/replace.c           |  21 ++--\n builtin/repo.c              |   9 +-\n builtin/rev-parse.c         |  12 +--\n builtin/show-branch.c       |  35 +++----\n builtin/show-ref.c          |  50 ++++-----\n builtin/submodule--helper.c |  10 +-\n builtin/tag.c               |   2 +-\n builtin/verify-tag.c        |   2 +-\n builtin/worktree.c          |   6 +-\n commit-graph.c              |  14 ++-\n delta-islands.c             |   9 +-\n fetch-pack.c                |  16 +--\n help.c                      |  10 +-\n http-backend.c              |  20 ++--\n log-tree.c                  |  24 ++---\n ls-refs.c                   |  36 ++++---\n midx-write.c                |  17 ++--\n negotiator/default.c        |   7 +-\n negotiator/skipping.c       |   7 +-\n notes.c                     |   8 +-\n object-name.c               |  10 +-\n object.c                    |  20 +++-\n object.h                    |  15 ++-\n pseudo-merge.c              |  21 ++--\n reachable.c                 |   9 +-\n ref-filter.c                | 239 ++++++++++++++++++++++++++++++--------------\n ref-filter.h                |   5 +-\n reflog.c                    |   9 +-\n refs.c                      |  85 +++++++++-------\n refs.h                      |  88 ++++++++++------\n refs/debug.c                |  17 +---\n refs/files-backend.c        |  71 +++++--------\n refs/iterator.c             |  73 +++-----------\n refs/packed-backend.c       |  71 +++++--------\n refs/ref-cache.c            |  18 +---\n refs/refs-internal.h        |  25 +----\n refs/reftable-backend.c     |  47 +++------\n remote.c                    |  27 +++--\n repack-midx.c               |  16 ++-\n replace-object.c            |  16 ++-\n revision.c                  |  12 +--\n server-info.c               |  12 +--\n shallow.c                   |  16 +--\n submodule.c                 |  12 +--\n t/for-each-ref-tests.sh     |   4 +-\n t/helper/test-reach.c       |   2 +-\n t/helper/test-ref-store.c   |   5 +-\n t/pack-refs-tests.sh        |  32 ++++++\n t/t0610-reftable-basics.sh  |  28 ++++++\n tag.c                       |  12 ---\n tag.h                       |   1 -\n upload-pack.c               |  49 ++++-----\n walker.c                    |   8 +-\n worktree.c                  |  11 +-\n 66 files changed, 794 insertions(+), 837 deletions(-)\n\nRange-diff versus v2:\n\n 1:  4a78d628cef !  1:  05cfb7e0364 refs: introduce wrapper struct for `each_ref_fn`\n    @@ builtin/remote.c: static void free_remote_ref_states(struct ref_states *states)\n      \t\tstring_list_append(&states->tracked, abbrev_branch(refspec.src));\n      \t\tfree(refspec.src);\n     \n    - ## builtin/repack.c ##\n    -@@ builtin/repack.c: struct midx_snapshot_ref_data {\n    - \tint preferred;\n    - };\n    - \n    --static int midx_snapshot_ref_one(const char *refname UNUSED,\n    --\t\t\t\t const char *referent UNUSED,\n    --\t\t\t\t const struct object_id *oid,\n    --\t\t\t\t int flag UNUSED, void *_data)\n    -+static int midx_snapshot_ref_one(const struct reference *ref, void *_data)\n    - {\n    - \tstruct midx_snapshot_ref_data *data = _data;\n    -+\tconst struct object_id *maybe_peeled = ref->oid;\n    - \tstruct object_id peeled;\n    - \n    --\tif (!peel_iterated_oid(the_repository, oid, &peeled))\n    --\t\toid = &peeled;\n    -+\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n    -+\t\tmaybe_peeled = &peeled;\n    - \n    --\tif (oidset_insert(&data->seen, oid))\n    -+\tif (oidset_insert(&data->seen, maybe_peeled))\n    - \t\treturn 0; /* already seen */\n    - \n    --\tif (odb_read_object_info(the_repository->objects, oid, NULL) != OBJ_COMMIT)\n    -+\tif (odb_read_object_info(the_repository->objects, maybe_peeled, NULL) != OBJ_COMMIT)\n    - \t\treturn 0;\n    - \n    - \tfprintf(data->f->fp, \"%s%s\\n\", data->preferred ? \"+\" : \"\",\n    --\t\toid_to_hex(oid));\n    -+\t\toid_to_hex(maybe_peeled));\n    - \n    - \treturn 0;\n    - }\n    -\n      ## builtin/replace.c ##\n     @@ builtin/replace.c: struct show_data {\n      \tenum replace_format format;\n    @@ builtin/replace.c: struct show_data {\n      \t}\n      \n     \n    + ## builtin/repo.c ##\n    +@@ builtin/repo.c: struct count_references_data {\n    + \tstruct progress *progress;\n    + };\n    + \n    +-static int count_references(const char *refname,\n    +-\t\t\t    const char *referent UNUSED,\n    +-\t\t\t    const struct object_id *oid,\n    +-\t\t\t    int flags UNUSED, void *cb_data)\n    ++static int count_references(const struct reference *ref, void *cb_data)\n    + {\n    + \tstruct count_references_data *data = cb_data;\n    + \tstruct ref_stats *stats = data->stats;\n    + \tsize_t ref_count;\n    + \n    +-\tswitch (ref_kind_from_refname(refname)) {\n    ++\tswitch (ref_kind_from_refname(ref->name)) {\n    + \tcase FILTER_REFS_BRANCHES:\n    + \t\tstats->branches++;\n    + \t\tbreak;\n    +@@ builtin/repo.c: static int count_references(const char *refname,\n    + \t * While iterating through references for counting, also add OIDs in\n    + \t * preparation for the path walk.\n    + \t */\n    +-\tadd_pending_oid(data->revs, NULL, oid, 0);\n    ++\tadd_pending_oid(data->revs, NULL, ref->oid, 0);\n    + \n    + \tref_count = get_total_reference_count(stats);\n    + \tdisplay_progress(data->progress, ref_count);\n    +\n      ## builtin/rev-parse.c ##\n     @@ builtin/rev-parse.c: static int show_default(void)\n      \treturn 0;\n    @@ remote.c: static int get_stale_heads_cb(const char *refname, const char *referen\n      \n      clean_exit:\n     \n    + ## repack-midx.c ##\n    +@@ repack-midx.c: struct midx_snapshot_ref_data {\n    + \tint preferred;\n    + };\n    + \n    +-static int midx_snapshot_ref_one(const char *refname UNUSED,\n    +-\t\t\t\t const char *referent UNUSED,\n    +-\t\t\t\t const struct object_id *oid,\n    +-\t\t\t\t int flag UNUSED, void *_data)\n    ++static int midx_snapshot_ref_one(const struct reference *ref, void *_data)\n    + {\n    + \tstruct midx_snapshot_ref_data *data = _data;\n    ++\tconst struct object_id *maybe_peeled = ref->oid;\n    + \tstruct object_id peeled;\n    + \n    +-\tif (!peel_iterated_oid(data->repo, oid, &peeled))\n    +-\t\toid = &peeled;\n    ++\tif (!peel_iterated_oid(data->repo, ref->oid, &peeled))\n    ++\t\tmaybe_peeled = &peeled;\n    + \n    +-\tif (oidset_insert(&data->seen, oid))\n    ++\tif (oidset_insert(&data->seen, maybe_peeled))\n    + \t\treturn 0; /* already seen */\n    + \n    +-\tif (odb_read_object_info(data->repo->objects, oid, NULL) != OBJ_COMMIT)\n    ++\tif (odb_read_object_info(data->repo->objects, maybe_peeled, NULL) != OBJ_COMMIT)\n    + \t\treturn 0;\n    + \n    + \tfprintf(data->f->fp, \"%s%s\\n\", data->preferred ? \"+\" : \"\",\n    +-\t\toid_to_hex(oid));\n    ++\t\toid_to_hex(maybe_peeled));\n    + \n    + \treturn 0;\n    + }\n    +\n      ## replace-object.c ##\n     @@\n      #include \"repository.h\"\n 2:  9efd804f546 =  2:  e3bebc60554 refs: introduce `.ref` field for the base iterator\n 3:  a4d79cf863d =  3:  99849a0323a refs: fully reset `struct ref_iterator::ref` on iteration\n 4:  b4dcba74daf =  4:  6b93b3601b8 refs: refactor reference status flags\n 5:  89cc42156b4 !  5:  8756dc100ab refs: expose peeled object ID via the iterator\n    @@ builtin/pack-objects.c: static int mark_bitmap_preferred_tip(const struct refere\n      \n      \tobject = parse_object_or_die(the_repository, maybe_peeled, ref->name);\n     \n    - ## builtin/repack.c ##\n    -@@ builtin/repack.c: static int midx_snapshot_ref_one(const struct reference *ref, void *_data)\n    - \tconst struct object_id *maybe_peeled = ref->oid;\n    - \tstruct object_id peeled;\n    - \n    --\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n    -+\tif (!reference_get_peeled_oid(the_repository, ref, &peeled))\n    - \t\tmaybe_peeled = &peeled;\n    - \n    - \tif (oidset_insert(&data->seen, maybe_peeled))\n    -\n      ## commit-graph.c ##\n     @@ commit-graph.c: static int add_ref_to_set(const struct reference *ref, void *cb_data)\n      \tstruct object_id peeled;\n    @@ refs/reftable-backend.c: static int reftable_ref_iterator_advance(struct ref_ite\n      \t\titer->base.ref.flags = flags;\n      \n      \t\tbreak;\n    +\n    + ## repack-midx.c ##\n    +@@ repack-midx.c: static int midx_snapshot_ref_one(const struct reference *ref, void *_data)\n    + \tconst struct object_id *maybe_peeled = ref->oid;\n    + \tstruct object_id peeled;\n    + \n    +-\tif (!peel_iterated_oid(data->repo, ref->oid, &peeled))\n    ++\tif (!reference_get_peeled_oid(data->repo, ref, &peeled))\n    + \t\tmaybe_peeled = &peeled;\n    + \n    + \tif (oidset_insert(&data->seen, maybe_peeled))\n 6:  3168a0ca998 =  6:  93ef81780ec upload-pack: convert to use `reference_get_peeled_oid()`\n 7:  e72d8ff494f =  7:  4546770861f ref-filter: propagate peeled object ID\n 8:  1eb5dee1e04 =  8:  fe65e6040f1 builtin/show-ref: convert to use `reference_get_peeled_oid()`\n 9:  f58de2e9eaa =  9:  c656a638c2b refs: drop `current_ref_iter` hack\n10:  1e62da6ed1e = 10:  03f85e095e8 refs: drop infrastructure to peel via iterators\n11:  af36e3bfe80 = 11:  8087e6db211 object: add flag to `peel_object()` to verify object type\n12:  14859152c57 = 12:  1cc87f74e11 refs: don't store peeled object IDs for invalid tags\n13:  2191cfee400 = 13:  a5a6f27aea9 ref-filter: detect broken tags when dereferencing them\n14:  0ef24754924 = 14:  2265eecd4ee ref-filter: parse objects on demand\n\n---\nbase-commit: 5c120f01eb88f4be8b06fd4bc6893763204b78c4\nchange-id: 20250918-b4-pks-ref-filter-skip-parsing-objects-f0d1f6af4a9f\n\n"},{"id":"529385","messageId":"20251022-b4-pks-ref-filter-skip-parsing-objects-v3-1-eb9f71985ef0@pks.im","threadId":"64260","inReplyTo":"20251022-b4-pks-ref-filter-skip-parsing-objects-v3-0-eb9f71985ef0@pks.im","subject":"[PATCH v3 01/14] refs: introduce wrapper struct for `each_ref_fn`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-22T06:41:01Z","receivedAt":"2025-10-22T06:41:13Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `each_ref_fn` callback function type is used across our code base\nfor several different functions that iterate through reference. There's\na bunch of callbacks implementing this type, which makes any changes to\nthe callback signature extremely noisy. An example of the required churn\nis e8207717f1 (refs: add referent to each_ref_fn, 2024-08-09): adding a\nsingle argument required us to change 48 files.\n\nIt was already proposed back then [1] that we might want to introduce a\nwrapper structure to alleviate the pain going forward. While this of\ncourse requires the same kind of global refactoring as just introducing\na new parameter, it at least allows us to more change the callback type\nafterwards by just extending the wrapper structure.\n\nOne counterargument to this refactoring is that it makes the structure\nmore opaque. While it is obvious which callsites need to be fixed up\nwhen we change the function type, it's not obvious anymore once we use\na structure. That being said, we only have a handful of sites that\nactually need to populate this wrapper structure: our ref backends,\n\"refs/iterator.c\" as well as very few sites that invoke the iterator\ncallback functions directly.\n\nIntroduce this wrapper structure so that we can adapt the iterator\ninterfaces more readily.\n\n[1]: <ZmarVcF5JjsZx0dl@tanuki>\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n bisect.c                    | 24 +++++++---------\n builtin/bisect.c            | 17 ++++--------\n builtin/checkout.c          |  6 ++--\n builtin/describe.c          | 18 ++++++------\n builtin/fetch.c             | 13 +++------\n builtin/fsck.c              | 33 ++++++++++++----------\n builtin/gc.c                | 15 ++++------\n builtin/name-rev.c          | 17 ++++++------\n builtin/pack-objects.c      | 27 ++++++++----------\n builtin/receive-pack.c      | 13 ++++-----\n builtin/remote.c            | 44 +++++++++++++----------------\n builtin/replace.c           | 21 ++++++--------\n builtin/repo.c              |  9 ++----\n builtin/rev-parse.c         | 12 ++++----\n builtin/show-branch.c       | 35 +++++++++++------------\n builtin/show-ref.c          | 20 ++++++--------\n builtin/submodule--helper.c | 10 ++-----\n builtin/worktree.c          |  6 +---\n commit-graph.c              | 14 ++++------\n delta-islands.c             |  9 +++---\n fetch-pack.c                | 16 +++--------\n help.c                      | 10 +++----\n http-backend.c              | 20 ++++++--------\n log-tree.c                  | 24 ++++++++--------\n ls-refs.c                   | 36 ++++++++++++++----------\n midx-write.c                | 17 ++++++------\n negotiator/default.c        |  7 ++---\n negotiator/skipping.c       |  7 ++---\n notes.c                     |  8 ++----\n object-name.c               | 10 +++----\n pseudo-merge.c              | 21 ++++++--------\n reachable.c                 |  9 +++---\n ref-filter.c                | 24 ++++++++--------\n reflog.c                    |  9 ++----\n refs.c                      | 67 +++++++++++++++++++++++++--------------------\n refs.h                      | 26 +++++++++++++++---\n refs/files-backend.c        |  7 ++---\n refs/iterator.c             |  9 +++++-\n remote.c                    | 27 ++++++++----------\n repack-midx.c               | 16 +++++------\n replace-object.c            | 16 ++++-------\n revision.c                  | 12 ++++----\n server-info.c               | 12 ++++----\n shallow.c                   | 16 +++--------\n submodule.c                 | 12 ++------\n t/helper/test-ref-store.c   |  5 ++--\n upload-pack.c               | 29 +++++++++-----------\n walker.c                    |  8 ++----\n worktree.c                  | 11 ++++++--\n 49 files changed, 392 insertions(+), 462 deletions(-)\n\ndiff --git a/bisect.c b/bisect.c\nindex a6dc76b15c9..326b59c0dc7 100644\n--- a/bisect.c\n+++ b/bisect.c\n@@ -450,21 +450,20 @@ void find_bisection(struct commit_list **commit_list, int *reaches,\n \tclear_commit_weight(&commit_weight);\n }\n \n-static int register_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\tint flags UNUSED, void *cb_data UNUSED)\n+static int register_ref(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct strbuf good_prefix = STRBUF_INIT;\n \tstrbuf_addstr(&good_prefix, term_good);\n \tstrbuf_addstr(&good_prefix, \"-\");\n \n-\tif (!strcmp(refname, term_bad)) {\n+\tif (!strcmp(ref->name, term_bad)) {\n \t\tfree(current_bad_oid);\n \t\tcurrent_bad_oid = xmalloc(sizeof(*current_bad_oid));\n-\t\toidcpy(current_bad_oid, oid);\n-\t} else if (starts_with(refname, good_prefix.buf)) {\n-\t\toid_array_append(&good_revs, oid);\n-\t} else if (starts_with(refname, \"skip-\")) {\n-\t\toid_array_append(&skipped_revs, oid);\n+\t\toidcpy(current_bad_oid, ref->oid);\n+\t} else if (starts_with(ref->name, good_prefix.buf)) {\n+\t\toid_array_append(&good_revs, ref->oid);\n+\t} else if (starts_with(ref->name, \"skip-\")) {\n+\t\toid_array_append(&skipped_revs, ref->oid);\n \t}\n \n \tstrbuf_release(&good_prefix);\n@@ -1178,14 +1177,11 @@ int estimate_bisect_steps(int all)\n \treturn (e < 3 * x) ? n : n - 1;\n }\n \n-static int mark_for_removal(const char *refname,\n-\t\t\t    const char *referent UNUSED,\n-\t\t\t    const struct object_id *oid UNUSED,\n-\t\t\t    int flag UNUSED, void *cb_data)\n+static int mark_for_removal(const struct reference *ref, void *cb_data)\n {\n \tstruct string_list *refs = cb_data;\n-\tchar *ref = xstrfmt(\"refs/bisect%s\", refname);\n-\tstring_list_append(refs, ref);\n+\tchar *bisect_ref = xstrfmt(\"refs/bisect%s\", ref->name);\n+\tstring_list_append(refs, bisect_ref);\n \treturn 0;\n }\n \ndiff --git a/builtin/bisect.c b/builtin/bisect.c\nindex 8b8d870cd1e..5b2024be62d 100644\n--- a/builtin/bisect.c\n+++ b/builtin/bisect.c\n@@ -358,10 +358,7 @@ static int check_and_set_terms(struct bisect_terms *terms, const char *cmd)\n \treturn 0;\n }\n \n-static int inc_nr(const char *refname UNUSED,\n-\t\t  const char *referent UNUSED,\n-\t\t  const struct object_id *oid UNUSED,\n-\t\t  int flag UNUSED, void *cb_data)\n+static int inc_nr(const struct reference *ref UNUSED, void *cb_data)\n {\n \tunsigned int *nr = (unsigned int *)cb_data;\n \t(*nr)++;\n@@ -549,12 +546,11 @@ static int bisect_append_log_quoted(const char **argv)\n \treturn res;\n }\n \n-static int add_bisect_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t  int flags UNUSED, void *cb)\n+static int add_bisect_ref(const struct reference *ref, void *cb)\n {\n \tstruct add_bisect_ref_data *data = cb;\n \n-\tadd_pending_oid(data->revs, refname, oid, data->object_flags);\n+\tadd_pending_oid(data->revs, ref->name, ref->oid, data->object_flags);\n \n \treturn 0;\n }\n@@ -1165,12 +1161,9 @@ static int bisect_visualize(struct bisect_terms *terms, int argc,\n \treturn run_command(&cmd);\n }\n \n-static int get_first_good(const char *refname UNUSED,\n-\t\t\t  const char *referent UNUSED,\n-\t\t\t  const struct object_id *oid,\n-\t\t\t  int flag UNUSED, void *cb_data)\n+static int get_first_good(const struct reference *ref, void *cb_data)\n {\n-\toidcpy(cb_data, oid);\n+\toidcpy(cb_data, ref->oid);\n \treturn 1;\n }\n \ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex f9453473fe2..66b69df6e67 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1063,11 +1063,9 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\treport_tracking(new_branch_info);\n }\n \n-static int add_pending_uninteresting_ref(const char *refname, const char *referent UNUSED,\n-\t\t\t\t\t const struct object_id *oid,\n-\t\t\t\t\t int flags UNUSED, void *cb_data)\n+static int add_pending_uninteresting_ref(const struct reference *ref, void *cb_data)\n {\n-\tadd_pending_oid(cb_data, refname, oid, UNINTERESTING);\n+\tadd_pending_oid(cb_data, ref->name, ref->oid, UNINTERESTING);\n \treturn 0;\n }\n \ndiff --git a/builtin/describe.c b/builtin/describe.c\nindex ffaf8d9f0aa..79545350443 100644\n--- a/builtin/describe.c\n+++ b/builtin/describe.c\n@@ -154,20 +154,19 @@ static void add_to_known_names(const char *path,\n \t}\n }\n \n-static int get_name(const char *path, const char *referent UNUSED, const struct object_id *oid,\n-\t\t    int flag UNUSED, void *cb_data UNUSED)\n+static int get_name(const struct reference *ref, void *cb_data UNUSED)\n {\n \tint is_tag = 0;\n \tstruct object_id peeled;\n \tint is_annotated, prio;\n \tconst char *path_to_match = NULL;\n \n-\tif (skip_prefix(path, \"refs/tags/\", &path_to_match)) {\n+\tif (skip_prefix(ref->name, \"refs/tags/\", &path_to_match)) {\n \t\tis_tag = 1;\n \t} else if (all) {\n \t\tif ((exclude_patterns.nr || patterns.nr) &&\n-\t\t    !skip_prefix(path, \"refs/heads/\", &path_to_match) &&\n-\t\t    !skip_prefix(path, \"refs/remotes/\", &path_to_match)) {\n+\t\t    !skip_prefix(ref->name, \"refs/heads/\", &path_to_match) &&\n+\t\t    !skip_prefix(ref->name, \"refs/remotes/\", &path_to_match)) {\n \t\t\t/* Only accept reference of known type if there are match/exclude patterns */\n \t\t\treturn 0;\n \t\t}\n@@ -209,10 +208,10 @@ static int get_name(const char *path, const char *referent UNUSED, const struct\n \t}\n \n \t/* Is it annotated? */\n-\tif (!peel_iterated_oid(the_repository, oid, &peeled)) {\n-\t\tis_annotated = !oideq(oid, &peeled);\n+\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled)) {\n+\t\tis_annotated = !oideq(ref->oid, &peeled);\n \t} else {\n-\t\toidcpy(&peeled, oid);\n+\t\toidcpy(&peeled, ref->oid);\n \t\tis_annotated = 0;\n \t}\n \n@@ -229,7 +228,8 @@ static int get_name(const char *path, const char *referent UNUSED, const struct\n \telse\n \t\tprio = 0;\n \n-\tadd_to_known_names(all ? path + 5 : path + 10, &peeled, prio, oid);\n+\tadd_to_known_names(all ? ref->name + 5 : ref->name + 10,\n+\t\t\t   &peeled, prio, ref->oid);\n \treturn 0;\n }\n \ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex c7ff3480fb1..7052e6ff215 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -289,13 +289,11 @@ static struct refname_hash_entry *refname_hash_add(struct hashmap *map,\n \treturn ent;\n }\n \n-static int add_one_refname(const char *refname, const char *referent UNUSED,\n-\t\t\t   const struct object_id *oid,\n-\t\t\t   int flag UNUSED, void *cbdata)\n+static int add_one_refname(const struct reference *ref, void *cbdata)\n {\n \tstruct hashmap *refname_map = cbdata;\n \n-\t(void) refname_hash_add(refname_map, refname, oid);\n+\t(void) refname_hash_add(refname_map, ref->name, ref->oid);\n \treturn 0;\n }\n \n@@ -1416,14 +1414,11 @@ static void set_option(struct transport *transport, const char *name, const char\n }\n \n \n-static int add_oid(const char *refname UNUSED,\n-\t\t   const char *referent UNUSED,\n-\t\t   const struct object_id *oid,\n-\t\t   int flags UNUSED, void *cb_data)\n+static int add_oid(const struct reference *ref, void *cb_data)\n {\n \tstruct oid_array *oids = cb_data;\n \n-\toid_array_append(oids, oid);\n+\toid_array_append(oids, ref->oid);\n \treturn 0;\n }\n \ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 8ee95e0d67c..ed4eea16803 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -530,14 +530,13 @@ static int fsck_handle_reflog(const char *logname, void *cb_data)\n \treturn 0;\n }\n \n-static int fsck_handle_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t   int flag UNUSED, void *cb_data UNUSED)\n+static int fsck_handle_ref(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct object *obj;\n \n-\tobj = parse_object(the_repository, oid);\n+\tobj = parse_object(the_repository, ref->oid);\n \tif (!obj) {\n-\t\tif (is_promisor_object(the_repository, oid)) {\n+\t\tif (is_promisor_object(the_repository, ref->oid)) {\n \t\t\t/*\n \t\t\t * Increment default_refs anyway, because this is a\n \t\t\t * valid ref.\n@@ -546,19 +545,19 @@ static int fsck_handle_ref(const char *refname, const char *referent UNUSED, con\n \t\t\t return 0;\n \t\t}\n \t\terror(_(\"%s: invalid sha1 pointer %s\"),\n-\t\t      refname, oid_to_hex(oid));\n+\t\t      ref->name, oid_to_hex(ref->oid));\n \t\terrors_found |= ERROR_REACHABLE;\n \t\t/* We'll continue with the rest despite the error.. */\n \t\treturn 0;\n \t}\n-\tif (obj->type != OBJ_COMMIT && is_branch(refname)) {\n-\t\terror(_(\"%s: not a commit\"), refname);\n+\tif (obj->type != OBJ_COMMIT && is_branch(ref->name)) {\n+\t\terror(_(\"%s: not a commit\"), ref->name);\n \t\terrors_found |= ERROR_REFS;\n \t}\n \tdefault_refs++;\n \tobj->flags |= USED;\n \tfsck_put_object_name(&fsck_walk_options,\n-\t\t\t     oid, \"%s\", refname);\n+\t\t\t     ref->oid, \"%s\", ref->name);\n \tmark_object_reachable(obj);\n \n \treturn 0;\n@@ -580,13 +579,19 @@ static void get_default_heads(void)\n \tworktrees = get_worktrees();\n \tfor (p = worktrees; *p; p++) {\n \t\tstruct worktree *wt = *p;\n-\t\tstruct strbuf ref = STRBUF_INIT;\n+\t\tstruct strbuf refname = STRBUF_INIT;\n \n-\t\tstrbuf_worktree_ref(wt, &ref, \"HEAD\");\n-\t\tfsck_head_link(ref.buf, &head_points_at, &head_oid);\n-\t\tif (head_points_at && !is_null_oid(&head_oid))\n-\t\t\tfsck_handle_ref(ref.buf, NULL, &head_oid, 0, NULL);\n-\t\tstrbuf_release(&ref);\n+\t\tstrbuf_worktree_ref(wt, &refname, \"HEAD\");\n+\t\tfsck_head_link(refname.buf, &head_points_at, &head_oid);\n+\t\tif (head_points_at && !is_null_oid(&head_oid)) {\n+\t\t\tstruct reference ref = {\n+\t\t\t\t.name = refname.buf,\n+\t\t\t\t.oid = &head_oid,\n+\t\t\t};\n+\n+\t\t\tfsck_handle_ref(&ref, NULL);\n+\t\t}\n+\t\tstrbuf_release(&refname);\n \n \t\tif (include_reflogs)\n \t\t\trefs_for_each_reflog(get_worktree_ref_store(wt),\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex e19e13d9788..9de5de175f6 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -1100,24 +1100,21 @@ struct cg_auto_data {\n \tint limit;\n };\n \n-static int dfs_on_ref(const char *refname UNUSED,\n-\t\t      const char *referent UNUSED,\n-\t\t      const struct object_id *oid,\n-\t\t      int flags UNUSED,\n-\t\t      void *cb_data)\n+static int dfs_on_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct cg_auto_data *data = (struct cg_auto_data *)cb_data;\n \tint result = 0;\n+\tconst struct object_id *maybe_peeled = ref->oid;\n \tstruct object_id peeled;\n \tstruct commit_list *stack = NULL;\n \tstruct commit *commit;\n \n-\tif (!peel_iterated_oid(the_repository, oid, &peeled))\n-\t\toid = &peeled;\n-\tif (odb_read_object_info(the_repository->objects, oid, NULL) != OBJ_COMMIT)\n+\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n+\t\tmaybe_peeled = &peeled;\n+\tif (odb_read_object_info(the_repository->objects, maybe_peeled, NULL) != OBJ_COMMIT)\n \t\treturn 0;\n \n-\tcommit = lookup_commit(the_repository, oid);\n+\tcommit = lookup_commit(the_repository, maybe_peeled);\n \tif (!commit)\n \t\treturn 0;\n \tif (repo_parse_commit(the_repository, commit) ||\ndiff --git a/builtin/name-rev.c b/builtin/name-rev.c\nindex 74512e54a38..615f7d1aae4 100644\n--- a/builtin/name-rev.c\n+++ b/builtin/name-rev.c\n@@ -339,10 +339,9 @@ static int cmp_by_tag_and_age(const void *a_, const void *b_)\n \treturn a->taggerdate != b->taggerdate;\n }\n \n-static int name_ref(const char *path, const char *referent UNUSED, const struct object_id *oid,\n-\t\t    int flags UNUSED, void *cb_data)\n+static int name_ref(const struct reference *ref, void *cb_data)\n {\n-\tstruct object *o = parse_object(the_repository, oid);\n+\tstruct object *o = parse_object(the_repository, ref->oid);\n \tstruct name_ref_data *data = cb_data;\n \tint can_abbreviate_output = data->tags_only && data->name_only;\n \tint deref = 0;\n@@ -350,14 +349,14 @@ static int name_ref(const char *path, const char *referent UNUSED, const struct\n \tstruct commit *commit = NULL;\n \ttimestamp_t taggerdate = TIME_MAX;\n \n-\tif (data->tags_only && !starts_with(path, \"refs/tags/\"))\n+\tif (data->tags_only && !starts_with(ref->name, \"refs/tags/\"))\n \t\treturn 0;\n \n \tif (data->exclude_filters.nr) {\n \t\tstruct string_list_item *item;\n \n \t\tfor_each_string_list_item(item, &data->exclude_filters) {\n-\t\t\tif (subpath_matches(path, item->string) >= 0)\n+\t\t\tif (subpath_matches(ref->name, item->string) >= 0)\n \t\t\t\treturn 0;\n \t\t}\n \t}\n@@ -378,7 +377,7 @@ static int name_ref(const char *path, const char *referent UNUSED, const struct\n \t\t\t * shouldn't stop when seeing 'refs/tags/v1.4' matches\n \t\t\t * 'refs/tags/v*'.  We should show it as 'v1.4'.\n \t\t\t */\n-\t\t\tswitch (subpath_matches(path, item->string)) {\n+\t\t\tswitch (subpath_matches(ref->name, item->string)) {\n \t\t\tcase -1: /* did not match */\n \t\t\t\tbreak;\n \t\t\tcase 0: /* matched fully */\n@@ -406,13 +405,13 @@ static int name_ref(const char *path, const char *referent UNUSED, const struct\n \t}\n \tif (o && o->type == OBJ_COMMIT) {\n \t\tcommit = (struct commit *)o;\n-\t\tfrom_tag = starts_with(path, \"refs/tags/\");\n+\t\tfrom_tag = starts_with(ref->name, \"refs/tags/\");\n \t\tif (taggerdate == TIME_MAX)\n \t\t\ttaggerdate = commit->date;\n \t}\n \n-\tadd_to_tip_table(oid, path, can_abbreviate_output, commit, taggerdate,\n-\t\t\t from_tag, deref);\n+\tadd_to_tip_table(ref->oid, ref->name, can_abbreviate_output,\n+\t\t\t commit, taggerdate, from_tag, deref);\n \treturn 0;\n }\n \ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 5bdc44fb2de..39633a0158e 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -831,15 +831,14 @@ static enum write_one_status write_one(struct hashfile *f,\n \treturn WRITE_ONE_WRITTEN;\n }\n \n-static int mark_tagged(const char *path UNUSED, const char *referent UNUSED, const struct object_id *oid,\n-\t\t       int flag UNUSED, void *cb_data UNUSED)\n+static int mark_tagged(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct object_id peeled;\n-\tstruct object_entry *entry = packlist_find(&to_pack, oid);\n+\tstruct object_entry *entry = packlist_find(&to_pack, ref->oid);\n \n \tif (entry)\n \t\tentry->tagged = 1;\n-\tif (!peel_iterated_oid(the_repository, oid, &peeled)) {\n+\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled)) {\n \t\tentry = packlist_find(&to_pack, &peeled);\n \t\tif (entry)\n \t\t\tentry->tagged = 1;\n@@ -3306,13 +3305,12 @@ static void add_tag_chain(const struct object_id *oid)\n \t}\n }\n \n-static int add_ref_tag(const char *tag UNUSED, const char *referent UNUSED, const struct object_id *oid,\n-\t\t       int flag UNUSED, void *cb_data UNUSED)\n+static int add_ref_tag(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct object_id peeled;\n \n-\tif (!peel_iterated_oid(the_repository, oid, &peeled) && obj_is_packed(&peeled))\n-\t\tadd_tag_chain(oid);\n+\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled) && obj_is_packed(&peeled))\n+\t\tadd_tag_chain(ref->oid);\n \treturn 0;\n }\n \n@@ -4533,19 +4531,16 @@ static void record_recent_commit(struct commit *commit, void *data UNUSED)\n \toid_array_append(&recent_objects, &commit->object.oid);\n }\n \n-static int mark_bitmap_preferred_tip(const char *refname,\n-\t\t\t\t     const char *referent UNUSED,\n-\t\t\t\t     const struct object_id *oid,\n-\t\t\t\t     int flags UNUSED,\n-\t\t\t\t     void *data UNUSED)\n+static int mark_bitmap_preferred_tip(const struct reference *ref, void *data UNUSED)\n {\n+\tconst struct object_id *maybe_peeled = ref->oid;\n \tstruct object_id peeled;\n \tstruct object *object;\n \n-\tif (!peel_iterated_oid(the_repository, oid, &peeled))\n-\t\toid = &peeled;\n+\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n+\t\tmaybe_peeled = &peeled;\n \n-\tobject = parse_object_or_die(the_repository, oid, refname);\n+\tobject = parse_object_or_die(the_repository, maybe_peeled, ref->name);\n \tif (object->type == OBJ_COMMIT)\n \t\tobject->flags |= NEEDS_BITMAP;\n \ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex c9288a9c7e3..e8ee0e73217 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -305,13 +305,12 @@ static void show_ref(const char *path, const struct object_id *oid)\n \t}\n }\n \n-static int show_ref_cb(const char *path_full, const char *referent UNUSED, const struct object_id *oid,\n-\t\t       int flag UNUSED, void *data)\n+static int show_ref_cb(const struct reference *ref, void *data)\n {\n \tstruct oidset *seen = data;\n-\tconst char *path = strip_namespace(path_full);\n+\tconst char *path = strip_namespace(ref->name);\n \n-\tif (ref_is_hidden(path, path_full, &hidden_refs))\n+\tif (ref_is_hidden(path, ref->name, &hidden_refs))\n \t\treturn 0;\n \n \t/*\n@@ -320,13 +319,13 @@ static int show_ref_cb(const char *path_full, const char *referent UNUSED, const\n \t * transfer but will otherwise ignore them.\n \t */\n \tif (!path) {\n-\t\tif (oidset_insert(seen, oid))\n+\t\tif (oidset_insert(seen, ref->oid))\n \t\t\treturn 0;\n \t\tpath = \".have\";\n \t} else {\n-\t\toidset_insert(seen, oid);\n+\t\toidset_insert(seen, ref->oid);\n \t}\n-\tshow_ref(path, oid);\n+\tshow_ref(path, ref->oid);\n \treturn 0;\n }\n \ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 8a7ed4299a4..7ffc14ba157 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -570,17 +570,14 @@ struct branches_for_remote {\n \tstruct known_remotes *keep;\n };\n \n-static int add_branch_for_removal(const char *refname,\n-\t\t\t\t  const char *referent UNUSED,\n-\t\t\t\t  const struct object_id *oid UNUSED,\n-\t\t\t\t  int flags UNUSED, void *cb_data)\n+static int add_branch_for_removal(const struct reference *ref, void *cb_data)\n {\n \tstruct branches_for_remote *branches = cb_data;\n \tstruct refspec_item refspec;\n \tstruct known_remote *kr;\n \n \tmemset(&refspec, 0, sizeof(refspec));\n-\trefspec.dst = (char *)refname;\n+\trefspec.dst = (char *)ref->name;\n \tif (remote_find_tracking(branches->remote, &refspec))\n \t\treturn 0;\n \tfree(refspec.src);\n@@ -588,7 +585,7 @@ static int add_branch_for_removal(const char *refname,\n \t/* don't delete a branch if another remote also uses it */\n \tfor (kr = branches->keep->list; kr; kr = kr->next) {\n \t\tmemset(&refspec, 0, sizeof(refspec));\n-\t\trefspec.dst = (char *)refname;\n+\t\trefspec.dst = (char *)ref->name;\n \t\tif (!remote_find_tracking(kr->remote, &refspec)) {\n \t\t\tfree(refspec.src);\n \t\t\treturn 0;\n@@ -596,16 +593,16 @@ static int add_branch_for_removal(const char *refname,\n \t}\n \n \t/* don't delete non-remote-tracking refs */\n-\tif (!starts_with(refname, \"refs/remotes/\")) {\n+\tif (!starts_with(ref->name, \"refs/remotes/\")) {\n \t\t/* advise user how to delete local branches */\n-\t\tif (starts_with(refname, \"refs/heads/\"))\n+\t\tif (starts_with(ref->name, \"refs/heads/\"))\n \t\t\tstring_list_append(branches->skipped,\n-\t\t\t\t\t   abbrev_branch(refname));\n+\t\t\t\t\t   abbrev_branch(ref->name));\n \t\t/* silently skip over other non-remote refs */\n \t\treturn 0;\n \t}\n \n-\tstring_list_append(branches->branches, refname);\n+\tstring_list_append(branches->branches, ref->name);\n \n \treturn 0;\n }\n@@ -713,18 +710,18 @@ static int rename_one_reflog(const char *old_refname,\n \treturn error;\n }\n \n-static int rename_one_ref(const char *old_refname, const char *referent,\n-\t\t\t  const struct object_id *oid,\n-\t\t\t  int flags, void *cb_data)\n+static int rename_one_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct strbuf new_referent = STRBUF_INIT;\n \tstruct strbuf new_refname = STRBUF_INIT;\n \tstruct rename_info *rename = cb_data;\n+\tconst struct object_id *oid = ref->oid;\n+\tconst char *referent = ref->target;\n \tint error;\n \n-\tcompute_renamed_ref(rename, old_refname, &new_refname);\n+\tcompute_renamed_ref(rename, ref->name, &new_refname);\n \n-\tif (flags & REF_ISSYMREF) {\n+\tif (ref->flags & REF_ISSYMREF) {\n \t\t/*\n \t\t * Stupidly enough `referent` is not pointing to the immediate\n \t\t * target of a symref, but it's the recursively resolved value.\n@@ -732,25 +729,25 @@ static int rename_one_ref(const char *old_refname, const char *referent,\n \t\t * unborn symrefs don't have any value for the `referent` at all.\n \t\t */\n \t\treferent = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),\n-\t\t\t\t\t\t   old_refname, RESOLVE_REF_NO_RECURSE,\n+\t\t\t\t\t\t   ref->name, RESOLVE_REF_NO_RECURSE,\n \t\t\t\t\t\t   NULL, NULL);\n \t\tcompute_renamed_ref(rename, referent, &new_referent);\n \t\toid = NULL;\n \t}\n \n-\terror = ref_transaction_delete(rename->transaction, old_refname,\n+\terror = ref_transaction_delete(rename->transaction, ref->name,\n \t\t\t\t       oid, referent, REF_NO_DEREF, NULL, rename->err);\n \tif (error < 0)\n \t\tgoto out;\n \n \terror = ref_transaction_update(rename->transaction, new_refname.buf, oid, null_oid(the_hash_algo),\n-\t\t\t\t       (flags & REF_ISSYMREF) ? new_referent.buf : NULL, NULL,\n+\t\t\t\t       (ref->flags & REF_ISSYMREF) ? new_referent.buf : NULL, NULL,\n \t\t\t\t       REF_SKIP_CREATE_REFLOG | REF_NO_DEREF | REF_SKIP_OID_VERIFICATION,\n \t\t\t\t       NULL, rename->err);\n \tif (error < 0)\n \t\tgoto out;\n \n-\terror = rename_one_reflog(old_refname, oid, rename);\n+\terror = rename_one_reflog(ref->name, oid, rename);\n \tif (error < 0)\n \t\tgoto out;\n \n@@ -1125,19 +1122,16 @@ static void free_remote_ref_states(struct ref_states *states)\n \tstring_list_clear_func(&states->push, clear_push_info);\n }\n \n-static int append_ref_to_tracked_list(const char *refname,\n-\t\t\t\t      const char *referent UNUSED,\n-\t\t\t\t      const struct object_id *oid UNUSED,\n-\t\t\t\t      int flags, void *cb_data)\n+static int append_ref_to_tracked_list(const struct reference *ref, void *cb_data)\n {\n \tstruct ref_states *states = cb_data;\n \tstruct refspec_item refspec;\n \n-\tif (flags & REF_ISSYMREF)\n+\tif (ref->flags & REF_ISSYMREF)\n \t\treturn 0;\n \n \tmemset(&refspec, 0, sizeof(refspec));\n-\trefspec.dst = (char *)refname;\n+\trefspec.dst = (char *)ref->name;\n \tif (!remote_find_tracking(states->remote, &refspec)) {\n \t\tstring_list_append(&states->tracked, abbrev_branch(refspec.src));\n \t\tfree(refspec.src);\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 900b560a77d..4c62c5ab58b 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -47,30 +47,27 @@ struct show_data {\n \tenum replace_format format;\n };\n \n-static int show_reference(const char *refname,\n-\t\t\t  const char *referent UNUSED,\n-\t\t\t  const struct object_id *oid,\n-\t\t\t  int flag UNUSED, void *cb_data)\n+static int show_reference(const struct reference *ref, void *cb_data)\n {\n \tstruct show_data *data = cb_data;\n \n-\tif (!wildmatch(data->pattern, refname, 0)) {\n+\tif (!wildmatch(data->pattern, ref->name, 0)) {\n \t\tif (data->format == REPLACE_FORMAT_SHORT)\n-\t\t\tprintf(\"%s\\n\", refname);\n+\t\t\tprintf(\"%s\\n\", ref->name);\n \t\telse if (data->format == REPLACE_FORMAT_MEDIUM)\n-\t\t\tprintf(\"%s -> %s\\n\", refname, oid_to_hex(oid));\n+\t\t\tprintf(\"%s -> %s\\n\", ref->name, oid_to_hex(ref->oid));\n \t\telse { /* data->format == REPLACE_FORMAT_LONG */\n \t\t\tstruct object_id object;\n \t\t\tenum object_type obj_type, repl_type;\n \n-\t\t\tif (repo_get_oid(data->repo, refname, &object))\n-\t\t\t\treturn error(_(\"failed to resolve '%s' as a valid ref\"), refname);\n+\t\t\tif (repo_get_oid(data->repo, ref->name, &object))\n+\t\t\t\treturn error(_(\"failed to resolve '%s' as a valid ref\"), ref->name);\n \n \t\t\tobj_type = odb_read_object_info(data->repo->objects, &object, NULL);\n-\t\t\trepl_type = odb_read_object_info(data->repo->objects, oid, NULL);\n+\t\t\trepl_type = odb_read_object_info(data->repo->objects, ref->oid, NULL);\n \n-\t\t\tprintf(\"%s (%s) -> %s (%s)\\n\", refname, type_name(obj_type),\n-\t\t\t       oid_to_hex(oid), type_name(repl_type));\n+\t\t\tprintf(\"%s (%s) -> %s (%s)\\n\", ref->name, type_name(obj_type),\n+\t\t\t       oid_to_hex(ref->oid), type_name(repl_type));\n \t\t}\n \t}\n \ndiff --git a/builtin/repo.c b/builtin/repo.c\nindex 9d4749f79be..f26640bd6ea 100644\n--- a/builtin/repo.c\n+++ b/builtin/repo.c\n@@ -366,16 +366,13 @@ struct count_references_data {\n \tstruct progress *progress;\n };\n \n-static int count_references(const char *refname,\n-\t\t\t    const char *referent UNUSED,\n-\t\t\t    const struct object_id *oid,\n-\t\t\t    int flags UNUSED, void *cb_data)\n+static int count_references(const struct reference *ref, void *cb_data)\n {\n \tstruct count_references_data *data = cb_data;\n \tstruct ref_stats *stats = data->stats;\n \tsize_t ref_count;\n \n-\tswitch (ref_kind_from_refname(refname)) {\n+\tswitch (ref_kind_from_refname(ref->name)) {\n \tcase FILTER_REFS_BRANCHES:\n \t\tstats->branches++;\n \t\tbreak;\n@@ -396,7 +393,7 @@ static int count_references(const char *refname,\n \t * While iterating through references for counting, also add OIDs in\n \t * preparation for the path walk.\n \t */\n-\tadd_pending_oid(data->revs, NULL, oid, 0);\n+\tadd_pending_oid(data->revs, NULL, ref->oid, 0);\n \n \tref_count = get_total_reference_count(stats);\n \tdisplay_progress(data->progress, ref_count);\ndiff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\nindex 9da92b990d0..3578591b4f2 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -217,19 +217,17 @@ static int show_default(void)\n \treturn 0;\n }\n \n-static int show_reference(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t  int flag UNUSED, void *cb_data UNUSED)\n+static int show_reference(const struct reference *ref, void *cb_data UNUSED)\n {\n-\tif (ref_excluded(&ref_excludes, refname))\n+\tif (ref_excluded(&ref_excludes, ref->name))\n \t\treturn 0;\n-\tshow_rev(NORMAL, oid, refname);\n+\tshow_rev(NORMAL, ref->oid, ref->name);\n \treturn 0;\n }\n \n-static int anti_reference(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t  int flag UNUSED, void *cb_data UNUSED)\n+static int anti_reference(const struct reference *ref, void *cb_data UNUSED)\n {\n-\tshow_rev(REVERSED, oid, refname);\n+\tshow_rev(REVERSED, ref->oid, ref->name);\n \treturn 0;\n }\n \ndiff --git a/builtin/show-branch.c b/builtin/show-branch.c\nindex 441babf2e35..10475a6b5ed 100644\n--- a/builtin/show-branch.c\n+++ b/builtin/show-branch.c\n@@ -413,34 +413,32 @@ static int append_ref(const char *refname, const struct object_id *oid,\n \treturn 0;\n }\n \n-static int append_head_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t   int flag UNUSED, void *cb_data UNUSED)\n+static int append_head_ref(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct object_id tmp;\n \tint ofs = 11;\n-\tif (!starts_with(refname, \"refs/heads/\"))\n+\tif (!starts_with(ref->name, \"refs/heads/\"))\n \t\treturn 0;\n \t/* If both heads/foo and tags/foo exists, get_sha1 would\n \t * get confused.\n \t */\n-\tif (repo_get_oid(the_repository, refname + ofs, &tmp) || !oideq(&tmp, oid))\n+\tif (repo_get_oid(the_repository, ref->name + ofs, &tmp) || !oideq(&tmp, ref->oid))\n \t\tofs = 5;\n-\treturn append_ref(refname + ofs, oid, 0);\n+\treturn append_ref(ref->name + ofs, ref->oid, 0);\n }\n \n-static int append_remote_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t     int flag UNUSED, void *cb_data UNUSED)\n+static int append_remote_ref(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct object_id tmp;\n \tint ofs = 13;\n-\tif (!starts_with(refname, \"refs/remotes/\"))\n+\tif (!starts_with(ref->name, \"refs/remotes/\"))\n \t\treturn 0;\n \t/* If both heads/foo and tags/foo exists, get_sha1 would\n \t * get confused.\n \t */\n-\tif (repo_get_oid(the_repository, refname + ofs, &tmp) || !oideq(&tmp, oid))\n+\tif (repo_get_oid(the_repository, ref->name + ofs, &tmp) || !oideq(&tmp, ref->oid))\n \t\tofs = 5;\n-\treturn append_ref(refname + ofs, oid, 0);\n+\treturn append_ref(ref->name + ofs, ref->oid, 0);\n }\n \n static int append_tag_ref(const char *refname, const struct object_id *oid,\n@@ -454,27 +452,26 @@ static int append_tag_ref(const char *refname, const struct object_id *oid,\n static const char *match_ref_pattern = NULL;\n static int match_ref_slash = 0;\n \n-static int append_matching_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t       int flag, void *cb_data)\n+static int append_matching_ref(const struct reference *ref, void *cb_data)\n {\n \t/* we want to allow pattern hold/<asterisk> to show all\n \t * branches under refs/heads/hold/, and v0.99.9? to show\n \t * refs/tags/v0.99.9a and friends.\n \t */\n \tconst char *tail;\n-\tint slash = count_slashes(refname);\n-\tfor (tail = refname; *tail && match_ref_slash < slash; )\n+\tint slash = count_slashes(ref->name);\n+\tfor (tail = ref->name; *tail && match_ref_slash < slash; )\n \t\tif (*tail++ == '/')\n \t\t\tslash--;\n \tif (!*tail)\n \t\treturn 0;\n \tif (wildmatch(match_ref_pattern, tail, 0))\n \t\treturn 0;\n-\tif (starts_with(refname, \"refs/heads/\"))\n-\t\treturn append_head_ref(refname, NULL, oid, flag, cb_data);\n-\tif (starts_with(refname, \"refs/tags/\"))\n-\t\treturn append_tag_ref(refname, oid, flag, cb_data);\n-\treturn append_ref(refname, oid, 0);\n+\tif (starts_with(ref->name, \"refs/heads/\"))\n+\t\treturn append_head_ref(ref, cb_data);\n+\tif (starts_with(ref->name, \"refs/tags/\"))\n+\t\treturn append_tag_ref(ref->name, ref->oid, ref->flags, cb_data);\n+\treturn append_ref(ref->name, ref->oid, 0);\n }\n \n static void snarf_refs(int head, int remotes)\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex 0b6f9edf86c..4803b5e5986 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -66,26 +66,25 @@ struct show_ref_data {\n \tint show_head;\n };\n \n-static int show_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t    int flag UNUSED, void *cbdata)\n+static int show_ref(const struct reference *ref, void *cbdata)\n {\n \tstruct show_ref_data *data = cbdata;\n \n-\tif (data->show_head && !strcmp(refname, \"HEAD\"))\n+\tif (data->show_head && !strcmp(ref->name, \"HEAD\"))\n \t\tgoto match;\n \n \tif (data->patterns) {\n-\t\tint reflen = strlen(refname);\n+\t\tint reflen = strlen(ref->name);\n \t\tconst char **p = data->patterns, *m;\n \t\twhile ((m = *p++) != NULL) {\n \t\t\tint len = strlen(m);\n \t\t\tif (len > reflen)\n \t\t\t\tcontinue;\n-\t\t\tif (memcmp(m, refname + reflen - len, len))\n+\t\t\tif (memcmp(m, ref->name + reflen - len, len))\n \t\t\t\tcontinue;\n \t\t\tif (len == reflen)\n \t\t\t\tgoto match;\n-\t\t\tif (refname[reflen - len - 1] == '/')\n+\t\t\tif (ref->name[reflen - len - 1] == '/')\n \t\t\t\tgoto match;\n \t\t}\n \t\treturn 0;\n@@ -94,18 +93,15 @@ static int show_ref(const char *refname, const char *referent UNUSED, const stru\n match:\n \tdata->found_match++;\n \n-\tshow_one(data->show_one_opts, refname, oid);\n+\tshow_one(data->show_one_opts, ref->name, ref->oid);\n \n \treturn 0;\n }\n \n-static int add_existing(const char *refname,\n-\t\t\tconst char *referent UNUSED,\n-\t\t\tconst struct object_id *oid UNUSED,\n-\t\t\tint flag UNUSED, void *cbdata)\n+static int add_existing(const struct reference *ref, void *cbdata)\n {\n \tstruct string_list *list = (struct string_list *)cbdata;\n-\tstring_list_insert(list, refname);\n+\tstring_list_insert(list, ref->name);\n \treturn 0;\n }\n \ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex fcd73abe533..35f6cf735e5 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -593,16 +593,12 @@ static void print_status(unsigned int flags, char state, const char *path,\n \tprintf(\"\\n\");\n }\n \n-static int handle_submodule_head_ref(const char *refname UNUSED,\n-\t\t\t\t     const char *referent UNUSED,\n-\t\t\t\t     const struct object_id *oid,\n-\t\t\t\t     int flags UNUSED,\n-\t\t\t\t     void *cb_data)\n+static int handle_submodule_head_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct object_id *output = cb_data;\n \n-\tif (oid)\n-\t\toidcpy(output, oid);\n+\tif (ref->oid)\n+\t\toidcpy(output, ref->oid);\n \n \treturn 0;\n }\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 812774a5ca9..b7f323b5e4d 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -635,11 +635,7 @@ static void print_preparing_worktree_line(int detach,\n  *\n  * Returns 0 on failure and non-zero on success.\n  */\n-static int first_valid_ref(const char *refname UNUSED,\n-\t\t\t   const char *referent UNUSED,\n-\t\t\t   const struct object_id *oid UNUSED,\n-\t\t\t   int flags UNUSED,\n-\t\t\t   void *cb_data UNUSED)\n+static int first_valid_ref(const struct reference *ref UNUSED, void *cb_data UNUSED)\n {\n \treturn 1;\n }\ndiff --git a/commit-graph.c b/commit-graph.c\nindex 474454db73d..f91af416259 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -1851,18 +1851,16 @@ struct refs_cb_data {\n \tstruct progress *progress;\n };\n \n-static int add_ref_to_set(const char *refname UNUSED,\n-\t\t\t  const char *referent UNUSED,\n-\t\t\t  const struct object_id *oid,\n-\t\t\t  int flags UNUSED, void *cb_data)\n+static int add_ref_to_set(const struct reference *ref, void *cb_data)\n {\n+\tconst struct object_id *maybe_peeled = ref->oid;\n \tstruct object_id peeled;\n \tstruct refs_cb_data *data = (struct refs_cb_data *)cb_data;\n \n-\tif (!peel_iterated_oid(data->repo, oid, &peeled))\n-\t\toid = &peeled;\n-\tif (odb_read_object_info(data->repo->objects, oid, NULL) == OBJ_COMMIT)\n-\t\toidset_insert(data->commits, oid);\n+\tif (!peel_iterated_oid(data->repo, ref->oid, &peeled))\n+\t\tmaybe_peeled = &peeled;\n+\tif (odb_read_object_info(data->repo->objects, maybe_peeled, NULL) == OBJ_COMMIT)\n+\t\toidset_insert(data->commits, maybe_peeled);\n \n \tdisplay_progress(data->progress, oidset_size(data->commits));\n \ndiff --git a/delta-islands.c b/delta-islands.c\nindex 36c94799d69..7cfebc4162b 100644\n--- a/delta-islands.c\n+++ b/delta-islands.c\n@@ -390,8 +390,7 @@ static void add_ref_to_island(kh_str_t *remote_islands, const char *island_name,\n \trl->hash += sha_core;\n }\n \n-static int find_island_for_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t       int flags UNUSED, void *cb)\n+static int find_island_for_ref(const struct reference *ref, void *cb)\n {\n \tstruct island_load_data *ild = cb;\n \n@@ -406,7 +405,7 @@ static int find_island_for_ref(const char *refname, const char *referent UNUSED,\n \n \t/* walk backwards to get last-one-wins ordering */\n \tfor (i = ild->nr - 1; i >= 0; i--) {\n-\t\tif (!regexec(&ild->rx[i], refname,\n+\t\tif (!regexec(&ild->rx[i], ref->name,\n \t\t\t     ARRAY_SIZE(matches), matches, 0))\n \t\t\tbreak;\n \t}\n@@ -428,10 +427,10 @@ static int find_island_for_ref(const char *refname, const char *referent UNUSED,\n \t\tif (island_name.len)\n \t\t\tstrbuf_addch(&island_name, '-');\n \n-\t\tstrbuf_add(&island_name, refname + match->rm_so, match->rm_eo - match->rm_so);\n+\t\tstrbuf_add(&island_name, ref->name + match->rm_so, match->rm_eo - match->rm_so);\n \t}\n \n-\tadd_ref_to_island(ild->remote_islands, island_name.buf, oid);\n+\tadd_ref_to_island(ild->remote_islands, island_name.buf, ref->oid);\n \tstrbuf_release(&island_name);\n \treturn 0;\n }\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex fe7a84bf2f9..78c45d4a155 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -188,13 +188,9 @@ static int rev_list_insert_ref(struct fetch_negotiator *negotiator,\n \treturn 0;\n }\n \n-static int rev_list_insert_ref_oid(const char *refname UNUSED,\n-\t\t\t\t   const char *referent UNUSED,\n-\t\t\t\t   const struct object_id *oid,\n-\t\t\t\t   int flag UNUSED,\n-\t\t\t\t   void *cb_data)\n+static int rev_list_insert_ref_oid(const struct reference *ref, void *cb_data)\n {\n-\treturn rev_list_insert_ref(cb_data, oid);\n+\treturn rev_list_insert_ref(cb_data, ref->oid);\n }\n \n enum ack_type {\n@@ -616,13 +612,9 @@ static int mark_complete(const struct object_id *oid)\n \treturn 0;\n }\n \n-static int mark_complete_oid(const char *refname UNUSED,\n-\t\t\t     const char *referent UNUSED,\n-\t\t\t     const struct object_id *oid,\n-\t\t\t     int flag UNUSED,\n-\t\t\t     void *cb_data UNUSED)\n+static int mark_complete_oid(const struct reference *ref, void *cb_data UNUSED)\n {\n-\treturn mark_complete(oid);\n+\treturn mark_complete(ref->oid);\n }\n \n static void mark_recent_complete_commits(struct fetch_pack_args *args,\ndiff --git a/help.c b/help.c\nindex 5854dd4a7e4..20e114432d7 100644\n--- a/help.c\n+++ b/help.c\n@@ -851,18 +851,16 @@ struct similar_ref_cb {\n \tstruct string_list *similar_refs;\n };\n \n-static int append_similar_ref(const char *refname, const char *referent UNUSED,\n-\t\t\t      const struct object_id *oid UNUSED,\n-\t\t\t      int flags UNUSED, void *cb_data)\n+static int append_similar_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct similar_ref_cb *cb = (struct similar_ref_cb *)(cb_data);\n-\tchar *branch = strrchr(refname, '/') + 1;\n+\tchar *branch = strrchr(ref->name, '/') + 1;\n \n \t/* A remote branch of the same name is deemed similar */\n-\tif (starts_with(refname, \"refs/remotes/\") &&\n+\tif (starts_with(ref->name, \"refs/remotes/\") &&\n \t    !strcmp(branch, cb->base_ref))\n \t\tstring_list_append_nodup(cb->similar_refs,\n-\t\t\t\t\t refs_shorten_unambiguous_ref(get_main_ref_store(the_repository), refname, 1));\n+\t\t\t\t\t refs_shorten_unambiguous_ref(get_main_ref_store(the_repository), ref->name, 1));\n \treturn 0;\n }\n \ndiff --git a/http-backend.c b/http-backend.c\nindex 9084058f1e9..92e1733f140 100644\n--- a/http-backend.c\n+++ b/http-backend.c\n@@ -513,18 +513,17 @@ static void run_service(const char **argv, int buffer_input)\n \t\texit(1);\n }\n \n-static int show_text_ref(const char *name, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t int flag UNUSED, void *cb_data)\n+static int show_text_ref(const struct reference *ref, void *cb_data)\n {\n-\tconst char *name_nons = strip_namespace(name);\n+\tconst char *name_nons = strip_namespace(ref->name);\n \tstruct strbuf *buf = cb_data;\n-\tstruct object *o = parse_object(the_repository, oid);\n+\tstruct object *o = parse_object(the_repository, ref->oid);\n \tif (!o)\n \t\treturn 0;\n \n-\tstrbuf_addf(buf, \"%s\\t%s\\n\", oid_to_hex(oid), name_nons);\n+\tstrbuf_addf(buf, \"%s\\t%s\\n\", oid_to_hex(ref->oid), name_nons);\n \tif (o->type == OBJ_TAG) {\n-\t\to = deref_tag(the_repository, o, name, 0);\n+\t\to = deref_tag(the_repository, o, ref->name, 0);\n \t\tif (!o)\n \t\t\treturn 0;\n \t\tstrbuf_addf(buf, \"%s\\t%s^{}\\n\", oid_to_hex(&o->oid),\n@@ -569,21 +568,20 @@ static void get_info_refs(struct strbuf *hdr, char *arg UNUSED)\n \tstrbuf_release(&buf);\n }\n \n-static int show_head_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t int flag, void *cb_data)\n+static int show_head_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct strbuf *buf = cb_data;\n \n-\tif (flag & REF_ISSYMREF) {\n+\tif (ref->flags & REF_ISSYMREF) {\n \t\tconst char *target = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),\n-\t\t\t\t\t\t\t     refname,\n+\t\t\t\t\t\t\t     ref->name,\n \t\t\t\t\t\t\t     RESOLVE_REF_READING,\n \t\t\t\t\t\t\t     NULL, NULL);\n \n \t\tif (target)\n \t\t\tstrbuf_addf(buf, \"ref: %s\\n\", strip_namespace(target));\n \t} else {\n-\t\tstrbuf_addf(buf, \"%s\\n\", oid_to_hex(oid));\n+\t\tstrbuf_addf(buf, \"%s\\n\", oid_to_hex(ref->oid));\n \t}\n \n \treturn 0;\ndiff --git a/log-tree.c b/log-tree.c\nindex 7d917f2a83d..1729b0c2012 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -147,9 +147,7 @@ static int ref_filter_match(const char *refname,\n \treturn 1;\n }\n \n-static int add_ref_decoration(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t      int flags UNUSED,\n-\t\t\t      void *cb_data)\n+static int add_ref_decoration(const struct reference *ref, void *cb_data)\n {\n \tint i;\n \tstruct object *obj;\n@@ -158,16 +156,16 @@ static int add_ref_decoration(const char *refname, const char *referent UNUSED,\n \tstruct decoration_filter *filter = (struct decoration_filter *)cb_data;\n \tconst char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref;\n \n-\tif (filter && !ref_filter_match(refname, filter))\n+\tif (filter && !ref_filter_match(ref->name, filter))\n \t\treturn 0;\n \n-\tif (starts_with(refname, git_replace_ref_base)) {\n+\tif (starts_with(ref->name, git_replace_ref_base)) {\n \t\tstruct object_id original_oid;\n \t\tif (!replace_refs_enabled(the_repository))\n \t\t\treturn 0;\n-\t\tif (get_oid_hex(refname + strlen(git_replace_ref_base),\n+\t\tif (get_oid_hex(ref->name + strlen(git_replace_ref_base),\n \t\t\t\t&original_oid)) {\n-\t\t\twarning(\"invalid replace ref %s\", refname);\n+\t\t\twarning(\"invalid replace ref %s\", ref->name);\n \t\t\treturn 0;\n \t\t}\n \t\tobj = parse_object(the_repository, &original_oid);\n@@ -176,10 +174,10 @@ static int add_ref_decoration(const char *refname, const char *referent UNUSED,\n \t\treturn 0;\n \t}\n \n-\tobjtype = odb_read_object_info(the_repository->objects, oid, NULL);\n+\tobjtype = odb_read_object_info(the_repository->objects, ref->oid, NULL);\n \tif (objtype < 0)\n \t\treturn 0;\n-\tobj = lookup_object_by_type(the_repository, oid, objtype);\n+\tobj = lookup_object_by_type(the_repository, ref->oid, objtype);\n \n \tfor (i = 0; i < ARRAY_SIZE(ref_namespace); i++) {\n \t\tstruct ref_namespace_info *info = &ref_namespace[i];\n@@ -187,24 +185,24 @@ static int add_ref_decoration(const char *refname, const char *referent UNUSED,\n \t\tif (!info->decoration)\n \t\t\tcontinue;\n \t\tif (info->exact) {\n-\t\t\tif (!strcmp(refname, info->ref)) {\n+\t\t\tif (!strcmp(ref->name, info->ref)) {\n \t\t\t\tdeco_type = info->decoration;\n \t\t\t\tbreak;\n \t\t\t}\n-\t\t} else if (starts_with(refname, info->ref)) {\n+\t\t} else if (starts_with(ref->name, info->ref)) {\n \t\t\tdeco_type = info->decoration;\n \t\t\tbreak;\n \t\t}\n \t}\n \n-\tadd_name_decoration(deco_type, refname, obj);\n+\tadd_name_decoration(deco_type, ref->name, obj);\n \twhile (obj->type == OBJ_TAG) {\n \t\tif (!obj->parsed)\n \t\t\tparse_object(the_repository, &obj->oid);\n \t\tobj = ((struct tag *)obj)->tagged;\n \t\tif (!obj)\n \t\t\tbreak;\n-\t\tadd_name_decoration(DECORATION_REF_TAG, refname, obj);\n+\t\tadd_name_decoration(DECORATION_REF_TAG, ref->name, obj);\n \t}\n \treturn 0;\n }\ndiff --git a/ls-refs.c b/ls-refs.c\nindex c47acde07f3..64d02723691 100644\n--- a/ls-refs.c\n+++ b/ls-refs.c\n@@ -75,42 +75,42 @@ struct ls_refs_data {\n \tunsigned unborn : 1;\n };\n \n-static int send_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t    int flag, void *cb_data)\n+static int send_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct ls_refs_data *data = cb_data;\n-\tconst char *refname_nons = strip_namespace(refname);\n+\tconst char *refname_nons = strip_namespace(ref->name);\n \n \tstrbuf_reset(&data->buf);\n \n-\tif (ref_is_hidden(refname_nons, refname, &data->hidden_refs))\n+\tif (ref_is_hidden(refname_nons, ref->name, &data->hidden_refs))\n \t\treturn 0;\n \n \tif (!ref_match(&data->prefixes, refname_nons))\n \t\treturn 0;\n \n-\tif (oid)\n-\t\tstrbuf_addf(&data->buf, \"%s %s\", oid_to_hex(oid), refname_nons);\n+\tif (ref->oid)\n+\t\tstrbuf_addf(&data->buf, \"%s %s\", oid_to_hex(ref->oid), refname_nons);\n \telse\n \t\tstrbuf_addf(&data->buf, \"unborn %s\", refname_nons);\n-\tif (data->symrefs && flag & REF_ISSYMREF) {\n+\tif (data->symrefs && ref->flags & REF_ISSYMREF) {\n+\t\tint unused_flag;\n \t\tstruct object_id unused;\n \t\tconst char *symref_target = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),\n-\t\t\t\t\t\t\t\t    refname,\n+\t\t\t\t\t\t\t\t    ref->name,\n \t\t\t\t\t\t\t\t    0,\n \t\t\t\t\t\t\t\t    &unused,\n-\t\t\t\t\t\t\t\t    &flag);\n+\t\t\t\t\t\t\t\t    &unused_flag);\n \n \t\tif (!symref_target)\n-\t\t\tdie(\"'%s' is a symref but it is not?\", refname);\n+\t\t\tdie(\"'%s' is a symref but it is not?\", ref->name);\n \n \t\tstrbuf_addf(&data->buf, \" symref-target:%s\",\n \t\t\t    strip_namespace(symref_target));\n \t}\n \n-\tif (data->peel && oid) {\n+\tif (data->peel && ref->oid) {\n \t\tstruct object_id peeled;\n-\t\tif (!peel_iterated_oid(the_repository, oid, &peeled))\n+\t\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n \t\t\tstrbuf_addf(&data->buf, \" peeled:%s\", oid_to_hex(&peeled));\n \t}\n \n@@ -131,9 +131,17 @@ static void send_possibly_unborn_head(struct ls_refs_data *data)\n \tif (!refs_resolve_ref_unsafe(get_main_ref_store(the_repository), namespaced.buf, 0, &oid, &flag))\n \t\treturn; /* bad ref */\n \toid_is_null = is_null_oid(&oid);\n+\n \tif (!oid_is_null ||\n-\t    (data->unborn && data->symrefs && (flag & REF_ISSYMREF)))\n-\t\tsend_ref(namespaced.buf, NULL, oid_is_null ? NULL : &oid, flag, data);\n+\t    (data->unborn && data->symrefs && (flag & REF_ISSYMREF))) {\n+\t\tstruct reference ref = {\n+\t\t\t.name = namespaced.buf,\n+\t\t\t.oid = oid_is_null ? NULL : &oid,\n+\t\t\t.flags = flag,\n+\t\t};\n+\n+\t\tsend_ref(&ref, data);\n+\t}\n \tstrbuf_release(&namespaced);\n }\n \ndiff --git a/midx-write.c b/midx-write.c\nindex c73010df6d3..f4dd875747a 100644\n--- a/midx-write.c\n+++ b/midx-write.c\n@@ -697,28 +697,27 @@ static void prepare_midx_packing_data(struct packing_data *pdata,\n \ttrace2_region_leave(\"midx\", \"prepare_midx_packing_data\", ctx->repo);\n }\n \n-static int add_ref_to_pending(const char *refname, const char *referent UNUSED,\n-\t\t\t      const struct object_id *oid,\n-\t\t\t      int flag, void *cb_data)\n+static int add_ref_to_pending(const struct reference *ref, void *cb_data)\n {\n \tstruct rev_info *revs = (struct rev_info*)cb_data;\n+\tconst struct object_id *maybe_peeled = ref->oid;\n \tstruct object_id peeled;\n \tstruct object *object;\n \n-\tif ((flag & REF_ISSYMREF) && (flag & REF_ISBROKEN)) {\n-\t\twarning(\"symbolic ref is dangling: %s\", refname);\n+\tif ((ref->flags & REF_ISSYMREF) && (ref->flags & REF_ISBROKEN)) {\n+\t\twarning(\"symbolic ref is dangling: %s\", ref->name);\n \t\treturn 0;\n \t}\n \n-\tif (!peel_iterated_oid(revs->repo, oid, &peeled))\n-\t\toid = &peeled;\n+\tif (!peel_iterated_oid(revs->repo, ref->oid, &peeled))\n+\t\tmaybe_peeled = &peeled;\n \n-\tobject = parse_object_or_die(revs->repo, oid, refname);\n+\tobject = parse_object_or_die(revs->repo, maybe_peeled, ref->name);\n \tif (object->type != OBJ_COMMIT)\n \t\treturn 0;\n \n \tadd_pending_object(revs, object, \"\");\n-\tif (bitmap_is_preferred_refname(revs->repo, refname))\n+\tif (bitmap_is_preferred_refname(revs->repo, ref->name))\n \t\tobject->flags |= NEEDS_BITMAP;\n \treturn 0;\n }\ndiff --git a/negotiator/default.c b/negotiator/default.c\nindex c479da9b091..116dedcf830 100644\n--- a/negotiator/default.c\n+++ b/negotiator/default.c\n@@ -38,11 +38,10 @@ static void rev_list_push(struct negotiation_state *ns,\n \t}\n }\n \n-static int clear_marks(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t       int flag UNUSED,\n-\t\t       void *cb_data UNUSED)\n+static int clear_marks(const struct reference *ref, void *cb_data UNUSED)\n {\n-\tstruct object *o = deref_tag(the_repository, parse_object(the_repository, oid), refname, 0);\n+\tstruct object *o = deref_tag(the_repository, parse_object(the_repository, ref->oid),\n+\t\t\t\t     ref->name, 0);\n \n \tif (o && o->type == OBJ_COMMIT)\n \t\tclear_commit_marks((struct commit *)o,\ndiff --git a/negotiator/skipping.c b/negotiator/skipping.c\nindex 616df6bf3af..0a272130fb1 100644\n--- a/negotiator/skipping.c\n+++ b/negotiator/skipping.c\n@@ -75,11 +75,10 @@ static struct entry *rev_list_push(struct data *data, struct commit *commit, int\n \treturn entry;\n }\n \n-static int clear_marks(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t       int flag UNUSED,\n-\t\t       void *cb_data UNUSED)\n+static int clear_marks(const struct reference *ref, void *cb_data UNUSED)\n {\n-\tstruct object *o = deref_tag(the_repository, parse_object(the_repository, oid), refname, 0);\n+\tstruct object *o = deref_tag(the_repository, parse_object(the_repository, ref->oid),\n+\t\t\t\t     ref->name, 0);\n \n \tif (o && o->type == OBJ_COMMIT)\n \t\tclear_commit_marks((struct commit *)o,\ndiff --git a/notes.c b/notes.c\nindex 9a2e9181fe6..8e00fd8c470 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -938,13 +938,11 @@ int combine_notes_cat_sort_uniq(struct object_id *cur_oid,\n \treturn ret;\n }\n \n-static int string_list_add_one_ref(const char *refname, const char *referent UNUSED,\n-\t\t\t\t   const struct object_id *oid UNUSED,\n-\t\t\t\t   int flag UNUSED, void *cb)\n+static int string_list_add_one_ref(const struct reference *ref, void *cb)\n {\n \tstruct string_list *refs = cb;\n-\tif (!unsorted_string_list_has_string(refs, refname))\n-\t\tstring_list_append(refs, refname);\n+\tif (!unsorted_string_list_has_string(refs, ref->name))\n+\t\tstring_list_append(refs, ref->name);\n \treturn 0;\n }\n \ndiff --git a/object-name.c b/object-name.c\nindex f6902e140dd..7e8109f25fb 100644\n--- a/object-name.c\n+++ b/object-name.c\n@@ -1444,18 +1444,16 @@ struct handle_one_ref_cb {\n \tstruct commit_list **list;\n };\n \n-static int handle_one_ref(const char *path, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t  int flag UNUSED,\n-\t\t\t  void *cb_data)\n+static int handle_one_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct handle_one_ref_cb *cb = cb_data;\n \tstruct commit_list **list = cb->list;\n-\tstruct object *object = parse_object(cb->repo, oid);\n+\tstruct object *object = parse_object(cb->repo, ref->oid);\n \tif (!object)\n \t\treturn 0;\n \tif (object->type == OBJ_TAG) {\n-\t\tobject = deref_tag(cb->repo, object, path,\n-\t\t\t\t   strlen(path));\n+\t\tobject = deref_tag(cb->repo, object, ref->name,\n+\t\t\t\t   strlen(ref->name));\n \t\tif (!object)\n \t\t\treturn 0;\n \t}\ndiff --git a/pseudo-merge.c b/pseudo-merge.c\nindex 893b763fe45..0abd51b42c1 100644\n--- a/pseudo-merge.c\n+++ b/pseudo-merge.c\n@@ -221,28 +221,25 @@ void load_pseudo_merges_from_config(struct repository *r,\n \t}\n }\n \n-static int find_pseudo_merge_group_for_ref(const char *refname,\n-\t\t\t\t\t   const char *referent UNUSED,\n-\t\t\t\t\t   const struct object_id *oid,\n-\t\t\t\t\t   int flags UNUSED,\n-\t\t\t\t\t   void *_data)\n+static int find_pseudo_merge_group_for_ref(const struct reference *ref, void *_data)\n {\n \tstruct bitmap_writer *writer = _data;\n+\tconst struct object_id *maybe_peeled = ref->oid;\n \tstruct object_id peeled;\n \tstruct commit *c;\n \tuint32_t i;\n \tint has_bitmap;\n \n-\tif (!peel_iterated_oid(the_repository, oid, &peeled))\n-\t\toid = &peeled;\n+\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n+\t\tmaybe_peeled = &peeled;\n \n-\tc = lookup_commit(the_repository, oid);\n+\tc = lookup_commit(the_repository, maybe_peeled);\n \tif (!c)\n \t\treturn 0;\n-\tif (!packlist_find(writer->to_pack, oid))\n+\tif (!packlist_find(writer->to_pack, maybe_peeled))\n \t\treturn 0;\n \n-\thas_bitmap = bitmap_writer_has_bitmapped_object_id(writer, oid);\n+\thas_bitmap = bitmap_writer_has_bitmapped_object_id(writer, maybe_peeled);\n \n \tfor (i = 0; i < writer->pseudo_merge_groups.nr; i++) {\n \t\tstruct pseudo_merge_group *group;\n@@ -252,7 +249,7 @@ static int find_pseudo_merge_group_for_ref(const char *refname,\n \t\tsize_t j;\n \n \t\tgroup = writer->pseudo_merge_groups.items[i].util;\n-\t\tif (regexec(group->pattern, refname, ARRAY_SIZE(captures),\n+\t\tif (regexec(group->pattern, ref->name, ARRAY_SIZE(captures),\n \t\t\t    captures, 0))\n \t\t\tcontinue;\n \n@@ -269,7 +266,7 @@ static int find_pseudo_merge_group_for_ref(const char *refname,\n \t\t\tif (group_name.len)\n \t\t\t\tstrbuf_addch(&group_name, '-');\n \n-\t\t\tstrbuf_add(&group_name, refname + match->rm_so,\n+\t\t\tstrbuf_add(&group_name, ref->name + match->rm_so,\n \t\t\t\t   match->rm_eo - match->rm_so);\n \t\t}\n \ndiff --git a/reachable.c b/reachable.c\nindex 22266db5233..b753c395530 100644\n--- a/reachable.c\n+++ b/reachable.c\n@@ -83,18 +83,17 @@ static void add_rebase_files(struct rev_info *revs)\n \tfree_worktrees(worktrees);\n }\n \n-static int add_one_ref(const char *path, const char *referent UNUSED, const struct object_id *oid,\n-\t\t       int flag, void *cb_data)\n+static int add_one_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct rev_info *revs = (struct rev_info *)cb_data;\n \tstruct object *object;\n \n-\tif ((flag & REF_ISSYMREF) && (flag & REF_ISBROKEN)) {\n-\t\twarning(\"symbolic ref is dangling: %s\", path);\n+\tif ((ref->flags & REF_ISSYMREF) && (ref->flags & REF_ISBROKEN)) {\n+\t\twarning(\"symbolic ref is dangling: %s\", ref->name);\n \t\treturn 0;\n \t}\n \n-\tobject = parse_object_or_die(the_repository, oid, path);\n+\tobject = parse_object_or_die(the_repository, ref->oid, ref->name);\n \tadd_pending_object(revs, object, \"\");\n \n \treturn 0;\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 30cc488d8ab..6837fa60a9b 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -2954,14 +2954,15 @@ struct ref_filter_cbdata {\n  * A call-back given to for_each_ref().  Filter refs and keep them for\n  * later object processing.\n  */\n-static int filter_one(const char *refname, const char *referent, const struct object_id *oid, int flag, void *cb_data)\n+static int filter_one(const struct reference *ref, void *cb_data)\n {\n \tstruct ref_filter_cbdata *ref_cbdata = cb_data;\n-\tstruct ref_array_item *ref;\n+\tstruct ref_array_item *item;\n \n-\tref = apply_ref_filter(refname, referent, oid, flag, ref_cbdata->filter);\n-\tif (ref)\n-\t\tref_array_append(ref_cbdata->array, ref);\n+\titem = apply_ref_filter(ref->name, ref->target, ref->oid,\n+\t\t\t\tref->flags, ref_cbdata->filter);\n+\tif (item)\n+\t\tref_array_append(ref_cbdata->array, item);\n \n \treturn 0;\n }\n@@ -2990,17 +2991,18 @@ struct ref_filter_and_format_cbdata {\n \t} internal;\n };\n \n-static int filter_and_format_one(const char *refname, const char *referent, const struct object_id *oid, int flag, void *cb_data)\n+static int filter_and_format_one(const struct reference *ref, void *cb_data)\n {\n \tstruct ref_filter_and_format_cbdata *ref_cbdata = cb_data;\n-\tstruct ref_array_item *ref;\n+\tstruct ref_array_item *item;\n \tstruct strbuf output = STRBUF_INIT, err = STRBUF_INIT;\n \n-\tref = apply_ref_filter(refname, referent, oid, flag, ref_cbdata->filter);\n-\tif (!ref)\n+\titem = apply_ref_filter(ref->name, ref->target, ref->oid,\n+\t\t\t\tref->flags, ref_cbdata->filter);\n+\tif (!item)\n \t\treturn 0;\n \n-\tif (format_ref_array_item(ref, ref_cbdata->format, &output, &err))\n+\tif (format_ref_array_item(item, ref_cbdata->format, &output, &err))\n \t\tdie(\"%s\", err.buf);\n \n \tif (output.len || !ref_cbdata->format->array_opts.omit_empty) {\n@@ -3010,7 +3012,7 @@ static int filter_and_format_one(const char *refname, const char *referent, cons\n \n \tstrbuf_release(&output);\n \tstrbuf_release(&err);\n-\tfree_array_item(ref);\n+\tfree_array_item(item);\n \n \t/*\n \t * Increment the running count of refs that match the filter. If\ndiff --git a/reflog.c b/reflog.c\nindex 65ef259b4f5..ac87e20c4f9 100644\n--- a/reflog.c\n+++ b/reflog.c\n@@ -423,16 +423,13 @@ int should_expire_reflog_ent_verbose(struct object_id *ooid,\n \treturn expire;\n }\n \n-static int push_tip_to_list(const char *refname UNUSED,\n-\t\t\t    const char *referent UNUSED,\n-\t\t\t    const struct object_id *oid,\n-\t\t\t    int flags, void *cb_data)\n+static int push_tip_to_list(const struct reference *ref, void *cb_data)\n {\n \tstruct commit_list **list = cb_data;\n \tstruct commit *tip_commit;\n-\tif (flags & REF_ISSYMREF)\n+\tif (ref->flags & REF_ISSYMREF)\n \t\treturn 0;\n-\ttip_commit = lookup_commit_reference_gently(the_repository, oid, 1);\n+\ttip_commit = lookup_commit_reference_gently(the_repository, ref->oid, 1);\n \tif (!tip_commit)\n \t\treturn 0;\n \tcommit_list_insert(tip_commit, list);\ndiff --git a/refs.c b/refs.c\nindex 965381367e0..25f0579d610 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -426,17 +426,19 @@ int refs_ref_exists(struct ref_store *refs, const char *refname)\n \t\t\t\t\t NULL, NULL);\n }\n \n-static int for_each_filter_refs(const char *refname, const char *referent,\n-\t\t\t\tconst struct object_id *oid,\n-\t\t\t\tint flags, void *data)\n+static int for_each_filter_refs(const struct reference *ref, void *data)\n {\n \tstruct for_each_ref_filter *filter = data;\n \n-\tif (wildmatch(filter->pattern, refname, 0))\n+\tif (wildmatch(filter->pattern, ref->name, 0))\n \t\treturn 0;\n-\tif (filter->prefix)\n-\t\tskip_prefix(refname, filter->prefix, &refname);\n-\treturn filter->fn(refname, referent, oid, flags, filter->cb_data);\n+\tif (filter->prefix) {\n+\t\tstruct reference skipped = *ref;\n+\t\tskip_prefix(skipped.name, filter->prefix, &skipped.name);\n+\t\treturn filter->fn(&skipped, filter->cb_data);\n+\t} else {\n+\t\treturn filter->fn(ref, filter->cb_data);\n+\t}\n }\n \n struct warn_if_dangling_data {\n@@ -447,17 +449,15 @@ struct warn_if_dangling_data {\n \tint dry_run;\n };\n \n-static int warn_if_dangling_symref(const char *refname, const char *referent UNUSED,\n-\t\t\t\t   const struct object_id *oid UNUSED,\n-\t\t\t\t   int flags, void *cb_data)\n+static int warn_if_dangling_symref(const struct reference *ref, void *cb_data)\n {\n \tstruct warn_if_dangling_data *d = cb_data;\n \tconst char *resolves_to, *msg;\n \n-\tif (!(flags & REF_ISSYMREF))\n+\tif (!(ref->flags & REF_ISSYMREF))\n \t\treturn 0;\n \n-\tresolves_to = refs_resolve_ref_unsafe(d->refs, refname, 0, NULL, NULL);\n+\tresolves_to = refs_resolve_ref_unsafe(d->refs, ref->name, 0, NULL, NULL);\n \tif (!resolves_to\n \t    || !string_list_has_string(d->refnames, resolves_to)) {\n \t\treturn 0;\n@@ -466,7 +466,7 @@ static int warn_if_dangling_symref(const char *refname, const char *referent UNU\n \tmsg = d->dry_run\n \t\t? _(\"%s%s will become dangling after %s is deleted\\n\")\n \t\t: _(\"%s%s has become dangling after %s was deleted\\n\");\n-\tfprintf(d->fp, msg, d->indent, refname, resolves_to);\n+\tfprintf(d->fp, msg, d->indent, ref->name, resolves_to);\n \treturn 0;\n }\n \n@@ -507,8 +507,15 @@ int refs_head_ref_namespaced(struct ref_store *refs, each_ref_fn fn, void *cb_da\n \tint flag;\n \n \tstrbuf_addf(&buf, \"%sHEAD\", get_git_namespace());\n-\tif (!refs_read_ref_full(refs, buf.buf, RESOLVE_REF_READING, &oid, &flag))\n-\t\tret = fn(buf.buf, NULL, &oid, flag, cb_data);\n+\tif (!refs_read_ref_full(refs, buf.buf, RESOLVE_REF_READING, &oid, &flag)) {\n+\t\tstruct reference ref = {\n+\t\t\t.name = buf.buf,\n+\t\t\t.oid = &oid,\n+\t\t\t.flags = flag,\n+\t\t};\n+\n+\t\tret = fn(&ref, cb_data);\n+\t}\n \tstrbuf_release(&buf);\n \n \treturn ret;\n@@ -1741,8 +1748,15 @@ int refs_head_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)\n \tint flag;\n \n \tif (refs_resolve_ref_unsafe(refs, \"HEAD\", RESOLVE_REF_READING,\n-\t\t\t\t    &oid, &flag))\n-\t\treturn fn(\"HEAD\", NULL, &oid, flag, cb_data);\n+\t\t\t\t    &oid, &flag)) {\n+\t\tstruct reference ref = {\n+\t\t\t.name = \"HEAD\",\n+\t\t\t.oid = &oid,\n+\t\t\t.flags = flag,\n+\t\t};\n+\n+\t\treturn fn(&ref, cb_data);\n+\t}\n \n \treturn 0;\n }\n@@ -2753,14 +2767,10 @@ struct do_for_each_reflog_help {\n \tvoid *cb_data;\n };\n \n-static int do_for_each_reflog_helper(const char *refname,\n-\t\t\t\t     const char *referent UNUSED,\n-\t\t\t\t     const struct object_id *oid UNUSED,\n-\t\t\t\t     int flags UNUSED,\n-\t\t\t\t     void *cb_data)\n+static int do_for_each_reflog_helper(const struct reference *ref, void *cb_data)\n {\n \tstruct do_for_each_reflog_help *hp = cb_data;\n-\treturn hp->fn(refname, hp->cb_data);\n+\treturn hp->fn(ref->name, hp->cb_data);\n }\n \n int refs_for_each_reflog(struct ref_store *refs, each_reflog_fn fn, void *cb_data)\n@@ -2976,25 +2986,24 @@ struct migration_data {\n \tuint64_t index;\n };\n \n-static int migrate_one_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t   int flags, void *cb_data)\n+static int migrate_one_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct migration_data *data = cb_data;\n \tstruct strbuf symref_target = STRBUF_INIT;\n \tint ret;\n \n-\tif (flags & REF_ISSYMREF) {\n-\t\tret = refs_read_symbolic_ref(data->old_refs, refname, &symref_target);\n+\tif (ref->flags & REF_ISSYMREF) {\n+\t\tret = refs_read_symbolic_ref(data->old_refs, ref->name, &symref_target);\n \t\tif (ret < 0)\n \t\t\tgoto done;\n \n-\t\tret = ref_transaction_update(data->transaction, refname, NULL, null_oid(the_hash_algo),\n+\t\tret = ref_transaction_update(data->transaction, ref->name, NULL, null_oid(the_hash_algo),\n \t\t\t\t\t     symref_target.buf, NULL,\n \t\t\t\t\t     REF_SKIP_CREATE_REFLOG | REF_NO_DEREF, NULL, data->errbuf);\n \t\tif (ret < 0)\n \t\t\tgoto done;\n \t} else {\n-\t\tret = ref_transaction_create(data->transaction, refname, oid, NULL,\n+\t\tret = ref_transaction_create(data->transaction, ref->name, ref->oid, NULL,\n \t\t\t\t\t     REF_SKIP_CREATE_REFLOG | REF_SKIP_OID_VERIFICATION,\n \t\t\t\t\t     NULL, data->errbuf);\n \t\tif (ret < 0)\ndiff --git a/refs.h b/refs.h\nindex 4e6bd63aa86..68d235438c2 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -355,14 +355,32 @@ struct ref_transaction;\n  */\n #define REF_BAD_NAME 0x08\n \n+/* A reference passed to `for_each_ref()`-style callbacks. */\n+struct reference {\n+\t/* The fully-qualified name of the reference. */\n+\tconst char *name;\n+\n+\t/* The target of a symbolic ref. `NULL` for direct references. */\n+\tconst char *target;\n+\n+\t/*\n+\t * The object ID of a reference. Either the direct object ID or the\n+\t * resolved object ID in the case of a symbolic ref. May be the zero\n+\t * object ID in case the symbolic ref cannot be resolved.\n+\t */\n+\tconst struct object_id *oid;\n+\n+\t/* A bitfield of `REF_` flags. */\n+\tint flags;\n+};\n+\n /*\n  * The signature for the callback function for the for_each_*()\n- * functions below.  The memory pointed to by the refname and oid\n- * arguments is only guaranteed to be valid for the duration of a\n+ * functions below.  The memory pointed to by the `struct reference`\n+ * argument is only guaranteed to be valid for the duration of a\n  * single callback invocation.\n  */\n-typedef int each_ref_fn(const char *refname, const char *referent,\n-\t\t\tconst struct object_id *oid, int flags, void *cb_data);\n+typedef int each_ref_fn(const struct reference *ref, void *cb_data);\n \n /*\n  * The following functions invoke the specified callback function for\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 8d7007f4aaa..eb3142f8f2d 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -3150,14 +3150,11 @@ static int parse_and_write_reflog(struct files_ref_store *refs,\n \treturn 0;\n }\n \n-static int ref_present(const char *refname, const char *referent UNUSED,\n-\t\t       const struct object_id *oid UNUSED,\n-\t\t       int flags UNUSED,\n-\t\t       void *cb_data)\n+static int ref_present(const struct reference *ref, void *cb_data)\n {\n \tstruct string_list *affected_refnames = cb_data;\n \n-\treturn string_list_has_string(affected_refnames, refname);\n+\treturn string_list_has_string(affected_refnames, ref->name);\n }\n \n static int files_transaction_finish_initial(struct files_ref_store *refs,\ndiff --git a/refs/iterator.c b/refs/iterator.c\nindex 17ef841d8a3..7f2e718f1c9 100644\n--- a/refs/iterator.c\n+++ b/refs/iterator.c\n@@ -476,7 +476,14 @@ int do_for_each_ref_iterator(struct ref_iterator *iter,\n \n \tcurrent_ref_iter = iter;\n \twhile ((ok = ref_iterator_advance(iter)) == ITER_OK) {\n-\t\tretval = fn(iter->refname, iter->referent, iter->oid, iter->flags, cb_data);\n+\t\tstruct reference ref = {\n+\t\t\t.name = iter->refname,\n+\t\t\t.target = iter->referent,\n+\t\t\t.oid = iter->oid,\n+\t\t\t.flags = iter->flags,\n+\t\t};\n+\n+\t\tretval = fn(&ref, cb_data);\n \t\tif (retval)\n \t\t\tgoto out;\n \t}\ndiff --git a/remote.c b/remote.c\nindex df9675cd330..59b37151208 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -2315,21 +2315,19 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,\n \treturn 1;\n }\n \n-static int one_local_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t int flag UNUSED,\n-\t\t\t void *cb_data)\n+static int one_local_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct ref ***local_tail = cb_data;\n-\tstruct ref *ref;\n+\tstruct ref *local_ref;\n \n \t/* we already know it starts with refs/ to get here */\n-\tif (check_refname_format(refname + 5, 0))\n+\tif (check_refname_format(ref->name + 5, 0))\n \t\treturn 0;\n \n-\tref = alloc_ref(refname);\n-\toidcpy(&ref->new_oid, oid);\n-\t**local_tail = ref;\n-\t*local_tail = &ref->next;\n+\tlocal_ref = alloc_ref(ref->name);\n+\toidcpy(&local_ref->new_oid, ref->oid);\n+\t**local_tail = local_ref;\n+\t*local_tail = &local_ref->next;\n \treturn 0;\n }\n \n@@ -2402,15 +2400,14 @@ struct stale_heads_info {\n \tstruct refspec *rs;\n };\n \n-static int get_stale_heads_cb(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t      int flags, void *cb_data)\n+static int get_stale_heads_cb(const struct reference *ref, void *cb_data)\n {\n \tstruct stale_heads_info *info = cb_data;\n \tstruct string_list matches = STRING_LIST_INIT_DUP;\n \tstruct refspec_item query;\n \tint i, stale = 1;\n \tmemset(&query, 0, sizeof(struct refspec_item));\n-\tquery.dst = (char *)refname;\n+\tquery.dst = (char *)ref->name;\n \n \trefspec_find_all_matches(info->rs, &query, &matches);\n \tif (matches.nr == 0)\n@@ -2423,7 +2420,7 @@ static int get_stale_heads_cb(const char *refname, const char *referent UNUSED,\n \t * overlapping refspecs, we need to go over all of the\n \t * matching refs.\n \t */\n-\tif (flags & REF_ISSYMREF)\n+\tif (ref->flags & REF_ISSYMREF)\n \t\tgoto clean_exit;\n \n \tfor (i = 0; stale && i < matches.nr; i++)\n@@ -2431,8 +2428,8 @@ static int get_stale_heads_cb(const char *refname, const char *referent UNUSED,\n \t\t\tstale = 0;\n \n \tif (stale) {\n-\t\tstruct ref *ref = make_linked_ref(refname, &info->stale_refs_tail);\n-\t\toidcpy(&ref->new_oid, oid);\n+\t\tstruct ref *linked_ref = make_linked_ref(ref->name, &info->stale_refs_tail);\n+\t\toidcpy(&linked_ref->new_oid, ref->oid);\n \t}\n \n clean_exit:\ndiff --git a/repack-midx.c b/repack-midx.c\nindex 6f6202c5bcc..349f7e20b53 100644\n--- a/repack-midx.c\n+++ b/repack-midx.c\n@@ -16,25 +16,23 @@ struct midx_snapshot_ref_data {\n \tint preferred;\n };\n \n-static int midx_snapshot_ref_one(const char *refname UNUSED,\n-\t\t\t\t const char *referent UNUSED,\n-\t\t\t\t const struct object_id *oid,\n-\t\t\t\t int flag UNUSED, void *_data)\n+static int midx_snapshot_ref_one(const struct reference *ref, void *_data)\n {\n \tstruct midx_snapshot_ref_data *data = _data;\n+\tconst struct object_id *maybe_peeled = ref->oid;\n \tstruct object_id peeled;\n \n-\tif (!peel_iterated_oid(data->repo, oid, &peeled))\n-\t\toid = &peeled;\n+\tif (!peel_iterated_oid(data->repo, ref->oid, &peeled))\n+\t\tmaybe_peeled = &peeled;\n \n-\tif (oidset_insert(&data->seen, oid))\n+\tif (oidset_insert(&data->seen, maybe_peeled))\n \t\treturn 0; /* already seen */\n \n-\tif (odb_read_object_info(data->repo->objects, oid, NULL) != OBJ_COMMIT)\n+\tif (odb_read_object_info(data->repo->objects, maybe_peeled, NULL) != OBJ_COMMIT)\n \t\treturn 0;\n \n \tfprintf(data->f->fp, \"%s%s\\n\", data->preferred ? \"+\" : \"\",\n-\t\toid_to_hex(oid));\n+\t\toid_to_hex(maybe_peeled));\n \n \treturn 0;\n }\ndiff --git a/replace-object.c b/replace-object.c\nindex 3eae0510745..03d0f1f083b 100644\n--- a/replace-object.c\n+++ b/replace-object.c\n@@ -8,31 +8,27 @@\n #include \"repository.h\"\n #include \"commit.h\"\n \n-static int register_replace_ref(const char *refname,\n-\t\t\t\tconst char *referent UNUSED,\n-\t\t\t\tconst struct object_id *oid,\n-\t\t\t\tint flag UNUSED,\n-\t\t\t\tvoid *cb_data)\n+static int register_replace_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct repository *r = cb_data;\n \n \t/* Get sha1 from refname */\n-\tconst char *slash = strrchr(refname, '/');\n-\tconst char *hash = slash ? slash + 1 : refname;\n+\tconst char *slash = strrchr(ref->name, '/');\n+\tconst char *hash = slash ? slash + 1 : ref->name;\n \tstruct replace_object *repl_obj = xmalloc(sizeof(*repl_obj));\n \n \tif (get_oid_hex_algop(hash, &repl_obj->original.oid, r->hash_algo)) {\n \t\tfree(repl_obj);\n-\t\twarning(_(\"bad replace ref name: %s\"), refname);\n+\t\twarning(_(\"bad replace ref name: %s\"), ref->name);\n \t\treturn 0;\n \t}\n \n \t/* Copy sha1 from the read ref */\n-\toidcpy(&repl_obj->replacement, oid);\n+\toidcpy(&repl_obj->replacement, ref->oid);\n \n \t/* Register new object */\n \tif (oidmap_put(&r->objects->replace_map, repl_obj))\n-\t\tdie(_(\"duplicate replace ref: %s\"), refname);\n+\t\tdie(_(\"duplicate replace ref: %s\"), ref->name);\n \n \treturn 0;\n }\ndiff --git a/revision.c b/revision.c\nindex cf5e6c1ec9e..5f0850ae5c9 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -1644,19 +1644,17 @@ struct all_refs_cb {\n \tstruct worktree *wt;\n };\n \n-static int handle_one_ref(const char *path, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t  int flag UNUSED,\n-\t\t\t  void *cb_data)\n+static int handle_one_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct all_refs_cb *cb = cb_data;\n \tstruct object *object;\n \n-\tif (ref_excluded(&cb->all_revs->ref_excludes, path))\n+\tif (ref_excluded(&cb->all_revs->ref_excludes, ref->name))\n \t    return 0;\n \n-\tobject = get_reference(cb->all_revs, path, oid, cb->all_flags);\n-\tadd_rev_cmdline(cb->all_revs, object, path, REV_CMD_REF, cb->all_flags);\n-\tadd_pending_object(cb->all_revs, object, path);\n+\tobject = get_reference(cb->all_revs, ref->name, ref->oid, cb->all_flags);\n+\tadd_rev_cmdline(cb->all_revs, object, ref->name, REV_CMD_REF, cb->all_flags);\n+\tadd_pending_object(cb->all_revs, object, ref->name);\n \treturn 0;\n }\n \ndiff --git a/server-info.c b/server-info.c\nindex 1d33de821e9..0a07c722e8b 100644\n--- a/server-info.c\n+++ b/server-info.c\n@@ -148,23 +148,21 @@ static int update_info_file(struct repository *r, char *path,\n \treturn ret;\n }\n \n-static int add_info_ref(const char *path, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\tint flag UNUSED,\n-\t\t\tvoid *cb_data)\n+static int add_info_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct update_info_ctx *uic = cb_data;\n-\tstruct object *o = parse_object(uic->repo, oid);\n+\tstruct object *o = parse_object(uic->repo, ref->oid);\n \tif (!o)\n \t\treturn -1;\n \n-\tif (uic_printf(uic, \"%s\t%s\\n\", oid_to_hex(oid), path) < 0)\n+\tif (uic_printf(uic, \"%s\t%s\\n\", oid_to_hex(ref->oid), ref->name) < 0)\n \t\treturn -1;\n \n \tif (o->type == OBJ_TAG) {\n-\t\to = deref_tag(uic->repo, o, path, 0);\n+\t\to = deref_tag(uic->repo, o, ref->name, 0);\n \t\tif (o)\n \t\t\tif (uic_printf(uic, \"%s\t%s^{}\\n\",\n-\t\t\t\toid_to_hex(&o->oid), path) < 0)\n+\t\t\t\toid_to_hex(&o->oid), ref->name) < 0)\n \t\t\t\treturn -1;\n \t}\n \treturn 0;\ndiff --git a/shallow.c b/shallow.c\nindex d9cd4e219cb..55b9cd9d3f2 100644\n--- a/shallow.c\n+++ b/shallow.c\n@@ -626,14 +626,10 @@ static void paint_down(struct paint_info *info, const struct object_id *oid,\n \tfree(tmp);\n }\n \n-static int mark_uninteresting(const char *refname UNUSED,\n-\t\t\t      const char *referent UNUSED,\n-\t\t\t      const struct object_id *oid,\n-\t\t\t      int flags UNUSED,\n-\t\t\t      void *cb_data UNUSED)\n+static int mark_uninteresting(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct commit *commit = lookup_commit_reference_gently(the_repository,\n-\t\t\t\t\t\t\t       oid, 1);\n+\t\t\t\t\t\t\t       ref->oid, 1);\n \tif (!commit)\n \t\treturn 0;\n \tcommit->object.flags |= UNINTERESTING;\n@@ -742,16 +738,12 @@ struct commit_array {\n \tsize_t nr, alloc;\n };\n \n-static int add_ref(const char *refname UNUSED,\n-\t\t  const char *referent UNUSED,\n-\t\t   const struct object_id *oid,\n-\t\t   int flags UNUSED,\n-\t\t   void *cb_data)\n+static int add_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct commit_array *ca = cb_data;\n \tALLOC_GROW(ca->commits, ca->nr + 1, ca->alloc);\n \tca->commits[ca->nr] = lookup_commit_reference_gently(the_repository,\n-\t\t\t\t\t\t\t     oid, 1);\n+\t\t\t\t\t\t\t     ref->oid, 1);\n \tif (ca->commits[ca->nr])\n \t\tca->nr++;\n \treturn 0;\ndiff --git a/submodule.c b/submodule.c\nindex 35c55155f7b..40a5c6fb9d1 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -934,10 +934,7 @@ static void free_submodules_data(struct string_list *submodules)\n \tstring_list_clear(submodules, 1);\n }\n \n-static int has_remote(const char *refname UNUSED,\n-\t\t      const char *referent UNUSED,\n-\t\t      const struct object_id *oid UNUSED,\n-\t\t      int flags UNUSED, void *cb_data UNUSED)\n+static int has_remote(const struct reference *ref UNUSED, void *cb_data UNUSED)\n {\n \treturn 1;\n }\n@@ -1255,13 +1252,10 @@ int push_unpushed_submodules(struct repository *r,\n \treturn ret;\n }\n \n-static int append_oid_to_array(const char *ref UNUSED,\n-\t\t\t       const char *referent UNUSED,\n-\t\t\t       const struct object_id *oid,\n-\t\t\t       int flags UNUSED, void *data)\n+static int append_oid_to_array(const struct reference *ref, void *data)\n {\n \tstruct oid_array *array = data;\n-\toid_array_append(array, oid);\n+\toid_array_append(array, ref->oid);\n \treturn 0;\n }\n \ndiff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c\nindex 83b06d39a36..b1215947c5e 100644\n--- a/t/helper/test-ref-store.c\n+++ b/t/helper/test-ref-store.c\n@@ -154,10 +154,9 @@ static int cmd_rename_ref(struct ref_store *refs, const char **argv)\n \treturn refs_rename_ref(refs, oldref, newref, logmsg);\n }\n \n-static int each_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t    int flags, void *cb_data UNUSED)\n+static int each_ref(const struct reference *ref, void *cb_data UNUSED)\n {\n-\tprintf(\"%s %s 0x%x\\n\", oid_to_hex(oid), refname, flags);\n+\tprintf(\"%s %s 0x%x\\n\", oid_to_hex(ref->oid), ref->name, ref->flags);\n \treturn 0;\n }\n \ndiff --git a/upload-pack.c b/upload-pack.c\nindex 1e87ae95593..0d563ae74e9 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -870,8 +870,8 @@ static void send_unshallow(struct upload_pack_data *data)\n \t}\n }\n \n-static int check_ref(const char *refname_full, const char *referent UNUSED, const struct object_id *oid,\n-\t\t     int flag, void *cb_data);\n+static int check_ref(const struct reference *ref, void *cb_data);\n+\n static void deepen(struct upload_pack_data *data, int depth)\n {\n \tif (depth == INFINITE_DEPTH && !is_repository_shallow(the_repository)) {\n@@ -1224,13 +1224,12 @@ static int mark_our_ref(const char *refname, const char *refname_full,\n \treturn 0;\n }\n \n-static int check_ref(const char *refname_full, const char *referent UNUSED,const struct object_id *oid,\n-\t\t     int flag UNUSED, void *cb_data)\n+static int check_ref(const struct reference *ref, void *cb_data)\n {\n-\tconst char *refname = strip_namespace(refname_full);\n+\tconst char *refname = strip_namespace(ref->name);\n \tstruct upload_pack_data *data = cb_data;\n \n-\tmark_our_ref(refname, refname_full, oid, &data->hidden_refs);\n+\tmark_our_ref(refname, ref->name, ref->oid, &data->hidden_refs);\n \treturn 0;\n }\n \n@@ -1292,27 +1291,25 @@ static void write_v0_ref(struct upload_pack_data *data,\n \treturn;\n }\n \n-static int send_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t    int flag UNUSED, void *cb_data)\n+static int send_ref(const struct reference *ref, void *cb_data)\n {\n-\twrite_v0_ref(cb_data, refname, strip_namespace(refname), oid);\n+\twrite_v0_ref(cb_data, ref->name, strip_namespace(ref->name), ref->oid);\n \treturn 0;\n }\n \n-static int find_symref(const char *refname, const char *referent UNUSED,\n-\t\t       const struct object_id *oid UNUSED,\n-\t\t       int flag, void *cb_data)\n+static int find_symref(const struct reference *ref, void *cb_data)\n {\n \tconst char *symref_target;\n \tstruct string_list_item *item;\n+\tint flag;\n \n-\tif ((flag & REF_ISSYMREF) == 0)\n+\tif ((ref->flags & REF_ISSYMREF) == 0)\n \t\treturn 0;\n \tsymref_target = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),\n-\t\t\t\t\t\trefname, 0, NULL, &flag);\n+\t\t\t\t\t\tref->name, 0, NULL, &flag);\n \tif (!symref_target || (flag & REF_ISSYMREF) == 0)\n-\t\tdie(\"'%s' is a symref but it is not?\", refname);\n-\titem = string_list_append(cb_data, strip_namespace(refname));\n+\t\tdie(\"'%s' is a symref but it is not?\", ref->name);\n+\titem = string_list_append(cb_data, strip_namespace(ref->name));\n \titem->util = xstrdup(strip_namespace(symref_target));\n \treturn 0;\n }\ndiff --git a/walker.c b/walker.c\nindex 80737545172..409b646578a 100644\n--- a/walker.c\n+++ b/walker.c\n@@ -226,14 +226,10 @@ static int interpret_target(struct walker *walker, char *target, struct object_i\n \treturn -1;\n }\n \n-static int mark_complete(const char *path UNUSED,\n-\t\t\tconst char *referent UNUSED,\n-\t\t\t const struct object_id *oid,\n-\t\t\t int flag UNUSED,\n-\t\t\t void *cb_data UNUSED)\n+static int mark_complete(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct commit *commit = lookup_commit_reference_gently(the_repository,\n-\t\t\t\t\t\t\t       oid, 1);\n+\t\t\t\t\t\t\t       ref->oid, 1);\n \n \tif (commit) {\n \t\tcommit->object.flags |= COMPLETE;\ndiff --git a/worktree.c b/worktree.c\nindex a2a5f51f29f..9308389cb6f 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -595,8 +595,15 @@ int other_head_refs(each_ref_fn fn, void *cb_data)\n \t\tif (refs_resolve_ref_unsafe(get_main_ref_store(the_repository),\n \t\t\t\t\t    refname.buf,\n \t\t\t\t\t    RESOLVE_REF_READING,\n-\t\t\t\t\t    &oid, &flag))\n-\t\t\tret = fn(refname.buf, NULL, &oid, flag, cb_data);\n+\t\t\t\t\t    &oid, &flag)) {\n+\t\t\tstruct reference ref = {\n+\t\t\t\t.name = refname.buf,\n+\t\t\t\t.oid = &oid,\n+\t\t\t\t.flags = flag,\n+\t\t\t};\n+\n+\t\t\tret = fn(&ref, cb_data);\n+\t\t}\n \t\tif (ret)\n \t\t\tbreak;\n \t}\n\n-- \n2.51.1.851.g4ebd6896fd.dirty\n\n"},{"id":"529383","messageId":"20251022-b4-pks-ref-filter-skip-parsing-objects-v3-2-eb9f71985ef0@pks.im","threadId":"64260","inReplyTo":"20251022-b4-pks-ref-filter-skip-parsing-objects-v3-0-eb9f71985ef0@pks.im","subject":"[PATCH v3 02/14] refs: introduce `.ref` field for the base iterator","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-22T06:41:02Z","receivedAt":"2025-10-22T06:41:16Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The base iterator has a couple of fields that tracks the name, target,\nobject ID and flags for the current reference. Due to this design we\nhave to create a new `struct reference` whenever we want to hand over\nthat reference to the callback function, which is tedious and not very\nefficient.\n\nConvert the structure to instead contain a `struct reference` as member.\nThis member is expected to be populated by the implementations of the\niterator and is handed over to the callback directly.\n\nWhile at it, simplify `should_pack_ref()` to take a `struct reference`\ndirectly instead of passing its respective fields.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c                  |  8 ++++----\n refs/debug.c            |  8 +++-----\n refs/files-backend.c    | 47 +++++++++++++++++++++--------------------------\n refs/iterator.c         | 39 ++++++++++++---------------------------\n refs/packed-backend.c   | 46 +++++++++++++++++++++++-----------------------\n refs/ref-cache.c        | 10 +++++-----\n refs/refs-internal.h    |  5 +----\n refs/reftable-backend.c | 12 ++++++------\n 8 files changed, 75 insertions(+), 100 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 25f0579d610..f96cf43b128 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2327,8 +2327,8 @@ int refs_optimize(struct ref_store *refs, struct pack_refs_opts *opts)\n int peel_iterated_oid(struct repository *r, const struct object_id *base, struct object_id *peeled)\n {\n \tif (current_ref_iter &&\n-\t    (current_ref_iter->oid == base ||\n-\t     oideq(current_ref_iter->oid, base)))\n+\t    (current_ref_iter->ref.oid == base ||\n+\t     oideq(current_ref_iter->ref.oid, base)))\n \t\treturn ref_iterator_peel(current_ref_iter, peeled);\n \n \treturn peel_object(r, base, peeled) ? -1 : 0;\n@@ -2703,7 +2703,7 @@ enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs\n \n \t\t\twhile ((ok = ref_iterator_advance(iter)) == ITER_OK) {\n \t\t\t\tif (skip &&\n-\t\t\t\t    string_list_has_string(skip, iter->refname))\n+\t\t\t\t    string_list_has_string(skip, iter->ref.name))\n \t\t\t\t\tcontinue;\n \n \t\t\t\tif (transaction && ref_transaction_maybe_set_rejected(\n@@ -2712,7 +2712,7 @@ enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs\n \t\t\t\t\tcontinue;\n \n \t\t\t\tstrbuf_addf(err, _(\"'%s' exists; cannot create '%s'\"),\n-\t\t\t\t\t    iter->refname, refname);\n+\t\t\t\t\t    iter->ref.name, refname);\n \t\t\t\tgoto cleanup;\n \t\t\t}\n \ndiff --git a/refs/debug.c b/refs/debug.c\nindex 697adbd0dc3..67718bd1f49 100644\n--- a/refs/debug.c\n+++ b/refs/debug.c\n@@ -160,11 +160,9 @@ static int debug_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\ttrace_printf_key(&trace_refs, \"iterator_advance: (%d)\\n\", res);\n \telse\n \t\ttrace_printf_key(&trace_refs, \"iterator_advance: %s (0)\\n\",\n-\t\t\tditer->iter->refname);\n+\t\t\tditer->iter->ref.name);\n \n-\tditer->base.refname = diter->iter->refname;\n-\tditer->base.oid = diter->iter->oid;\n-\tditer->base.flags = diter->iter->flags;\n+\tditer->base.ref = diter->iter->ref;\n \treturn res;\n }\n \n@@ -185,7 +183,7 @@ static int debug_ref_iterator_peel(struct ref_iterator *ref_iterator,\n \tstruct debug_ref_iterator *diter =\n \t\t(struct debug_ref_iterator *)ref_iterator;\n \tint res = diter->iter->vtable->peel(diter->iter, peeled);\n-\ttrace_printf_key(&trace_refs, \"iterator_peel: %s: %d\\n\", diter->iter->refname, res);\n+\ttrace_printf_key(&trace_refs, \"iterator_peel: %s: %d\\n\", diter->iter->ref.name, res);\n \treturn res;\n }\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex eb3142f8f2d..fac53fa052d 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -961,26 +961,23 @@ static int files_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \n \twhile ((ok = ref_iterator_advance(iter->iter0)) == ITER_OK) {\n \t\tif (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY &&\n-\t\t    parse_worktree_ref(iter->iter0->refname, NULL, NULL,\n+\t\t    parse_worktree_ref(iter->iter0->ref.name, NULL, NULL,\n \t\t\t\t       NULL) != REF_WORKTREE_CURRENT)\n \t\t\tcontinue;\n \n \t\tif ((iter->flags & DO_FOR_EACH_OMIT_DANGLING_SYMREFS) &&\n-\t\t    (iter->iter0->flags & REF_ISSYMREF) &&\n-\t\t    (iter->iter0->flags & REF_ISBROKEN))\n+\t\t    (iter->iter0->ref.flags & REF_ISSYMREF) &&\n+\t\t    (iter->iter0->ref.flags & REF_ISBROKEN))\n \t\t\tcontinue;\n \n \t\tif (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&\n-\t\t    !ref_resolves_to_object(iter->iter0->refname,\n+\t\t    !ref_resolves_to_object(iter->iter0->ref.name,\n \t\t\t\t\t    iter->repo,\n-\t\t\t\t\t    iter->iter0->oid,\n-\t\t\t\t\t    iter->iter0->flags))\n+\t\t\t\t\t    iter->iter0->ref.oid,\n+\t\t\t\t\t    iter->iter0->ref.flags))\n \t\t\tcontinue;\n \n-\t\titer->base.refname = iter->iter0->refname;\n-\t\titer->base.oid = iter->iter0->oid;\n-\t\titer->base.flags = iter->iter0->flags;\n-\t\titer->base.referent = iter->iter0->referent;\n+\t\titer->base.ref = iter->iter0->ref;\n \n \t\treturn ITER_OK;\n \t}\n@@ -1367,30 +1364,29 @@ static void prune_refs(struct files_ref_store *refs, struct ref_to_prune **refs_\n  * Return true if the specified reference should be packed.\n  */\n static int should_pack_ref(struct files_ref_store *refs,\n-\t\t\t   const char *refname,\n-\t\t\t   const struct object_id *oid, unsigned int ref_flags,\n+\t\t\t   const struct reference *ref,\n \t\t\t   struct pack_refs_opts *opts)\n {\n \tstruct string_list_item *item;\n \n \t/* Do not pack per-worktree refs: */\n-\tif (parse_worktree_ref(refname, NULL, NULL, NULL) !=\n+\tif (parse_worktree_ref(ref->name, NULL, NULL, NULL) !=\n \t    REF_WORKTREE_SHARED)\n \t\treturn 0;\n \n \t/* Do not pack symbolic refs: */\n-\tif (ref_flags & REF_ISSYMREF)\n+\tif (ref->flags & REF_ISSYMREF)\n \t\treturn 0;\n \n \t/* Do not pack broken refs: */\n-\tif (!ref_resolves_to_object(refname, refs->base.repo, oid, ref_flags))\n+\tif (!ref_resolves_to_object(ref->name, refs->base.repo, ref->oid, ref->flags))\n \t\treturn 0;\n \n-\tif (ref_excluded(opts->exclusions, refname))\n+\tif (ref_excluded(opts->exclusions, ref->name))\n \t\treturn 0;\n \n \tfor_each_string_list_item(item, opts->includes)\n-\t\tif (!wildmatch(item->string, refname, 0))\n+\t\tif (!wildmatch(item->string, ref->name, 0))\n \t\t\treturn 1;\n \n \treturn 0;\n@@ -1443,8 +1439,7 @@ static int should_pack_refs(struct files_ref_store *refs,\n \titer = cache_ref_iterator_begin(get_loose_ref_cache(refs, 0), NULL,\n \t\t\t\t\trefs->base.repo, 0);\n \twhile ((ret = ref_iterator_advance(iter)) == ITER_OK) {\n-\t\tif (should_pack_ref(refs, iter->refname, iter->oid,\n-\t\t\t\t    iter->flags, opts))\n+\t\tif (should_pack_ref(refs, &iter->ref, opts))\n \t\t\trefcount++;\n \t\tif (refcount >= limit) {\n \t\t\tref_iterator_free(iter);\n@@ -1489,24 +1484,24 @@ static int files_pack_refs(struct ref_store *ref_store,\n \t\t * in the packed ref cache. If the reference should be\n \t\t * pruned, also add it to refs_to_prune.\n \t\t */\n-\t\tif (!should_pack_ref(refs, iter->refname, iter->oid, iter->flags, opts))\n+\t\tif (!should_pack_ref(refs, &iter->ref, opts))\n \t\t\tcontinue;\n \n \t\t/*\n \t\t * Add a reference creation for this reference to the\n \t\t * packed-refs transaction:\n \t\t */\n-\t\tif (ref_transaction_update(transaction, iter->refname,\n-\t\t\t\t\t   iter->oid, NULL, NULL, NULL,\n+\t\tif (ref_transaction_update(transaction, iter->ref.name,\n+\t\t\t\t\t   iter->ref.oid, NULL, NULL, NULL,\n \t\t\t\t\t   REF_NO_DEREF, NULL, &err))\n \t\t\tdie(\"failure preparing to create packed reference %s: %s\",\n-\t\t\t    iter->refname, err.buf);\n+\t\t\t    iter->ref.name, err.buf);\n \n \t\t/* Schedule the loose reference for pruning if requested. */\n \t\tif ((opts->flags & PACK_REFS_PRUNE)) {\n \t\t\tstruct ref_to_prune *n;\n-\t\t\tFLEX_ALLOC_STR(n, name, iter->refname);\n-\t\t\toidcpy(&n->oid, iter->oid);\n+\t\t\tFLEX_ALLOC_STR(n, name, iter->ref.name);\n+\t\t\toidcpy(&n->oid, iter->ref.oid);\n \t\t\tn->next = refs_to_prune;\n \t\t\trefs_to_prune = n;\n \t\t}\n@@ -2379,7 +2374,7 @@ static int files_reflog_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t\t\t\t REFNAME_ALLOW_ONELEVEL))\n \t\t\tcontinue;\n \n-\t\titer->base.refname = diter->relative_path;\n+\t\titer->base.ref.name = diter->relative_path;\n \t\treturn ITER_OK;\n \t}\n \ndiff --git a/refs/iterator.c b/refs/iterator.c\nindex 7f2e718f1c9..fe5980e1b6c 100644\n--- a/refs/iterator.c\n+++ b/refs/iterator.c\n@@ -41,10 +41,7 @@ void base_ref_iterator_init(struct ref_iterator *iter,\n \t\t\t    struct ref_iterator_vtable *vtable)\n {\n \titer->vtable = vtable;\n-\titer->refname = NULL;\n-\titer->referent = NULL;\n-\titer->oid = NULL;\n-\titer->flags = 0;\n+\tmemset(&iter->ref, 0, sizeof(iter->ref));\n }\n \n struct empty_ref_iterator {\n@@ -127,8 +124,8 @@ enum iterator_selection ref_iterator_select(struct ref_iterator *iter_worktree,\n \t\t * latter.\n \t\t */\n \t\tif (iter_worktree) {\n-\t\t\tint cmp = strcmp(iter_worktree->refname,\n-\t\t\t\t\t iter_common->refname);\n+\t\t\tint cmp = strcmp(iter_worktree->ref.name,\n+\t\t\t\t\t iter_common->ref.name);\n \t\t\tif (cmp < 0)\n \t\t\t\treturn ITER_SELECT_0;\n \t\t\telse if (!cmp)\n@@ -139,7 +136,7 @@ enum iterator_selection ref_iterator_select(struct ref_iterator *iter_worktree,\n \t\t  * We now know that the lexicographically-next ref is a common\n \t\t  * ref. When the common ref is a shared one we return it.\n \t\t  */\n-\t\tif (parse_worktree_ref(iter_common->refname, NULL, NULL,\n+\t\tif (parse_worktree_ref(iter_common->ref.name, NULL, NULL,\n \t\t\t\t       NULL) == REF_WORKTREE_SHARED)\n \t\t\treturn ITER_SELECT_1;\n \n@@ -212,10 +209,7 @@ static int merge_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t}\n \n \t\tif (selection & ITER_YIELD_CURRENT) {\n-\t\t\titer->base.referent = (*iter->current)->referent;\n-\t\t\titer->base.refname = (*iter->current)->refname;\n-\t\t\titer->base.oid = (*iter->current)->oid;\n-\t\t\titer->base.flags = (*iter->current)->flags;\n+\t\t\titer->base.ref = (*iter->current)->ref;\n \t\t\treturn ITER_OK;\n \t\t}\n \t}\n@@ -313,7 +307,7 @@ static enum iterator_selection overlay_iterator_select(\n \telse if (!front)\n \t\treturn ITER_SELECT_1;\n \n-\tcmp = strcmp(front->refname, back->refname);\n+\tcmp = strcmp(front->ref.name, back->ref.name);\n \n \tif (cmp < 0)\n \t\treturn ITER_SELECT_0;\n@@ -371,7 +365,7 @@ static int prefix_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \tint ok;\n \n \twhile ((ok = ref_iterator_advance(iter->iter0)) == ITER_OK) {\n-\t\tint cmp = compare_prefix(iter->iter0->refname, iter->prefix);\n+\t\tint cmp = compare_prefix(iter->iter0->ref.name, iter->prefix);\n \t\tif (cmp < 0)\n \t\t\tcontinue;\n \t\t/*\n@@ -382,6 +376,8 @@ static int prefix_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\tif (cmp > 0)\n \t\t\treturn ITER_DONE;\n \n+\t\titer->base.ref = iter->iter0->ref;\n+\n \t\tif (iter->trim) {\n \t\t\t/*\n \t\t\t * It is nonsense to trim off characters that\n@@ -392,15 +388,11 @@ static int prefix_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t\t * one character left in the refname after\n \t\t\t * trimming, report it as a bug:\n \t\t\t */\n-\t\t\tif (strlen(iter->iter0->refname) <= iter->trim)\n+\t\t\tif (strlen(iter->base.ref.name) <= iter->trim)\n \t\t\t\tBUG(\"attempt to trim too many characters\");\n-\t\t\titer->base.refname = iter->iter0->refname + iter->trim;\n-\t\t} else {\n-\t\t\titer->base.refname = iter->iter0->refname;\n+\t\t\titer->base.ref.name += iter->trim;\n \t\t}\n \n-\t\titer->base.oid = iter->iter0->oid;\n-\t\titer->base.flags = iter->iter0->flags;\n \t\treturn ITER_OK;\n \t}\n \n@@ -476,14 +468,7 @@ int do_for_each_ref_iterator(struct ref_iterator *iter,\n \n \tcurrent_ref_iter = iter;\n \twhile ((ok = ref_iterator_advance(iter)) == ITER_OK) {\n-\t\tstruct reference ref = {\n-\t\t\t.name = iter->refname,\n-\t\t\t.target = iter->referent,\n-\t\t\t.oid = iter->oid,\n-\t\t\t.flags = iter->flags,\n-\t\t};\n-\n-\t\tretval = fn(&ref, cb_data);\n+\t\tretval = fn(&iter->ref, cb_data);\n \t\tif (retval)\n \t\t\tgoto out;\n \t}\ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex a8c22a0a7ff..7987acdc96a 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -908,7 +908,7 @@ static int next_record(struct packed_ref_iterator *iter)\n \tif (iter->pos == iter->eof)\n \t\treturn ITER_DONE;\n \n-\titer->base.flags = REF_ISPACKED;\n+\titer->base.ref.flags = REF_ISPACKED;\n \tp = iter->pos;\n \n \tif (iter->eof - p < snapshot_hexsz(iter->snapshot) + 2 ||\n@@ -923,22 +923,22 @@ static int next_record(struct packed_ref_iterator *iter)\n \t\t\t\t      iter->pos, iter->eof - iter->pos);\n \n \tstrbuf_add(&iter->refname_buf, p, eol - p);\n-\titer->base.refname = iter->refname_buf.buf;\n+\titer->base.ref.name = iter->refname_buf.buf;\n \n \tif (refname_contains_nul(&iter->refname_buf))\n-\t\tdie(\"packed refname contains embedded NULL: %s\", iter->base.refname);\n+\t\tdie(\"packed refname contains embedded NULL: %s\", iter->base.ref.name);\n \n-\tif (check_refname_format(iter->base.refname, REFNAME_ALLOW_ONELEVEL)) {\n-\t\tif (!refname_is_safe(iter->base.refname))\n+\tif (check_refname_format(iter->base.ref.name, REFNAME_ALLOW_ONELEVEL)) {\n+\t\tif (!refname_is_safe(iter->base.ref.name))\n \t\t\tdie(\"packed refname is dangerous: %s\",\n-\t\t\t    iter->base.refname);\n+\t\t\t    iter->base.ref.name);\n \t\toidclr(&iter->oid, iter->repo->hash_algo);\n-\t\titer->base.flags |= REF_BAD_NAME | REF_ISBROKEN;\n+\t\titer->base.ref.flags |= REF_BAD_NAME | REF_ISBROKEN;\n \t}\n \tif (iter->snapshot->peeled == PEELED_FULLY ||\n \t    (iter->snapshot->peeled == PEELED_TAGS &&\n-\t     starts_with(iter->base.refname, \"refs/tags/\")))\n-\t\titer->base.flags |= REF_KNOWS_PEELED;\n+\t     starts_with(iter->base.ref.name, \"refs/tags/\")))\n+\t\titer->base.ref.flags |= REF_KNOWS_PEELED;\n \n \titer->pos = eol + 1;\n \n@@ -956,11 +956,11 @@ static int next_record(struct packed_ref_iterator *iter)\n \t\t * definitely know the value of *this* reference. But\n \t\t * we suppress it if the reference is broken:\n \t\t */\n-\t\tif ((iter->base.flags & REF_ISBROKEN)) {\n+\t\tif ((iter->base.ref.flags & REF_ISBROKEN)) {\n \t\t\toidclr(&iter->peeled, iter->repo->hash_algo);\n-\t\t\titer->base.flags &= ~REF_KNOWS_PEELED;\n+\t\t\titer->base.ref.flags &= ~REF_KNOWS_PEELED;\n \t\t} else {\n-\t\t\titer->base.flags |= REF_KNOWS_PEELED;\n+\t\t\titer->base.ref.flags |= REF_KNOWS_PEELED;\n \t\t}\n \t} else {\n \t\toidclr(&iter->peeled, iter->repo->hash_algo);\n@@ -976,15 +976,15 @@ static int packed_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \tint ok;\n \n \twhile ((ok = next_record(iter)) == ITER_OK) {\n-\t\tconst char *refname = iter->base.refname;\n+\t\tconst char *refname = iter->base.ref.name;\n \t\tconst char *prefix = iter->prefix;\n \n \t\tif (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY &&\n-\t\t    !is_per_worktree_ref(iter->base.refname))\n+\t\t    !is_per_worktree_ref(iter->base.ref.name))\n \t\t\tcontinue;\n \n \t\tif (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&\n-\t\t    !ref_resolves_to_object(iter->base.refname, iter->repo,\n+\t\t    !ref_resolves_to_object(iter->base.ref.name, iter->repo,\n \t\t\t\t\t    &iter->oid, iter->flags))\n \t\t\tcontinue;\n \n@@ -1033,10 +1033,10 @@ static int packed_ref_iterator_peel(struct ref_iterator *ref_iterator,\n \tstruct packed_ref_iterator *iter =\n \t\t(struct packed_ref_iterator *)ref_iterator;\n \n-\tif ((iter->base.flags & REF_KNOWS_PEELED)) {\n+\tif ((iter->base.ref.flags & REF_KNOWS_PEELED)) {\n \t\toidcpy(peeled, &iter->peeled);\n \t\treturn is_null_oid(&iter->peeled) ? -1 : 0;\n-\t} else if ((iter->base.flags & (REF_ISBROKEN | REF_ISSYMREF))) {\n+\t} else if ((iter->base.ref.flags & (REF_ISBROKEN | REF_ISSYMREF))) {\n \t\treturn -1;\n \t} else {\n \t\treturn peel_object(iter->repo, &iter->oid, peeled) ? -1 : 0;\n@@ -1194,7 +1194,7 @@ static struct ref_iterator *packed_ref_iterator_begin(\n \titer->snapshot = snapshot;\n \tacquire_snapshot(snapshot);\n \tstrbuf_init(&iter->refname_buf, 0);\n-\titer->base.oid = &iter->oid;\n+\titer->base.ref.oid = &iter->oid;\n \titer->repo = ref_store->repo;\n \titer->flags = flags;\n \n@@ -1436,7 +1436,7 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re\n \t\t\tif (!iter)\n \t\t\t\tcmp = +1;\n \t\t\telse\n-\t\t\t\tcmp = strcmp(iter->refname, update->refname);\n+\t\t\t\tcmp = strcmp(iter->ref.name, update->refname);\n \t\t}\n \n \t\tif (!cmp) {\n@@ -1459,11 +1459,11 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re\n \t\t\t\t\t}\n \n \t\t\t\t\tgoto error;\n-\t\t\t\t} else if (!oideq(&update->old_oid, iter->oid)) {\n+\t\t\t\t} else if (!oideq(&update->old_oid, iter->ref.oid)) {\n \t\t\t\t\tstrbuf_addf(err, \"cannot update ref '%s': \"\n \t\t\t\t\t\t    \"is at %s but expected %s\",\n \t\t\t\t\t\t    update->refname,\n-\t\t\t\t\t\t    oid_to_hex(iter->oid),\n+\t\t\t\t\t\t    oid_to_hex(iter->ref.oid),\n \t\t\t\t\t\t    oid_to_hex(&update->old_oid));\n \t\t\t\t\tret = REF_TRANSACTION_ERROR_INCORRECT_OLD_VALUE;\n \n@@ -1527,8 +1527,8 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re\n \t\t\tstruct object_id peeled;\n \t\t\tint peel_error = ref_iterator_peel(iter, &peeled);\n \n-\t\t\tif (write_packed_entry(out, iter->refname,\n-\t\t\t\t\t       iter->oid,\n+\t\t\tif (write_packed_entry(out, iter->ref.name,\n+\t\t\t\t\t       iter->ref.oid,\n \t\t\t\t\t       peel_error ? NULL : &peeled))\n \t\t\t\tgoto write_error;\n \ndiff --git a/refs/ref-cache.c b/refs/ref-cache.c\nindex e5e5df16d85..f1abc396241 100644\n--- a/refs/ref-cache.c\n+++ b/refs/ref-cache.c\n@@ -425,10 +425,10 @@ static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t\tlevel->prefix_state = entry_prefix_state;\n \t\t\tlevel->index = -1;\n \t\t} else {\n-\t\t\titer->base.refname = entry->name;\n-\t\t\titer->base.referent = entry->u.value.referent;\n-\t\t\titer->base.oid = &entry->u.value.oid;\n-\t\t\titer->base.flags = entry->flag;\n+\t\t\titer->base.ref.name = entry->name;\n+\t\t\titer->base.ref.target = entry->u.value.referent;\n+\t\t\titer->base.ref.oid = &entry->u.value.oid;\n+\t\t\titer->base.ref.flags = entry->flag;\n \t\t\treturn ITER_OK;\n \t\t}\n \t}\n@@ -550,7 +550,7 @@ static int cache_ref_iterator_peel(struct ref_iterator *ref_iterator,\n {\n \tstruct cache_ref_iterator *iter =\n \t\t(struct cache_ref_iterator *)ref_iterator;\n-\treturn peel_object(iter->repo, ref_iterator->oid, peeled) ? -1 : 0;\n+\treturn peel_object(iter->repo, ref_iterator->ref.oid, peeled) ? -1 : 0;\n }\n \n static void cache_ref_iterator_release(struct ref_iterator *ref_iterator)\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 4ef3bd75c6a..ed749d16572 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -249,10 +249,7 @@ const char *find_descendant_ref(const char *dirname,\n  */\n struct ref_iterator {\n \tstruct ref_iterator_vtable *vtable;\n-\tconst char *refname;\n-\tconst char *referent;\n-\tconst struct object_id *oid;\n-\tunsigned int flags;\n+\tstruct reference ref;\n };\n \n /*\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex d4b79286202..0e47986cb5b 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -704,10 +704,10 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t\t\t\t    &iter->oid, flags))\n \t\t\t\tcontinue;\n \n-\t\titer->base.refname = iter->ref.refname;\n-\t\titer->base.referent = referent;\n-\t\titer->base.oid = &iter->oid;\n-\t\titer->base.flags = flags;\n+\t\titer->base.ref.name = iter->ref.refname;\n+\t\titer->base.ref.target = referent;\n+\t\titer->base.ref.oid = &iter->oid;\n+\t\titer->base.ref.flags = flags;\n \n \t\tbreak;\n \t}\n@@ -828,7 +828,7 @@ static struct reftable_ref_iterator *ref_iterator_for_stack(struct reftable_ref_\n \n \titer = xcalloc(1, sizeof(*iter));\n \tbase_ref_iterator_init(&iter->base, &reftable_ref_iterator_vtable);\n-\titer->base.oid = &iter->oid;\n+\titer->base.ref.oid = &iter->oid;\n \titer->flags = flags;\n \titer->refs = refs;\n \titer->exclude_patterns = filter_exclude_patterns(exclude_patterns);\n@@ -2072,7 +2072,7 @@ static int reftable_reflog_iterator_advance(struct ref_iterator *ref_iterator)\n \n \t\tstrbuf_reset(&iter->last_name);\n \t\tstrbuf_addstr(&iter->last_name, iter->log.refname);\n-\t\titer->base.refname = iter->log.refname;\n+\t\titer->base.ref.name = iter->log.refname;\n \n \t\tbreak;\n \t}\n\n-- \n2.51.1.851.g4ebd6896fd.dirty\n\n"},{"id":"529384","messageId":"20251022-b4-pks-ref-filter-skip-parsing-objects-v3-3-eb9f71985ef0@pks.im","threadId":"64260","inReplyTo":"20251022-b4-pks-ref-filter-skip-parsing-objects-v3-0-eb9f71985ef0@pks.im","subject":"[PATCH v3 03/14] refs: fully reset `struct ref_iterator::ref` on iteration","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-22T06:41:03Z","receivedAt":"2025-10-22T06:41:18Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"With the introduction of the `struct ref_iterator::ref` field it now is\na whole lot easier to introduce new fields that become accessible to the\ncaller without having to adapt every single callsite. But there's a\ndownside: when a new field is introduced we always have to adapt all\nbackends to set that field.\n\nThis isn't something we can avoid in the general case: when the new\nfield is expected to be populated by all backends we of course cannot\navoid doing so. But new fields may be entirely optional, in which case\nwe'd still have such churn. And furthermore, it is very easy right now\nto leak state from a previous iteration into the next iteration.\n\nAddress this issue by ensuring that the reference backends all fully\nreset the field on every single iteration. This ensures that no state\nfrom previous iterations can leak into the next one. And it ensures that\nany newly introduced fields will be zeroed out by default.\n\nNote that we don't have to explicitly adapt the \"files\" backend, as it\nuses the `cache_ref_iterator` internally. Furthermore, other \"wrapping\"\niterators like for example the `prefix_ref_iterator` copy around the\nwhole reference, so these don't need to be adapted either.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs/packed-backend.c   | 3 ++-\n refs/ref-cache.c        | 1 +\n refs/reftable-backend.c | 1 +\n 3 files changed, 4 insertions(+), 1 deletion(-)\n\ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 7987acdc96a..711e07f8326 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -882,6 +882,7 @@ static int next_record(struct packed_ref_iterator *iter)\n {\n \tconst char *p, *eol;\n \n+\tmemset(&iter->base.ref, 0, sizeof(iter->base.ref));\n \tstrbuf_reset(&iter->refname_buf);\n \n \t/*\n@@ -916,6 +917,7 @@ static int next_record(struct packed_ref_iterator *iter)\n \t    !isspace(*p++))\n \t\tdie_invalid_line(iter->snapshot->refs->path,\n \t\t\t\t iter->pos, iter->eof - iter->pos);\n+\titer->base.ref.oid = &iter->oid;\n \n \teol = memchr(p, '\\n', iter->eof - p);\n \tif (!eol)\n@@ -1194,7 +1196,6 @@ static struct ref_iterator *packed_ref_iterator_begin(\n \titer->snapshot = snapshot;\n \tacquire_snapshot(snapshot);\n \tstrbuf_init(&iter->refname_buf, 0);\n-\titer->base.ref.oid = &iter->oid;\n \titer->repo = ref_store->repo;\n \titer->flags = flags;\n \ndiff --git a/refs/ref-cache.c b/refs/ref-cache.c\nindex f1abc396241..e427848879d 100644\n--- a/refs/ref-cache.c\n+++ b/refs/ref-cache.c\n@@ -425,6 +425,7 @@ static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t\tlevel->prefix_state = entry_prefix_state;\n \t\t\tlevel->index = -1;\n \t\t} else {\n+\t\t\tmemset(&iter->base.ref, 0, sizeof(iter->base.ref));\n \t\t\titer->base.ref.name = entry->name;\n \t\t\titer->base.ref.target = entry->u.value.referent;\n \t\t\titer->base.ref.oid = &entry->u.value.oid;\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 0e47986cb5b..728886eafd3 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -704,6 +704,7 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t\t\t\t    &iter->oid, flags))\n \t\t\t\tcontinue;\n \n+\t\tmemset(&iter->base.ref, 0, sizeof(iter->base.ref));\n \t\titer->base.ref.name = iter->ref.refname;\n \t\titer->base.ref.target = referent;\n \t\titer->base.ref.oid = &iter->oid;\n\n-- \n2.51.1.851.g4ebd6896fd.dirty\n\n"},{"id":"529386","messageId":"20251022-b4-pks-ref-filter-skip-parsing-objects-v3-4-eb9f71985ef0@pks.im","threadId":"64260","inReplyTo":"20251022-b4-pks-ref-filter-skip-parsing-objects-v3-0-eb9f71985ef0@pks.im","subject":"[PATCH v3 04/14] refs: refactor reference status flags","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-22T06:41:04Z","receivedAt":"2025-10-22T06:41:20Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The reference flags encode information like whether or not a reference\nis a symbolic reference or whether it may be broken. This information is\nstored in a `int flags` bitfield, which is in conflict with our modern\nbest practices; we tend to use an unsigned integer to store flags.\n\nChange the type of the field to be `unsigned`. While at it, refactor the\nindividual flags to be part of an `enum` instead of using preprocessor\ndefines.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.h | 41 +++++++++++++++++++++--------------------\n 1 file changed, 21 insertions(+), 20 deletions(-)\n\ndiff --git a/refs.h b/refs.h\nindex 68d235438c2..4f0a685714f 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -333,27 +333,28 @@ struct ref_transaction;\n  * stored in ref_iterator::flags. Other bits are for internal use\n  * only:\n  */\n+enum reference_status {\n+\t/* Reference is a symbolic reference. */\n+\tREF_ISSYMREF = (1 << 0),\n \n-/* Reference is a symbolic reference. */\n-#define REF_ISSYMREF 0x01\n+\t/* Reference is a packed reference. */\n+\tREF_ISPACKED = (1 << 1),\n \n-/* Reference is a packed reference. */\n-#define REF_ISPACKED 0x02\n-\n-/*\n- * Reference cannot be resolved to an object name: dangling symbolic\n- * reference (directly or indirectly), corrupt reference file,\n- * reference exists but name is bad, or symbolic reference refers to\n- * ill-formatted reference name.\n- */\n-#define REF_ISBROKEN 0x04\n+\t/*\n+\t * Reference cannot be resolved to an object name: dangling symbolic\n+\t * reference (directly or indirectly), corrupt reference file,\n+\t * reference exists but name is bad, or symbolic reference refers to\n+\t * ill-formatted reference name.\n+\t */\n+\tREF_ISBROKEN = (1 << 2),\n \n-/*\n- * Reference name is not well formed.\n- *\n- * See git-check-ref-format(1) for the definition of well formed ref names.\n- */\n-#define REF_BAD_NAME 0x08\n+\t/*\n+\t * Reference name is not well formed.\n+\t *\n+\t * See git-check-ref-format(1) for the definition of well formed ref names.\n+\t */\n+\tREF_BAD_NAME = (1 << 3),\n+};\n \n /* A reference passed to `for_each_ref()`-style callbacks. */\n struct reference {\n@@ -370,8 +371,8 @@ struct reference {\n \t */\n \tconst struct object_id *oid;\n \n-\t/* A bitfield of `REF_` flags. */\n-\tint flags;\n+\t/* A bitfield of `enum reference_status` flags. */\n+\tunsigned flags;\n };\n \n /*\n\n-- \n2.51.1.851.g4ebd6896fd.dirty\n\n"},{"id":"529387","messageId":"20251022-b4-pks-ref-filter-skip-parsing-objects-v3-5-eb9f71985ef0@pks.im","threadId":"64260","inReplyTo":"20251022-b4-pks-ref-filter-skip-parsing-objects-v3-0-eb9f71985ef0@pks.im","subject":"[PATCH v3 05/14] refs: expose peeled object ID via the iterator","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-22T06:41:05Z","receivedAt":"2025-10-22T06:41:23Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Both the \"files\" and \"reftable\" backend are able to store peeled values\nfor tags in the respective formats. This allows for a more efficient\nlookup of the target object of such a tag without having to manually\npeel via the object database.\n\nThe infrastructure to access these peeled object IDs is somewhat funky\nthough. When iterating through objects, we store a pointer reference to\nthe current iterator in a global variable. The callbacks invoked by that\niterator are then expected to call `peel_iterated_oid()`, which checks\nwhether the globally-stored iterator's current reference refers to the\none handed into that function. If so, we ask the iterator to peel the\nobject, otherwise we manually peel the object via the object database.\nDepending on global state like this is somewhat weird and also quite\nfragile.\n\nIntroduce a new `struct reference::peeled_oid` field that can be\npopulated by the reference backends. This field can be accessed via a\nnew function `reference_get_peeled_oid()` that either uses that value,\nif set, or alternatively peels via the ODB. With this change we don't\nhave to rely on global state anymore, but make the peeled object ID\navailable to the callback functions directly.\n\nAdjust trivial callers that already have a `struct reference` available.\nRemaining callers will be adjusted in subsequent commits.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/describe.c      |  2 +-\n builtin/gc.c            |  2 +-\n builtin/pack-objects.c  |  7 ++++---\n commit-graph.c          |  2 +-\n ls-refs.c               |  2 +-\n midx-write.c            |  2 +-\n pseudo-merge.c          |  2 +-\n refs.c                  | 12 ++++++++++++\n refs.h                  | 19 +++++++++++++++++++\n refs/packed-backend.c   |  1 +\n refs/reftable-backend.c |  5 +++++\n repack-midx.c           |  2 +-\n 12 files changed, 48 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/describe.c b/builtin/describe.c\nindex 79545350443..443546aaac9 100644\n--- a/builtin/describe.c\n+++ b/builtin/describe.c\n@@ -208,7 +208,7 @@ static int get_name(const struct reference *ref, void *cb_data UNUSED)\n \t}\n \n \t/* Is it annotated? */\n-\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled)) {\n+\tif (!reference_get_peeled_oid(the_repository, ref, &peeled)) {\n \t\tis_annotated = !oideq(ref->oid, &peeled);\n \t} else {\n \t\toidcpy(&peeled, ref->oid);\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 9de5de175f6..f0cf20d4238 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -1109,7 +1109,7 @@ static int dfs_on_ref(const struct reference *ref, void *cb_data)\n \tstruct commit_list *stack = NULL;\n \tstruct commit *commit;\n \n-\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n+\tif (!reference_get_peeled_oid(the_repository, ref, &peeled))\n \t\tmaybe_peeled = &peeled;\n \tif (odb_read_object_info(the_repository->objects, maybe_peeled, NULL) != OBJ_COMMIT)\n \t\treturn 0;\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 39633a0158e..1613fecb669 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -838,7 +838,7 @@ static int mark_tagged(const struct reference *ref, void *cb_data UNUSED)\n \n \tif (entry)\n \t\tentry->tagged = 1;\n-\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled)) {\n+\tif (!reference_get_peeled_oid(the_repository, ref, &peeled)) {\n \t\tentry = packlist_find(&to_pack, &peeled);\n \t\tif (entry)\n \t\t\tentry->tagged = 1;\n@@ -3309,7 +3309,8 @@ static int add_ref_tag(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct object_id peeled;\n \n-\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled) && obj_is_packed(&peeled))\n+\tif (!reference_get_peeled_oid(the_repository, ref, &peeled) &&\n+\t    obj_is_packed(&peeled))\n \t\tadd_tag_chain(ref->oid);\n \treturn 0;\n }\n@@ -4537,7 +4538,7 @@ static int mark_bitmap_preferred_tip(const struct reference *ref, void *data UNU\n \tstruct object_id peeled;\n \tstruct object *object;\n \n-\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n+\tif (!reference_get_peeled_oid(the_repository, ref, &peeled))\n \t\tmaybe_peeled = &peeled;\n \n \tobject = parse_object_or_die(the_repository, maybe_peeled, ref->name);\ndiff --git a/commit-graph.c b/commit-graph.c\nindex f91af416259..80be2ff2c39 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -1857,7 +1857,7 @@ static int add_ref_to_set(const struct reference *ref, void *cb_data)\n \tstruct object_id peeled;\n \tstruct refs_cb_data *data = (struct refs_cb_data *)cb_data;\n \n-\tif (!peel_iterated_oid(data->repo, ref->oid, &peeled))\n+\tif (!reference_get_peeled_oid(data->repo, ref, &peeled))\n \t\tmaybe_peeled = &peeled;\n \tif (odb_read_object_info(data->repo->objects, maybe_peeled, NULL) == OBJ_COMMIT)\n \t\toidset_insert(data->commits, maybe_peeled);\ndiff --git a/ls-refs.c b/ls-refs.c\nindex 64d02723691..8641281b86c 100644\n--- a/ls-refs.c\n+++ b/ls-refs.c\n@@ -110,7 +110,7 @@ static int send_ref(const struct reference *ref, void *cb_data)\n \n \tif (data->peel && ref->oid) {\n \t\tstruct object_id peeled;\n-\t\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n+\t\tif (!reference_get_peeled_oid(the_repository, ref, &peeled))\n \t\t\tstrbuf_addf(&data->buf, \" peeled:%s\", oid_to_hex(&peeled));\n \t}\n \ndiff --git a/midx-write.c b/midx-write.c\nindex f4dd875747a..23e61cb0001 100644\n--- a/midx-write.c\n+++ b/midx-write.c\n@@ -709,7 +709,7 @@ static int add_ref_to_pending(const struct reference *ref, void *cb_data)\n \t\treturn 0;\n \t}\n \n-\tif (!peel_iterated_oid(revs->repo, ref->oid, &peeled))\n+\tif (!reference_get_peeled_oid(revs->repo, ref, &peeled))\n \t\tmaybe_peeled = &peeled;\n \n \tobject = parse_object_or_die(revs->repo, maybe_peeled, ref->name);\ndiff --git a/pseudo-merge.c b/pseudo-merge.c\nindex 0abd51b42c1..a2d5bd85f95 100644\n--- a/pseudo-merge.c\n+++ b/pseudo-merge.c\n@@ -230,7 +230,7 @@ static int find_pseudo_merge_group_for_ref(const struct reference *ref, void *_d\n \tuint32_t i;\n \tint has_bitmap;\n \n-\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n+\tif (!reference_get_peeled_oid(the_repository, ref, &peeled))\n \t\tmaybe_peeled = &peeled;\n \n \tc = lookup_commit(the_repository, maybe_peeled);\ndiff --git a/refs.c b/refs.c\nindex f96cf43b128..1b1551f9814 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2334,6 +2334,18 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct\n \treturn peel_object(r, base, peeled) ? -1 : 0;\n }\n \n+int reference_get_peeled_oid(struct repository *repo,\n+\t\t\t     const struct reference *ref,\n+\t\t\t     struct object_id *peeled_oid)\n+{\n+\tif (ref->peeled_oid) {\n+\t\toidcpy(peeled_oid, ref->peeled_oid);\n+\t\treturn 0;\n+\t}\n+\n+\treturn peel_object(repo, ref->oid, peeled_oid) ? -1 : 0;\n+}\n+\n int refs_update_symref(struct ref_store *refs, const char *ref,\n \t\t       const char *target, const char *logmsg)\n {\ndiff --git a/refs.h b/refs.h\nindex 4f0a685714f..886ed2c0f43 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -371,10 +371,29 @@ struct reference {\n \t */\n \tconst struct object_id *oid;\n \n+\t/*\n+\t * An optional peeled object ID. This field _may_ be set for tags in\n+\t * case the peeled value is present in the backend. Please refer to\n+\t * `reference_get_peeled_oid()`.\n+\t */\n+\tconst struct object_id *peeled_oid;\n+\n \t/* A bitfield of `enum reference_status` flags. */\n \tunsigned flags;\n };\n \n+/*\n+ * Peel the tag to a non-tag commit. If present, this uses the peeled object ID\n+ * exposed by the reference backend. Otherwise, the object is peeled via the\n+ * object database, which is less efficient.\n+ *\n+ * Return `0` if the reference could be peeled, a negative error code\n+ * otherwise.\n+ */\n+int reference_get_peeled_oid(struct repository *repo,\n+\t\t\t     const struct reference *ref,\n+\t\t\t     struct object_id *peeled_oid);\n+\n /*\n  * The signature for the callback function for the for_each_*()\n  * functions below.  The memory pointed to by the `struct reference`\ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 711e07f8326..1fefefd54ed 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -963,6 +963,7 @@ static int next_record(struct packed_ref_iterator *iter)\n \t\t\titer->base.ref.flags &= ~REF_KNOWS_PEELED;\n \t\t} else {\n \t\t\titer->base.ref.flags |= REF_KNOWS_PEELED;\n+\t\t\titer->base.ref.peeled_oid = &iter->peeled;\n \t\t}\n \t} else {\n \t\toidclr(&iter->peeled, iter->repo->hash_algo);\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 728886eafd3..e214e120d77 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -547,6 +547,7 @@ struct reftable_ref_iterator {\n \tstruct reftable_iterator iter;\n \tstruct reftable_ref_record ref;\n \tstruct object_id oid;\n+\tstruct object_id peeled_oid;\n \n \tchar *prefix;\n \tsize_t prefix_len;\n@@ -671,6 +672,8 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\tcase REFTABLE_REF_VAL2:\n \t\t\toidread(&iter->oid, iter->ref.value.val2.value,\n \t\t\t\trefs->base.repo->hash_algo);\n+\t\t\toidread(&iter->peeled_oid, iter->ref.value.val2.target_value,\n+\t\t\t\trefs->base.repo->hash_algo);\n \t\t\tbreak;\n \t\tcase REFTABLE_REF_SYMREF:\n \t\t\treferent = refs_resolve_ref_unsafe(&iter->refs->base,\n@@ -708,6 +711,8 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\titer->base.ref.name = iter->ref.refname;\n \t\titer->base.ref.target = referent;\n \t\titer->base.ref.oid = &iter->oid;\n+\t\tif (iter->ref.value_type == REFTABLE_REF_VAL2)\n+\t\t\titer->base.ref.peeled_oid = &iter->peeled_oid;\n \t\titer->base.ref.flags = flags;\n \n \t\tbreak;\ndiff --git a/repack-midx.c b/repack-midx.c\nindex 349f7e20b53..74bdfa3a6e9 100644\n--- a/repack-midx.c\n+++ b/repack-midx.c\n@@ -22,7 +22,7 @@ static int midx_snapshot_ref_one(const struct reference *ref, void *_data)\n \tconst struct object_id *maybe_peeled = ref->oid;\n \tstruct object_id peeled;\n \n-\tif (!peel_iterated_oid(data->repo, ref->oid, &peeled))\n+\tif (!reference_get_peeled_oid(data->repo, ref, &peeled))\n \t\tmaybe_peeled = &peeled;\n \n \tif (oidset_insert(&data->seen, maybe_peeled))\n\n-- \n2.51.1.851.g4ebd6896fd.dirty\n\n"},{"id":"529388","messageId":"20251022-b4-pks-ref-filter-skip-parsing-objects-v3-6-eb9f71985ef0@pks.im","threadId":"64260","inReplyTo":"20251022-b4-pks-ref-filter-skip-parsing-objects-v3-0-eb9f71985ef0@pks.im","subject":"[PATCH v3 06/14] upload-pack: convert to use `reference_get_peeled_oid()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-22T06:41:06Z","receivedAt":"2025-10-22T06:41:26Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `write_v0_ref()` callback is invoked from two callsites:\n\n  - Once via `send_ref()` which is a callback passed to\n    `for_each_namespaced_ref_1()` and `refs_head_ref_namespaced()`.\n\n  - Once manually to announce capabilities.\n\nWhen sending references to the client we also send the peeled value of\ntags. As we don't have a `struct reference` available in the second\ncase, we cannot easily peel by calling `reference_get_peeled_oid()`, but\nwe instead have to depend on on global state via `peel_iterated_oid()`.\n\nWe do have a reference available though in the first case, it's only the\nsecond case that keeps us from using `reference_get_peeled_oid()`. But\nthat second case only announces capabilities anyway, so we're not really\nhandling a reference at all here.\n\nAdapt that case to construct a reference manually and pass that to\n`write_v0_ref()`. Start to use `reference_get_peeled_oid()` now that we\nalways have a `struct reference` available.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n upload-pack.c | 22 +++++++++++++---------\n 1 file changed, 13 insertions(+), 9 deletions(-)\n\ndiff --git a/upload-pack.c b/upload-pack.c\nindex 0d563ae74e9..2d2b70cbf2d 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -1249,15 +1249,15 @@ static void format_session_id(struct strbuf *buf, struct upload_pack_data *d) {\n }\n \n static void write_v0_ref(struct upload_pack_data *data,\n-\t\t\tconst char *refname, const char *refname_nons,\n-\t\t\tconst struct object_id *oid)\n+\t\t\t const struct reference *ref,\n+\t\t\t const char *refname_nons)\n {\n \tstatic const char *capabilities = \"multi_ack thin-pack side-band\"\n \t\t\" side-band-64k ofs-delta shallow deepen-since deepen-not\"\n \t\t\" deepen-relative no-progress include-tag multi_ack_detailed\";\n \tstruct object_id peeled;\n \n-\tif (mark_our_ref(refname_nons, refname, oid, &data->hidden_refs))\n+\tif (mark_our_ref(refname_nons, ref->name, ref->oid, &data->hidden_refs))\n \t\treturn;\n \n \tif (capabilities) {\n@@ -1267,7 +1267,7 @@ static void write_v0_ref(struct upload_pack_data *data,\n \t\tformat_symref_info(&symref_info, &data->symref);\n \t\tformat_session_id(&session_id, data);\n \t\tpacket_fwrite_fmt(stdout, \"%s %s%c%s%s%s%s%s%s%s object-format=%s agent=%s\\n\",\n-\t\t\t     oid_to_hex(oid), refname_nons,\n+\t\t\t     oid_to_hex(ref->oid), refname_nons,\n \t\t\t     0, capabilities,\n \t\t\t     (data->allow_uor & ALLOW_TIP_SHA1) ?\n \t\t\t\t     \" allow-tip-sha1-in-want\" : \"\",\n@@ -1283,17 +1283,17 @@ static void write_v0_ref(struct upload_pack_data *data,\n \t\tstrbuf_release(&session_id);\n \t\tdata->sent_capabilities = 1;\n \t} else {\n-\t\tpacket_fwrite_fmt(stdout, \"%s %s\\n\", oid_to_hex(oid), refname_nons);\n+\t\tpacket_fwrite_fmt(stdout, \"%s %s\\n\", oid_to_hex(ref->oid), refname_nons);\n \t}\n \tcapabilities = NULL;\n-\tif (!peel_iterated_oid(the_repository, oid, &peeled))\n+\tif (!reference_get_peeled_oid(the_repository, ref, &peeled))\n \t\tpacket_fwrite_fmt(stdout, \"%s %s^{}\\n\", oid_to_hex(&peeled), refname_nons);\n \treturn;\n }\n \n static int send_ref(const struct reference *ref, void *cb_data)\n {\n-\twrite_v0_ref(cb_data, ref->name, strip_namespace(ref->name), ref->oid);\n+\twrite_v0_ref(cb_data, ref, strip_namespace(ref->name));\n \treturn 0;\n }\n \n@@ -1442,8 +1442,12 @@ void upload_pack(const int advertise_refs, const int stateless_rpc,\n \t\t\t\t\t send_ref, &data);\n \t\tfor_each_namespaced_ref_1(send_ref, &data);\n \t\tif (!data.sent_capabilities) {\n-\t\t\tconst char *refname = \"capabilities^{}\";\n-\t\t\twrite_v0_ref(&data, refname, refname, null_oid(the_hash_algo));\n+\t\t\tstruct reference ref = {\n+\t\t\t\t.name = \"capabilities^{}\",\n+\t\t\t\t.oid = null_oid(the_hash_algo),\n+\t\t\t};\n+\n+\t\t\twrite_v0_ref(&data, &ref, ref.name);\n \t\t}\n \t\t/*\n \t\t * fflush stdout before calling advertise_shallow_grafts because send_ref\n\n-- \n2.51.1.851.g4ebd6896fd.dirty\n\n"},{"id":"529389","messageId":"20251022-b4-pks-ref-filter-skip-parsing-objects-v3-7-eb9f71985ef0@pks.im","threadId":"64260","inReplyTo":"20251022-b4-pks-ref-filter-skip-parsing-objects-v3-0-eb9f71985ef0@pks.im","subject":"[PATCH v3 07/14] ref-filter: propagate peeled object ID","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-22T06:41:07Z","receivedAt":"2025-10-22T06:41:28Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When queueing a reference in the \"ref-filter\" subsystem we end up\ncreating a new ref array item that contains the reference's info. One\nbit of info that we always discard though is the peeled object ID, and\nbecause of that we are forced to use `peel_iterated_oid()`.\n\nRefactor the code to propagate the peeled object ID via the ref array,\nif available. This allows us to manually peel tags without having to go\nthrough the object database.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/ls-remote.c  |  2 +-\n builtin/tag.c        |  2 +-\n builtin/verify-tag.c |  2 +-\n ref-filter.c         | 66 ++++++++++++++++++++++++++++++----------------------\n ref-filter.h         |  5 +++-\n 5 files changed, 45 insertions(+), 32 deletions(-)\n\ndiff --git a/builtin/ls-remote.c b/builtin/ls-remote.c\nindex df09000b30d..fe77829557f 100644\n--- a/builtin/ls-remote.c\n+++ b/builtin/ls-remote.c\n@@ -156,7 +156,7 @@ int cmd_ls_remote(int argc,\n \t\t\tcontinue;\n \t\tif (!tail_match(&pattern, ref->name))\n \t\t\tcontinue;\n-\t\titem = ref_array_push(&ref_array, ref->name, &ref->old_oid);\n+\t\titem = ref_array_push(&ref_array, ref->name, &ref->old_oid, NULL);\n \t\titem->symref = xstrdup_or_null(ref->symref);\n \t}\n \ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex f0665af3acd..01eba90c5c7 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -153,7 +153,7 @@ static int verify_tag(const char *name, const char *ref UNUSED,\n \t\treturn -1;\n \n \tif (format->format)\n-\t\tpretty_print_ref(name, oid, format);\n+\t\tpretty_print_ref(name, oid, NULL, format);\n \n \treturn 0;\n }\ndiff --git a/builtin/verify-tag.c b/builtin/verify-tag.c\nindex cd6bc11095d..558121eaa16 100644\n--- a/builtin/verify-tag.c\n+++ b/builtin/verify-tag.c\n@@ -67,7 +67,7 @@ int cmd_verify_tag(int argc,\n \t\t}\n \n \t\tif (format.format)\n-\t\t\tpretty_print_ref(name, &oid, &format);\n+\t\t\tpretty_print_ref(name, &oid, NULL, &format);\n \t}\n \treturn had_error;\n }\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 6837fa60a9b..7fd8babec8f 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -2578,8 +2578,15 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)\n \t * If it is a tag object, see if we use the peeled value. If we do,\n \t * grab the peeled OID.\n \t */\n-\tif (need_tagged && peel_iterated_oid(the_repository, &obj->oid, &oi_deref.oid))\n-\t\tdie(\"bad tag\");\n+\tif (need_tagged) {\n+\t\tif (!is_null_oid(&ref->peeled_oid)) {\n+\t\t\toidcpy(&oi_deref.oid, &ref->peeled_oid);\n+\t\t} else if (!peel_object(the_repository, &obj->oid, &oi_deref.oid)) {\n+\t\t\t/* We managed to peel the object ourselves. */\n+\t\t} else {\n+\t\t\tdie(\"bad tag\");\n+\t\t}\n+\t}\n \n \treturn get_object(ref, 1, &obj, &oi_deref, err);\n }\n@@ -2807,12 +2814,15 @@ static int match_points_at(struct oid_array *points_at,\n  * Callers can then fill in other struct members at their leisure.\n  */\n static struct ref_array_item *new_ref_array_item(const char *refname,\n-\t\t\t\t\t\t const struct object_id *oid)\n+\t\t\t\t\t\t const struct object_id *oid,\n+\t\t\t\t\t\t const struct object_id *peeled_oid)\n {\n \tstruct ref_array_item *ref;\n \n \tFLEX_ALLOC_STR(ref, refname, refname);\n \toidcpy(&ref->objectname, oid);\n+\tif (peeled_oid)\n+\t\toidcpy(&ref->peeled_oid, peeled_oid);\n \tref->rest = NULL;\n \n \treturn ref;\n@@ -2826,9 +2836,10 @@ static void ref_array_append(struct ref_array *array, struct ref_array_item *ref\n \n struct ref_array_item *ref_array_push(struct ref_array *array,\n \t\t\t\t      const char *refname,\n-\t\t\t\t      const struct object_id *oid)\n+\t\t\t\t      const struct object_id *oid,\n+\t\t\t\t      const struct object_id *peeled_oid)\n {\n-\tstruct ref_array_item *ref = new_ref_array_item(refname, oid);\n+\tstruct ref_array_item *ref = new_ref_array_item(refname, oid, peeled_oid);\n \tref_array_append(array, ref);\n \treturn ref;\n }\n@@ -2871,25 +2882,25 @@ static int filter_ref_kind(struct ref_filter *filter, const char *refname)\n \treturn ref_kind_from_refname(refname);\n }\n \n-static struct ref_array_item *apply_ref_filter(const char *refname, const char *referent, const struct object_id *oid,\n-\t\t\t    int flag, struct ref_filter *filter)\n+static struct ref_array_item *apply_ref_filter(const struct reference *ref,\n+\t\t\t\t\t       struct ref_filter *filter)\n {\n-\tstruct ref_array_item *ref;\n+\tstruct ref_array_item *item;\n \tstruct commit *commit = NULL;\n \tunsigned int kind;\n \n-\tif (flag & REF_BAD_NAME) {\n-\t\twarning(_(\"ignoring ref with broken name %s\"), refname);\n+\tif (ref->flags & REF_BAD_NAME) {\n+\t\twarning(_(\"ignoring ref with broken name %s\"), ref->name);\n \t\treturn NULL;\n \t}\n \n-\tif (flag & REF_ISBROKEN) {\n-\t\twarning(_(\"ignoring broken ref %s\"), refname);\n+\tif (ref->flags & REF_ISBROKEN) {\n+\t\twarning(_(\"ignoring broken ref %s\"), ref->name);\n \t\treturn NULL;\n \t}\n \n \t/* Obtain the current ref kind from filter_ref_kind() and ignore unwanted refs. */\n-\tkind = filter_ref_kind(filter, refname);\n+\tkind = filter_ref_kind(filter, ref->name);\n \n \t/*\n \t * Generally HEAD refs are printed with special description denoting a rebase,\n@@ -2902,13 +2913,13 @@ static struct ref_array_item *apply_ref_filter(const char *refname, const char *\n \telse if (!(kind & filter->kind))\n \t\treturn NULL;\n \n-\tif (!filter_pattern_match(filter, refname))\n+\tif (!filter_pattern_match(filter, ref->name))\n \t\treturn NULL;\n \n-\tif (filter_exclude_match(filter, refname))\n+\tif (filter_exclude_match(filter, ref->name))\n \t\treturn NULL;\n \n-\tif (filter->points_at.nr && !match_points_at(&filter->points_at, oid, refname))\n+\tif (filter->points_at.nr && !match_points_at(&filter->points_at, ref->oid, ref->name))\n \t\treturn NULL;\n \n \t/*\n@@ -2918,7 +2929,7 @@ static struct ref_array_item *apply_ref_filter(const char *refname, const char *\n \t */\n \tif (filter->reachable_from || filter->unreachable_from ||\n \t    filter->with_commit || filter->no_commit || filter->verbose) {\n-\t\tcommit = lookup_commit_reference_gently(the_repository, oid, 1);\n+\t\tcommit = lookup_commit_reference_gently(the_repository, ref->oid, 1);\n \t\tif (!commit)\n \t\t\treturn NULL;\n \t\t/* We perform the filtering for the '--contains' option... */\n@@ -2936,13 +2947,13 @@ static struct ref_array_item *apply_ref_filter(const char *refname, const char *\n \t * to do its job and the resulting list may yet to be pruned\n \t * by maxcount logic.\n \t */\n-\tref = new_ref_array_item(refname, oid);\n-\tref->commit = commit;\n-\tref->flag = flag;\n-\tref->kind = kind;\n-\tref->symref = xstrdup_or_null(referent);\n+\titem = new_ref_array_item(ref->name, ref->oid, ref->peeled_oid);\n+\titem->commit = commit;\n+\titem->flag = ref->flags;\n+\titem->kind = kind;\n+\titem->symref = xstrdup_or_null(ref->target);\n \n-\treturn ref;\n+\treturn item;\n }\n \n struct ref_filter_cbdata {\n@@ -2959,8 +2970,7 @@ static int filter_one(const struct reference *ref, void *cb_data)\n \tstruct ref_filter_cbdata *ref_cbdata = cb_data;\n \tstruct ref_array_item *item;\n \n-\titem = apply_ref_filter(ref->name, ref->target, ref->oid,\n-\t\t\t\tref->flags, ref_cbdata->filter);\n+\titem = apply_ref_filter(ref, ref_cbdata->filter);\n \tif (item)\n \t\tref_array_append(ref_cbdata->array, item);\n \n@@ -2997,8 +3007,7 @@ static int filter_and_format_one(const struct reference *ref, void *cb_data)\n \tstruct ref_array_item *item;\n \tstruct strbuf output = STRBUF_INIT, err = STRBUF_INIT;\n \n-\titem = apply_ref_filter(ref->name, ref->target, ref->oid,\n-\t\t\t\tref->flags, ref_cbdata->filter);\n+\titem = apply_ref_filter(ref, ref_cbdata->filter);\n \tif (!item)\n \t\treturn 0;\n \n@@ -3585,13 +3594,14 @@ void print_formatted_ref_array(struct ref_array *array, struct ref_format *forma\n }\n \n void pretty_print_ref(const char *name, const struct object_id *oid,\n+\t\t      const struct object_id *peeled_oid,\n \t\t      struct ref_format *format)\n {\n \tstruct ref_array_item *ref_item;\n \tstruct strbuf output = STRBUF_INIT;\n \tstruct strbuf err = STRBUF_INIT;\n \n-\tref_item = new_ref_array_item(name, oid);\n+\tref_item = new_ref_array_item(name, oid, peeled_oid);\n \tref_item->kind = ref_kind_from_refname(name);\n \tif (format_ref_array_item(ref_item, format, &output, &err))\n \t\tdie(\"%s\", err.buf);\ndiff --git a/ref-filter.h b/ref-filter.h\nindex 235c60f79c9..120221b47fa 100644\n--- a/ref-filter.h\n+++ b/ref-filter.h\n@@ -41,6 +41,7 @@ enum ref_sorting_order {\n \n struct ref_array_item {\n \tstruct object_id objectname;\n+\tstruct object_id peeled_oid;\n \tconst char *rest;\n \tint flag;\n \tunsigned int kind;\n@@ -187,6 +188,7 @@ void print_formatted_ref_array(struct ref_array *array, struct ref_format *forma\n  * name must be a fully qualified refname.\n  */\n void pretty_print_ref(const char *name, const struct object_id *oid,\n+\t\t      const struct object_id *peeled_oid,\n \t\t      struct ref_format *format);\n \n /*\n@@ -195,7 +197,8 @@ void pretty_print_ref(const char *name, const struct object_id *oid,\n  */\n struct ref_array_item *ref_array_push(struct ref_array *array,\n \t\t\t\t      const char *refname,\n-\t\t\t\t      const struct object_id *oid);\n+\t\t\t\t      const struct object_id *oid,\n+\t\t\t\t      const struct object_id *peeled_oid);\n \n /*\n  * If the provided format includes ahead-behind atoms, then compute the\n\n-- \n2.51.1.851.g4ebd6896fd.dirty\n\n"},{"id":"529390","messageId":"20251022-b4-pks-ref-filter-skip-parsing-objects-v3-8-eb9f71985ef0@pks.im","threadId":"64260","inReplyTo":"20251022-b4-pks-ref-filter-skip-parsing-objects-v3-0-eb9f71985ef0@pks.im","subject":"[PATCH v3 08/14] builtin/show-ref: convert to use `reference_get_peeled_oid()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-22T06:41:08Z","receivedAt":"2025-10-22T06:41:31Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The git-show-ref(1) command has multiple different modes:\n\n  - It knows to show all references matching a pattern.\n\n  - It knows to list all references that are an exact match to whatever\n    the user has provided.\n\n  - It knows to check for reference existence.\n\nThe first two commands use mostly the same infrastructure to print the\nreferences via `show_one()`. But while the former mode uses a proper\niterator and thus has a `struct reference` available in its context, the\nlatter calls `refs_read_ref()` and thus doesn't. Consequently, we cannot\neasily use `reference_get_peeled_oid()` to print the peeled value.\n\nAdapt the code so that we manually construct a `struct reference` when\nverifying refs. We wouldn't ever have the peeled value available anyway\nas we're not using an iterator here, so we can simply plug in the values\nwe _do_ have.\n\nWith this change we now have a `struct reference` available at both\ncallsites of `show_one()` and can thus pass it, which allows us to use\n`reference_get_peeled_oid()` instead of `peel_iterated_oid()`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/show-ref.c | 32 +++++++++++++++++++-------------\n 1 file changed, 19 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex 4803b5e5986..4d4984e4e0c 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -31,31 +31,31 @@ struct show_one_options {\n };\n \n static void show_one(const struct show_one_options *opts,\n-\t\t     const char *refname, const struct object_id *oid)\n+\t\t     const struct reference *ref)\n {\n \tconst char *hex;\n \tstruct object_id peeled;\n \n-\tif (!odb_has_object(the_repository->objects, oid,\n+\tif (!odb_has_object(the_repository->objects, ref->oid,\n \t\t\t    HAS_OBJECT_RECHECK_PACKED | HAS_OBJECT_FETCH_PROMISOR))\n-\t\tdie(\"git show-ref: bad ref %s (%s)\", refname,\n-\t\t    oid_to_hex(oid));\n+\t\tdie(\"git show-ref: bad ref %s (%s)\", ref->name,\n+\t\t    oid_to_hex(ref->oid));\n \n \tif (opts->quiet)\n \t\treturn;\n \n-\thex = repo_find_unique_abbrev(the_repository, oid, opts->abbrev);\n+\thex = repo_find_unique_abbrev(the_repository, ref->oid, opts->abbrev);\n \tif (opts->hash_only)\n \t\tprintf(\"%s\\n\", hex);\n \telse\n-\t\tprintf(\"%s %s\\n\", hex, refname);\n+\t\tprintf(\"%s %s\\n\", hex, ref->name);\n \n \tif (!opts->deref_tags)\n \t\treturn;\n \n-\tif (!peel_iterated_oid(the_repository, oid, &peeled)) {\n+\tif (!reference_get_peeled_oid(the_repository, ref, &peeled)) {\n \t\thex = repo_find_unique_abbrev(the_repository, &peeled, opts->abbrev);\n-\t\tprintf(\"%s %s^{}\\n\", hex, refname);\n+\t\tprintf(\"%s %s^{}\\n\", hex, ref->name);\n \t}\n }\n \n@@ -93,7 +93,7 @@ static int show_ref(const struct reference *ref, void *cbdata)\n match:\n \tdata->found_match++;\n \n-\tshow_one(data->show_one_opts, ref->name, ref->oid);\n+\tshow_one(data->show_one_opts, ref);\n \n \treturn 0;\n }\n@@ -175,12 +175,18 @@ static int cmd_show_ref__verify(const struct show_one_options *show_one_opts,\n \n \t\tif ((starts_with(*refs, \"refs/\") || refname_is_safe(*refs)) &&\n \t\t    !refs_read_ref(get_main_ref_store(the_repository), *refs, &oid)) {\n-\t\t\tshow_one(show_one_opts, *refs, &oid);\n-\t\t}\n-\t\telse if (!show_one_opts->quiet)\n+\t\t\tstruct reference ref = {\n+\t\t\t\t.name = *refs,\n+\t\t\t\t.oid = &oid,\n+\t\t\t};\n+\n+\t\t\tshow_one(show_one_opts, &ref);\n+\t\t} else if (!show_one_opts->quiet) {\n \t\t\tdie(\"'%s' - not a valid ref\", *refs);\n-\t\telse\n+\t\t} else {\n \t\t\treturn 1;\n+\t\t}\n+\n \t\trefs++;\n \t}\n \n\n-- \n2.51.1.851.g4ebd6896fd.dirty\n\n"},{"id":"529391","messageId":"20251022-b4-pks-ref-filter-skip-parsing-objects-v3-9-eb9f71985ef0@pks.im","threadId":"64260","inReplyTo":"20251022-b4-pks-ref-filter-skip-parsing-objects-v3-0-eb9f71985ef0@pks.im","subject":"[PATCH v3 09/14] refs: drop `current_ref_iter` hack","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-22T06:41:09Z","receivedAt":"2025-10-22T06:41:34Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In preceding commits we have refactored all callers of\n`peel_iterated_oid()` to instead use `reference_get_peeled_oid()`. This\nallows us to thus get rid of the former function.\n\nGetting rid of that function is nice, but even nicer is that this also\nallows us to get rid of the `current_ref_iter` hack. This global\nvariable tracked the currently-active ref iterator so that we can use it\nto peel an object ID. Now that the peeled object ID is propagated via\n`struct reference` though we don't have to depend on this hack anymore,\nwhich makes for a more robust and easier-to-understand infrastructure.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c               | 10 ----------\n refs/iterator.c      |  5 -----\n refs/refs-internal.h | 13 -------------\n 3 files changed, 28 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 1b1551f9814..9d8f0a9ca4a 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2324,16 +2324,6 @@ int refs_optimize(struct ref_store *refs, struct pack_refs_opts *opts)\n \treturn refs->be->optimize(refs, opts);\n }\n \n-int peel_iterated_oid(struct repository *r, const struct object_id *base, struct object_id *peeled)\n-{\n-\tif (current_ref_iter &&\n-\t    (current_ref_iter->ref.oid == base ||\n-\t     oideq(current_ref_iter->ref.oid, base)))\n-\t\treturn ref_iterator_peel(current_ref_iter, peeled);\n-\n-\treturn peel_object(r, base, peeled) ? -1 : 0;\n-}\n-\n int reference_get_peeled_oid(struct repository *repo,\n \t\t\t     const struct reference *ref,\n \t\t\t     struct object_id *peeled_oid)\ndiff --git a/refs/iterator.c b/refs/iterator.c\nindex fe5980e1b6c..072c6aacdb0 100644\n--- a/refs/iterator.c\n+++ b/refs/iterator.c\n@@ -458,15 +458,11 @@ struct ref_iterator *prefix_ref_iterator_begin(struct ref_iterator *iter0,\n \treturn ref_iterator;\n }\n \n-struct ref_iterator *current_ref_iter = NULL;\n-\n int do_for_each_ref_iterator(struct ref_iterator *iter,\n \t\t\t     each_ref_fn fn, void *cb_data)\n {\n \tint retval = 0, ok;\n-\tstruct ref_iterator *old_ref_iter = current_ref_iter;\n \n-\tcurrent_ref_iter = iter;\n \twhile ((ok = ref_iterator_advance(iter)) == ITER_OK) {\n \t\tretval = fn(&iter->ref, cb_data);\n \t\tif (retval)\n@@ -474,7 +470,6 @@ int do_for_each_ref_iterator(struct ref_iterator *iter,\n \t}\n \n out:\n-\tcurrent_ref_iter = old_ref_iter;\n \tif (ok == ITER_ERROR)\n \t\tretval = -1;\n \tref_iterator_free(iter);\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex ed749d16572..f4f845bbeaf 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -376,19 +376,6 @@ struct ref_iterator_vtable {\n \tref_iterator_release_fn *release;\n };\n \n-/*\n- * current_ref_iter is a performance hack: when iterating over\n- * references using the for_each_ref*() functions, current_ref_iter is\n- * set to the reference iterator before calling the callback function.\n- * If the callback function calls peel_ref(), then peel_ref() first\n- * checks whether the reference to be peeled is the one referred to by\n- * the iterator (it usually is) and if so, asks the iterator for the\n- * peeled version of the reference if it is available. This avoids a\n- * refname lookup in a common case. current_ref_iter is set to NULL\n- * when the iteration is over.\n- */\n-extern struct ref_iterator *current_ref_iter;\n-\n struct ref_store;\n \n /* refs backends */\n\n-- \n2.51.1.851.g4ebd6896fd.dirty\n\n"},{"id":"529392","messageId":"20251022-b4-pks-ref-filter-skip-parsing-objects-v3-10-eb9f71985ef0@pks.im","threadId":"64260","inReplyTo":"20251022-b4-pks-ref-filter-skip-parsing-objects-v3-0-eb9f71985ef0@pks.im","subject":"[PATCH v3 10/14] refs: drop infrastructure to peel via iterators","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-22T06:41:10Z","receivedAt":"2025-10-22T06:41:37Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Now that the peeled object ID gets propagated via the `struct reference`\nthere is no need anymore to call into the reference iterator itself to\ndereference an object. Remove this infrastructure.\n\nMost of the changes are straight-forward deletions of code. There is one\nexception though in `refs/packed-backend.c::write_with_updates()`. Here\nwe stop peeling the iterator and instead just pass the peeled object ID\nof that iterator directly.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.h                  | 14 --------------\n refs/debug.c            | 11 -----------\n refs/files-backend.c    | 17 -----------------\n refs/iterator.c         | 36 ------------------------------------\n refs/packed-backend.c   | 24 +-----------------------\n refs/ref-cache.c        |  9 ---------\n refs/refs-internal.h    |  7 -------\n refs/reftable-backend.c | 24 ------------------------\n 8 files changed, 1 insertion(+), 141 deletions(-)\n\ndiff --git a/refs.h b/refs.h\nindex 886ed2c0f43..2dd7ac1a16a 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -1289,10 +1289,6 @@ int repo_migrate_ref_storage_format(struct repository *repo,\n  * to the next entry, ref_iterator_advance() aborts the iteration,\n  * frees the ref_iterator, and returns ITER_ERROR.\n  *\n- * The reference currently being looked at can be peeled by calling\n- * ref_iterator_peel(). This function is often faster than peel_ref(),\n- * so it should be preferred when iterating over references.\n- *\n  * Putting it all together, a typical iteration looks like this:\n  *\n  *     int ok;\n@@ -1307,9 +1303,6 @@ int repo_migrate_ref_storage_format(struct repository *repo,\n  *             // Access information about the current reference:\n  *             if (!(iter->flags & REF_ISSYMREF))\n  *                     printf(\"%s is %s\\n\", iter->refname, oid_to_hex(iter->oid));\n- *\n- *             // If you need to peel the reference:\n- *             ref_iterator_peel(iter, &oid);\n  *     }\n  *\n  *     if (ok != ITER_DONE)\n@@ -1400,13 +1393,6 @@ enum ref_iterator_seek_flag {\n int ref_iterator_seek(struct ref_iterator *ref_iterator, const char *refname,\n \t\t      unsigned int flags);\n \n-/*\n- * If possible, peel the reference currently being viewed by the\n- * iterator. Return 0 on success.\n- */\n-int ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t      struct object_id *peeled);\n-\n /* Free the reference iterator and any associated resources. */\n void ref_iterator_free(struct ref_iterator *ref_iterator);\n \ndiff --git a/refs/debug.c b/refs/debug.c\nindex 67718bd1f49..01499b9033c 100644\n--- a/refs/debug.c\n+++ b/refs/debug.c\n@@ -177,16 +177,6 @@ static int debug_ref_iterator_seek(struct ref_iterator *ref_iterator,\n \treturn res;\n }\n \n-static int debug_ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t\t\t   struct object_id *peeled)\n-{\n-\tstruct debug_ref_iterator *diter =\n-\t\t(struct debug_ref_iterator *)ref_iterator;\n-\tint res = diter->iter->vtable->peel(diter->iter, peeled);\n-\ttrace_printf_key(&trace_refs, \"iterator_peel: %s: %d\\n\", diter->iter->ref.name, res);\n-\treturn res;\n-}\n-\n static void debug_ref_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct debug_ref_iterator *diter =\n@@ -198,7 +188,6 @@ static void debug_ref_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable debug_ref_iterator_vtable = {\n \t.advance = debug_ref_iterator_advance,\n \t.seek = debug_ref_iterator_seek,\n-\t.peel = debug_ref_iterator_peel,\n \t.release = debug_ref_iterator_release,\n };\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex fac53fa052d..5aeb454fb47 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -993,15 +993,6 @@ static int files_ref_iterator_seek(struct ref_iterator *ref_iterator,\n \treturn ref_iterator_seek(iter->iter0, refname, flags);\n }\n \n-static int files_ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t\t\t   struct object_id *peeled)\n-{\n-\tstruct files_ref_iterator *iter =\n-\t\t(struct files_ref_iterator *)ref_iterator;\n-\n-\treturn ref_iterator_peel(iter->iter0, peeled);\n-}\n-\n static void files_ref_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct files_ref_iterator *iter =\n@@ -1012,7 +1003,6 @@ static void files_ref_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable files_ref_iterator_vtable = {\n \t.advance = files_ref_iterator_advance,\n \t.seek = files_ref_iterator_seek,\n-\t.peel = files_ref_iterator_peel,\n \t.release = files_ref_iterator_release,\n };\n \n@@ -2388,12 +2378,6 @@ static int files_reflog_iterator_seek(struct ref_iterator *ref_iterator UNUSED,\n \tBUG(\"ref_iterator_seek() called for reflog_iterator\");\n }\n \n-static int files_reflog_iterator_peel(struct ref_iterator *ref_iterator UNUSED,\n-\t\t\t\t      struct object_id *peeled UNUSED)\n-{\n-\tBUG(\"ref_iterator_peel() called for reflog_iterator\");\n-}\n-\n static void files_reflog_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct files_reflog_iterator *iter =\n@@ -2404,7 +2388,6 @@ static void files_reflog_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable files_reflog_iterator_vtable = {\n \t.advance = files_reflog_iterator_advance,\n \t.seek = files_reflog_iterator_seek,\n-\t.peel = files_reflog_iterator_peel,\n \t.release = files_reflog_iterator_release,\n };\n \ndiff --git a/refs/iterator.c b/refs/iterator.c\nindex 072c6aacdb0..d79aa5ec82d 100644\n--- a/refs/iterator.c\n+++ b/refs/iterator.c\n@@ -21,12 +21,6 @@ int ref_iterator_seek(struct ref_iterator *ref_iterator, const char *refname,\n \treturn ref_iterator->vtable->seek(ref_iterator, refname, flags);\n }\n \n-int ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t      struct object_id *peeled)\n-{\n-\treturn ref_iterator->vtable->peel(ref_iterator, peeled);\n-}\n-\n void ref_iterator_free(struct ref_iterator *ref_iterator)\n {\n \tif (ref_iterator) {\n@@ -60,12 +54,6 @@ static int empty_ref_iterator_seek(struct ref_iterator *ref_iterator UNUSED,\n \treturn 0;\n }\n \n-static int empty_ref_iterator_peel(struct ref_iterator *ref_iterator UNUSED,\n-\t\t\t\t   struct object_id *peeled UNUSED)\n-{\n-\tBUG(\"peel called for empty iterator\");\n-}\n-\n static void empty_ref_iterator_release(struct ref_iterator *ref_iterator UNUSED)\n {\n }\n@@ -73,7 +61,6 @@ static void empty_ref_iterator_release(struct ref_iterator *ref_iterator UNUSED)\n static struct ref_iterator_vtable empty_ref_iterator_vtable = {\n \t.advance = empty_ref_iterator_advance,\n \t.seek = empty_ref_iterator_seek,\n-\t.peel = empty_ref_iterator_peel,\n \t.release = empty_ref_iterator_release,\n };\n \n@@ -240,18 +227,6 @@ static int merge_ref_iterator_seek(struct ref_iterator *ref_iterator,\n \treturn 0;\n }\n \n-static int merge_ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t\t\t   struct object_id *peeled)\n-{\n-\tstruct merge_ref_iterator *iter =\n-\t\t(struct merge_ref_iterator *)ref_iterator;\n-\n-\tif (!iter->current) {\n-\t\tBUG(\"peel called before advance for merge iterator\");\n-\t}\n-\treturn ref_iterator_peel(*iter->current, peeled);\n-}\n-\n static void merge_ref_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct merge_ref_iterator *iter =\n@@ -263,7 +238,6 @@ static void merge_ref_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable merge_ref_iterator_vtable = {\n \t.advance = merge_ref_iterator_advance,\n \t.seek = merge_ref_iterator_seek,\n-\t.peel = merge_ref_iterator_peel,\n \t.release = merge_ref_iterator_release,\n };\n \n@@ -412,15 +386,6 @@ static int prefix_ref_iterator_seek(struct ref_iterator *ref_iterator,\n \treturn ref_iterator_seek(iter->iter0, refname, flags);\n }\n \n-static int prefix_ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t\t\t    struct object_id *peeled)\n-{\n-\tstruct prefix_ref_iterator *iter =\n-\t\t(struct prefix_ref_iterator *)ref_iterator;\n-\n-\treturn ref_iterator_peel(iter->iter0, peeled);\n-}\n-\n static void prefix_ref_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct prefix_ref_iterator *iter =\n@@ -432,7 +397,6 @@ static void prefix_ref_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable prefix_ref_iterator_vtable = {\n \t.advance = prefix_ref_iterator_advance,\n \t.seek = prefix_ref_iterator_seek,\n-\t.peel = prefix_ref_iterator_peel,\n \t.release = prefix_ref_iterator_release,\n };\n \ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 1fefefd54ed..6fa229edd0f 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -1030,22 +1030,6 @@ static int packed_ref_iterator_seek(struct ref_iterator *ref_iterator,\n \treturn 0;\n }\n \n-static int packed_ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t\t\t   struct object_id *peeled)\n-{\n-\tstruct packed_ref_iterator *iter =\n-\t\t(struct packed_ref_iterator *)ref_iterator;\n-\n-\tif ((iter->base.ref.flags & REF_KNOWS_PEELED)) {\n-\t\toidcpy(peeled, &iter->peeled);\n-\t\treturn is_null_oid(&iter->peeled) ? -1 : 0;\n-\t} else if ((iter->base.ref.flags & (REF_ISBROKEN | REF_ISSYMREF))) {\n-\t\treturn -1;\n-\t} else {\n-\t\treturn peel_object(iter->repo, &iter->oid, peeled) ? -1 : 0;\n-\t}\n-}\n-\n static void packed_ref_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct packed_ref_iterator *iter =\n@@ -1059,7 +1043,6 @@ static void packed_ref_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable packed_ref_iterator_vtable = {\n \t.advance = packed_ref_iterator_advance,\n \t.seek = packed_ref_iterator_seek,\n-\t.peel = packed_ref_iterator_peel,\n \t.release = packed_ref_iterator_release,\n };\n \n@@ -1525,13 +1508,8 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re\n \n \t\tif (cmp < 0) {\n \t\t\t/* Pass the old reference through. */\n-\n-\t\t\tstruct object_id peeled;\n-\t\t\tint peel_error = ref_iterator_peel(iter, &peeled);\n-\n \t\t\tif (write_packed_entry(out, iter->ref.name,\n-\t\t\t\t\t       iter->ref.oid,\n-\t\t\t\t\t       peel_error ? NULL : &peeled))\n+\t\t\t\t\t       iter->ref.oid, iter->ref.peeled_oid))\n \t\t\t\tgoto write_error;\n \n \t\t\tif ((ok = ref_iterator_advance(iter)) != ITER_OK) {\ndiff --git a/refs/ref-cache.c b/refs/ref-cache.c\nindex e427848879d..ffef01a5975 100644\n--- a/refs/ref-cache.c\n+++ b/refs/ref-cache.c\n@@ -546,14 +546,6 @@ static int cache_ref_iterator_seek(struct ref_iterator *ref_iterator,\n \treturn 0;\n }\n \n-static int cache_ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t\t\t   struct object_id *peeled)\n-{\n-\tstruct cache_ref_iterator *iter =\n-\t\t(struct cache_ref_iterator *)ref_iterator;\n-\treturn peel_object(iter->repo, ref_iterator->ref.oid, peeled) ? -1 : 0;\n-}\n-\n static void cache_ref_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct cache_ref_iterator *iter =\n@@ -565,7 +557,6 @@ static void cache_ref_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable cache_ref_iterator_vtable = {\n \t.advance = cache_ref_iterator_advance,\n \t.seek = cache_ref_iterator_seek,\n-\t.peel = cache_ref_iterator_peel,\n \t.release = cache_ref_iterator_release,\n };\n \ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex f4f845bbeaf..4671517dade 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -357,12 +357,6 @@ typedef int ref_iterator_advance_fn(struct ref_iterator *ref_iterator);\n typedef int ref_iterator_seek_fn(struct ref_iterator *ref_iterator,\n \t\t\t\t const char *refname, unsigned int flags);\n \n-/*\n- * Peels the current ref, returning 0 for success or -1 for failure.\n- */\n-typedef int ref_iterator_peel_fn(struct ref_iterator *ref_iterator,\n-\t\t\t\t struct object_id *peeled);\n-\n /*\n  * Implementations of this function should free any resources specific\n  * to the derived class.\n@@ -372,7 +366,6 @@ typedef void ref_iterator_release_fn(struct ref_iterator *ref_iterator);\n struct ref_iterator_vtable {\n \tref_iterator_advance_fn *advance;\n \tref_iterator_seek_fn *seek;\n-\tref_iterator_peel_fn *peel;\n \tref_iterator_release_fn *release;\n };\n \ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex e214e120d77..e329d4a423a 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -744,21 +744,6 @@ static int reftable_ref_iterator_seek(struct ref_iterator *ref_iterator,\n \treturn iter->err;\n }\n \n-static int reftable_ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t\t\t      struct object_id *peeled)\n-{\n-\tstruct reftable_ref_iterator *iter =\n-\t\t(struct reftable_ref_iterator *)ref_iterator;\n-\n-\tif (iter->ref.value_type == REFTABLE_REF_VAL2) {\n-\t\toidread(peeled, iter->ref.value.val2.target_value,\n-\t\t\titer->refs->base.repo->hash_algo);\n-\t\treturn 0;\n-\t}\n-\n-\treturn -1;\n-}\n-\n static void reftable_ref_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct reftable_ref_iterator *iter =\n@@ -776,7 +761,6 @@ static void reftable_ref_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable reftable_ref_iterator_vtable = {\n \t.advance = reftable_ref_iterator_advance,\n \t.seek = reftable_ref_iterator_seek,\n-\t.peel = reftable_ref_iterator_peel,\n \t.release = reftable_ref_iterator_release,\n };\n \n@@ -2098,13 +2082,6 @@ static int reftable_reflog_iterator_seek(struct ref_iterator *ref_iterator UNUSE\n \treturn -1;\n }\n \n-static int reftable_reflog_iterator_peel(struct ref_iterator *ref_iterator UNUSED,\n-\t\t\t\t\t struct object_id *peeled UNUSED)\n-{\n-\tBUG(\"reftable reflog iterator cannot be peeled\");\n-\treturn -1;\n-}\n-\n static void reftable_reflog_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct reftable_reflog_iterator *iter =\n@@ -2117,7 +2094,6 @@ static void reftable_reflog_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable reftable_reflog_iterator_vtable = {\n \t.advance = reftable_reflog_iterator_advance,\n \t.seek = reftable_reflog_iterator_seek,\n-\t.peel = reftable_reflog_iterator_peel,\n \t.release = reftable_reflog_iterator_release,\n };\n \n\n-- \n2.51.1.851.g4ebd6896fd.dirty\n\n"},{"id":"529393","messageId":"20251022-b4-pks-ref-filter-skip-parsing-objects-v3-11-eb9f71985ef0@pks.im","threadId":"64260","inReplyTo":"20251022-b4-pks-ref-filter-skip-parsing-objects-v3-0-eb9f71985ef0@pks.im","subject":"[PATCH v3 11/14] object: add flag to `peel_object()` to verify object type","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-22T06:41:11Z","receivedAt":"2025-10-22T06:41:40Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When peeling a tag to a non-tag object we repeatedly call\n`parse_object()` on the tagged object until we find the first object\nthat isn't a tag. While this feels sensible at first, there is a big\ncatch here: `parse_object()` doesn't actually verify the type of the\ntagged object.\n\nThe relevant code path here eventually ends up in `parse_tag_buffer()`.\nHere, we parse the various fields of the tag, including the \"type\". Once\nwe've figured out the type and the tagged object ID, we call one of the\n`lookup_${type}()` functions for whatever type we have found. There is\ntwo possible outcomes in the successful case:\n\n  1. The object is already part of our cached objects. In that case we\n     double-check whether the type we're trying to look up matches the\n     type that was cached.\n\n  2. The object is _not_ part of our cached objects. In that case, we\n     simply create a new object with the expected type, but we don't\n     parse that object.\n\nIn the first case we might notice type mismatches, but only in the case\nwhere our cache has the object with the correct type. In the second\ncase, we'll blindly assume that the type is correct and then go with it.\nWe'll only notice that the type might be wrong when we try to parse the\nobject at a later point.\n\nNow arguably, we could change `parse_tag_buffer()` to verify the tagged\nobject's type for us. But that would have the effect that such a tag\ncannot be parsed at all anymore, and we have a small bunch of tests for\nexactly this case that assert we still can open such tags. So this\nchange does not feel like something we can retroactively tighten, even\nthough one shouldn't ever hit such corrupted tags.\n\nInstead, add a new `flags` field to `peel_object()` that allows the\ncaller to opt in to strict object verification. This will be wired up at\na subset of callsites over the next few commits.\n\nNote that this change also inlines `deref_tag_noverify()`. There's only\nbeen two callsites of that function, the one we're changing and one in\nour test helpers. The latter callsite can trivially use `deref_tag()`\ninstead, so by inlining the function we avoid having to pass down the\nflag.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object.c                | 20 +++++++++++++++++---\n object.h                | 15 ++++++++++++++-\n ref-filter.c            |  2 +-\n refs.c                  |  2 +-\n refs/packed-backend.c   |  5 ++---\n refs/reftable-backend.c |  4 ++--\n t/helper/test-reach.c   |  2 +-\n tag.c                   | 12 ------------\n tag.h                   |  1 -\n 9 files changed, 38 insertions(+), 25 deletions(-)\n\ndiff --git a/object.c b/object.c\nindex 986114a6dba..e72b0ed4360 100644\n--- a/object.c\n+++ b/object.c\n@@ -209,11 +209,12 @@ struct object *lookup_object_by_type(struct repository *r,\n \n enum peel_status peel_object(struct repository *r,\n \t\t\t     const struct object_id *name,\n-\t\t\t     struct object_id *oid)\n+\t\t\t     struct object_id *oid,\n+\t\t\t     unsigned flags)\n {\n \tstruct object *o = lookup_unknown_object(r, name);\n \n-\tif (o->type == OBJ_NONE) {\n+\tif (o->type == OBJ_NONE || flags & PEEL_OBJECT_VERIFY_OBJECT_TYPE) {\n \t\tint type = odb_read_object_info(r->objects, name, NULL);\n \t\tif (type < 0 || !object_as_type(o, type, 0))\n \t\t\treturn PEEL_INVALID;\n@@ -222,7 +223,20 @@ enum peel_status peel_object(struct repository *r,\n \tif (o->type != OBJ_TAG)\n \t\treturn PEEL_NON_TAG;\n \n-\to = deref_tag_noverify(r, o);\n+\twhile (o && o->type == OBJ_TAG) {\n+\t\to = parse_object(r, &o->oid);\n+\t\tif (o && o->type == OBJ_TAG && ((struct tag *)o)->tagged) {\n+\t\t\to = ((struct tag *)o)->tagged;\n+\n+\t\t\tif (flags & PEEL_OBJECT_VERIFY_OBJECT_TYPE) {\n+\t\t\t\tint type = odb_read_object_info(r->objects, &o->oid, NULL);\n+\t\t\t\tif (type < 0 || !object_as_type(o, type, 0))\n+\t\t\t\t\treturn PEEL_INVALID;\n+\t\t\t}\n+\t\t} else {\n+\t\t\to = NULL;\n+\t\t}\n+\t}\n \tif (!o)\n \t\treturn PEEL_INVALID;\n \ndiff --git a/object.h b/object.h\nindex 8c3c1c46e1b..1499f63d507 100644\n--- a/object.h\n+++ b/object.h\n@@ -287,6 +287,17 @@ enum peel_status {\n \tPEEL_BROKEN = -4\n };\n \n+enum peel_object_flags {\n+\t/*\n+\t * Always verify the object type, even in the case where the looked-up\n+\t * object already has an object type. This can be useful when the\n+\t * stored object type may be invalid. One such case is when looking up\n+\t * objects via tags, where we blindly trust the object type declared by\n+\t * the tag.\n+\t */\n+\tPEEL_OBJECT_VERIFY_OBJECT_TYPE = (1 << 0),\n+};\n+\n /*\n  * Peel the named object; i.e., if the object is a tag, resolve the\n  * tag recursively until a non-tag is found.  If successful, store the\n@@ -295,7 +306,9 @@ enum peel_status {\n  * and leave oid unchanged.\n  */\n enum peel_status peel_object(struct repository *r,\n-\t\t\t     const struct object_id *name, struct object_id *oid);\n+\t\t\t     const struct object_id *name,\n+\t\t\t     struct object_id *oid,\n+\t\t\t     unsigned flags);\n \n struct object_list *object_list_insert(struct object *item,\n \t\t\t\t       struct object_list **list_p);\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 7fd8babec8f..9a8ed8c8fc1 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -2581,7 +2581,7 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)\n \tif (need_tagged) {\n \t\tif (!is_null_oid(&ref->peeled_oid)) {\n \t\t\toidcpy(&oi_deref.oid, &ref->peeled_oid);\n-\t\t} else if (!peel_object(the_repository, &obj->oid, &oi_deref.oid)) {\n+\t\t} else if (!peel_object(the_repository, &oi.oid, &oi_deref.oid, 0)) {\n \t\t\t/* We managed to peel the object ourselves. */\n \t\t} else {\n \t\t\tdie(\"bad tag\");\ndiff --git a/refs.c b/refs.c\nindex 9d8f0a9ca4a..a41a94ae55b 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2333,7 +2333,7 @@ int reference_get_peeled_oid(struct repository *repo,\n \t\treturn 0;\n \t}\n \n-\treturn peel_object(repo, ref->oid, peeled_oid) ? -1 : 0;\n+\treturn peel_object(repo, ref->oid, peeled_oid, 0) ? -1 : 0;\n }\n \n int refs_update_symref(struct ref_store *refs, const char *ref,\ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 6fa229edd0f..4752d3f3981 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -1527,9 +1527,8 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re\n \t\t\ti++;\n \t\t} else {\n \t\t\tstruct object_id peeled;\n-\t\t\tint peel_error = peel_object(refs->base.repo,\n-\t\t\t\t\t\t     &update->new_oid,\n-\t\t\t\t\t\t     &peeled);\n+\t\t\tint peel_error = peel_object(refs->base.repo, &update->new_oid,\n+\t\t\t\t\t\t     &peeled, 0);\n \n \t\t\tif (write_packed_entry(out, update->refname,\n \t\t\t\t\t       &update->new_oid,\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex e329d4a423a..9febb2322c3 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1632,7 +1632,7 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n \t\t\tref.refname = (char *)u->refname;\n \t\t\tref.update_index = ts;\n \n-\t\t\tpeel_error = peel_object(arg->refs->base.repo, &u->new_oid, &peeled);\n+\t\t\tpeel_error = peel_object(arg->refs->base.repo, &u->new_oid, &peeled, 0);\n \t\t\tif (!peel_error) {\n \t\t\t\tref.value_type = REFTABLE_REF_VAL2;\n \t\t\t\tmemcpy(ref.value.val2.target_value, peeled.hash, GIT_MAX_RAWSZ);\n@@ -2497,7 +2497,7 @@ static int write_reflog_expiry_table(struct reftable_writer *writer, void *cb_da\n \t\tref.refname = (char *)arg->refname;\n \t\tref.update_index = ts;\n \n-\t\tif (!peel_object(arg->refs->base.repo, &arg->update_oid, &peeled)) {\n+\t\tif (!peel_object(arg->refs->base.repo, &arg->update_oid, &peeled, 0)) {\n \t\t\tref.value_type = REFTABLE_REF_VAL2;\n \t\t\tmemcpy(ref.value.val2.target_value, peeled.hash, GIT_MAX_RAWSZ);\n \t\t\tmemcpy(ref.value.val2.value, arg->update_oid.hash, GIT_MAX_RAWSZ);\ndiff --git a/t/helper/test-reach.c b/t/helper/test-reach.c\nindex 028ec003067..c58c93800f3 100644\n--- a/t/helper/test-reach.c\n+++ b/t/helper/test-reach.c\n@@ -63,7 +63,7 @@ int cmd__reach(int ac, const char **av)\n \t\t\tdie(\"failed to resolve %s\", buf.buf + 2);\n \n \t\torig = parse_object(r, &oid);\n-\t\tpeeled = deref_tag_noverify(the_repository, orig);\n+\t\tpeeled = deref_tag(the_repository, orig, NULL, 0);\n \n \t\tif (!peeled)\n \t\t\tdie(\"failed to load commit for input %s resulting in oid %s\",\ndiff --git a/tag.c b/tag.c\nindex 1d52686ee10..f5c232d2f1f 100644\n--- a/tag.c\n+++ b/tag.c\n@@ -94,18 +94,6 @@ struct object *deref_tag(struct repository *r, struct object *o, const char *war\n \treturn o;\n }\n \n-struct object *deref_tag_noverify(struct repository *r, struct object *o)\n-{\n-\twhile (o && o->type == OBJ_TAG) {\n-\t\to = parse_object(r, &o->oid);\n-\t\tif (o && o->type == OBJ_TAG && ((struct tag *)o)->tagged)\n-\t\t\to = ((struct tag *)o)->tagged;\n-\t\telse\n-\t\t\to = NULL;\n-\t}\n-\treturn o;\n-}\n-\n struct tag *lookup_tag(struct repository *r, const struct object_id *oid)\n {\n \tstruct object *obj = lookup_object(r, oid);\ndiff --git a/tag.h b/tag.h\nindex c49d7c19ad3..ef12a610372 100644\n--- a/tag.h\n+++ b/tag.h\n@@ -16,7 +16,6 @@ int parse_tag_buffer(struct repository *r, struct tag *item, const void *data, u\n int parse_tag(struct tag *item);\n void release_tag_memory(struct tag *t);\n struct object *deref_tag(struct repository *r, struct object *, const char *, int);\n-struct object *deref_tag_noverify(struct repository *r, struct object *);\n int gpg_verify_tag(const struct object_id *oid,\n \t\t   const char *name_to_report, unsigned flags);\n struct object_id *get_tagged_oid(struct tag *tag);\n\n-- \n2.51.1.851.g4ebd6896fd.dirty\n\n"},{"id":"529394","messageId":"20251022-b4-pks-ref-filter-skip-parsing-objects-v3-12-eb9f71985ef0@pks.im","threadId":"64260","inReplyTo":"20251022-b4-pks-ref-filter-skip-parsing-objects-v3-0-eb9f71985ef0@pks.im","subject":"[PATCH v3 12/14] refs: don't store peeled object IDs for invalid tags","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-22T06:41:12Z","receivedAt":"2025-10-22T06:41:44Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Both the \"files\" and \"reftable\" backend store peeled object IDs for\nreferences that point to tags:\n\n  - The \"files\" backend stores the value when packing refs, where each\n    peeled object ID is prefixed with \"^\".\n\n  - The \"reftable\" backend stores the value whenever writing a new\n    reference that points to a tag via a special ref record type.\n\nBoth of these backends use `peel_object()` to find the peeled object ID.\nBut as explained in the preceding commit, that function does not detect\nthe case where the tag's tagged object and its claimed type mismatch.\n\nThe consequence of storing these bogus peeled object IDs is that we're\nless likely to detect such corruption in other parts of Git.\ngit-for-each-ref(1) for example does not notice anymore that the tag is\nbroken when using \"--format=%(*objectname)\" to dereference tags.\n\nOne could claim that this is good, because it still allows us to mostly\nuse the tag as intended. But the biggest problem here is that we now\nhave different behaviour for such a broken tag depending on whether or\nnot we have its peeled value in the refdb.\n\nFix the issue by verifying the object type when peeling the object. If\nthat verification fails we simply skip storing the peeled value in\neither of the reference formats.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs/packed-backend.c      |  2 +-\n refs/reftable-backend.c    |  3 ++-\n t/pack-refs-tests.sh       | 32 ++++++++++++++++++++++++++++++++\n t/t0610-reftable-basics.sh | 28 ++++++++++++++++++++++++++++\n 4 files changed, 63 insertions(+), 2 deletions(-)\n\ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 4752d3f3981..1ab0c503930 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -1528,7 +1528,7 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re\n \t\t} else {\n \t\t\tstruct object_id peeled;\n \t\t\tint peel_error = peel_object(refs->base.repo, &update->new_oid,\n-\t\t\t\t\t\t     &peeled, 0);\n+\t\t\t\t\t\t     &peeled, PEEL_OBJECT_VERIFY_OBJECT_TYPE);\n \n \t\t\tif (write_packed_entry(out, update->refname,\n \t\t\t\t\t       &update->new_oid,\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 9febb2322c3..6bbfd5618da 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1632,7 +1632,8 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n \t\t\tref.refname = (char *)u->refname;\n \t\t\tref.update_index = ts;\n \n-\t\t\tpeel_error = peel_object(arg->refs->base.repo, &u->new_oid, &peeled, 0);\n+\t\t\tpeel_error = peel_object(arg->refs->base.repo, &u->new_oid, &peeled,\n+\t\t\t\t\t\t PEEL_OBJECT_VERIFY_OBJECT_TYPE);\n \t\t\tif (!peel_error) {\n \t\t\t\tref.value_type = REFTABLE_REF_VAL2;\n \t\t\t\tmemcpy(ref.value.val2.target_value, peeled.hash, GIT_MAX_RAWSZ);\ndiff --git a/t/pack-refs-tests.sh b/t/pack-refs-tests.sh\nindex 3dbcc01718e..095823d915f 100644\n--- a/t/pack-refs-tests.sh\n+++ b/t/pack-refs-tests.sh\n@@ -428,4 +428,36 @@ do\n \t'\n done\n \n+test_expect_success 'pack-refs does not store invalid peeled tag value' '\n+\ttest_when_finished rm -rf repo &&\n+\tgit init repo &&\n+\t(\n+\t\tcd repo &&\n+\t\tgit commit --allow-empty --message initial &&\n+\n+\t\techo garbage >blob-content &&\n+\t\tblob_id=$(git hash-object -w -t blob blob-content) &&\n+\n+\t\t# Write an invalid tag into the object database. The tag itself\n+\t\t# is well-formed, but the tagged object is a blob while we\n+\t\t# claim that it is a commit.\n+\t\tcat >tag-content <<-EOF &&\n+\t\tobject $blob_id\n+\t\ttype commit\n+\t\ttag bad-tag\n+\t\ttagger C O Mitter <committer@example.com> 1112354055 +0200\n+\n+\t\tannotated\n+\t\tEOF\n+\t\ttag_id=$(git hash-object -w -t tag tag-content) &&\n+\t\tgit update-ref refs/tags/bad-tag \"$tag_id\" &&\n+\n+\t\t# The packed-refs file should not contain the peeled object ID.\n+\t\t# If it did this would cause commands that use the peeled value\n+\t\t# to not notice this corrupted tag.\n+\t\tgit pack-refs --all &&\n+\t\ttest_grep ! \"^\\^\" .git/packed-refs\n+\t)\n+'\n+\n test_done\ndiff --git a/t/t0610-reftable-basics.sh b/t/t0610-reftable-basics.sh\nindex 3ea5d51532a..6575528f212 100755\n--- a/t/t0610-reftable-basics.sh\n+++ b/t/t0610-reftable-basics.sh\n@@ -1135,4 +1135,32 @@ test_expect_success 'fetch: accessing FETCH_HEAD special ref works' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'writes do not persist peeled value for invalid tags' '\n+\ttest_when_finished rm -rf repo &&\n+\tgit init repo &&\n+\t(\n+\t\tcd repo &&\n+\t\tgit commit --allow-empty --message initial &&\n+\n+\t\t# We cannot easily verify that the peeled value is not stored\n+\t\t# in the tables. Instead, we test this indirectly: we create\n+\t\t# two tags that both point to the same object, but they claim\n+\t\t# different object types. If we parse both tags we notice that\n+\t\t# the parsed tagged object has a mismatch between the two tags\n+\t\t# and bail out.\n+\t\t#\n+\t\t# If we instead use the persisted peeled value we would not\n+\t\t# even parse the tags. As such, we would not notice the\n+\t\t# discrepancy either and thus listing these tags would succeed.\n+\t\tgit tag tag-1 -m \"tag 1\" &&\n+\t\tgit cat-file tag tag-1 >raw-tag &&\n+\t\tsed \"s/^type commit$/type blob/\" <raw-tag >broken-tag &&\n+\t\tbroken_tag_id=$(git hash-object -w -t tag broken-tag) &&\n+\t\tgit update-ref refs/tags/tag-2 $broken_tag_id &&\n+\n+\t\ttest_must_fail git for-each-ref --format=\"%(*objectname)\" refs/tags/ 2>err &&\n+\t\ttest_grep \"bad tag pointer\" err\n+\t)\n+'\n+\n test_done\n\n-- \n2.51.1.851.g4ebd6896fd.dirty\n\n"},{"id":"529395","messageId":"20251022-b4-pks-ref-filter-skip-parsing-objects-v3-13-eb9f71985ef0@pks.im","threadId":"64260","inReplyTo":"20251022-b4-pks-ref-filter-skip-parsing-objects-v3-0-eb9f71985ef0@pks.im","subject":"[PATCH v3 13/14] ref-filter: detect broken tags when dereferencing them","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-22T06:41:13Z","receivedAt":"2025-10-22T06:41:47Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Users can ask git-for-each-ref(1) to peel tags and return information of\nthe tagged object by adding an asterisk to the format, like for example\n\"%(*$objectname)\". If so, git-for-each-ref(1) peels that object to the\nfirst non-tag object and then returns its values.\n\nAs mentioned in preceding commits, it can happen that the tagged object\ntype and the claimed object type differ, effectively resulting in a\ncorrupt tag. git-for-each-ref(1) would notice this mismatch, print an\nerror and then bail out when trying to peel the tag.\n\nBut we only notice this corruption in some very specific edge cases!\nWhile we have a test in \"t/for-each-ref-tests.sh\" that verifies the\nabove scenario, this test is specifically crafted to detect the issue at\nhand. Namely, we create two tags:\n\n  - One tag points to a specific object with the correct type.\n\n  - The other tag points to the *same* object with a different type.\n\nThe fact that both tags point to the same object is important here:\n`peel_object()` wouldn't notice the corruption if the tagged objects\nwere different.\n\nThe root cause is that `peel_object()` calls `lookup_${type}()`\neventually, where the type is the same type declared in the tag object.\nConsequently, when we have two tags pointing to the same object but with\ndifferent declared types we'll call two different lookup functions. The\nfirst lookup will store the object with an unverified type A, whereas\nthe second lookup will try to look up the object with a different\nunverified type B. And it is only now that we notice the discrepancy in\nobject types, even though type A could've already been the wrong type.\n\nFix the issue by verifying the object type in `populate_value()`. With\nthis change we'll also notice type mismatches when only dereferencing a\ntag once.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n ref-filter.c            | 3 ++-\n t/for-each-ref-tests.sh | 4 +++-\n 2 files changed, 5 insertions(+), 2 deletions(-)\n\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 9a8ed8c8fc1..c54025d6b4c 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -2581,7 +2581,8 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)\n \tif (need_tagged) {\n \t\tif (!is_null_oid(&ref->peeled_oid)) {\n \t\t\toidcpy(&oi_deref.oid, &ref->peeled_oid);\n-\t\t} else if (!peel_object(the_repository, &oi.oid, &oi_deref.oid, 0)) {\n+\t\t} else if (!peel_object(the_repository, &oi.oid, &oi_deref.oid,\n+\t\t\t\t\tPEEL_OBJECT_VERIFY_OBJECT_TYPE)) {\n \t\t\t/* We managed to peel the object ourselves. */\n \t\t} else {\n \t\t\tdie(\"bad tag\");\ndiff --git a/t/for-each-ref-tests.sh b/t/for-each-ref-tests.sh\nindex e3ad19298ac..4593be5fd54 100644\n--- a/t/for-each-ref-tests.sh\n+++ b/t/for-each-ref-tests.sh\n@@ -1809,7 +1809,9 @@ test_expect_success \"${git_for_each_ref} reports broken tags\" '\n \tbad=$(git hash-object -w -t tag bad) &&\n \tgit update-ref refs/tags/broken-tag-bad $bad &&\n \ttest_must_fail ${git_for_each_ref} --format=\"%(*objectname)\" \\\n-\t\trefs/tags/broken-tag-*\n+\t\trefs/tags/broken-tag-* &&\n+\ttest_must_fail ${git_for_each_ref} --format=\"%(*objectname)\" \\\n+\t\trefs/tags/broken-tag-bad\n '\n \n test_expect_success 'set up tag with signature and no blank lines' '\n\n-- \n2.51.1.851.g4ebd6896fd.dirty\n\n"},{"id":"529396","messageId":"20251022-b4-pks-ref-filter-skip-parsing-objects-v3-14-eb9f71985ef0@pks.im","threadId":"64260","inReplyTo":"20251022-b4-pks-ref-filter-skip-parsing-objects-v3-0-eb9f71985ef0@pks.im","subject":"[PATCH v3 14/14] ref-filter: parse objects on demand","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-22T06:41:14Z","receivedAt":"2025-10-22T06:41:50Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When formatting an arbitrary object we parse that object regardless of\nwhether or not we actually need any parsed data. In fact, many of the\natoms we have don't require any.\n\nRefactor the code so that we parse the data on demand when we see an\natom that wants to access the objects. This leads to a small speedup,\nfor example in the Chromium repository with around 40000 refs:\n\n    Benchmark 1: for-each-ref --format='%(raw)' (HEAD~)\n      Time (mean ± σ):     388.7 ms ±   1.1 ms    [User: 322.2 ms, System: 65.0 ms]\n      Range (min … max):   387.3 ms … 390.8 ms    10 runs\n\n    Benchmark 2: for-each-ref --format='%(raw)' (HEAD)\n      Time (mean ± σ):     344.7 ms ±   0.7 ms    [User: 287.8 ms, System: 55.1 ms]\n      Range (min … max):   343.9 ms … 345.7 ms    10 runs\n\n    Summary\n      for-each-ref --format='%(raw)' (HEAD) ran\n        1.13 ± 0.00 times faster than for-each-ref --format='%(raw)' (HEAD~)\n\nWith this change, we now spend ~90% of the time decompressing objects,\nwhich is almost as good as it gets regarding git-for-each-ref(1)'s own\ninfrastructure.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n ref-filter.c | 156 ++++++++++++++++++++++++++++++++++++++++++++---------------\n 1 file changed, 117 insertions(+), 39 deletions(-)\n\ndiff --git a/ref-filter.c b/ref-filter.c\nindex c54025d6b4..ca58870aed 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -91,6 +91,7 @@ static struct expand_data {\n \tstruct object_id delta_base_oid;\n \tvoid *content;\n \n+\tstruct object *maybe_object;\n \tstruct object_info info;\n } oi, oi_deref;\n \n@@ -1475,11 +1476,28 @@ static void grab_common_values(struct atom_value *val, int deref, struct expand_\n \t}\n }\n \n+static int get_or_parse_object(struct expand_data *data, const char *refname,\n+\t\t\t       struct object **object, struct strbuf *err, int *eaten)\n+{\n+\tif (!data->maybe_object) {\n+\t\tdata->maybe_object = parse_object_buffer(the_repository, &data->oid, data->type,\n+\t\t\t\t\t\t\t data->size, data->content, eaten);\n+\t\tif (!data->maybe_object)\n+\t\t\treturn strbuf_addf_ret(err, -1, _(\"parse_object_buffer failed on %s for %s\"),\n+\t\t\t\t\t       oid_to_hex(&data->oid), refname);\n+\t}\n+\n+\t*object = data->maybe_object;\n+\treturn 0;\n+}\n+\n /* See grab_values */\n-static void grab_tag_values(struct atom_value *val, int deref, struct object *obj)\n+static int grab_tag_values(struct atom_value *val, int deref,\n+\t\t\t   struct expand_data *data, const char *refname,\n+\t\t\t   struct strbuf *err, int *eaten)\n {\n-\tint i;\n-\tstruct tag *tag = (struct tag *) obj;\n+\tstruct tag *tag = NULL;\n+\tint i, ret;\n \n \tfor (i = 0; i < used_atom_cnt; i++) {\n \t\tconst char *name = used_atom[i].name;\n@@ -1487,6 +1505,17 @@ static void grab_tag_values(struct atom_value *val, int deref, struct object *ob\n \t\tstruct atom_value *v = &val[i];\n \t\tif (!!deref != (*name == '*'))\n \t\t\tcontinue;\n+\n+\t\tif (!tag) {\n+\t\t\tstruct object *object;\n+\n+\t\t\tret = get_or_parse_object(data, refname, &object, err, eaten);\n+\t\t\tif (ret < 0)\n+\t\t\t\treturn ret;\n+\n+\t\t\ttag = (struct tag *) object;\n+\t\t}\n+\n \t\tif (deref)\n \t\t\tname++;\n \t\tif (atom_type == ATOM_TAG)\n@@ -1496,22 +1525,38 @@ static void grab_tag_values(struct atom_value *val, int deref, struct object *ob\n \t\telse if (atom_type == ATOM_OBJECT && tag->tagged)\n \t\t\tv->s = xstrdup(oid_to_hex(&tag->tagged->oid));\n \t}\n+\n+\treturn 0;\n }\n \n /* See grab_values */\n-static void grab_commit_values(struct atom_value *val, int deref, struct object *obj)\n+static int grab_commit_values(struct atom_value *val, int deref,\n+\t\t\t      struct expand_data *data, const char *refname,\n+\t\t\t      struct strbuf *err, int *eaten)\n {\n-\tint i;\n-\tstruct commit *commit = (struct commit *) obj;\n+\tint i, ret;\n+\tstruct commit *commit = NULL;\n \n \tfor (i = 0; i < used_atom_cnt; i++) {\n \t\tconst char *name = used_atom[i].name;\n \t\tenum atom_type atom_type = used_atom[i].atom_type;\n \t\tstruct atom_value *v = &val[i];\n+\n \t\tif (!!deref != (*name == '*'))\n \t\t\tcontinue;\n \t\tif (deref)\n \t\t\tname++;\n+\n+\t\tif (!commit) {\n+\t\t\tstruct object *object;\n+\n+\t\t\tret = get_or_parse_object(data, refname, &object, err, eaten);\n+\t\t\tif (ret < 0)\n+\t\t\t\treturn ret;\n+\n+\t\t\tcommit = (struct commit *) object;\n+\t\t}\n+\n \t\tif (atom_type == ATOM_TREE &&\n \t\t    grab_oid(name, \"tree\", get_commit_tree_oid(commit), v, &used_atom[i]))\n \t\t\tcontinue;\n@@ -1531,6 +1576,8 @@ static void grab_commit_values(struct atom_value *val, int deref, struct object\n \t\t\tv->s = strbuf_detach(&s, NULL);\n \t\t}\n \t}\n+\n+\treturn 0;\n }\n \n static const char *find_wholine(const char *who, int wholen, const char *buf)\n@@ -1759,10 +1806,12 @@ static void grab_person(const char *who, struct atom_value *val, int deref, void\n \t}\n }\n \n-static void grab_signature(struct atom_value *val, int deref, struct object *obj)\n+static int grab_signature(struct atom_value *val, int deref,\n+\t\t\t  struct expand_data *data, const char *refname,\n+\t\t\t  struct strbuf *err, int *eaten)\n {\n-\tint i;\n-\tstruct commit *commit = (struct commit *) obj;\n+\tint i, ret;\n+\tstruct commit *commit = NULL;\n \tstruct signature_check sigc = { 0 };\n \tint signature_checked = 0;\n \n@@ -1790,6 +1839,16 @@ static void grab_signature(struct atom_value *val, int deref, struct object *obj\n \t\t\tcontinue;\n \n \t\tif (!signature_checked) {\n+\t\t\tif (!commit) {\n+\t\t\t\tstruct object *object;\n+\n+\t\t\t\tret = get_or_parse_object(data, refname, &object, err, eaten);\n+\t\t\t\tif (ret < 0)\n+\t\t\t\t\treturn ret;\n+\n+\t\t\t\tcommit = (struct commit *) object;\n+\t\t\t}\n+\n \t\t\tcheck_commit_signature(commit, &sigc);\n \t\t\tsignature_checked = 1;\n \t\t}\n@@ -1843,6 +1902,8 @@ static void grab_signature(struct atom_value *val, int deref, struct object *obj\n \n \tif (signature_checked)\n \t\tsignature_check_clear(&sigc);\n+\n+\treturn 0;\n }\n \n static void find_subpos(const char *buf,\n@@ -1920,9 +1981,8 @@ static void append_lines(struct strbuf *out, const char *buf, unsigned long size\n }\n \n static void grab_describe_values(struct atom_value *val, int deref,\n-\t\t\t\t struct object *obj)\n+\t\t\t\t struct expand_data *data)\n {\n-\tstruct commit *commit = (struct commit *)obj;\n \tint i;\n \n \tfor (i = 0; i < used_atom_cnt; i++) {\n@@ -1944,7 +2004,7 @@ static void grab_describe_values(struct atom_value *val, int deref,\n \t\tcmd.git_cmd = 1;\n \t\tstrvec_push(&cmd.args, \"describe\");\n \t\tstrvec_pushv(&cmd.args, atom->u.describe_args.v);\n-\t\tstrvec_push(&cmd.args, oid_to_hex(&commit->object.oid));\n+\t\tstrvec_push(&cmd.args, oid_to_hex(&data->oid));\n \t\tif (pipe_command(&cmd, NULL, 0, &out, 0, &err, 0) < 0) {\n \t\t\terror(_(\"failed to run 'describe'\"));\n \t\t\tv->s = xstrdup(\"\");\n@@ -2066,24 +2126,36 @@ static void fill_missing_values(struct atom_value *val)\n  * pointed at by the ref itself; otherwise it is the object the\n  * ref (which is a tag) refers to.\n  */\n-static void grab_values(struct atom_value *val, int deref, struct object *obj, struct expand_data *data)\n+static int grab_values(struct atom_value *val, int deref, struct expand_data *data,\n+\t\t       const char *refname, struct strbuf *err, int *eaten)\n {\n \tvoid *buf = data->content;\n+\tint ret;\n \n-\tswitch (obj->type) {\n+\tswitch (data->type) {\n \tcase OBJ_TAG:\n-\t\tgrab_tag_values(val, deref, obj);\n+\t\tret = grab_tag_values(val, deref, data, refname, err, eaten);\n+\t\tif (ret < 0)\n+\t\t\tgoto out;\n+\n \t\tgrab_sub_body_contents(val, deref, data);\n \t\tgrab_person(\"tagger\", val, deref, buf);\n-\t\tgrab_describe_values(val, deref, obj);\n+\t\tgrab_describe_values(val, deref, data);\n \t\tbreak;\n \tcase OBJ_COMMIT:\n-\t\tgrab_commit_values(val, deref, obj);\n+\t\tret = grab_commit_values(val, deref, data, refname, err, eaten);\n+\t\tif (ret < 0)\n+\t\t\tgoto out;\n+\n \t\tgrab_sub_body_contents(val, deref, data);\n \t\tgrab_person(\"author\", val, deref, buf);\n \t\tgrab_person(\"committer\", val, deref, buf);\n-\t\tgrab_signature(val, deref, obj);\n-\t\tgrab_describe_values(val, deref, obj);\n+\n+\t\tret = grab_signature(val, deref, data, refname, err, eaten);\n+\t\tif (ret < 0)\n+\t\t\tgoto out;\n+\n+\t\tgrab_describe_values(val, deref, data);\n \t\tbreak;\n \tcase OBJ_TREE:\n \t\t/* grab_tree_values(val, deref, obj, buf, sz); */\n@@ -2094,8 +2166,12 @@ static void grab_values(struct atom_value *val, int deref, struct object *obj, s\n \t\tgrab_sub_body_contents(val, deref, data);\n \t\tbreak;\n \tdefault:\n-\t\tdie(\"Eh?  Object of type %d?\", obj->type);\n+\t\tdie(\"Eh?  Object of type %d?\", data->type);\n \t}\n+\n+\tret = 0;\n+out:\n+\treturn ret;\n }\n \n static inline char *copy_advance(char *dst, const char *src)\n@@ -2292,38 +2368,41 @@ static const char *get_refname(struct used_atom *atom, struct ref_array_item *re\n \treturn show_ref(&atom->u.refname, ref->refname);\n }\n \n-static int get_object(struct ref_array_item *ref, int deref, struct object **obj,\n+static int get_object(struct ref_array_item *ref, int deref,\n \t\t      struct expand_data *oi, struct strbuf *err)\n {\n-\t/* parse_object_buffer() will set eaten to 0 if free() will be needed */\n-\tint eaten = 1;\n+\t/* parse_object_buffer() will set eaten to 1 if free() will be needed */\n+\tint eaten = 0;\n+\tint ret;\n+\n \tif (oi->info.contentp) {\n \t\t/* We need to know that to use parse_object_buffer properly */\n \t\toi->info.sizep = &oi->size;\n \t\toi->info.typep = &oi->type;\n \t}\n+\n \tif (odb_read_object_info_extended(the_repository->objects, &oi->oid, &oi->info,\n-\t\t\t\t\t  OBJECT_INFO_LOOKUP_REPLACE))\n-\t\treturn strbuf_addf_ret(err, -1, _(\"missing object %s for %s\"),\n-\t\t\t\t       oid_to_hex(&oi->oid), ref->refname);\n+\t\t\t\t\t  OBJECT_INFO_LOOKUP_REPLACE)) {\n+\t\tret = strbuf_addf_ret(err, -1, _(\"missing object %s for %s\"),\n+\t\t\t\t      oid_to_hex(&oi->oid), ref->refname);\n+\t\tgoto out;\n+\t}\n \tif (oi->info.disk_sizep && oi->disk_size < 0)\n \t\tBUG(\"Object size is less than zero.\");\n \n \tif (oi->info.contentp) {\n-\t\t*obj = parse_object_buffer(the_repository, &oi->oid, oi->type, oi->size, oi->content, &eaten);\n-\t\tif (!*obj) {\n-\t\t\tif (!eaten)\n-\t\t\t\tfree(oi->content);\n-\t\t\treturn strbuf_addf_ret(err, -1, _(\"parse_object_buffer failed on %s for %s\"),\n-\t\t\t\t\t       oid_to_hex(&oi->oid), ref->refname);\n-\t\t}\n-\t\tgrab_values(ref->value, deref, *obj, oi);\n+\t\tret = grab_values(ref->value, deref, oi, ref->refname, err, &eaten);\n+\t\tif (ret < 0)\n+\t\t\tgoto out;\n \t}\n \n \tgrab_common_values(ref->value, deref, oi);\n+\tret = 0;\n+\n+out:\n \tif (!eaten)\n \t\tfree(oi->content);\n-\treturn 0;\n+\treturn ret;\n }\n \n static void populate_worktree_map(struct hashmap *map, struct worktree **worktrees)\n@@ -2376,7 +2455,6 @@ static char *get_worktree_path(const struct ref_array_item *ref)\n  */\n static int populate_value(struct ref_array_item *ref, struct strbuf *err)\n {\n-\tstruct object *obj;\n \tint i;\n \tstruct object_info empty = OBJECT_INFO_INIT;\n \tint ahead_behind_atoms = 0;\n@@ -2564,14 +2642,14 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)\n \n \n \toi.oid = ref->objectname;\n-\tif (get_object(ref, 0, &obj, &oi, err))\n+\tif (get_object(ref, 0, &oi, err))\n \t\treturn -1;\n \n \t/*\n \t * If there is no atom that wants to know about tagged\n \t * object, we are done.\n \t */\n-\tif (!need_tagged || (obj->type != OBJ_TAG))\n+\tif (!need_tagged || (oi.type != OBJ_TAG))\n \t\treturn 0;\n \n \t/*\n@@ -2589,7 +2667,7 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)\n \t\t}\n \t}\n \n-\treturn get_object(ref, 1, &obj, &oi_deref, err);\n+\treturn get_object(ref, 1, &oi_deref, err);\n }\n \n /*\n\n-- \n2.51.1.851.g4ebd6896fd.dirty\n\n"},{"id":"529407","messageId":"CAOLa=ZTo0pbPDxrHoTgcOoyAiuN+AYgrACti6kga25+trQnXtw@mail.gmail.com","threadId":"64260","inReplyTo":"20251022-b4-pks-ref-filter-skip-parsing-objects-v3-0-eb9f71985ef0@pks.im","subject":"Re: [PATCH v3 00/14] refs: improvements and fixes for peeling tags","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2025-10-22T10:57:18Z","receivedAt":"2025-10-22T10:57:21Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Hi,\n>\n> originally, all I wanted to do was the last patch: a small performance\n> optimization that stops parsing objects in git-for-each-ref(1) unless we\n> really need to parse them. But that fix cause one specific test to fail,\n> and only with the reftable backend. So this led me down the rabbit hole\n> of tag peeling, ending up with this patch series.\n>\n> The series is structured like follows:\n>\n>   - Patches 1 to 8 refactor our codebase so that we don't have the\n>     `peel_iterated_object()` hack anymore. I just found it hard to\n>     follow and thought it shouldn't be too hard to get rid of it.\n>\n>   - Patches 9 and 10 remove infrastructure that we don't need anymore\n>     after the first couple of patches.\n>\n>   - Patches 11 to 13 fix a couple of issues with peeled tags that I\n>     found. The underlying issue is that tags store both the tagged\n>     object and their type, but this information may not match. We never\n>     verify the actual object type though when allocating the tagged\n>     object, so this only blows up much later.\n>\n>   - Patch 14 was my original motivation, a small performance\n>     optimization.\n>\n> I'm not particularly fond of the patches 11 to 13. It feels more like\n> playing whack-a-mole, and I very much assume that there still are edge\n> cases where we should properly verify the tagged object type. But\n> changing it in `parse_tag_buffer()` itself causes a bunch of tests to\n> fail where we intentionally create such corrupted tags. So I didn't\n> really dare to touch that part, to be honest.\n>\n> If anybody has suggestions for an alternative approach I'd be very open\n> to it.\n>\n> Changes in v3:\n>   - I've rebuilt the topic on 133d151831 (The twenty-first batch, 2025-10-20) with\n>         - tb/incremental-midx-part-3.1 at 935ab44a0a (builtin/repack.c:\n>           clean up unused `#include`s, 2025-10-15)\n>         - jt/16a93c03c7 at (builtin/repo: add progress meter for\n>           structure stats, 2025-10-21)\n>     merged into it. This is done to fix a couple of merge conflicts with\n>     \"seen\". Both of the topics are only in \"seen\" right now, but they\n>     are close to be merged.\n>   - Link to v2: https://lore.kernel.org/r/20251008-b4-pks-ref-filter-skip-parsing-objects-v2-0-76e30d5c9542@pks.im\n>\n\nI had already reviewed version 1, the changes from v2 and v3 look good\nto me! :)\n\nKarthik\n"},{"id":"529422","messageId":"xmqq4irr0z8k.fsf@gitster.g","threadId":"64260","inReplyTo":"20251022-b4-pks-ref-filter-skip-parsing-objects-v3-0-eb9f71985ef0@pks.im","subject":"Re: [PATCH v3 00/14] refs: improvements and fixes for peeling tags","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-10-22T14:47:55Z","receivedAt":"2025-10-22T14:47:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Changes in v3:\n>   - I've rebuilt the topic on 133d151831 (The twenty-first batch, 2025-10-20) with\n>         - tb/incremental-midx-part-3.1 at 935ab44a0a (builtin/repack.c:\n>           clean up unused `#include`s, 2025-10-15)\n>         - jt/16a93c03c7 at (builtin/repo: add progress meter for\n>           structure stats, 2025-10-21)\n>     merged into it. This is done to fix a couple of merge conflicts with\n>     \"seen\". Both of the topics are only in \"seen\" right now, but they\n>     are close to be merged.\n\nThe latter reference is weird, but I think I know which topic you\nmeant, as I just finished preparing a merge-fix to account for that\ntopic.\n\nWill re-queue.\n\nThanks.\n"},{"id":"529424","messageId":"xmqqtszryn10.fsf@gitster.g","threadId":"64260","inReplyTo":"20251022-b4-pks-ref-filter-skip-parsing-objects-v3-14-eb9f71985ef0@pks.im","subject":"Re: [PATCH v3 14/14] ref-filter: parse objects on demand","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-10-22T15:27:39Z","receivedAt":"2025-10-22T15:27:44Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> +static int get_or_parse_object(struct expand_data *data, const char *refname,\n> +\t\t\t       struct object **object, struct strbuf *err, int *eaten)\n> +{\n> +\tif (!data->maybe_object) {\n> +\t\tdata->maybe_object = parse_object_buffer(the_repository, &data->oid, data->type,\n> +\t\t\t\t\t\t\t data->size, data->content, eaten);\n> +\t\tif (!data->maybe_object)\n> +\t\t\treturn strbuf_addf_ret(err, -1, _(\"parse_object_buffer failed on %s for %s\"),\n> +\t\t\t\t\t       oid_to_hex(&data->oid), refname);\n> +\t}\n> +\n> +\t*object = data->maybe_object;\n> +\treturn 0;\n> +}\n\nI wonder if the calling convention for this function can use further\nergonomic improvements, after seeing that ...\n\n> +\t\tif (!tag) {\n> +\t\t\tstruct object *object;\n> +\n> +\t\t\tret = get_or_parse_object(data, refname, &object, err, eaten);\n> +\t\t\tif (ret < 0)\n> +\t\t\t\treturn ret;\n> +\n> +\t\t\ttag = (struct tag *) object;\n> +\t\t}\n\n... this pattern is repeated three times in the patch.  If the\nhelper cannot legitimately return 0 (success) while stuffing NULL to\nthe object, then something like this\n\n\t\tif (!tag) {\n\t\t\ttag = (struct tag *) get_or_parse(data,\trefname, err, eaten);\n\t\t\tif (!tag)\n\t\t\t\treturn -1;\n\t\t}\n\nought to be sufficient for this caller and the other two.  I also\nwonder if this can further be simplified, but stopping at the above\nis fine to me.\n\nIn any case, the overall idea of this step is very much in line with\nthe original vision of ref-filter.c:populate_value() to try avoiding\nunnecessary work as much as possible, and it is very much welcome.\n\nNice performance improvements.\n\nWill queue.  Thanks.\n\n"},{"id":"529465","messageId":"aPnCs-zK1Mzi4wb8@pks.im","threadId":"64260","inReplyTo":"xmqq4irr0z8k.fsf@gitster.g","subject":"Re: [PATCH v3 00/14] refs: improvements and fixes for peeling tags","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-23T05:52:51Z","receivedAt":"2025-10-23T05:52:58Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Oct 22, 2025 at 07:47:55AM -0700, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > Changes in v3:\n> >   - I've rebuilt the topic on 133d151831 (The twenty-first batch, 2025-10-20) with\n> >         - tb/incremental-midx-part-3.1 at 935ab44a0a (builtin/repack.c:\n> >           clean up unused `#include`s, 2025-10-15)\n> >         - jt/16a93c03c7 at (builtin/repo: add progress meter for\n> >           structure stats, 2025-10-21)\n> >     merged into it. This is done to fix a couple of merge conflicts with\n> >     \"seen\". Both of the topics are only in \"seen\" right now, but they\n> >     are close to be merged.\n> \n> The latter reference is weird, but I think I know which topic you\n> meant, as I just finished preparing a merge-fix to account for that\n> topic.\n> \n> Will re-queue.\n\nOh, right, that was supposed to be jt/repo-structure. But I see that you\nmanaged to figure that out correctly, thanks!\n\nPatrick\n"},{"id":"529466","messageId":"aPnEYzHvY9u8tfw6@pks.im","threadId":"64260","inReplyTo":"xmqqtszryn10.fsf@gitster.g","subject":"Re: [PATCH v3 14/14] ref-filter: parse objects on demand","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-23T06:00:03Z","receivedAt":"2025-10-23T06:00:10Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Oct 22, 2025 at 08:27:39AM -0700, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > +static int get_or_parse_object(struct expand_data *data, const char *refname,\n> > +\t\t\t       struct object **object, struct strbuf *err, int *eaten)\n> > +{\n> > +\tif (!data->maybe_object) {\n> > +\t\tdata->maybe_object = parse_object_buffer(the_repository, &data->oid, data->type,\n> > +\t\t\t\t\t\t\t data->size, data->content, eaten);\n> > +\t\tif (!data->maybe_object)\n> > +\t\t\treturn strbuf_addf_ret(err, -1, _(\"parse_object_buffer failed on %s for %s\"),\n> > +\t\t\t\t\t       oid_to_hex(&data->oid), refname);\n> > +\t}\n> > +\n> > +\t*object = data->maybe_object;\n> > +\treturn 0;\n> > +}\n> \n> I wonder if the calling convention for this function can use further\n> ergonomic improvements, after seeing that ...\n> \n> > +\t\tif (!tag) {\n> > +\t\t\tstruct object *object;\n> > +\n> > +\t\t\tret = get_or_parse_object(data, refname, &object, err, eaten);\n> > +\t\t\tif (ret < 0)\n> > +\t\t\t\treturn ret;\n> > +\n> > +\t\t\ttag = (struct tag *) object;\n> > +\t\t}\n> \n> ... this pattern is repeated three times in the patch.  If the\n> helper cannot legitimately return 0 (success) while stuffing NULL to\n> the object, then something like this\n> \n> \t\tif (!tag) {\n> \t\t\ttag = (struct tag *) get_or_parse(data,\trefname, err, eaten);\n> \t\t\tif (!tag)\n> \t\t\t\treturn -1;\n> \t\t}\n> \n> ought to be sufficient for this caller and the other two.  I also\n> wonder if this can further be simplified, but stopping at the above\n> is fine to me.\n\nGood suggestion indeed. Let me use this style and send another version.\nThanks!\n\nPatrick\n"},{"id":"529468","messageId":"20251023-b4-pks-ref-filter-skip-parsing-objects-v4-0-2be68ce82c9a@pks.im","threadId":"64260","inReplyTo":"20251007-b4-pks-ref-filter-skip-parsing-objects-v1-0-916cc7c6886b@pks.im","subject":"[PATCH v4 00/14] refs: improvements and fixes for peeling tags","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-23T07:16:09Z","receivedAt":"2025-10-23T07:16:18Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\noriginally, all I wanted to do was the last patch: a small performance\noptimization that stops parsing objects in git-for-each-ref(1) unless we\nreally need to parse them. But that fix cause one specific test to fail,\nand only with the reftable backend. So this led me down the rabbit hole\nof tag peeling, ending up with this patch series.\n\nThe series is structured like follows:\n\n  - Patches 1 to 8 refactor our codebase so that we don't have the\n    `peel_iterated_object()` hack anymore. I just found it hard to\n    follow and thought it shouldn't be too hard to get rid of it.\n\n  - Patches 9 and 10 remove infrastructure that we don't need anymore\n    after the first couple of patches.\n\n  - Patches 11 to 13 fix a couple of issues with peeled tags that I\n    found. The underlying issue is that tags store both the tagged\n    object and their type, but this information may not match. We never\n    verify the actual object type though when allocating the tagged\n    object, so this only blows up much later.\n\n  - Patch 14 was my original motivation, a small performance\n    optimization.\n\nI'm not particularly fond of the patches 11 to 13. It feels more like\nplaying whack-a-mole, and I very much assume that there still are edge\ncases where we should properly verify the tagged object type. But\nchanging it in `parse_tag_buffer()` itself causes a bunch of tests to\nfail where we intentionally create such corrupted tags. So I didn't\nreally dare to touch that part, to be honest.\n\nIf anybody has suggestions for an alternative approach I'd be very open\nto it.\n\nChanges in v4:\n  - Improve `get_or_parse_object()` to have better ergonomics.\n  - Link to v3: https://lore.kernel.org/r/20251022-b4-pks-ref-filter-skip-parsing-objects-v3-0-eb9f71985ef0@pks.im\n\nChanges in v3:\n  - I've rebuilt the topic on 133d151831 (The twenty-first batch, 2025-10-20) with\n        - tb/incremental-midx-part-3.1 at 935ab44a0a (builtin/repack.c:\n          clean up unused `#include`s, 2025-10-15)\n        - jt/repo-structure 16a93c03c7 at (builtin/repo: add progress meter for\n          structure stats, 2025-10-21)\n    merged into it. This is done to fix a couple of merge conflicts with\n    \"seen\". Both of the topics are only in \"seen\" right now, but they\n    are close to be merged.\n  - Link to v2: https://lore.kernel.org/r/20251008-b4-pks-ref-filter-skip-parsing-objects-v2-0-76e30d5c9542@pks.im\n\nChanges in v2:\n  - A couple of improvements to commit messages.\n  - A new commit that ensures that `struct ref_iterator::ref` is always\n    zeroed out to protect against stale state.\n  - Link to v1: https://lore.kernel.org/r/20251007-b4-pks-ref-filter-skip-parsing-objects-v1-0-916cc7c6886b@pks.im\n\nThanks!\n\nPatrick\n\n---\nPatrick Steinhardt (14):\n      refs: introduce wrapper struct for `each_ref_fn`\n      refs: introduce `.ref` field for the base iterator\n      refs: fully reset `struct ref_iterator::ref` on iteration\n      refs: refactor reference status flags\n      refs: expose peeled object ID via the iterator\n      upload-pack: convert to use `reference_get_peeled_oid()`\n      ref-filter: propagate peeled object ID\n      builtin/show-ref: convert to use `reference_get_peeled_oid()`\n      refs: drop `current_ref_iter` hack\n      refs: drop infrastructure to peel via iterators\n      object: add flag to `peel_object()` to verify object type\n      refs: don't store peeled object IDs for invalid tags\n      ref-filter: detect broken tags when dereferencing them\n      ref-filter: parse objects on demand\n\n bisect.c                    |  24 ++---\n builtin/bisect.c            |  17 +---\n builtin/checkout.c          |   6 +-\n builtin/describe.c          |  18 ++--\n builtin/fetch.c             |  13 +--\n builtin/fsck.c              |  33 ++++---\n builtin/gc.c                |  15 ++-\n builtin/ls-remote.c         |   2 +-\n builtin/name-rev.c          |  17 ++--\n builtin/pack-objects.c      |  28 +++---\n builtin/receive-pack.c      |  13 ++-\n builtin/remote.c            |  44 ++++-----\n builtin/replace.c           |  21 ++---\n builtin/repo.c              |   9 +-\n builtin/rev-parse.c         |  12 +--\n builtin/show-branch.c       |  35 ++++---\n builtin/show-ref.c          |  50 +++++-----\n builtin/submodule--helper.c |  10 +-\n builtin/tag.c               |   2 +-\n builtin/verify-tag.c        |   2 +-\n builtin/worktree.c          |   6 +-\n commit-graph.c              |  14 ++-\n delta-islands.c             |   9 +-\n fetch-pack.c                |  16 +---\n help.c                      |  10 +-\n http-backend.c              |  20 ++--\n log-tree.c                  |  24 +++--\n ls-refs.c                   |  36 ++++---\n midx-write.c                |  17 ++--\n negotiator/default.c        |   7 +-\n negotiator/skipping.c       |   7 +-\n notes.c                     |   8 +-\n object-name.c               |  10 +-\n object.c                    |  20 +++-\n object.h                    |  15 ++-\n pseudo-merge.c              |  21 ++---\n reachable.c                 |   9 +-\n ref-filter.c                | 225 ++++++++++++++++++++++++++++++--------------\n ref-filter.h                |   5 +-\n reflog.c                    |   9 +-\n refs.c                      |  85 +++++++++--------\n refs.h                      |  88 ++++++++++-------\n refs/debug.c                |  17 +---\n refs/files-backend.c        |  71 +++++---------\n refs/iterator.c             |  73 +++-----------\n refs/packed-backend.c       |  71 +++++---------\n refs/ref-cache.c            |  18 +---\n refs/refs-internal.h        |  25 +----\n refs/reftable-backend.c     |  47 +++------\n remote.c                    |  27 +++---\n repack-midx.c               |  16 ++--\n replace-object.c            |  16 ++--\n revision.c                  |  12 +--\n server-info.c               |  12 +--\n shallow.c                   |  16 +---\n submodule.c                 |  12 +--\n t/for-each-ref-tests.sh     |   4 +-\n t/helper/test-reach.c       |   2 +-\n t/helper/test-ref-store.c   |   5 +-\n t/pack-refs-tests.sh        |  32 +++++++\n t/t0610-reftable-basics.sh  |  28 ++++++\n tag.c                       |  12 ---\n tag.h                       |   1 -\n upload-pack.c               |  49 +++++-----\n walker.c                    |   8 +-\n worktree.c                  |  11 ++-\n 66 files changed, 783 insertions(+), 834 deletions(-)\n\nRange-diff versus v3:\n\n 1:  3c95bfae6a0 =  1:  a8aafa4392b refs: introduce wrapper struct for `each_ref_fn`\n 2:  f8a24267571 =  2:  cf5faae21bd refs: introduce `.ref` field for the base iterator\n 3:  e4e576977bf =  3:  c0715cf44d8 refs: fully reset `struct ref_iterator::ref` on iteration\n 4:  ea00af0721f =  4:  52f65b25e89 refs: refactor reference status flags\n 5:  c3254472ddc =  5:  230cc4e7431 refs: expose peeled object ID via the iterator\n 6:  9df9622a93c =  6:  bca033ba86d upload-pack: convert to use `reference_get_peeled_oid()`\n 7:  b09a02a7f53 =  7:  fa914f4cede ref-filter: propagate peeled object ID\n 8:  160788e6a7c =  8:  1d9ba79bcf7 builtin/show-ref: convert to use `reference_get_peeled_oid()`\n 9:  f5793b2f1af =  9:  8451059caff refs: drop `current_ref_iter` hack\n10:  a467e8f55d1 = 10:  de3e67c31d2 refs: drop infrastructure to peel via iterators\n11:  49f45d1d680 = 11:  8782d6729df object: add flag to `peel_object()` to verify object type\n12:  4e355684a9a = 12:  ba7f85c8fef refs: don't store peeled object IDs for invalid tags\n13:  1f709cd4b9c = 13:  74ad4413535 ref-filter: detect broken tags when dereferencing them\n14:  ef24fc33852 ! 14:  e833e9943e2 ref-filter: parse objects on demand\n    @@ ref-filter.c: static void grab_common_values(struct atom_value *val, int deref,\n      \t}\n      }\n      \n    -+static int get_or_parse_object(struct expand_data *data, const char *refname,\n    -+\t\t\t       struct object **object, struct strbuf *err, int *eaten)\n    ++static struct object *get_or_parse_object(struct expand_data *data, const char *refname,\n    ++\t\t\t\t\t  struct strbuf *err, int *eaten)\n     +{\n     +\tif (!data->maybe_object) {\n     +\t\tdata->maybe_object = parse_object_buffer(the_repository, &data->oid, data->type,\n     +\t\t\t\t\t\t\t data->size, data->content, eaten);\n    -+\t\tif (!data->maybe_object)\n    -+\t\t\treturn strbuf_addf_ret(err, -1, _(\"parse_object_buffer failed on %s for %s\"),\n    -+\t\t\t\t\t       oid_to_hex(&data->oid), refname);\n    ++\t\tif (!data->maybe_object) {\n    ++\t\t\tstrbuf_addf(err, _(\"parse_object_buffer failed on %s for %s\"),\n    ++\t\t\t\t    oid_to_hex(&data->oid), refname);\n    ++\t\t\treturn NULL;\n    ++\t\t}\n     +\t}\n     +\n    -+\t*object = data->maybe_object;\n    -+\treturn 0;\n    ++\treturn data->maybe_object;\n     +}\n     +\n      /* See grab_values */\n    @@ ref-filter.c: static void grab_common_values(struct atom_value *val, int deref,\n     +\t\t\t   struct expand_data *data, const char *refname,\n     +\t\t\t   struct strbuf *err, int *eaten)\n      {\n    --\tint i;\n    --\tstruct tag *tag = (struct tag *) obj;\n     +\tstruct tag *tag = NULL;\n    -+\tint i, ret;\n    + \tint i;\n    +-\tstruct tag *tag = (struct tag *) obj;\n      \n      \tfor (i = 0; i < used_atom_cnt; i++) {\n      \t\tconst char *name = used_atom[i].name;\n    @@ ref-filter.c: static void grab_tag_values(struct atom_value *val, int deref, str\n      \t\t\tcontinue;\n     +\n     +\t\tif (!tag) {\n    -+\t\t\tstruct object *object;\n    -+\n    -+\t\t\tret = get_or_parse_object(data, refname, &object, err, eaten);\n    -+\t\t\tif (ret < 0)\n    -+\t\t\t\treturn ret;\n    -+\n    -+\t\t\ttag = (struct tag *) object;\n    ++\t\t\ttag = (struct tag *) get_or_parse_object(data, refname,\n    ++\t\t\t\t\t\t\t\t err, eaten);\n    ++\t\t\tif (!tag)\n    ++\t\t\t\treturn -1;\n     +\t\t}\n     +\n      \t\tif (deref)\n    @@ ref-filter.c: static void grab_tag_values(struct atom_value *val, int deref, str\n     +\t\t\t      struct expand_data *data, const char *refname,\n     +\t\t\t      struct strbuf *err, int *eaten)\n      {\n    --\tint i;\n    + \tint i;\n     -\tstruct commit *commit = (struct commit *) obj;\n    -+\tint i, ret;\n     +\tstruct commit *commit = NULL;\n      \n      \tfor (i = 0; i < used_atom_cnt; i++) {\n    @@ ref-filter.c: static void grab_tag_values(struct atom_value *val, int deref, str\n      \t\t\tname++;\n     +\n     +\t\tif (!commit) {\n    -+\t\t\tstruct object *object;\n    -+\n    -+\t\t\tret = get_or_parse_object(data, refname, &object, err, eaten);\n    -+\t\t\tif (ret < 0)\n    -+\t\t\t\treturn ret;\n    -+\n    -+\t\t\tcommit = (struct commit *) object;\n    ++\t\t\tcommit = (struct commit *) get_or_parse_object(data, refname,\n    ++\t\t\t\t\t\t\t\t       err, eaten);\n    ++\t\t\tif (!commit)\n    ++\t\t\t\treturn -1;\n     +\t\t}\n     +\n      \t\tif (atom_type == ATOM_TREE &&\n    @@ ref-filter.c: static void grab_person(const char *who, struct atom_value *val, i\n     +\t\t\t  struct expand_data *data, const char *refname,\n     +\t\t\t  struct strbuf *err, int *eaten)\n      {\n    --\tint i;\n    + \tint i;\n     -\tstruct commit *commit = (struct commit *) obj;\n    -+\tint i, ret;\n     +\tstruct commit *commit = NULL;\n      \tstruct signature_check sigc = { 0 };\n      \tint signature_checked = 0;\n    @@ ref-filter.c: static void grab_signature(struct atom_value *val, int deref, stru\n      \n      \t\tif (!signature_checked) {\n     +\t\t\tif (!commit) {\n    -+\t\t\t\tstruct object *object;\n    -+\n    -+\t\t\t\tret = get_or_parse_object(data, refname, &object, err, eaten);\n    -+\t\t\t\tif (ret < 0)\n    -+\t\t\t\t\treturn ret;\n    -+\n    -+\t\t\t\tcommit = (struct commit *) object;\n    ++\t\t\t\tcommit = (struct commit *) get_or_parse_object(data, refname,\n    ++\t\t\t\t\t\t\t\t\t       err, eaten);\n    ++\t\t\t\tif (!commit)\n    ++\t\t\t\t\treturn -1;\n     +\t\t\t}\n     +\n      \t\t\tcheck_commit_signature(commit, &sigc);\n\n---\nbase-commit: 5c120f01eb88f4be8b06fd4bc6893763204b78c4\nchange-id: 20250918-b4-pks-ref-filter-skip-parsing-objects-f0d1f6af4a9f\n\n"},{"id":"529469","messageId":"20251023-b4-pks-ref-filter-skip-parsing-objects-v4-1-2be68ce82c9a@pks.im","threadId":"64260","inReplyTo":"20251023-b4-pks-ref-filter-skip-parsing-objects-v4-0-2be68ce82c9a@pks.im","subject":"[PATCH v4 01/14] refs: introduce wrapper struct for `each_ref_fn`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-23T07:16:10Z","receivedAt":"2025-10-23T07:16:20Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `each_ref_fn` callback function type is used across our code base\nfor several different functions that iterate through reference. There's\na bunch of callbacks implementing this type, which makes any changes to\nthe callback signature extremely noisy. An example of the required churn\nis e8207717f1 (refs: add referent to each_ref_fn, 2024-08-09): adding a\nsingle argument required us to change 48 files.\n\nIt was already proposed back then [1] that we might want to introduce a\nwrapper structure to alleviate the pain going forward. While this of\ncourse requires the same kind of global refactoring as just introducing\na new parameter, it at least allows us to more change the callback type\nafterwards by just extending the wrapper structure.\n\nOne counterargument to this refactoring is that it makes the structure\nmore opaque. While it is obvious which callsites need to be fixed up\nwhen we change the function type, it's not obvious anymore once we use\na structure. That being said, we only have a handful of sites that\nactually need to populate this wrapper structure: our ref backends,\n\"refs/iterator.c\" as well as very few sites that invoke the iterator\ncallback functions directly.\n\nIntroduce this wrapper structure so that we can adapt the iterator\ninterfaces more readily.\n\n[1]: <ZmarVcF5JjsZx0dl@tanuki>\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n bisect.c                    | 24 +++++++---------\n builtin/bisect.c            | 17 ++++--------\n builtin/checkout.c          |  6 ++--\n builtin/describe.c          | 18 ++++++------\n builtin/fetch.c             | 13 +++------\n builtin/fsck.c              | 33 ++++++++++++----------\n builtin/gc.c                | 15 ++++------\n builtin/name-rev.c          | 17 ++++++------\n builtin/pack-objects.c      | 27 ++++++++----------\n builtin/receive-pack.c      | 13 ++++-----\n builtin/remote.c            | 44 +++++++++++++----------------\n builtin/replace.c           | 21 ++++++--------\n builtin/repo.c              |  9 ++----\n builtin/rev-parse.c         | 12 ++++----\n builtin/show-branch.c       | 35 +++++++++++------------\n builtin/show-ref.c          | 20 ++++++--------\n builtin/submodule--helper.c | 10 ++-----\n builtin/worktree.c          |  6 +---\n commit-graph.c              | 14 ++++------\n delta-islands.c             |  9 +++---\n fetch-pack.c                | 16 +++--------\n help.c                      | 10 +++----\n http-backend.c              | 20 ++++++--------\n log-tree.c                  | 24 ++++++++--------\n ls-refs.c                   | 36 ++++++++++++++----------\n midx-write.c                | 17 ++++++------\n negotiator/default.c        |  7 ++---\n negotiator/skipping.c       |  7 ++---\n notes.c                     |  8 ++----\n object-name.c               | 10 +++----\n pseudo-merge.c              | 21 ++++++--------\n reachable.c                 |  9 +++---\n ref-filter.c                | 24 ++++++++--------\n reflog.c                    |  9 ++----\n refs.c                      | 67 +++++++++++++++++++++++++--------------------\n refs.h                      | 26 +++++++++++++++---\n refs/files-backend.c        |  7 ++---\n refs/iterator.c             |  9 +++++-\n remote.c                    | 27 ++++++++----------\n repack-midx.c               | 16 +++++------\n replace-object.c            | 16 ++++-------\n revision.c                  | 12 ++++----\n server-info.c               | 12 ++++----\n shallow.c                   | 16 +++--------\n submodule.c                 | 12 ++------\n t/helper/test-ref-store.c   |  5 ++--\n upload-pack.c               | 29 +++++++++-----------\n walker.c                    |  8 ++----\n worktree.c                  | 11 ++++++--\n 49 files changed, 392 insertions(+), 462 deletions(-)\n\ndiff --git a/bisect.c b/bisect.c\nindex a6dc76b15c9..326b59c0dc7 100644\n--- a/bisect.c\n+++ b/bisect.c\n@@ -450,21 +450,20 @@ void find_bisection(struct commit_list **commit_list, int *reaches,\n \tclear_commit_weight(&commit_weight);\n }\n \n-static int register_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\tint flags UNUSED, void *cb_data UNUSED)\n+static int register_ref(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct strbuf good_prefix = STRBUF_INIT;\n \tstrbuf_addstr(&good_prefix, term_good);\n \tstrbuf_addstr(&good_prefix, \"-\");\n \n-\tif (!strcmp(refname, term_bad)) {\n+\tif (!strcmp(ref->name, term_bad)) {\n \t\tfree(current_bad_oid);\n \t\tcurrent_bad_oid = xmalloc(sizeof(*current_bad_oid));\n-\t\toidcpy(current_bad_oid, oid);\n-\t} else if (starts_with(refname, good_prefix.buf)) {\n-\t\toid_array_append(&good_revs, oid);\n-\t} else if (starts_with(refname, \"skip-\")) {\n-\t\toid_array_append(&skipped_revs, oid);\n+\t\toidcpy(current_bad_oid, ref->oid);\n+\t} else if (starts_with(ref->name, good_prefix.buf)) {\n+\t\toid_array_append(&good_revs, ref->oid);\n+\t} else if (starts_with(ref->name, \"skip-\")) {\n+\t\toid_array_append(&skipped_revs, ref->oid);\n \t}\n \n \tstrbuf_release(&good_prefix);\n@@ -1178,14 +1177,11 @@ int estimate_bisect_steps(int all)\n \treturn (e < 3 * x) ? n : n - 1;\n }\n \n-static int mark_for_removal(const char *refname,\n-\t\t\t    const char *referent UNUSED,\n-\t\t\t    const struct object_id *oid UNUSED,\n-\t\t\t    int flag UNUSED, void *cb_data)\n+static int mark_for_removal(const struct reference *ref, void *cb_data)\n {\n \tstruct string_list *refs = cb_data;\n-\tchar *ref = xstrfmt(\"refs/bisect%s\", refname);\n-\tstring_list_append(refs, ref);\n+\tchar *bisect_ref = xstrfmt(\"refs/bisect%s\", ref->name);\n+\tstring_list_append(refs, bisect_ref);\n \treturn 0;\n }\n \ndiff --git a/builtin/bisect.c b/builtin/bisect.c\nindex 8b8d870cd1e..5b2024be62d 100644\n--- a/builtin/bisect.c\n+++ b/builtin/bisect.c\n@@ -358,10 +358,7 @@ static int check_and_set_terms(struct bisect_terms *terms, const char *cmd)\n \treturn 0;\n }\n \n-static int inc_nr(const char *refname UNUSED,\n-\t\t  const char *referent UNUSED,\n-\t\t  const struct object_id *oid UNUSED,\n-\t\t  int flag UNUSED, void *cb_data)\n+static int inc_nr(const struct reference *ref UNUSED, void *cb_data)\n {\n \tunsigned int *nr = (unsigned int *)cb_data;\n \t(*nr)++;\n@@ -549,12 +546,11 @@ static int bisect_append_log_quoted(const char **argv)\n \treturn res;\n }\n \n-static int add_bisect_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t  int flags UNUSED, void *cb)\n+static int add_bisect_ref(const struct reference *ref, void *cb)\n {\n \tstruct add_bisect_ref_data *data = cb;\n \n-\tadd_pending_oid(data->revs, refname, oid, data->object_flags);\n+\tadd_pending_oid(data->revs, ref->name, ref->oid, data->object_flags);\n \n \treturn 0;\n }\n@@ -1165,12 +1161,9 @@ static int bisect_visualize(struct bisect_terms *terms, int argc,\n \treturn run_command(&cmd);\n }\n \n-static int get_first_good(const char *refname UNUSED,\n-\t\t\t  const char *referent UNUSED,\n-\t\t\t  const struct object_id *oid,\n-\t\t\t  int flag UNUSED, void *cb_data)\n+static int get_first_good(const struct reference *ref, void *cb_data)\n {\n-\toidcpy(cb_data, oid);\n+\toidcpy(cb_data, ref->oid);\n \treturn 1;\n }\n \ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex f9453473fe2..66b69df6e67 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1063,11 +1063,9 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\treport_tracking(new_branch_info);\n }\n \n-static int add_pending_uninteresting_ref(const char *refname, const char *referent UNUSED,\n-\t\t\t\t\t const struct object_id *oid,\n-\t\t\t\t\t int flags UNUSED, void *cb_data)\n+static int add_pending_uninteresting_ref(const struct reference *ref, void *cb_data)\n {\n-\tadd_pending_oid(cb_data, refname, oid, UNINTERESTING);\n+\tadd_pending_oid(cb_data, ref->name, ref->oid, UNINTERESTING);\n \treturn 0;\n }\n \ndiff --git a/builtin/describe.c b/builtin/describe.c\nindex ffaf8d9f0aa..79545350443 100644\n--- a/builtin/describe.c\n+++ b/builtin/describe.c\n@@ -154,20 +154,19 @@ static void add_to_known_names(const char *path,\n \t}\n }\n \n-static int get_name(const char *path, const char *referent UNUSED, const struct object_id *oid,\n-\t\t    int flag UNUSED, void *cb_data UNUSED)\n+static int get_name(const struct reference *ref, void *cb_data UNUSED)\n {\n \tint is_tag = 0;\n \tstruct object_id peeled;\n \tint is_annotated, prio;\n \tconst char *path_to_match = NULL;\n \n-\tif (skip_prefix(path, \"refs/tags/\", &path_to_match)) {\n+\tif (skip_prefix(ref->name, \"refs/tags/\", &path_to_match)) {\n \t\tis_tag = 1;\n \t} else if (all) {\n \t\tif ((exclude_patterns.nr || patterns.nr) &&\n-\t\t    !skip_prefix(path, \"refs/heads/\", &path_to_match) &&\n-\t\t    !skip_prefix(path, \"refs/remotes/\", &path_to_match)) {\n+\t\t    !skip_prefix(ref->name, \"refs/heads/\", &path_to_match) &&\n+\t\t    !skip_prefix(ref->name, \"refs/remotes/\", &path_to_match)) {\n \t\t\t/* Only accept reference of known type if there are match/exclude patterns */\n \t\t\treturn 0;\n \t\t}\n@@ -209,10 +208,10 @@ static int get_name(const char *path, const char *referent UNUSED, const struct\n \t}\n \n \t/* Is it annotated? */\n-\tif (!peel_iterated_oid(the_repository, oid, &peeled)) {\n-\t\tis_annotated = !oideq(oid, &peeled);\n+\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled)) {\n+\t\tis_annotated = !oideq(ref->oid, &peeled);\n \t} else {\n-\t\toidcpy(&peeled, oid);\n+\t\toidcpy(&peeled, ref->oid);\n \t\tis_annotated = 0;\n \t}\n \n@@ -229,7 +228,8 @@ static int get_name(const char *path, const char *referent UNUSED, const struct\n \telse\n \t\tprio = 0;\n \n-\tadd_to_known_names(all ? path + 5 : path + 10, &peeled, prio, oid);\n+\tadd_to_known_names(all ? ref->name + 5 : ref->name + 10,\n+\t\t\t   &peeled, prio, ref->oid);\n \treturn 0;\n }\n \ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex c7ff3480fb1..7052e6ff215 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -289,13 +289,11 @@ static struct refname_hash_entry *refname_hash_add(struct hashmap *map,\n \treturn ent;\n }\n \n-static int add_one_refname(const char *refname, const char *referent UNUSED,\n-\t\t\t   const struct object_id *oid,\n-\t\t\t   int flag UNUSED, void *cbdata)\n+static int add_one_refname(const struct reference *ref, void *cbdata)\n {\n \tstruct hashmap *refname_map = cbdata;\n \n-\t(void) refname_hash_add(refname_map, refname, oid);\n+\t(void) refname_hash_add(refname_map, ref->name, ref->oid);\n \treturn 0;\n }\n \n@@ -1416,14 +1414,11 @@ static void set_option(struct transport *transport, const char *name, const char\n }\n \n \n-static int add_oid(const char *refname UNUSED,\n-\t\t   const char *referent UNUSED,\n-\t\t   const struct object_id *oid,\n-\t\t   int flags UNUSED, void *cb_data)\n+static int add_oid(const struct reference *ref, void *cb_data)\n {\n \tstruct oid_array *oids = cb_data;\n \n-\toid_array_append(oids, oid);\n+\toid_array_append(oids, ref->oid);\n \treturn 0;\n }\n \ndiff --git a/builtin/fsck.c b/builtin/fsck.c\nindex 8ee95e0d67c..ed4eea16803 100644\n--- a/builtin/fsck.c\n+++ b/builtin/fsck.c\n@@ -530,14 +530,13 @@ static int fsck_handle_reflog(const char *logname, void *cb_data)\n \treturn 0;\n }\n \n-static int fsck_handle_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t   int flag UNUSED, void *cb_data UNUSED)\n+static int fsck_handle_ref(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct object *obj;\n \n-\tobj = parse_object(the_repository, oid);\n+\tobj = parse_object(the_repository, ref->oid);\n \tif (!obj) {\n-\t\tif (is_promisor_object(the_repository, oid)) {\n+\t\tif (is_promisor_object(the_repository, ref->oid)) {\n \t\t\t/*\n \t\t\t * Increment default_refs anyway, because this is a\n \t\t\t * valid ref.\n@@ -546,19 +545,19 @@ static int fsck_handle_ref(const char *refname, const char *referent UNUSED, con\n \t\t\t return 0;\n \t\t}\n \t\terror(_(\"%s: invalid sha1 pointer %s\"),\n-\t\t      refname, oid_to_hex(oid));\n+\t\t      ref->name, oid_to_hex(ref->oid));\n \t\terrors_found |= ERROR_REACHABLE;\n \t\t/* We'll continue with the rest despite the error.. */\n \t\treturn 0;\n \t}\n-\tif (obj->type != OBJ_COMMIT && is_branch(refname)) {\n-\t\terror(_(\"%s: not a commit\"), refname);\n+\tif (obj->type != OBJ_COMMIT && is_branch(ref->name)) {\n+\t\terror(_(\"%s: not a commit\"), ref->name);\n \t\terrors_found |= ERROR_REFS;\n \t}\n \tdefault_refs++;\n \tobj->flags |= USED;\n \tfsck_put_object_name(&fsck_walk_options,\n-\t\t\t     oid, \"%s\", refname);\n+\t\t\t     ref->oid, \"%s\", ref->name);\n \tmark_object_reachable(obj);\n \n \treturn 0;\n@@ -580,13 +579,19 @@ static void get_default_heads(void)\n \tworktrees = get_worktrees();\n \tfor (p = worktrees; *p; p++) {\n \t\tstruct worktree *wt = *p;\n-\t\tstruct strbuf ref = STRBUF_INIT;\n+\t\tstruct strbuf refname = STRBUF_INIT;\n \n-\t\tstrbuf_worktree_ref(wt, &ref, \"HEAD\");\n-\t\tfsck_head_link(ref.buf, &head_points_at, &head_oid);\n-\t\tif (head_points_at && !is_null_oid(&head_oid))\n-\t\t\tfsck_handle_ref(ref.buf, NULL, &head_oid, 0, NULL);\n-\t\tstrbuf_release(&ref);\n+\t\tstrbuf_worktree_ref(wt, &refname, \"HEAD\");\n+\t\tfsck_head_link(refname.buf, &head_points_at, &head_oid);\n+\t\tif (head_points_at && !is_null_oid(&head_oid)) {\n+\t\t\tstruct reference ref = {\n+\t\t\t\t.name = refname.buf,\n+\t\t\t\t.oid = &head_oid,\n+\t\t\t};\n+\n+\t\t\tfsck_handle_ref(&ref, NULL);\n+\t\t}\n+\t\tstrbuf_release(&refname);\n \n \t\tif (include_reflogs)\n \t\t\trefs_for_each_reflog(get_worktree_ref_store(wt),\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex e19e13d9788..9de5de175f6 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -1100,24 +1100,21 @@ struct cg_auto_data {\n \tint limit;\n };\n \n-static int dfs_on_ref(const char *refname UNUSED,\n-\t\t      const char *referent UNUSED,\n-\t\t      const struct object_id *oid,\n-\t\t      int flags UNUSED,\n-\t\t      void *cb_data)\n+static int dfs_on_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct cg_auto_data *data = (struct cg_auto_data *)cb_data;\n \tint result = 0;\n+\tconst struct object_id *maybe_peeled = ref->oid;\n \tstruct object_id peeled;\n \tstruct commit_list *stack = NULL;\n \tstruct commit *commit;\n \n-\tif (!peel_iterated_oid(the_repository, oid, &peeled))\n-\t\toid = &peeled;\n-\tif (odb_read_object_info(the_repository->objects, oid, NULL) != OBJ_COMMIT)\n+\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n+\t\tmaybe_peeled = &peeled;\n+\tif (odb_read_object_info(the_repository->objects, maybe_peeled, NULL) != OBJ_COMMIT)\n \t\treturn 0;\n \n-\tcommit = lookup_commit(the_repository, oid);\n+\tcommit = lookup_commit(the_repository, maybe_peeled);\n \tif (!commit)\n \t\treturn 0;\n \tif (repo_parse_commit(the_repository, commit) ||\ndiff --git a/builtin/name-rev.c b/builtin/name-rev.c\nindex 74512e54a38..615f7d1aae4 100644\n--- a/builtin/name-rev.c\n+++ b/builtin/name-rev.c\n@@ -339,10 +339,9 @@ static int cmp_by_tag_and_age(const void *a_, const void *b_)\n \treturn a->taggerdate != b->taggerdate;\n }\n \n-static int name_ref(const char *path, const char *referent UNUSED, const struct object_id *oid,\n-\t\t    int flags UNUSED, void *cb_data)\n+static int name_ref(const struct reference *ref, void *cb_data)\n {\n-\tstruct object *o = parse_object(the_repository, oid);\n+\tstruct object *o = parse_object(the_repository, ref->oid);\n \tstruct name_ref_data *data = cb_data;\n \tint can_abbreviate_output = data->tags_only && data->name_only;\n \tint deref = 0;\n@@ -350,14 +349,14 @@ static int name_ref(const char *path, const char *referent UNUSED, const struct\n \tstruct commit *commit = NULL;\n \ttimestamp_t taggerdate = TIME_MAX;\n \n-\tif (data->tags_only && !starts_with(path, \"refs/tags/\"))\n+\tif (data->tags_only && !starts_with(ref->name, \"refs/tags/\"))\n \t\treturn 0;\n \n \tif (data->exclude_filters.nr) {\n \t\tstruct string_list_item *item;\n \n \t\tfor_each_string_list_item(item, &data->exclude_filters) {\n-\t\t\tif (subpath_matches(path, item->string) >= 0)\n+\t\t\tif (subpath_matches(ref->name, item->string) >= 0)\n \t\t\t\treturn 0;\n \t\t}\n \t}\n@@ -378,7 +377,7 @@ static int name_ref(const char *path, const char *referent UNUSED, const struct\n \t\t\t * shouldn't stop when seeing 'refs/tags/v1.4' matches\n \t\t\t * 'refs/tags/v*'.  We should show it as 'v1.4'.\n \t\t\t */\n-\t\t\tswitch (subpath_matches(path, item->string)) {\n+\t\t\tswitch (subpath_matches(ref->name, item->string)) {\n \t\t\tcase -1: /* did not match */\n \t\t\t\tbreak;\n \t\t\tcase 0: /* matched fully */\n@@ -406,13 +405,13 @@ static int name_ref(const char *path, const char *referent UNUSED, const struct\n \t}\n \tif (o && o->type == OBJ_COMMIT) {\n \t\tcommit = (struct commit *)o;\n-\t\tfrom_tag = starts_with(path, \"refs/tags/\");\n+\t\tfrom_tag = starts_with(ref->name, \"refs/tags/\");\n \t\tif (taggerdate == TIME_MAX)\n \t\t\ttaggerdate = commit->date;\n \t}\n \n-\tadd_to_tip_table(oid, path, can_abbreviate_output, commit, taggerdate,\n-\t\t\t from_tag, deref);\n+\tadd_to_tip_table(ref->oid, ref->name, can_abbreviate_output,\n+\t\t\t commit, taggerdate, from_tag, deref);\n \treturn 0;\n }\n \ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 5bdc44fb2de..39633a0158e 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -831,15 +831,14 @@ static enum write_one_status write_one(struct hashfile *f,\n \treturn WRITE_ONE_WRITTEN;\n }\n \n-static int mark_tagged(const char *path UNUSED, const char *referent UNUSED, const struct object_id *oid,\n-\t\t       int flag UNUSED, void *cb_data UNUSED)\n+static int mark_tagged(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct object_id peeled;\n-\tstruct object_entry *entry = packlist_find(&to_pack, oid);\n+\tstruct object_entry *entry = packlist_find(&to_pack, ref->oid);\n \n \tif (entry)\n \t\tentry->tagged = 1;\n-\tif (!peel_iterated_oid(the_repository, oid, &peeled)) {\n+\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled)) {\n \t\tentry = packlist_find(&to_pack, &peeled);\n \t\tif (entry)\n \t\t\tentry->tagged = 1;\n@@ -3306,13 +3305,12 @@ static void add_tag_chain(const struct object_id *oid)\n \t}\n }\n \n-static int add_ref_tag(const char *tag UNUSED, const char *referent UNUSED, const struct object_id *oid,\n-\t\t       int flag UNUSED, void *cb_data UNUSED)\n+static int add_ref_tag(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct object_id peeled;\n \n-\tif (!peel_iterated_oid(the_repository, oid, &peeled) && obj_is_packed(&peeled))\n-\t\tadd_tag_chain(oid);\n+\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled) && obj_is_packed(&peeled))\n+\t\tadd_tag_chain(ref->oid);\n \treturn 0;\n }\n \n@@ -4533,19 +4531,16 @@ static void record_recent_commit(struct commit *commit, void *data UNUSED)\n \toid_array_append(&recent_objects, &commit->object.oid);\n }\n \n-static int mark_bitmap_preferred_tip(const char *refname,\n-\t\t\t\t     const char *referent UNUSED,\n-\t\t\t\t     const struct object_id *oid,\n-\t\t\t\t     int flags UNUSED,\n-\t\t\t\t     void *data UNUSED)\n+static int mark_bitmap_preferred_tip(const struct reference *ref, void *data UNUSED)\n {\n+\tconst struct object_id *maybe_peeled = ref->oid;\n \tstruct object_id peeled;\n \tstruct object *object;\n \n-\tif (!peel_iterated_oid(the_repository, oid, &peeled))\n-\t\toid = &peeled;\n+\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n+\t\tmaybe_peeled = &peeled;\n \n-\tobject = parse_object_or_die(the_repository, oid, refname);\n+\tobject = parse_object_or_die(the_repository, maybe_peeled, ref->name);\n \tif (object->type == OBJ_COMMIT)\n \t\tobject->flags |= NEEDS_BITMAP;\n \ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex c9288a9c7e3..e8ee0e73217 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -305,13 +305,12 @@ static void show_ref(const char *path, const struct object_id *oid)\n \t}\n }\n \n-static int show_ref_cb(const char *path_full, const char *referent UNUSED, const struct object_id *oid,\n-\t\t       int flag UNUSED, void *data)\n+static int show_ref_cb(const struct reference *ref, void *data)\n {\n \tstruct oidset *seen = data;\n-\tconst char *path = strip_namespace(path_full);\n+\tconst char *path = strip_namespace(ref->name);\n \n-\tif (ref_is_hidden(path, path_full, &hidden_refs))\n+\tif (ref_is_hidden(path, ref->name, &hidden_refs))\n \t\treturn 0;\n \n \t/*\n@@ -320,13 +319,13 @@ static int show_ref_cb(const char *path_full, const char *referent UNUSED, const\n \t * transfer but will otherwise ignore them.\n \t */\n \tif (!path) {\n-\t\tif (oidset_insert(seen, oid))\n+\t\tif (oidset_insert(seen, ref->oid))\n \t\t\treturn 0;\n \t\tpath = \".have\";\n \t} else {\n-\t\toidset_insert(seen, oid);\n+\t\toidset_insert(seen, ref->oid);\n \t}\n-\tshow_ref(path, oid);\n+\tshow_ref(path, ref->oid);\n \treturn 0;\n }\n \ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 8a7ed4299a4..7ffc14ba157 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -570,17 +570,14 @@ struct branches_for_remote {\n \tstruct known_remotes *keep;\n };\n \n-static int add_branch_for_removal(const char *refname,\n-\t\t\t\t  const char *referent UNUSED,\n-\t\t\t\t  const struct object_id *oid UNUSED,\n-\t\t\t\t  int flags UNUSED, void *cb_data)\n+static int add_branch_for_removal(const struct reference *ref, void *cb_data)\n {\n \tstruct branches_for_remote *branches = cb_data;\n \tstruct refspec_item refspec;\n \tstruct known_remote *kr;\n \n \tmemset(&refspec, 0, sizeof(refspec));\n-\trefspec.dst = (char *)refname;\n+\trefspec.dst = (char *)ref->name;\n \tif (remote_find_tracking(branches->remote, &refspec))\n \t\treturn 0;\n \tfree(refspec.src);\n@@ -588,7 +585,7 @@ static int add_branch_for_removal(const char *refname,\n \t/* don't delete a branch if another remote also uses it */\n \tfor (kr = branches->keep->list; kr; kr = kr->next) {\n \t\tmemset(&refspec, 0, sizeof(refspec));\n-\t\trefspec.dst = (char *)refname;\n+\t\trefspec.dst = (char *)ref->name;\n \t\tif (!remote_find_tracking(kr->remote, &refspec)) {\n \t\t\tfree(refspec.src);\n \t\t\treturn 0;\n@@ -596,16 +593,16 @@ static int add_branch_for_removal(const char *refname,\n \t}\n \n \t/* don't delete non-remote-tracking refs */\n-\tif (!starts_with(refname, \"refs/remotes/\")) {\n+\tif (!starts_with(ref->name, \"refs/remotes/\")) {\n \t\t/* advise user how to delete local branches */\n-\t\tif (starts_with(refname, \"refs/heads/\"))\n+\t\tif (starts_with(ref->name, \"refs/heads/\"))\n \t\t\tstring_list_append(branches->skipped,\n-\t\t\t\t\t   abbrev_branch(refname));\n+\t\t\t\t\t   abbrev_branch(ref->name));\n \t\t/* silently skip over other non-remote refs */\n \t\treturn 0;\n \t}\n \n-\tstring_list_append(branches->branches, refname);\n+\tstring_list_append(branches->branches, ref->name);\n \n \treturn 0;\n }\n@@ -713,18 +710,18 @@ static int rename_one_reflog(const char *old_refname,\n \treturn error;\n }\n \n-static int rename_one_ref(const char *old_refname, const char *referent,\n-\t\t\t  const struct object_id *oid,\n-\t\t\t  int flags, void *cb_data)\n+static int rename_one_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct strbuf new_referent = STRBUF_INIT;\n \tstruct strbuf new_refname = STRBUF_INIT;\n \tstruct rename_info *rename = cb_data;\n+\tconst struct object_id *oid = ref->oid;\n+\tconst char *referent = ref->target;\n \tint error;\n \n-\tcompute_renamed_ref(rename, old_refname, &new_refname);\n+\tcompute_renamed_ref(rename, ref->name, &new_refname);\n \n-\tif (flags & REF_ISSYMREF) {\n+\tif (ref->flags & REF_ISSYMREF) {\n \t\t/*\n \t\t * Stupidly enough `referent` is not pointing to the immediate\n \t\t * target of a symref, but it's the recursively resolved value.\n@@ -732,25 +729,25 @@ static int rename_one_ref(const char *old_refname, const char *referent,\n \t\t * unborn symrefs don't have any value for the `referent` at all.\n \t\t */\n \t\treferent = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),\n-\t\t\t\t\t\t   old_refname, RESOLVE_REF_NO_RECURSE,\n+\t\t\t\t\t\t   ref->name, RESOLVE_REF_NO_RECURSE,\n \t\t\t\t\t\t   NULL, NULL);\n \t\tcompute_renamed_ref(rename, referent, &new_referent);\n \t\toid = NULL;\n \t}\n \n-\terror = ref_transaction_delete(rename->transaction, old_refname,\n+\terror = ref_transaction_delete(rename->transaction, ref->name,\n \t\t\t\t       oid, referent, REF_NO_DEREF, NULL, rename->err);\n \tif (error < 0)\n \t\tgoto out;\n \n \terror = ref_transaction_update(rename->transaction, new_refname.buf, oid, null_oid(the_hash_algo),\n-\t\t\t\t       (flags & REF_ISSYMREF) ? new_referent.buf : NULL, NULL,\n+\t\t\t\t       (ref->flags & REF_ISSYMREF) ? new_referent.buf : NULL, NULL,\n \t\t\t\t       REF_SKIP_CREATE_REFLOG | REF_NO_DEREF | REF_SKIP_OID_VERIFICATION,\n \t\t\t\t       NULL, rename->err);\n \tif (error < 0)\n \t\tgoto out;\n \n-\terror = rename_one_reflog(old_refname, oid, rename);\n+\terror = rename_one_reflog(ref->name, oid, rename);\n \tif (error < 0)\n \t\tgoto out;\n \n@@ -1125,19 +1122,16 @@ static void free_remote_ref_states(struct ref_states *states)\n \tstring_list_clear_func(&states->push, clear_push_info);\n }\n \n-static int append_ref_to_tracked_list(const char *refname,\n-\t\t\t\t      const char *referent UNUSED,\n-\t\t\t\t      const struct object_id *oid UNUSED,\n-\t\t\t\t      int flags, void *cb_data)\n+static int append_ref_to_tracked_list(const struct reference *ref, void *cb_data)\n {\n \tstruct ref_states *states = cb_data;\n \tstruct refspec_item refspec;\n \n-\tif (flags & REF_ISSYMREF)\n+\tif (ref->flags & REF_ISSYMREF)\n \t\treturn 0;\n \n \tmemset(&refspec, 0, sizeof(refspec));\n-\trefspec.dst = (char *)refname;\n+\trefspec.dst = (char *)ref->name;\n \tif (!remote_find_tracking(states->remote, &refspec)) {\n \t\tstring_list_append(&states->tracked, abbrev_branch(refspec.src));\n \t\tfree(refspec.src);\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 900b560a77d..4c62c5ab58b 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -47,30 +47,27 @@ struct show_data {\n \tenum replace_format format;\n };\n \n-static int show_reference(const char *refname,\n-\t\t\t  const char *referent UNUSED,\n-\t\t\t  const struct object_id *oid,\n-\t\t\t  int flag UNUSED, void *cb_data)\n+static int show_reference(const struct reference *ref, void *cb_data)\n {\n \tstruct show_data *data = cb_data;\n \n-\tif (!wildmatch(data->pattern, refname, 0)) {\n+\tif (!wildmatch(data->pattern, ref->name, 0)) {\n \t\tif (data->format == REPLACE_FORMAT_SHORT)\n-\t\t\tprintf(\"%s\\n\", refname);\n+\t\t\tprintf(\"%s\\n\", ref->name);\n \t\telse if (data->format == REPLACE_FORMAT_MEDIUM)\n-\t\t\tprintf(\"%s -> %s\\n\", refname, oid_to_hex(oid));\n+\t\t\tprintf(\"%s -> %s\\n\", ref->name, oid_to_hex(ref->oid));\n \t\telse { /* data->format == REPLACE_FORMAT_LONG */\n \t\t\tstruct object_id object;\n \t\t\tenum object_type obj_type, repl_type;\n \n-\t\t\tif (repo_get_oid(data->repo, refname, &object))\n-\t\t\t\treturn error(_(\"failed to resolve '%s' as a valid ref\"), refname);\n+\t\t\tif (repo_get_oid(data->repo, ref->name, &object))\n+\t\t\t\treturn error(_(\"failed to resolve '%s' as a valid ref\"), ref->name);\n \n \t\t\tobj_type = odb_read_object_info(data->repo->objects, &object, NULL);\n-\t\t\trepl_type = odb_read_object_info(data->repo->objects, oid, NULL);\n+\t\t\trepl_type = odb_read_object_info(data->repo->objects, ref->oid, NULL);\n \n-\t\t\tprintf(\"%s (%s) -> %s (%s)\\n\", refname, type_name(obj_type),\n-\t\t\t       oid_to_hex(oid), type_name(repl_type));\n+\t\t\tprintf(\"%s (%s) -> %s (%s)\\n\", ref->name, type_name(obj_type),\n+\t\t\t       oid_to_hex(ref->oid), type_name(repl_type));\n \t\t}\n \t}\n \ndiff --git a/builtin/repo.c b/builtin/repo.c\nindex 9d4749f79be..f26640bd6ea 100644\n--- a/builtin/repo.c\n+++ b/builtin/repo.c\n@@ -366,16 +366,13 @@ struct count_references_data {\n \tstruct progress *progress;\n };\n \n-static int count_references(const char *refname,\n-\t\t\t    const char *referent UNUSED,\n-\t\t\t    const struct object_id *oid,\n-\t\t\t    int flags UNUSED, void *cb_data)\n+static int count_references(const struct reference *ref, void *cb_data)\n {\n \tstruct count_references_data *data = cb_data;\n \tstruct ref_stats *stats = data->stats;\n \tsize_t ref_count;\n \n-\tswitch (ref_kind_from_refname(refname)) {\n+\tswitch (ref_kind_from_refname(ref->name)) {\n \tcase FILTER_REFS_BRANCHES:\n \t\tstats->branches++;\n \t\tbreak;\n@@ -396,7 +393,7 @@ static int count_references(const char *refname,\n \t * While iterating through references for counting, also add OIDs in\n \t * preparation for the path walk.\n \t */\n-\tadd_pending_oid(data->revs, NULL, oid, 0);\n+\tadd_pending_oid(data->revs, NULL, ref->oid, 0);\n \n \tref_count = get_total_reference_count(stats);\n \tdisplay_progress(data->progress, ref_count);\ndiff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\nindex 9da92b990d0..3578591b4f2 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -217,19 +217,17 @@ static int show_default(void)\n \treturn 0;\n }\n \n-static int show_reference(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t  int flag UNUSED, void *cb_data UNUSED)\n+static int show_reference(const struct reference *ref, void *cb_data UNUSED)\n {\n-\tif (ref_excluded(&ref_excludes, refname))\n+\tif (ref_excluded(&ref_excludes, ref->name))\n \t\treturn 0;\n-\tshow_rev(NORMAL, oid, refname);\n+\tshow_rev(NORMAL, ref->oid, ref->name);\n \treturn 0;\n }\n \n-static int anti_reference(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t  int flag UNUSED, void *cb_data UNUSED)\n+static int anti_reference(const struct reference *ref, void *cb_data UNUSED)\n {\n-\tshow_rev(REVERSED, oid, refname);\n+\tshow_rev(REVERSED, ref->oid, ref->name);\n \treturn 0;\n }\n \ndiff --git a/builtin/show-branch.c b/builtin/show-branch.c\nindex 441babf2e35..10475a6b5ed 100644\n--- a/builtin/show-branch.c\n+++ b/builtin/show-branch.c\n@@ -413,34 +413,32 @@ static int append_ref(const char *refname, const struct object_id *oid,\n \treturn 0;\n }\n \n-static int append_head_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t   int flag UNUSED, void *cb_data UNUSED)\n+static int append_head_ref(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct object_id tmp;\n \tint ofs = 11;\n-\tif (!starts_with(refname, \"refs/heads/\"))\n+\tif (!starts_with(ref->name, \"refs/heads/\"))\n \t\treturn 0;\n \t/* If both heads/foo and tags/foo exists, get_sha1 would\n \t * get confused.\n \t */\n-\tif (repo_get_oid(the_repository, refname + ofs, &tmp) || !oideq(&tmp, oid))\n+\tif (repo_get_oid(the_repository, ref->name + ofs, &tmp) || !oideq(&tmp, ref->oid))\n \t\tofs = 5;\n-\treturn append_ref(refname + ofs, oid, 0);\n+\treturn append_ref(ref->name + ofs, ref->oid, 0);\n }\n \n-static int append_remote_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t     int flag UNUSED, void *cb_data UNUSED)\n+static int append_remote_ref(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct object_id tmp;\n \tint ofs = 13;\n-\tif (!starts_with(refname, \"refs/remotes/\"))\n+\tif (!starts_with(ref->name, \"refs/remotes/\"))\n \t\treturn 0;\n \t/* If both heads/foo and tags/foo exists, get_sha1 would\n \t * get confused.\n \t */\n-\tif (repo_get_oid(the_repository, refname + ofs, &tmp) || !oideq(&tmp, oid))\n+\tif (repo_get_oid(the_repository, ref->name + ofs, &tmp) || !oideq(&tmp, ref->oid))\n \t\tofs = 5;\n-\treturn append_ref(refname + ofs, oid, 0);\n+\treturn append_ref(ref->name + ofs, ref->oid, 0);\n }\n \n static int append_tag_ref(const char *refname, const struct object_id *oid,\n@@ -454,27 +452,26 @@ static int append_tag_ref(const char *refname, const struct object_id *oid,\n static const char *match_ref_pattern = NULL;\n static int match_ref_slash = 0;\n \n-static int append_matching_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t       int flag, void *cb_data)\n+static int append_matching_ref(const struct reference *ref, void *cb_data)\n {\n \t/* we want to allow pattern hold/<asterisk> to show all\n \t * branches under refs/heads/hold/, and v0.99.9? to show\n \t * refs/tags/v0.99.9a and friends.\n \t */\n \tconst char *tail;\n-\tint slash = count_slashes(refname);\n-\tfor (tail = refname; *tail && match_ref_slash < slash; )\n+\tint slash = count_slashes(ref->name);\n+\tfor (tail = ref->name; *tail && match_ref_slash < slash; )\n \t\tif (*tail++ == '/')\n \t\t\tslash--;\n \tif (!*tail)\n \t\treturn 0;\n \tif (wildmatch(match_ref_pattern, tail, 0))\n \t\treturn 0;\n-\tif (starts_with(refname, \"refs/heads/\"))\n-\t\treturn append_head_ref(refname, NULL, oid, flag, cb_data);\n-\tif (starts_with(refname, \"refs/tags/\"))\n-\t\treturn append_tag_ref(refname, oid, flag, cb_data);\n-\treturn append_ref(refname, oid, 0);\n+\tif (starts_with(ref->name, \"refs/heads/\"))\n+\t\treturn append_head_ref(ref, cb_data);\n+\tif (starts_with(ref->name, \"refs/tags/\"))\n+\t\treturn append_tag_ref(ref->name, ref->oid, ref->flags, cb_data);\n+\treturn append_ref(ref->name, ref->oid, 0);\n }\n \n static void snarf_refs(int head, int remotes)\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex 0b6f9edf86c..4803b5e5986 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -66,26 +66,25 @@ struct show_ref_data {\n \tint show_head;\n };\n \n-static int show_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t    int flag UNUSED, void *cbdata)\n+static int show_ref(const struct reference *ref, void *cbdata)\n {\n \tstruct show_ref_data *data = cbdata;\n \n-\tif (data->show_head && !strcmp(refname, \"HEAD\"))\n+\tif (data->show_head && !strcmp(ref->name, \"HEAD\"))\n \t\tgoto match;\n \n \tif (data->patterns) {\n-\t\tint reflen = strlen(refname);\n+\t\tint reflen = strlen(ref->name);\n \t\tconst char **p = data->patterns, *m;\n \t\twhile ((m = *p++) != NULL) {\n \t\t\tint len = strlen(m);\n \t\t\tif (len > reflen)\n \t\t\t\tcontinue;\n-\t\t\tif (memcmp(m, refname + reflen - len, len))\n+\t\t\tif (memcmp(m, ref->name + reflen - len, len))\n \t\t\t\tcontinue;\n \t\t\tif (len == reflen)\n \t\t\t\tgoto match;\n-\t\t\tif (refname[reflen - len - 1] == '/')\n+\t\t\tif (ref->name[reflen - len - 1] == '/')\n \t\t\t\tgoto match;\n \t\t}\n \t\treturn 0;\n@@ -94,18 +93,15 @@ static int show_ref(const char *refname, const char *referent UNUSED, const stru\n match:\n \tdata->found_match++;\n \n-\tshow_one(data->show_one_opts, refname, oid);\n+\tshow_one(data->show_one_opts, ref->name, ref->oid);\n \n \treturn 0;\n }\n \n-static int add_existing(const char *refname,\n-\t\t\tconst char *referent UNUSED,\n-\t\t\tconst struct object_id *oid UNUSED,\n-\t\t\tint flag UNUSED, void *cbdata)\n+static int add_existing(const struct reference *ref, void *cbdata)\n {\n \tstruct string_list *list = (struct string_list *)cbdata;\n-\tstring_list_insert(list, refname);\n+\tstring_list_insert(list, ref->name);\n \treturn 0;\n }\n \ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex fcd73abe533..35f6cf735e5 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -593,16 +593,12 @@ static void print_status(unsigned int flags, char state, const char *path,\n \tprintf(\"\\n\");\n }\n \n-static int handle_submodule_head_ref(const char *refname UNUSED,\n-\t\t\t\t     const char *referent UNUSED,\n-\t\t\t\t     const struct object_id *oid,\n-\t\t\t\t     int flags UNUSED,\n-\t\t\t\t     void *cb_data)\n+static int handle_submodule_head_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct object_id *output = cb_data;\n \n-\tif (oid)\n-\t\toidcpy(output, oid);\n+\tif (ref->oid)\n+\t\toidcpy(output, ref->oid);\n \n \treturn 0;\n }\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 812774a5ca9..b7f323b5e4d 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -635,11 +635,7 @@ static void print_preparing_worktree_line(int detach,\n  *\n  * Returns 0 on failure and non-zero on success.\n  */\n-static int first_valid_ref(const char *refname UNUSED,\n-\t\t\t   const char *referent UNUSED,\n-\t\t\t   const struct object_id *oid UNUSED,\n-\t\t\t   int flags UNUSED,\n-\t\t\t   void *cb_data UNUSED)\n+static int first_valid_ref(const struct reference *ref UNUSED, void *cb_data UNUSED)\n {\n \treturn 1;\n }\ndiff --git a/commit-graph.c b/commit-graph.c\nindex 474454db73d..f91af416259 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -1851,18 +1851,16 @@ struct refs_cb_data {\n \tstruct progress *progress;\n };\n \n-static int add_ref_to_set(const char *refname UNUSED,\n-\t\t\t  const char *referent UNUSED,\n-\t\t\t  const struct object_id *oid,\n-\t\t\t  int flags UNUSED, void *cb_data)\n+static int add_ref_to_set(const struct reference *ref, void *cb_data)\n {\n+\tconst struct object_id *maybe_peeled = ref->oid;\n \tstruct object_id peeled;\n \tstruct refs_cb_data *data = (struct refs_cb_data *)cb_data;\n \n-\tif (!peel_iterated_oid(data->repo, oid, &peeled))\n-\t\toid = &peeled;\n-\tif (odb_read_object_info(data->repo->objects, oid, NULL) == OBJ_COMMIT)\n-\t\toidset_insert(data->commits, oid);\n+\tif (!peel_iterated_oid(data->repo, ref->oid, &peeled))\n+\t\tmaybe_peeled = &peeled;\n+\tif (odb_read_object_info(data->repo->objects, maybe_peeled, NULL) == OBJ_COMMIT)\n+\t\toidset_insert(data->commits, maybe_peeled);\n \n \tdisplay_progress(data->progress, oidset_size(data->commits));\n \ndiff --git a/delta-islands.c b/delta-islands.c\nindex 36c94799d69..7cfebc4162b 100644\n--- a/delta-islands.c\n+++ b/delta-islands.c\n@@ -390,8 +390,7 @@ static void add_ref_to_island(kh_str_t *remote_islands, const char *island_name,\n \trl->hash += sha_core;\n }\n \n-static int find_island_for_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t       int flags UNUSED, void *cb)\n+static int find_island_for_ref(const struct reference *ref, void *cb)\n {\n \tstruct island_load_data *ild = cb;\n \n@@ -406,7 +405,7 @@ static int find_island_for_ref(const char *refname, const char *referent UNUSED,\n \n \t/* walk backwards to get last-one-wins ordering */\n \tfor (i = ild->nr - 1; i >= 0; i--) {\n-\t\tif (!regexec(&ild->rx[i], refname,\n+\t\tif (!regexec(&ild->rx[i], ref->name,\n \t\t\t     ARRAY_SIZE(matches), matches, 0))\n \t\t\tbreak;\n \t}\n@@ -428,10 +427,10 @@ static int find_island_for_ref(const char *refname, const char *referent UNUSED,\n \t\tif (island_name.len)\n \t\t\tstrbuf_addch(&island_name, '-');\n \n-\t\tstrbuf_add(&island_name, refname + match->rm_so, match->rm_eo - match->rm_so);\n+\t\tstrbuf_add(&island_name, ref->name + match->rm_so, match->rm_eo - match->rm_so);\n \t}\n \n-\tadd_ref_to_island(ild->remote_islands, island_name.buf, oid);\n+\tadd_ref_to_island(ild->remote_islands, island_name.buf, ref->oid);\n \tstrbuf_release(&island_name);\n \treturn 0;\n }\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex fe7a84bf2f9..78c45d4a155 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -188,13 +188,9 @@ static int rev_list_insert_ref(struct fetch_negotiator *negotiator,\n \treturn 0;\n }\n \n-static int rev_list_insert_ref_oid(const char *refname UNUSED,\n-\t\t\t\t   const char *referent UNUSED,\n-\t\t\t\t   const struct object_id *oid,\n-\t\t\t\t   int flag UNUSED,\n-\t\t\t\t   void *cb_data)\n+static int rev_list_insert_ref_oid(const struct reference *ref, void *cb_data)\n {\n-\treturn rev_list_insert_ref(cb_data, oid);\n+\treturn rev_list_insert_ref(cb_data, ref->oid);\n }\n \n enum ack_type {\n@@ -616,13 +612,9 @@ static int mark_complete(const struct object_id *oid)\n \treturn 0;\n }\n \n-static int mark_complete_oid(const char *refname UNUSED,\n-\t\t\t     const char *referent UNUSED,\n-\t\t\t     const struct object_id *oid,\n-\t\t\t     int flag UNUSED,\n-\t\t\t     void *cb_data UNUSED)\n+static int mark_complete_oid(const struct reference *ref, void *cb_data UNUSED)\n {\n-\treturn mark_complete(oid);\n+\treturn mark_complete(ref->oid);\n }\n \n static void mark_recent_complete_commits(struct fetch_pack_args *args,\ndiff --git a/help.c b/help.c\nindex 5854dd4a7e4..20e114432d7 100644\n--- a/help.c\n+++ b/help.c\n@@ -851,18 +851,16 @@ struct similar_ref_cb {\n \tstruct string_list *similar_refs;\n };\n \n-static int append_similar_ref(const char *refname, const char *referent UNUSED,\n-\t\t\t      const struct object_id *oid UNUSED,\n-\t\t\t      int flags UNUSED, void *cb_data)\n+static int append_similar_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct similar_ref_cb *cb = (struct similar_ref_cb *)(cb_data);\n-\tchar *branch = strrchr(refname, '/') + 1;\n+\tchar *branch = strrchr(ref->name, '/') + 1;\n \n \t/* A remote branch of the same name is deemed similar */\n-\tif (starts_with(refname, \"refs/remotes/\") &&\n+\tif (starts_with(ref->name, \"refs/remotes/\") &&\n \t    !strcmp(branch, cb->base_ref))\n \t\tstring_list_append_nodup(cb->similar_refs,\n-\t\t\t\t\t refs_shorten_unambiguous_ref(get_main_ref_store(the_repository), refname, 1));\n+\t\t\t\t\t refs_shorten_unambiguous_ref(get_main_ref_store(the_repository), ref->name, 1));\n \treturn 0;\n }\n \ndiff --git a/http-backend.c b/http-backend.c\nindex 9084058f1e9..92e1733f140 100644\n--- a/http-backend.c\n+++ b/http-backend.c\n@@ -513,18 +513,17 @@ static void run_service(const char **argv, int buffer_input)\n \t\texit(1);\n }\n \n-static int show_text_ref(const char *name, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t int flag UNUSED, void *cb_data)\n+static int show_text_ref(const struct reference *ref, void *cb_data)\n {\n-\tconst char *name_nons = strip_namespace(name);\n+\tconst char *name_nons = strip_namespace(ref->name);\n \tstruct strbuf *buf = cb_data;\n-\tstruct object *o = parse_object(the_repository, oid);\n+\tstruct object *o = parse_object(the_repository, ref->oid);\n \tif (!o)\n \t\treturn 0;\n \n-\tstrbuf_addf(buf, \"%s\\t%s\\n\", oid_to_hex(oid), name_nons);\n+\tstrbuf_addf(buf, \"%s\\t%s\\n\", oid_to_hex(ref->oid), name_nons);\n \tif (o->type == OBJ_TAG) {\n-\t\to = deref_tag(the_repository, o, name, 0);\n+\t\to = deref_tag(the_repository, o, ref->name, 0);\n \t\tif (!o)\n \t\t\treturn 0;\n \t\tstrbuf_addf(buf, \"%s\\t%s^{}\\n\", oid_to_hex(&o->oid),\n@@ -569,21 +568,20 @@ static void get_info_refs(struct strbuf *hdr, char *arg UNUSED)\n \tstrbuf_release(&buf);\n }\n \n-static int show_head_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t int flag, void *cb_data)\n+static int show_head_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct strbuf *buf = cb_data;\n \n-\tif (flag & REF_ISSYMREF) {\n+\tif (ref->flags & REF_ISSYMREF) {\n \t\tconst char *target = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),\n-\t\t\t\t\t\t\t     refname,\n+\t\t\t\t\t\t\t     ref->name,\n \t\t\t\t\t\t\t     RESOLVE_REF_READING,\n \t\t\t\t\t\t\t     NULL, NULL);\n \n \t\tif (target)\n \t\t\tstrbuf_addf(buf, \"ref: %s\\n\", strip_namespace(target));\n \t} else {\n-\t\tstrbuf_addf(buf, \"%s\\n\", oid_to_hex(oid));\n+\t\tstrbuf_addf(buf, \"%s\\n\", oid_to_hex(ref->oid));\n \t}\n \n \treturn 0;\ndiff --git a/log-tree.c b/log-tree.c\nindex 7d917f2a83d..1729b0c2012 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -147,9 +147,7 @@ static int ref_filter_match(const char *refname,\n \treturn 1;\n }\n \n-static int add_ref_decoration(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t      int flags UNUSED,\n-\t\t\t      void *cb_data)\n+static int add_ref_decoration(const struct reference *ref, void *cb_data)\n {\n \tint i;\n \tstruct object *obj;\n@@ -158,16 +156,16 @@ static int add_ref_decoration(const char *refname, const char *referent UNUSED,\n \tstruct decoration_filter *filter = (struct decoration_filter *)cb_data;\n \tconst char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref;\n \n-\tif (filter && !ref_filter_match(refname, filter))\n+\tif (filter && !ref_filter_match(ref->name, filter))\n \t\treturn 0;\n \n-\tif (starts_with(refname, git_replace_ref_base)) {\n+\tif (starts_with(ref->name, git_replace_ref_base)) {\n \t\tstruct object_id original_oid;\n \t\tif (!replace_refs_enabled(the_repository))\n \t\t\treturn 0;\n-\t\tif (get_oid_hex(refname + strlen(git_replace_ref_base),\n+\t\tif (get_oid_hex(ref->name + strlen(git_replace_ref_base),\n \t\t\t\t&original_oid)) {\n-\t\t\twarning(\"invalid replace ref %s\", refname);\n+\t\t\twarning(\"invalid replace ref %s\", ref->name);\n \t\t\treturn 0;\n \t\t}\n \t\tobj = parse_object(the_repository, &original_oid);\n@@ -176,10 +174,10 @@ static int add_ref_decoration(const char *refname, const char *referent UNUSED,\n \t\treturn 0;\n \t}\n \n-\tobjtype = odb_read_object_info(the_repository->objects, oid, NULL);\n+\tobjtype = odb_read_object_info(the_repository->objects, ref->oid, NULL);\n \tif (objtype < 0)\n \t\treturn 0;\n-\tobj = lookup_object_by_type(the_repository, oid, objtype);\n+\tobj = lookup_object_by_type(the_repository, ref->oid, objtype);\n \n \tfor (i = 0; i < ARRAY_SIZE(ref_namespace); i++) {\n \t\tstruct ref_namespace_info *info = &ref_namespace[i];\n@@ -187,24 +185,24 @@ static int add_ref_decoration(const char *refname, const char *referent UNUSED,\n \t\tif (!info->decoration)\n \t\t\tcontinue;\n \t\tif (info->exact) {\n-\t\t\tif (!strcmp(refname, info->ref)) {\n+\t\t\tif (!strcmp(ref->name, info->ref)) {\n \t\t\t\tdeco_type = info->decoration;\n \t\t\t\tbreak;\n \t\t\t}\n-\t\t} else if (starts_with(refname, info->ref)) {\n+\t\t} else if (starts_with(ref->name, info->ref)) {\n \t\t\tdeco_type = info->decoration;\n \t\t\tbreak;\n \t\t}\n \t}\n \n-\tadd_name_decoration(deco_type, refname, obj);\n+\tadd_name_decoration(deco_type, ref->name, obj);\n \twhile (obj->type == OBJ_TAG) {\n \t\tif (!obj->parsed)\n \t\t\tparse_object(the_repository, &obj->oid);\n \t\tobj = ((struct tag *)obj)->tagged;\n \t\tif (!obj)\n \t\t\tbreak;\n-\t\tadd_name_decoration(DECORATION_REF_TAG, refname, obj);\n+\t\tadd_name_decoration(DECORATION_REF_TAG, ref->name, obj);\n \t}\n \treturn 0;\n }\ndiff --git a/ls-refs.c b/ls-refs.c\nindex c47acde07f3..64d02723691 100644\n--- a/ls-refs.c\n+++ b/ls-refs.c\n@@ -75,42 +75,42 @@ struct ls_refs_data {\n \tunsigned unborn : 1;\n };\n \n-static int send_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t    int flag, void *cb_data)\n+static int send_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct ls_refs_data *data = cb_data;\n-\tconst char *refname_nons = strip_namespace(refname);\n+\tconst char *refname_nons = strip_namespace(ref->name);\n \n \tstrbuf_reset(&data->buf);\n \n-\tif (ref_is_hidden(refname_nons, refname, &data->hidden_refs))\n+\tif (ref_is_hidden(refname_nons, ref->name, &data->hidden_refs))\n \t\treturn 0;\n \n \tif (!ref_match(&data->prefixes, refname_nons))\n \t\treturn 0;\n \n-\tif (oid)\n-\t\tstrbuf_addf(&data->buf, \"%s %s\", oid_to_hex(oid), refname_nons);\n+\tif (ref->oid)\n+\t\tstrbuf_addf(&data->buf, \"%s %s\", oid_to_hex(ref->oid), refname_nons);\n \telse\n \t\tstrbuf_addf(&data->buf, \"unborn %s\", refname_nons);\n-\tif (data->symrefs && flag & REF_ISSYMREF) {\n+\tif (data->symrefs && ref->flags & REF_ISSYMREF) {\n+\t\tint unused_flag;\n \t\tstruct object_id unused;\n \t\tconst char *symref_target = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),\n-\t\t\t\t\t\t\t\t    refname,\n+\t\t\t\t\t\t\t\t    ref->name,\n \t\t\t\t\t\t\t\t    0,\n \t\t\t\t\t\t\t\t    &unused,\n-\t\t\t\t\t\t\t\t    &flag);\n+\t\t\t\t\t\t\t\t    &unused_flag);\n \n \t\tif (!symref_target)\n-\t\t\tdie(\"'%s' is a symref but it is not?\", refname);\n+\t\t\tdie(\"'%s' is a symref but it is not?\", ref->name);\n \n \t\tstrbuf_addf(&data->buf, \" symref-target:%s\",\n \t\t\t    strip_namespace(symref_target));\n \t}\n \n-\tif (data->peel && oid) {\n+\tif (data->peel && ref->oid) {\n \t\tstruct object_id peeled;\n-\t\tif (!peel_iterated_oid(the_repository, oid, &peeled))\n+\t\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n \t\t\tstrbuf_addf(&data->buf, \" peeled:%s\", oid_to_hex(&peeled));\n \t}\n \n@@ -131,9 +131,17 @@ static void send_possibly_unborn_head(struct ls_refs_data *data)\n \tif (!refs_resolve_ref_unsafe(get_main_ref_store(the_repository), namespaced.buf, 0, &oid, &flag))\n \t\treturn; /* bad ref */\n \toid_is_null = is_null_oid(&oid);\n+\n \tif (!oid_is_null ||\n-\t    (data->unborn && data->symrefs && (flag & REF_ISSYMREF)))\n-\t\tsend_ref(namespaced.buf, NULL, oid_is_null ? NULL : &oid, flag, data);\n+\t    (data->unborn && data->symrefs && (flag & REF_ISSYMREF))) {\n+\t\tstruct reference ref = {\n+\t\t\t.name = namespaced.buf,\n+\t\t\t.oid = oid_is_null ? NULL : &oid,\n+\t\t\t.flags = flag,\n+\t\t};\n+\n+\t\tsend_ref(&ref, data);\n+\t}\n \tstrbuf_release(&namespaced);\n }\n \ndiff --git a/midx-write.c b/midx-write.c\nindex c73010df6d3..f4dd875747a 100644\n--- a/midx-write.c\n+++ b/midx-write.c\n@@ -697,28 +697,27 @@ static void prepare_midx_packing_data(struct packing_data *pdata,\n \ttrace2_region_leave(\"midx\", \"prepare_midx_packing_data\", ctx->repo);\n }\n \n-static int add_ref_to_pending(const char *refname, const char *referent UNUSED,\n-\t\t\t      const struct object_id *oid,\n-\t\t\t      int flag, void *cb_data)\n+static int add_ref_to_pending(const struct reference *ref, void *cb_data)\n {\n \tstruct rev_info *revs = (struct rev_info*)cb_data;\n+\tconst struct object_id *maybe_peeled = ref->oid;\n \tstruct object_id peeled;\n \tstruct object *object;\n \n-\tif ((flag & REF_ISSYMREF) && (flag & REF_ISBROKEN)) {\n-\t\twarning(\"symbolic ref is dangling: %s\", refname);\n+\tif ((ref->flags & REF_ISSYMREF) && (ref->flags & REF_ISBROKEN)) {\n+\t\twarning(\"symbolic ref is dangling: %s\", ref->name);\n \t\treturn 0;\n \t}\n \n-\tif (!peel_iterated_oid(revs->repo, oid, &peeled))\n-\t\toid = &peeled;\n+\tif (!peel_iterated_oid(revs->repo, ref->oid, &peeled))\n+\t\tmaybe_peeled = &peeled;\n \n-\tobject = parse_object_or_die(revs->repo, oid, refname);\n+\tobject = parse_object_or_die(revs->repo, maybe_peeled, ref->name);\n \tif (object->type != OBJ_COMMIT)\n \t\treturn 0;\n \n \tadd_pending_object(revs, object, \"\");\n-\tif (bitmap_is_preferred_refname(revs->repo, refname))\n+\tif (bitmap_is_preferred_refname(revs->repo, ref->name))\n \t\tobject->flags |= NEEDS_BITMAP;\n \treturn 0;\n }\ndiff --git a/negotiator/default.c b/negotiator/default.c\nindex c479da9b091..116dedcf830 100644\n--- a/negotiator/default.c\n+++ b/negotiator/default.c\n@@ -38,11 +38,10 @@ static void rev_list_push(struct negotiation_state *ns,\n \t}\n }\n \n-static int clear_marks(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t       int flag UNUSED,\n-\t\t       void *cb_data UNUSED)\n+static int clear_marks(const struct reference *ref, void *cb_data UNUSED)\n {\n-\tstruct object *o = deref_tag(the_repository, parse_object(the_repository, oid), refname, 0);\n+\tstruct object *o = deref_tag(the_repository, parse_object(the_repository, ref->oid),\n+\t\t\t\t     ref->name, 0);\n \n \tif (o && o->type == OBJ_COMMIT)\n \t\tclear_commit_marks((struct commit *)o,\ndiff --git a/negotiator/skipping.c b/negotiator/skipping.c\nindex 616df6bf3af..0a272130fb1 100644\n--- a/negotiator/skipping.c\n+++ b/negotiator/skipping.c\n@@ -75,11 +75,10 @@ static struct entry *rev_list_push(struct data *data, struct commit *commit, int\n \treturn entry;\n }\n \n-static int clear_marks(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t       int flag UNUSED,\n-\t\t       void *cb_data UNUSED)\n+static int clear_marks(const struct reference *ref, void *cb_data UNUSED)\n {\n-\tstruct object *o = deref_tag(the_repository, parse_object(the_repository, oid), refname, 0);\n+\tstruct object *o = deref_tag(the_repository, parse_object(the_repository, ref->oid),\n+\t\t\t\t     ref->name, 0);\n \n \tif (o && o->type == OBJ_COMMIT)\n \t\tclear_commit_marks((struct commit *)o,\ndiff --git a/notes.c b/notes.c\nindex 9a2e9181fe6..8e00fd8c470 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -938,13 +938,11 @@ int combine_notes_cat_sort_uniq(struct object_id *cur_oid,\n \treturn ret;\n }\n \n-static int string_list_add_one_ref(const char *refname, const char *referent UNUSED,\n-\t\t\t\t   const struct object_id *oid UNUSED,\n-\t\t\t\t   int flag UNUSED, void *cb)\n+static int string_list_add_one_ref(const struct reference *ref, void *cb)\n {\n \tstruct string_list *refs = cb;\n-\tif (!unsorted_string_list_has_string(refs, refname))\n-\t\tstring_list_append(refs, refname);\n+\tif (!unsorted_string_list_has_string(refs, ref->name))\n+\t\tstring_list_append(refs, ref->name);\n \treturn 0;\n }\n \ndiff --git a/object-name.c b/object-name.c\nindex f6902e140dd..7e8109f25fb 100644\n--- a/object-name.c\n+++ b/object-name.c\n@@ -1444,18 +1444,16 @@ struct handle_one_ref_cb {\n \tstruct commit_list **list;\n };\n \n-static int handle_one_ref(const char *path, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t  int flag UNUSED,\n-\t\t\t  void *cb_data)\n+static int handle_one_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct handle_one_ref_cb *cb = cb_data;\n \tstruct commit_list **list = cb->list;\n-\tstruct object *object = parse_object(cb->repo, oid);\n+\tstruct object *object = parse_object(cb->repo, ref->oid);\n \tif (!object)\n \t\treturn 0;\n \tif (object->type == OBJ_TAG) {\n-\t\tobject = deref_tag(cb->repo, object, path,\n-\t\t\t\t   strlen(path));\n+\t\tobject = deref_tag(cb->repo, object, ref->name,\n+\t\t\t\t   strlen(ref->name));\n \t\tif (!object)\n \t\t\treturn 0;\n \t}\ndiff --git a/pseudo-merge.c b/pseudo-merge.c\nindex 893b763fe45..0abd51b42c1 100644\n--- a/pseudo-merge.c\n+++ b/pseudo-merge.c\n@@ -221,28 +221,25 @@ void load_pseudo_merges_from_config(struct repository *r,\n \t}\n }\n \n-static int find_pseudo_merge_group_for_ref(const char *refname,\n-\t\t\t\t\t   const char *referent UNUSED,\n-\t\t\t\t\t   const struct object_id *oid,\n-\t\t\t\t\t   int flags UNUSED,\n-\t\t\t\t\t   void *_data)\n+static int find_pseudo_merge_group_for_ref(const struct reference *ref, void *_data)\n {\n \tstruct bitmap_writer *writer = _data;\n+\tconst struct object_id *maybe_peeled = ref->oid;\n \tstruct object_id peeled;\n \tstruct commit *c;\n \tuint32_t i;\n \tint has_bitmap;\n \n-\tif (!peel_iterated_oid(the_repository, oid, &peeled))\n-\t\toid = &peeled;\n+\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n+\t\tmaybe_peeled = &peeled;\n \n-\tc = lookup_commit(the_repository, oid);\n+\tc = lookup_commit(the_repository, maybe_peeled);\n \tif (!c)\n \t\treturn 0;\n-\tif (!packlist_find(writer->to_pack, oid))\n+\tif (!packlist_find(writer->to_pack, maybe_peeled))\n \t\treturn 0;\n \n-\thas_bitmap = bitmap_writer_has_bitmapped_object_id(writer, oid);\n+\thas_bitmap = bitmap_writer_has_bitmapped_object_id(writer, maybe_peeled);\n \n \tfor (i = 0; i < writer->pseudo_merge_groups.nr; i++) {\n \t\tstruct pseudo_merge_group *group;\n@@ -252,7 +249,7 @@ static int find_pseudo_merge_group_for_ref(const char *refname,\n \t\tsize_t j;\n \n \t\tgroup = writer->pseudo_merge_groups.items[i].util;\n-\t\tif (regexec(group->pattern, refname, ARRAY_SIZE(captures),\n+\t\tif (regexec(group->pattern, ref->name, ARRAY_SIZE(captures),\n \t\t\t    captures, 0))\n \t\t\tcontinue;\n \n@@ -269,7 +266,7 @@ static int find_pseudo_merge_group_for_ref(const char *refname,\n \t\t\tif (group_name.len)\n \t\t\t\tstrbuf_addch(&group_name, '-');\n \n-\t\t\tstrbuf_add(&group_name, refname + match->rm_so,\n+\t\t\tstrbuf_add(&group_name, ref->name + match->rm_so,\n \t\t\t\t   match->rm_eo - match->rm_so);\n \t\t}\n \ndiff --git a/reachable.c b/reachable.c\nindex 22266db5233..b753c395530 100644\n--- a/reachable.c\n+++ b/reachable.c\n@@ -83,18 +83,17 @@ static void add_rebase_files(struct rev_info *revs)\n \tfree_worktrees(worktrees);\n }\n \n-static int add_one_ref(const char *path, const char *referent UNUSED, const struct object_id *oid,\n-\t\t       int flag, void *cb_data)\n+static int add_one_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct rev_info *revs = (struct rev_info *)cb_data;\n \tstruct object *object;\n \n-\tif ((flag & REF_ISSYMREF) && (flag & REF_ISBROKEN)) {\n-\t\twarning(\"symbolic ref is dangling: %s\", path);\n+\tif ((ref->flags & REF_ISSYMREF) && (ref->flags & REF_ISBROKEN)) {\n+\t\twarning(\"symbolic ref is dangling: %s\", ref->name);\n \t\treturn 0;\n \t}\n \n-\tobject = parse_object_or_die(the_repository, oid, path);\n+\tobject = parse_object_or_die(the_repository, ref->oid, ref->name);\n \tadd_pending_object(revs, object, \"\");\n \n \treturn 0;\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 30cc488d8ab..6837fa60a9b 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -2954,14 +2954,15 @@ struct ref_filter_cbdata {\n  * A call-back given to for_each_ref().  Filter refs and keep them for\n  * later object processing.\n  */\n-static int filter_one(const char *refname, const char *referent, const struct object_id *oid, int flag, void *cb_data)\n+static int filter_one(const struct reference *ref, void *cb_data)\n {\n \tstruct ref_filter_cbdata *ref_cbdata = cb_data;\n-\tstruct ref_array_item *ref;\n+\tstruct ref_array_item *item;\n \n-\tref = apply_ref_filter(refname, referent, oid, flag, ref_cbdata->filter);\n-\tif (ref)\n-\t\tref_array_append(ref_cbdata->array, ref);\n+\titem = apply_ref_filter(ref->name, ref->target, ref->oid,\n+\t\t\t\tref->flags, ref_cbdata->filter);\n+\tif (item)\n+\t\tref_array_append(ref_cbdata->array, item);\n \n \treturn 0;\n }\n@@ -2990,17 +2991,18 @@ struct ref_filter_and_format_cbdata {\n \t} internal;\n };\n \n-static int filter_and_format_one(const char *refname, const char *referent, const struct object_id *oid, int flag, void *cb_data)\n+static int filter_and_format_one(const struct reference *ref, void *cb_data)\n {\n \tstruct ref_filter_and_format_cbdata *ref_cbdata = cb_data;\n-\tstruct ref_array_item *ref;\n+\tstruct ref_array_item *item;\n \tstruct strbuf output = STRBUF_INIT, err = STRBUF_INIT;\n \n-\tref = apply_ref_filter(refname, referent, oid, flag, ref_cbdata->filter);\n-\tif (!ref)\n+\titem = apply_ref_filter(ref->name, ref->target, ref->oid,\n+\t\t\t\tref->flags, ref_cbdata->filter);\n+\tif (!item)\n \t\treturn 0;\n \n-\tif (format_ref_array_item(ref, ref_cbdata->format, &output, &err))\n+\tif (format_ref_array_item(item, ref_cbdata->format, &output, &err))\n \t\tdie(\"%s\", err.buf);\n \n \tif (output.len || !ref_cbdata->format->array_opts.omit_empty) {\n@@ -3010,7 +3012,7 @@ static int filter_and_format_one(const char *refname, const char *referent, cons\n \n \tstrbuf_release(&output);\n \tstrbuf_release(&err);\n-\tfree_array_item(ref);\n+\tfree_array_item(item);\n \n \t/*\n \t * Increment the running count of refs that match the filter. If\ndiff --git a/reflog.c b/reflog.c\nindex 65ef259b4f5..ac87e20c4f9 100644\n--- a/reflog.c\n+++ b/reflog.c\n@@ -423,16 +423,13 @@ int should_expire_reflog_ent_verbose(struct object_id *ooid,\n \treturn expire;\n }\n \n-static int push_tip_to_list(const char *refname UNUSED,\n-\t\t\t    const char *referent UNUSED,\n-\t\t\t    const struct object_id *oid,\n-\t\t\t    int flags, void *cb_data)\n+static int push_tip_to_list(const struct reference *ref, void *cb_data)\n {\n \tstruct commit_list **list = cb_data;\n \tstruct commit *tip_commit;\n-\tif (flags & REF_ISSYMREF)\n+\tif (ref->flags & REF_ISSYMREF)\n \t\treturn 0;\n-\ttip_commit = lookup_commit_reference_gently(the_repository, oid, 1);\n+\ttip_commit = lookup_commit_reference_gently(the_repository, ref->oid, 1);\n \tif (!tip_commit)\n \t\treturn 0;\n \tcommit_list_insert(tip_commit, list);\ndiff --git a/refs.c b/refs.c\nindex 965381367e0..25f0579d610 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -426,17 +426,19 @@ int refs_ref_exists(struct ref_store *refs, const char *refname)\n \t\t\t\t\t NULL, NULL);\n }\n \n-static int for_each_filter_refs(const char *refname, const char *referent,\n-\t\t\t\tconst struct object_id *oid,\n-\t\t\t\tint flags, void *data)\n+static int for_each_filter_refs(const struct reference *ref, void *data)\n {\n \tstruct for_each_ref_filter *filter = data;\n \n-\tif (wildmatch(filter->pattern, refname, 0))\n+\tif (wildmatch(filter->pattern, ref->name, 0))\n \t\treturn 0;\n-\tif (filter->prefix)\n-\t\tskip_prefix(refname, filter->prefix, &refname);\n-\treturn filter->fn(refname, referent, oid, flags, filter->cb_data);\n+\tif (filter->prefix) {\n+\t\tstruct reference skipped = *ref;\n+\t\tskip_prefix(skipped.name, filter->prefix, &skipped.name);\n+\t\treturn filter->fn(&skipped, filter->cb_data);\n+\t} else {\n+\t\treturn filter->fn(ref, filter->cb_data);\n+\t}\n }\n \n struct warn_if_dangling_data {\n@@ -447,17 +449,15 @@ struct warn_if_dangling_data {\n \tint dry_run;\n };\n \n-static int warn_if_dangling_symref(const char *refname, const char *referent UNUSED,\n-\t\t\t\t   const struct object_id *oid UNUSED,\n-\t\t\t\t   int flags, void *cb_data)\n+static int warn_if_dangling_symref(const struct reference *ref, void *cb_data)\n {\n \tstruct warn_if_dangling_data *d = cb_data;\n \tconst char *resolves_to, *msg;\n \n-\tif (!(flags & REF_ISSYMREF))\n+\tif (!(ref->flags & REF_ISSYMREF))\n \t\treturn 0;\n \n-\tresolves_to = refs_resolve_ref_unsafe(d->refs, refname, 0, NULL, NULL);\n+\tresolves_to = refs_resolve_ref_unsafe(d->refs, ref->name, 0, NULL, NULL);\n \tif (!resolves_to\n \t    || !string_list_has_string(d->refnames, resolves_to)) {\n \t\treturn 0;\n@@ -466,7 +466,7 @@ static int warn_if_dangling_symref(const char *refname, const char *referent UNU\n \tmsg = d->dry_run\n \t\t? _(\"%s%s will become dangling after %s is deleted\\n\")\n \t\t: _(\"%s%s has become dangling after %s was deleted\\n\");\n-\tfprintf(d->fp, msg, d->indent, refname, resolves_to);\n+\tfprintf(d->fp, msg, d->indent, ref->name, resolves_to);\n \treturn 0;\n }\n \n@@ -507,8 +507,15 @@ int refs_head_ref_namespaced(struct ref_store *refs, each_ref_fn fn, void *cb_da\n \tint flag;\n \n \tstrbuf_addf(&buf, \"%sHEAD\", get_git_namespace());\n-\tif (!refs_read_ref_full(refs, buf.buf, RESOLVE_REF_READING, &oid, &flag))\n-\t\tret = fn(buf.buf, NULL, &oid, flag, cb_data);\n+\tif (!refs_read_ref_full(refs, buf.buf, RESOLVE_REF_READING, &oid, &flag)) {\n+\t\tstruct reference ref = {\n+\t\t\t.name = buf.buf,\n+\t\t\t.oid = &oid,\n+\t\t\t.flags = flag,\n+\t\t};\n+\n+\t\tret = fn(&ref, cb_data);\n+\t}\n \tstrbuf_release(&buf);\n \n \treturn ret;\n@@ -1741,8 +1748,15 @@ int refs_head_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)\n \tint flag;\n \n \tif (refs_resolve_ref_unsafe(refs, \"HEAD\", RESOLVE_REF_READING,\n-\t\t\t\t    &oid, &flag))\n-\t\treturn fn(\"HEAD\", NULL, &oid, flag, cb_data);\n+\t\t\t\t    &oid, &flag)) {\n+\t\tstruct reference ref = {\n+\t\t\t.name = \"HEAD\",\n+\t\t\t.oid = &oid,\n+\t\t\t.flags = flag,\n+\t\t};\n+\n+\t\treturn fn(&ref, cb_data);\n+\t}\n \n \treturn 0;\n }\n@@ -2753,14 +2767,10 @@ struct do_for_each_reflog_help {\n \tvoid *cb_data;\n };\n \n-static int do_for_each_reflog_helper(const char *refname,\n-\t\t\t\t     const char *referent UNUSED,\n-\t\t\t\t     const struct object_id *oid UNUSED,\n-\t\t\t\t     int flags UNUSED,\n-\t\t\t\t     void *cb_data)\n+static int do_for_each_reflog_helper(const struct reference *ref, void *cb_data)\n {\n \tstruct do_for_each_reflog_help *hp = cb_data;\n-\treturn hp->fn(refname, hp->cb_data);\n+\treturn hp->fn(ref->name, hp->cb_data);\n }\n \n int refs_for_each_reflog(struct ref_store *refs, each_reflog_fn fn, void *cb_data)\n@@ -2976,25 +2986,24 @@ struct migration_data {\n \tuint64_t index;\n };\n \n-static int migrate_one_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t   int flags, void *cb_data)\n+static int migrate_one_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct migration_data *data = cb_data;\n \tstruct strbuf symref_target = STRBUF_INIT;\n \tint ret;\n \n-\tif (flags & REF_ISSYMREF) {\n-\t\tret = refs_read_symbolic_ref(data->old_refs, refname, &symref_target);\n+\tif (ref->flags & REF_ISSYMREF) {\n+\t\tret = refs_read_symbolic_ref(data->old_refs, ref->name, &symref_target);\n \t\tif (ret < 0)\n \t\t\tgoto done;\n \n-\t\tret = ref_transaction_update(data->transaction, refname, NULL, null_oid(the_hash_algo),\n+\t\tret = ref_transaction_update(data->transaction, ref->name, NULL, null_oid(the_hash_algo),\n \t\t\t\t\t     symref_target.buf, NULL,\n \t\t\t\t\t     REF_SKIP_CREATE_REFLOG | REF_NO_DEREF, NULL, data->errbuf);\n \t\tif (ret < 0)\n \t\t\tgoto done;\n \t} else {\n-\t\tret = ref_transaction_create(data->transaction, refname, oid, NULL,\n+\t\tret = ref_transaction_create(data->transaction, ref->name, ref->oid, NULL,\n \t\t\t\t\t     REF_SKIP_CREATE_REFLOG | REF_SKIP_OID_VERIFICATION,\n \t\t\t\t\t     NULL, data->errbuf);\n \t\tif (ret < 0)\ndiff --git a/refs.h b/refs.h\nindex 4e6bd63aa86..68d235438c2 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -355,14 +355,32 @@ struct ref_transaction;\n  */\n #define REF_BAD_NAME 0x08\n \n+/* A reference passed to `for_each_ref()`-style callbacks. */\n+struct reference {\n+\t/* The fully-qualified name of the reference. */\n+\tconst char *name;\n+\n+\t/* The target of a symbolic ref. `NULL` for direct references. */\n+\tconst char *target;\n+\n+\t/*\n+\t * The object ID of a reference. Either the direct object ID or the\n+\t * resolved object ID in the case of a symbolic ref. May be the zero\n+\t * object ID in case the symbolic ref cannot be resolved.\n+\t */\n+\tconst struct object_id *oid;\n+\n+\t/* A bitfield of `REF_` flags. */\n+\tint flags;\n+};\n+\n /*\n  * The signature for the callback function for the for_each_*()\n- * functions below.  The memory pointed to by the refname and oid\n- * arguments is only guaranteed to be valid for the duration of a\n+ * functions below.  The memory pointed to by the `struct reference`\n+ * argument is only guaranteed to be valid for the duration of a\n  * single callback invocation.\n  */\n-typedef int each_ref_fn(const char *refname, const char *referent,\n-\t\t\tconst struct object_id *oid, int flags, void *cb_data);\n+typedef int each_ref_fn(const struct reference *ref, void *cb_data);\n \n /*\n  * The following functions invoke the specified callback function for\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 8d7007f4aaa..eb3142f8f2d 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -3150,14 +3150,11 @@ static int parse_and_write_reflog(struct files_ref_store *refs,\n \treturn 0;\n }\n \n-static int ref_present(const char *refname, const char *referent UNUSED,\n-\t\t       const struct object_id *oid UNUSED,\n-\t\t       int flags UNUSED,\n-\t\t       void *cb_data)\n+static int ref_present(const struct reference *ref, void *cb_data)\n {\n \tstruct string_list *affected_refnames = cb_data;\n \n-\treturn string_list_has_string(affected_refnames, refname);\n+\treturn string_list_has_string(affected_refnames, ref->name);\n }\n \n static int files_transaction_finish_initial(struct files_ref_store *refs,\ndiff --git a/refs/iterator.c b/refs/iterator.c\nindex 17ef841d8a3..7f2e718f1c9 100644\n--- a/refs/iterator.c\n+++ b/refs/iterator.c\n@@ -476,7 +476,14 @@ int do_for_each_ref_iterator(struct ref_iterator *iter,\n \n \tcurrent_ref_iter = iter;\n \twhile ((ok = ref_iterator_advance(iter)) == ITER_OK) {\n-\t\tretval = fn(iter->refname, iter->referent, iter->oid, iter->flags, cb_data);\n+\t\tstruct reference ref = {\n+\t\t\t.name = iter->refname,\n+\t\t\t.target = iter->referent,\n+\t\t\t.oid = iter->oid,\n+\t\t\t.flags = iter->flags,\n+\t\t};\n+\n+\t\tretval = fn(&ref, cb_data);\n \t\tif (retval)\n \t\t\tgoto out;\n \t}\ndiff --git a/remote.c b/remote.c\nindex df9675cd330..59b37151208 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -2315,21 +2315,19 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,\n \treturn 1;\n }\n \n-static int one_local_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t int flag UNUSED,\n-\t\t\t void *cb_data)\n+static int one_local_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct ref ***local_tail = cb_data;\n-\tstruct ref *ref;\n+\tstruct ref *local_ref;\n \n \t/* we already know it starts with refs/ to get here */\n-\tif (check_refname_format(refname + 5, 0))\n+\tif (check_refname_format(ref->name + 5, 0))\n \t\treturn 0;\n \n-\tref = alloc_ref(refname);\n-\toidcpy(&ref->new_oid, oid);\n-\t**local_tail = ref;\n-\t*local_tail = &ref->next;\n+\tlocal_ref = alloc_ref(ref->name);\n+\toidcpy(&local_ref->new_oid, ref->oid);\n+\t**local_tail = local_ref;\n+\t*local_tail = &local_ref->next;\n \treturn 0;\n }\n \n@@ -2402,15 +2400,14 @@ struct stale_heads_info {\n \tstruct refspec *rs;\n };\n \n-static int get_stale_heads_cb(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t      int flags, void *cb_data)\n+static int get_stale_heads_cb(const struct reference *ref, void *cb_data)\n {\n \tstruct stale_heads_info *info = cb_data;\n \tstruct string_list matches = STRING_LIST_INIT_DUP;\n \tstruct refspec_item query;\n \tint i, stale = 1;\n \tmemset(&query, 0, sizeof(struct refspec_item));\n-\tquery.dst = (char *)refname;\n+\tquery.dst = (char *)ref->name;\n \n \trefspec_find_all_matches(info->rs, &query, &matches);\n \tif (matches.nr == 0)\n@@ -2423,7 +2420,7 @@ static int get_stale_heads_cb(const char *refname, const char *referent UNUSED,\n \t * overlapping refspecs, we need to go over all of the\n \t * matching refs.\n \t */\n-\tif (flags & REF_ISSYMREF)\n+\tif (ref->flags & REF_ISSYMREF)\n \t\tgoto clean_exit;\n \n \tfor (i = 0; stale && i < matches.nr; i++)\n@@ -2431,8 +2428,8 @@ static int get_stale_heads_cb(const char *refname, const char *referent UNUSED,\n \t\t\tstale = 0;\n \n \tif (stale) {\n-\t\tstruct ref *ref = make_linked_ref(refname, &info->stale_refs_tail);\n-\t\toidcpy(&ref->new_oid, oid);\n+\t\tstruct ref *linked_ref = make_linked_ref(ref->name, &info->stale_refs_tail);\n+\t\toidcpy(&linked_ref->new_oid, ref->oid);\n \t}\n \n clean_exit:\ndiff --git a/repack-midx.c b/repack-midx.c\nindex 6f6202c5bcc..349f7e20b53 100644\n--- a/repack-midx.c\n+++ b/repack-midx.c\n@@ -16,25 +16,23 @@ struct midx_snapshot_ref_data {\n \tint preferred;\n };\n \n-static int midx_snapshot_ref_one(const char *refname UNUSED,\n-\t\t\t\t const char *referent UNUSED,\n-\t\t\t\t const struct object_id *oid,\n-\t\t\t\t int flag UNUSED, void *_data)\n+static int midx_snapshot_ref_one(const struct reference *ref, void *_data)\n {\n \tstruct midx_snapshot_ref_data *data = _data;\n+\tconst struct object_id *maybe_peeled = ref->oid;\n \tstruct object_id peeled;\n \n-\tif (!peel_iterated_oid(data->repo, oid, &peeled))\n-\t\toid = &peeled;\n+\tif (!peel_iterated_oid(data->repo, ref->oid, &peeled))\n+\t\tmaybe_peeled = &peeled;\n \n-\tif (oidset_insert(&data->seen, oid))\n+\tif (oidset_insert(&data->seen, maybe_peeled))\n \t\treturn 0; /* already seen */\n \n-\tif (odb_read_object_info(data->repo->objects, oid, NULL) != OBJ_COMMIT)\n+\tif (odb_read_object_info(data->repo->objects, maybe_peeled, NULL) != OBJ_COMMIT)\n \t\treturn 0;\n \n \tfprintf(data->f->fp, \"%s%s\\n\", data->preferred ? \"+\" : \"\",\n-\t\toid_to_hex(oid));\n+\t\toid_to_hex(maybe_peeled));\n \n \treturn 0;\n }\ndiff --git a/replace-object.c b/replace-object.c\nindex 3eae0510745..03d0f1f083b 100644\n--- a/replace-object.c\n+++ b/replace-object.c\n@@ -8,31 +8,27 @@\n #include \"repository.h\"\n #include \"commit.h\"\n \n-static int register_replace_ref(const char *refname,\n-\t\t\t\tconst char *referent UNUSED,\n-\t\t\t\tconst struct object_id *oid,\n-\t\t\t\tint flag UNUSED,\n-\t\t\t\tvoid *cb_data)\n+static int register_replace_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct repository *r = cb_data;\n \n \t/* Get sha1 from refname */\n-\tconst char *slash = strrchr(refname, '/');\n-\tconst char *hash = slash ? slash + 1 : refname;\n+\tconst char *slash = strrchr(ref->name, '/');\n+\tconst char *hash = slash ? slash + 1 : ref->name;\n \tstruct replace_object *repl_obj = xmalloc(sizeof(*repl_obj));\n \n \tif (get_oid_hex_algop(hash, &repl_obj->original.oid, r->hash_algo)) {\n \t\tfree(repl_obj);\n-\t\twarning(_(\"bad replace ref name: %s\"), refname);\n+\t\twarning(_(\"bad replace ref name: %s\"), ref->name);\n \t\treturn 0;\n \t}\n \n \t/* Copy sha1 from the read ref */\n-\toidcpy(&repl_obj->replacement, oid);\n+\toidcpy(&repl_obj->replacement, ref->oid);\n \n \t/* Register new object */\n \tif (oidmap_put(&r->objects->replace_map, repl_obj))\n-\t\tdie(_(\"duplicate replace ref: %s\"), refname);\n+\t\tdie(_(\"duplicate replace ref: %s\"), ref->name);\n \n \treturn 0;\n }\ndiff --git a/revision.c b/revision.c\nindex cf5e6c1ec9e..5f0850ae5c9 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -1644,19 +1644,17 @@ struct all_refs_cb {\n \tstruct worktree *wt;\n };\n \n-static int handle_one_ref(const char *path, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\t  int flag UNUSED,\n-\t\t\t  void *cb_data)\n+static int handle_one_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct all_refs_cb *cb = cb_data;\n \tstruct object *object;\n \n-\tif (ref_excluded(&cb->all_revs->ref_excludes, path))\n+\tif (ref_excluded(&cb->all_revs->ref_excludes, ref->name))\n \t    return 0;\n \n-\tobject = get_reference(cb->all_revs, path, oid, cb->all_flags);\n-\tadd_rev_cmdline(cb->all_revs, object, path, REV_CMD_REF, cb->all_flags);\n-\tadd_pending_object(cb->all_revs, object, path);\n+\tobject = get_reference(cb->all_revs, ref->name, ref->oid, cb->all_flags);\n+\tadd_rev_cmdline(cb->all_revs, object, ref->name, REV_CMD_REF, cb->all_flags);\n+\tadd_pending_object(cb->all_revs, object, ref->name);\n \treturn 0;\n }\n \ndiff --git a/server-info.c b/server-info.c\nindex 1d33de821e9..0a07c722e8b 100644\n--- a/server-info.c\n+++ b/server-info.c\n@@ -148,23 +148,21 @@ static int update_info_file(struct repository *r, char *path,\n \treturn ret;\n }\n \n-static int add_info_ref(const char *path, const char *referent UNUSED, const struct object_id *oid,\n-\t\t\tint flag UNUSED,\n-\t\t\tvoid *cb_data)\n+static int add_info_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct update_info_ctx *uic = cb_data;\n-\tstruct object *o = parse_object(uic->repo, oid);\n+\tstruct object *o = parse_object(uic->repo, ref->oid);\n \tif (!o)\n \t\treturn -1;\n \n-\tif (uic_printf(uic, \"%s\t%s\\n\", oid_to_hex(oid), path) < 0)\n+\tif (uic_printf(uic, \"%s\t%s\\n\", oid_to_hex(ref->oid), ref->name) < 0)\n \t\treturn -1;\n \n \tif (o->type == OBJ_TAG) {\n-\t\to = deref_tag(uic->repo, o, path, 0);\n+\t\to = deref_tag(uic->repo, o, ref->name, 0);\n \t\tif (o)\n \t\t\tif (uic_printf(uic, \"%s\t%s^{}\\n\",\n-\t\t\t\toid_to_hex(&o->oid), path) < 0)\n+\t\t\t\toid_to_hex(&o->oid), ref->name) < 0)\n \t\t\t\treturn -1;\n \t}\n \treturn 0;\ndiff --git a/shallow.c b/shallow.c\nindex d9cd4e219cb..55b9cd9d3f2 100644\n--- a/shallow.c\n+++ b/shallow.c\n@@ -626,14 +626,10 @@ static void paint_down(struct paint_info *info, const struct object_id *oid,\n \tfree(tmp);\n }\n \n-static int mark_uninteresting(const char *refname UNUSED,\n-\t\t\t      const char *referent UNUSED,\n-\t\t\t      const struct object_id *oid,\n-\t\t\t      int flags UNUSED,\n-\t\t\t      void *cb_data UNUSED)\n+static int mark_uninteresting(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct commit *commit = lookup_commit_reference_gently(the_repository,\n-\t\t\t\t\t\t\t       oid, 1);\n+\t\t\t\t\t\t\t       ref->oid, 1);\n \tif (!commit)\n \t\treturn 0;\n \tcommit->object.flags |= UNINTERESTING;\n@@ -742,16 +738,12 @@ struct commit_array {\n \tsize_t nr, alloc;\n };\n \n-static int add_ref(const char *refname UNUSED,\n-\t\t  const char *referent UNUSED,\n-\t\t   const struct object_id *oid,\n-\t\t   int flags UNUSED,\n-\t\t   void *cb_data)\n+static int add_ref(const struct reference *ref, void *cb_data)\n {\n \tstruct commit_array *ca = cb_data;\n \tALLOC_GROW(ca->commits, ca->nr + 1, ca->alloc);\n \tca->commits[ca->nr] = lookup_commit_reference_gently(the_repository,\n-\t\t\t\t\t\t\t     oid, 1);\n+\t\t\t\t\t\t\t     ref->oid, 1);\n \tif (ca->commits[ca->nr])\n \t\tca->nr++;\n \treturn 0;\ndiff --git a/submodule.c b/submodule.c\nindex 35c55155f7b..40a5c6fb9d1 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -934,10 +934,7 @@ static void free_submodules_data(struct string_list *submodules)\n \tstring_list_clear(submodules, 1);\n }\n \n-static int has_remote(const char *refname UNUSED,\n-\t\t      const char *referent UNUSED,\n-\t\t      const struct object_id *oid UNUSED,\n-\t\t      int flags UNUSED, void *cb_data UNUSED)\n+static int has_remote(const struct reference *ref UNUSED, void *cb_data UNUSED)\n {\n \treturn 1;\n }\n@@ -1255,13 +1252,10 @@ int push_unpushed_submodules(struct repository *r,\n \treturn ret;\n }\n \n-static int append_oid_to_array(const char *ref UNUSED,\n-\t\t\t       const char *referent UNUSED,\n-\t\t\t       const struct object_id *oid,\n-\t\t\t       int flags UNUSED, void *data)\n+static int append_oid_to_array(const struct reference *ref, void *data)\n {\n \tstruct oid_array *array = data;\n-\toid_array_append(array, oid);\n+\toid_array_append(array, ref->oid);\n \treturn 0;\n }\n \ndiff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c\nindex 83b06d39a36..b1215947c5e 100644\n--- a/t/helper/test-ref-store.c\n+++ b/t/helper/test-ref-store.c\n@@ -154,10 +154,9 @@ static int cmd_rename_ref(struct ref_store *refs, const char **argv)\n \treturn refs_rename_ref(refs, oldref, newref, logmsg);\n }\n \n-static int each_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t    int flags, void *cb_data UNUSED)\n+static int each_ref(const struct reference *ref, void *cb_data UNUSED)\n {\n-\tprintf(\"%s %s 0x%x\\n\", oid_to_hex(oid), refname, flags);\n+\tprintf(\"%s %s 0x%x\\n\", oid_to_hex(ref->oid), ref->name, ref->flags);\n \treturn 0;\n }\n \ndiff --git a/upload-pack.c b/upload-pack.c\nindex 1e87ae95593..0d563ae74e9 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -870,8 +870,8 @@ static void send_unshallow(struct upload_pack_data *data)\n \t}\n }\n \n-static int check_ref(const char *refname_full, const char *referent UNUSED, const struct object_id *oid,\n-\t\t     int flag, void *cb_data);\n+static int check_ref(const struct reference *ref, void *cb_data);\n+\n static void deepen(struct upload_pack_data *data, int depth)\n {\n \tif (depth == INFINITE_DEPTH && !is_repository_shallow(the_repository)) {\n@@ -1224,13 +1224,12 @@ static int mark_our_ref(const char *refname, const char *refname_full,\n \treturn 0;\n }\n \n-static int check_ref(const char *refname_full, const char *referent UNUSED,const struct object_id *oid,\n-\t\t     int flag UNUSED, void *cb_data)\n+static int check_ref(const struct reference *ref, void *cb_data)\n {\n-\tconst char *refname = strip_namespace(refname_full);\n+\tconst char *refname = strip_namespace(ref->name);\n \tstruct upload_pack_data *data = cb_data;\n \n-\tmark_our_ref(refname, refname_full, oid, &data->hidden_refs);\n+\tmark_our_ref(refname, ref->name, ref->oid, &data->hidden_refs);\n \treturn 0;\n }\n \n@@ -1292,27 +1291,25 @@ static void write_v0_ref(struct upload_pack_data *data,\n \treturn;\n }\n \n-static int send_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid,\n-\t\t    int flag UNUSED, void *cb_data)\n+static int send_ref(const struct reference *ref, void *cb_data)\n {\n-\twrite_v0_ref(cb_data, refname, strip_namespace(refname), oid);\n+\twrite_v0_ref(cb_data, ref->name, strip_namespace(ref->name), ref->oid);\n \treturn 0;\n }\n \n-static int find_symref(const char *refname, const char *referent UNUSED,\n-\t\t       const struct object_id *oid UNUSED,\n-\t\t       int flag, void *cb_data)\n+static int find_symref(const struct reference *ref, void *cb_data)\n {\n \tconst char *symref_target;\n \tstruct string_list_item *item;\n+\tint flag;\n \n-\tif ((flag & REF_ISSYMREF) == 0)\n+\tif ((ref->flags & REF_ISSYMREF) == 0)\n \t\treturn 0;\n \tsymref_target = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),\n-\t\t\t\t\t\trefname, 0, NULL, &flag);\n+\t\t\t\t\t\tref->name, 0, NULL, &flag);\n \tif (!symref_target || (flag & REF_ISSYMREF) == 0)\n-\t\tdie(\"'%s' is a symref but it is not?\", refname);\n-\titem = string_list_append(cb_data, strip_namespace(refname));\n+\t\tdie(\"'%s' is a symref but it is not?\", ref->name);\n+\titem = string_list_append(cb_data, strip_namespace(ref->name));\n \titem->util = xstrdup(strip_namespace(symref_target));\n \treturn 0;\n }\ndiff --git a/walker.c b/walker.c\nindex 80737545172..409b646578a 100644\n--- a/walker.c\n+++ b/walker.c\n@@ -226,14 +226,10 @@ static int interpret_target(struct walker *walker, char *target, struct object_i\n \treturn -1;\n }\n \n-static int mark_complete(const char *path UNUSED,\n-\t\t\tconst char *referent UNUSED,\n-\t\t\t const struct object_id *oid,\n-\t\t\t int flag UNUSED,\n-\t\t\t void *cb_data UNUSED)\n+static int mark_complete(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct commit *commit = lookup_commit_reference_gently(the_repository,\n-\t\t\t\t\t\t\t       oid, 1);\n+\t\t\t\t\t\t\t       ref->oid, 1);\n \n \tif (commit) {\n \t\tcommit->object.flags |= COMPLETE;\ndiff --git a/worktree.c b/worktree.c\nindex a2a5f51f29f..9308389cb6f 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -595,8 +595,15 @@ int other_head_refs(each_ref_fn fn, void *cb_data)\n \t\tif (refs_resolve_ref_unsafe(get_main_ref_store(the_repository),\n \t\t\t\t\t    refname.buf,\n \t\t\t\t\t    RESOLVE_REF_READING,\n-\t\t\t\t\t    &oid, &flag))\n-\t\t\tret = fn(refname.buf, NULL, &oid, flag, cb_data);\n+\t\t\t\t\t    &oid, &flag)) {\n+\t\t\tstruct reference ref = {\n+\t\t\t\t.name = refname.buf,\n+\t\t\t\t.oid = &oid,\n+\t\t\t\t.flags = flag,\n+\t\t\t};\n+\n+\t\t\tret = fn(&ref, cb_data);\n+\t\t}\n \t\tif (ret)\n \t\t\tbreak;\n \t}\n\n-- \n2.51.1.930.gacf6e81ea2.dirty\n\n"},{"id":"529470","messageId":"20251023-b4-pks-ref-filter-skip-parsing-objects-v4-2-2be68ce82c9a@pks.im","threadId":"64260","inReplyTo":"20251023-b4-pks-ref-filter-skip-parsing-objects-v4-0-2be68ce82c9a@pks.im","subject":"[PATCH v4 02/14] refs: introduce `.ref` field for the base iterator","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-23T07:16:11Z","receivedAt":"2025-10-23T07:16:24Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The base iterator has a couple of fields that tracks the name, target,\nobject ID and flags for the current reference. Due to this design we\nhave to create a new `struct reference` whenever we want to hand over\nthat reference to the callback function, which is tedious and not very\nefficient.\n\nConvert the structure to instead contain a `struct reference` as member.\nThis member is expected to be populated by the implementations of the\niterator and is handed over to the callback directly.\n\nWhile at it, simplify `should_pack_ref()` to take a `struct reference`\ndirectly instead of passing its respective fields.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c                  |  8 ++++----\n refs/debug.c            |  8 +++-----\n refs/files-backend.c    | 47 +++++++++++++++++++++--------------------------\n refs/iterator.c         | 39 ++++++++++++---------------------------\n refs/packed-backend.c   | 46 +++++++++++++++++++++++-----------------------\n refs/ref-cache.c        | 10 +++++-----\n refs/refs-internal.h    |  5 +----\n refs/reftable-backend.c | 12 ++++++------\n 8 files changed, 75 insertions(+), 100 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 25f0579d610..f96cf43b128 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2327,8 +2327,8 @@ int refs_optimize(struct ref_store *refs, struct pack_refs_opts *opts)\n int peel_iterated_oid(struct repository *r, const struct object_id *base, struct object_id *peeled)\n {\n \tif (current_ref_iter &&\n-\t    (current_ref_iter->oid == base ||\n-\t     oideq(current_ref_iter->oid, base)))\n+\t    (current_ref_iter->ref.oid == base ||\n+\t     oideq(current_ref_iter->ref.oid, base)))\n \t\treturn ref_iterator_peel(current_ref_iter, peeled);\n \n \treturn peel_object(r, base, peeled) ? -1 : 0;\n@@ -2703,7 +2703,7 @@ enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs\n \n \t\t\twhile ((ok = ref_iterator_advance(iter)) == ITER_OK) {\n \t\t\t\tif (skip &&\n-\t\t\t\t    string_list_has_string(skip, iter->refname))\n+\t\t\t\t    string_list_has_string(skip, iter->ref.name))\n \t\t\t\t\tcontinue;\n \n \t\t\t\tif (transaction && ref_transaction_maybe_set_rejected(\n@@ -2712,7 +2712,7 @@ enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs\n \t\t\t\t\tcontinue;\n \n \t\t\t\tstrbuf_addf(err, _(\"'%s' exists; cannot create '%s'\"),\n-\t\t\t\t\t    iter->refname, refname);\n+\t\t\t\t\t    iter->ref.name, refname);\n \t\t\t\tgoto cleanup;\n \t\t\t}\n \ndiff --git a/refs/debug.c b/refs/debug.c\nindex 697adbd0dc3..67718bd1f49 100644\n--- a/refs/debug.c\n+++ b/refs/debug.c\n@@ -160,11 +160,9 @@ static int debug_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\ttrace_printf_key(&trace_refs, \"iterator_advance: (%d)\\n\", res);\n \telse\n \t\ttrace_printf_key(&trace_refs, \"iterator_advance: %s (0)\\n\",\n-\t\t\tditer->iter->refname);\n+\t\t\tditer->iter->ref.name);\n \n-\tditer->base.refname = diter->iter->refname;\n-\tditer->base.oid = diter->iter->oid;\n-\tditer->base.flags = diter->iter->flags;\n+\tditer->base.ref = diter->iter->ref;\n \treturn res;\n }\n \n@@ -185,7 +183,7 @@ static int debug_ref_iterator_peel(struct ref_iterator *ref_iterator,\n \tstruct debug_ref_iterator *diter =\n \t\t(struct debug_ref_iterator *)ref_iterator;\n \tint res = diter->iter->vtable->peel(diter->iter, peeled);\n-\ttrace_printf_key(&trace_refs, \"iterator_peel: %s: %d\\n\", diter->iter->refname, res);\n+\ttrace_printf_key(&trace_refs, \"iterator_peel: %s: %d\\n\", diter->iter->ref.name, res);\n \treturn res;\n }\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex eb3142f8f2d..fac53fa052d 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -961,26 +961,23 @@ static int files_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \n \twhile ((ok = ref_iterator_advance(iter->iter0)) == ITER_OK) {\n \t\tif (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY &&\n-\t\t    parse_worktree_ref(iter->iter0->refname, NULL, NULL,\n+\t\t    parse_worktree_ref(iter->iter0->ref.name, NULL, NULL,\n \t\t\t\t       NULL) != REF_WORKTREE_CURRENT)\n \t\t\tcontinue;\n \n \t\tif ((iter->flags & DO_FOR_EACH_OMIT_DANGLING_SYMREFS) &&\n-\t\t    (iter->iter0->flags & REF_ISSYMREF) &&\n-\t\t    (iter->iter0->flags & REF_ISBROKEN))\n+\t\t    (iter->iter0->ref.flags & REF_ISSYMREF) &&\n+\t\t    (iter->iter0->ref.flags & REF_ISBROKEN))\n \t\t\tcontinue;\n \n \t\tif (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&\n-\t\t    !ref_resolves_to_object(iter->iter0->refname,\n+\t\t    !ref_resolves_to_object(iter->iter0->ref.name,\n \t\t\t\t\t    iter->repo,\n-\t\t\t\t\t    iter->iter0->oid,\n-\t\t\t\t\t    iter->iter0->flags))\n+\t\t\t\t\t    iter->iter0->ref.oid,\n+\t\t\t\t\t    iter->iter0->ref.flags))\n \t\t\tcontinue;\n \n-\t\titer->base.refname = iter->iter0->refname;\n-\t\titer->base.oid = iter->iter0->oid;\n-\t\titer->base.flags = iter->iter0->flags;\n-\t\titer->base.referent = iter->iter0->referent;\n+\t\titer->base.ref = iter->iter0->ref;\n \n \t\treturn ITER_OK;\n \t}\n@@ -1367,30 +1364,29 @@ static void prune_refs(struct files_ref_store *refs, struct ref_to_prune **refs_\n  * Return true if the specified reference should be packed.\n  */\n static int should_pack_ref(struct files_ref_store *refs,\n-\t\t\t   const char *refname,\n-\t\t\t   const struct object_id *oid, unsigned int ref_flags,\n+\t\t\t   const struct reference *ref,\n \t\t\t   struct pack_refs_opts *opts)\n {\n \tstruct string_list_item *item;\n \n \t/* Do not pack per-worktree refs: */\n-\tif (parse_worktree_ref(refname, NULL, NULL, NULL) !=\n+\tif (parse_worktree_ref(ref->name, NULL, NULL, NULL) !=\n \t    REF_WORKTREE_SHARED)\n \t\treturn 0;\n \n \t/* Do not pack symbolic refs: */\n-\tif (ref_flags & REF_ISSYMREF)\n+\tif (ref->flags & REF_ISSYMREF)\n \t\treturn 0;\n \n \t/* Do not pack broken refs: */\n-\tif (!ref_resolves_to_object(refname, refs->base.repo, oid, ref_flags))\n+\tif (!ref_resolves_to_object(ref->name, refs->base.repo, ref->oid, ref->flags))\n \t\treturn 0;\n \n-\tif (ref_excluded(opts->exclusions, refname))\n+\tif (ref_excluded(opts->exclusions, ref->name))\n \t\treturn 0;\n \n \tfor_each_string_list_item(item, opts->includes)\n-\t\tif (!wildmatch(item->string, refname, 0))\n+\t\tif (!wildmatch(item->string, ref->name, 0))\n \t\t\treturn 1;\n \n \treturn 0;\n@@ -1443,8 +1439,7 @@ static int should_pack_refs(struct files_ref_store *refs,\n \titer = cache_ref_iterator_begin(get_loose_ref_cache(refs, 0), NULL,\n \t\t\t\t\trefs->base.repo, 0);\n \twhile ((ret = ref_iterator_advance(iter)) == ITER_OK) {\n-\t\tif (should_pack_ref(refs, iter->refname, iter->oid,\n-\t\t\t\t    iter->flags, opts))\n+\t\tif (should_pack_ref(refs, &iter->ref, opts))\n \t\t\trefcount++;\n \t\tif (refcount >= limit) {\n \t\t\tref_iterator_free(iter);\n@@ -1489,24 +1484,24 @@ static int files_pack_refs(struct ref_store *ref_store,\n \t\t * in the packed ref cache. If the reference should be\n \t\t * pruned, also add it to refs_to_prune.\n \t\t */\n-\t\tif (!should_pack_ref(refs, iter->refname, iter->oid, iter->flags, opts))\n+\t\tif (!should_pack_ref(refs, &iter->ref, opts))\n \t\t\tcontinue;\n \n \t\t/*\n \t\t * Add a reference creation for this reference to the\n \t\t * packed-refs transaction:\n \t\t */\n-\t\tif (ref_transaction_update(transaction, iter->refname,\n-\t\t\t\t\t   iter->oid, NULL, NULL, NULL,\n+\t\tif (ref_transaction_update(transaction, iter->ref.name,\n+\t\t\t\t\t   iter->ref.oid, NULL, NULL, NULL,\n \t\t\t\t\t   REF_NO_DEREF, NULL, &err))\n \t\t\tdie(\"failure preparing to create packed reference %s: %s\",\n-\t\t\t    iter->refname, err.buf);\n+\t\t\t    iter->ref.name, err.buf);\n \n \t\t/* Schedule the loose reference for pruning if requested. */\n \t\tif ((opts->flags & PACK_REFS_PRUNE)) {\n \t\t\tstruct ref_to_prune *n;\n-\t\t\tFLEX_ALLOC_STR(n, name, iter->refname);\n-\t\t\toidcpy(&n->oid, iter->oid);\n+\t\t\tFLEX_ALLOC_STR(n, name, iter->ref.name);\n+\t\t\toidcpy(&n->oid, iter->ref.oid);\n \t\t\tn->next = refs_to_prune;\n \t\t\trefs_to_prune = n;\n \t\t}\n@@ -2379,7 +2374,7 @@ static int files_reflog_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t\t\t\t REFNAME_ALLOW_ONELEVEL))\n \t\t\tcontinue;\n \n-\t\titer->base.refname = diter->relative_path;\n+\t\titer->base.ref.name = diter->relative_path;\n \t\treturn ITER_OK;\n \t}\n \ndiff --git a/refs/iterator.c b/refs/iterator.c\nindex 7f2e718f1c9..fe5980e1b6c 100644\n--- a/refs/iterator.c\n+++ b/refs/iterator.c\n@@ -41,10 +41,7 @@ void base_ref_iterator_init(struct ref_iterator *iter,\n \t\t\t    struct ref_iterator_vtable *vtable)\n {\n \titer->vtable = vtable;\n-\titer->refname = NULL;\n-\titer->referent = NULL;\n-\titer->oid = NULL;\n-\titer->flags = 0;\n+\tmemset(&iter->ref, 0, sizeof(iter->ref));\n }\n \n struct empty_ref_iterator {\n@@ -127,8 +124,8 @@ enum iterator_selection ref_iterator_select(struct ref_iterator *iter_worktree,\n \t\t * latter.\n \t\t */\n \t\tif (iter_worktree) {\n-\t\t\tint cmp = strcmp(iter_worktree->refname,\n-\t\t\t\t\t iter_common->refname);\n+\t\t\tint cmp = strcmp(iter_worktree->ref.name,\n+\t\t\t\t\t iter_common->ref.name);\n \t\t\tif (cmp < 0)\n \t\t\t\treturn ITER_SELECT_0;\n \t\t\telse if (!cmp)\n@@ -139,7 +136,7 @@ enum iterator_selection ref_iterator_select(struct ref_iterator *iter_worktree,\n \t\t  * We now know that the lexicographically-next ref is a common\n \t\t  * ref. When the common ref is a shared one we return it.\n \t\t  */\n-\t\tif (parse_worktree_ref(iter_common->refname, NULL, NULL,\n+\t\tif (parse_worktree_ref(iter_common->ref.name, NULL, NULL,\n \t\t\t\t       NULL) == REF_WORKTREE_SHARED)\n \t\t\treturn ITER_SELECT_1;\n \n@@ -212,10 +209,7 @@ static int merge_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t}\n \n \t\tif (selection & ITER_YIELD_CURRENT) {\n-\t\t\titer->base.referent = (*iter->current)->referent;\n-\t\t\titer->base.refname = (*iter->current)->refname;\n-\t\t\titer->base.oid = (*iter->current)->oid;\n-\t\t\titer->base.flags = (*iter->current)->flags;\n+\t\t\titer->base.ref = (*iter->current)->ref;\n \t\t\treturn ITER_OK;\n \t\t}\n \t}\n@@ -313,7 +307,7 @@ static enum iterator_selection overlay_iterator_select(\n \telse if (!front)\n \t\treturn ITER_SELECT_1;\n \n-\tcmp = strcmp(front->refname, back->refname);\n+\tcmp = strcmp(front->ref.name, back->ref.name);\n \n \tif (cmp < 0)\n \t\treturn ITER_SELECT_0;\n@@ -371,7 +365,7 @@ static int prefix_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \tint ok;\n \n \twhile ((ok = ref_iterator_advance(iter->iter0)) == ITER_OK) {\n-\t\tint cmp = compare_prefix(iter->iter0->refname, iter->prefix);\n+\t\tint cmp = compare_prefix(iter->iter0->ref.name, iter->prefix);\n \t\tif (cmp < 0)\n \t\t\tcontinue;\n \t\t/*\n@@ -382,6 +376,8 @@ static int prefix_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\tif (cmp > 0)\n \t\t\treturn ITER_DONE;\n \n+\t\titer->base.ref = iter->iter0->ref;\n+\n \t\tif (iter->trim) {\n \t\t\t/*\n \t\t\t * It is nonsense to trim off characters that\n@@ -392,15 +388,11 @@ static int prefix_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t\t * one character left in the refname after\n \t\t\t * trimming, report it as a bug:\n \t\t\t */\n-\t\t\tif (strlen(iter->iter0->refname) <= iter->trim)\n+\t\t\tif (strlen(iter->base.ref.name) <= iter->trim)\n \t\t\t\tBUG(\"attempt to trim too many characters\");\n-\t\t\titer->base.refname = iter->iter0->refname + iter->trim;\n-\t\t} else {\n-\t\t\titer->base.refname = iter->iter0->refname;\n+\t\t\titer->base.ref.name += iter->trim;\n \t\t}\n \n-\t\titer->base.oid = iter->iter0->oid;\n-\t\titer->base.flags = iter->iter0->flags;\n \t\treturn ITER_OK;\n \t}\n \n@@ -476,14 +468,7 @@ int do_for_each_ref_iterator(struct ref_iterator *iter,\n \n \tcurrent_ref_iter = iter;\n \twhile ((ok = ref_iterator_advance(iter)) == ITER_OK) {\n-\t\tstruct reference ref = {\n-\t\t\t.name = iter->refname,\n-\t\t\t.target = iter->referent,\n-\t\t\t.oid = iter->oid,\n-\t\t\t.flags = iter->flags,\n-\t\t};\n-\n-\t\tretval = fn(&ref, cb_data);\n+\t\tretval = fn(&iter->ref, cb_data);\n \t\tif (retval)\n \t\t\tgoto out;\n \t}\ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex a8c22a0a7ff..7987acdc96a 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -908,7 +908,7 @@ static int next_record(struct packed_ref_iterator *iter)\n \tif (iter->pos == iter->eof)\n \t\treturn ITER_DONE;\n \n-\titer->base.flags = REF_ISPACKED;\n+\titer->base.ref.flags = REF_ISPACKED;\n \tp = iter->pos;\n \n \tif (iter->eof - p < snapshot_hexsz(iter->snapshot) + 2 ||\n@@ -923,22 +923,22 @@ static int next_record(struct packed_ref_iterator *iter)\n \t\t\t\t      iter->pos, iter->eof - iter->pos);\n \n \tstrbuf_add(&iter->refname_buf, p, eol - p);\n-\titer->base.refname = iter->refname_buf.buf;\n+\titer->base.ref.name = iter->refname_buf.buf;\n \n \tif (refname_contains_nul(&iter->refname_buf))\n-\t\tdie(\"packed refname contains embedded NULL: %s\", iter->base.refname);\n+\t\tdie(\"packed refname contains embedded NULL: %s\", iter->base.ref.name);\n \n-\tif (check_refname_format(iter->base.refname, REFNAME_ALLOW_ONELEVEL)) {\n-\t\tif (!refname_is_safe(iter->base.refname))\n+\tif (check_refname_format(iter->base.ref.name, REFNAME_ALLOW_ONELEVEL)) {\n+\t\tif (!refname_is_safe(iter->base.ref.name))\n \t\t\tdie(\"packed refname is dangerous: %s\",\n-\t\t\t    iter->base.refname);\n+\t\t\t    iter->base.ref.name);\n \t\toidclr(&iter->oid, iter->repo->hash_algo);\n-\t\titer->base.flags |= REF_BAD_NAME | REF_ISBROKEN;\n+\t\titer->base.ref.flags |= REF_BAD_NAME | REF_ISBROKEN;\n \t}\n \tif (iter->snapshot->peeled == PEELED_FULLY ||\n \t    (iter->snapshot->peeled == PEELED_TAGS &&\n-\t     starts_with(iter->base.refname, \"refs/tags/\")))\n-\t\titer->base.flags |= REF_KNOWS_PEELED;\n+\t     starts_with(iter->base.ref.name, \"refs/tags/\")))\n+\t\titer->base.ref.flags |= REF_KNOWS_PEELED;\n \n \titer->pos = eol + 1;\n \n@@ -956,11 +956,11 @@ static int next_record(struct packed_ref_iterator *iter)\n \t\t * definitely know the value of *this* reference. But\n \t\t * we suppress it if the reference is broken:\n \t\t */\n-\t\tif ((iter->base.flags & REF_ISBROKEN)) {\n+\t\tif ((iter->base.ref.flags & REF_ISBROKEN)) {\n \t\t\toidclr(&iter->peeled, iter->repo->hash_algo);\n-\t\t\titer->base.flags &= ~REF_KNOWS_PEELED;\n+\t\t\titer->base.ref.flags &= ~REF_KNOWS_PEELED;\n \t\t} else {\n-\t\t\titer->base.flags |= REF_KNOWS_PEELED;\n+\t\t\titer->base.ref.flags |= REF_KNOWS_PEELED;\n \t\t}\n \t} else {\n \t\toidclr(&iter->peeled, iter->repo->hash_algo);\n@@ -976,15 +976,15 @@ static int packed_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \tint ok;\n \n \twhile ((ok = next_record(iter)) == ITER_OK) {\n-\t\tconst char *refname = iter->base.refname;\n+\t\tconst char *refname = iter->base.ref.name;\n \t\tconst char *prefix = iter->prefix;\n \n \t\tif (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY &&\n-\t\t    !is_per_worktree_ref(iter->base.refname))\n+\t\t    !is_per_worktree_ref(iter->base.ref.name))\n \t\t\tcontinue;\n \n \t\tif (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&\n-\t\t    !ref_resolves_to_object(iter->base.refname, iter->repo,\n+\t\t    !ref_resolves_to_object(iter->base.ref.name, iter->repo,\n \t\t\t\t\t    &iter->oid, iter->flags))\n \t\t\tcontinue;\n \n@@ -1033,10 +1033,10 @@ static int packed_ref_iterator_peel(struct ref_iterator *ref_iterator,\n \tstruct packed_ref_iterator *iter =\n \t\t(struct packed_ref_iterator *)ref_iterator;\n \n-\tif ((iter->base.flags & REF_KNOWS_PEELED)) {\n+\tif ((iter->base.ref.flags & REF_KNOWS_PEELED)) {\n \t\toidcpy(peeled, &iter->peeled);\n \t\treturn is_null_oid(&iter->peeled) ? -1 : 0;\n-\t} else if ((iter->base.flags & (REF_ISBROKEN | REF_ISSYMREF))) {\n+\t} else if ((iter->base.ref.flags & (REF_ISBROKEN | REF_ISSYMREF))) {\n \t\treturn -1;\n \t} else {\n \t\treturn peel_object(iter->repo, &iter->oid, peeled) ? -1 : 0;\n@@ -1194,7 +1194,7 @@ static struct ref_iterator *packed_ref_iterator_begin(\n \titer->snapshot = snapshot;\n \tacquire_snapshot(snapshot);\n \tstrbuf_init(&iter->refname_buf, 0);\n-\titer->base.oid = &iter->oid;\n+\titer->base.ref.oid = &iter->oid;\n \titer->repo = ref_store->repo;\n \titer->flags = flags;\n \n@@ -1436,7 +1436,7 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re\n \t\t\tif (!iter)\n \t\t\t\tcmp = +1;\n \t\t\telse\n-\t\t\t\tcmp = strcmp(iter->refname, update->refname);\n+\t\t\t\tcmp = strcmp(iter->ref.name, update->refname);\n \t\t}\n \n \t\tif (!cmp) {\n@@ -1459,11 +1459,11 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re\n \t\t\t\t\t}\n \n \t\t\t\t\tgoto error;\n-\t\t\t\t} else if (!oideq(&update->old_oid, iter->oid)) {\n+\t\t\t\t} else if (!oideq(&update->old_oid, iter->ref.oid)) {\n \t\t\t\t\tstrbuf_addf(err, \"cannot update ref '%s': \"\n \t\t\t\t\t\t    \"is at %s but expected %s\",\n \t\t\t\t\t\t    update->refname,\n-\t\t\t\t\t\t    oid_to_hex(iter->oid),\n+\t\t\t\t\t\t    oid_to_hex(iter->ref.oid),\n \t\t\t\t\t\t    oid_to_hex(&update->old_oid));\n \t\t\t\t\tret = REF_TRANSACTION_ERROR_INCORRECT_OLD_VALUE;\n \n@@ -1527,8 +1527,8 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re\n \t\t\tstruct object_id peeled;\n \t\t\tint peel_error = ref_iterator_peel(iter, &peeled);\n \n-\t\t\tif (write_packed_entry(out, iter->refname,\n-\t\t\t\t\t       iter->oid,\n+\t\t\tif (write_packed_entry(out, iter->ref.name,\n+\t\t\t\t\t       iter->ref.oid,\n \t\t\t\t\t       peel_error ? NULL : &peeled))\n \t\t\t\tgoto write_error;\n \ndiff --git a/refs/ref-cache.c b/refs/ref-cache.c\nindex e5e5df16d85..f1abc396241 100644\n--- a/refs/ref-cache.c\n+++ b/refs/ref-cache.c\n@@ -425,10 +425,10 @@ static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t\tlevel->prefix_state = entry_prefix_state;\n \t\t\tlevel->index = -1;\n \t\t} else {\n-\t\t\titer->base.refname = entry->name;\n-\t\t\titer->base.referent = entry->u.value.referent;\n-\t\t\titer->base.oid = &entry->u.value.oid;\n-\t\t\titer->base.flags = entry->flag;\n+\t\t\titer->base.ref.name = entry->name;\n+\t\t\titer->base.ref.target = entry->u.value.referent;\n+\t\t\titer->base.ref.oid = &entry->u.value.oid;\n+\t\t\titer->base.ref.flags = entry->flag;\n \t\t\treturn ITER_OK;\n \t\t}\n \t}\n@@ -550,7 +550,7 @@ static int cache_ref_iterator_peel(struct ref_iterator *ref_iterator,\n {\n \tstruct cache_ref_iterator *iter =\n \t\t(struct cache_ref_iterator *)ref_iterator;\n-\treturn peel_object(iter->repo, ref_iterator->oid, peeled) ? -1 : 0;\n+\treturn peel_object(iter->repo, ref_iterator->ref.oid, peeled) ? -1 : 0;\n }\n \n static void cache_ref_iterator_release(struct ref_iterator *ref_iterator)\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 4ef3bd75c6a..ed749d16572 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -249,10 +249,7 @@ const char *find_descendant_ref(const char *dirname,\n  */\n struct ref_iterator {\n \tstruct ref_iterator_vtable *vtable;\n-\tconst char *refname;\n-\tconst char *referent;\n-\tconst struct object_id *oid;\n-\tunsigned int flags;\n+\tstruct reference ref;\n };\n \n /*\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex d4b79286202..0e47986cb5b 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -704,10 +704,10 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t\t\t\t    &iter->oid, flags))\n \t\t\t\tcontinue;\n \n-\t\titer->base.refname = iter->ref.refname;\n-\t\titer->base.referent = referent;\n-\t\titer->base.oid = &iter->oid;\n-\t\titer->base.flags = flags;\n+\t\titer->base.ref.name = iter->ref.refname;\n+\t\titer->base.ref.target = referent;\n+\t\titer->base.ref.oid = &iter->oid;\n+\t\titer->base.ref.flags = flags;\n \n \t\tbreak;\n \t}\n@@ -828,7 +828,7 @@ static struct reftable_ref_iterator *ref_iterator_for_stack(struct reftable_ref_\n \n \titer = xcalloc(1, sizeof(*iter));\n \tbase_ref_iterator_init(&iter->base, &reftable_ref_iterator_vtable);\n-\titer->base.oid = &iter->oid;\n+\titer->base.ref.oid = &iter->oid;\n \titer->flags = flags;\n \titer->refs = refs;\n \titer->exclude_patterns = filter_exclude_patterns(exclude_patterns);\n@@ -2072,7 +2072,7 @@ static int reftable_reflog_iterator_advance(struct ref_iterator *ref_iterator)\n \n \t\tstrbuf_reset(&iter->last_name);\n \t\tstrbuf_addstr(&iter->last_name, iter->log.refname);\n-\t\titer->base.refname = iter->log.refname;\n+\t\titer->base.ref.name = iter->log.refname;\n \n \t\tbreak;\n \t}\n\n-- \n2.51.1.930.gacf6e81ea2.dirty\n\n"},{"id":"529471","messageId":"20251023-b4-pks-ref-filter-skip-parsing-objects-v4-3-2be68ce82c9a@pks.im","threadId":"64260","inReplyTo":"20251023-b4-pks-ref-filter-skip-parsing-objects-v4-0-2be68ce82c9a@pks.im","subject":"[PATCH v4 03/14] refs: fully reset `struct ref_iterator::ref` on iteration","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-23T07:16:12Z","receivedAt":"2025-10-23T07:16:26Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"With the introduction of the `struct ref_iterator::ref` field it now is\na whole lot easier to introduce new fields that become accessible to the\ncaller without having to adapt every single callsite. But there's a\ndownside: when a new field is introduced we always have to adapt all\nbackends to set that field.\n\nThis isn't something we can avoid in the general case: when the new\nfield is expected to be populated by all backends we of course cannot\navoid doing so. But new fields may be entirely optional, in which case\nwe'd still have such churn. And furthermore, it is very easy right now\nto leak state from a previous iteration into the next iteration.\n\nAddress this issue by ensuring that the reference backends all fully\nreset the field on every single iteration. This ensures that no state\nfrom previous iterations can leak into the next one. And it ensures that\nany newly introduced fields will be zeroed out by default.\n\nNote that we don't have to explicitly adapt the \"files\" backend, as it\nuses the `cache_ref_iterator` internally. Furthermore, other \"wrapping\"\niterators like for example the `prefix_ref_iterator` copy around the\nwhole reference, so these don't need to be adapted either.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs/packed-backend.c   | 3 ++-\n refs/ref-cache.c        | 1 +\n refs/reftable-backend.c | 1 +\n 3 files changed, 4 insertions(+), 1 deletion(-)\n\ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 7987acdc96a..711e07f8326 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -882,6 +882,7 @@ static int next_record(struct packed_ref_iterator *iter)\n {\n \tconst char *p, *eol;\n \n+\tmemset(&iter->base.ref, 0, sizeof(iter->base.ref));\n \tstrbuf_reset(&iter->refname_buf);\n \n \t/*\n@@ -916,6 +917,7 @@ static int next_record(struct packed_ref_iterator *iter)\n \t    !isspace(*p++))\n \t\tdie_invalid_line(iter->snapshot->refs->path,\n \t\t\t\t iter->pos, iter->eof - iter->pos);\n+\titer->base.ref.oid = &iter->oid;\n \n \teol = memchr(p, '\\n', iter->eof - p);\n \tif (!eol)\n@@ -1194,7 +1196,6 @@ static struct ref_iterator *packed_ref_iterator_begin(\n \titer->snapshot = snapshot;\n \tacquire_snapshot(snapshot);\n \tstrbuf_init(&iter->refname_buf, 0);\n-\titer->base.ref.oid = &iter->oid;\n \titer->repo = ref_store->repo;\n \titer->flags = flags;\n \ndiff --git a/refs/ref-cache.c b/refs/ref-cache.c\nindex f1abc396241..e427848879d 100644\n--- a/refs/ref-cache.c\n+++ b/refs/ref-cache.c\n@@ -425,6 +425,7 @@ static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t\tlevel->prefix_state = entry_prefix_state;\n \t\t\tlevel->index = -1;\n \t\t} else {\n+\t\t\tmemset(&iter->base.ref, 0, sizeof(iter->base.ref));\n \t\t\titer->base.ref.name = entry->name;\n \t\t\titer->base.ref.target = entry->u.value.referent;\n \t\t\titer->base.ref.oid = &entry->u.value.oid;\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 0e47986cb5b..728886eafd3 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -704,6 +704,7 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\t\t\t\t    &iter->oid, flags))\n \t\t\t\tcontinue;\n \n+\t\tmemset(&iter->base.ref, 0, sizeof(iter->base.ref));\n \t\titer->base.ref.name = iter->ref.refname;\n \t\titer->base.ref.target = referent;\n \t\titer->base.ref.oid = &iter->oid;\n\n-- \n2.51.1.930.gacf6e81ea2.dirty\n\n"},{"id":"529472","messageId":"20251023-b4-pks-ref-filter-skip-parsing-objects-v4-4-2be68ce82c9a@pks.im","threadId":"64260","inReplyTo":"20251023-b4-pks-ref-filter-skip-parsing-objects-v4-0-2be68ce82c9a@pks.im","subject":"[PATCH v4 04/14] refs: refactor reference status flags","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-23T07:16:13Z","receivedAt":"2025-10-23T07:16:29Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The reference flags encode information like whether or not a reference\nis a symbolic reference or whether it may be broken. This information is\nstored in a `int flags` bitfield, which is in conflict with our modern\nbest practices; we tend to use an unsigned integer to store flags.\n\nChange the type of the field to be `unsigned`. While at it, refactor the\nindividual flags to be part of an `enum` instead of using preprocessor\ndefines.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.h | 41 +++++++++++++++++++++--------------------\n 1 file changed, 21 insertions(+), 20 deletions(-)\n\ndiff --git a/refs.h b/refs.h\nindex 68d235438c2..4f0a685714f 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -333,27 +333,28 @@ struct ref_transaction;\n  * stored in ref_iterator::flags. Other bits are for internal use\n  * only:\n  */\n+enum reference_status {\n+\t/* Reference is a symbolic reference. */\n+\tREF_ISSYMREF = (1 << 0),\n \n-/* Reference is a symbolic reference. */\n-#define REF_ISSYMREF 0x01\n+\t/* Reference is a packed reference. */\n+\tREF_ISPACKED = (1 << 1),\n \n-/* Reference is a packed reference. */\n-#define REF_ISPACKED 0x02\n-\n-/*\n- * Reference cannot be resolved to an object name: dangling symbolic\n- * reference (directly or indirectly), corrupt reference file,\n- * reference exists but name is bad, or symbolic reference refers to\n- * ill-formatted reference name.\n- */\n-#define REF_ISBROKEN 0x04\n+\t/*\n+\t * Reference cannot be resolved to an object name: dangling symbolic\n+\t * reference (directly or indirectly), corrupt reference file,\n+\t * reference exists but name is bad, or symbolic reference refers to\n+\t * ill-formatted reference name.\n+\t */\n+\tREF_ISBROKEN = (1 << 2),\n \n-/*\n- * Reference name is not well formed.\n- *\n- * See git-check-ref-format(1) for the definition of well formed ref names.\n- */\n-#define REF_BAD_NAME 0x08\n+\t/*\n+\t * Reference name is not well formed.\n+\t *\n+\t * See git-check-ref-format(1) for the definition of well formed ref names.\n+\t */\n+\tREF_BAD_NAME = (1 << 3),\n+};\n \n /* A reference passed to `for_each_ref()`-style callbacks. */\n struct reference {\n@@ -370,8 +371,8 @@ struct reference {\n \t */\n \tconst struct object_id *oid;\n \n-\t/* A bitfield of `REF_` flags. */\n-\tint flags;\n+\t/* A bitfield of `enum reference_status` flags. */\n+\tunsigned flags;\n };\n \n /*\n\n-- \n2.51.1.930.gacf6e81ea2.dirty\n\n"},{"id":"529473","messageId":"20251023-b4-pks-ref-filter-skip-parsing-objects-v4-5-2be68ce82c9a@pks.im","threadId":"64260","inReplyTo":"20251023-b4-pks-ref-filter-skip-parsing-objects-v4-0-2be68ce82c9a@pks.im","subject":"[PATCH v4 05/14] refs: expose peeled object ID via the iterator","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-23T07:16:14Z","receivedAt":"2025-10-23T07:16:33Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Both the \"files\" and \"reftable\" backend are able to store peeled values\nfor tags in the respective formats. This allows for a more efficient\nlookup of the target object of such a tag without having to manually\npeel via the object database.\n\nThe infrastructure to access these peeled object IDs is somewhat funky\nthough. When iterating through objects, we store a pointer reference to\nthe current iterator in a global variable. The callbacks invoked by that\niterator are then expected to call `peel_iterated_oid()`, which checks\nwhether the globally-stored iterator's current reference refers to the\none handed into that function. If so, we ask the iterator to peel the\nobject, otherwise we manually peel the object via the object database.\nDepending on global state like this is somewhat weird and also quite\nfragile.\n\nIntroduce a new `struct reference::peeled_oid` field that can be\npopulated by the reference backends. This field can be accessed via a\nnew function `reference_get_peeled_oid()` that either uses that value,\nif set, or alternatively peels via the ODB. With this change we don't\nhave to rely on global state anymore, but make the peeled object ID\navailable to the callback functions directly.\n\nAdjust trivial callers that already have a `struct reference` available.\nRemaining callers will be adjusted in subsequent commits.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/describe.c      |  2 +-\n builtin/gc.c            |  2 +-\n builtin/pack-objects.c  |  7 ++++---\n commit-graph.c          |  2 +-\n ls-refs.c               |  2 +-\n midx-write.c            |  2 +-\n pseudo-merge.c          |  2 +-\n refs.c                  | 12 ++++++++++++\n refs.h                  | 19 +++++++++++++++++++\n refs/packed-backend.c   |  1 +\n refs/reftable-backend.c |  5 +++++\n repack-midx.c           |  2 +-\n 12 files changed, 48 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/describe.c b/builtin/describe.c\nindex 79545350443..443546aaac9 100644\n--- a/builtin/describe.c\n+++ b/builtin/describe.c\n@@ -208,7 +208,7 @@ static int get_name(const struct reference *ref, void *cb_data UNUSED)\n \t}\n \n \t/* Is it annotated? */\n-\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled)) {\n+\tif (!reference_get_peeled_oid(the_repository, ref, &peeled)) {\n \t\tis_annotated = !oideq(ref->oid, &peeled);\n \t} else {\n \t\toidcpy(&peeled, ref->oid);\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 9de5de175f6..f0cf20d4238 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -1109,7 +1109,7 @@ static int dfs_on_ref(const struct reference *ref, void *cb_data)\n \tstruct commit_list *stack = NULL;\n \tstruct commit *commit;\n \n-\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n+\tif (!reference_get_peeled_oid(the_repository, ref, &peeled))\n \t\tmaybe_peeled = &peeled;\n \tif (odb_read_object_info(the_repository->objects, maybe_peeled, NULL) != OBJ_COMMIT)\n \t\treturn 0;\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 39633a0158e..1613fecb669 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -838,7 +838,7 @@ static int mark_tagged(const struct reference *ref, void *cb_data UNUSED)\n \n \tif (entry)\n \t\tentry->tagged = 1;\n-\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled)) {\n+\tif (!reference_get_peeled_oid(the_repository, ref, &peeled)) {\n \t\tentry = packlist_find(&to_pack, &peeled);\n \t\tif (entry)\n \t\t\tentry->tagged = 1;\n@@ -3309,7 +3309,8 @@ static int add_ref_tag(const struct reference *ref, void *cb_data UNUSED)\n {\n \tstruct object_id peeled;\n \n-\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled) && obj_is_packed(&peeled))\n+\tif (!reference_get_peeled_oid(the_repository, ref, &peeled) &&\n+\t    obj_is_packed(&peeled))\n \t\tadd_tag_chain(ref->oid);\n \treturn 0;\n }\n@@ -4537,7 +4538,7 @@ static int mark_bitmap_preferred_tip(const struct reference *ref, void *data UNU\n \tstruct object_id peeled;\n \tstruct object *object;\n \n-\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n+\tif (!reference_get_peeled_oid(the_repository, ref, &peeled))\n \t\tmaybe_peeled = &peeled;\n \n \tobject = parse_object_or_die(the_repository, maybe_peeled, ref->name);\ndiff --git a/commit-graph.c b/commit-graph.c\nindex f91af416259..80be2ff2c39 100644\n--- a/commit-graph.c\n+++ b/commit-graph.c\n@@ -1857,7 +1857,7 @@ static int add_ref_to_set(const struct reference *ref, void *cb_data)\n \tstruct object_id peeled;\n \tstruct refs_cb_data *data = (struct refs_cb_data *)cb_data;\n \n-\tif (!peel_iterated_oid(data->repo, ref->oid, &peeled))\n+\tif (!reference_get_peeled_oid(data->repo, ref, &peeled))\n \t\tmaybe_peeled = &peeled;\n \tif (odb_read_object_info(data->repo->objects, maybe_peeled, NULL) == OBJ_COMMIT)\n \t\toidset_insert(data->commits, maybe_peeled);\ndiff --git a/ls-refs.c b/ls-refs.c\nindex 64d02723691..8641281b86c 100644\n--- a/ls-refs.c\n+++ b/ls-refs.c\n@@ -110,7 +110,7 @@ static int send_ref(const struct reference *ref, void *cb_data)\n \n \tif (data->peel && ref->oid) {\n \t\tstruct object_id peeled;\n-\t\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n+\t\tif (!reference_get_peeled_oid(the_repository, ref, &peeled))\n \t\t\tstrbuf_addf(&data->buf, \" peeled:%s\", oid_to_hex(&peeled));\n \t}\n \ndiff --git a/midx-write.c b/midx-write.c\nindex f4dd875747a..23e61cb0001 100644\n--- a/midx-write.c\n+++ b/midx-write.c\n@@ -709,7 +709,7 @@ static int add_ref_to_pending(const struct reference *ref, void *cb_data)\n \t\treturn 0;\n \t}\n \n-\tif (!peel_iterated_oid(revs->repo, ref->oid, &peeled))\n+\tif (!reference_get_peeled_oid(revs->repo, ref, &peeled))\n \t\tmaybe_peeled = &peeled;\n \n \tobject = parse_object_or_die(revs->repo, maybe_peeled, ref->name);\ndiff --git a/pseudo-merge.c b/pseudo-merge.c\nindex 0abd51b42c1..a2d5bd85f95 100644\n--- a/pseudo-merge.c\n+++ b/pseudo-merge.c\n@@ -230,7 +230,7 @@ static int find_pseudo_merge_group_for_ref(const struct reference *ref, void *_d\n \tuint32_t i;\n \tint has_bitmap;\n \n-\tif (!peel_iterated_oid(the_repository, ref->oid, &peeled))\n+\tif (!reference_get_peeled_oid(the_repository, ref, &peeled))\n \t\tmaybe_peeled = &peeled;\n \n \tc = lookup_commit(the_repository, maybe_peeled);\ndiff --git a/refs.c b/refs.c\nindex f96cf43b128..1b1551f9814 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2334,6 +2334,18 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct\n \treturn peel_object(r, base, peeled) ? -1 : 0;\n }\n \n+int reference_get_peeled_oid(struct repository *repo,\n+\t\t\t     const struct reference *ref,\n+\t\t\t     struct object_id *peeled_oid)\n+{\n+\tif (ref->peeled_oid) {\n+\t\toidcpy(peeled_oid, ref->peeled_oid);\n+\t\treturn 0;\n+\t}\n+\n+\treturn peel_object(repo, ref->oid, peeled_oid) ? -1 : 0;\n+}\n+\n int refs_update_symref(struct ref_store *refs, const char *ref,\n \t\t       const char *target, const char *logmsg)\n {\ndiff --git a/refs.h b/refs.h\nindex 4f0a685714f..886ed2c0f43 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -371,10 +371,29 @@ struct reference {\n \t */\n \tconst struct object_id *oid;\n \n+\t/*\n+\t * An optional peeled object ID. This field _may_ be set for tags in\n+\t * case the peeled value is present in the backend. Please refer to\n+\t * `reference_get_peeled_oid()`.\n+\t */\n+\tconst struct object_id *peeled_oid;\n+\n \t/* A bitfield of `enum reference_status` flags. */\n \tunsigned flags;\n };\n \n+/*\n+ * Peel the tag to a non-tag commit. If present, this uses the peeled object ID\n+ * exposed by the reference backend. Otherwise, the object is peeled via the\n+ * object database, which is less efficient.\n+ *\n+ * Return `0` if the reference could be peeled, a negative error code\n+ * otherwise.\n+ */\n+int reference_get_peeled_oid(struct repository *repo,\n+\t\t\t     const struct reference *ref,\n+\t\t\t     struct object_id *peeled_oid);\n+\n /*\n  * The signature for the callback function for the for_each_*()\n  * functions below.  The memory pointed to by the `struct reference`\ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 711e07f8326..1fefefd54ed 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -963,6 +963,7 @@ static int next_record(struct packed_ref_iterator *iter)\n \t\t\titer->base.ref.flags &= ~REF_KNOWS_PEELED;\n \t\t} else {\n \t\t\titer->base.ref.flags |= REF_KNOWS_PEELED;\n+\t\t\titer->base.ref.peeled_oid = &iter->peeled;\n \t\t}\n \t} else {\n \t\toidclr(&iter->peeled, iter->repo->hash_algo);\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 728886eafd3..e214e120d77 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -547,6 +547,7 @@ struct reftable_ref_iterator {\n \tstruct reftable_iterator iter;\n \tstruct reftable_ref_record ref;\n \tstruct object_id oid;\n+\tstruct object_id peeled_oid;\n \n \tchar *prefix;\n \tsize_t prefix_len;\n@@ -671,6 +672,8 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\tcase REFTABLE_REF_VAL2:\n \t\t\toidread(&iter->oid, iter->ref.value.val2.value,\n \t\t\t\trefs->base.repo->hash_algo);\n+\t\t\toidread(&iter->peeled_oid, iter->ref.value.val2.target_value,\n+\t\t\t\trefs->base.repo->hash_algo);\n \t\t\tbreak;\n \t\tcase REFTABLE_REF_SYMREF:\n \t\t\treferent = refs_resolve_ref_unsafe(&iter->refs->base,\n@@ -708,6 +711,8 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)\n \t\titer->base.ref.name = iter->ref.refname;\n \t\titer->base.ref.target = referent;\n \t\titer->base.ref.oid = &iter->oid;\n+\t\tif (iter->ref.value_type == REFTABLE_REF_VAL2)\n+\t\t\titer->base.ref.peeled_oid = &iter->peeled_oid;\n \t\titer->base.ref.flags = flags;\n \n \t\tbreak;\ndiff --git a/repack-midx.c b/repack-midx.c\nindex 349f7e20b53..74bdfa3a6e9 100644\n--- a/repack-midx.c\n+++ b/repack-midx.c\n@@ -22,7 +22,7 @@ static int midx_snapshot_ref_one(const struct reference *ref, void *_data)\n \tconst struct object_id *maybe_peeled = ref->oid;\n \tstruct object_id peeled;\n \n-\tif (!peel_iterated_oid(data->repo, ref->oid, &peeled))\n+\tif (!reference_get_peeled_oid(data->repo, ref, &peeled))\n \t\tmaybe_peeled = &peeled;\n \n \tif (oidset_insert(&data->seen, maybe_peeled))\n\n-- \n2.51.1.930.gacf6e81ea2.dirty\n\n"},{"id":"529474","messageId":"20251023-b4-pks-ref-filter-skip-parsing-objects-v4-6-2be68ce82c9a@pks.im","threadId":"64260","inReplyTo":"20251023-b4-pks-ref-filter-skip-parsing-objects-v4-0-2be68ce82c9a@pks.im","subject":"[PATCH v4 06/14] upload-pack: convert to use `reference_get_peeled_oid()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-23T07:16:15Z","receivedAt":"2025-10-23T07:16:36Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `write_v0_ref()` callback is invoked from two callsites:\n\n  - Once via `send_ref()` which is a callback passed to\n    `for_each_namespaced_ref_1()` and `refs_head_ref_namespaced()`.\n\n  - Once manually to announce capabilities.\n\nWhen sending references to the client we also send the peeled value of\ntags. As we don't have a `struct reference` available in the second\ncase, we cannot easily peel by calling `reference_get_peeled_oid()`, but\nwe instead have to depend on on global state via `peel_iterated_oid()`.\n\nWe do have a reference available though in the first case, it's only the\nsecond case that keeps us from using `reference_get_peeled_oid()`. But\nthat second case only announces capabilities anyway, so we're not really\nhandling a reference at all here.\n\nAdapt that case to construct a reference manually and pass that to\n`write_v0_ref()`. Start to use `reference_get_peeled_oid()` now that we\nalways have a `struct reference` available.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n upload-pack.c | 22 +++++++++++++---------\n 1 file changed, 13 insertions(+), 9 deletions(-)\n\ndiff --git a/upload-pack.c b/upload-pack.c\nindex 0d563ae74e9..2d2b70cbf2d 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -1249,15 +1249,15 @@ static void format_session_id(struct strbuf *buf, struct upload_pack_data *d) {\n }\n \n static void write_v0_ref(struct upload_pack_data *data,\n-\t\t\tconst char *refname, const char *refname_nons,\n-\t\t\tconst struct object_id *oid)\n+\t\t\t const struct reference *ref,\n+\t\t\t const char *refname_nons)\n {\n \tstatic const char *capabilities = \"multi_ack thin-pack side-band\"\n \t\t\" side-band-64k ofs-delta shallow deepen-since deepen-not\"\n \t\t\" deepen-relative no-progress include-tag multi_ack_detailed\";\n \tstruct object_id peeled;\n \n-\tif (mark_our_ref(refname_nons, refname, oid, &data->hidden_refs))\n+\tif (mark_our_ref(refname_nons, ref->name, ref->oid, &data->hidden_refs))\n \t\treturn;\n \n \tif (capabilities) {\n@@ -1267,7 +1267,7 @@ static void write_v0_ref(struct upload_pack_data *data,\n \t\tformat_symref_info(&symref_info, &data->symref);\n \t\tformat_session_id(&session_id, data);\n \t\tpacket_fwrite_fmt(stdout, \"%s %s%c%s%s%s%s%s%s%s object-format=%s agent=%s\\n\",\n-\t\t\t     oid_to_hex(oid), refname_nons,\n+\t\t\t     oid_to_hex(ref->oid), refname_nons,\n \t\t\t     0, capabilities,\n \t\t\t     (data->allow_uor & ALLOW_TIP_SHA1) ?\n \t\t\t\t     \" allow-tip-sha1-in-want\" : \"\",\n@@ -1283,17 +1283,17 @@ static void write_v0_ref(struct upload_pack_data *data,\n \t\tstrbuf_release(&session_id);\n \t\tdata->sent_capabilities = 1;\n \t} else {\n-\t\tpacket_fwrite_fmt(stdout, \"%s %s\\n\", oid_to_hex(oid), refname_nons);\n+\t\tpacket_fwrite_fmt(stdout, \"%s %s\\n\", oid_to_hex(ref->oid), refname_nons);\n \t}\n \tcapabilities = NULL;\n-\tif (!peel_iterated_oid(the_repository, oid, &peeled))\n+\tif (!reference_get_peeled_oid(the_repository, ref, &peeled))\n \t\tpacket_fwrite_fmt(stdout, \"%s %s^{}\\n\", oid_to_hex(&peeled), refname_nons);\n \treturn;\n }\n \n static int send_ref(const struct reference *ref, void *cb_data)\n {\n-\twrite_v0_ref(cb_data, ref->name, strip_namespace(ref->name), ref->oid);\n+\twrite_v0_ref(cb_data, ref, strip_namespace(ref->name));\n \treturn 0;\n }\n \n@@ -1442,8 +1442,12 @@ void upload_pack(const int advertise_refs, const int stateless_rpc,\n \t\t\t\t\t send_ref, &data);\n \t\tfor_each_namespaced_ref_1(send_ref, &data);\n \t\tif (!data.sent_capabilities) {\n-\t\t\tconst char *refname = \"capabilities^{}\";\n-\t\t\twrite_v0_ref(&data, refname, refname, null_oid(the_hash_algo));\n+\t\t\tstruct reference ref = {\n+\t\t\t\t.name = \"capabilities^{}\",\n+\t\t\t\t.oid = null_oid(the_hash_algo),\n+\t\t\t};\n+\n+\t\t\twrite_v0_ref(&data, &ref, ref.name);\n \t\t}\n \t\t/*\n \t\t * fflush stdout before calling advertise_shallow_grafts because send_ref\n\n-- \n2.51.1.930.gacf6e81ea2.dirty\n\n"},{"id":"529475","messageId":"20251023-b4-pks-ref-filter-skip-parsing-objects-v4-7-2be68ce82c9a@pks.im","threadId":"64260","inReplyTo":"20251023-b4-pks-ref-filter-skip-parsing-objects-v4-0-2be68ce82c9a@pks.im","subject":"[PATCH v4 07/14] ref-filter: propagate peeled object ID","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-23T07:16:16Z","receivedAt":"2025-10-23T07:16:40Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When queueing a reference in the \"ref-filter\" subsystem we end up\ncreating a new ref array item that contains the reference's info. One\nbit of info that we always discard though is the peeled object ID, and\nbecause of that we are forced to use `peel_iterated_oid()`.\n\nRefactor the code to propagate the peeled object ID via the ref array,\nif available. This allows us to manually peel tags without having to go\nthrough the object database.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/ls-remote.c  |  2 +-\n builtin/tag.c        |  2 +-\n builtin/verify-tag.c |  2 +-\n ref-filter.c         | 66 ++++++++++++++++++++++++++++++----------------------\n ref-filter.h         |  5 +++-\n 5 files changed, 45 insertions(+), 32 deletions(-)\n\ndiff --git a/builtin/ls-remote.c b/builtin/ls-remote.c\nindex df09000b30d..fe77829557f 100644\n--- a/builtin/ls-remote.c\n+++ b/builtin/ls-remote.c\n@@ -156,7 +156,7 @@ int cmd_ls_remote(int argc,\n \t\t\tcontinue;\n \t\tif (!tail_match(&pattern, ref->name))\n \t\t\tcontinue;\n-\t\titem = ref_array_push(&ref_array, ref->name, &ref->old_oid);\n+\t\titem = ref_array_push(&ref_array, ref->name, &ref->old_oid, NULL);\n \t\titem->symref = xstrdup_or_null(ref->symref);\n \t}\n \ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex f0665af3acd..01eba90c5c7 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -153,7 +153,7 @@ static int verify_tag(const char *name, const char *ref UNUSED,\n \t\treturn -1;\n \n \tif (format->format)\n-\t\tpretty_print_ref(name, oid, format);\n+\t\tpretty_print_ref(name, oid, NULL, format);\n \n \treturn 0;\n }\ndiff --git a/builtin/verify-tag.c b/builtin/verify-tag.c\nindex cd6bc11095d..558121eaa16 100644\n--- a/builtin/verify-tag.c\n+++ b/builtin/verify-tag.c\n@@ -67,7 +67,7 @@ int cmd_verify_tag(int argc,\n \t\t}\n \n \t\tif (format.format)\n-\t\t\tpretty_print_ref(name, &oid, &format);\n+\t\t\tpretty_print_ref(name, &oid, NULL, &format);\n \t}\n \treturn had_error;\n }\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 6837fa60a9b..7fd8babec8f 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -2578,8 +2578,15 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)\n \t * If it is a tag object, see if we use the peeled value. If we do,\n \t * grab the peeled OID.\n \t */\n-\tif (need_tagged && peel_iterated_oid(the_repository, &obj->oid, &oi_deref.oid))\n-\t\tdie(\"bad tag\");\n+\tif (need_tagged) {\n+\t\tif (!is_null_oid(&ref->peeled_oid)) {\n+\t\t\toidcpy(&oi_deref.oid, &ref->peeled_oid);\n+\t\t} else if (!peel_object(the_repository, &obj->oid, &oi_deref.oid)) {\n+\t\t\t/* We managed to peel the object ourselves. */\n+\t\t} else {\n+\t\t\tdie(\"bad tag\");\n+\t\t}\n+\t}\n \n \treturn get_object(ref, 1, &obj, &oi_deref, err);\n }\n@@ -2807,12 +2814,15 @@ static int match_points_at(struct oid_array *points_at,\n  * Callers can then fill in other struct members at their leisure.\n  */\n static struct ref_array_item *new_ref_array_item(const char *refname,\n-\t\t\t\t\t\t const struct object_id *oid)\n+\t\t\t\t\t\t const struct object_id *oid,\n+\t\t\t\t\t\t const struct object_id *peeled_oid)\n {\n \tstruct ref_array_item *ref;\n \n \tFLEX_ALLOC_STR(ref, refname, refname);\n \toidcpy(&ref->objectname, oid);\n+\tif (peeled_oid)\n+\t\toidcpy(&ref->peeled_oid, peeled_oid);\n \tref->rest = NULL;\n \n \treturn ref;\n@@ -2826,9 +2836,10 @@ static void ref_array_append(struct ref_array *array, struct ref_array_item *ref\n \n struct ref_array_item *ref_array_push(struct ref_array *array,\n \t\t\t\t      const char *refname,\n-\t\t\t\t      const struct object_id *oid)\n+\t\t\t\t      const struct object_id *oid,\n+\t\t\t\t      const struct object_id *peeled_oid)\n {\n-\tstruct ref_array_item *ref = new_ref_array_item(refname, oid);\n+\tstruct ref_array_item *ref = new_ref_array_item(refname, oid, peeled_oid);\n \tref_array_append(array, ref);\n \treturn ref;\n }\n@@ -2871,25 +2882,25 @@ static int filter_ref_kind(struct ref_filter *filter, const char *refname)\n \treturn ref_kind_from_refname(refname);\n }\n \n-static struct ref_array_item *apply_ref_filter(const char *refname, const char *referent, const struct object_id *oid,\n-\t\t\t    int flag, struct ref_filter *filter)\n+static struct ref_array_item *apply_ref_filter(const struct reference *ref,\n+\t\t\t\t\t       struct ref_filter *filter)\n {\n-\tstruct ref_array_item *ref;\n+\tstruct ref_array_item *item;\n \tstruct commit *commit = NULL;\n \tunsigned int kind;\n \n-\tif (flag & REF_BAD_NAME) {\n-\t\twarning(_(\"ignoring ref with broken name %s\"), refname);\n+\tif (ref->flags & REF_BAD_NAME) {\n+\t\twarning(_(\"ignoring ref with broken name %s\"), ref->name);\n \t\treturn NULL;\n \t}\n \n-\tif (flag & REF_ISBROKEN) {\n-\t\twarning(_(\"ignoring broken ref %s\"), refname);\n+\tif (ref->flags & REF_ISBROKEN) {\n+\t\twarning(_(\"ignoring broken ref %s\"), ref->name);\n \t\treturn NULL;\n \t}\n \n \t/* Obtain the current ref kind from filter_ref_kind() and ignore unwanted refs. */\n-\tkind = filter_ref_kind(filter, refname);\n+\tkind = filter_ref_kind(filter, ref->name);\n \n \t/*\n \t * Generally HEAD refs are printed with special description denoting a rebase,\n@@ -2902,13 +2913,13 @@ static struct ref_array_item *apply_ref_filter(const char *refname, const char *\n \telse if (!(kind & filter->kind))\n \t\treturn NULL;\n \n-\tif (!filter_pattern_match(filter, refname))\n+\tif (!filter_pattern_match(filter, ref->name))\n \t\treturn NULL;\n \n-\tif (filter_exclude_match(filter, refname))\n+\tif (filter_exclude_match(filter, ref->name))\n \t\treturn NULL;\n \n-\tif (filter->points_at.nr && !match_points_at(&filter->points_at, oid, refname))\n+\tif (filter->points_at.nr && !match_points_at(&filter->points_at, ref->oid, ref->name))\n \t\treturn NULL;\n \n \t/*\n@@ -2918,7 +2929,7 @@ static struct ref_array_item *apply_ref_filter(const char *refname, const char *\n \t */\n \tif (filter->reachable_from || filter->unreachable_from ||\n \t    filter->with_commit || filter->no_commit || filter->verbose) {\n-\t\tcommit = lookup_commit_reference_gently(the_repository, oid, 1);\n+\t\tcommit = lookup_commit_reference_gently(the_repository, ref->oid, 1);\n \t\tif (!commit)\n \t\t\treturn NULL;\n \t\t/* We perform the filtering for the '--contains' option... */\n@@ -2936,13 +2947,13 @@ static struct ref_array_item *apply_ref_filter(const char *refname, const char *\n \t * to do its job and the resulting list may yet to be pruned\n \t * by maxcount logic.\n \t */\n-\tref = new_ref_array_item(refname, oid);\n-\tref->commit = commit;\n-\tref->flag = flag;\n-\tref->kind = kind;\n-\tref->symref = xstrdup_or_null(referent);\n+\titem = new_ref_array_item(ref->name, ref->oid, ref->peeled_oid);\n+\titem->commit = commit;\n+\titem->flag = ref->flags;\n+\titem->kind = kind;\n+\titem->symref = xstrdup_or_null(ref->target);\n \n-\treturn ref;\n+\treturn item;\n }\n \n struct ref_filter_cbdata {\n@@ -2959,8 +2970,7 @@ static int filter_one(const struct reference *ref, void *cb_data)\n \tstruct ref_filter_cbdata *ref_cbdata = cb_data;\n \tstruct ref_array_item *item;\n \n-\titem = apply_ref_filter(ref->name, ref->target, ref->oid,\n-\t\t\t\tref->flags, ref_cbdata->filter);\n+\titem = apply_ref_filter(ref, ref_cbdata->filter);\n \tif (item)\n \t\tref_array_append(ref_cbdata->array, item);\n \n@@ -2997,8 +3007,7 @@ static int filter_and_format_one(const struct reference *ref, void *cb_data)\n \tstruct ref_array_item *item;\n \tstruct strbuf output = STRBUF_INIT, err = STRBUF_INIT;\n \n-\titem = apply_ref_filter(ref->name, ref->target, ref->oid,\n-\t\t\t\tref->flags, ref_cbdata->filter);\n+\titem = apply_ref_filter(ref, ref_cbdata->filter);\n \tif (!item)\n \t\treturn 0;\n \n@@ -3585,13 +3594,14 @@ void print_formatted_ref_array(struct ref_array *array, struct ref_format *forma\n }\n \n void pretty_print_ref(const char *name, const struct object_id *oid,\n+\t\t      const struct object_id *peeled_oid,\n \t\t      struct ref_format *format)\n {\n \tstruct ref_array_item *ref_item;\n \tstruct strbuf output = STRBUF_INIT;\n \tstruct strbuf err = STRBUF_INIT;\n \n-\tref_item = new_ref_array_item(name, oid);\n+\tref_item = new_ref_array_item(name, oid, peeled_oid);\n \tref_item->kind = ref_kind_from_refname(name);\n \tif (format_ref_array_item(ref_item, format, &output, &err))\n \t\tdie(\"%s\", err.buf);\ndiff --git a/ref-filter.h b/ref-filter.h\nindex 235c60f79c9..120221b47fa 100644\n--- a/ref-filter.h\n+++ b/ref-filter.h\n@@ -41,6 +41,7 @@ enum ref_sorting_order {\n \n struct ref_array_item {\n \tstruct object_id objectname;\n+\tstruct object_id peeled_oid;\n \tconst char *rest;\n \tint flag;\n \tunsigned int kind;\n@@ -187,6 +188,7 @@ void print_formatted_ref_array(struct ref_array *array, struct ref_format *forma\n  * name must be a fully qualified refname.\n  */\n void pretty_print_ref(const char *name, const struct object_id *oid,\n+\t\t      const struct object_id *peeled_oid,\n \t\t      struct ref_format *format);\n \n /*\n@@ -195,7 +197,8 @@ void pretty_print_ref(const char *name, const struct object_id *oid,\n  */\n struct ref_array_item *ref_array_push(struct ref_array *array,\n \t\t\t\t      const char *refname,\n-\t\t\t\t      const struct object_id *oid);\n+\t\t\t\t      const struct object_id *oid,\n+\t\t\t\t      const struct object_id *peeled_oid);\n \n /*\n  * If the provided format includes ahead-behind atoms, then compute the\n\n-- \n2.51.1.930.gacf6e81ea2.dirty\n\n"},{"id":"529476","messageId":"20251023-b4-pks-ref-filter-skip-parsing-objects-v4-8-2be68ce82c9a@pks.im","threadId":"64260","inReplyTo":"20251023-b4-pks-ref-filter-skip-parsing-objects-v4-0-2be68ce82c9a@pks.im","subject":"[PATCH v4 08/14] builtin/show-ref: convert to use `reference_get_peeled_oid()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-23T07:16:17Z","receivedAt":"2025-10-23T07:16:43Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The git-show-ref(1) command has multiple different modes:\n\n  - It knows to show all references matching a pattern.\n\n  - It knows to list all references that are an exact match to whatever\n    the user has provided.\n\n  - It knows to check for reference existence.\n\nThe first two commands use mostly the same infrastructure to print the\nreferences via `show_one()`. But while the former mode uses a proper\niterator and thus has a `struct reference` available in its context, the\nlatter calls `refs_read_ref()` and thus doesn't. Consequently, we cannot\neasily use `reference_get_peeled_oid()` to print the peeled value.\n\nAdapt the code so that we manually construct a `struct reference` when\nverifying refs. We wouldn't ever have the peeled value available anyway\nas we're not using an iterator here, so we can simply plug in the values\nwe _do_ have.\n\nWith this change we now have a `struct reference` available at both\ncallsites of `show_one()` and can thus pass it, which allows us to use\n`reference_get_peeled_oid()` instead of `peel_iterated_oid()`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/show-ref.c | 32 +++++++++++++++++++-------------\n 1 file changed, 19 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/show-ref.c b/builtin/show-ref.c\nindex 4803b5e5986..4d4984e4e0c 100644\n--- a/builtin/show-ref.c\n+++ b/builtin/show-ref.c\n@@ -31,31 +31,31 @@ struct show_one_options {\n };\n \n static void show_one(const struct show_one_options *opts,\n-\t\t     const char *refname, const struct object_id *oid)\n+\t\t     const struct reference *ref)\n {\n \tconst char *hex;\n \tstruct object_id peeled;\n \n-\tif (!odb_has_object(the_repository->objects, oid,\n+\tif (!odb_has_object(the_repository->objects, ref->oid,\n \t\t\t    HAS_OBJECT_RECHECK_PACKED | HAS_OBJECT_FETCH_PROMISOR))\n-\t\tdie(\"git show-ref: bad ref %s (%s)\", refname,\n-\t\t    oid_to_hex(oid));\n+\t\tdie(\"git show-ref: bad ref %s (%s)\", ref->name,\n+\t\t    oid_to_hex(ref->oid));\n \n \tif (opts->quiet)\n \t\treturn;\n \n-\thex = repo_find_unique_abbrev(the_repository, oid, opts->abbrev);\n+\thex = repo_find_unique_abbrev(the_repository, ref->oid, opts->abbrev);\n \tif (opts->hash_only)\n \t\tprintf(\"%s\\n\", hex);\n \telse\n-\t\tprintf(\"%s %s\\n\", hex, refname);\n+\t\tprintf(\"%s %s\\n\", hex, ref->name);\n \n \tif (!opts->deref_tags)\n \t\treturn;\n \n-\tif (!peel_iterated_oid(the_repository, oid, &peeled)) {\n+\tif (!reference_get_peeled_oid(the_repository, ref, &peeled)) {\n \t\thex = repo_find_unique_abbrev(the_repository, &peeled, opts->abbrev);\n-\t\tprintf(\"%s %s^{}\\n\", hex, refname);\n+\t\tprintf(\"%s %s^{}\\n\", hex, ref->name);\n \t}\n }\n \n@@ -93,7 +93,7 @@ static int show_ref(const struct reference *ref, void *cbdata)\n match:\n \tdata->found_match++;\n \n-\tshow_one(data->show_one_opts, ref->name, ref->oid);\n+\tshow_one(data->show_one_opts, ref);\n \n \treturn 0;\n }\n@@ -175,12 +175,18 @@ static int cmd_show_ref__verify(const struct show_one_options *show_one_opts,\n \n \t\tif ((starts_with(*refs, \"refs/\") || refname_is_safe(*refs)) &&\n \t\t    !refs_read_ref(get_main_ref_store(the_repository), *refs, &oid)) {\n-\t\t\tshow_one(show_one_opts, *refs, &oid);\n-\t\t}\n-\t\telse if (!show_one_opts->quiet)\n+\t\t\tstruct reference ref = {\n+\t\t\t\t.name = *refs,\n+\t\t\t\t.oid = &oid,\n+\t\t\t};\n+\n+\t\t\tshow_one(show_one_opts, &ref);\n+\t\t} else if (!show_one_opts->quiet) {\n \t\t\tdie(\"'%s' - not a valid ref\", *refs);\n-\t\telse\n+\t\t} else {\n \t\t\treturn 1;\n+\t\t}\n+\n \t\trefs++;\n \t}\n \n\n-- \n2.51.1.930.gacf6e81ea2.dirty\n\n"},{"id":"529477","messageId":"20251023-b4-pks-ref-filter-skip-parsing-objects-v4-9-2be68ce82c9a@pks.im","threadId":"64260","inReplyTo":"20251023-b4-pks-ref-filter-skip-parsing-objects-v4-0-2be68ce82c9a@pks.im","subject":"[PATCH v4 09/14] refs: drop `current_ref_iter` hack","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-23T07:16:18Z","receivedAt":"2025-10-23T07:16:46Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In preceding commits we have refactored all callers of\n`peel_iterated_oid()` to instead use `reference_get_peeled_oid()`. This\nallows us to thus get rid of the former function.\n\nGetting rid of that function is nice, but even nicer is that this also\nallows us to get rid of the `current_ref_iter` hack. This global\nvariable tracked the currently-active ref iterator so that we can use it\nto peel an object ID. Now that the peeled object ID is propagated via\n`struct reference` though we don't have to depend on this hack anymore,\nwhich makes for a more robust and easier-to-understand infrastructure.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c               | 10 ----------\n refs/iterator.c      |  5 -----\n refs/refs-internal.h | 13 -------------\n 3 files changed, 28 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 1b1551f9814..9d8f0a9ca4a 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2324,16 +2324,6 @@ int refs_optimize(struct ref_store *refs, struct pack_refs_opts *opts)\n \treturn refs->be->optimize(refs, opts);\n }\n \n-int peel_iterated_oid(struct repository *r, const struct object_id *base, struct object_id *peeled)\n-{\n-\tif (current_ref_iter &&\n-\t    (current_ref_iter->ref.oid == base ||\n-\t     oideq(current_ref_iter->ref.oid, base)))\n-\t\treturn ref_iterator_peel(current_ref_iter, peeled);\n-\n-\treturn peel_object(r, base, peeled) ? -1 : 0;\n-}\n-\n int reference_get_peeled_oid(struct repository *repo,\n \t\t\t     const struct reference *ref,\n \t\t\t     struct object_id *peeled_oid)\ndiff --git a/refs/iterator.c b/refs/iterator.c\nindex fe5980e1b6c..072c6aacdb0 100644\n--- a/refs/iterator.c\n+++ b/refs/iterator.c\n@@ -458,15 +458,11 @@ struct ref_iterator *prefix_ref_iterator_begin(struct ref_iterator *iter0,\n \treturn ref_iterator;\n }\n \n-struct ref_iterator *current_ref_iter = NULL;\n-\n int do_for_each_ref_iterator(struct ref_iterator *iter,\n \t\t\t     each_ref_fn fn, void *cb_data)\n {\n \tint retval = 0, ok;\n-\tstruct ref_iterator *old_ref_iter = current_ref_iter;\n \n-\tcurrent_ref_iter = iter;\n \twhile ((ok = ref_iterator_advance(iter)) == ITER_OK) {\n \t\tretval = fn(&iter->ref, cb_data);\n \t\tif (retval)\n@@ -474,7 +470,6 @@ int do_for_each_ref_iterator(struct ref_iterator *iter,\n \t}\n \n out:\n-\tcurrent_ref_iter = old_ref_iter;\n \tif (ok == ITER_ERROR)\n \t\tretval = -1;\n \tref_iterator_free(iter);\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex ed749d16572..f4f845bbeaf 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -376,19 +376,6 @@ struct ref_iterator_vtable {\n \tref_iterator_release_fn *release;\n };\n \n-/*\n- * current_ref_iter is a performance hack: when iterating over\n- * references using the for_each_ref*() functions, current_ref_iter is\n- * set to the reference iterator before calling the callback function.\n- * If the callback function calls peel_ref(), then peel_ref() first\n- * checks whether the reference to be peeled is the one referred to by\n- * the iterator (it usually is) and if so, asks the iterator for the\n- * peeled version of the reference if it is available. This avoids a\n- * refname lookup in a common case. current_ref_iter is set to NULL\n- * when the iteration is over.\n- */\n-extern struct ref_iterator *current_ref_iter;\n-\n struct ref_store;\n \n /* refs backends */\n\n-- \n2.51.1.930.gacf6e81ea2.dirty\n\n"},{"id":"529478","messageId":"20251023-b4-pks-ref-filter-skip-parsing-objects-v4-10-2be68ce82c9a@pks.im","threadId":"64260","inReplyTo":"20251023-b4-pks-ref-filter-skip-parsing-objects-v4-0-2be68ce82c9a@pks.im","subject":"[PATCH v4 10/14] refs: drop infrastructure to peel via iterators","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-23T07:16:19Z","receivedAt":"2025-10-23T07:16:50Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Now that the peeled object ID gets propagated via the `struct reference`\nthere is no need anymore to call into the reference iterator itself to\ndereference an object. Remove this infrastructure.\n\nMost of the changes are straight-forward deletions of code. There is one\nexception though in `refs/packed-backend.c::write_with_updates()`. Here\nwe stop peeling the iterator and instead just pass the peeled object ID\nof that iterator directly.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.h                  | 14 --------------\n refs/debug.c            | 11 -----------\n refs/files-backend.c    | 17 -----------------\n refs/iterator.c         | 36 ------------------------------------\n refs/packed-backend.c   | 24 +-----------------------\n refs/ref-cache.c        |  9 ---------\n refs/refs-internal.h    |  7 -------\n refs/reftable-backend.c | 24 ------------------------\n 8 files changed, 1 insertion(+), 141 deletions(-)\n\ndiff --git a/refs.h b/refs.h\nindex 886ed2c0f43..2dd7ac1a16a 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -1289,10 +1289,6 @@ int repo_migrate_ref_storage_format(struct repository *repo,\n  * to the next entry, ref_iterator_advance() aborts the iteration,\n  * frees the ref_iterator, and returns ITER_ERROR.\n  *\n- * The reference currently being looked at can be peeled by calling\n- * ref_iterator_peel(). This function is often faster than peel_ref(),\n- * so it should be preferred when iterating over references.\n- *\n  * Putting it all together, a typical iteration looks like this:\n  *\n  *     int ok;\n@@ -1307,9 +1303,6 @@ int repo_migrate_ref_storage_format(struct repository *repo,\n  *             // Access information about the current reference:\n  *             if (!(iter->flags & REF_ISSYMREF))\n  *                     printf(\"%s is %s\\n\", iter->refname, oid_to_hex(iter->oid));\n- *\n- *             // If you need to peel the reference:\n- *             ref_iterator_peel(iter, &oid);\n  *     }\n  *\n  *     if (ok != ITER_DONE)\n@@ -1400,13 +1393,6 @@ enum ref_iterator_seek_flag {\n int ref_iterator_seek(struct ref_iterator *ref_iterator, const char *refname,\n \t\t      unsigned int flags);\n \n-/*\n- * If possible, peel the reference currently being viewed by the\n- * iterator. Return 0 on success.\n- */\n-int ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t      struct object_id *peeled);\n-\n /* Free the reference iterator and any associated resources. */\n void ref_iterator_free(struct ref_iterator *ref_iterator);\n \ndiff --git a/refs/debug.c b/refs/debug.c\nindex 67718bd1f49..01499b9033c 100644\n--- a/refs/debug.c\n+++ b/refs/debug.c\n@@ -177,16 +177,6 @@ static int debug_ref_iterator_seek(struct ref_iterator *ref_iterator,\n \treturn res;\n }\n \n-static int debug_ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t\t\t   struct object_id *peeled)\n-{\n-\tstruct debug_ref_iterator *diter =\n-\t\t(struct debug_ref_iterator *)ref_iterator;\n-\tint res = diter->iter->vtable->peel(diter->iter, peeled);\n-\ttrace_printf_key(&trace_refs, \"iterator_peel: %s: %d\\n\", diter->iter->ref.name, res);\n-\treturn res;\n-}\n-\n static void debug_ref_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct debug_ref_iterator *diter =\n@@ -198,7 +188,6 @@ static void debug_ref_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable debug_ref_iterator_vtable = {\n \t.advance = debug_ref_iterator_advance,\n \t.seek = debug_ref_iterator_seek,\n-\t.peel = debug_ref_iterator_peel,\n \t.release = debug_ref_iterator_release,\n };\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex fac53fa052d..5aeb454fb47 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -993,15 +993,6 @@ static int files_ref_iterator_seek(struct ref_iterator *ref_iterator,\n \treturn ref_iterator_seek(iter->iter0, refname, flags);\n }\n \n-static int files_ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t\t\t   struct object_id *peeled)\n-{\n-\tstruct files_ref_iterator *iter =\n-\t\t(struct files_ref_iterator *)ref_iterator;\n-\n-\treturn ref_iterator_peel(iter->iter0, peeled);\n-}\n-\n static void files_ref_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct files_ref_iterator *iter =\n@@ -1012,7 +1003,6 @@ static void files_ref_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable files_ref_iterator_vtable = {\n \t.advance = files_ref_iterator_advance,\n \t.seek = files_ref_iterator_seek,\n-\t.peel = files_ref_iterator_peel,\n \t.release = files_ref_iterator_release,\n };\n \n@@ -2388,12 +2378,6 @@ static int files_reflog_iterator_seek(struct ref_iterator *ref_iterator UNUSED,\n \tBUG(\"ref_iterator_seek() called for reflog_iterator\");\n }\n \n-static int files_reflog_iterator_peel(struct ref_iterator *ref_iterator UNUSED,\n-\t\t\t\t      struct object_id *peeled UNUSED)\n-{\n-\tBUG(\"ref_iterator_peel() called for reflog_iterator\");\n-}\n-\n static void files_reflog_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct files_reflog_iterator *iter =\n@@ -2404,7 +2388,6 @@ static void files_reflog_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable files_reflog_iterator_vtable = {\n \t.advance = files_reflog_iterator_advance,\n \t.seek = files_reflog_iterator_seek,\n-\t.peel = files_reflog_iterator_peel,\n \t.release = files_reflog_iterator_release,\n };\n \ndiff --git a/refs/iterator.c b/refs/iterator.c\nindex 072c6aacdb0..d79aa5ec82d 100644\n--- a/refs/iterator.c\n+++ b/refs/iterator.c\n@@ -21,12 +21,6 @@ int ref_iterator_seek(struct ref_iterator *ref_iterator, const char *refname,\n \treturn ref_iterator->vtable->seek(ref_iterator, refname, flags);\n }\n \n-int ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t      struct object_id *peeled)\n-{\n-\treturn ref_iterator->vtable->peel(ref_iterator, peeled);\n-}\n-\n void ref_iterator_free(struct ref_iterator *ref_iterator)\n {\n \tif (ref_iterator) {\n@@ -60,12 +54,6 @@ static int empty_ref_iterator_seek(struct ref_iterator *ref_iterator UNUSED,\n \treturn 0;\n }\n \n-static int empty_ref_iterator_peel(struct ref_iterator *ref_iterator UNUSED,\n-\t\t\t\t   struct object_id *peeled UNUSED)\n-{\n-\tBUG(\"peel called for empty iterator\");\n-}\n-\n static void empty_ref_iterator_release(struct ref_iterator *ref_iterator UNUSED)\n {\n }\n@@ -73,7 +61,6 @@ static void empty_ref_iterator_release(struct ref_iterator *ref_iterator UNUSED)\n static struct ref_iterator_vtable empty_ref_iterator_vtable = {\n \t.advance = empty_ref_iterator_advance,\n \t.seek = empty_ref_iterator_seek,\n-\t.peel = empty_ref_iterator_peel,\n \t.release = empty_ref_iterator_release,\n };\n \n@@ -240,18 +227,6 @@ static int merge_ref_iterator_seek(struct ref_iterator *ref_iterator,\n \treturn 0;\n }\n \n-static int merge_ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t\t\t   struct object_id *peeled)\n-{\n-\tstruct merge_ref_iterator *iter =\n-\t\t(struct merge_ref_iterator *)ref_iterator;\n-\n-\tif (!iter->current) {\n-\t\tBUG(\"peel called before advance for merge iterator\");\n-\t}\n-\treturn ref_iterator_peel(*iter->current, peeled);\n-}\n-\n static void merge_ref_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct merge_ref_iterator *iter =\n@@ -263,7 +238,6 @@ static void merge_ref_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable merge_ref_iterator_vtable = {\n \t.advance = merge_ref_iterator_advance,\n \t.seek = merge_ref_iterator_seek,\n-\t.peel = merge_ref_iterator_peel,\n \t.release = merge_ref_iterator_release,\n };\n \n@@ -412,15 +386,6 @@ static int prefix_ref_iterator_seek(struct ref_iterator *ref_iterator,\n \treturn ref_iterator_seek(iter->iter0, refname, flags);\n }\n \n-static int prefix_ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t\t\t    struct object_id *peeled)\n-{\n-\tstruct prefix_ref_iterator *iter =\n-\t\t(struct prefix_ref_iterator *)ref_iterator;\n-\n-\treturn ref_iterator_peel(iter->iter0, peeled);\n-}\n-\n static void prefix_ref_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct prefix_ref_iterator *iter =\n@@ -432,7 +397,6 @@ static void prefix_ref_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable prefix_ref_iterator_vtable = {\n \t.advance = prefix_ref_iterator_advance,\n \t.seek = prefix_ref_iterator_seek,\n-\t.peel = prefix_ref_iterator_peel,\n \t.release = prefix_ref_iterator_release,\n };\n \ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 1fefefd54ed..6fa229edd0f 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -1030,22 +1030,6 @@ static int packed_ref_iterator_seek(struct ref_iterator *ref_iterator,\n \treturn 0;\n }\n \n-static int packed_ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t\t\t   struct object_id *peeled)\n-{\n-\tstruct packed_ref_iterator *iter =\n-\t\t(struct packed_ref_iterator *)ref_iterator;\n-\n-\tif ((iter->base.ref.flags & REF_KNOWS_PEELED)) {\n-\t\toidcpy(peeled, &iter->peeled);\n-\t\treturn is_null_oid(&iter->peeled) ? -1 : 0;\n-\t} else if ((iter->base.ref.flags & (REF_ISBROKEN | REF_ISSYMREF))) {\n-\t\treturn -1;\n-\t} else {\n-\t\treturn peel_object(iter->repo, &iter->oid, peeled) ? -1 : 0;\n-\t}\n-}\n-\n static void packed_ref_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct packed_ref_iterator *iter =\n@@ -1059,7 +1043,6 @@ static void packed_ref_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable packed_ref_iterator_vtable = {\n \t.advance = packed_ref_iterator_advance,\n \t.seek = packed_ref_iterator_seek,\n-\t.peel = packed_ref_iterator_peel,\n \t.release = packed_ref_iterator_release,\n };\n \n@@ -1525,13 +1508,8 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re\n \n \t\tif (cmp < 0) {\n \t\t\t/* Pass the old reference through. */\n-\n-\t\t\tstruct object_id peeled;\n-\t\t\tint peel_error = ref_iterator_peel(iter, &peeled);\n-\n \t\t\tif (write_packed_entry(out, iter->ref.name,\n-\t\t\t\t\t       iter->ref.oid,\n-\t\t\t\t\t       peel_error ? NULL : &peeled))\n+\t\t\t\t\t       iter->ref.oid, iter->ref.peeled_oid))\n \t\t\t\tgoto write_error;\n \n \t\t\tif ((ok = ref_iterator_advance(iter)) != ITER_OK) {\ndiff --git a/refs/ref-cache.c b/refs/ref-cache.c\nindex e427848879d..ffef01a5975 100644\n--- a/refs/ref-cache.c\n+++ b/refs/ref-cache.c\n@@ -546,14 +546,6 @@ static int cache_ref_iterator_seek(struct ref_iterator *ref_iterator,\n \treturn 0;\n }\n \n-static int cache_ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t\t\t   struct object_id *peeled)\n-{\n-\tstruct cache_ref_iterator *iter =\n-\t\t(struct cache_ref_iterator *)ref_iterator;\n-\treturn peel_object(iter->repo, ref_iterator->ref.oid, peeled) ? -1 : 0;\n-}\n-\n static void cache_ref_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct cache_ref_iterator *iter =\n@@ -565,7 +557,6 @@ static void cache_ref_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable cache_ref_iterator_vtable = {\n \t.advance = cache_ref_iterator_advance,\n \t.seek = cache_ref_iterator_seek,\n-\t.peel = cache_ref_iterator_peel,\n \t.release = cache_ref_iterator_release,\n };\n \ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex f4f845bbeaf..4671517dade 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -357,12 +357,6 @@ typedef int ref_iterator_advance_fn(struct ref_iterator *ref_iterator);\n typedef int ref_iterator_seek_fn(struct ref_iterator *ref_iterator,\n \t\t\t\t const char *refname, unsigned int flags);\n \n-/*\n- * Peels the current ref, returning 0 for success or -1 for failure.\n- */\n-typedef int ref_iterator_peel_fn(struct ref_iterator *ref_iterator,\n-\t\t\t\t struct object_id *peeled);\n-\n /*\n  * Implementations of this function should free any resources specific\n  * to the derived class.\n@@ -372,7 +366,6 @@ typedef void ref_iterator_release_fn(struct ref_iterator *ref_iterator);\n struct ref_iterator_vtable {\n \tref_iterator_advance_fn *advance;\n \tref_iterator_seek_fn *seek;\n-\tref_iterator_peel_fn *peel;\n \tref_iterator_release_fn *release;\n };\n \ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex e214e120d77..e329d4a423a 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -744,21 +744,6 @@ static int reftable_ref_iterator_seek(struct ref_iterator *ref_iterator,\n \treturn iter->err;\n }\n \n-static int reftable_ref_iterator_peel(struct ref_iterator *ref_iterator,\n-\t\t\t\t      struct object_id *peeled)\n-{\n-\tstruct reftable_ref_iterator *iter =\n-\t\t(struct reftable_ref_iterator *)ref_iterator;\n-\n-\tif (iter->ref.value_type == REFTABLE_REF_VAL2) {\n-\t\toidread(peeled, iter->ref.value.val2.target_value,\n-\t\t\titer->refs->base.repo->hash_algo);\n-\t\treturn 0;\n-\t}\n-\n-\treturn -1;\n-}\n-\n static void reftable_ref_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct reftable_ref_iterator *iter =\n@@ -776,7 +761,6 @@ static void reftable_ref_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable reftable_ref_iterator_vtable = {\n \t.advance = reftable_ref_iterator_advance,\n \t.seek = reftable_ref_iterator_seek,\n-\t.peel = reftable_ref_iterator_peel,\n \t.release = reftable_ref_iterator_release,\n };\n \n@@ -2098,13 +2082,6 @@ static int reftable_reflog_iterator_seek(struct ref_iterator *ref_iterator UNUSE\n \treturn -1;\n }\n \n-static int reftable_reflog_iterator_peel(struct ref_iterator *ref_iterator UNUSED,\n-\t\t\t\t\t struct object_id *peeled UNUSED)\n-{\n-\tBUG(\"reftable reflog iterator cannot be peeled\");\n-\treturn -1;\n-}\n-\n static void reftable_reflog_iterator_release(struct ref_iterator *ref_iterator)\n {\n \tstruct reftable_reflog_iterator *iter =\n@@ -2117,7 +2094,6 @@ static void reftable_reflog_iterator_release(struct ref_iterator *ref_iterator)\n static struct ref_iterator_vtable reftable_reflog_iterator_vtable = {\n \t.advance = reftable_reflog_iterator_advance,\n \t.seek = reftable_reflog_iterator_seek,\n-\t.peel = reftable_reflog_iterator_peel,\n \t.release = reftable_reflog_iterator_release,\n };\n \n\n-- \n2.51.1.930.gacf6e81ea2.dirty\n\n"},{"id":"529479","messageId":"20251023-b4-pks-ref-filter-skip-parsing-objects-v4-11-2be68ce82c9a@pks.im","threadId":"64260","inReplyTo":"20251023-b4-pks-ref-filter-skip-parsing-objects-v4-0-2be68ce82c9a@pks.im","subject":"[PATCH v4 11/14] object: add flag to `peel_object()` to verify object type","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-23T07:16:20Z","receivedAt":"2025-10-23T07:16:52Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When peeling a tag to a non-tag object we repeatedly call\n`parse_object()` on the tagged object until we find the first object\nthat isn't a tag. While this feels sensible at first, there is a big\ncatch here: `parse_object()` doesn't actually verify the type of the\ntagged object.\n\nThe relevant code path here eventually ends up in `parse_tag_buffer()`.\nHere, we parse the various fields of the tag, including the \"type\". Once\nwe've figured out the type and the tagged object ID, we call one of the\n`lookup_${type}()` functions for whatever type we have found. There is\ntwo possible outcomes in the successful case:\n\n  1. The object is already part of our cached objects. In that case we\n     double-check whether the type we're trying to look up matches the\n     type that was cached.\n\n  2. The object is _not_ part of our cached objects. In that case, we\n     simply create a new object with the expected type, but we don't\n     parse that object.\n\nIn the first case we might notice type mismatches, but only in the case\nwhere our cache has the object with the correct type. In the second\ncase, we'll blindly assume that the type is correct and then go with it.\nWe'll only notice that the type might be wrong when we try to parse the\nobject at a later point.\n\nNow arguably, we could change `parse_tag_buffer()` to verify the tagged\nobject's type for us. But that would have the effect that such a tag\ncannot be parsed at all anymore, and we have a small bunch of tests for\nexactly this case that assert we still can open such tags. So this\nchange does not feel like something we can retroactively tighten, even\nthough one shouldn't ever hit such corrupted tags.\n\nInstead, add a new `flags` field to `peel_object()` that allows the\ncaller to opt in to strict object verification. This will be wired up at\na subset of callsites over the next few commits.\n\nNote that this change also inlines `deref_tag_noverify()`. There's only\nbeen two callsites of that function, the one we're changing and one in\nour test helpers. The latter callsite can trivially use `deref_tag()`\ninstead, so by inlining the function we avoid having to pass down the\nflag.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n object.c                | 20 +++++++++++++++++---\n object.h                | 15 ++++++++++++++-\n ref-filter.c            |  2 +-\n refs.c                  |  2 +-\n refs/packed-backend.c   |  5 ++---\n refs/reftable-backend.c |  4 ++--\n t/helper/test-reach.c   |  2 +-\n tag.c                   | 12 ------------\n tag.h                   |  1 -\n 9 files changed, 38 insertions(+), 25 deletions(-)\n\ndiff --git a/object.c b/object.c\nindex 986114a6dba..e72b0ed4360 100644\n--- a/object.c\n+++ b/object.c\n@@ -209,11 +209,12 @@ struct object *lookup_object_by_type(struct repository *r,\n \n enum peel_status peel_object(struct repository *r,\n \t\t\t     const struct object_id *name,\n-\t\t\t     struct object_id *oid)\n+\t\t\t     struct object_id *oid,\n+\t\t\t     unsigned flags)\n {\n \tstruct object *o = lookup_unknown_object(r, name);\n \n-\tif (o->type == OBJ_NONE) {\n+\tif (o->type == OBJ_NONE || flags & PEEL_OBJECT_VERIFY_OBJECT_TYPE) {\n \t\tint type = odb_read_object_info(r->objects, name, NULL);\n \t\tif (type < 0 || !object_as_type(o, type, 0))\n \t\t\treturn PEEL_INVALID;\n@@ -222,7 +223,20 @@ enum peel_status peel_object(struct repository *r,\n \tif (o->type != OBJ_TAG)\n \t\treturn PEEL_NON_TAG;\n \n-\to = deref_tag_noverify(r, o);\n+\twhile (o && o->type == OBJ_TAG) {\n+\t\to = parse_object(r, &o->oid);\n+\t\tif (o && o->type == OBJ_TAG && ((struct tag *)o)->tagged) {\n+\t\t\to = ((struct tag *)o)->tagged;\n+\n+\t\t\tif (flags & PEEL_OBJECT_VERIFY_OBJECT_TYPE) {\n+\t\t\t\tint type = odb_read_object_info(r->objects, &o->oid, NULL);\n+\t\t\t\tif (type < 0 || !object_as_type(o, type, 0))\n+\t\t\t\t\treturn PEEL_INVALID;\n+\t\t\t}\n+\t\t} else {\n+\t\t\to = NULL;\n+\t\t}\n+\t}\n \tif (!o)\n \t\treturn PEEL_INVALID;\n \ndiff --git a/object.h b/object.h\nindex 8c3c1c46e1b..1499f63d507 100644\n--- a/object.h\n+++ b/object.h\n@@ -287,6 +287,17 @@ enum peel_status {\n \tPEEL_BROKEN = -4\n };\n \n+enum peel_object_flags {\n+\t/*\n+\t * Always verify the object type, even in the case where the looked-up\n+\t * object already has an object type. This can be useful when the\n+\t * stored object type may be invalid. One such case is when looking up\n+\t * objects via tags, where we blindly trust the object type declared by\n+\t * the tag.\n+\t */\n+\tPEEL_OBJECT_VERIFY_OBJECT_TYPE = (1 << 0),\n+};\n+\n /*\n  * Peel the named object; i.e., if the object is a tag, resolve the\n  * tag recursively until a non-tag is found.  If successful, store the\n@@ -295,7 +306,9 @@ enum peel_status {\n  * and leave oid unchanged.\n  */\n enum peel_status peel_object(struct repository *r,\n-\t\t\t     const struct object_id *name, struct object_id *oid);\n+\t\t\t     const struct object_id *name,\n+\t\t\t     struct object_id *oid,\n+\t\t\t     unsigned flags);\n \n struct object_list *object_list_insert(struct object *item,\n \t\t\t\t       struct object_list **list_p);\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 7fd8babec8f..9a8ed8c8fc1 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -2581,7 +2581,7 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)\n \tif (need_tagged) {\n \t\tif (!is_null_oid(&ref->peeled_oid)) {\n \t\t\toidcpy(&oi_deref.oid, &ref->peeled_oid);\n-\t\t} else if (!peel_object(the_repository, &obj->oid, &oi_deref.oid)) {\n+\t\t} else if (!peel_object(the_repository, &oi.oid, &oi_deref.oid, 0)) {\n \t\t\t/* We managed to peel the object ourselves. */\n \t\t} else {\n \t\t\tdie(\"bad tag\");\ndiff --git a/refs.c b/refs.c\nindex 9d8f0a9ca4a..a41a94ae55b 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2333,7 +2333,7 @@ int reference_get_peeled_oid(struct repository *repo,\n \t\treturn 0;\n \t}\n \n-\treturn peel_object(repo, ref->oid, peeled_oid) ? -1 : 0;\n+\treturn peel_object(repo, ref->oid, peeled_oid, 0) ? -1 : 0;\n }\n \n int refs_update_symref(struct ref_store *refs, const char *ref,\ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 6fa229edd0f..4752d3f3981 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -1527,9 +1527,8 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re\n \t\t\ti++;\n \t\t} else {\n \t\t\tstruct object_id peeled;\n-\t\t\tint peel_error = peel_object(refs->base.repo,\n-\t\t\t\t\t\t     &update->new_oid,\n-\t\t\t\t\t\t     &peeled);\n+\t\t\tint peel_error = peel_object(refs->base.repo, &update->new_oid,\n+\t\t\t\t\t\t     &peeled, 0);\n \n \t\t\tif (write_packed_entry(out, update->refname,\n \t\t\t\t\t       &update->new_oid,\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex e329d4a423a..9febb2322c3 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1632,7 +1632,7 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n \t\t\tref.refname = (char *)u->refname;\n \t\t\tref.update_index = ts;\n \n-\t\t\tpeel_error = peel_object(arg->refs->base.repo, &u->new_oid, &peeled);\n+\t\t\tpeel_error = peel_object(arg->refs->base.repo, &u->new_oid, &peeled, 0);\n \t\t\tif (!peel_error) {\n \t\t\t\tref.value_type = REFTABLE_REF_VAL2;\n \t\t\t\tmemcpy(ref.value.val2.target_value, peeled.hash, GIT_MAX_RAWSZ);\n@@ -2497,7 +2497,7 @@ static int write_reflog_expiry_table(struct reftable_writer *writer, void *cb_da\n \t\tref.refname = (char *)arg->refname;\n \t\tref.update_index = ts;\n \n-\t\tif (!peel_object(arg->refs->base.repo, &arg->update_oid, &peeled)) {\n+\t\tif (!peel_object(arg->refs->base.repo, &arg->update_oid, &peeled, 0)) {\n \t\t\tref.value_type = REFTABLE_REF_VAL2;\n \t\t\tmemcpy(ref.value.val2.target_value, peeled.hash, GIT_MAX_RAWSZ);\n \t\t\tmemcpy(ref.value.val2.value, arg->update_oid.hash, GIT_MAX_RAWSZ);\ndiff --git a/t/helper/test-reach.c b/t/helper/test-reach.c\nindex 028ec003067..c58c93800f3 100644\n--- a/t/helper/test-reach.c\n+++ b/t/helper/test-reach.c\n@@ -63,7 +63,7 @@ int cmd__reach(int ac, const char **av)\n \t\t\tdie(\"failed to resolve %s\", buf.buf + 2);\n \n \t\torig = parse_object(r, &oid);\n-\t\tpeeled = deref_tag_noverify(the_repository, orig);\n+\t\tpeeled = deref_tag(the_repository, orig, NULL, 0);\n \n \t\tif (!peeled)\n \t\t\tdie(\"failed to load commit for input %s resulting in oid %s\",\ndiff --git a/tag.c b/tag.c\nindex 1d52686ee10..f5c232d2f1f 100644\n--- a/tag.c\n+++ b/tag.c\n@@ -94,18 +94,6 @@ struct object *deref_tag(struct repository *r, struct object *o, const char *war\n \treturn o;\n }\n \n-struct object *deref_tag_noverify(struct repository *r, struct object *o)\n-{\n-\twhile (o && o->type == OBJ_TAG) {\n-\t\to = parse_object(r, &o->oid);\n-\t\tif (o && o->type == OBJ_TAG && ((struct tag *)o)->tagged)\n-\t\t\to = ((struct tag *)o)->tagged;\n-\t\telse\n-\t\t\to = NULL;\n-\t}\n-\treturn o;\n-}\n-\n struct tag *lookup_tag(struct repository *r, const struct object_id *oid)\n {\n \tstruct object *obj = lookup_object(r, oid);\ndiff --git a/tag.h b/tag.h\nindex c49d7c19ad3..ef12a610372 100644\n--- a/tag.h\n+++ b/tag.h\n@@ -16,7 +16,6 @@ int parse_tag_buffer(struct repository *r, struct tag *item, const void *data, u\n int parse_tag(struct tag *item);\n void release_tag_memory(struct tag *t);\n struct object *deref_tag(struct repository *r, struct object *, const char *, int);\n-struct object *deref_tag_noverify(struct repository *r, struct object *);\n int gpg_verify_tag(const struct object_id *oid,\n \t\t   const char *name_to_report, unsigned flags);\n struct object_id *get_tagged_oid(struct tag *tag);\n\n-- \n2.51.1.930.gacf6e81ea2.dirty\n\n"},{"id":"529480","messageId":"20251023-b4-pks-ref-filter-skip-parsing-objects-v4-12-2be68ce82c9a@pks.im","threadId":"64260","inReplyTo":"20251023-b4-pks-ref-filter-skip-parsing-objects-v4-0-2be68ce82c9a@pks.im","subject":"[PATCH v4 12/14] refs: don't store peeled object IDs for invalid tags","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-23T07:16:21Z","receivedAt":"2025-10-23T07:16:56Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Both the \"files\" and \"reftable\" backend store peeled object IDs for\nreferences that point to tags:\n\n  - The \"files\" backend stores the value when packing refs, where each\n    peeled object ID is prefixed with \"^\".\n\n  - The \"reftable\" backend stores the value whenever writing a new\n    reference that points to a tag via a special ref record type.\n\nBoth of these backends use `peel_object()` to find the peeled object ID.\nBut as explained in the preceding commit, that function does not detect\nthe case where the tag's tagged object and its claimed type mismatch.\n\nThe consequence of storing these bogus peeled object IDs is that we're\nless likely to detect such corruption in other parts of Git.\ngit-for-each-ref(1) for example does not notice anymore that the tag is\nbroken when using \"--format=%(*objectname)\" to dereference tags.\n\nOne could claim that this is good, because it still allows us to mostly\nuse the tag as intended. But the biggest problem here is that we now\nhave different behaviour for such a broken tag depending on whether or\nnot we have its peeled value in the refdb.\n\nFix the issue by verifying the object type when peeling the object. If\nthat verification fails we simply skip storing the peeled value in\neither of the reference formats.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs/packed-backend.c      |  2 +-\n refs/reftable-backend.c    |  3 ++-\n t/pack-refs-tests.sh       | 32 ++++++++++++++++++++++++++++++++\n t/t0610-reftable-basics.sh | 28 ++++++++++++++++++++++++++++\n 4 files changed, 63 insertions(+), 2 deletions(-)\n\ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 4752d3f3981..1ab0c503930 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -1528,7 +1528,7 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re\n \t\t} else {\n \t\t\tstruct object_id peeled;\n \t\t\tint peel_error = peel_object(refs->base.repo, &update->new_oid,\n-\t\t\t\t\t\t     &peeled, 0);\n+\t\t\t\t\t\t     &peeled, PEEL_OBJECT_VERIFY_OBJECT_TYPE);\n \n \t\t\tif (write_packed_entry(out, update->refname,\n \t\t\t\t\t       &update->new_oid,\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 9febb2322c3..6bbfd5618da 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1632,7 +1632,8 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n \t\t\tref.refname = (char *)u->refname;\n \t\t\tref.update_index = ts;\n \n-\t\t\tpeel_error = peel_object(arg->refs->base.repo, &u->new_oid, &peeled, 0);\n+\t\t\tpeel_error = peel_object(arg->refs->base.repo, &u->new_oid, &peeled,\n+\t\t\t\t\t\t PEEL_OBJECT_VERIFY_OBJECT_TYPE);\n \t\t\tif (!peel_error) {\n \t\t\t\tref.value_type = REFTABLE_REF_VAL2;\n \t\t\t\tmemcpy(ref.value.val2.target_value, peeled.hash, GIT_MAX_RAWSZ);\ndiff --git a/t/pack-refs-tests.sh b/t/pack-refs-tests.sh\nindex 3dbcc01718e..095823d915f 100644\n--- a/t/pack-refs-tests.sh\n+++ b/t/pack-refs-tests.sh\n@@ -428,4 +428,36 @@ do\n \t'\n done\n \n+test_expect_success 'pack-refs does not store invalid peeled tag value' '\n+\ttest_when_finished rm -rf repo &&\n+\tgit init repo &&\n+\t(\n+\t\tcd repo &&\n+\t\tgit commit --allow-empty --message initial &&\n+\n+\t\techo garbage >blob-content &&\n+\t\tblob_id=$(git hash-object -w -t blob blob-content) &&\n+\n+\t\t# Write an invalid tag into the object database. The tag itself\n+\t\t# is well-formed, but the tagged object is a blob while we\n+\t\t# claim that it is a commit.\n+\t\tcat >tag-content <<-EOF &&\n+\t\tobject $blob_id\n+\t\ttype commit\n+\t\ttag bad-tag\n+\t\ttagger C O Mitter <committer@example.com> 1112354055 +0200\n+\n+\t\tannotated\n+\t\tEOF\n+\t\ttag_id=$(git hash-object -w -t tag tag-content) &&\n+\t\tgit update-ref refs/tags/bad-tag \"$tag_id\" &&\n+\n+\t\t# The packed-refs file should not contain the peeled object ID.\n+\t\t# If it did this would cause commands that use the peeled value\n+\t\t# to not notice this corrupted tag.\n+\t\tgit pack-refs --all &&\n+\t\ttest_grep ! \"^\\^\" .git/packed-refs\n+\t)\n+'\n+\n test_done\ndiff --git a/t/t0610-reftable-basics.sh b/t/t0610-reftable-basics.sh\nindex 3ea5d51532a..6575528f212 100755\n--- a/t/t0610-reftable-basics.sh\n+++ b/t/t0610-reftable-basics.sh\n@@ -1135,4 +1135,32 @@ test_expect_success 'fetch: accessing FETCH_HEAD special ref works' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'writes do not persist peeled value for invalid tags' '\n+\ttest_when_finished rm -rf repo &&\n+\tgit init repo &&\n+\t(\n+\t\tcd repo &&\n+\t\tgit commit --allow-empty --message initial &&\n+\n+\t\t# We cannot easily verify that the peeled value is not stored\n+\t\t# in the tables. Instead, we test this indirectly: we create\n+\t\t# two tags that both point to the same object, but they claim\n+\t\t# different object types. If we parse both tags we notice that\n+\t\t# the parsed tagged object has a mismatch between the two tags\n+\t\t# and bail out.\n+\t\t#\n+\t\t# If we instead use the persisted peeled value we would not\n+\t\t# even parse the tags. As such, we would not notice the\n+\t\t# discrepancy either and thus listing these tags would succeed.\n+\t\tgit tag tag-1 -m \"tag 1\" &&\n+\t\tgit cat-file tag tag-1 >raw-tag &&\n+\t\tsed \"s/^type commit$/type blob/\" <raw-tag >broken-tag &&\n+\t\tbroken_tag_id=$(git hash-object -w -t tag broken-tag) &&\n+\t\tgit update-ref refs/tags/tag-2 $broken_tag_id &&\n+\n+\t\ttest_must_fail git for-each-ref --format=\"%(*objectname)\" refs/tags/ 2>err &&\n+\t\ttest_grep \"bad tag pointer\" err\n+\t)\n+'\n+\n test_done\n\n-- \n2.51.1.930.gacf6e81ea2.dirty\n\n"},{"id":"529481","messageId":"20251023-b4-pks-ref-filter-skip-parsing-objects-v4-13-2be68ce82c9a@pks.im","threadId":"64260","inReplyTo":"20251023-b4-pks-ref-filter-skip-parsing-objects-v4-0-2be68ce82c9a@pks.im","subject":"[PATCH v4 13/14] ref-filter: detect broken tags when dereferencing them","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-23T07:16:22Z","receivedAt":"2025-10-23T07:16:59Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Users can ask git-for-each-ref(1) to peel tags and return information of\nthe tagged object by adding an asterisk to the format, like for example\n\"%(*$objectname)\". If so, git-for-each-ref(1) peels that object to the\nfirst non-tag object and then returns its values.\n\nAs mentioned in preceding commits, it can happen that the tagged object\ntype and the claimed object type differ, effectively resulting in a\ncorrupt tag. git-for-each-ref(1) would notice this mismatch, print an\nerror and then bail out when trying to peel the tag.\n\nBut we only notice this corruption in some very specific edge cases!\nWhile we have a test in \"t/for-each-ref-tests.sh\" that verifies the\nabove scenario, this test is specifically crafted to detect the issue at\nhand. Namely, we create two tags:\n\n  - One tag points to a specific object with the correct type.\n\n  - The other tag points to the *same* object with a different type.\n\nThe fact that both tags point to the same object is important here:\n`peel_object()` wouldn't notice the corruption if the tagged objects\nwere different.\n\nThe root cause is that `peel_object()` calls `lookup_${type}()`\neventually, where the type is the same type declared in the tag object.\nConsequently, when we have two tags pointing to the same object but with\ndifferent declared types we'll call two different lookup functions. The\nfirst lookup will store the object with an unverified type A, whereas\nthe second lookup will try to look up the object with a different\nunverified type B. And it is only now that we notice the discrepancy in\nobject types, even though type A could've already been the wrong type.\n\nFix the issue by verifying the object type in `populate_value()`. With\nthis change we'll also notice type mismatches when only dereferencing a\ntag once.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n ref-filter.c            | 3 ++-\n t/for-each-ref-tests.sh | 4 +++-\n 2 files changed, 5 insertions(+), 2 deletions(-)\n\ndiff --git a/ref-filter.c b/ref-filter.c\nindex 9a8ed8c8fc1..c54025d6b4c 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -2581,7 +2581,8 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)\n \tif (need_tagged) {\n \t\tif (!is_null_oid(&ref->peeled_oid)) {\n \t\t\toidcpy(&oi_deref.oid, &ref->peeled_oid);\n-\t\t} else if (!peel_object(the_repository, &oi.oid, &oi_deref.oid, 0)) {\n+\t\t} else if (!peel_object(the_repository, &oi.oid, &oi_deref.oid,\n+\t\t\t\t\tPEEL_OBJECT_VERIFY_OBJECT_TYPE)) {\n \t\t\t/* We managed to peel the object ourselves. */\n \t\t} else {\n \t\t\tdie(\"bad tag\");\ndiff --git a/t/for-each-ref-tests.sh b/t/for-each-ref-tests.sh\nindex e3ad19298ac..4593be5fd54 100644\n--- a/t/for-each-ref-tests.sh\n+++ b/t/for-each-ref-tests.sh\n@@ -1809,7 +1809,9 @@ test_expect_success \"${git_for_each_ref} reports broken tags\" '\n \tbad=$(git hash-object -w -t tag bad) &&\n \tgit update-ref refs/tags/broken-tag-bad $bad &&\n \ttest_must_fail ${git_for_each_ref} --format=\"%(*objectname)\" \\\n-\t\trefs/tags/broken-tag-*\n+\t\trefs/tags/broken-tag-* &&\n+\ttest_must_fail ${git_for_each_ref} --format=\"%(*objectname)\" \\\n+\t\trefs/tags/broken-tag-bad\n '\n \n test_expect_success 'set up tag with signature and no blank lines' '\n\n-- \n2.51.1.930.gacf6e81ea2.dirty\n\n"},{"id":"529482","messageId":"20251023-b4-pks-ref-filter-skip-parsing-objects-v4-14-2be68ce82c9a@pks.im","threadId":"64260","inReplyTo":"20251023-b4-pks-ref-filter-skip-parsing-objects-v4-0-2be68ce82c9a@pks.im","subject":"[PATCH v4 14/14] ref-filter: parse objects on demand","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-23T07:16:23Z","receivedAt":"2025-10-23T07:17:01Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When formatting an arbitrary object we parse that object regardless of\nwhether or not we actually need any parsed data. In fact, many of the\natoms we have don't require any.\n\nRefactor the code so that we parse the data on demand when we see an\natom that wants to access the objects. This leads to a small speedup,\nfor example in the Chromium repository with around 40000 refs:\n\n    Benchmark 1: for-each-ref --format='%(raw)' (HEAD~)\n      Time (mean ± σ):     388.7 ms ±   1.1 ms    [User: 322.2 ms, System: 65.0 ms]\n      Range (min … max):   387.3 ms … 390.8 ms    10 runs\n\n    Benchmark 2: for-each-ref --format='%(raw)' (HEAD)\n      Time (mean ± σ):     344.7 ms ±   0.7 ms    [User: 287.8 ms, System: 55.1 ms]\n      Range (min … max):   343.9 ms … 345.7 ms    10 runs\n\n    Summary\n      for-each-ref --format='%(raw)' (HEAD) ran\n        1.13 ± 0.00 times faster than for-each-ref --format='%(raw)' (HEAD~)\n\nWith this change, we now spend ~90% of the time decompressing objects,\nwhich is almost as good as it gets regarding git-for-each-ref(1)'s own\ninfrastructure.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n ref-filter.c | 142 ++++++++++++++++++++++++++++++++++++++++++++---------------\n 1 file changed, 106 insertions(+), 36 deletions(-)\n\ndiff --git a/ref-filter.c b/ref-filter.c\nindex c54025d6b4c..7cfcd5c3554 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -91,6 +91,7 @@ static struct expand_data {\n \tstruct object_id delta_base_oid;\n \tvoid *content;\n \n+\tstruct object *maybe_object;\n \tstruct object_info info;\n } oi, oi_deref;\n \n@@ -1475,11 +1476,29 @@ static void grab_common_values(struct atom_value *val, int deref, struct expand_\n \t}\n }\n \n+static struct object *get_or_parse_object(struct expand_data *data, const char *refname,\n+\t\t\t\t\t  struct strbuf *err, int *eaten)\n+{\n+\tif (!data->maybe_object) {\n+\t\tdata->maybe_object = parse_object_buffer(the_repository, &data->oid, data->type,\n+\t\t\t\t\t\t\t data->size, data->content, eaten);\n+\t\tif (!data->maybe_object) {\n+\t\t\tstrbuf_addf(err, _(\"parse_object_buffer failed on %s for %s\"),\n+\t\t\t\t    oid_to_hex(&data->oid), refname);\n+\t\t\treturn NULL;\n+\t\t}\n+\t}\n+\n+\treturn data->maybe_object;\n+}\n+\n /* See grab_values */\n-static void grab_tag_values(struct atom_value *val, int deref, struct object *obj)\n+static int grab_tag_values(struct atom_value *val, int deref,\n+\t\t\t   struct expand_data *data, const char *refname,\n+\t\t\t   struct strbuf *err, int *eaten)\n {\n+\tstruct tag *tag = NULL;\n \tint i;\n-\tstruct tag *tag = (struct tag *) obj;\n \n \tfor (i = 0; i < used_atom_cnt; i++) {\n \t\tconst char *name = used_atom[i].name;\n@@ -1487,6 +1506,14 @@ static void grab_tag_values(struct atom_value *val, int deref, struct object *ob\n \t\tstruct atom_value *v = &val[i];\n \t\tif (!!deref != (*name == '*'))\n \t\t\tcontinue;\n+\n+\t\tif (!tag) {\n+\t\t\ttag = (struct tag *) get_or_parse_object(data, refname,\n+\t\t\t\t\t\t\t\t err, eaten);\n+\t\t\tif (!tag)\n+\t\t\t\treturn -1;\n+\t\t}\n+\n \t\tif (deref)\n \t\t\tname++;\n \t\tif (atom_type == ATOM_TAG)\n@@ -1496,22 +1523,35 @@ static void grab_tag_values(struct atom_value *val, int deref, struct object *ob\n \t\telse if (atom_type == ATOM_OBJECT && tag->tagged)\n \t\t\tv->s = xstrdup(oid_to_hex(&tag->tagged->oid));\n \t}\n+\n+\treturn 0;\n }\n \n /* See grab_values */\n-static void grab_commit_values(struct atom_value *val, int deref, struct object *obj)\n+static int grab_commit_values(struct atom_value *val, int deref,\n+\t\t\t      struct expand_data *data, const char *refname,\n+\t\t\t      struct strbuf *err, int *eaten)\n {\n \tint i;\n-\tstruct commit *commit = (struct commit *) obj;\n+\tstruct commit *commit = NULL;\n \n \tfor (i = 0; i < used_atom_cnt; i++) {\n \t\tconst char *name = used_atom[i].name;\n \t\tenum atom_type atom_type = used_atom[i].atom_type;\n \t\tstruct atom_value *v = &val[i];\n+\n \t\tif (!!deref != (*name == '*'))\n \t\t\tcontinue;\n \t\tif (deref)\n \t\t\tname++;\n+\n+\t\tif (!commit) {\n+\t\t\tcommit = (struct commit *) get_or_parse_object(data, refname,\n+\t\t\t\t\t\t\t\t       err, eaten);\n+\t\t\tif (!commit)\n+\t\t\t\treturn -1;\n+\t\t}\n+\n \t\tif (atom_type == ATOM_TREE &&\n \t\t    grab_oid(name, \"tree\", get_commit_tree_oid(commit), v, &used_atom[i]))\n \t\t\tcontinue;\n@@ -1531,6 +1571,8 @@ static void grab_commit_values(struct atom_value *val, int deref, struct object\n \t\t\tv->s = strbuf_detach(&s, NULL);\n \t\t}\n \t}\n+\n+\treturn 0;\n }\n \n static const char *find_wholine(const char *who, int wholen, const char *buf)\n@@ -1759,10 +1801,12 @@ static void grab_person(const char *who, struct atom_value *val, int deref, void\n \t}\n }\n \n-static void grab_signature(struct atom_value *val, int deref, struct object *obj)\n+static int grab_signature(struct atom_value *val, int deref,\n+\t\t\t  struct expand_data *data, const char *refname,\n+\t\t\t  struct strbuf *err, int *eaten)\n {\n \tint i;\n-\tstruct commit *commit = (struct commit *) obj;\n+\tstruct commit *commit = NULL;\n \tstruct signature_check sigc = { 0 };\n \tint signature_checked = 0;\n \n@@ -1790,6 +1834,13 @@ static void grab_signature(struct atom_value *val, int deref, struct object *obj\n \t\t\tcontinue;\n \n \t\tif (!signature_checked) {\n+\t\t\tif (!commit) {\n+\t\t\t\tcommit = (struct commit *) get_or_parse_object(data, refname,\n+\t\t\t\t\t\t\t\t\t       err, eaten);\n+\t\t\t\tif (!commit)\n+\t\t\t\t\treturn -1;\n+\t\t\t}\n+\n \t\t\tcheck_commit_signature(commit, &sigc);\n \t\t\tsignature_checked = 1;\n \t\t}\n@@ -1843,6 +1894,8 @@ static void grab_signature(struct atom_value *val, int deref, struct object *obj\n \n \tif (signature_checked)\n \t\tsignature_check_clear(&sigc);\n+\n+\treturn 0;\n }\n \n static void find_subpos(const char *buf,\n@@ -1920,9 +1973,8 @@ static void append_lines(struct strbuf *out, const char *buf, unsigned long size\n }\n \n static void grab_describe_values(struct atom_value *val, int deref,\n-\t\t\t\t struct object *obj)\n+\t\t\t\t struct expand_data *data)\n {\n-\tstruct commit *commit = (struct commit *)obj;\n \tint i;\n \n \tfor (i = 0; i < used_atom_cnt; i++) {\n@@ -1944,7 +1996,7 @@ static void grab_describe_values(struct atom_value *val, int deref,\n \t\tcmd.git_cmd = 1;\n \t\tstrvec_push(&cmd.args, \"describe\");\n \t\tstrvec_pushv(&cmd.args, atom->u.describe_args.v);\n-\t\tstrvec_push(&cmd.args, oid_to_hex(&commit->object.oid));\n+\t\tstrvec_push(&cmd.args, oid_to_hex(&data->oid));\n \t\tif (pipe_command(&cmd, NULL, 0, &out, 0, &err, 0) < 0) {\n \t\t\terror(_(\"failed to run 'describe'\"));\n \t\t\tv->s = xstrdup(\"\");\n@@ -2066,24 +2118,36 @@ static void fill_missing_values(struct atom_value *val)\n  * pointed at by the ref itself; otherwise it is the object the\n  * ref (which is a tag) refers to.\n  */\n-static void grab_values(struct atom_value *val, int deref, struct object *obj, struct expand_data *data)\n+static int grab_values(struct atom_value *val, int deref, struct expand_data *data,\n+\t\t       const char *refname, struct strbuf *err, int *eaten)\n {\n \tvoid *buf = data->content;\n+\tint ret;\n \n-\tswitch (obj->type) {\n+\tswitch (data->type) {\n \tcase OBJ_TAG:\n-\t\tgrab_tag_values(val, deref, obj);\n+\t\tret = grab_tag_values(val, deref, data, refname, err, eaten);\n+\t\tif (ret < 0)\n+\t\t\tgoto out;\n+\n \t\tgrab_sub_body_contents(val, deref, data);\n \t\tgrab_person(\"tagger\", val, deref, buf);\n-\t\tgrab_describe_values(val, deref, obj);\n+\t\tgrab_describe_values(val, deref, data);\n \t\tbreak;\n \tcase OBJ_COMMIT:\n-\t\tgrab_commit_values(val, deref, obj);\n+\t\tret = grab_commit_values(val, deref, data, refname, err, eaten);\n+\t\tif (ret < 0)\n+\t\t\tgoto out;\n+\n \t\tgrab_sub_body_contents(val, deref, data);\n \t\tgrab_person(\"author\", val, deref, buf);\n \t\tgrab_person(\"committer\", val, deref, buf);\n-\t\tgrab_signature(val, deref, obj);\n-\t\tgrab_describe_values(val, deref, obj);\n+\n+\t\tret = grab_signature(val, deref, data, refname, err, eaten);\n+\t\tif (ret < 0)\n+\t\t\tgoto out;\n+\n+\t\tgrab_describe_values(val, deref, data);\n \t\tbreak;\n \tcase OBJ_TREE:\n \t\t/* grab_tree_values(val, deref, obj, buf, sz); */\n@@ -2094,8 +2158,12 @@ static void grab_values(struct atom_value *val, int deref, struct object *obj, s\n \t\tgrab_sub_body_contents(val, deref, data);\n \t\tbreak;\n \tdefault:\n-\t\tdie(\"Eh?  Object of type %d?\", obj->type);\n+\t\tdie(\"Eh?  Object of type %d?\", data->type);\n \t}\n+\n+\tret = 0;\n+out:\n+\treturn ret;\n }\n \n static inline char *copy_advance(char *dst, const char *src)\n@@ -2292,38 +2360,41 @@ static const char *get_refname(struct used_atom *atom, struct ref_array_item *re\n \treturn show_ref(&atom->u.refname, ref->refname);\n }\n \n-static int get_object(struct ref_array_item *ref, int deref, struct object **obj,\n+static int get_object(struct ref_array_item *ref, int deref,\n \t\t      struct expand_data *oi, struct strbuf *err)\n {\n-\t/* parse_object_buffer() will set eaten to 0 if free() will be needed */\n-\tint eaten = 1;\n+\t/* parse_object_buffer() will set eaten to 1 if free() will be needed */\n+\tint eaten = 0;\n+\tint ret;\n+\n \tif (oi->info.contentp) {\n \t\t/* We need to know that to use parse_object_buffer properly */\n \t\toi->info.sizep = &oi->size;\n \t\toi->info.typep = &oi->type;\n \t}\n+\n \tif (odb_read_object_info_extended(the_repository->objects, &oi->oid, &oi->info,\n-\t\t\t\t\t  OBJECT_INFO_LOOKUP_REPLACE))\n-\t\treturn strbuf_addf_ret(err, -1, _(\"missing object %s for %s\"),\n-\t\t\t\t       oid_to_hex(&oi->oid), ref->refname);\n+\t\t\t\t\t  OBJECT_INFO_LOOKUP_REPLACE)) {\n+\t\tret = strbuf_addf_ret(err, -1, _(\"missing object %s for %s\"),\n+\t\t\t\t      oid_to_hex(&oi->oid), ref->refname);\n+\t\tgoto out;\n+\t}\n \tif (oi->info.disk_sizep && oi->disk_size < 0)\n \t\tBUG(\"Object size is less than zero.\");\n \n \tif (oi->info.contentp) {\n-\t\t*obj = parse_object_buffer(the_repository, &oi->oid, oi->type, oi->size, oi->content, &eaten);\n-\t\tif (!*obj) {\n-\t\t\tif (!eaten)\n-\t\t\t\tfree(oi->content);\n-\t\t\treturn strbuf_addf_ret(err, -1, _(\"parse_object_buffer failed on %s for %s\"),\n-\t\t\t\t\t       oid_to_hex(&oi->oid), ref->refname);\n-\t\t}\n-\t\tgrab_values(ref->value, deref, *obj, oi);\n+\t\tret = grab_values(ref->value, deref, oi, ref->refname, err, &eaten);\n+\t\tif (ret < 0)\n+\t\t\tgoto out;\n \t}\n \n \tgrab_common_values(ref->value, deref, oi);\n+\tret = 0;\n+\n+out:\n \tif (!eaten)\n \t\tfree(oi->content);\n-\treturn 0;\n+\treturn ret;\n }\n \n static void populate_worktree_map(struct hashmap *map, struct worktree **worktrees)\n@@ -2376,7 +2447,6 @@ static char *get_worktree_path(const struct ref_array_item *ref)\n  */\n static int populate_value(struct ref_array_item *ref, struct strbuf *err)\n {\n-\tstruct object *obj;\n \tint i;\n \tstruct object_info empty = OBJECT_INFO_INIT;\n \tint ahead_behind_atoms = 0;\n@@ -2564,14 +2634,14 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)\n \n \n \toi.oid = ref->objectname;\n-\tif (get_object(ref, 0, &obj, &oi, err))\n+\tif (get_object(ref, 0, &oi, err))\n \t\treturn -1;\n \n \t/*\n \t * If there is no atom that wants to know about tagged\n \t * object, we are done.\n \t */\n-\tif (!need_tagged || (obj->type != OBJ_TAG))\n+\tif (!need_tagged || (oi.type != OBJ_TAG))\n \t\treturn 0;\n \n \t/*\n@@ -2589,7 +2659,7 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)\n \t\t}\n \t}\n \n-\treturn get_object(ref, 1, &obj, &oi_deref, err);\n+\treturn get_object(ref, 1, &oi_deref, err);\n }\n \n /*\n\n-- \n2.51.1.930.gacf6e81ea2.dirty\n\n"},{"id":"529537","messageId":"xmqq3479rzfq.fsf@gitster.g","threadId":"64260","inReplyTo":"20251023-b4-pks-ref-filter-skip-parsing-objects-v4-0-2be68ce82c9a@pks.im","subject":"Re: [PATCH v4 00/14] refs: improvements and fixes for peeling tags","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-10-23T23:06:01Z","receivedAt":"2025-10-23T23:06:04Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> originally, all I wanted to do was the last patch: a small performance\n> optimization that stops parsing objects in git-for-each-ref(1) unless we\n> really need to parse them. But that fix cause one specific test to fail,\n> and only with the reftable backend. So this led me down the rabbit hole\n> of tag peeling, ending up with this patch series.\n>\n> The series is structured like follows:\n>\n>   - Patches 1 to 8 refactor our codebase so that we don't have the\n>     `peel_iterated_object()` hack anymore. I just found it hard to\n>     follow and thought it shouldn't be too hard to get rid of it.\n>\n>   - Patches 9 and 10 remove infrastructure that we don't need anymore\n>     after the first couple of patches.\n>\n>   - Patches 11 to 13 fix a couple of issues with peeled tags that I\n>     found. The underlying issue is that tags store both the tagged\n>     object and their type, but this information may not match. We never\n>     verify the actual object type though when allocating the tagged\n>     object, so this only blows up much later.\n>\n>   - Patch 14 was my original motivation, a small performance\n>     optimization.\n>\n> I'm not particularly fond of the patches 11 to 13. It feels more like\n> playing whack-a-mole, and I very much assume that there still are edge\n> cases where we should properly verify the tagged object type. But\n> changing it in `parse_tag_buffer()` itself causes a bunch of tests to\n> fail where we intentionally create such corrupted tags. So I didn't\n> really dare to touch that part, to be honest.\n>\n> If anybody has suggestions for an alternative approach I'd be very open\n> to it.\n\nAre you still ;-), or are we ready to declare victory for now and\nmark the topic for 'next'?  It seems that another topic depends on\nthis and the topic itself is a good shape enough to advance.\n\nThanks.\n"},{"id":"529552","messageId":"aPsK0ydFVUKa7ReK@pks.im","threadId":"64260","inReplyTo":"xmqq3479rzfq.fsf@gitster.g","subject":"Re: [PATCH v4 00/14] refs: improvements and fixes for peeling tags","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-10-24T05:12:51Z","receivedAt":"2025-10-24T05:12:59Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Oct 23, 2025 at 04:06:01PM -0700, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> > I'm not particularly fond of the patches 11 to 13. It feels more like\n> > playing whack-a-mole, and I very much assume that there still are edge\n> > cases where we should properly verify the tagged object type. But\n> > changing it in `parse_tag_buffer()` itself causes a bunch of tests to\n> > fail where we intentionally create such corrupted tags. So I didn't\n> > really dare to touch that part, to be honest.\n> >\n> > If anybody has suggestions for an alternative approach I'd be very open\n> > to it.\n> \n> Are you still ;-), or are we ready to declare victory for now and\n> mark the topic for 'next'?  It seems that another topic depends on\n> this and the topic itself is a good shape enough to advance.\n\nYes and no. I still think that this is playing whack-a-mole, and I'm not\nparticularly happy about that. But nobody else had a better suggestion,\nand I don't see any downside with merging the current approach. So let's\nmerge it down.\n\nThanks!\n\nPatrick\n"},{"id":"530222","messageId":"20251104220748.GA2638011@coredump.intra.peff.net","threadId":"64260","inReplyTo":"20251023-b4-pks-ref-filter-skip-parsing-objects-v4-14-2be68ce82c9a@pks.im","subject":"Re: [PATCH v4 14/14] ref-filter: parse objects on demand","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-11-04T22:07:48Z","receivedAt":"2025-11-04T22:07:50Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Oct 23, 2025 at 09:16:23AM +0200, Patrick Steinhardt wrote:\n\n> -static int get_object(struct ref_array_item *ref, int deref, struct object **obj,\n> +static int get_object(struct ref_array_item *ref, int deref,\n>  \t\t      struct expand_data *oi, struct strbuf *err)\n>  {\n> -\t/* parse_object_buffer() will set eaten to 0 if free() will be needed */\n> -\tint eaten = 1;\n> +\t/* parse_object_buffer() will set eaten to 1 if free() will be needed */\n> +\tint eaten = 0;\n\nThis comment is surely wrong now, isn't it? It will be set to 1 if\nfree() is _not_ needed:\n\n> +out:\n>  \tif (!eaten)\n>  \t\tfree(oi->content);\n\n-Peff\n"},{"id":"530225","messageId":"xmqqcy5xnz7e.fsf@gitster.g","threadId":"64260","inReplyTo":"20251104220748.GA2638011@coredump.intra.peff.net","subject":"Re: [PATCH v4 14/14] ref-filter: parse objects on demand","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-11-04T23:40:53Z","receivedAt":"2025-11-04T23:40:56Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> On Thu, Oct 23, 2025 at 09:16:23AM +0200, Patrick Steinhardt wrote:\n>\n>> -static int get_object(struct ref_array_item *ref, int deref, struct object **obj,\n>> +static int get_object(struct ref_array_item *ref, int deref,\n>>  \t\t      struct expand_data *oi, struct strbuf *err)\n>>  {\n>> -\t/* parse_object_buffer() will set eaten to 0 if free() will be needed */\n>> -\tint eaten = 1;\n>> +\t/* parse_object_buffer() will set eaten to 1 if free() will be needed */\n>> +\tint eaten = 0;\n>\n> This comment is surely wrong now, isn't it? It will be set to 1 if\n> free() is _not_ needed:\n>\n>> +out:\n>>  \tif (!eaten)\n>>  \t\tfree(oi->content);\n>\n> -Peff\n\nWow.  Is it just the comment or the updated logic is upside down,\ntoo?\n\n"},{"id":"530228","messageId":"20251104235446.GA3667@coredump.intra.peff.net","threadId":"64260","inReplyTo":"xmqqcy5xnz7e.fsf@gitster.g","subject":"Re: [PATCH v4 14/14] ref-filter: parse objects on demand","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-11-04T23:54:46Z","receivedAt":"2025-11-04T23:54:48Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Nov 04, 2025 at 03:40:53PM -0800, Junio C Hamano wrote:\n\n> Jeff King <peff@peff.net> writes:\n> \n> > On Thu, Oct 23, 2025 at 09:16:23AM +0200, Patrick Steinhardt wrote:\n> >\n> >> -static int get_object(struct ref_array_item *ref, int deref, struct object **obj,\n> >> +static int get_object(struct ref_array_item *ref, int deref,\n> >>  \t\t      struct expand_data *oi, struct strbuf *err)\n> >>  {\n> >> -\t/* parse_object_buffer() will set eaten to 0 if free() will be needed */\n> >> -\tint eaten = 1;\n> >> +\t/* parse_object_buffer() will set eaten to 1 if free() will be needed */\n> >> +\tint eaten = 0;\n> >\n> > This comment is surely wrong now, isn't it? It will be set to 1 if\n> > free() is _not_ needed:\n> >\n> >> +out:\n> >>  \tif (!eaten)\n> >>  \t\tfree(oi->content);\n> >\n> > -Peff\n> \n> Wow.  Is it just the comment or the updated logic is upside down,\n> too?\n\nI think the logic is fine. The meaning of \"eaten\" did not change. It's\njust that some code paths will not bother calling parse_object_buffer()\nnow (if no atoms need it). So we need to default to \"0\" (the buffer must\nbe freed) for those cases. And if parse_object_buffer() is called, it\nwill always correctly set the value to 1.\n\nThe new code does mean that if contentp is NULL, we will always call\nfree(oi->content), even though nobody would ever have set it. But\npresumably it was initialized to NULL in that case and the free is a\nnoop.\n\n-Peff\n"}]}