[PATCH v5 0/3] refs: report old OIDs for batched deletions
- From
Maciej Ciemborowicz <maciej.ciemborowicz@gmail.com>
- Date
- Sep 23, 2026, 21:04 UTC
- Message-ID
- <cover.1790196627.git.maciej.ciemborowicz@gmail.com>
- In-Reply-To
- <cover.1790113781.git.maciej.ciemborowicz@gmail.com>
Thanks for catching the broken intermediate commits. This version updates every refs_delete_refs() call site in 1/3, using NULL for the optional data that is not supplied until the later patches. Each patch now builds on its predecessor. The final tree is identical to v4.
This follows up on the reference-transaction bug report at [1].
The reference-transaction hook receives zero as both the old and new OID when branch, tag, fetch, and remote delete refs through refs_delete_refs(). Those callers already know the values that they selected for deletion.
Teach refs_delete_refs() to accept a parallel array of expected old OIDs and pass them into the transaction. Besides making hook records useful, this restores conditional deletion for branch and tag and adds it to pruning without additional ref reads. Non-atomic batches preserve best-effort behavior, while atomic fetches remain all-or-nothing.
Changes since v4:
* Update all refs_delete_refs() call sites in 1/3 for the new signature. * Verify that 1/3, 1/3--2/3, and the complete series each build with DEVELOPER=1.
The focused reference-transaction tests pass with SHA-1 and SHA-256 using both the files and reftable backends. The full test suite passed on the identical final tree in v4.
[1] https://lore.kernel.org/git/CACQ=SRGTTdQ+dHXhN6F52dBv5KxZBRfk_Em2fvmEmGJDoB6oTg@mail.gmail.com/
Maciej Ciemborowicz (3): refs: allow callers to supply old OIDs for batch deletion branch, tag: retain old OIDs in batched deletions fetch, remote: retain old OIDs when pruning refs
bisect.c | 2 +- builtin/branch.c | 7 +- builtin/fetch.c | 30 ++++-- builtin/remote.c | 47 +++++++-- builtin/tag.c | 28 ++++-- refs.c | 67 ++++++++++--- refs.h | 31 ++++-- t/helper/test-ref-store.c | 2 +- t/t1416-ref-transaction-hooks.sh | 160 +++++++++++++++++++++++++++++++ 9 files changed, 324 insertions(+), 50 deletions(-)
Range-diff against v4:
1: f4a9d065c ! 1: 9b76cc2c4 refs: allow callers to supply old OIDs for batch deletion
@@ builtin/branch.c: static int delete_branches(int argc, const char **argv, int ki
if (!(flags & DELETE_BRANCH_DRY_RUN) &&
- refs_delete_refs(get_main_ref_store(the_repository), NULL, &refs_to_delete, REF_NO_DEREF))
+ refs_delete_refs(get_main_ref_store(the_repository), NULL,
-+ &refs_to_delete, NULL, REF_NO_DEREF))
++ &refs_to_delete, NULL, NULL, REF_NO_DEREF))
ret = 1;
for_each_string_list_item(item, &refs_to_delete) {
@@ builtin/fetch.c: static int prune_refs(struct display_state *display_state,
result = refs_delete_refs(get_main_ref_store(the_repository),
"fetch: prune", &refnames,
- 0);
-+ NULL, 0);
++ NULL, NULL, 0);
}
}
@@ builtin/remote.c: static int prune_remote(const char *remote, int dry_run)
result |= refs_delete_refs(get_main_ref_store(the_repository),
- "remote: prune", &refs_to_prune, 0);
+ "remote: prune", &refs_to_prune,
-+ NULL, 0);
++ NULL, NULL, 0);
for_each_string_list_item(item, &states.stale) {
const char *refname = item->util;
@@ builtin/tag.c: static int delete_tags(const char **argv)
result = for_each_tag_name(argv, collect_tags, (void *)&refs_to_delete);
- if (refs_delete_refs(get_main_ref_store(the_repository), NULL, &refs_to_delete, REF_NO_DEREF))
+ if (refs_delete_refs(get_main_ref_store(the_repository), NULL,
-+ &refs_to_delete, NULL, REF_NO_DEREF))
++ &refs_to_delete, NULL, NULL, REF_NO_DEREF))
result = 1;
for_each_string_list_item(item, &refs_to_delete) {
2: 918c97d2b ! 2: 6a8401c44 branch, tag: retain old OIDs in batched deletions
@@ builtin/branch.c: static int delete_branches(int argc, const char **argv, int ki
if (!(flags & DELETE_BRANCH_DRY_RUN) &&
refs_delete_refs(get_main_ref_store(the_repository), NULL,
-- &refs_to_delete, NULL, REF_NO_DEREF))
+- &refs_to_delete, NULL, NULL, REF_NO_DEREF))
+ &refs_to_delete, &old_oids, NULL, REF_NO_DEREF))
ret = 1;
@@ builtin/tag.c: static int for_each_tag_name(const char **argv, each_tag_name_fn
- result = for_each_tag_name(argv, collect_tags, (void *)&refs_to_delete);
+ result = for_each_tag_name(argv, collect_tags, &data);
if (refs_delete_refs(get_main_ref_store(the_repository), NULL,
-- &refs_to_delete, NULL, REF_NO_DEREF))
+- &refs_to_delete, NULL, NULL, REF_NO_DEREF))
+ &data.refs, &data.old_oids, NULL, REF_NO_DEREF))
result = 1;
3: 6f34853c7 ! 3: 541da44c3 fetch, remote: retain old OIDs when pruning refs
@@ builtin/fetch.c: static int prune_refs(struct display_state *display_state,
+ }
result = refs_delete_refs(get_main_ref_store(the_repository),
"fetch: prune", &refnames,
-- NULL, 0);
+- NULL, NULL, 0);
+ &old_oids, &failed_refs, 0);
+ if (result && !failed_refs.nr)
+ goto cleanup;
@@ builtin/remote.c: static int prune_remote(const char *remote, int dry_run)
+ if (!dry_run) {
result |= refs_delete_refs(get_main_ref_store(the_repository),
"remote: prune", &refs_to_prune,
-- NULL, 0);
+- NULL, NULL, 0);
+ &old_oids, &failed_refs, 0);
+ if (result && !failed_refs.nr)
+ goto cleanup;-- 2.39.3 (Apple Git-146)