{"thread":{"id":"60515","subject":"[PATCH 0/4] refs: remove virtual `delete_refs()` function","startedAt":"2023-11-14T08:58:40Z","lastAt":"2023-11-14T08:58:55Z","messageCount":5,"participants":["Patrick Steinhardt"],"isPatch":true,"patchVersion":1,"patchTotal":4},"messages":[{"id":"484848","messageId":"cover.1699951815.git.ps@pks.im","threadId":"60515","inReplyTo":null,"subject":"[PATCH 0/4] refs: remove virtual `delete_refs()` function","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-11-14T08:58:34Z","receivedAt":"2023-11-14T08:58:40Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\nthis patch series refactors the virtual `delete_refs()` function to\ninstead be implemented generically via a single reference transaction.\n\nThe main intent of this patch series is to reduce complexity that we\nhave in the reference backends so that it becomes easier to implement\nnew backends that have the same semantics as others. But at the same\ntime, benchmarks show that the new generic code is even faster than the\nold backend-specific code. This is mostly because we avoid the overhead\nof per-reference transactions when deleting many references, but also\nbecause the transactional code in the files backend knows to avoid\nrewriting the packed-refs file in case it contains none of the refs that\nare to be deleted.\n\nSo in the end we have less duplicate code in the files backend, make it\neasier to implement new backends, and have faster deletion of many refs\nin the files backend.\n\nPatrick\n\nPatrick Steinhardt (4):\n  t5510: ensure that the packed-refs file needs locking\n  refs/files: use transactions to delete references\n  refs: deduplicate code to delete references\n  refs: remove `delete_refs` callback from backends\n\n refs.c                | 48 ++++++++++++++++++++++++++++++++++++++---\n refs/debug.c          | 15 -------------\n refs/files-backend.c  | 49 ------------------------------------------\n refs/packed-backend.c | 50 -------------------------------------------\n refs/refs-internal.h  |  3 ---\n t/t5510-fetch.sh      |  1 +\n 6 files changed, 46 insertions(+), 120 deletions(-)\n\n\nbase-commit: e0939bec273052b1a8d69db4a3f7c87aaf83e220\n-- \n2.42.0\n\n"},{"id":"484849","messageId":"4be411fa413fda675314b8507d41f4141a1f97ac.1699951815.git.ps@pks.im","threadId":"60515","inReplyTo":"cover.1699951815.git.ps@pks.im","subject":"[PATCH 1/4] t5510: ensure that the packed-refs file needs locking","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-11-14T08:58:38Z","receivedAt":"2023-11-14T08:58:43Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"One of the tests in t5510 asserts that a `git fetch --prune` detects\nfailures to prune branches. This is done by locking the packed-refs\nfile, which would then later lead to a locking issue when Git tries to\nrewrite the file to prune the branches from it.\n\nInterestingly though, we do not pack the about-to-be-pruned branch into\nthe packed-refs file, so it never even contained that branch in the\nfirst place. While this is good enough right now because the pruning\nwill always lock the file regardless of whether it contains the branch\nor not, this is a mere implementation detail. In fact, we're about to\nrewrite branch deletions to make use of the ref transaction interface,\nwhich knows to skip rewrites of the packed-refs file in the case where\nit does not contain the branches in the first place, and this will break\nthe test.\n\nPrepare the test for that change by packing the refs before trying to\nprune them.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n t/t5510-fetch.sh | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex dcadd56d3a..79592a3b0a 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -169,6 +169,7 @@ test_expect_success REFFILES 'fetch --prune fails to delete branches' '\n \tgit clone . prune-fail &&\n \tcd prune-fail &&\n \tgit update-ref refs/remotes/origin/extrabranch main &&\n+\tgit pack-refs --all &&\n \t: this will prevent --prune from locking packed-refs for deleting refs, but adding loose refs still succeeds  &&\n \t>.git/packed-refs.new &&\n \n-- \n2.42.0\n\n"},{"id":"484850","messageId":"2cfa0245fb089ed9d85cd44dad38c7f261e2200a.1699951815.git.ps@pks.im","threadId":"60515","inReplyTo":"cover.1699951815.git.ps@pks.im","subject":"[PATCH 2/4] refs/files: use transactions to delete references","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-11-14T08:58:42Z","receivedAt":"2023-11-14T08:58:47Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In the `files_delete_refs()` callback function of the files backend we\nimplement deletion of references. This is done in two steps:\n\n    1. We lock the packed-refs file and delete all references from it in\n       a single transaction.\n\n    2. We delete all loose references via separate calls to\n       `refs_delete_ref()`.\n\nThese steps essentially duplicate the logic around locking and deletion\norder that we already have in the transactional interfaces, where we do\nknow to lock and evict references from the packed-refs file. Despite the\nfact that we duplicate the logic, it's also less efficient than if we\nused a single generic transaction:\n\n    - The transactional interface knows to skip locking of the packed\n      refs in case they don't contain any of the refs which are about to\n      be deleted.\n\n    - We end up creating N+1 separate reference transactions, one for\n      the packed-refs file and N for the individual loose references.\n\nRefactor the code to instead delete references via a single transaction.\nAs we don't assert the expected old object ID this is equivalent to the\nprevious behaviour, and we already do the same in the packed-refs\nbackend.\n\nDespite the fact that the result is simpler to reason about, this change\nalso results in improved performance. The following benchmarks have been\nexecuted in linux.git:\n\n```\n$ hyperfine -n '{rev}, packed={packed} refcount={refcount}' \\\n    -L packed true,false -L refcount 1,1000 -L rev master,pks-ref-store-generic-delete-refs \\\n    --setup 'git -C /home/pks/Development/git switch --detach {rev} && make -C /home/pks/Development/git -j17' \\\n    --prepare 'printf \"create refs/heads/new-branch-%d HEAD\\n\" $(seq {refcount}) | git -C /home/pks/Reproduction/linux.git update-ref --stdin && if test {packed} = true; then git pack-refs --all; fi' \\\n    --warmup=10 \\\n    '/home/pks/Development/git/bin-wrappers/git -C /home/pks/Reproduction/linux.git branch -d new-branch-{1..{refcount}}'\n\nBenchmark 1: master packed=true refcount=1\n  Time (mean ± σ):       7.8 ms ±   1.6 ms    [User: 3.4 ms, System: 4.4 ms]\n  Range (min … max):     5.5 ms …  11.0 ms    120 runs\n\nBenchmark 2: master packed=false refcount=1\n  Time (mean ± σ):       7.0 ms ±   1.1 ms    [User: 3.2 ms, System: 3.8 ms]\n  Range (min … max):     5.7 ms …   9.8 ms    180 runs\n\nBenchmark 3: master packed=true refcount=1000\n  Time (mean ± σ):     283.8 ms ±   5.2 ms    [User: 45.7 ms, System: 231.5 ms]\n  Range (min … max):   276.7 ms … 291.6 ms    10 runs\n\nBenchmark 4: master packed=false refcount=1000\n  Time (mean ± σ):     284.4 ms ±   5.3 ms    [User: 44.2 ms, System: 233.6 ms]\n  Range (min … max):   277.1 ms … 293.3 ms    10 runs\n\nBenchmark 5: generic-delete-refs packed=true refcount=1\n  Time (mean ± σ):       6.2 ms ±   1.8 ms    [User: 2.3 ms, System: 3.9 ms]\n  Range (min … max):     4.1 ms …  12.2 ms    142 runs\n\nBenchmark 6: generic-delete-refs packed=false refcount=1\n  Time (mean ± σ):       7.1 ms ±   1.4 ms    [User: 2.8 ms, System: 4.3 ms]\n  Range (min … max):     4.2 ms …  10.8 ms    157 runs\n\nBenchmark 7: generic-delete-refs packed=true refcount=1000\n  Time (mean ± σ):     198.9 ms ±   1.7 ms    [User: 29.5 ms, System: 165.7 ms]\n  Range (min … max):   196.1 ms … 201.4 ms    10 runs\n\nBenchmark 8: generic-delete-refs packed=false refcount=1000\n  Time (mean ± σ):     199.7 ms ±   7.8 ms    [User: 32.2 ms, System: 163.2 ms]\n  Range (min … max):   193.8 ms … 220.7 ms    10 runs\n\nSummary\n  generic-delete-refs packed=true refcount=1 ran\n    1.14 ± 0.37 times faster than master packed=false refcount=1\n    1.15 ± 0.40 times faster than generic-delete-refs packed=false refcount=1\n    1.26 ± 0.44 times faster than master packed=true refcount=1\n   32.24 ± 9.17 times faster than generic-delete-refs packed=true refcount=1000\n   32.36 ± 9.29 times faster than generic-delete-refs packed=false refcount=1000\n   46.00 ± 13.10 times faster than master packed=true refcount=1000\n   46.10 ± 13.13 times faster than master packed=false refcount=1000\n```\n\nEspecially in the case where we have many references we can see a clear\nperformance speedup of nearly 30%.\n\nThis is in contrast to the stated objecive in a27dcf89b68 (refs: make\ndelete_refs() virtual, 2016-09-04), where the virtual `delete_refs()`\nfunction was introduced with the intent to speed things up rather than\nmaking things slower. So it seems like we have outlived the need for a\nvirtual function.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs/files-backend.c | 66 +++++++++++++++++++++++---------------------\n 1 file changed, 34 insertions(+), 32 deletions(-)\n\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex db5c0c7a72..778d3a96ba 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -1268,49 +1268,51 @@ static int files_pack_refs(struct ref_store *ref_store,\n static int files_delete_refs(struct ref_store *ref_store, const char *msg,\n \t\t\t     struct string_list *refnames, unsigned int flags)\n {\n-\tstruct files_ref_store *refs =\n-\t\tfiles_downcast(ref_store, REF_STORE_WRITE, \"delete_refs\");\n+\tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n-\tint i, result = 0;\n+\tstruct string_list_item *item;\n+\tint ret = 0, failures = 0;\n \n \tif (!refnames->nr)\n \t\treturn 0;\n \n-\tif (packed_refs_lock(refs->packed_ref_store, 0, &err))\n-\t\tgoto error;\n-\n-\tif (refs_delete_refs(refs->packed_ref_store, msg, refnames, flags)) {\n-\t\tpacked_refs_unlock(refs->packed_ref_store);\n-\t\tgoto error;\n+\t/*\n+\t * Since we don't check the references' old_oids, the\n+\t * individual updates can't fail, so we can pack all of the\n+\t * updates into a single transaction.\n+\t */\n+\ttransaction = ref_store_transaction_begin(ref_store, &err);\n+\tif (!transaction) {\n+\t\tret = error(\"%s\", err.buf);\n+\t\tgoto out;\n \t}\n \n-\tpacked_refs_unlock(refs->packed_ref_store);\n-\n-\tfor (i = 0; i < refnames->nr; i++) {\n-\t\tconst char *refname = refnames->items[i].string;\n-\n-\t\tif (refs_delete_ref(&refs->base, msg, refname, NULL, flags))\n-\t\t\tresult |= error(_(\"could not remove reference %s\"), refname);\n+\tfor_each_string_list_item(item, refnames) {\n+\t\tret = ref_transaction_delete(transaction, item->string,\n+\t\t\t\t\t     NULL, flags, msg, &err);\n+\t\tif (ret) {\n+\t\t\twarning(_(\"could not delete reference %s: %s\"),\n+\t\t\t\titem->string, err.buf);\n+\t\t\tstrbuf_reset(&err);\n+\t\t\tfailures = 1;\n+\t\t}\n \t}\n \n-\tstrbuf_release(&err);\n-\treturn result;\n-\n-error:\n-\t/*\n-\t * If we failed to rewrite the packed-refs file, then it is\n-\t * unsafe to try to remove loose refs, because doing so might\n-\t * expose an obsolete packed value for a reference that might\n-\t * even point at an object that has been garbage collected.\n-\t */\n-\tif (refnames->nr == 1)\n-\t\terror(_(\"could not delete reference %s: %s\"),\n-\t\t      refnames->items[0].string, err.buf);\n-\telse\n-\t\terror(_(\"could not delete references: %s\"), err.buf);\n+\tret = ref_transaction_commit(transaction, &err);\n+\tif (ret) {\n+\t\tif (refnames->nr == 1)\n+\t\t\terror(_(\"could not delete reference %s: %s\"),\n+\t\t\t      refnames->items[0].string, err.buf);\n+\t\telse\n+\t\t\terror(_(\"could not delete references: %s\"), err.buf);\n+\t}\n \n+out:\n+\tif (!ret && failures)\n+\t\tret = -1;\n+\tref_transaction_free(transaction);\n \tstrbuf_release(&err);\n-\treturn -1;\n+\treturn ret;\n }\n \n /*\n-- \n2.42.0\n\n"},{"id":"484851","messageId":"bb9f45543daf32e6485a57781e1a71e9503936f8.1699951815.git.ps@pks.im","threadId":"60515","inReplyTo":"cover.1699951815.git.ps@pks.im","subject":"[PATCH 3/4] refs: deduplicate code to delete references","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-11-14T08:58:46Z","receivedAt":"2023-11-14T08:58:51Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Both the files and the packed-refs reference backends now use the same\ngeneric transactions-based code to delete references. Let's pull these\nimplementations up into `refs_delete_refs()` to deduplicate the code.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs.c                | 48 ++++++++++++++++++++++++++++++++++++++++---\n refs/files-backend.c  | 46 +----------------------------------------\n refs/packed-backend.c | 45 +---------------------------------------\n 3 files changed, 47 insertions(+), 92 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex fcae5dddc6..16bfa21df7 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2599,13 +2599,55 @@ void ref_transaction_for_each_queued_update(struct ref_transaction *transaction,\n int refs_delete_refs(struct ref_store *refs, const char *logmsg,\n \t\t     struct string_list *refnames, unsigned int flags)\n {\n+\tstruct ref_transaction *transaction;\n+\tstruct strbuf err = STRBUF_INIT;\n+\tstruct string_list_item *item;\n+\tint ret = 0, failures = 0;\n \tchar *msg;\n-\tint retval;\n+\n+\tif (!refnames->nr)\n+\t\treturn 0;\n \n \tmsg = normalize_reflog_message(logmsg);\n-\tretval = refs->be->delete_refs(refs, msg, refnames, flags);\n+\n+\t/*\n+\t * Since we don't check the references' old_oids, the\n+\t * individual updates can't fail, so we can pack all of the\n+\t * updates into a single transaction.\n+\t */\n+\ttransaction = ref_store_transaction_begin(refs, &err);\n+\tif (!transaction) {\n+\t\tret = error(\"%s\", err.buf);\n+\t\tgoto out;\n+\t}\n+\n+\tfor_each_string_list_item(item, refnames) {\n+\t\tret = ref_transaction_delete(transaction, item->string,\n+\t\t\t\t\t     NULL, flags, msg, &err);\n+\t\tif (ret) {\n+\t\t\twarning(_(\"could not delete reference %s: %s\"),\n+\t\t\t\titem->string, err.buf);\n+\t\t\tstrbuf_reset(&err);\n+\t\t\tfailures = 1;\n+\t\t}\n+\t}\n+\n+\tret = ref_transaction_commit(transaction, &err);\n+\tif (ret) {\n+\t\tif (refnames->nr == 1)\n+\t\t\terror(_(\"could not delete reference %s: %s\"),\n+\t\t\t      refnames->items[0].string, err.buf);\n+\t\telse\n+\t\t\terror(_(\"could not delete references: %s\"), err.buf);\n+\t}\n+\n+out:\n+\tif (!ret && failures)\n+\t\tret = -1;\n+\tref_transaction_free(transaction);\n+\tstrbuf_release(&err);\n \tfree(msg);\n-\treturn retval;\n+\treturn ret;\n }\n \n int delete_refs(const char *msg, struct string_list *refnames,\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 778d3a96ba..8d28810e67 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -1268,51 +1268,7 @@ static int files_pack_refs(struct ref_store *ref_store,\n static int files_delete_refs(struct ref_store *ref_store, const char *msg,\n \t\t\t     struct string_list *refnames, unsigned int flags)\n {\n-\tstruct ref_transaction *transaction;\n-\tstruct strbuf err = STRBUF_INIT;\n-\tstruct string_list_item *item;\n-\tint ret = 0, failures = 0;\n-\n-\tif (!refnames->nr)\n-\t\treturn 0;\n-\n-\t/*\n-\t * Since we don't check the references' old_oids, the\n-\t * individual updates can't fail, so we can pack all of the\n-\t * updates into a single transaction.\n-\t */\n-\ttransaction = ref_store_transaction_begin(ref_store, &err);\n-\tif (!transaction) {\n-\t\tret = error(\"%s\", err.buf);\n-\t\tgoto out;\n-\t}\n-\n-\tfor_each_string_list_item(item, refnames) {\n-\t\tret = ref_transaction_delete(transaction, item->string,\n-\t\t\t\t\t     NULL, flags, msg, &err);\n-\t\tif (ret) {\n-\t\t\twarning(_(\"could not delete reference %s: %s\"),\n-\t\t\t\titem->string, err.buf);\n-\t\t\tstrbuf_reset(&err);\n-\t\t\tfailures = 1;\n-\t\t}\n-\t}\n-\n-\tret = ref_transaction_commit(transaction, &err);\n-\tif (ret) {\n-\t\tif (refnames->nr == 1)\n-\t\t\terror(_(\"could not delete reference %s: %s\"),\n-\t\t\t      refnames->items[0].string, err.buf);\n-\t\telse\n-\t\t\terror(_(\"could not delete references: %s\"), err.buf);\n-\t}\n-\n-out:\n-\tif (!ret && failures)\n-\t\tret = -1;\n-\tref_transaction_free(transaction);\n-\tstrbuf_release(&err);\n-\treturn ret;\n+\treturn refs_delete_refs(ref_store, msg, refnames, flags);\n }\n \n /*\ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 59c78d7941..1589577005 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -1691,50 +1691,7 @@ static int packed_initial_transaction_commit(struct ref_store *ref_store UNUSED,\n static int packed_delete_refs(struct ref_store *ref_store, const char *msg,\n \t\t\t     struct string_list *refnames, unsigned int flags)\n {\n-\tstruct packed_ref_store *refs =\n-\t\tpacked_downcast(ref_store, REF_STORE_WRITE, \"delete_refs\");\n-\tstruct strbuf err = STRBUF_INIT;\n-\tstruct ref_transaction *transaction;\n-\tstruct string_list_item *item;\n-\tint ret;\n-\n-\t(void)refs; /* We need the check above, but don't use the variable */\n-\n-\tif (!refnames->nr)\n-\t\treturn 0;\n-\n-\t/*\n-\t * Since we don't check the references' old_oids, the\n-\t * individual updates can't fail, so we can pack all of the\n-\t * updates into a single transaction.\n-\t */\n-\n-\ttransaction = ref_store_transaction_begin(ref_store, &err);\n-\tif (!transaction)\n-\t\treturn -1;\n-\n-\tfor_each_string_list_item(item, refnames) {\n-\t\tif (ref_transaction_delete(transaction, item->string, NULL,\n-\t\t\t\t\t   flags, msg, &err)) {\n-\t\t\twarning(_(\"could not delete reference %s: %s\"),\n-\t\t\t\titem->string, err.buf);\n-\t\t\tstrbuf_reset(&err);\n-\t\t}\n-\t}\n-\n-\tret = ref_transaction_commit(transaction, &err);\n-\n-\tif (ret) {\n-\t\tif (refnames->nr == 1)\n-\t\t\terror(_(\"could not delete reference %s: %s\"),\n-\t\t\t      refnames->items[0].string, err.buf);\n-\t\telse\n-\t\t\terror(_(\"could not delete references: %s\"), err.buf);\n-\t}\n-\n-\tref_transaction_free(transaction);\n-\tstrbuf_release(&err);\n-\treturn ret;\n+\treturn refs_delete_refs(ref_store, msg, refnames, flags);\n }\n \n static int packed_pack_refs(struct ref_store *ref_store UNUSED,\n-- \n2.42.0\n\n"},{"id":"484852","messageId":"0e61c42a92f5fbaba28d7326645a6f1903a5ae66.1699951815.git.ps@pks.im","threadId":"60515","inReplyTo":"cover.1699951815.git.ps@pks.im","subject":"[PATCH 4/4] refs: remove `delete_refs` callback from backends","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2023-11-14T08:58:50Z","receivedAt":"2023-11-14T08:58:55Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Now that `refs_delete_refs` is implemented in a generic way via the ref\ntransaction interfaces there are no callers left that invoke the\n`delete_refs` callback anymore. Remove it from all of our backends.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n refs/debug.c          | 15 ---------------\n refs/files-backend.c  |  7 -------\n refs/packed-backend.c |  7 -------\n refs/refs-internal.h  |  3 ---\n 4 files changed, 32 deletions(-)\n\ndiff --git a/refs/debug.c b/refs/debug.c\nindex b7ffc4ce67..83b7a0ba65 100644\n--- a/refs/debug.c\n+++ b/refs/debug.c\n@@ -143,20 +143,6 @@ static int debug_create_symref(struct ref_store *ref_store,\n \treturn res;\n }\n \n-static int debug_delete_refs(struct ref_store *ref_store, const char *msg,\n-\t\t\t     struct string_list *refnames, unsigned int flags)\n-{\n-\tstruct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;\n-\tint res =\n-\t\tdrefs->refs->be->delete_refs(drefs->refs, msg, refnames, flags);\n-\tint i;\n-\ttrace_printf_key(&trace_refs, \"delete_refs {\\n\");\n-\tfor (i = 0; i < refnames->nr; i++)\n-\t\ttrace_printf_key(&trace_refs, \"%s\\n\", refnames->items[i].string);\n-\ttrace_printf_key(&trace_refs, \"}: %d\\n\", res);\n-\treturn res;\n-}\n-\n static int debug_rename_ref(struct ref_store *ref_store, const char *oldref,\n \t\t\t    const char *newref, const char *logmsg)\n {\n@@ -458,7 +444,6 @@ struct ref_storage_be refs_be_debug = {\n \n \t.pack_refs = debug_pack_refs,\n \t.create_symref = debug_create_symref,\n-\t.delete_refs = debug_delete_refs,\n \t.rename_ref = debug_rename_ref,\n \t.copy_ref = debug_copy_ref,\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 8d28810e67..ad8b1d143f 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -1265,12 +1265,6 @@ static int files_pack_refs(struct ref_store *ref_store,\n \treturn 0;\n }\n \n-static int files_delete_refs(struct ref_store *ref_store, const char *msg,\n-\t\t\t     struct string_list *refnames, unsigned int flags)\n-{\n-\treturn refs_delete_refs(ref_store, msg, refnames, flags);\n-}\n-\n /*\n  * People using contrib's git-new-workdir have .git/logs/refs ->\n  * /some/other/path/.git/logs/refs, and that may live on another device.\n@@ -3258,7 +3252,6 @@ struct ref_storage_be refs_be_files = {\n \n \t.pack_refs = files_pack_refs,\n \t.create_symref = files_create_symref,\n-\t.delete_refs = files_delete_refs,\n \t.rename_ref = files_rename_ref,\n \t.copy_ref = files_copy_ref,\n \ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 1589577005..b9fa097a29 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -1688,12 +1688,6 @@ static int packed_initial_transaction_commit(struct ref_store *ref_store UNUSED,\n \treturn ref_transaction_commit(transaction, err);\n }\n \n-static int packed_delete_refs(struct ref_store *ref_store, const char *msg,\n-\t\t\t     struct string_list *refnames, unsigned int flags)\n-{\n-\treturn refs_delete_refs(ref_store, msg, refnames, flags);\n-}\n-\n static int packed_pack_refs(struct ref_store *ref_store UNUSED,\n \t\t\t    struct pack_refs_opts *pack_opts UNUSED)\n {\n@@ -1722,7 +1716,6 @@ struct ref_storage_be refs_be_packed = {\n \n \t.pack_refs = packed_pack_refs,\n \t.create_symref = NULL,\n-\t.delete_refs = packed_delete_refs,\n \t.rename_ref = NULL,\n \t.copy_ref = NULL,\n \ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 9db8aec4da..4af83bf9a5 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -553,8 +553,6 @@ typedef int create_symref_fn(struct ref_store *ref_store,\n \t\t\t     const char *ref_target,\n \t\t\t     const char *refs_heads_master,\n \t\t\t     const char *logmsg);\n-typedef int delete_refs_fn(struct ref_store *ref_store, const char *msg,\n-\t\t\t   struct string_list *refnames, unsigned int flags);\n typedef int rename_ref_fn(struct ref_store *ref_store,\n \t\t\t  const char *oldref, const char *newref,\n \t\t\t  const char *logmsg);\n@@ -677,7 +675,6 @@ struct ref_storage_be {\n \n \tpack_refs_fn *pack_refs;\n \tcreate_symref_fn *create_symref;\n-\tdelete_refs_fn *delete_refs;\n \trename_ref_fn *rename_ref;\n \tcopy_ref_fn *copy_ref;\n \n-- \n2.42.0\n\n"}]}