{"thread":{"id":"63726","subject":"[PATCH v4 0/3] fetch --prune performance problem","startedAt":"2025-07-02T01:13:20Z","lastAt":"2025-07-08T01:36:14Z","messageCount":8,"participants":["Phil Hord","Junio C Hamano","Jeff King"],"isPatch":true,"patchVersion":4,"patchTotal":3},"messages":[{"id":"521134","messageId":"20250702011214.2835529-2-phil.hord@gmail.com","threadId":"63726","inReplyTo":null,"subject":"[PATCH v4 0/3] fetch --prune performance problem","fromName":"Phil Hord","fromEmail":"phil.hord@gmail.com","sentAt":"2025-07-02T01:12:12Z","receivedAt":"2025-07-02T01:13:20Z","isPatch":true,"sender":{"key":"phil.hord@gmail.com","avatar":"https://avatars.githubusercontent.com/u/123908?v=4"},"body":"From: Phil Hord <phil.hord@gmail.com>\n\n`git fetch --prune` runs in O(N^2) time normally. This happens because the code\niterates over each ref to be pruned to display its status. In a repo with\n174,000 refs, where I was pruning 15,000 refs, the current code made 2.6 billion\ncalls to strcmp and consumed 470 seconds of CPU. After this change, the same\noperation completes in under 1 second.\n\nThe loop looks like this:\n\n    for p in prune_refs { for ref in all_refs { if p == ref { ... }}}\n\nThat loop runs only to check for and report newly dangling refs. A workaround to\navoid this slowness is to run with `-q` to bypass this check.\n\nThere is similar check/report functionality in `git remote prune`, but it uses a\nmore efficient method to check for dangling refs. prune_refs is first sorted, so\nit can be searched in O(logN), so this loop is O(N*logN).\n\n    for ref in all_refs { if ref in prune_refs { ... }}\n\nWe can use that function instead, with some minor cleanup to the output to deal\nwith the ordering being changed.\n\nThis patch version only adds the deleted branch name to the output of the dangling\nsym refs since the ordering has changed. This is only a minor cleanup and was\nnot actually needed since, for example, `git origin prune` already did not\nmind losing track of this information in its output. But now it is improved\nto be more explicit.\n\nV3 forgot to include the first commit in the series (I forgot it grew).\nSo here's V4.\n\nPhil Hord (3):\n  fetch-prune: optimize dangling-ref reporting\n  refs: remove old refs_warn_dangling_symref\n  clean up interface for refs_warn_dangling_symrefs\n\n builtin/fetch.c  | 19 ++++++++-----------\n builtin/remote.c |  5 +----\n refs.c           | 34 ++++++++++++----------------------\n refs.h           |  5 ++---\n 4 files changed, 23 insertions(+), 40 deletions(-)\n\n-- \n2.50.0.149.g1bab277d62\n\n"},{"id":"521135","messageId":"20250702011214.2835529-3-phil.hord@gmail.com","threadId":"63726","inReplyTo":"20250702011214.2835529-2-phil.hord@gmail.com","subject":"[PATCH v4 1/3] fetch-prune: optimize dangling-ref reporting","fromName":"Phil Hord","fromEmail":"phil.hord@gmail.com","sentAt":"2025-07-02T01:12:13Z","receivedAt":"2025-07-02T01:13:30Z","isPatch":true,"sender":{"key":"phil.hord@gmail.com","avatar":"https://avatars.githubusercontent.com/u/123908?v=4"},"body":"From: Phil Hord <phil.hord@gmail.com>\n\nWhen pruning during `git fetch` we check each pruned ref against the\nref_store one at a time to decide whether to report it as dangling.\nThis causes every local ref to be scanned for each ref being pruned.\n\nIf there are N refs in the repo and M refs being pruned, this code is\nO(M*N). However, `git remote prune` uses a very similar function that\nis only O(N*log(M)).\n\nRemove the wasteful ref scanning for each pruned ref and use the faster\nversion already available in refs_warn_dangling_symrefs. Change the\nmessage to include the original refname since the message is no longer\nprinted immediately after the line that did just print the refname.\n\nIn a repo with 126,000 refs, where I was pruning 28,000 refs, this\ncode made about 3.6 billion calls to strcmp and consumed 410 seconds\nof CPU. (Invariably in that time, my remote would timeout and the\nfetch would fail anyway.)\n\nAfter this change, the same operation completes in under a second.\n\nSigned-off-by: Phil Hord <phil.hord@gmail.com>\nReviewed-by: Jacob Keller <jacob.e.keller@intel.com>\n---\n builtin/fetch.c  | 20 ++++++++++----------\n builtin/remote.c |  4 ++--\n refs.c           |  2 +-\n 3 files changed, 13 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex d48262bdc717..04d10c9e781a 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1383,9 +1383,13 @@ static int prune_refs(struct display_state *display_state,\n \tint result = 0;\n \tstruct ref *ref, *stale_refs = get_stale_heads(rs, ref_map);\n \tstruct strbuf err = STRBUF_INIT;\n+\tstruct string_list refnames = STRING_LIST_INIT_NODUP;\n \tconst char *dangling_msg = dry_run\n-\t\t? _(\"   (%s will become dangling)\")\n-\t\t: _(\"   (%s has become dangling)\");\n+\t\t? _(\"   %s will become dangling after %s is deleted\")\n+\t\t: _(\"   %s has become dangling after %s was deleted\");\n+\n+\tfor (ref = stale_refs; ref; ref = ref->next)\n+\t\tstring_list_append(&refnames, ref->name);\n \n \tif (!dry_run) {\n \t\tif (transaction) {\n@@ -1396,15 +1400,9 @@ static int prune_refs(struct display_state *display_state,\n \t\t\t\t\tgoto cleanup;\n \t\t\t}\n \t\t} else {\n-\t\t\tstruct string_list refnames = STRING_LIST_INIT_NODUP;\n-\n-\t\t\tfor (ref = stale_refs; ref; ref = ref->next)\n-\t\t\t\tstring_list_append(&refnames, ref->name);\n-\n \t\t\tresult = refs_delete_refs(get_main_ref_store(the_repository),\n \t\t\t\t\t\t  \"fetch: prune\", &refnames,\n \t\t\t\t\t\t  0);\n-\t\t\tstring_list_clear(&refnames, 0);\n \t\t}\n \t}\n \n@@ -1416,12 +1414,14 @@ static int prune_refs(struct display_state *display_state,\n \t\t\t\t\t   _(\"(none)\"), ref->name,\n \t\t\t\t\t   &ref->new_oid, &ref->old_oid,\n \t\t\t\t\t   summary_width);\n-\t\t\trefs_warn_dangling_symref(get_main_ref_store(the_repository),\n-\t\t\t\t\t\t  stderr, dangling_msg, ref->name);\n \t\t}\n+\t\tstring_list_sort(&refnames);\n+\t\trefs_warn_dangling_symrefs(get_main_ref_store(the_repository),\n+\t\t\t\t\t   stderr, dangling_msg, &refnames);\n \t}\n \n cleanup:\n+\tstring_list_clear(&refnames, 0);\n \tstrbuf_release(&err);\n \tfree_refs(stale_refs);\n \treturn result;\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 0d6755bcb71e..4de7dd373ae5 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1522,8 +1522,8 @@ static int prune_remote(const char *remote, int dry_run)\n \tstruct string_list refs_to_prune = STRING_LIST_INIT_NODUP;\n \tstruct string_list_item *item;\n \tconst char *dangling_msg = dry_run\n-\t\t? _(\" %s will become dangling!\")\n-\t\t: _(\" %s has become dangling!\");\n+\t\t? _(\" %s will become dangling after %s is deleted!\")\n+\t\t: _(\" %s has become dangling after %s was deleted!\");\n \n \tget_remote_ref_states(remote, &states, GET_REF_STATES);\n \ndiff --git a/refs.c b/refs.c\nindex dce5c49ca2ba..651fb2d41299 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -461,7 +461,7 @@ static int warn_if_dangling_symref(const char *refname, const char *referent UNU\n \t\treturn 0;\n \t}\n \n-\tfprintf(d->fp, d->msg_fmt, refname);\n+\tfprintf(d->fp, d->msg_fmt, refname, resolves_to);\n \tfputc('\\n', d->fp);\n \treturn 0;\n }\n-- \n2.50.0.149.g1bab277d62\n\n"},{"id":"521136","messageId":"20250702011214.2835529-4-phil.hord@gmail.com","threadId":"63726","inReplyTo":"20250702011214.2835529-2-phil.hord@gmail.com","subject":"[PATCH v4 2/3] refs: remove old refs_warn_dangling_symref","fromName":"Phil Hord","fromEmail":"phil.hord@gmail.com","sentAt":"2025-07-02T01:12:14Z","receivedAt":"2025-07-02T01:13:32Z","isPatch":true,"sender":{"key":"phil.hord@gmail.com","avatar":"https://avatars.githubusercontent.com/u/123908?v=4"},"body":"From: Phil Hord <phil.hord@gmail.com>\n\nThe dangling warning function that takes a single ref to search for\nis no longer used.  Remove it.\n\nSigned-off-by: Phil Hord <phil.hord@gmail.com>\n---\n refs.c | 17 +----------------\n refs.h |  2 --\n 2 files changed, 1 insertion(+), 18 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 651fb2d41299..07197c239e33 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -438,7 +438,6 @@ static int for_each_filter_refs(const char *refname, const char *referent,\n struct warn_if_dangling_data {\n \tstruct ref_store *refs;\n \tFILE *fp;\n-\tconst char *refname;\n \tconst struct string_list *refnames;\n \tconst char *msg_fmt;\n };\n@@ -455,9 +454,7 @@ static int warn_if_dangling_symref(const char *refname, const char *referent UNU\n \n \tresolves_to = refs_resolve_ref_unsafe(d->refs, refname, 0, NULL, NULL);\n \tif (!resolves_to\n-\t    || (d->refname\n-\t\t? strcmp(resolves_to, d->refname)\n-\t\t: !string_list_has_string(d->refnames, resolves_to))) {\n+\t    || !string_list_has_string(d->refnames, resolves_to)) {\n \t\treturn 0;\n \t}\n \n@@ -466,18 +463,6 @@ static int warn_if_dangling_symref(const char *refname, const char *referent UNU\n \treturn 0;\n }\n \n-void refs_warn_dangling_symref(struct ref_store *refs, FILE *fp,\n-\t\t\t       const char *msg_fmt, const char *refname)\n-{\n-\tstruct warn_if_dangling_data data = {\n-\t\t.refs = refs,\n-\t\t.fp = fp,\n-\t\t.refname = refname,\n-\t\t.msg_fmt = msg_fmt,\n-\t};\n-\trefs_for_each_rawref(refs, warn_if_dangling_symref, &data);\n-}\n-\n void refs_warn_dangling_symrefs(struct ref_store *refs, FILE *fp,\n \t\t\t\tconst char *msg_fmt, const struct string_list *refnames)\n {\ndiff --git a/refs.h b/refs.h\nindex 46a6008e07f2..07f21824d480 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -452,8 +452,6 @@ static inline const char *has_glob_specials(const char *pattern)\n \treturn strpbrk(pattern, \"?*[\");\n }\n \n-void refs_warn_dangling_symref(struct ref_store *refs, FILE *fp,\n-\t\t\t       const char *msg_fmt, const char *refname);\n void refs_warn_dangling_symrefs(struct ref_store *refs, FILE *fp,\n \t\t\t\tconst char *msg_fmt, const struct string_list *refnames);\n \n-- \n2.50.0.149.g1bab277d62\n\n"},{"id":"521137","messageId":"20250702011214.2835529-5-phil.hord@gmail.com","threadId":"63726","inReplyTo":"20250702011214.2835529-2-phil.hord@gmail.com","subject":"[PATCH v4 3/3] clean up interface for refs_warn_dangling_symrefs","fromName":"Phil Hord","fromEmail":"phil.hord@gmail.com","sentAt":"2025-07-02T01:12:15Z","receivedAt":"2025-07-02T01:13:34Z","isPatch":true,"sender":{"key":"phil.hord@gmail.com","avatar":"https://avatars.githubusercontent.com/u/123908?v=4"},"body":"From: Phil Hord <phil.hord@gmail.com>\n\nThe refs_warn_dangling_symrefs interface is a bit fragile as it passes\nin printf-formatting strings with expectations about the number of\narguments. This patch series made it worse by adding a 2nd positional\nargument. But there are only two call sites, and they both use almost\nidentical display options.\n\nMake this safer by moving the format strings into the function that uses\nthem to make it easier to see when the arguments don't match. Pass a\nprefix string and a dry_run flag so the decision logic can be handled\nwhere needed.\n\nSigned-off-by: Phil Hord <phil.hord@gmail.com>\n---\n builtin/fetch.c  |  5 +----\n builtin/remote.c |  5 +----\n refs.c           | 17 +++++++++++------\n refs.h           |  3 ++-\n 4 files changed, 15 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 04d10c9e781a..fc72f2119c56 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1384,9 +1384,6 @@ static int prune_refs(struct display_state *display_state,\n \tstruct ref *ref, *stale_refs = get_stale_heads(rs, ref_map);\n \tstruct strbuf err = STRBUF_INIT;\n \tstruct string_list refnames = STRING_LIST_INIT_NODUP;\n-\tconst char *dangling_msg = dry_run\n-\t\t? _(\"   %s will become dangling after %s is deleted\")\n-\t\t: _(\"   %s has become dangling after %s was deleted\");\n \n \tfor (ref = stale_refs; ref; ref = ref->next)\n \t\tstring_list_append(&refnames, ref->name);\n@@ -1417,7 +1414,7 @@ static int prune_refs(struct display_state *display_state,\n \t\t}\n \t\tstring_list_sort(&refnames);\n \t\trefs_warn_dangling_symrefs(get_main_ref_store(the_repository),\n-\t\t\t\t\t   stderr, dangling_msg, &refnames);\n+\t\t\t\t\t   stderr, \"   \", dry_run, &refnames);\n \t}\n \n cleanup:\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 4de7dd373ae5..f672799e0d92 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1521,9 +1521,6 @@ static int prune_remote(const char *remote, int dry_run)\n \tstruct ref_states states = REF_STATES_INIT;\n \tstruct string_list refs_to_prune = STRING_LIST_INIT_NODUP;\n \tstruct string_list_item *item;\n-\tconst char *dangling_msg = dry_run\n-\t\t? _(\" %s will become dangling after %s is deleted!\")\n-\t\t: _(\" %s has become dangling after %s was deleted!\");\n \n \tget_remote_ref_states(remote, &states, GET_REF_STATES);\n \n@@ -1555,7 +1552,7 @@ static int prune_remote(const char *remote, int dry_run)\n \t}\n \n \trefs_warn_dangling_symrefs(get_main_ref_store(the_repository),\n-\t\t\t\t   stdout, dangling_msg, &refs_to_prune);\n+\t\t\t\t   stdout, \" \", dry_run, &refs_to_prune);\n \n \tstring_list_clear(&refs_to_prune, 0);\n \tfree_remote_ref_states(&states);\ndiff --git a/refs.c b/refs.c\nindex 07197c239e33..5602c18dbd5b 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -439,7 +439,8 @@ struct warn_if_dangling_data {\n \tstruct ref_store *refs;\n \tFILE *fp;\n \tconst struct string_list *refnames;\n-\tconst char *msg_fmt;\n+\tconst char *indent;\n+\tint dry_run;\n };\n \n static int warn_if_dangling_symref(const char *refname, const char *referent UNUSED,\n@@ -447,7 +448,7 @@ static int warn_if_dangling_symref(const char *refname, const char *referent UNU\n \t\t\t\t   int flags, void *cb_data)\n {\n \tstruct warn_if_dangling_data *d = cb_data;\n-\tconst char *resolves_to;\n+\tconst char *resolves_to, *msg;\n \n \tif (!(flags & REF_ISSYMREF))\n \t\treturn 0;\n@@ -458,19 +459,23 @@ static int warn_if_dangling_symref(const char *refname, const char *referent UNU\n \t\treturn 0;\n \t}\n \n-\tfprintf(d->fp, d->msg_fmt, refname, resolves_to);\n-\tfputc('\\n', d->fp);\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 \treturn 0;\n }\n \n void refs_warn_dangling_symrefs(struct ref_store *refs, FILE *fp,\n-\t\t\t\tconst char *msg_fmt, const struct string_list *refnames)\n+\t\t\t\tconst char *indent, int dry_run,\n+\t\t\t\tconst struct string_list *refnames)\n {\n \tstruct warn_if_dangling_data data = {\n \t\t.refs = refs,\n \t\t.fp = fp,\n \t\t.refnames = refnames,\n-\t\t.msg_fmt = msg_fmt,\n+\t\t.indent = indent,\n+\t\t.dry_run = dry_run,\n \t};\n \trefs_for_each_rawref(refs, warn_if_dangling_symref, &data);\n }\ndiff --git a/refs.h b/refs.h\nindex 07f21824d480..25bed4d792e7 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -453,7 +453,8 @@ static inline const char *has_glob_specials(const char *pattern)\n }\n \n void refs_warn_dangling_symrefs(struct ref_store *refs, FILE *fp,\n-\t\t\t\tconst char *msg_fmt, const struct string_list *refnames);\n+\t\t\t\tconst char *indent, int dry_run,\n+\t\t\t\tconst struct string_list *refnames);\n \n /*\n  * Flags for controlling behaviour of pack_refs()\n-- \n2.50.0.149.g1bab277d62\n\n"},{"id":"521481","messageId":"xmqqtt3n1v27.fsf@gitster.g","threadId":"63726","inReplyTo":"20250702011214.2835529-2-phil.hord@gmail.com","subject":"Re: [PATCH v4 0/3] fetch --prune performance problem","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-07-07T22:43:12Z","receivedAt":"2025-07-07T22:43:15Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phil Hord <phil.hord@gmail.com> writes:\n\n> From: Phil Hord <phil.hord@gmail.com>\n>\n> `git fetch --prune` runs in O(N^2) time normally. This happens because the code\n> iterates over each ref to be pruned to display its status. In a repo with\n> 174,000 refs, where I was pruning 15,000 refs, the current code made 2.6 billion\n> calls to strcmp and consumed 470 seconds of CPU. After this change, the same\n> operation completes in under 1 second.\n> ...\n> V3 forgot to include the first commit in the series (I forgot it grew).\n> So here's V4.\n>\n> Phil Hord (3):\n>   fetch-prune: optimize dangling-ref reporting\n>   refs: remove old refs_warn_dangling_symref\n>   clean up interface for refs_warn_dangling_symrefs\n\nIt seems that the thread has gone quiet.  What's the status of this\ntopic?\n\nThanks.\n\n"},{"id":"521489","messageId":"CABURp0ohikpNPjxP_SfzK+txfbyvmM1adHZJ1C_Oa3SmY-EJwQ@mail.gmail.com","threadId":"63726","inReplyTo":"xmqqtt3n1v27.fsf@gitster.g","subject":"Re: [PATCH v4 0/3] fetch --prune performance problem","fromName":"Phil Hord","fromEmail":"phil.hord@gmail.com","sentAt":"2025-07-08T01:00:50Z","receivedAt":"2025-07-08T01:01:04Z","isPatch":true,"sender":{"key":"phil.hord@gmail.com","avatar":"https://avatars.githubusercontent.com/u/123908?v=4"},"body":"On Mon, Jul 7, 2025, 3:43 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Phil Hord <phil.hord@gmail.com> writes:\n>\n> > From: Phil Hord <phil.hord@gmail.com>\n> >\n> > `git fetch --prune` runs in O(N^2) time normally. This happens because the code\n> > iterates over each ref to be pruned to display its status. In a repo with\n> > 174,000 refs, where I was pruning 15,000 refs, the current code made 2.6 billion\n> > calls to strcmp and consumed 470 seconds of CPU. After this change, the same\n> > operation completes in under 1 second.\n> > ...\n> > V3 forgot to include the first commit in the series (I forgot it grew).\n> > So here's V4.\n> >\n> > Phil Hord (3):\n> >   fetch-prune: optimize dangling-ref reporting\n> >   refs: remove old refs_warn_dangling_symref\n> >   clean up interface for refs_warn_dangling_symrefs\n>\n> It seems that the thread has gone quiet.  What's the status of this\n> topic?\n>\n> Thanks.\n\nLGTM\n"},{"id":"521491","messageId":"20250708013534.GA549007@coredump.intra.peff.net","threadId":"63726","inReplyTo":"20250702011214.2835529-5-phil.hord@gmail.com","subject":"Re: [PATCH v4 3/3] clean up interface for refs_warn_dangling_symrefs","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-07-08T01:35:34Z","receivedAt":"2025-07-08T01:35:35Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Jul 01, 2025 at 06:12:15PM -0700, Phil Hord wrote:\n\n> The refs_warn_dangling_symrefs interface is a bit fragile as it passes\n> in printf-formatting strings with expectations about the number of\n> arguments. This patch series made it worse by adding a 2nd positional\n> argument. But there are only two call sites, and they both use almost\n> identical display options.\n> \n> Make this safer by moving the format strings into the function that uses\n> them to make it easier to see when the arguments don't match. Pass a\n> prefix string and a dry_run flag so the decision logic can be handled\n> where needed.\n\nThanks, I think the result is nicer. I have two comments, but I don't\nthink either will merit a re-roll.\n\n> @@ -1384,9 +1384,6 @@ static int prune_refs(struct display_state *display_state,\n>  \tstruct ref *ref, *stale_refs = get_stale_heads(rs, ref_map);\n>  \tstruct strbuf err = STRBUF_INIT;\n>  \tstruct string_list refnames = STRING_LIST_INIT_NODUP;\n> -\tconst char *dangling_msg = dry_run\n> -\t\t? _(\"   %s will become dangling after %s is deleted\")\n> -\t\t: _(\"   %s has become dangling after %s was deleted\");\n>  \n>  \tfor (ref = stale_refs; ref; ref = ref->next)\n>  \t\tstring_list_append(&refnames, ref->name);\n> @@ -1417,7 +1414,7 @@ static int prune_refs(struct display_state *display_state,\n>  \t\t}\n>  \t\tstring_list_sort(&refnames);\n>  \t\trefs_warn_dangling_symrefs(get_main_ref_store(the_repository),\n> -\t\t\t\t\t   stderr, dangling_msg, &refnames);\n> +\t\t\t\t\t   stderr, \"   \", dry_run, &refnames);\n>  \t}\n\nI had imagined passing in an \"int indent\", and not an arbitrary string.\nBut passing in the string is actually more flexible (it really could be\nany prefix, not just an indentation). I think calling it \"prefix\" in the\nactual function might be the more usual term here, but it's probably\njust bike-shedding.\n\n> -\tfprintf(d->fp, d->msg_fmt, refname, resolves_to);\n> -\tfputc('\\n', d->fp);\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\nTranslators might find the extra \"%s\" at the beginning confusing without\ncontext. I think you can do something like:\n\n  /* TRANSLATORS: The first %s is whitespace indentation. */\n\nor similar. But maybe it would be more obvious as:\n\n  fputs(d->indent, d->fp);\n  fprintf(d->fp, msg, refname, resolves_to);\n  fputc('\\n', d->fp);\n\n? I dunno. Maybe putting it all together gives translators more options\n(e.g., in a RTL language). I don't know much about translation.\n\nLikewise on including the newline in the translated string. I think we\nusually don't, just because we're mostly passing in strings for error(),\netc. But I don't know how much it matters.\n\n-Peff\n"},{"id":"521492","messageId":"20250708013612.GB549007@coredump.intra.peff.net","threadId":"63726","inReplyTo":"xmqqtt3n1v27.fsf@gitster.g","subject":"Re: [PATCH v4 0/3] fetch --prune performance problem","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-07-08T01:36:12Z","receivedAt":"2025-07-08T01:36:14Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Jul 07, 2025 at 03:43:12PM -0700, Junio C Hamano wrote:\n\n> Phil Hord <phil.hord@gmail.com> writes:\n> \n> > From: Phil Hord <phil.hord@gmail.com>\n> >\n> > `git fetch --prune` runs in O(N^2) time normally. This happens because the code\n> > iterates over each ref to be pruned to display its status. In a repo with\n> > 174,000 refs, where I was pruning 15,000 refs, the current code made 2.6 billion\n> > calls to strcmp and consumed 470 seconds of CPU. After this change, the same\n> > operation completes in under 1 second.\n> > ...\n> > V3 forgot to include the first commit in the series (I forgot it grew).\n> > So here's V4.\n> >\n> > Phil Hord (3):\n> >   fetch-prune: optimize dangling-ref reporting\n> >   refs: remove old refs_warn_dangling_symref\n> >   clean up interface for refs_warn_dangling_symrefs\n> \n> It seems that the thread has gone quiet.  What's the status of this\n> topic?\n\nv4 looks fine to me. I raised a few questions about the translation\nstrings, but I don't know if they're meaningful or not.\n\n-Peff\n"}]}