{"thread":{"id":"62096","subject":"[RFC PATCH v2 0/2] set remote/HEAD with fetch","startedAt":"2024-09-10T20:39:28Z","lastAt":"2025-01-06T14:50:20Z","messageCount":258,"participants":["Bence Ferdinandy","Junio C Hamano","Phillip Wood","Patrick Steinhardt","Ramsay Jones","karthik nayak","Taylor Blau","Kristoffer Haugsbakk","Josh Steadmon","Teng Long"],"isPatch":true,"patchVersion":2,"patchTotal":2},"messages":[{"id":"502591","messageId":"20240910203835.2288291-1-bence@ferdinandy.com","threadId":"62096","inReplyTo":null,"subject":"[RFC PATCH v2 0/2] set remote/HEAD with fetch","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-09-10T20:37:12Z","receivedAt":"2024-09-10T20:39:28Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Sorry for the noise, I managed to badly mess up receipients in the previous\nthread so v2 is just fixing that ...\n\nHi,\n\nthese two patches attempt to solve the issue raised in\nhttps://lore.kernel.org/git/D3HBD7C1FR14.74FL1Q1S9UCB@ferdinandy.com/\n\nAs a quick summary: `clone` sets `refs/remotes/[remote]/HEAD` while going init\n-> remote add -> fetch does not, one has to manually run `remote set-head -a\n[remote]`.\n\nThe first patch adds a `--set-head` flag to `fetch` and `remote update` which\nruns `remote set-head -a` for us. Unfortunately, with the current behaviour of\nset-head this will always print a message, even though a no-op fetch doesn't\nprint anything, and I think this is also confusing for people who do not care\nabout remote/HEAD, so the second patch removes the print if `set-head -a` is\na no-op (and actually makes it into a no-op, instead of just idempotent).\n\nAnother way could of course be duplicating some of the code from remote\nset-head in fetch.c instead of calling directly, but it didn't look like an\nanti-pattern in the code-base and it felt the best way to insure identical\nbehaviour between a `git fetch --all --set-head` and a \n`git fetch --all && git remote | xargs -i git remote set-head -a {}`.\n\nWhat is missing for sure is:\n- documentation\n- tests (if needed)\n- settings\n\nFor settings, my idea would be a fetch/remote.set_head that could take three values:\n    * never\n    * missing: run it only if the ref is missing, this setting would basically\n      allow replicating the result of a clone\n    * always (with the other patch, this would still be a no-op if it didn't change)\n\nThis would probably also require a --no-set-head flag, to disable an\nalways/missing setting. A --missing-set-head or something of the like also may\nor may not make sense. Alternatively, only two behaviours might be enough\n(missing and always) since clone already sort of does this.\n\nI'm not sure if the general approach is fine or not, nor am I sure the code\nitself is any good, but it \"works on my computer\" :) I'm also hoping that\nI managed to read all the relevant parts for sending a patch.\n\nFeedback would be highly appreciated!\n\nThanks,\nBence\n\n\nBence Ferdinandy (2):\n  fetch: set-head with --set-head option\n  set-head: do not update if there is no change\n\n builtin/fetch.c  | 29 ++++++++++++++++++++++++-----\n builtin/remote.c | 15 +++++++++++----\n 2 files changed, 35 insertions(+), 9 deletions(-)\n\n-- \n2.46.0\n\n"},{"id":"502592","messageId":"20240910203835.2288291-2-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20240910203835.2288291-1-bence@ferdinandy.com","subject":"[RFC PATCH v2 1/2] fetch: set-head with --set-head option","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-09-10T20:37:13Z","receivedAt":"2024-09-10T20:44:35Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"When cloning a repository refs/remotes/origin/HEAD is set automatically.\nIn contrast, when using init, remote add and fetch to set a remote, one\nneeds to call remote set-head --auto to achieve the same result.\n\nAdd a --set-head option to git fetch to automatically set heads on\nremotes.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n builtin/fetch.c  | 29 ++++++++++++++++++++++++-----\n builtin/remote.c |  5 +++++\n 2 files changed, 29 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex b2b5aee5bf..6392314c6a 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1961,8 +1961,19 @@ static int fetch_finished(int result, struct strbuf *out,\n \treturn 0;\n }\n \n-static int fetch_multiple(struct string_list *list, int max_children,\n-\t\t\t  const struct fetch_config *config)\n+static int run_set_head(const char *name)\n+{\n+\tstruct child_process cmd = CHILD_PROCESS_INIT;\n+\tstrvec_push(&cmd.args, \"remote\");\n+\tstrvec_push(&cmd.args, \"set-head\");\n+\tstrvec_push(&cmd.args, \"--auto\");\n+\tstrvec_push(&cmd.args, name);\n+\tcmd.git_cmd = 1;\n+\treturn run_command(&cmd);\n+}\n+\n+static int fetch_multiple(struct string_list *list, int max_children, int set_head,\n+\t\t\tconst struct fetch_config *config)\n {\n \tint i, result = 0;\n \tstruct strvec argv = STRVEC_INIT;\n@@ -2014,6 +2025,8 @@ static int fetch_multiple(struct string_list *list, int max_children,\n \t\t\t\terror(_(\"could not fetch %s\"), name);\n \t\t\t\tresult = 1;\n \t\t\t}\n+\t\t\tif (set_head && run_set_head(name))\n+\t\t\t\tresult = 1;\n \t\t}\n \n \tstrvec_clear(&argv);\n@@ -2062,7 +2075,7 @@ static inline void fetch_one_setup_partial(struct remote *remote)\n }\n \n static int fetch_one(struct remote *remote, int argc, const char **argv,\n-\t\t     int prune_tags_ok, int use_stdin_refspecs,\n+\t\t     int prune_tags_ok, int set_head, int use_stdin_refspecs,\n \t\t     const struct fetch_config *config)\n {\n \tstruct refspec rs = REFSPEC_INIT_FETCH;\n@@ -2135,9 +2148,12 @@ static int fetch_one(struct remote *remote, int argc, const char **argv,\n \trefspec_clear(&rs);\n \ttransport_disconnect(gtransport);\n \tgtransport = NULL;\n+\tif (set_head && run_set_head(remote -> name))\n+\t\texit_code = 1;\n \treturn exit_code;\n }\n \n+\n int cmd_fetch(int argc, const char **argv, const char *prefix)\n {\n \tstruct fetch_config config = {\n@@ -2154,6 +2170,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)\n \tstruct string_list list = STRING_LIST_INIT_DUP;\n \tstruct remote *remote = NULL;\n \tint all = -1, multiple = 0;\n+\tint set_head = 0;\n \tint result = 0;\n \tint prune_tags_ok = 1;\n \tint enable_auto_gc = 1;\n@@ -2171,6 +2188,8 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)\n \t\tOPT__VERBOSITY(&verbosity),\n \t\tOPT_BOOL(0, \"all\", &all,\n \t\t\t N_(\"fetch from all remotes\")),\n+\t\tOPT_BOOL(0, \"set-head\", &set_head,\n+\t\t\t N_(\"auto set remote HEAD\")),\n \t\tOPT_BOOL(0, \"set-upstream\", &set_upstream,\n \t\t\t N_(\"set upstream for git pull/fetch\")),\n \t\tOPT_BOOL('a', \"append\", &append,\n@@ -2436,7 +2455,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)\n \t\t\ttrace2_region_leave(\"fetch\", \"setup-partial\", the_repository);\n \t\t}\n \t\ttrace2_region_enter(\"fetch\", \"fetch-one\", the_repository);\n-\t\tresult = fetch_one(remote, argc, argv, prune_tags_ok, stdin_refspecs,\n+\t\tresult = fetch_one(remote, argc, argv, prune_tags_ok, set_head, stdin_refspecs,\n \t\t\t\t   &config);\n \t\ttrace2_region_leave(\"fetch\", \"fetch-one\", the_repository);\n \t} else {\n@@ -2459,7 +2478,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)\n \n \t\t/* TODO should this also die if we have a previous partial-clone? */\n \t\ttrace2_region_enter(\"fetch\", \"fetch-multiple\", the_repository);\n-\t\tresult = fetch_multiple(&list, max_children, &config);\n+\t\tresult = fetch_multiple(&list, max_children, set_head, &config);\n \t\ttrace2_region_leave(\"fetch\", \"fetch-multiple\", the_repository);\n \t}\n \ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 0acc547d69..35c54dd103 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1536,9 +1536,12 @@ static int get_remote_default(const char *key, const char *value UNUSED,\n static int update(int argc, const char **argv, const char *prefix)\n {\n \tint i, prune = -1;\n+\tint set_head = 0;\n \tstruct option options[] = {\n \t\tOPT_BOOL('p', \"prune\", &prune,\n \t\t\t N_(\"prune remotes after fetching\")),\n+\t\tOPT_BOOL(0, \"set-head\", &set_head,\n+\t\t\t N_(\"auto set remote HEAD\")),\n \t\tOPT_END()\n \t};\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n@@ -1552,6 +1555,8 @@ static int update(int argc, const char **argv, const char *prefix)\n \n \tif (prune != -1)\n \t\tstrvec_push(&cmd.args, prune ? \"--prune\" : \"--no-prune\");\n+\tif (set_head)\n+\t\tstrvec_push(&cmd.args, \"--set-head\");\n \tif (verbose)\n \t\tstrvec_push(&cmd.args, \"-v\");\n \tstrvec_push(&cmd.args, \"--multiple\");\n-- \n2.46.0\n\n"},{"id":"502593","messageId":"20240910203835.2288291-3-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20240910203835.2288291-1-bence@ferdinandy.com","subject":"[RFC PATCH v2 2/2] set-head: do not update if there is no change","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-09-10T20:37:14Z","receivedAt":"2024-09-10T20:44:46Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Currently, even if there is no actual change to remote/HEAD calling\nremote set-head will overwrite the appropriate file and if set to --auto\nwill also print a message saying \"remote/HEAD set to branch\", which\nimplies something was changed. In contrast, on a nil operation e.g. pull\nwill clearly state that nothing was done, while fetch will not output\nanything.\n\nChange the behaviour of remote set-head so that the reference is only\nupdated if it actually needs to change. Since set-head --auto is\nessentially a fetch-like operation, align it's behaviour with fetch and\nonly print output if something was actually done.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n builtin/remote.c | 10 ++++++----\n 1 file changed, 6 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 35c54dd103..e220e51b84 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1400,8 +1400,8 @@ static int show(int argc, const char **argv, const char *prefix)\n \n static int set_head(int argc, const char **argv, const char *prefix)\n {\n-\tint i, opt_a = 0, opt_d = 0, result = 0;\n-\tstruct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;\n+\tint i, opt_a = 0, opt_d = 0, is_ref_changed = 0, result = 0;\n+\tstruct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT, buf3 = STRBUF_INIT;\n \tchar *head_name = NULL;\n \n \tstruct option options[] = {\n@@ -1440,12 +1440,14 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \n \tif (head_name) {\n \t\tstrbuf_addf(&buf2, \"refs/remotes/%s/%s\", argv[0], head_name);\n+\t\trefs_read_symbolic_ref(get_main_ref_store(the_repository),buf.buf,&buf3);\n+\t\tis_ref_changed = strcmp(buf2.buf,buf3.buf);\n \t\t/* make sure it's valid */\n \t\tif (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))\n \t\t\tresult |= error(_(\"Not a valid ref: %s\"), buf2.buf);\n-\t\telse if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote set-head\"))\n+\t\telse if (is_ref_changed && refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote set-head\"))\n \t\t\tresult |= error(_(\"Could not setup %s\"), buf.buf);\n-\t\telse if (opt_a)\n+\t\telse if (opt_a && is_ref_changed)\n \t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\n \t\tfree(head_name);\n \t}\n-- \n2.46.0\n\n"},{"id":"502603","messageId":"xmqqseu7cfsl.fsf@gitster.g","threadId":"62096","inReplyTo":"20240910203835.2288291-2-bence@ferdinandy.com","subject":"Re: [RFC PATCH v2 1/2] fetch: set-head with --set-head option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-09-10T22:19:54Z","receivedAt":"2024-09-10T22:19:56Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bence Ferdinandy <bence@ferdinandy.com> writes:\n\n> When cloning a repository refs/remotes/origin/HEAD is set automatically.\n> In contrast, when using init, remote add and fetch to set a remote, one\n> needs to call remote set-head --auto to achieve the same result.\n\nIn other words, \"clone\" is roughly equivalent to \"init && remote add\n&& fetch && remote set-head\".\n\nI do not see a problem with it.  You'd do such a repository creation\ntask only once per repository anyway.\n\n> Add a --set-head option to git fetch to automatically set heads on\n> remotes.\n\nThis practically would help only those who want to blindly follow\nwhat the remote considers their primary branch.  If the remote\nflip-flops that every day betweein 'master' and 'main', you'd want\nto either \"fetch && remote set-head\" or \"fetch --set-head\", but it\nwould be weird to flip-flop the HEAD every day to begin with.\n\nAnd the feature does not help those who want to inspect and then\nswitch.  When they say \"--set-head\", they will unconditionally\nswitch whatever the remote uses and there is no opportunity for them\nto check.\n\nIdeally, because every \"git fetch\" you do will automatically learn\nwhat their HEAD of the day points at, even without \"--set-head\", it\nmay be nice to let the user know when their HEAD changed, so that\nthe user can inspect the situation and decide.\n\nIf \"fetch $repo\", when it notices that refs/remotes/$repo/HEAD is\nmissing, always unconditionally stores where their HEAD points at\nin refs/remotes/$repo/HEAD, and did nothing else, wouldn't that be\nsufficient?\n\nThe users have \"remote set-head\" to do this when needed.  What is\nthe true motivation that \"fetch\" (which presumably happens a lot\nmore often) needs to be involved in this process?  The *only* upside\nI can see with \"fetch --set-head\" to blindly follow every switch of\nHEAD on the remote end is that you can keep up with the remote that\nflips its HEAD very often, but is that really a realistic need?  If\nwe reject such a use case as invalid, I suspect that the end-user\nexperience would be simplified quite a lot.  Imagine that we teach\n\"git fetch $repo\" to notice refs/remotes/$repo/HEAD is missing, and\ncreate it from the remote HEAD information automatically.  And we do\nNOTHING ELSE.  What would the end-user experience look like?\n\n * Usually, you start with \"git clone\" and 'origin' will know which\n   branch 'origin/HEAD' points at.\n\n * You may run \"git remote add -f $repo $URL\" to add one.  Because\n   this runs \"git fetch $repo\", the previous addition to the \"git\n   fetch\" will make sure refs/remotes/$repo/HEAD would be there.\n\n * You may run \"git remote add $repo $URL\" to add one, and then\n   separately \"git fetch $repo\" yourself.  The end result would be\n   the same; refs/remotes/$repo/HEAD will be there.\n\nHaving said all that, the implementation here ...\n\n> +static int run_set_head(const char *name)\n> +{\n> +\tstruct child_process cmd = CHILD_PROCESS_INIT;\n> +\tstrvec_push(&cmd.args, \"remote\");\n> +\tstrvec_push(&cmd.args, \"set-head\");\n> +\tstrvec_push(&cmd.args, \"--auto\");\n> +\tstrvec_push(&cmd.args, name);\n> +\tcmd.git_cmd = 1;\n> +\treturn run_command(&cmd);\n> +}\n\n... does look quite straight-forward.\n\n> +static int fetch_multiple(struct string_list *list, int max_children, int set_head,\n> +\t\t\tconst struct fetch_config *config)\n>  {\n>  \tint i, result = 0;\n>  \tstruct strvec argv = STRVEC_INIT;\n> @@ -2014,6 +2025,8 @@ static int fetch_multiple(struct string_list *list, int max_children,\n>  \t\t\t\terror(_(\"could not fetch %s\"), name);\n>  \t\t\t\tresult = 1;\n>  \t\t\t}\n> +\t\t\tif (set_head && run_set_head(name))\n> +\t\t\t\tresult = 1;\n\nThis looked a bit questionable---I expected that we'd run the\nsubfetches with \"--set-head\" option, but this makes us do the\nset-head step ourselves after the subfetches are done.  What are\npros-and-cons considered to reach the decision to do it this way?\n\n\nThanks.\n"},{"id":"502604","messageId":"xmqqldzzcfcv.fsf@gitster.g","threadId":"62096","inReplyTo":"20240910203835.2288291-1-bence@ferdinandy.com","subject":"Re: [RFC PATCH v2 0/2] set remote/HEAD with fetch","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-09-10T22:29:20Z","receivedAt":"2024-09-10T22:29:22Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bence Ferdinandy <bence@ferdinandy.com> writes:\n\n> What is missing for sure is:\n> - documentation\n> - tests (if needed)\n\nWhat change does not need tests?\n\n> - settings\n>\n> For settings, my idea would be a fetch/remote.set_head that could take three values:\n>     * never\n>     * missing: run it only if the ref is missing, this setting would basically\n>       allow replicating the result of a clone\n>     * always (with the other patch, this would still be a no-op if it didn't change)\n>\n> This would probably also require a --no-set-head flag, to disable an\n> always/missing setting. A --missing-set-head or something of the like also may\n> or may not make sense. Alternatively, only two behaviours might be enough\n> (missing and always) since clone already sort of does this.\n\nIf we were to assume \"always\" is needed, then the tristate like the\nabove may be a reasonable way to go.\n\nBut as I outlined in my response to [1/2], I suspect that an\napproach without configuration or command line option would give the\nusers the most smooth experience.  They are used to seeing \"clone\"\nto give them a remote tracking HEAD and nobody complained that we\nlack the option to \"clone\" to prevent that.  If \"fetch\" notices we\ndo not have remote tracking HEAD for a remote [*] and stores what it\nobserved at their HEAD in remote tracking HEAD, that should not\nbother anybody.  No matter what mechanism gave you the initial\nremote tracking HEAD, if you want to update it to something else, we\nalready have the \"remote set-head\" command.\n\n\n[Footnote]\n\n * One thing we MUST be careful about is that some remotes may not\n   have ANY remote tracking branches (i.e. you only want to use the\n   remote mechanism to give you a shorthand for URL, but you do not\n   have fetch refspec at all).  Even if refs/remotes/$repo/HEAD is\n   missing for such a remote, we should *not* attempt to create it,\n   as we are not populating refs/remotes/$repo/master and friends at\n   all in such a case.\n\n"},{"id":"502616","messageId":"D43G2CGX2N7L.ZRETD4HLIH0E@ferdinandy.com","threadId":"62096","inReplyTo":"xmqqseu7cfsl.fsf@gitster.g","subject":"Re: [RFC PATCH v2 1/2] fetch: set-head with --set-head option","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-09-11T12:13:05Z","receivedAt":"2024-09-11T12:19:37Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Hey Junio,\n\nthanks for the very speedy feedback!\n\nOn Wed Sep 11, 2024 at 00:19, Junio C Hamano <gitster@pobox.com> wrote:\n\n[snip]\n\n>\n> Ideally, because every \"git fetch\" you do will automatically learn\n> what their HEAD of the day points at, even without \"--set-head\", it\n> may be nice to let the user know when their HEAD changed, so that\n> the user can inspect the situation and decide.\n\nThat would actually make sense, we could print a message saying HEAD has\nchanged and I guess helpfully print the exact set-head command they would need\nto manually update should they wish to do so.\n\n>\n> If \"fetch $repo\", when it notices that refs/remotes/$repo/HEAD is\n> missing, always unconditionally stores where their HEAD points at\n> in refs/remotes/$repo/HEAD, and did nothing else, wouldn't that be\n> sufficient?\n\n>\n> The users have \"remote set-head\" to do this when needed.  What is\n> the true motivation that \"fetch\" (which presumably happens a lot\n> more often) needs to be involved in this process?  The *only* upside\n> I can see with \"fetch --set-head\" to blindly follow every switch of\n> HEAD on the remote end is that you can keep up with the remote that\n> flips its HEAD very often, but is that really a realistic need?  If\n> we reject such a use case as invalid, I suspect that the end-user\n> experience would be simplified quite a lot.  Imagine that we teach\n> \"git fetch $repo\" to notice refs/remotes/$repo/HEAD is missing, and\n> create it from the remote HEAD information automatically.  And we do\n> NOTHING ELSE.  What would the end-user experience look like?\n>\n>  * Usually, you start with \"git clone\" and 'origin' will know which\n>    branch 'origin/HEAD' points at.\n>\n>  * You may run \"git remote add -f $repo $URL\" to add one.  Because\n>    this runs \"git fetch $repo\", the previous addition to the \"git\n>    fetch\" will make sure refs/remotes/$repo/HEAD would be there.\n>\n>  * You may run \"git remote add $repo $URL\" to add one, and then\n>    separately \"git fetch $repo\" yourself.  The end result would be\n>    the same; refs/remotes/$repo/HEAD will be there.\n\nI'm convinced :) It also does drop a lot of complexity. So there will be no\nextra flag for fetch, rather:\n\n- if the remote HEAD does not exist, we create it\n\n- if it does exist, but has changed we print a message saying it was changed from X to Y and print the required command to update to this\n\n- no configuration needed\n\nThe only place I can imagine this not being sufficient is in a CI/CD process\nthat is fetching into a cached repo needs to be updated, but then those people\ncan just always run remote set-head -a.\n\n>\n> Having said all that, the implementation here ...\n>\n> > +static int run_set_head(const char *name)\n> > +{\n> > +\tstruct child_process cmd = CHILD_PROCESS_INIT;\n> > +\tstrvec_push(&cmd.args, \"remote\");\n> > +\tstrvec_push(&cmd.args, \"set-head\");\n> > +\tstrvec_push(&cmd.args, \"--auto\");\n> > +\tstrvec_push(&cmd.args, name);\n> > +\tcmd.git_cmd = 1;\n> > +\treturn run_command(&cmd);\n> > +}\n>\n> ... does look quite straight-forward.\n\nUnfortunately, as Jeff has pointed out in the other thread, this implementation\nrequires the user to authenticate twice ... Now, we could still rely on\nset-head to actually do the writing, since if you explicitly give it what to\nset to it will not do a network query, but considering all of the above,\nI think it would make more sense not to do this, rather just bring in the\nrequired logic here. This would also allow for the second patch to be a bit\nmore explicit on what did or did not happen during a remote set-head.\n\n>\n> > +static int fetch_multiple(struct string_list *list, int max_children, int set_head,\n> > +\t\t\tconst struct fetch_config *config)\n> >  {\n> >  \tint i, result = 0;\n> >  \tstruct strvec argv = STRVEC_INIT;\n> > @@ -2014,6 +2025,8 @@ static int fetch_multiple(struct string_list *list, int max_children,\n> >  \t\t\t\terror(_(\"could not fetch %s\"), name);\n> >  \t\t\t\tresult = 1;\n> >  \t\t\t}\n> > +\t\t\tif (set_head && run_set_head(name))\n> > +\t\t\t\tresult = 1;\n>\n> This looked a bit questionable---I expected that we'd run the\n> subfetches with \"--set-head\" option, but this makes us do the\n> set-head step ourselves after the subfetches are done.  What are\n> pros-and-cons considered to reach the decision to do it this way?\n\nJust noobish oversight, I figured out how to do it in the subfetch, but with\nthe above there will be no need for this anyway I guess.\n\nI'll send a v2 soonish.\n\nBest,\nBence\n"},{"id":"502617","messageId":"D43GB8KUM7RF.3Q4SYKM4JCQ5L@ferdinandy.com","threadId":"62096","inReplyTo":"xmqqldzzcfcv.fsf@gitster.g","subject":"Re: [RFC PATCH v2 0/2] set remote/HEAD with fetch","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-09-11T12:24:42Z","receivedAt":"2024-09-11T12:30:07Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Wed Sep 11, 2024 at 00:29, Junio C Hamano <gitster@pobox.com> wrote:\n> Bence Ferdinandy <bence@ferdinandy.com> writes:\n>\n> > What is missing for sure is:\n> > - documentation\n> > - tests (if needed)\n>\n> What change does not need tests?\n\nFair enough, for the next iteration I'll look into tests as well!\n\n>\n> > - settings\n> >\n> > For settings, my idea would be a fetch/remote.set_head that could take three values:\n> >     * never\n> >     * missing: run it only if the ref is missing, this setting would basically\n> >       allow replicating the result of a clone\n> >     * always (with the other patch, this would still be a no-op if it didn't change)\n> >\n> > This would probably also require a --no-set-head flag, to disable an\n> > always/missing setting. A --missing-set-head or something of the like also may\n> > or may not make sense. Alternatively, only two behaviours might be enough\n> > (missing and always) since clone already sort of does this.\n>\n> If we were to assume \"always\" is needed, then the tristate like the\n> above may be a reasonable way to go.\n>\n> But as I outlined in my response to [1/2], I suspect that an\n> approach without configuration or command line option would give the\n> users the most smooth experience.  They are used to seeing \"clone\"\n> to give them a remote tracking HEAD and nobody complained that we\n> lack the option to \"clone\" to prevent that.  If \"fetch\" notices we\n> do not have remote tracking HEAD for a remote [*] and stores what it\n> observed at their HEAD in remote tracking HEAD, that should not\n> bother anybody.  No matter what mechanism gave you the initial\n> remote tracking HEAD, if you want to update it to something else, we\n> already have the \"remote set-head\" command.\n\nSo I guess we did conclude that no settings are actually needed.\n\n>\n>\n> [Footnote]\n>\n>  * One thing we MUST be careful about is that some remotes may not\n>    have ANY remote tracking branches (i.e. you only want to use the\n>    remote mechanism to give you a shorthand for URL, but you do not\n>    have fetch refspec at all).  Even if refs/remotes/$repo/HEAD is\n>    missing for such a remote, we should *not* attempt to create it,\n>    as we are not populating refs/remotes/$repo/master and friends at\n>    all in such a case.\n\nYou mean that somebody does git init && git remote add origin $remote, but\nnever does calls fetch? Currently remote set-head -a origin will fail with\n\nerror: Not a valid ref: refs/remotes/origin/master\n\nAlthough if everything is done _after_ a fetch, unless I misunderstood, we\nshouldn't be able to be in this situation at all.\n\nBest,\nBence\n"},{"id":"502629","messageId":"xmqq1q1qb32k.fsf@gitster.g","threadId":"62096","inReplyTo":"D43G2CGX2N7L.ZRETD4HLIH0E@ferdinandy.com","subject":"Re: [RFC PATCH v2 1/2] fetch: set-head with --set-head option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-09-11T15:52:19Z","receivedAt":"2024-09-11T15:52:28Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Bence Ferdinandy\" <bence@ferdinandy.com> writes:\n\n>> Ideally, because every \"git fetch\" you do will automatically learn\n>> what their HEAD of the day points at, even without \"--set-head\", it\n>> may be nice to let the user know when their HEAD changed, so that\n>> the user can inspect the situation and decide.\n>\n> That would actually make sense, we could print a message saying HEAD has\n> changed and I guess helpfully print the exact set-head command they would need\n> to manually update should they wish to do so.\n\nWe need to be careful if we were to do this, though.  A user may see\nthat their HEAD points at their 'main' branch, and they know the\nremote-tracking HEAD points at 'next' branch.  But the only thing\nthe latter tells us is that 'next' is, as far as this user is\nconcerned, the primary branch they are interested in from this\nremote.  It may have been set earlier with \"git remtoe set-head\"\nexplicitly, or it may have been set back when \"git clone\" created\nthis repository and back then the remote had 'next' marked as its\nprimary branch.  In other words, the HEAD we learned from remote\nwhile we are fetching from it may or may not be the same from the\nremote-tracking HEAD we have, but it does not mean that the remote\nrecently flipped its HEAD if these two are different, but what we\nwant to report is \"their HEAD used to point at 'master' but now it\nis pointing at 'main'\".\n\n> So there will be no\n> extra flag for fetch, rather:\n>\n> - if the remote HEAD does not exist, we create it\n>\n> - if it does exist, but has changed we print a message saying it was changed from X to Y and print the required command to update to this\n>\n> - no configuration needed\n\nVery good, with two reservations.\n\n - if the remote HEAD does not exist, we may still not want to\n   create it.  Imagine\n\n\t[remote \"his\"]\n\t\turl = https://git.kernel.org/pub/scm/git/git\n\t\tpush = refs/heads/maint\n\t\tpush = refs/heads/master\n\t\tpush = refs/heads/next\n\t\tpush = +refs/heads/seen\n\n   without any refspec for the fetching side.  \"git fetch his master\"\n   may learn where the remote HEAD is, and it may even be pointing\n   at their 'master' branch, but because we do not maintain any\n   remote tracking information for their 'master' (in other words,\n   refs/remotes/his/master is not updated by this 'fetch' and there\n   is no configuration to make future 'fetch' to do so).\n\n - it would be very nice to report when we notice that they changed\n   their HEAD, but I do not think we have enough information to do\n   so.  Our existing refs/remotes/origin/HEAD may not have been set\n   to match their HEAD in the first place.\n\nThanks.\n"},{"id":"502631","messageId":"xmqqmske9o5s.fsf@gitster.g","threadId":"62096","inReplyTo":"D43GB8KUM7RF.3Q4SYKM4JCQ5L@ferdinandy.com","subject":"Re: [RFC PATCH v2 0/2] set remote/HEAD with fetch","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-09-11T15:59:43Z","receivedAt":"2024-09-11T15:59:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Bence Ferdinandy\" <bence@ferdinandy.com> writes:\n\n> On Wed Sep 11, 2024 at 00:29, Junio C Hamano <gitster@pobox.com> wrote:\n>> Bence Ferdinandy <bence@ferdinandy.com> writes:\n>>\n>> > What is missing for sure is:\n>> > - documentation\n>> > - tests (if needed)\n>>\n>> What change does not need tests?\n>\n> Fair enough, for the next iteration I'll look into tests as well!\n\nIn this project, tests are written not because we want to\ndemonstrate the shiny new feature we just invented.  We write tests\nso that the invention we are shipping to our end users will keep\nbehaveing in the way we originall intended without getting broken by\nfuture careless developers.  The tests serve as watching eyes while\nthe original author of the feature is not watching ;-) \n\nAnd that is why we also write tests that a feature does not trigger\nwhen it should not, as well as it triggers when it should.\n\n>>  * One thing we MUST be careful about is that some remotes may not\n>>    have ANY remote tracking branches (i.e. you only want to use the\n>>    remote mechanism to give you a shorthand for URL, but you do not\n>>    have fetch refspec at all).  Even if refs/remotes/$repo/HEAD is\n>>    missing for such a remote, we should *not* attempt to create it,\n>>    as we are not populating refs/remotes/$repo/master and friends at\n>>    all in such a case.\n>\n> You mean that somebody does git init && git remote add origin $remote, but\n> never does calls fetch?\n\nNo.  If the remote HEAD does not exist, we may still not want to\ncreate it.  Imagine\n\n\t[remote \"his\"]\n\t\turl = https://git.kernel.org/pub/scm/git/git\n\t\tpush = refs/heads/maint\n\t\tpush = refs/heads/master\n\t\tpush = refs/heads/next\n\t\tpush = +refs/heads/seen\n\nwithout any refspec for the fetching side.  \"git fetch his master\"\nmay learn where the remote HEAD is, and it may even be pointing at\ntheir 'master' branch, but because we do not maintain any remote\ntracking information for their 'master' (in other words,\nrefs/remotes/his/master is not updated by this 'fetch' and there is\nno configuration to make future 'fetch' to do so).\n\nThanks.\n"},{"id":"503055","messageId":"20240919121335.298856-3-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20240919121335.298856-1-bence@ferdinandy.com","subject":"[PATCH v3 2/2] fetch: set remote/HEAD if it does not exist","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-09-19T12:13:26Z","receivedAt":"2024-09-19T12:15:45Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"If the user has remote/HEAD set already and it looks like it has changed\non the server, then print a message, otherwise set it if we can.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v3: - does not rely on remote set-head anymore so it only authenticates\n        once\n        - uses the new REF_CREATE_ONLY to atomically check if the ref exists\n          and only write it if it doesn't\n        - in all other cases the maximum it does is print a warning\n\n builtin/fetch.c | 71 +++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 71 insertions(+)\n\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 55f97134aa..2746d40bb5 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1577,6 +1577,70 @@ static int backfill_tags(struct display_state *display_state,\n \treturn retcode;\n }\n \n+static const char *abbrev_ref(const char *name, const char *prefix)\n+{\n+\tskip_prefix(name, prefix, &name);\n+\treturn name;\n+}\n+#define abbrev_branch(name) abbrev_ref((name), \"refs/heads/\")\n+\n+static inline int set_head(const struct ref *remote_refs)\n+{\n+\tint result, ref_changed = 0;\n+\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT, b_local_head = STRBUF_INIT, b_prefix = STRBUF_INIT;\n+\tconst char *remote = gtransport->remote->name;\n+\tchar * head_name = NULL;\n+\tstruct ref *ref, *matches;\n+\tstruct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;\n+\tstruct refspec_item refspec = {\n+\t\t.force = 0,\n+\t\t.pattern = 1,\n+\t\t.src = (char *) \"refs/heads/*\",\n+\t\t.dst = (char *) \"refs/heads/*\",\n+\t};\n+\tstruct string_list heads = STRING_LIST_INIT_DUP;\n+\n+\tget_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);\n+\tmatches = guess_remote_head(find_ref_by_name(remote_refs, \"HEAD\"),\n+\t\t\t\t    fetch_map, 1);\n+\tfor (ref = matches; ref; ref = ref->next)\n+\t\tstring_list_append(&heads, abbrev_branch(ref->name));\n+\n+\n+\tif (!heads.nr)\n+\t\tresult = 1;\n+\telse if (heads.nr > 1) {\n+\t\tresult = 1;\n+\t} else\n+\t\thead_name = xstrdup(heads.items[0].string);\n+\tif (head_name) {\n+\t\tstrbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", remote);\n+\t\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", remote, head_name);\n+\t\tstrbuf_addf(&b_prefix, \"refs/remotes/%s/\", remote);\n+\t\tif (!refs_read_symbolic_ref(get_main_ref_store(the_repository),b_head.buf,&b_local_head)) {\n+\t\t\tref_changed = strcmp(b_remote_head.buf,b_local_head.buf);\n+\t\t\tif (heads.nr == 1 && ref_changed) {\n+\t\t\t\tprintf(\"The ref \\'%s/HEAD\\' has changed from the locally recorded \"\n+\t\t\t\t\t\"\\'%s\\' to \\'%s\\'.\\n\",remote, abbrev_ref(b_local_head.buf,b_prefix.buf), head_name);\n+\t\t\t\tprintf(\"Run \\'git remote set-head -a %s\\' to set it automatically.\\n\", remote);\n+\t\t\t}\n+\t\t}\n+\t\t/* make sure it's valid */\n+\t\tif (!refs_ref_exists(get_main_ref_store(the_repository), b_remote_head.buf))\n+\t\t\tresult = 1;\n+\t\telse if (refs_update_symref(get_main_ref_store(the_repository), b_head.buf,\n+\t\t\t\t\t\tb_remote_head.buf, REF_CREATE_ONLY, \"fetch\"))\n+\t\t\tresult = 1;\n+\t\tfree(head_name);\n+\t}\n+\n+\tstrbuf_release(&b_head);\n+\tstrbuf_release(&b_local_head);\n+\tstrbuf_release(&b_remote_head);\n+\tstrbuf_release(&b_prefix);\n+\treturn result;\n+}\n+\n static int do_fetch(struct transport *transport,\n \t\t    struct refspec *rs,\n \t\t    const struct fetch_config *config)\n@@ -1646,6 +1710,8 @@ static int do_fetch(struct transport *transport,\n \t\t\t\t    \"refs/tags/\");\n \t}\n \n+\tstrvec_push(&transport_ls_refs_options.ref_prefixes,\"HEAD\");\n+\n \tif (must_list_refs) {\n \t\ttrace2_region_enter(\"fetch\", \"remote_refs\", the_repository);\n \t\tremote_refs = transport_get_remote_refs(transport,\n@@ -1790,6 +1856,10 @@ static int do_fetch(struct transport *transport,\n \t\t\t\t  \"you need to specify exactly one branch with the --set-upstream option\"));\n \t\t}\n \t}\n+\tif (!set_head(remote_refs))\n+\t\tprintf(\"Ran into issues with \\'%s/HEAD\\',\\n\"\n+\t\t\t\"use \\'git remote set-head -a %s\\' to investigate\",\n+\t\t\tgtransport->remote->name,gtransport->remote->name);\n \n cleanup:\n \tif (retcode) {\n@@ -2020,6 +2090,7 @@ static int fetch_multiple(struct string_list *list, int max_children,\n \treturn !!result;\n }\n \n+\n /*\n  * Fetching from the promisor remote should use the given filter-spec\n  * or inherit the default filter-spec from the config.\n-- \n2.46.1.546.gaa0cd55cd3.dirty\n\n"},{"id":"503056","messageId":"20240919121335.298856-1-bence@ferdinandy.com","threadId":"62096","inReplyTo":"D43G2CGX2N7L.ZRETD4HLIH0E@ferdinandy.com","subject":"[PATCH v3 0/2] fetch: set remote/HEAD if missing","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-09-19T12:13:24Z","receivedAt":"2024-09-19T12:19:54Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"This series is a complete rewrite of the RFC without using or touching\nthe code of `git remote set-head` (that part of the RFC is being handled\nin a new thread of it's own).\n\nBased on the previous discussions it does an atomic update of\nremote/HEAD if it doesn't exist and is able to. Since we have git remote\nset-head for manual handling of remote/HEAD, my idea was that it only\noutputs minimal information and in general send the user to use remote\nset-head for figuring out issues. If there is a simple, clear HEAD and\nis changed it will give the command to use for switching to this and if\nsomething went wrong it tells the user to check with remote set-head.\nI'm not sure if this is really needed to be honest.\n\nA note on tests:\n\n- the first patch changing refs_update_symref passes all tests\n\n- the series in itself breaks many fetch tests currently, most of them\n  seem to stem from the fact that we now have remote/HEAD refs as extra\n\nI'm planning to add a test to fetch for testing not to overwrite\nexisting refs, and of course I'll go and clean up all the tests, but it\nwould be nice to know beforehand if the approach is good and to finalize\nall the outputs before changing 30 something tests.\n\nBence Ferdinandy (2):\n  update_symref: add REF_CREATE_ONLY option\n  fetch: set remote/HEAD if it does not exist\n\n builtin/branch.c          |  2 +-\n builtin/checkout.c        |  4 +--\n builtin/clone.c           |  6 ++--\n builtin/fetch.c           | 71 +++++++++++++++++++++++++++++++++++++++\n builtin/notes.c           |  2 +-\n builtin/remote.c          |  6 ++--\n builtin/symbolic-ref.c    |  2 +-\n builtin/worktree.c        |  2 +-\n refs.c                    |  6 ++--\n refs.h                    | 12 +++++--\n refs/files-backend.c      |  8 +++++\n reset.c                   |  2 +-\n sequencer.c               |  2 +-\n setup.c                   |  2 +-\n t/helper/test-ref-store.c |  2 +-\n 15 files changed, 108 insertions(+), 21 deletions(-)\n\n-- \n2.46.1.546.gaa0cd55cd3.dirty\n\n"},{"id":"503057","messageId":"20240919121335.298856-2-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20240919121335.298856-1-bence@ferdinandy.com","subject":"[PATCH v3 1/2] update_symref: add REF_CREATE_ONLY option","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-09-19T12:13:25Z","receivedAt":"2024-09-19T12:20:04Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Add a new REF_CREATE_ONLY flag for use by the files backend which will\nonly update the symref if it doesn't already exist. Add the possibility\nto pass extra flags to refs_update_symref so that it can utilize this\nnew flag.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v3: new patch, passes all tests on it's own\n\n builtin/branch.c          |  2 +-\n builtin/checkout.c        |  4 ++--\n builtin/clone.c           |  6 +++---\n builtin/notes.c           |  2 +-\n builtin/remote.c          |  6 +++---\n builtin/symbolic-ref.c    |  2 +-\n builtin/worktree.c        |  2 +-\n refs.c                    |  6 +++---\n refs.h                    | 12 ++++++++++--\n refs/files-backend.c      |  8 ++++++++\n reset.c                   |  2 +-\n sequencer.c               |  2 +-\n setup.c                   |  2 +-\n t/helper/test-ref-store.c |  2 +-\n 14 files changed, 37 insertions(+), 21 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex c98601c6fe..6025bca45e 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -559,7 +559,7 @@ static int replace_each_worktree_head_symref(struct worktree **worktrees,\n \t\t\tcontinue;\n \n \t\trefs = get_worktree_ref_store(worktrees[i]);\n-\t\tif (refs_update_symref(refs, \"HEAD\", newref, logmsg))\n+\t\tif (refs_update_symref(refs, \"HEAD\", newref, 0, logmsg))\n \t\t\tret = error(_(\"HEAD of working tree %s is not updated\"),\n \t\t\t\t    worktrees[i]->path);\n \t}\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 4cfe6fab50..23e28321d6 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1011,7 +1011,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\t\tdescribe_detached_head(_(\"HEAD is now at\"), new_branch_info->commit);\n \t\t}\n \t} else if (new_branch_info->path) {\t/* Switch branches. */\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", new_branch_info->path, msg.buf) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", new_branch_info->path, 0, msg.buf) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t\tif (!opts->quiet) {\n \t\t\tif (old_branch_info->path && !strcmp(new_branch_info->path, old_branch_info->path)) {\n@@ -1475,7 +1475,7 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts)\n \t\tdie(_(\"You are on a branch yet to be born\"));\n \tstrbuf_addf(&branch_ref, \"refs/heads/%s\", opts->new_branch);\n \tstatus = refs_update_symref(get_main_ref_store(the_repository),\n-\t\t\t\t    \"HEAD\", branch_ref.buf, \"checkout -b\");\n+\t\t\t\t    \"HEAD\", branch_ref.buf, 0, \"checkout -b\");\n \tstrbuf_release(&branch_ref);\n \tif (!opts->quiet)\n \t\tfprintf(stderr, _(\"Switched to a new branch '%s'\\n\"),\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 269b6e18a4..43b7878a79 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -659,7 +659,7 @@ static void update_remote_refs(const struct ref *refs,\n \t\tstrbuf_addstr(&head_ref, branch_top);\n \t\tstrbuf_addstr(&head_ref, \"HEAD\");\n \t\tif (refs_update_symref(get_main_ref_store(the_repository), head_ref.buf,\n-\t\t\t\t       remote_head_points_at->peer_ref->name,\n+\t\t\t\t       remote_head_points_at->peer_ref->name, 0,\n \t\t\t\t       msg) < 0)\n \t\t\tdie(_(\"unable to update %s\"), head_ref.buf);\n \t\tstrbuf_release(&head_ref);\n@@ -672,7 +672,7 @@ static void update_head(const struct ref *our, const struct ref *remote,\n \tconst char *head;\n \tif (our && skip_prefix(our->name, \"refs/heads/\", &head)) {\n \t\t/* Local default branch link */\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", our->name, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", our->name, 0, NULL) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t\tif (!option_bare) {\n \t\t\trefs_update_ref(get_main_ref_store(the_repository),\n@@ -701,7 +701,7 @@ static void update_head(const struct ref *our, const struct ref *remote,\n \t\t * Unborn head from remote; same as \"our\" case above except\n \t\t * that we have no ref to update.\n \t\t */\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", unborn, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", unborn, 0, NULL) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t\tif (!option_bare)\n \t\t\tinstall_branch_config(0, head, remote_name, unborn);\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex 04f9dfb7fb..6b42d1139f 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -978,7 +978,7 @@ static int merge(int argc, const char **argv, const char *prefix)\n \t\t\tdie(_(\"a notes merge into %s is already in-progress at %s\"),\n \t\t\t    default_notes_ref(), wt->path);\n \t\tfree_worktrees(worktrees);\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"NOTES_MERGE_REF\", default_notes_ref(), NULL))\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"NOTES_MERGE_REF\", default_notes_ref(), 0, NULL))\n \t\t\tdie(_(\"failed to store link to current notes ref (%s)\"),\n \t\t\t    default_notes_ref());\n \t\tfprintf(stderr, _(\"Automatic notes merge failed. Fix conflicts in %s \"\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 0acc547d69..d28c65599d 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -243,7 +243,7 @@ static int add(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_reset(&buf2);\n \t\tstrbuf_addf(&buf2, \"refs/remotes/%s/%s\", name, master);\n \n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote add\"))\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, 0, \"remote add\"))\n \t\t\tresult = error(_(\"Could not setup master '%s'\"), master);\n \t}\n \n@@ -863,7 +863,7 @@ static int mv(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_reset(&buf3);\n \t\tstrbuf_addf(&buf3, \"remote: renamed %s to %s\",\n \t\t\t\titem->string, buf.buf);\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, buf3.buf))\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, 0, buf3.buf))\n \t\t\tdie(_(\"creating '%s' failed\"), buf.buf);\n \t\tdisplay_progress(progress, ++refs_renamed_nr);\n \t}\n@@ -1443,7 +1443,7 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\t/* make sure it's valid */\n \t\tif (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))\n \t\t\tresult |= error(_(\"Not a valid ref: %s\"), buf2.buf);\n-\t\telse if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote set-head\"))\n+\t\telse if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, 0, \"remote set-head\"))\n \t\t\tresult |= error(_(\"Could not setup %s\"), buf.buf);\n \t\telse if (opt_a)\n \t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\ndiff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c\nindex 81abdd170f..52de3e02af 100644\n--- a/builtin/symbolic-ref.c\n+++ b/builtin/symbolic-ref.c\n@@ -84,7 +84,7 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)\n \t\tif (check_refname_format(argv[1], REFNAME_ALLOW_ONELEVEL) < 0)\n \t\t\tdie(\"Refusing to set '%s' to invalid ref '%s'\", argv[0], argv[1]);\n \t\tret = !!refs_update_symref(get_main_ref_store(the_repository),\n-\t\t\t\t\t   argv[0], argv[1], msg);\n+\t\t\t\t\t   argv[0], argv[1], 0, msg);\n \t\tbreak;\n \tdefault:\n \t\tusage_with_options(git_symbolic_ref_usage, options);\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 41e7f6a327..71434737e8 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -517,7 +517,7 @@ static int add_worktree(const char *path, const char *refname,\n \t\tret = refs_update_ref(wt_refs, NULL, \"HEAD\", &commit->object.oid,\n \t\t\t\t      NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n \telse\n-\t\tret = refs_update_symref(wt_refs, \"HEAD\", symref.buf, NULL);\n+\t\tret = refs_update_symref(wt_refs, \"HEAD\", symref.buf, 0, NULL);\n \tif (ret)\n \t\tgoto done;\n \ndiff --git a/refs.c b/refs.c\nindex ceb72d4bd7..7afe46cadc 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2085,8 +2085,9 @@ 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+\n int refs_update_symref(struct ref_store *refs, const char *ref,\n-\t\t       const char *target, const char *logmsg)\n+\t\t       const char *target, const unsigned int extra_flags, const char *logmsg)\n {\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n@@ -2095,7 +2096,7 @@ int refs_update_symref(struct ref_store *refs, const char *ref,\n \ttransaction = ref_store_transaction_begin(refs, &err);\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, ref, NULL, NULL,\n-\t\t\t\t   target, NULL, REF_NO_DEREF,\n+\t\t\t\t   target, NULL, REF_NO_DEREF | extra_flags,\n \t\t\t\t   logmsg, &err) ||\n \t    ref_transaction_commit(transaction, &err)) {\n \t\tret = error(\"%s\", err.buf);\n@@ -2920,4 +2921,3 @@ int ref_update_expects_existing_old_ref(struct ref_update *update)\n \treturn (update->flags & REF_HAVE_OLD) &&\n \t\t(!is_null_oid(&update->old_oid) || update->old_target);\n }\n-\ndiff --git a/refs.h b/refs.h\nindex f8b919a138..d907451d13 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -569,7 +569,7 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,\n \t\t    const char *newref, const char *logmsg);\n \n int refs_update_symref(struct ref_store *refs, const char *refname,\n-\t\t       const char *target, const char *logmsg);\n+\t\t       const char *target, const unsigned int extra_flags, const char *logmsg);\n \n enum action_on_err {\n \tUPDATE_REFS_MSG_ON_ERR,\n@@ -672,13 +672,21 @@ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs,\n  */\n #define REF_SKIP_CREATE_REFLOG (1 << 12)\n \n+/*\n+ * If the reference has already been created do not touch it.\n+ */\n+\n+#define REF_CREATE_ONLY (1 << 13)\n+\n+\n /*\n  * Bitmask of all of the flags that are allowed to be passed in to\n  * ref_transaction_update() and friends:\n  */\n #define REF_TRANSACTION_UPDATE_ALLOWED_FLAGS                                  \\\n \t(REF_NO_DEREF | REF_FORCE_CREATE_REFLOG | REF_SKIP_OID_VERIFICATION | \\\n-\t REF_SKIP_REFNAME_VERIFICATION | REF_SKIP_CREATE_REFLOG)\n+\t REF_SKIP_REFNAME_VERIFICATION | REF_SKIP_CREATE_REFLOG | \\\n+\t REF_CREATE_ONLY)\n \n /*\n  * Add a reference update to transaction. `new_oid` is the value that\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex c7f3f4e591..1440b69b87 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2993,6 +2993,8 @@ static int files_transaction_finish(struct ref_store *ref_store,\n \t\tstruct ref_update *update = transaction->updates[i];\n \t\tstruct ref_lock *lock = update->backend_data;\n \n+\t\tif (update->flags & REF_CREATE_ONLY && refs_ref_exists(ref_store, update->refname))\n+\t\t\tcontinue;\n \t\tif (update->flags & REF_NEEDS_COMMIT ||\n \t\t    update->flags & REF_LOG_ONLY) {\n \t\t\tif (parse_and_write_reflog(refs, update, lock, err)) {\n@@ -3031,6 +3033,8 @@ static int files_transaction_finish(struct ref_store *ref_store,\n \t */\n \tfor (i = 0; i < transaction->nr; i++) {\n \t\tstruct ref_update *update = transaction->updates[i];\n+\t\tif (update->flags & REF_CREATE_ONLY && refs_ref_exists(ref_store, update->refname))\n+\t\t\tcontinue;\n \t\tif (update->flags & REF_DELETING &&\n \t\t    !(update->flags & REF_LOG_ONLY) &&\n \t\t    !(update->flags & REF_IS_PRUNING)) {\n@@ -3061,6 +3065,8 @@ static int files_transaction_finish(struct ref_store *ref_store,\n \tfor (i = 0; i < transaction->nr; i++) {\n \t\tstruct ref_update *update = transaction->updates[i];\n \t\tstruct ref_lock *lock = update->backend_data;\n+\t\tif (update->flags & REF_CREATE_ONLY && refs_ref_exists(ref_store, update->refname))\n+\t\t\tcontinue;\n \n \t\tif (update->flags & REF_DELETING &&\n \t\t    !(update->flags & REF_LOG_ONLY)) {\n@@ -3085,6 +3091,8 @@ static int files_transaction_finish(struct ref_store *ref_store,\n \n \tfor (i = 0; i < transaction->nr; i++) {\n \t\tstruct ref_update *update = transaction->updates[i];\n+\t\tif (update->flags & REF_CREATE_ONLY && refs_ref_exists(ref_store, update->refname))\n+\t\t\tcontinue;\n \n \t\tif (update->flags & REF_DELETED_RMDIR) {\n \t\t\t/*\ndiff --git a/reset.c b/reset.c\nindex b22b1be792..8dce5f2133 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -75,7 +75,7 @@ static int update_refs(const struct reset_head_opts *opts,\n \t\t\t\t      UPDATE_REFS_MSG_ON_ERR);\n \t\tif (!ret)\n \t\t\tret = refs_update_symref(get_main_ref_store(the_repository),\n-\t\t\t\t\t\t \"HEAD\", switch_to_branch,\n+\t\t\t\t\t\t \"HEAD\", switch_to_branch, 0,\n \t\t\t\t\t\t reflog_head);\n \t}\n \tif (!ret && run_hook)\ndiff --git a/sequencer.c b/sequencer.c\nindex 8d01cd50ac..924a78dab8 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -5107,7 +5107,7 @@ static int pick_commits(struct repository *r,\n \t\t\t}\n \t\t\tmsg = reflog_message(opts, \"finish\", \"returning to %s\",\n \t\t\t\thead_ref.buf);\n-\t\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", head_ref.buf, msg)) {\n+\t\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", head_ref.buf, 0, msg)) {\n \t\t\t\tres = error(_(\"could not update HEAD to %s\"),\n \t\t\t\t\thead_ref.buf);\n \t\t\t\tgoto cleanup_head_ref;\ndiff --git a/setup.c b/setup.c\nindex 29f8673921..6a1fdef2c3 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -2169,7 +2169,7 @@ void create_reference_database(enum ref_storage_format ref_storage_format,\n \t\t\tdie(_(\"invalid initial branch name: '%s'\"),\n \t\t\t    initial_branch);\n \n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", ref, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", ref, 0, NULL) < 0)\n \t\t\texit(1);\n \t\tfree(ref);\n \t}\ndiff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c\nindex 65346dee55..90af41edce 100644\n--- a/t/helper/test-ref-store.c\n+++ b/t/helper/test-ref-store.c\n@@ -120,7 +120,7 @@ static int cmd_create_symref(struct ref_store *refs, const char **argv)\n \tconst char *target = notnull(*argv++, \"target\");\n \tconst char *logmsg = *argv++;\n \n-\treturn refs_update_symref(refs, refname, target, logmsg);\n+\treturn refs_update_symref(refs, refname, target, 0, logmsg);\n }\n \n static struct flag_definition transaction_flags[] = {\n-- \n2.46.1.546.gaa0cd55cd3.dirty\n\n"},{"id":"503103","messageId":"xmqqtteb45zr.fsf@gitster.g","threadId":"62096","inReplyTo":"20240919121335.298856-2-bence@ferdinandy.com","subject":"Re: [PATCH v3 1/2] update_symref: add REF_CREATE_ONLY option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-09-19T22:46:00Z","receivedAt":"2024-09-19T22:46:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bence Ferdinandy <bence@ferdinandy.com> writes:\n\n> Add a new REF_CREATE_ONLY flag for use by the files backend which will\n> only update the symref if it doesn't already exist. Add the possibility\n> to pass extra flags to refs_update_symref so that it can utilize this\n> new flag.\n\nIf I wanted to create a symref that points at A, there are three cases:\n\n (1) the symref does not exist.  \n (2) the symref exists and points at A.\n (3) the symref exists and points at B.\n\nI'll see a symref that points at A at the end in the first two\ncases, and my request is silently ignored in the third case.\n\nI'd expect that the caller can tell the failing case apart from the\nsuccessful case with the return value or something.  The caller\nmight want to tell between the first two cases for reporting\npurposes, but I do not care as much as I would care about detecting\ntrue failures.\n\nNobody actually passes the flag yet, so we would not be able to tell\nif any of the added code is buggy from this step alone.  Let's see\nwhat happens in the next patch ;-).\n\n> diff --git a/refs.c b/refs.c\n> index ceb72d4bd7..7afe46cadc 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -2085,8 +2085,9 @@ 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> +\n>  int refs_update_symref(struct ref_store *refs, const char *ref,\n> -\t\t       const char *target, const char *logmsg)\n> +\t\t       const char *target, const unsigned int extra_flags, const char *logmsg)\n\nWhile it is not _wrong_ per-se to mark an \"unsigned int\" parameter\nas \"const\", it is a bit unusual in this code base.  The only thing\nit prevents us from doing is to mutate it until this function\nreturns, which does not help all that much in making the code safer,\nas opposed to marking a parameter of a pointer type as a const\npointer.\n\n"},{"id":"503104","messageId":"xmqqjzf744zg.fsf@gitster.g","threadId":"62096","inReplyTo":"20240919121335.298856-3-bence@ferdinandy.com","subject":"Re: [PATCH v3 2/2] fetch: set remote/HEAD if it does not exist","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-09-19T23:07:47Z","receivedAt":"2024-09-19T23:07:50Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bence Ferdinandy <bence@ferdinandy.com> writes:\n\n> If the user has remote/HEAD set already and it looks like it has changed\n> on the server, then print a message, otherwise set it if we can.\n>\n> Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n> ---\n>\n> Notes:\n>     v3: - does not rely on remote set-head anymore so it only authenticates\n>         once\n>         - uses the new REF_CREATE_ONLY to atomically check if the ref exists\n>           and only write it if it doesn't\n>         - in all other cases the maximum it does is print a warning\n\nExcept for one thing, I can tell from the above that the design is\nsensible without looking at the actual patch.  Nicely described.\n\nThe \"one thing\" is what to do if you normally do *not* keep track of\nthe remote-tracking refs for this particular remote.\n\nIf I am only fetching refs (or HEAD) in FETCH_HEAD for immediate\nconsumtion by doing \"git pull bence HEAD\" with something like\n\n    [remote \"bence\"]\n\t URL = http://github.com/bence/git\n\n(with no other configuration under remote.bence.* hierarchy), I do\nnot think I want the code to create refs/remotes/bence/HEAD, with\nno other remote-tracking refs in the same hierarchy, even if it finds\nno existing refs/remotes/bence/HEAD there.  For that, I suspect that \nyou'd need to see if the pointee of refs/remotes/bence/HEAD either\nalready exists or this fetch is about to create it, or something\nlike that, before deciding to create a ref with the REF_CREATE_ONLY\nflag.\n\nIf you are doing so already in the code (I haven't started reading\nit yet at this moment), you should mention it in your proposed log\nmessage, so discount my earlier \"Nicely described\" a bit ;-).\n\nLet's see what actually happens in the code now.\n\n> +static const char *abbrev_ref(const char *name, const char *prefix)\n> +{\n> +\tskip_prefix(name, prefix, &name);\n> +\treturn name;\n> +}\n> +#define abbrev_branch(name) abbrev_ref((name), \"refs/heads/\")\n\nWe do not call the act of optionally skipping prefix \"abbreviate\" in\nthis project (object names are abbreviated but that is done by\nchomping the later bytes of a long name).\n\nI suspect that a caller of either of these two functions are\ninherently buggy in that they _optionally_ skip the prefix so they\ndo the same thing for \"refs/heads/main\" and \"main\" but not\n\"heads/main\".  The callsites may need to be inspected to see how\nthey should deal with cases where skip_prefix() did *not* see\nanything to skip.\n\n> +static inline int set_head(const struct ref *remote_refs)\n\nDrop \"inline\".  You do not want to inline this much code if there\nare many callers, and if you have only one caller, the compiler\nwould inline it into the sole caller if it is the more efficient\nthing to do.\n\n> +{\n> +\tint result, ref_changed = 0;\n> +\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT, b_local_head = STRBUF_INIT, b_prefix = STRBUF_INIT;\n> +\tconst char *remote = gtransport->remote->name;\n> +\tchar * head_name = NULL;\n> +\tstruct ref *ref, *matches;\n> +\tstruct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;\n> +\tstruct refspec_item refspec = {\n> +\t\t.force = 0,\n> +\t\t.pattern = 1,\n> +\t\t.src = (char *) \"refs/heads/*\",\n> +\t\t.dst = (char *) \"refs/heads/*\",\n> +\t};\n> +\tstruct string_list heads = STRING_LIST_INIT_DUP;\n> +\n> +\tget_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);\n> +\tmatches = guess_remote_head(find_ref_by_name(remote_refs, \"HEAD\"),\n> +\t\t\t\t    fetch_map, 1);\n> +\tfor (ref = matches; ref; ref = ref->next)\n> +\t\tstring_list_append(&heads, abbrev_branch(ref->name));\n> +\n> +\n> +\tif (!heads.nr)\n> +\t\tresult = 1;\n> +\telse if (heads.nr > 1) {\n> +\t\tresult = 1;\n> +\t} else\n> +\t\thead_name = xstrdup(heads.items[0].string);\n> +\tif (head_name) {\n> +\t\tstrbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", remote);\n> +\t\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", remote, head_name);\n> +\t\tstrbuf_addf(&b_prefix, \"refs/remotes/%s/\", remote);\n> +\t\tif (!refs_read_symbolic_ref(get_main_ref_store(the_repository),b_head.buf,&b_local_head)) {\n\nOverly long lines around here cannot be sanely reviewed on my\nterminal, so I'll stop here for now.  \n\nDo not omit SP after \",\", as that is the least effective way to\nshorten overly long lines.  Instead, consider introducing a\none-time-use temporary variables with meaningful name, e.g.\n\n\t\tstruct ref_store *store = get_main_ref_store(the_repository);\n                \n                strbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", remote);\n\t\t...\n\t\tif (!refs_read_symbolic_ref(store, b_head.buf, &current)) {\n    \t\t\t/* it's current value is in \"current\" */\n\t\t\t...\n\n"},{"id":"503125","messageId":"D4B5S3TNZTQI.3GI68VA0NFGJS@ferdinandy.com","threadId":"62096","inReplyTo":"xmqqjzf744zg.fsf@gitster.g","subject":"Re: [PATCH v3 2/2] fetch: set remote/HEAD if it does not exist","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-09-20T13:50:51Z","receivedAt":"2024-09-20T13:51:38Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Fri Sep 20, 2024 at 01:07, Junio C Hamano <gitster@pobox.com> wrote:\n> Bence Ferdinandy <bence@ferdinandy.com> writes:\n>\n> > If the user has remote/HEAD set already and it looks like it has changed\n> > on the server, then print a message, otherwise set it if we can.\n> >\n> > Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n> > ---\n> >\n> > Notes:\n> >     v3: - does not rely on remote set-head anymore so it only authenticates\n> >         once\n> >         - uses the new REF_CREATE_ONLY to atomically check if the ref exists\n> >           and only write it if it doesn't\n> >         - in all other cases the maximum it does is print a warning\n>\n> Except for one thing, I can tell from the above that the design is\n> sensible without looking at the actual patch.  Nicely described.\n>\n> The \"one thing\" is what to do if you normally do *not* keep track of\n> the remote-tracking refs for this particular remote.\n>\n> If I am only fetching refs (or HEAD) in FETCH_HEAD for immediate\n> consumtion by doing \"git pull bence HEAD\" with something like\n>\n>     [remote \"bence\"]\n> \t URL = http://github.com/bence/git\n>\n> (with no other configuration under remote.bence.* hierarchy), I do\n> not think I want the code to create refs/remotes/bence/HEAD, with\n> no other remote-tracking refs in the same hierarchy, even if it finds\n> no existing refs/remotes/bence/HEAD there.  For that, I suspect that \n> you'd need to see if the pointee of refs/remotes/bence/HEAD either\n> already exists or this fetch is about to create it, or something\n> like that, before deciding to create a ref with the REF_CREATE_ONLY\n> flag.\n\nI'll take another look at this.\n\n>\n> If you are doing so already in the code (I haven't started reading\n> it yet at this moment), you should mention it in your proposed log\n> message, so discount my earlier \"Nicely described\" a bit ;-).\n\n:D\n\n>\n> Let's see what actually happens in the code now.\n>\n> > +static const char *abbrev_ref(const char *name, const char *prefix)\n> > +{\n> > +\tskip_prefix(name, prefix, &name);\n> > +\treturn name;\n> > +}\n> > +#define abbrev_branch(name) abbrev_ref((name), \"refs/heads/\")\n>\n> We do not call the act of optionally skipping prefix \"abbreviate\" in\n> this project (object names are abbreviated but that is done by\n> chomping the later bytes of a long name).\n>\n> I suspect that a caller of either of these two functions are\n> inherently buggy in that they _optionally_ skip the prefix so they\n> do the same thing for \"refs/heads/main\" and \"main\" but not\n> \"heads/main\".  The callsites may need to be inspected to see how\n> they should deal with cases where skip_prefix() did *not* see\n> anything to skip.\n\nI copied this verbatim from builtin/remote.c\nhttps://github.com/git/git/blob/master/builtin/remote.c#L267 so it looked like\na safe bet T_T I'm not 100% sure, but I think there's no use case where you\npass a ref directly to remote except for set_head, but there it's not used, so\nit looks safe?\n\nThat being said, I can change the name(s) to not use abbrev here, especially if\nyou have a good suggestion. Maybe strip_prefix_ref/branch? Again in fetch, we\n(should) know what type of refs we're calling it on, so should be safe?\n\n>\n> > +static inline int set_head(const struct ref *remote_refs)\n>\n> Drop \"inline\".  You do not want to inline this much code if there\n> are many callers, and if you have only one caller, the compiler\n> would inline it into the sole caller if it is the more efficient\n> thing to do.\n\nAck.\n\n>\n> > +{\n> > +\tint result, ref_changed = 0;\n> > +\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT, b_local_head = STRBUF_INIT, b_prefix = STRBUF_INIT;\n> > +\tconst char *remote = gtransport->remote->name;\n> > +\tchar * head_name = NULL;\n> > +\tstruct ref *ref, *matches;\n> > +\tstruct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;\n> > +\tstruct refspec_item refspec = {\n> > +\t\t.force = 0,\n> > +\t\t.pattern = 1,\n> > +\t\t.src = (char *) \"refs/heads/*\",\n> > +\t\t.dst = (char *) \"refs/heads/*\",\n> > +\t};\n> > +\tstruct string_list heads = STRING_LIST_INIT_DUP;\n> > +\n> > +\tget_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);\n> > +\tmatches = guess_remote_head(find_ref_by_name(remote_refs, \"HEAD\"),\n> > +\t\t\t\t    fetch_map, 1);\n> > +\tfor (ref = matches; ref; ref = ref->next)\n> > +\t\tstring_list_append(&heads, abbrev_branch(ref->name));\n> > +\n> > +\n> > +\tif (!heads.nr)\n> > +\t\tresult = 1;\n> > +\telse if (heads.nr > 1) {\n> > +\t\tresult = 1;\n> > +\t} else\n> > +\t\thead_name = xstrdup(heads.items[0].string);\n> > +\tif (head_name) {\n> > +\t\tstrbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", remote);\n> > +\t\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", remote, head_name);\n> > +\t\tstrbuf_addf(&b_prefix, \"refs/remotes/%s/\", remote);\n> > +\t\tif (!refs_read_symbolic_ref(get_main_ref_store(the_repository),b_head.buf,&b_local_head)) {\n>\n> Overly long lines around here cannot be sanely reviewed on my\n> terminal, so I'll stop here for now.  \n>\n> Do not omit SP after \",\", as that is the least effective way to\n> shorten overly long lines.  Instead, consider introducing a\n> one-time-use temporary variables with meaningful name, e.g.\n>\n> \t\tstruct ref_store *store = get_main_ref_store(the_repository);\n>                 \n>                 strbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", remote);\n> \t\t...\n> \t\tif (!refs_read_symbolic_ref(store, b_head.buf, &current)) {\n>     \t\t\t/* it's current value is in \"current\" */\n> \t\t\t...\n\n\nAh, yeah terribly sorry about this, I've gotten so used to automatic\nformatters, that I just didn't take a second look. I'll be more careful in the\nnext round.\n\n-- \nbence.ferdinandy.com\n\n"},{"id":"503126","messageId":"D4B6893HT776.LNXNEK2YJHQE@ferdinandy.com","threadId":"62096","inReplyTo":"xmqqtteb45zr.fsf@gitster.g","subject":"Re: [PATCH v3 1/2] update_symref: add REF_CREATE_ONLY option","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-09-20T14:11:56Z","receivedAt":"2024-09-20T14:12:27Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Fri Sep 20, 2024 at 00:46, Junio C Hamano <gitster@pobox.com> wrote:\n> Bence Ferdinandy <bence@ferdinandy.com> writes:\n[ snip] \n> If I wanted to create a symref that points at A, there are three cases:\n>\n>  (1) the symref does not exist.  \n>  (2) the symref exists and points at A.\n>  (3) the symref exists and points at B.\n>\n> I'll see a symref that points at A at the end in the first two\n> cases, and my request is silently ignored in the third case.\n>\n> I'd expect that the caller can tell the failing case apart from the\n> successful case with the return value or something.  The caller\n> might want to tell between the first two cases for reporting\n> purposes, but I do not care as much as I would care about detecting\n> true failures.\n\nHmm. So in case I'm passing REF_CREATE_ONLY I would not expect the above cases\nto be error in the sense that transaction_finish should not report a failure\nand thus have all callers assume things went wrong. On the other hand it's\na valid concern, that the caller may want to check what happened. Actually, the\nidea that I mentioned in \n\nhttps://lore.kernel.org/git/D4AK4USDVP5T.10INJOFE2I8LE@ferdinandy.com/  \n\nmay actually be useful here as well. We could record the state of the reference\natomically before the transaction in update, and then if the caller is\ninterested, they can match this against what they requested. That way they can\nfigure out which of the 3 cases they were in without raceconditions after the\nsituation. Actually, this way any feedback could be given to the user post\ntransaction here as well.\n\nIf this sounds sensible, then I guess it would make sense to rejoin the\nset-head patch into this thread as well ...\n\n[snip]\n\n>\n> > diff --git a/refs.c b/refs.c\n> > index ceb72d4bd7..7afe46cadc 100644\n> > --- a/refs.c\n> > +++ b/refs.c\n> > @@ -2085,8 +2085,9 @@ 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> > +\n> >  int refs_update_symref(struct ref_store *refs, const char *ref,\n> > -\t\t       const char *target, const char *logmsg)\n> > +\t\t       const char *target, const unsigned int extra_flags, const char *logmsg)\n>\n> While it is not _wrong_ per-se to mark an \"unsigned int\" parameter\n> as \"const\", it is a bit unusual in this code base.  The only thing\n> it prevents us from doing is to mutate it until this function\n> returns, which does not help all that much in making the code safer,\n> as opposed to marking a parameter of a pointer type as a const\n> pointer.\n\nMakes sense, I'll drop it.\n\nThanks very much for your patience!\n\nBest,\nBence\n\n-- \nbence.ferdinandy.com\n\n"},{"id":"503190","messageId":"7fecc442-8d5e-4542-8ce8-907c35db870d@gmail.com","threadId":"62096","inReplyTo":"20240919121335.298856-2-bence@ferdinandy.com","subject":"Re: [PATCH v3 1/2] update_symref: add REF_CREATE_ONLY option","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-09-21T13:40:00Z","receivedAt":"2024-09-21T13:40:03Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 19/09/2024 13:13, Bence Ferdinandy wrote:\n> Add a new REF_CREATE_ONLY flag for use by the files backend which will\n> only update the symref if it doesn't already exist. Add the possibility\n> to pass extra flags to refs_update_symref so that it can utilize this\n> new flag.\n\nI'm not sure we need a new flag to do this as it is already supported by\nthe ref transaction api.\n\n\tstruct ref_transaction *t;\n\tstruct strbuf err = STRBUF_INIT;\n\tstruct ref_store refs = get_main_ref_store(the_repository);\n\tint ret = 0;\n\n\tt = ref_transaction_begin(refs, &err);\n\tif (!t ||\n\t    ref_transaction_create(t, b_head.buf, NULL, b_remote_head.buf,\n\t\t\t\t   REF_NO_DEREF, \"fetch\", &err) ||\n\t    ref_transaction_commit(t, &err))\n\t\tret = error(_(\"%s\", err.buf));\n\n\tref_transaction_free(transaction);\n\tstrbuf_release(&err);\n\n\treturn ret;\n\nBest Wishes\n\nPhillip\n\n> Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n> ---\n> \n> Notes:\n>      v3: new patch, passes all tests on it's own\n> \n>   builtin/branch.c          |  2 +-\n>   builtin/checkout.c        |  4 ++--\n>   builtin/clone.c           |  6 +++---\n>   builtin/notes.c           |  2 +-\n>   builtin/remote.c          |  6 +++---\n>   builtin/symbolic-ref.c    |  2 +-\n>   builtin/worktree.c        |  2 +-\n>   refs.c                    |  6 +++---\n>   refs.h                    | 12 ++++++++++--\n>   refs/files-backend.c      |  8 ++++++++\n>   reset.c                   |  2 +-\n>   sequencer.c               |  2 +-\n>   setup.c                   |  2 +-\n>   t/helper/test-ref-store.c |  2 +-\n>   14 files changed, 37 insertions(+), 21 deletions(-)\n> \n> diff --git a/builtin/branch.c b/builtin/branch.c\n> index c98601c6fe..6025bca45e 100644\n> --- a/builtin/branch.c\n> +++ b/builtin/branch.c\n> @@ -559,7 +559,7 @@ static int replace_each_worktree_head_symref(struct worktree **worktrees,\n>   \t\t\tcontinue;\n>   \n>   \t\trefs = get_worktree_ref_store(worktrees[i]);\n> -\t\tif (refs_update_symref(refs, \"HEAD\", newref, logmsg))\n> +\t\tif (refs_update_symref(refs, \"HEAD\", newref, 0, logmsg))\n>   \t\t\tret = error(_(\"HEAD of working tree %s is not updated\"),\n>   \t\t\t\t    worktrees[i]->path);\n>   \t}\n> diff --git a/builtin/checkout.c b/builtin/checkout.c\n> index 4cfe6fab50..23e28321d6 100644\n> --- a/builtin/checkout.c\n> +++ b/builtin/checkout.c\n> @@ -1011,7 +1011,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n>   \t\t\tdescribe_detached_head(_(\"HEAD is now at\"), new_branch_info->commit);\n>   \t\t}\n>   \t} else if (new_branch_info->path) {\t/* Switch branches. */\n> -\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", new_branch_info->path, msg.buf) < 0)\n> +\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", new_branch_info->path, 0, msg.buf) < 0)\n>   \t\t\tdie(_(\"unable to update HEAD\"));\n>   \t\tif (!opts->quiet) {\n>   \t\t\tif (old_branch_info->path && !strcmp(new_branch_info->path, old_branch_info->path)) {\n> @@ -1475,7 +1475,7 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts)\n>   \t\tdie(_(\"You are on a branch yet to be born\"));\n>   \tstrbuf_addf(&branch_ref, \"refs/heads/%s\", opts->new_branch);\n>   \tstatus = refs_update_symref(get_main_ref_store(the_repository),\n> -\t\t\t\t    \"HEAD\", branch_ref.buf, \"checkout -b\");\n> +\t\t\t\t    \"HEAD\", branch_ref.buf, 0, \"checkout -b\");\n>   \tstrbuf_release(&branch_ref);\n>   \tif (!opts->quiet)\n>   \t\tfprintf(stderr, _(\"Switched to a new branch '%s'\\n\"),\n> diff --git a/builtin/clone.c b/builtin/clone.c\n> index 269b6e18a4..43b7878a79 100644\n> --- a/builtin/clone.c\n> +++ b/builtin/clone.c\n> @@ -659,7 +659,7 @@ static void update_remote_refs(const struct ref *refs,\n>   \t\tstrbuf_addstr(&head_ref, branch_top);\n>   \t\tstrbuf_addstr(&head_ref, \"HEAD\");\n>   \t\tif (refs_update_symref(get_main_ref_store(the_repository), head_ref.buf,\n> -\t\t\t\t       remote_head_points_at->peer_ref->name,\n> +\t\t\t\t       remote_head_points_at->peer_ref->name, 0,\n>   \t\t\t\t       msg) < 0)\n>   \t\t\tdie(_(\"unable to update %s\"), head_ref.buf);\n>   \t\tstrbuf_release(&head_ref);\n> @@ -672,7 +672,7 @@ static void update_head(const struct ref *our, const struct ref *remote,\n>   \tconst char *head;\n>   \tif (our && skip_prefix(our->name, \"refs/heads/\", &head)) {\n>   \t\t/* Local default branch link */\n> -\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", our->name, NULL) < 0)\n> +\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", our->name, 0, NULL) < 0)\n>   \t\t\tdie(_(\"unable to update HEAD\"));\n>   \t\tif (!option_bare) {\n>   \t\t\trefs_update_ref(get_main_ref_store(the_repository),\n> @@ -701,7 +701,7 @@ static void update_head(const struct ref *our, const struct ref *remote,\n>   \t\t * Unborn head from remote; same as \"our\" case above except\n>   \t\t * that we have no ref to update.\n>   \t\t */\n> -\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", unborn, NULL) < 0)\n> +\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", unborn, 0, NULL) < 0)\n>   \t\t\tdie(_(\"unable to update HEAD\"));\n>   \t\tif (!option_bare)\n>   \t\t\tinstall_branch_config(0, head, remote_name, unborn);\n> diff --git a/builtin/notes.c b/builtin/notes.c\n> index 04f9dfb7fb..6b42d1139f 100644\n> --- a/builtin/notes.c\n> +++ b/builtin/notes.c\n> @@ -978,7 +978,7 @@ static int merge(int argc, const char **argv, const char *prefix)\n>   \t\t\tdie(_(\"a notes merge into %s is already in-progress at %s\"),\n>   \t\t\t    default_notes_ref(), wt->path);\n>   \t\tfree_worktrees(worktrees);\n> -\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"NOTES_MERGE_REF\", default_notes_ref(), NULL))\n> +\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"NOTES_MERGE_REF\", default_notes_ref(), 0, NULL))\n>   \t\t\tdie(_(\"failed to store link to current notes ref (%s)\"),\n>   \t\t\t    default_notes_ref());\n>   \t\tfprintf(stderr, _(\"Automatic notes merge failed. Fix conflicts in %s \"\n> diff --git a/builtin/remote.c b/builtin/remote.c\n> index 0acc547d69..d28c65599d 100644\n> --- a/builtin/remote.c\n> +++ b/builtin/remote.c\n> @@ -243,7 +243,7 @@ static int add(int argc, const char **argv, const char *prefix)\n>   \t\tstrbuf_reset(&buf2);\n>   \t\tstrbuf_addf(&buf2, \"refs/remotes/%s/%s\", name, master);\n>   \n> -\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote add\"))\n> +\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, 0, \"remote add\"))\n>   \t\t\tresult = error(_(\"Could not setup master '%s'\"), master);\n>   \t}\n>   \n> @@ -863,7 +863,7 @@ static int mv(int argc, const char **argv, const char *prefix)\n>   \t\tstrbuf_reset(&buf3);\n>   \t\tstrbuf_addf(&buf3, \"remote: renamed %s to %s\",\n>   \t\t\t\titem->string, buf.buf);\n> -\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, buf3.buf))\n> +\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, 0, buf3.buf))\n>   \t\t\tdie(_(\"creating '%s' failed\"), buf.buf);\n>   \t\tdisplay_progress(progress, ++refs_renamed_nr);\n>   \t}\n> @@ -1443,7 +1443,7 @@ static int set_head(int argc, const char **argv, const char *prefix)\n>   \t\t/* make sure it's valid */\n>   \t\tif (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))\n>   \t\t\tresult |= error(_(\"Not a valid ref: %s\"), buf2.buf);\n> -\t\telse if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote set-head\"))\n> +\t\telse if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, 0, \"remote set-head\"))\n>   \t\t\tresult |= error(_(\"Could not setup %s\"), buf.buf);\n>   \t\telse if (opt_a)\n>   \t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\n> diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c\n> index 81abdd170f..52de3e02af 100644\n> --- a/builtin/symbolic-ref.c\n> +++ b/builtin/symbolic-ref.c\n> @@ -84,7 +84,7 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)\n>   \t\tif (check_refname_format(argv[1], REFNAME_ALLOW_ONELEVEL) < 0)\n>   \t\t\tdie(\"Refusing to set '%s' to invalid ref '%s'\", argv[0], argv[1]);\n>   \t\tret = !!refs_update_symref(get_main_ref_store(the_repository),\n> -\t\t\t\t\t   argv[0], argv[1], msg);\n> +\t\t\t\t\t   argv[0], argv[1], 0, msg);\n>   \t\tbreak;\n>   \tdefault:\n>   \t\tusage_with_options(git_symbolic_ref_usage, options);\n> diff --git a/builtin/worktree.c b/builtin/worktree.c\n> index 41e7f6a327..71434737e8 100644\n> --- a/builtin/worktree.c\n> +++ b/builtin/worktree.c\n> @@ -517,7 +517,7 @@ static int add_worktree(const char *path, const char *refname,\n>   \t\tret = refs_update_ref(wt_refs, NULL, \"HEAD\", &commit->object.oid,\n>   \t\t\t\t      NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n>   \telse\n> -\t\tret = refs_update_symref(wt_refs, \"HEAD\", symref.buf, NULL);\n> +\t\tret = refs_update_symref(wt_refs, \"HEAD\", symref.buf, 0, NULL);\n>   \tif (ret)\n>   \t\tgoto done;\n>   \n> diff --git a/refs.c b/refs.c\n> index ceb72d4bd7..7afe46cadc 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -2085,8 +2085,9 @@ 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> +\n>   int refs_update_symref(struct ref_store *refs, const char *ref,\n> -\t\t       const char *target, const char *logmsg)\n> +\t\t       const char *target, const unsigned int extra_flags, const char *logmsg)\n>   {\n>   \tstruct ref_transaction *transaction;\n>   \tstruct strbuf err = STRBUF_INIT;\n> @@ -2095,7 +2096,7 @@ int refs_update_symref(struct ref_store *refs, const char *ref,\n>   \ttransaction = ref_store_transaction_begin(refs, &err);\n>   \tif (!transaction ||\n>   \t    ref_transaction_update(transaction, ref, NULL, NULL,\n> -\t\t\t\t   target, NULL, REF_NO_DEREF,\n> +\t\t\t\t   target, NULL, REF_NO_DEREF | extra_flags,\n>   \t\t\t\t   logmsg, &err) ||\n>   \t    ref_transaction_commit(transaction, &err)) {\n>   \t\tret = error(\"%s\", err.buf);\n> @@ -2920,4 +2921,3 @@ int ref_update_expects_existing_old_ref(struct ref_update *update)\n>   \treturn (update->flags & REF_HAVE_OLD) &&\n>   \t\t(!is_null_oid(&update->old_oid) || update->old_target);\n>   }\n> -\n> diff --git a/refs.h b/refs.h\n> index f8b919a138..d907451d13 100644\n> --- a/refs.h\n> +++ b/refs.h\n> @@ -569,7 +569,7 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,\n>   \t\t    const char *newref, const char *logmsg);\n>   \n>   int refs_update_symref(struct ref_store *refs, const char *refname,\n> -\t\t       const char *target, const char *logmsg);\n> +\t\t       const char *target, const unsigned int extra_flags, const char *logmsg);\n>   \n>   enum action_on_err {\n>   \tUPDATE_REFS_MSG_ON_ERR,\n> @@ -672,13 +672,21 @@ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs,\n>    */\n>   #define REF_SKIP_CREATE_REFLOG (1 << 12)\n>   \n> +/*\n> + * If the reference has already been created do not touch it.\n> + */\n> +\n> +#define REF_CREATE_ONLY (1 << 13)\n> +\n> +\n>   /*\n>    * Bitmask of all of the flags that are allowed to be passed in to\n>    * ref_transaction_update() and friends:\n>    */\n>   #define REF_TRANSACTION_UPDATE_ALLOWED_FLAGS                                  \\\n>   \t(REF_NO_DEREF | REF_FORCE_CREATE_REFLOG | REF_SKIP_OID_VERIFICATION | \\\n> -\t REF_SKIP_REFNAME_VERIFICATION | REF_SKIP_CREATE_REFLOG)\n> +\t REF_SKIP_REFNAME_VERIFICATION | REF_SKIP_CREATE_REFLOG | \\\n> +\t REF_CREATE_ONLY)\n>   \n>   /*\n>    * Add a reference update to transaction. `new_oid` is the value that\n> diff --git a/refs/files-backend.c b/refs/files-backend.c\n> index c7f3f4e591..1440b69b87 100644\n> --- a/refs/files-backend.c\n> +++ b/refs/files-backend.c\n> @@ -2993,6 +2993,8 @@ static int files_transaction_finish(struct ref_store *ref_store,\n>   \t\tstruct ref_update *update = transaction->updates[i];\n>   \t\tstruct ref_lock *lock = update->backend_data;\n>   \n> +\t\tif (update->flags & REF_CREATE_ONLY && refs_ref_exists(ref_store, update->refname))\n> +\t\t\tcontinue;\n>   \t\tif (update->flags & REF_NEEDS_COMMIT ||\n>   \t\t    update->flags & REF_LOG_ONLY) {\n>   \t\t\tif (parse_and_write_reflog(refs, update, lock, err)) {\n> @@ -3031,6 +3033,8 @@ static int files_transaction_finish(struct ref_store *ref_store,\n>   \t */\n>   \tfor (i = 0; i < transaction->nr; i++) {\n>   \t\tstruct ref_update *update = transaction->updates[i];\n> +\t\tif (update->flags & REF_CREATE_ONLY && refs_ref_exists(ref_store, update->refname))\n> +\t\t\tcontinue;\n>   \t\tif (update->flags & REF_DELETING &&\n>   \t\t    !(update->flags & REF_LOG_ONLY) &&\n>   \t\t    !(update->flags & REF_IS_PRUNING)) {\n> @@ -3061,6 +3065,8 @@ static int files_transaction_finish(struct ref_store *ref_store,\n>   \tfor (i = 0; i < transaction->nr; i++) {\n>   \t\tstruct ref_update *update = transaction->updates[i];\n>   \t\tstruct ref_lock *lock = update->backend_data;\n> +\t\tif (update->flags & REF_CREATE_ONLY && refs_ref_exists(ref_store, update->refname))\n> +\t\t\tcontinue;\n>   \n>   \t\tif (update->flags & REF_DELETING &&\n>   \t\t    !(update->flags & REF_LOG_ONLY)) {\n> @@ -3085,6 +3091,8 @@ static int files_transaction_finish(struct ref_store *ref_store,\n>   \n>   \tfor (i = 0; i < transaction->nr; i++) {\n>   \t\tstruct ref_update *update = transaction->updates[i];\n> +\t\tif (update->flags & REF_CREATE_ONLY && refs_ref_exists(ref_store, update->refname))\n> +\t\t\tcontinue;\n>   \n>   \t\tif (update->flags & REF_DELETED_RMDIR) {\n>   \t\t\t/*\n> diff --git a/reset.c b/reset.c\n> index b22b1be792..8dce5f2133 100644\n> --- a/reset.c\n> +++ b/reset.c\n> @@ -75,7 +75,7 @@ static int update_refs(const struct reset_head_opts *opts,\n>   \t\t\t\t      UPDATE_REFS_MSG_ON_ERR);\n>   \t\tif (!ret)\n>   \t\t\tret = refs_update_symref(get_main_ref_store(the_repository),\n> -\t\t\t\t\t\t \"HEAD\", switch_to_branch,\n> +\t\t\t\t\t\t \"HEAD\", switch_to_branch, 0,\n>   \t\t\t\t\t\t reflog_head);\n>   \t}\n>   \tif (!ret && run_hook)\n> diff --git a/sequencer.c b/sequencer.c\n> index 8d01cd50ac..924a78dab8 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -5107,7 +5107,7 @@ static int pick_commits(struct repository *r,\n>   \t\t\t}\n>   \t\t\tmsg = reflog_message(opts, \"finish\", \"returning to %s\",\n>   \t\t\t\thead_ref.buf);\n> -\t\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", head_ref.buf, msg)) {\n> +\t\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", head_ref.buf, 0, msg)) {\n>   \t\t\t\tres = error(_(\"could not update HEAD to %s\"),\n>   \t\t\t\t\thead_ref.buf);\n>   \t\t\t\tgoto cleanup_head_ref;\n> diff --git a/setup.c b/setup.c\n> index 29f8673921..6a1fdef2c3 100644\n> --- a/setup.c\n> +++ b/setup.c\n> @@ -2169,7 +2169,7 @@ void create_reference_database(enum ref_storage_format ref_storage_format,\n>   \t\t\tdie(_(\"invalid initial branch name: '%s'\"),\n>   \t\t\t    initial_branch);\n>   \n> -\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", ref, NULL) < 0)\n> +\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", ref, 0, NULL) < 0)\n>   \t\t\texit(1);\n>   \t\tfree(ref);\n>   \t}\n> diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c\n> index 65346dee55..90af41edce 100644\n> --- a/t/helper/test-ref-store.c\n> +++ b/t/helper/test-ref-store.c\n> @@ -120,7 +120,7 @@ static int cmd_create_symref(struct ref_store *refs, const char **argv)\n>   \tconst char *target = notnull(*argv++, \"target\");\n>   \tconst char *logmsg = *argv++;\n>   \n> -\treturn refs_update_symref(refs, refname, target, logmsg);\n> +\treturn refs_update_symref(refs, refname, target, 0, logmsg);\n>   }\n>   \n>   static struct flag_definition transaction_flags[] = {\n"},{"id":"503208","messageId":"D4CB89OCTXWW.2A4NL7RQP4IS@ferdinandy.com","threadId":"62096","inReplyTo":"7fecc442-8d5e-4542-8ce8-907c35db870d@gmail.com","subject":"Re: [PATCH v3 1/2] update_symref: add REF_CREATE_ONLY option","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-09-21T22:19:43Z","receivedAt":"2024-09-21T22:20:16Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Sat Sep 21, 2024 at 15:40, Phillip Wood <phillip.wood123@gmail.com> wrote:\n> On 19/09/2024 13:13, Bence Ferdinandy wrote:\n> > Add a new REF_CREATE_ONLY flag for use by the files backend which will\n> > only update the symref if it doesn't already exist. Add the possibility\n> > to pass extra flags to refs_update_symref so that it can utilize this\n> > new flag.\n>\n> I'm not sure we need a new flag to do this as it is already supported by\n> the ref transaction api.\n\nThanks, I was not aware of ref_transaction_create. It also seems to return with\nTRANSACTION_NAME_CONFLICT so we should be able to see from the error code if\nindeed the existence was the problem or something else went wrong.\n\nBest,\nBence\n"},{"id":"503222","messageId":"xmqq1q1b8w5n.fsf@gitster.g","threadId":"62096","inReplyTo":"D4CB89OCTXWW.2A4NL7RQP4IS@ferdinandy.com","subject":"Re: [PATCH v3 1/2] update_symref: add REF_CREATE_ONLY option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-09-22T16:56:20Z","receivedAt":"2024-09-22T16:56:23Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Bence Ferdinandy\" <bence@ferdinandy.com> writes:\n\n> On Sat Sep 21, 2024 at 15:40, Phillip Wood <phillip.wood123@gmail.com> wrote:\n>> On 19/09/2024 13:13, Bence Ferdinandy wrote:\n>> > Add a new REF_CREATE_ONLY flag for use by the files backend which will\n>> > only update the symref if it doesn't already exist. Add the possibility\n>> > to pass extra flags to refs_update_symref so that it can utilize this\n>> > new flag.\n>>\n>> I'm not sure we need a new flag to do this as it is already supported by\n>> the ref transaction api.\n>\n> Thanks, I was not aware of ref_transaction_create. It also seems to return with\n> TRANSACTION_NAME_CONFLICT so we should be able to see from the error code if\n> indeed the existence was the problem or something else went wrong.\n\nThank you for working on this, and thanks, Phillip, for a useful\nsuggestion.\n\n"},{"id":"503665","messageId":"D4J51ZYZXBTV.60W3NPNVL78L@ferdinandy.com","threadId":"62096","inReplyTo":"D4CB89OCTXWW.2A4NL7RQP4IS@ferdinandy.com","subject":"Re: [PATCH v3 1/2] update_symref: add REF_CREATE_ONLY option","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-09-29T22:58:05Z","receivedAt":"2024-09-29T23:04:12Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Sun Sep 22, 2024 at 00:19, Bence Ferdinandy <bence@ferdinandy.com> wrote:\n>\n> On Sat Sep 21, 2024 at 15:40, Phillip Wood <phillip.wood123@gmail.com> wrote:\n>> On 19/09/2024 13:13, Bence Ferdinandy wrote:\n>> > Add a new REF_CREATE_ONLY flag for use by the files backend which will\n>> > only update the symref if it doesn't already exist. Add the possibility\n>> > to pass extra flags to refs_update_symref so that it can utilize this\n>> > new flag.\n>>\n>> I'm not sure we need a new flag to do this as it is already supported by\n>> the ref transaction api.\n>\n> Thanks, I was not aware of ref_transaction_create. It also seems to return with\n> TRANSACTION_NAME_CONFLICT so we should be able to see from the error code if\n> indeed the existence was the problem or something else went wrong.\n\nUnfortunately, it seems that my reading of the code did not pass practice. When\nusing ref_transaction_create ref_transaction_commit will return with -2 if the\nreference already exists, but it also returns with -2 for various other issues,\nlike if the lock file already exists. I could parse the error message to see\nwhat was the cause, but that doesn't feel like a robust solution. Since fetch\nshould _not_ error out on this, I think the REF_CREATE_ONLY flag is warranted.\nAs it stands, it would serve a different purpose than ref_transaction_create,\ni.e. a \"silent\" create-only. \n\nI'll send a v4 tomorrow hopefully.\n\nBest,\nBence\n"},{"id":"503667","messageId":"ZvpHxsPc8IABtIDo@pks.im","threadId":"62096","inReplyTo":"D4J51ZYZXBTV.60W3NPNVL78L@ferdinandy.com","subject":"Re: [PATCH v3 1/2] update_symref: add REF_CREATE_ONLY option","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-09-30T06:40:11Z","receivedAt":"2024-09-30T06:40:19Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, Sep 30, 2024 at 12:58:05AM +0200, Bence Ferdinandy wrote:\n> \n> On Sun Sep 22, 2024 at 00:19, Bence Ferdinandy <bence@ferdinandy.com> wrote:\n> >\n> > On Sat Sep 21, 2024 at 15:40, Phillip Wood <phillip.wood123@gmail.com> wrote:\n> >> On 19/09/2024 13:13, Bence Ferdinandy wrote:\n> >> > Add a new REF_CREATE_ONLY flag for use by the files backend which will\n> >> > only update the symref if it doesn't already exist. Add the possibility\n> >> > to pass extra flags to refs_update_symref so that it can utilize this\n> >> > new flag.\n> >>\n> >> I'm not sure we need a new flag to do this as it is already supported by\n> >> the ref transaction api.\n> >\n> > Thanks, I was not aware of ref_transaction_create. It also seems to return with\n> > TRANSACTION_NAME_CONFLICT so we should be able to see from the error code if\n> > indeed the existence was the problem or something else went wrong.\n> \n> Unfortunately, it seems that my reading of the code did not pass practice. When\n> using ref_transaction_create ref_transaction_commit will return with -2 if the\n> reference already exists, but it also returns with -2 for various other issues,\n> like if the lock file already exists. I could parse the error message to see\n> what was the cause, but that doesn't feel like a robust solution. Since fetch\n> should _not_ error out on this, I think the REF_CREATE_ONLY flag is warranted.\n> As it stands, it would serve a different purpose than ref_transaction_create,\n> i.e. a \"silent\" create-only. \n> \n> I'll send a v4 tomorrow hopefully.\n\nI don't think that is a good reason to introduce this new flag though.\nIf we need to have a proper way to identify this specific failure case\nwe should rather update the already-existing mechanism to give us useful\nsignals, shouldn't we?\n\nThe problem with this flag is that it basically duplicates functionality\nthat already exists, and it needs to be wired up by every ref backend\nthat we have and that we're adding in the future. Your patch for example\nonly implements the functionality for the \"files\" backend, but it must\nalso be wired up for the \"reftable\" backend or otherwise it would be\nbroken.\n\nAnother issue is that it gives you more ways to create nonsensical ref\nupdates. With it you could for example create requests with a non-zero\nold object ID, and if it has `REF_CREATE_ONLY` set it would never be\npossible to fulfill the request. There's probably other cases where you\ncan create nonsensical ref updates already, but we shouldn't add more\nways of doing that.\n\nMind you, if we go the way I propose and improve the error reporting\nwe'd also have to adapt both backends to do so. But that would be\nplugging a gap for which we have no proper solution right now instead of\ncircumventing the current design by duplicating the functionality that\nwe already have in a way that makes us able to handle this.\n\nPatrick\n"},{"id":"503724","messageId":"D4JIG4VS5WVN.2F0PNU5514UEL@ferdinandy.com","threadId":"62096","inReplyTo":"ZvpHxsPc8IABtIDo@pks.im","subject":"Re: [PATCH v3 1/2] update_symref: add REF_CREATE_ONLY option","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-09-30T09:27:47Z","receivedAt":"2024-09-30T09:33:17Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Mon Sep 30, 2024 at 08:40, Patrick Steinhardt <ps@pks.im> wrote:\n> I don't think that is a good reason to introduce this new flag though.\n> If we need to have a proper way to identify this specific failure case\n> we should rather update the already-existing mechanism to give us useful\n> signals, shouldn't we?\n>\n> The problem with this flag is that it basically duplicates functionality\n> that already exists, and it needs to be wired up by every ref backend\n> that we have and that we're adding in the future. Your patch for example\n> only implements the functionality for the \"files\" backend, but it must\n> also be wired up for the \"reftable\" backend or otherwise it would be\n> broken.\n>\n> Another issue is that it gives you more ways to create nonsensical ref\n> updates. With it you could for example create requests with a non-zero\n> old object ID, and if it has `REF_CREATE_ONLY` set it would never be\n> possible to fulfill the request. There's probably other cases where you\n> can create nonsensical ref updates already, but we shouldn't add more\n> ways of doing that.\n>\n> Mind you, if we go the way I propose and improve the error reporting\n> we'd also have to adapt both backends to do so. But that would be\n> plugging a gap for which we have no proper solution right now instead of\n> circumventing the current design by duplicating the functionality that\n> we already have in a way that makes us able to handle this.\n>\n> Patrick\n\nYou make a convincing argument :) I'll try to see if I can add another\ntransaction error code for when you only want to create not overwrite and the\nref already exists and pass it up (for both files and reftables, you also don't\nmention it, but I think this would not concern packed after a quick glance at\nthe code).\n\n\nBest,\nBence\n\n-- \nbence.ferdinandy.com\n\n"},{"id":"503783","messageId":"20240930222025.2349008-1-bence@ferdinandy.com","threadId":"62096","inReplyTo":"D4JIG4VS5WVN.2F0PNU5514UEL@ferdinandy.com","subject":"[PATCH v4 0/5] improve handling of remote/HEAD","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-09-30T22:19:50Z","receivedAt":"2024-09-30T22:22:07Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"This version has dropped some previous patches in favour of new ones.\nThese solve the problem of atomically deciding what the head was before\nwe updated it and also not reinventing the wheel for having an \"create\nthis ref, but if it already exists, fail silently\" call.\n\nSince now both changes to remote set-head and fetch utilize some common\npreceding patches I have again included in this series my changes to\nremote set-head. Sorry for the mess tangent there.\n\nAll tests (that run on my system) pass for the patches up to the\nlast patch, which changes fetch. For the last patch I updated some of\nthe test where it was trivial (lot of remote/HEAD -> something appeared\nas expected), but there is still work to be done there (i.e. figure out\nwhich tests need updating and which are actual bugs in the patch).\n\nOne specific thing I wanted to mention here. Junio asked earlier what\nhappens on fetch, when we have a config like this:\n\n> If I am only fetching refs (or HEAD) in FETCH_HEAD for immediate\n> consumtion by doing \"git pull bence HEAD\" with something like\n>\n>     [remote \"bence\"]\n>          URL = http://github.com/bence/git\n\nIn this case setting head will error out with the generic error message\nI put at the end.\n\nBence Ferdinandy (5):\n  refs_update_symref: atomically record overwritten ref\n  set-head: better output for --auto\n  transaction: add TRANSACTION_CREATE_EXISTS error\n  refs_update_symref: add create_only option\n  fetch: set remote/HEAD if it does not exist\n\n builtin/branch.c          |  2 +-\n builtin/checkout.c        |  5 ++-\n builtin/clone.c           |  8 ++--\n builtin/fetch.c           | 83 +++++++++++++++++++++++++++++++++++++++\n builtin/notes.c           |  3 +-\n builtin/remote.c          | 43 ++++++++++++++++----\n builtin/symbolic-ref.c    |  2 +-\n builtin/worktree.c        |  2 +-\n refs.c                    | 35 ++++++++++++-----\n refs.h                    |  7 +++-\n refs/files-backend.c      | 29 ++++++++++----\n refs/refs-internal.h      |  8 ++++\n refs/reftable-backend.c   |  6 ++-\n reset.c                   |  2 +-\n sequencer.c               |  3 +-\n setup.c                   |  3 +-\n t/helper/test-ref-store.c |  2 +-\n t/t5505-remote.sh         | 13 +++++-\n t/t5514-fetch-multiple.sh |  9 +++++\n 19 files changed, 222 insertions(+), 43 deletions(-)\n\n-- \n2.47.0.rc0.5.gf1cffeb8df.dirty\n\n"},{"id":"503784","messageId":"20240930222025.2349008-2-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20240930222025.2349008-1-bence@ferdinandy.com","subject":"[PATCH v4 1/5] refs_update_symref: atomically record overwritten ref","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-09-30T22:19:51Z","receivedAt":"2024-09-30T22:22:10Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"When updating a symref it's currently not possible to know for sure what\nwas the previous value that was overwritten. Make use of\nref_transaction's atomicity and record the previous value there. Add\na new variable to refs_update_symref's signature to be able to pass this\ninformation back up to callers.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v4: new patch\n\n builtin/branch.c          | 2 +-\n builtin/checkout.c        | 4 ++--\n builtin/clone.c           | 6 +++---\n builtin/notes.c           | 2 +-\n builtin/remote.c          | 6 +++---\n builtin/symbolic-ref.c    | 2 +-\n builtin/worktree.c        | 2 +-\n refs.c                    | 8 ++++++--\n refs.h                    | 3 ++-\n refs/files-backend.c      | 1 +\n refs/refs-internal.h      | 8 ++++++++\n reset.c                   | 2 +-\n sequencer.c               | 2 +-\n setup.c                   | 2 +-\n t/helper/test-ref-store.c | 2 +-\n 15 files changed, 33 insertions(+), 19 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex fd1611ebf5..6c87690b58 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -559,7 +559,7 @@ static int replace_each_worktree_head_symref(struct worktree **worktrees,\n \t\t\tcontinue;\n \n \t\trefs = get_worktree_ref_store(worktrees[i]);\n-\t\tif (refs_update_symref(refs, \"HEAD\", newref, logmsg))\n+\t\tif (refs_update_symref(refs, \"HEAD\", newref, logmsg, NULL))\n \t\t\tret = error(_(\"HEAD of working tree %s is not updated\"),\n \t\t\t\t    worktrees[i]->path);\n \t}\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 9c30000d3a..356ee9bcde 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1015,7 +1015,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\t\tdescribe_detached_head(_(\"HEAD is now at\"), new_branch_info->commit);\n \t\t}\n \t} else if (new_branch_info->path) {\t/* Switch branches. */\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", new_branch_info->path, msg.buf) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", new_branch_info->path, msg.buf, NULL) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t\tif (!opts->quiet) {\n \t\t\tif (old_branch_info->path && !strcmp(new_branch_info->path, old_branch_info->path)) {\n@@ -1479,7 +1479,7 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts)\n \t\tdie(_(\"You are on a branch yet to be born\"));\n \tstrbuf_addf(&branch_ref, \"refs/heads/%s\", opts->new_branch);\n \tstatus = refs_update_symref(get_main_ref_store(the_repository),\n-\t\t\t\t    \"HEAD\", branch_ref.buf, \"checkout -b\");\n+\t\t\t\t    \"HEAD\", branch_ref.buf, \"checkout -b\", NULL);\n \tstrbuf_release(&branch_ref);\n \tif (!opts->quiet)\n \t\tfprintf(stderr, _(\"Switched to a new branch '%s'\\n\"),\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex e77339c847..ead2af20ea 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -661,7 +661,7 @@ static void update_remote_refs(const struct ref *refs,\n \t\tstrbuf_addstr(&head_ref, \"HEAD\");\n \t\tif (refs_update_symref(get_main_ref_store(the_repository), head_ref.buf,\n \t\t\t\t       remote_head_points_at->peer_ref->name,\n-\t\t\t\t       msg) < 0)\n+\t\t\t\t       msg, NULL) < 0)\n \t\t\tdie(_(\"unable to update %s\"), head_ref.buf);\n \t\tstrbuf_release(&head_ref);\n \t}\n@@ -673,7 +673,7 @@ static void update_head(const struct ref *our, const struct ref *remote,\n \tconst char *head;\n \tif (our && skip_prefix(our->name, \"refs/heads/\", &head)) {\n \t\t/* Local default branch link */\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", our->name, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", our->name, NULL, NULL) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t\tif (!option_bare) {\n \t\t\trefs_update_ref(get_main_ref_store(the_repository),\n@@ -702,7 +702,7 @@ static void update_head(const struct ref *our, const struct ref *remote,\n \t\t * Unborn head from remote; same as \"our\" case above except\n \t\t * that we have no ref to update.\n \t\t */\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", unborn, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", unborn, NULL, NULL) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t\tif (!option_bare)\n \t\t\tinstall_branch_config(0, head, remote_name, unborn);\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex 8c26e45526..ba646f06ff 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -980,7 +980,7 @@ static int merge(int argc, const char **argv, const char *prefix)\n \t\t\tdie(_(\"a notes merge into %s is already in-progress at %s\"),\n \t\t\t    notes_ref, wt->path);\n \t\tfree_worktrees(worktrees);\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"NOTES_MERGE_REF\", notes_ref, NULL))\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"NOTES_MERGE_REF\", notes_ref, NULL, NULL))\n \t\t\tdie(_(\"failed to store link to current notes ref (%s)\"),\n \t\t\t    notes_ref);\n \t\tfprintf(stderr, _(\"Automatic notes merge failed. Fix conflicts in %s \"\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 76670ddd8b..d8ff440027 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -244,7 +244,7 @@ static int add(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_reset(&buf2);\n \t\tstrbuf_addf(&buf2, \"refs/remotes/%s/%s\", name, master);\n \n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote add\"))\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote add\", NULL))\n \t\t\tresult = error(_(\"Could not setup master '%s'\"), master);\n \t}\n \n@@ -864,7 +864,7 @@ static int mv(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_reset(&buf3);\n \t\tstrbuf_addf(&buf3, \"remote: renamed %s to %s\",\n \t\t\t\titem->string, buf.buf);\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, buf3.buf))\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, buf3.buf, NULL))\n \t\t\tdie(_(\"creating '%s' failed\"), buf.buf);\n \t\tdisplay_progress(progress, ++refs_renamed_nr);\n \t}\n@@ -1444,7 +1444,7 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\t/* make sure it's valid */\n \t\tif (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))\n \t\t\tresult |= error(_(\"Not a valid ref: %s\"), buf2.buf);\n-\t\telse if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote set-head\"))\n+\t\telse if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote set-head\", NULL))\n \t\t\tresult |= error(_(\"Could not setup %s\"), buf.buf);\n \t\telse if (opt_a)\n \t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\ndiff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c\nindex 299d23d76a..7728fbc3c1 100644\n--- a/builtin/symbolic-ref.c\n+++ b/builtin/symbolic-ref.c\n@@ -88,7 +88,7 @@ int cmd_symbolic_ref(int argc,\n \t\tif (check_refname_format(argv[1], REFNAME_ALLOW_ONELEVEL) < 0)\n \t\t\tdie(\"Refusing to set '%s' to invalid ref '%s'\", argv[0], argv[1]);\n \t\tret = !!refs_update_symref(get_main_ref_store(the_repository),\n-\t\t\t\t\t   argv[0], argv[1], msg);\n+\t\t\t\t\t   argv[0], argv[1], msg, NULL);\n \t\tbreak;\n \tdefault:\n \t\tusage_with_options(git_symbolic_ref_usage, options);\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex fc31d072a6..a7ab4193c1 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -517,7 +517,7 @@ static int add_worktree(const char *path, const char *refname,\n \t\tret = refs_update_ref(wt_refs, NULL, \"HEAD\", &commit->object.oid,\n \t\t\t\t      NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n \telse\n-\t\tret = refs_update_symref(wt_refs, \"HEAD\", symref.buf, NULL);\n+\t\tret = refs_update_symref(wt_refs, \"HEAD\", symref.buf, NULL, NULL);\n \tif (ret)\n \t\tgoto done;\n \ndiff --git a/refs.c b/refs.c\nindex 5f729ed412..301db0dcdc 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2114,7 +2114,8 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct\n }\n \n int refs_update_symref(struct ref_store *refs, const char *ref,\n-\t\t       const char *target, const char *logmsg)\n+\t\t       const char *target, const char *logmsg,\n+\t\t       struct strbuf *before_target)\n {\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n@@ -2130,6 +2131,10 @@ int refs_update_symref(struct ref_store *refs, const char *ref,\n \t}\n \n \tstrbuf_release(&err);\n+\n+\tif (before_target && transaction->updates[0]->before_target)\n+\t\tstrbuf_addstr(before_target, transaction->updates[0]->before_target);\n+\n \tif (transaction)\n \t\tref_transaction_free(transaction);\n \n@@ -2948,4 +2953,3 @@ int ref_update_expects_existing_old_ref(struct ref_update *update)\n \treturn (update->flags & REF_HAVE_OLD) &&\n \t\t(!is_null_oid(&update->old_oid) || update->old_target);\n }\n-\ndiff --git a/refs.h b/refs.h\nindex 108dfc93b3..f38616db84 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -571,7 +571,8 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,\n \t\t    const char *newref, const char *logmsg);\n \n int refs_update_symref(struct ref_store *refs, const char *refname,\n-\t\t       const char *target, const char *logmsg);\n+\t\t       const char *target, const char *logmsg,\n+\t\t       struct strbuf *before_target);\n \n enum action_on_err {\n \tUPDATE_REFS_MSG_ON_ERR,\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 0824c0b8a9..8415f2d020 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2577,6 +2577,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t}\n \n \tupdate->backend_data = lock;\n+\tupdate->before_target = xstrdup_or_null(referent.buf);\n \n \tif (update->type & REF_ISSYMREF) {\n \t\tif (update->flags & REF_NO_DEREF) {\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 2313c830d8..7df3e6271e 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -104,6 +104,14 @@ struct ref_update {\n \t */\n \tconst char *old_target;\n \n+\t/*\n+\t * The previous target before applying new_target will be\n+\t * written here, to be used by callers when they do not want to\n+\t * check old_target during the transaction, but do want to know\n+\t * what it was.\n+\t */\n+\tconst char *before_target;\n+\n \t/*\n \t * One or more of REF_NO_DEREF, REF_FORCE_CREATE_REFLOG,\n \t * REF_HAVE_NEW, REF_HAVE_OLD, or backend-specific flags.\ndiff --git a/reset.c b/reset.c\nindex b22b1be792..cc36a9ed56 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -76,7 +76,7 @@ static int update_refs(const struct reset_head_opts *opts,\n \t\tif (!ret)\n \t\t\tret = refs_update_symref(get_main_ref_store(the_repository),\n \t\t\t\t\t\t \"HEAD\", switch_to_branch,\n-\t\t\t\t\t\t reflog_head);\n+\t\t\t\t\t\t reflog_head, NULL);\n \t}\n \tif (!ret && run_hook)\n \t\trun_hooks_l(the_repository, \"post-checkout\",\ndiff --git a/sequencer.c b/sequencer.c\nindex 8d01cd50ac..23b162924c 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -5107,7 +5107,7 @@ static int pick_commits(struct repository *r,\n \t\t\t}\n \t\t\tmsg = reflog_message(opts, \"finish\", \"returning to %s\",\n \t\t\t\thead_ref.buf);\n-\t\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", head_ref.buf, msg)) {\n+\t\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", head_ref.buf, msg, NULL)) {\n \t\t\t\tres = error(_(\"could not update HEAD to %s\"),\n \t\t\t\t\thead_ref.buf);\n \t\t\t\tgoto cleanup_head_ref;\ndiff --git a/setup.c b/setup.c\nindex 94e79b2e48..d95f051465 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -2275,7 +2275,7 @@ void create_reference_database(enum ref_storage_format ref_storage_format,\n \t\t\tdie(_(\"invalid initial branch name: '%s'\"),\n \t\t\t    initial_branch);\n \n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", ref, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", ref, NULL, NULL) < 0)\n \t\t\texit(1);\n \t\tfree(ref);\n \t}\ndiff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c\nindex 65346dee55..a911302bea 100644\n--- a/t/helper/test-ref-store.c\n+++ b/t/helper/test-ref-store.c\n@@ -120,7 +120,7 @@ static int cmd_create_symref(struct ref_store *refs, const char **argv)\n \tconst char *target = notnull(*argv++, \"target\");\n \tconst char *logmsg = *argv++;\n \n-\treturn refs_update_symref(refs, refname, target, logmsg);\n+\treturn refs_update_symref(refs, refname, target, logmsg, NULL);\n }\n \n static struct flag_definition transaction_flags[] = {\n-- \n2.47.0.rc0.5.gf1cffeb8df.dirty\n\n"},{"id":"503786","messageId":"20240930222025.2349008-3-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20240930222025.2349008-1-bence@ferdinandy.com","subject":"[PATCH v4 2/5] set-head: better output for --auto","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-09-30T22:19:52Z","receivedAt":"2024-09-30T22:23:17Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Currently, set-head --auto will print a message saying \"remote/HEAD set\nto branch\", which implies something was changed.\n\nChange the output of --auto, so the output actually reflects what was\ndone: a) set a previously unset HEAD, b) change HEAD because remote\nchanged or c) no updates.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v1-v2:\n    \n        was RFC in https://lore.kernel.org/git/20240910203835.2288291-1-bence@ferdinandy.com/\n    \n    v3:\n    \n        This patch was originally sent along when I thought set-head was\n        going to be invoked by fetch, but the discussion on the RFC\n        concluded that it should be not. This opened the possibility to make\n        it more explicit.\n    \n        Note: although I feel both things the patch does are really just\n        cosmetic, an argument could be made for breaking it into two, one\n        for the no-op part and one for the --auto print update.\n    \n        Was sent in:\n        https://lore.kernel.org/git/20240915221055.904107-1-bence@ferdinandy.com/\n    \n    v4:\n        - changes are now handled atomically via the ref update transaction\n        - outputs have changed along the lines of Junio's suggestion\n        - minor refactor to set_head for improved legibility\n\n builtin/remote.c  | 36 ++++++++++++++++++++++++++++++------\n t/t5505-remote.sh | 13 ++++++++++++-\n 2 files changed, 42 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex d8ff440027..c61a7800ff 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1399,12 +1399,34 @@ static int show(int argc, const char **argv, const char *prefix)\n \treturn result;\n }\n \n+static void report_auto(const char *remote, const char *head_name,\n+\t\t\tstruct strbuf *buf_prev) {\n+\tstruct strbuf buf_prefix = STRBUF_INIT;\n+\tconst char *prev_head;\n+\n+\tstrbuf_addf(&buf_prefix, \"refs/remotes/%s/\", remote);\n+\tskip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);\n+\n+\tif (prev_head && !strcmp(prev_head, head_name))\n+\t\tprintf(\"'%s/HEAD' is unchanged and points to '%s'\\n\",\n+\t\t\tremote, head_name);\n+\telse if (prev_head)\n+\t\tprintf(\"'%s/HEAD' has changed from '%s' and now points to '%s'\\n\",\n+\t\t\tremote, prev_head, head_name);\n+\telse\n+\t\tprintf(\"'%s/HEAD' is now created and points to '%s'\\n\",\n+\t\t\tremote, head_name);\n+}\n+\n static int set_head(int argc, const char **argv, const char *prefix)\n {\n \tint i, opt_a = 0, opt_d = 0, result = 0;\n-\tstruct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;\n+\tstruct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT,\n+\t\tbuf_prev = STRBUF_INIT;\n \tchar *head_name = NULL;\n \n+\tstruct ref_store *refs = get_main_ref_store(the_repository);\n+\n \tstruct option options[] = {\n \t\tOPT_BOOL('a', \"auto\", &opt_a,\n \t\t\t N_(\"set refs/remotes/<name>/HEAD according to remote\")),\n@@ -1434,7 +1456,7 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\t\thead_name = xstrdup(states.heads.items[0].string);\n \t\tfree_remote_ref_states(&states);\n \t} else if (opt_d && !opt_a && argc == 1) {\n-\t\tif (refs_delete_ref(get_main_ref_store(the_repository), NULL, buf.buf, NULL, REF_NO_DEREF))\n+\t\tif (refs_delete_ref(refs, NULL, buf.buf, NULL, REF_NO_DEREF))\n \t\t\tresult |= error(_(\"Could not delete %s\"), buf.buf);\n \t} else\n \t\tusage_with_options(builtin_remote_sethead_usage, options);\n@@ -1442,17 +1464,19 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \tif (head_name) {\n \t\tstrbuf_addf(&buf2, \"refs/remotes/%s/%s\", argv[0], head_name);\n \t\t/* make sure it's valid */\n-\t\tif (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))\n+\t\tif (!refs_ref_exists(refs, buf2.buf))\n \t\t\tresult |= error(_(\"Not a valid ref: %s\"), buf2.buf);\n-\t\telse if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote set-head\", NULL))\n+\t\telse if (refs_update_symref(refs, buf.buf, buf2.buf, \"remote set-head\", &buf_prev))\n \t\t\tresult |= error(_(\"Could not setup %s\"), buf.buf);\n-\t\telse if (opt_a)\n-\t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\n+\t\telse if (opt_a) {\n+\t\t\treport_auto(argv[0], head_name, &buf_prev);\n+\t\t}\n \t\tfree(head_name);\n \t}\n \n \tstrbuf_release(&buf);\n \tstrbuf_release(&buf2);\n+\tstrbuf_release(&buf_prev);\n \treturn result;\n }\n \ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex 532035933f..711d6e5598 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -434,7 +434,7 @@ test_expect_success 'set-head --auto has no problem w/multiple HEADs' '\n \t\tcd test &&\n \t\tgit fetch two \"refs/heads/*:refs/remotes/two/*\" &&\n \t\tgit remote set-head --auto two >output 2>&1 &&\n-\t\techo \"two/HEAD set to main\" >expect &&\n+\t\techo \"'\\''two/HEAD'\\'' is now created and points to '\\''main'\\''\" >expect &&\n \t\ttest_cmp expect output\n \t)\n '\n@@ -453,6 +453,17 @@ test_expect_success 'set-head explicit' '\n \t)\n '\n \n+\n+test_expect_success 'set-head --auto reports change' '\n+\t(\n+\t\tcd test &&\n+\t\tgit remote set-head origin side2 &&\n+\t\tgit remote set-head --auto origin >output 2>&1 &&\n+\t\techo \"'\\''origin/HEAD'\\'' has changed from '\\''side2'\\'' and now points to '\\''main'\\''\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n cat >test/expect <<EOF\n Pruning origin\n URL: $(pwd)/one\n-- \n2.47.0.rc0.5.gf1cffeb8df.dirty\n\n"},{"id":"503787","messageId":"20240930222025.2349008-6-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20240930222025.2349008-1-bence@ferdinandy.com","subject":"[PATCH v4 5/5] fetch: set remote/HEAD if it does not exist","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-09-30T22:19:55Z","receivedAt":"2024-09-30T22:24:05Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"If the user has remote/HEAD set already and it looks like it has changed\non the server, then print a message, otherwise set it if we can.\nSilently pass if the user already has the same remote/HEAD set as\nreported by the server.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v3: - does not rely on remote set-head anymore so it only authenticates\n        once\n        - uses the new REF_CREATE_ONLY to atomically check if the ref exists\n          and only write it if it doesn't\n        - in all other cases the maximum it does is print a warning\n    \n    v4: - instead of the discarded REF_CREATE_ONLY, it uses the existing,\n          but updated transaction api to request a silent create only\n        - it now uses the atomic before_target to determine reporting\n        - refactored for legibility\n\n builtin/fetch.c           | 83 +++++++++++++++++++++++++++++++++++++++\n t/t5514-fetch-multiple.sh |  9 +++++\n 2 files changed, 92 insertions(+)\n\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex c900f57721..f10a9d5371 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1577,6 +1577,82 @@ static int backfill_tags(struct display_state *display_state,\n \treturn retcode;\n }\n \n+static void report_set_head(const char *remote, const char *head_name,\n+\t\t\tstruct strbuf *buf_prev) {\n+\tstruct strbuf buf_prefix = STRBUF_INIT;\n+\tconst char *prev_head;\n+\n+\tstrbuf_addf(&buf_prefix, \"refs/remotes/%s/\", remote);\n+\tskip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);\n+\n+\tif (prev_head && !strcmp(prev_head, head_name))\n+\t\t;\n+\telse if (prev_head) {\n+\t\tprintf(\"'HEAD' at '%s' has changed from '%s' to '%s'\\n\",\n+\t\t\tremote, prev_head, head_name);\n+\t\tprintf(\"Run 'git remote set-head %s %s' to follow the change.\\n\",\n+\t\t\tremote, head_name);\n+\t}\n+}\n+\n+static const char *strip_refshead(const char *name){\n+\tskip_prefix(name, \"refs/heads/\", &name);\n+\treturn name;\n+}\n+\n+static int set_head(const struct ref *remote_refs)\n+{\n+\tint result = 0;\n+\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,\n+\t\tb_local_head = STRBUF_INIT;\n+\tconst char *remote = gtransport->remote->name;\n+\tchar *head_name = NULL;\n+\tstruct ref *ref, *matches;\n+\tstruct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;\n+\tstruct refspec_item refspec = {\n+\t\t.force = 0,\n+\t\t.pattern = 1,\n+\t\t.src = (char *) \"refs/heads/*\",\n+\t\t.dst = (char *) \"refs/heads/*\",\n+\t};\n+\tstruct string_list heads = STRING_LIST_INIT_DUP;\n+\tstruct ref_store *refs = get_main_ref_store(the_repository);\n+\n+\tget_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);\n+\tmatches = guess_remote_head(find_ref_by_name(remote_refs, \"HEAD\"),\n+\t\t\t\t    fetch_map, 1);\n+\tfor (ref = matches; ref; ref = ref->next) {\n+\t\tstring_list_append(&heads, strip_refshead(ref->name));\n+\t}\n+\n+\n+\tif (!heads.nr)\n+\t\tresult = 1;\n+\telse if (heads.nr > 1) {\n+\t\tresult = 1;\n+\t} else\n+\t\thead_name = xstrdup(heads.items[0].string);\n+\tif (head_name) {\n+\t\tstrbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", remote);\n+\t\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", remote, head_name);\n+\t\t/* make sure it's valid */\n+\t\tif (!refs_ref_exists(refs, b_remote_head.buf))\n+\t\t\tresult |= error(_(\"Not a valid ref: %s\"), b_remote_head.buf);\n+\t\telse if (refs_update_symref(refs, b_head.buf, b_remote_head.buf,\n+\t\t\t\t\t\"remote set-head\", &b_local_head, true))\n+\t\t\tresult |= error(_(\"Could not setup %s\"), b_head.buf);\n+\t\telse {\n+\t\t\treport_set_head(remote, head_name, &b_local_head);\n+\t\t}\n+\t\tfree(head_name);\n+\t}\n+\n+\tstrbuf_release(&b_head);\n+\tstrbuf_release(&b_local_head);\n+\tstrbuf_release(&b_remote_head);\n+\treturn result;\n+}\n+\n static int do_fetch(struct transport *transport,\n \t\t    struct refspec *rs,\n \t\t    const struct fetch_config *config)\n@@ -1646,6 +1722,8 @@ static int do_fetch(struct transport *transport,\n \t\t\t\t    \"refs/tags/\");\n \t}\n \n+\tstrvec_push(&transport_ls_refs_options.ref_prefixes,\"HEAD\");\n+\n \tif (must_list_refs) {\n \t\ttrace2_region_enter(\"fetch\", \"remote_refs\", the_repository);\n \t\tremote_refs = transport_get_remote_refs(transport,\n@@ -1790,6 +1868,10 @@ static int do_fetch(struct transport *transport,\n \t\t\t\t  \"you need to specify exactly one branch with the --set-upstream option\"));\n \t\t}\n \t}\n+\tif (set_head(remote_refs))\n+\t\tprintf(\"Ran into issues with creating \\'%s/HEAD\\',\\n\"\n+\t\t\t\"use \\'git remote set-head -a %s\\' to investigate.\\n\",\n+\t\t\tgtransport->remote->name,gtransport->remote->name);\n \n cleanup:\n \tif (retcode) {\n@@ -2020,6 +2102,7 @@ static int fetch_multiple(struct string_list *list, int max_children,\n \treturn !!result;\n }\n \n+\n /*\n  * Fetching from the promisor remote should use the given filter-spec\n  * or inherit the default filter-spec from the config.\ndiff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh\nindex 579872c258..ef5874b7b3 100755\n--- a/t/t5514-fetch-multiple.sh\n+++ b/t/t5514-fetch-multiple.sh\n@@ -45,14 +45,17 @@ test_expect_success setup '\n '\n \n cat > test/expect << EOF\n+  one/HEAD -> one/main\n   one/main\n   one/side\n   origin/HEAD -> origin/main\n   origin/main\n   origin/side\n+  three/HEAD -> three/main\n   three/another\n   three/main\n   three/side\n+  two/HEAD -> two/main\n   two/another\n   two/main\n   two/side\n@@ -97,6 +100,7 @@ cat > expect << EOF\n   origin/HEAD -> origin/main\n   origin/main\n   origin/side\n+  three/HEAD -> three/main\n   three/another\n   three/main\n   three/side\n@@ -112,8 +116,10 @@ test_expect_success 'git fetch --multiple (but only one remote)' '\n '\n \n cat > expect << EOF\n+  one/HEAD -> one/main\n   one/main\n   one/side\n+  two/HEAD -> two/main\n   two/another\n   two/main\n   two/side\n@@ -221,14 +227,17 @@ test_expect_success 'git fetch --multiple --jobs=0 picks a default' '\n \n create_fetch_all_expect () {\n \tcat >expect <<-\\EOF\n+\t  one/HEAD -> one/main\n \t  one/main\n \t  one/side\n \t  origin/HEAD -> origin/main\n \t  origin/main\n \t  origin/side\n+\t  three/HEAD -> three/main\n \t  three/another\n \t  three/main\n \t  three/side\n+\t  two/HEAD -> two/main\n \t  two/another\n \t  two/main\n \t  two/side\n-- \n2.47.0.rc0.5.gf1cffeb8df.dirty\n\n"},{"id":"503789","messageId":"20240930222025.2349008-4-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20240930222025.2349008-1-bence@ferdinandy.com","subject":"[PATCH v4 3/5] transaction: add TRANSACTION_CREATE_EXISTS error","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-09-30T22:19:53Z","receivedAt":"2024-09-30T22:28:23Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Currently there is only one special error for transaction, for when\nthere is a naming conflict, all other errors are dumped under a generic\nerror. Add a new special error case for when the caller requests the\nreference to be updated only when it does not yet exist and the\nreference actually does exist.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v4: new patch\n\n refs.h                  |  4 +++-\n refs/files-backend.c    | 28 ++++++++++++++++++++--------\n refs/reftable-backend.c |  6 ++++--\n 3 files changed, 27 insertions(+), 11 deletions(-)\n\ndiff --git a/refs.h b/refs.h\nindex f38616db84..166affbc89 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -759,8 +759,10 @@ int ref_transaction_verify(struct ref_transaction *transaction,\n \n /* Naming conflict (for example, the ref names A and A/B conflict). */\n #define TRANSACTION_NAME_CONFLICT -1\n+/* When only creation was requested, but the ref already exists. */\n+#define TRANSACTION_CREATE_EXISTS -2\n /* All other errors. */\n-#define TRANSACTION_GENERIC_ERROR -2\n+#define TRANSACTION_GENERIC_ERROR -3\n \n /*\n  * Perform the preparatory stages of committing `transaction`. Acquire\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 8415f2d020..272ad81315 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2502,14 +2502,18 @@ static int split_symref_update(struct ref_update *update,\n static int check_old_oid(struct ref_update *update, struct object_id *oid,\n \t\t\t struct strbuf *err)\n {\n+\tint ret = TRANSACTION_GENERIC_ERROR;\n+\n \tif (!(update->flags & REF_HAVE_OLD) ||\n \t\t   oideq(oid, &update->old_oid))\n \t\treturn 0;\n \n-\tif (is_null_oid(&update->old_oid))\n+\tif (is_null_oid(&update->old_oid)) {\n \t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n \t\t\t    \"reference already exists\",\n \t\t\t    ref_update_original_update_refname(update));\n+\t\tret = TRANSACTION_CREATE_EXISTS;\n+\t}\n \telse if (is_null_oid(oid))\n \t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n \t\t\t    \"reference is missing but expected %s\",\n@@ -2522,7 +2526,7 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid,\n \t\t\t    oid_to_hex(oid),\n \t\t\t    oid_to_hex(&update->old_oid));\n \n-\treturn -1;\n+\treturn ret;\n }\n \n /*\n@@ -2603,9 +2607,13 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\t\tgoto out;\n \t\t\t\t}\n-\t\t\t} else if  (check_old_oid(update, &lock->old_oid, err)) {\n-\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n-\t\t\t\tgoto out;\n+\t\t\t} else {\n+\t\t\t\tint checkret;\n+\t\t\t\tcheckret = check_old_oid(update, &lock->old_oid, err);\n+\t\t\t\tif  (checkret) {\n+\t\t\t\t\tret = checkret;\n+\t\t\t\t\tgoto out;\n+\t\t\t\t}\n \t\t\t}\n \t\t} else {\n \t\t\t/*\n@@ -2636,9 +2644,13 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\t\t    update->old_target);\n \t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\tgoto out;\n-\t\t} else if  (check_old_oid(update, &lock->old_oid, err)) {\n-\t\t\tret = TRANSACTION_GENERIC_ERROR;\n-\t\t\tgoto out;\n+\t\t} else {\n+\t\t\tint checkret;\n+\t\t\tcheckret = check_old_oid(update, &lock->old_oid, err);\n+\t\t\tif  (checkret) {\n+\t\t\t\tret = checkret;\n+\t\t\t\tgoto out;\n+\t\t\t}\n \t\t}\n \n \t\t/*\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex f5f957e6de..f66bbf5d49 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1197,10 +1197,13 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\tgoto done;\n \t\t\t}\n \t\t} else if ((u->flags & REF_HAVE_OLD) && !oideq(&current_oid, &u->old_oid)) {\n-\t\t\tif (is_null_oid(&u->old_oid))\n+\t\t\tret = TRANSACTION_NAME_CONFLICT;\n+\t\t\tif (is_null_oid(&u->old_oid)) {\n \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n \t\t\t\t\t\t   \"reference already exists\"),\n \t\t\t\t\t    ref_update_original_update_refname(u));\n+\t\t\t\tret = TRANSACTION_CREATE_EXISTS;\n+\t\t\t}\n \t\t\telse if (is_null_oid(&current_oid))\n \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n \t\t\t\t\t\t   \"reference is missing but expected %s\"),\n@@ -1212,7 +1215,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\t\t    ref_update_original_update_refname(u),\n \t\t\t\t\t    oid_to_hex(&current_oid),\n \t\t\t\t\t    oid_to_hex(&u->old_oid));\n-\t\t\tret = -1;\n \t\t\tgoto done;\n \t\t}\n \n-- \n2.47.0.rc0.5.gf1cffeb8df.dirty\n\n"},{"id":"503790","messageId":"20240930222025.2349008-5-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20240930222025.2349008-1-bence@ferdinandy.com","subject":"[PATCH v4 4/5] refs_update_symref: add create_only option","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-09-30T22:19:54Z","receivedAt":"2024-09-30T22:29:14Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Allow the caller to specify that it only wants to update the symref if\nit does not already exist. Silently ignore the error from the\ntransaction API if the symref already exists.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v4: new patch\n\n builtin/branch.c          |  2 +-\n builtin/checkout.c        |  5 +++--\n builtin/clone.c           |  8 +++++---\n builtin/notes.c           |  3 ++-\n builtin/remote.c          |  9 ++++++---\n builtin/symbolic-ref.c    |  2 +-\n builtin/worktree.c        |  2 +-\n refs.c                    | 29 +++++++++++++++++++++--------\n refs.h                    |  2 +-\n reset.c                   |  2 +-\n sequencer.c               |  3 ++-\n setup.c                   |  3 ++-\n t/helper/test-ref-store.c |  2 +-\n 13 files changed, 47 insertions(+), 25 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 6c87690b58..3c9bc39800 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -559,7 +559,7 @@ static int replace_each_worktree_head_symref(struct worktree **worktrees,\n \t\t\tcontinue;\n \n \t\trefs = get_worktree_ref_store(worktrees[i]);\n-\t\tif (refs_update_symref(refs, \"HEAD\", newref, logmsg, NULL))\n+\t\tif (refs_update_symref(refs, \"HEAD\", newref, logmsg, NULL, false))\n \t\t\tret = error(_(\"HEAD of working tree %s is not updated\"),\n \t\t\t\t    worktrees[i]->path);\n \t}\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 356ee9bcde..d9514e9b7a 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1015,7 +1015,8 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\t\tdescribe_detached_head(_(\"HEAD is now at\"), new_branch_info->commit);\n \t\t}\n \t} else if (new_branch_info->path) {\t/* Switch branches. */\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", new_branch_info->path, msg.buf, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", new_branch_info->path, \n+\t\t\t\t\tmsg.buf, NULL, false) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t\tif (!opts->quiet) {\n \t\t\tif (old_branch_info->path && !strcmp(new_branch_info->path, old_branch_info->path)) {\n@@ -1479,7 +1480,7 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts)\n \t\tdie(_(\"You are on a branch yet to be born\"));\n \tstrbuf_addf(&branch_ref, \"refs/heads/%s\", opts->new_branch);\n \tstatus = refs_update_symref(get_main_ref_store(the_repository),\n-\t\t\t\t    \"HEAD\", branch_ref.buf, \"checkout -b\", NULL);\n+\t\t\t\t    \"HEAD\", branch_ref.buf, \"checkout -b\", NULL, false);\n \tstrbuf_release(&branch_ref);\n \tif (!opts->quiet)\n \t\tfprintf(stderr, _(\"Switched to a new branch '%s'\\n\"),\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex ead2af20ea..25d0bcf3aa 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -661,7 +661,7 @@ static void update_remote_refs(const struct ref *refs,\n \t\tstrbuf_addstr(&head_ref, \"HEAD\");\n \t\tif (refs_update_symref(get_main_ref_store(the_repository), head_ref.buf,\n \t\t\t\t       remote_head_points_at->peer_ref->name,\n-\t\t\t\t       msg, NULL) < 0)\n+\t\t\t\t       msg, NULL, false) < 0)\n \t\t\tdie(_(\"unable to update %s\"), head_ref.buf);\n \t\tstrbuf_release(&head_ref);\n \t}\n@@ -673,7 +673,8 @@ static void update_head(const struct ref *our, const struct ref *remote,\n \tconst char *head;\n \tif (our && skip_prefix(our->name, \"refs/heads/\", &head)) {\n \t\t/* Local default branch link */\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", our->name, NULL, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", our->name, \n+\t\t\t\t\tNULL, NULL, false) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t\tif (!option_bare) {\n \t\t\trefs_update_ref(get_main_ref_store(the_repository),\n@@ -702,7 +703,8 @@ static void update_head(const struct ref *our, const struct ref *remote,\n \t\t * Unborn head from remote; same as \"our\" case above except\n \t\t * that we have no ref to update.\n \t\t */\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", unborn, NULL, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", unborn, \n+\t\t\t\t\tNULL, NULL, false) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t\tif (!option_bare)\n \t\t\tinstall_branch_config(0, head, remote_name, unborn);\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex ba646f06ff..fb49e491c3 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -980,7 +980,8 @@ static int merge(int argc, const char **argv, const char *prefix)\n \t\t\tdie(_(\"a notes merge into %s is already in-progress at %s\"),\n \t\t\t    notes_ref, wt->path);\n \t\tfree_worktrees(worktrees);\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"NOTES_MERGE_REF\", notes_ref, NULL, NULL))\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"NOTES_MERGE_REF\", notes_ref, \n+\t\t\t\t\tNULL, NULL, false))\n \t\t\tdie(_(\"failed to store link to current notes ref (%s)\"),\n \t\t\t    notes_ref);\n \t\tfprintf(stderr, _(\"Automatic notes merge failed. Fix conflicts in %s \"\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex c61a7800ff..a1cec6276d 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -244,7 +244,8 @@ static int add(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_reset(&buf2);\n \t\tstrbuf_addf(&buf2, \"refs/remotes/%s/%s\", name, master);\n \n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote add\", NULL))\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf,\n+\t\t\t\t\t\"remote add\", NULL, false))\n \t\t\tresult = error(_(\"Could not setup master '%s'\"), master);\n \t}\n \n@@ -864,7 +865,8 @@ static int mv(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_reset(&buf3);\n \t\tstrbuf_addf(&buf3, \"remote: renamed %s to %s\",\n \t\t\t\titem->string, buf.buf);\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, buf3.buf, NULL))\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf,\n+\t\t\t\t\tbuf3.buf, NULL, false))\n \t\t\tdie(_(\"creating '%s' failed\"), buf.buf);\n \t\tdisplay_progress(progress, ++refs_renamed_nr);\n \t}\n@@ -1466,7 +1468,8 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\t/* make sure it's valid */\n \t\tif (!refs_ref_exists(refs, buf2.buf))\n \t\t\tresult |= error(_(\"Not a valid ref: %s\"), buf2.buf);\n-\t\telse if (refs_update_symref(refs, buf.buf, buf2.buf, \"remote set-head\", &buf_prev))\n+\t\telse if (refs_update_symref(refs, buf.buf, buf2.buf,\n+\t\t\t\t\t\"remote set-head\", &buf_prev, false))\n \t\t\tresult |= error(_(\"Could not setup %s\"), buf.buf);\n \t\telse if (opt_a) {\n \t\t\treport_auto(argv[0], head_name, &buf_prev);\ndiff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c\nindex 7728fbc3c1..169f5f5340 100644\n--- a/builtin/symbolic-ref.c\n+++ b/builtin/symbolic-ref.c\n@@ -88,7 +88,7 @@ int cmd_symbolic_ref(int argc,\n \t\tif (check_refname_format(argv[1], REFNAME_ALLOW_ONELEVEL) < 0)\n \t\t\tdie(\"Refusing to set '%s' to invalid ref '%s'\", argv[0], argv[1]);\n \t\tret = !!refs_update_symref(get_main_ref_store(the_repository),\n-\t\t\t\t\t   argv[0], argv[1], msg, NULL);\n+\t\t\t\t\t   argv[0], argv[1], msg, NULL, false);\n \t\tbreak;\n \tdefault:\n \t\tusage_with_options(git_symbolic_ref_usage, options);\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex a7ab4193c1..46b515a243 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -517,7 +517,7 @@ static int add_worktree(const char *path, const char *refname,\n \t\tret = refs_update_ref(wt_refs, NULL, \"HEAD\", &commit->object.oid,\n \t\t\t\t      NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n \telse\n-\t\tret = refs_update_symref(wt_refs, \"HEAD\", symref.buf, NULL, NULL);\n+\t\tret = refs_update_symref(wt_refs, \"HEAD\", symref.buf, NULL, NULL, false);\n \tif (ret)\n \t\tgoto done;\n \ndiff --git a/refs.c b/refs.c\nindex 301db0dcdc..e012b89ee9 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2115,19 +2115,32 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct\n \n int refs_update_symref(struct ref_store *refs, const char *ref,\n \t\t       const char *target, const char *logmsg,\n-\t\t       struct strbuf *before_target)\n+\t\t       struct strbuf *before_target, bool create_only)\n {\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n-\tint ret = 0;\n+\tint ret = 0, create_ret = 0;\n \n \ttransaction = ref_store_transaction_begin(refs, &err);\n-\tif (!transaction ||\n-\t    ref_transaction_update(transaction, ref, NULL, NULL,\n-\t\t\t\t   target, NULL, REF_NO_DEREF,\n-\t\t\t\t   logmsg, &err) ||\n-\t    ref_transaction_commit(transaction, &err)) {\n-\t\tret = error(\"%s\", err.buf);\n+\tif (create_only) {\n+\t\tif (!transaction ||\n+\t\t    ref_transaction_create(transaction, ref, NULL, target,\n+\t\t\t\t\t   REF_NO_DEREF, logmsg, &err)) {\n+\t\t\tret = error(\"%s\", err.buf);\n+\t\t}\n+\t\telse {\n+\t\t    create_ret = ref_transaction_commit(transaction, &err);\n+\t\t    if (create_ret && create_ret != TRANSACTION_CREATE_EXISTS)\n+\t\t\tret = error(\"%s\", err.buf);\n+\t\t}\n+\t}\n+\telse\n+\t\tif (!transaction ||\n+\t\t    ref_transaction_update(transaction, ref, NULL, NULL,\n+\t\t\t\t\t   target, NULL, REF_NO_DEREF,\n+\t\t\t\t\t   logmsg, &err) ||\n+\t\t    ref_transaction_commit(transaction, &err)) {\n+\t\t\tret = error(\"%s\", err.buf);\n \t}\n \n \tstrbuf_release(&err);\ndiff --git a/refs.h b/refs.h\nindex 166affbc89..b64fd2318b 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -572,7 +572,7 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,\n \n int refs_update_symref(struct ref_store *refs, const char *refname,\n \t\t       const char *target, const char *logmsg,\n-\t\t       struct strbuf *before_target);\n+\t\t       struct strbuf *before_target, bool create_only);\n \n enum action_on_err {\n \tUPDATE_REFS_MSG_ON_ERR,\ndiff --git a/reset.c b/reset.c\nindex cc36a9ed56..674896fb61 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -76,7 +76,7 @@ static int update_refs(const struct reset_head_opts *opts,\n \t\tif (!ret)\n \t\t\tret = refs_update_symref(get_main_ref_store(the_repository),\n \t\t\t\t\t\t \"HEAD\", switch_to_branch,\n-\t\t\t\t\t\t reflog_head, NULL);\n+\t\t\t\t\t\t reflog_head, NULL, false);\n \t}\n \tif (!ret && run_hook)\n \t\trun_hooks_l(the_repository, \"post-checkout\",\ndiff --git a/sequencer.c b/sequencer.c\nindex 23b162924c..1a46ef56ba 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -5107,7 +5107,8 @@ static int pick_commits(struct repository *r,\n \t\t\t}\n \t\t\tmsg = reflog_message(opts, \"finish\", \"returning to %s\",\n \t\t\t\thead_ref.buf);\n-\t\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", head_ref.buf, msg, NULL)) {\n+\t\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", head_ref.buf, \n+\t\t\t\t\t\tmsg, NULL, false)) {\n \t\t\t\tres = error(_(\"could not update HEAD to %s\"),\n \t\t\t\t\thead_ref.buf);\n \t\t\t\tgoto cleanup_head_ref;\ndiff --git a/setup.c b/setup.c\nindex d95f051465..67abbfaf3c 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -2275,7 +2275,8 @@ void create_reference_database(enum ref_storage_format ref_storage_format,\n \t\t\tdie(_(\"invalid initial branch name: '%s'\"),\n \t\t\t    initial_branch);\n \n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", ref, NULL, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", ref, \n+\t\t\t\t\tNULL, NULL, false) < 0)\n \t\t\texit(1);\n \t\tfree(ref);\n \t}\ndiff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c\nindex a911302bea..b6b06bb2e9 100644\n--- a/t/helper/test-ref-store.c\n+++ b/t/helper/test-ref-store.c\n@@ -120,7 +120,7 @@ static int cmd_create_symref(struct ref_store *refs, const char **argv)\n \tconst char *target = notnull(*argv++, \"target\");\n \tconst char *logmsg = *argv++;\n \n-\treturn refs_update_symref(refs, refname, target, logmsg, NULL);\n+\treturn refs_update_symref(refs, refname, target, logmsg, NULL, false);\n }\n \n static struct flag_definition transaction_flags[] = {\n-- \n2.47.0.rc0.5.gf1cffeb8df.dirty\n\n"},{"id":"503862","messageId":"xmqq7carwser.fsf@gitster.g","threadId":"62096","inReplyTo":"20240930222025.2349008-2-bence@ferdinandy.com","subject":"Re: [PATCH v4 1/5] refs_update_symref: atomically record overwritten ref","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-10-01T19:10:36Z","receivedAt":"2024-10-01T19:10:39Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bence Ferdinandy <bence@ferdinandy.com> writes:\n\n> diff --git a/refs.c b/refs.c\n> index 5f729ed412..301db0dcdc 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -2114,7 +2114,8 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct\n>  }\n>  \n>  int refs_update_symref(struct ref_store *refs, const char *ref,\n> -\t\t       const char *target, const char *logmsg)\n> +\t\t       const char *target, const char *logmsg,\n> +\t\t       struct strbuf *before_target)\n>  {\n>  \tstruct ref_transaction *transaction;\n>  \tstruct strbuf err = STRBUF_INIT;\n> @@ -2130,6 +2131,10 @@ int refs_update_symref(struct ref_store *refs, const char *ref,\n\nLet's extend the precontext of this hunk a bit.  The function begins\nlike this:\n\n\ttransaction = ref_store_transaction_begin(refs, &err);\n\tif (!transaction ||\n\t    ref_transaction_update(transaction, ref, NULL, NULL,\n\t\t\t\t   target, NULL, REF_NO_DEREF,\n\t\t\t\t   logmsg, &err) ||\n\t    ref_transaction_commit(transaction, &err)) {\n\t\tret = error(\"%s\", err.buf);\n>  \t}\n>  \tstrbuf_release(&err);\n\nWe begin a transaction, update ref to point to target in the\ntransaction, and commit the transaction.  An error at any stage of\nthis three-step process will bypass the rest and we give an error\nmessage.\n\n> +\n> +\tif (before_target && transaction->updates[0]->before_target)\n> +\t\tstrbuf_addstr(before_target, transaction->updates[0]->before_target);\n\nWhat if ref_store_transaction_begin() failed?\n\nIf we want to say \"we append the before_target recorded in the\ntransaction to the caller-supplied strbuf only when we manage to do\nthe update, and we leave before_target intact otherwise\" We'd at\nleast need\n\n\tif (transaction && before_target &&\n            transaction->updates[0]->before_target)\n\nwouldn't it?  Like the code that frees it (below), this new call\nshould be prepared to see !transaction.\n\n>  \tif (transaction)\n>  \t\tref_transaction_free(transaction);\n\n> @@ -2948,4 +2953,3 @@ int ref_update_expects_existing_old_ref(struct ref_update *update)\n>  \treturn (update->flags & REF_HAVE_OLD) &&\n>  \t\t(!is_null_oid(&update->old_oid) || update->old_target);\n>  }\n> -\n\nGood (even though it is unrelated to the topic of this series).\n\n> diff --git a/refs.h b/refs.h\n> index 108dfc93b3..f38616db84 100644\n> --- a/refs.h\n> +++ b/refs.h\n> @@ -571,7 +571,8 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,\n>  \t\t    const char *newref, const char *logmsg);\n>  \n>  int refs_update_symref(struct ref_store *refs, const char *refname,\n> -\t\t       const char *target, const char *logmsg);\n> +\t\t       const char *target, const char *logmsg,\n> +\t\t       struct strbuf *before_target);\n>  \n>  enum action_on_err {\n>  \tUPDATE_REFS_MSG_ON_ERR,\n> diff --git a/refs/files-backend.c b/refs/files-backend.c\n> index 0824c0b8a9..8415f2d020 100644\n> --- a/refs/files-backend.c\n> +++ b/refs/files-backend.c\n> @@ -2577,6 +2577,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n>  \t}\n>  \n>  \tupdate->backend_data = lock;\n> +\tupdate->before_target = xstrdup_or_null(referent.buf);\n\nOK, so this comes from the backends, as they are the only thing that\nknows what the current value is (the caller can only indirectly infer\nif it has old_target, in which case the backend checks if the attempt\nis stale).\n\nDo we need a corresponding change for the other, reftable, backend?\n\n> diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c\n> index 65346dee55..a911302bea 100644\n> --- a/t/helper/test-ref-store.c\n> +++ b/t/helper/test-ref-store.c\n> @@ -120,7 +120,7 @@ static int cmd_create_symref(struct ref_store *refs, const char **argv)\n>  \tconst char *target = notnull(*argv++, \"target\");\n>  \tconst char *logmsg = *argv++;\n>  \n> -\treturn refs_update_symref(refs, refname, target, logmsg);\n> +\treturn refs_update_symref(refs, refname, target, logmsg, NULL);\n>  }\n>  \n>  static struct flag_definition transaction_flags[] = {\n"},{"id":"503876","messageId":"xmqqbk03sabp.fsf@gitster.g","threadId":"62096","inReplyTo":"20240930222025.2349008-3-bence@ferdinandy.com","subject":"Re: [PATCH v4 2/5] set-head: better output for --auto","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-10-01T22:54:50Z","receivedAt":"2024-10-01T22:54:53Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bence Ferdinandy <bence@ferdinandy.com> writes:\n\n> +static void report_auto(const char *remote, const char *head_name,\n> +\t\t\tstruct strbuf *buf_prev) {\n> +\tstruct strbuf buf_prefix = STRBUF_INIT;\n> +\tconst char *prev_head;\n> +\n> +\tstrbuf_addf(&buf_prefix, \"refs/remotes/%s/\", remote);\n> +\tskip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);\n> +\n> +\tif (prev_head && !strcmp(prev_head, head_name))\n> +\t\tprintf(\"'%s/HEAD' is unchanged and points to '%s'\\n\",\n> +\t\t\tremote, head_name);\n> +\telse if (prev_head)\n> +\t\tprintf(\"'%s/HEAD' has changed from '%s' and now points to '%s'\\n\",\n> +\t\t\tremote, prev_head, head_name);\n> +\telse\n> +\t\tprintf(\"'%s/HEAD' is now created and points to '%s'\\n\",\n> +\t\t\tremote, head_name);\n> +}\n\nOK.\n\n>  static int set_head(int argc, const char **argv, const char *prefix)\n>  {\n>  \tint i, opt_a = 0, opt_d = 0, result = 0;\n> -\tstruct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;\n> +\tstruct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT,\n> +\t\tbuf_prev = STRBUF_INIT;\n>  \tchar *head_name = NULL;\n>  \n> +\tstruct ref_store *refs = get_main_ref_store(the_repository);\n> +\n\nWhat does the blank line between \"head_name\" and \"refs\" declaration\nindicate?  Is \"refs\" so special that it must stand separately from\nall others?\n\nThe primary purpose of this step is to pass a strbuf to\nrefs_update_symref() so that we can learn what the original value of\nthe HEAD was, so that report_auto() can give a more intelligent\nmessage.  The introduction of \"refs\" to avoid repetitive calls to\nget_main_ref_store() is orthogonal to that.  It is a good change,\nbut it should stand on its own, as a separate \"preparatory clean-up\"\npatch.\n\n> diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\n> index 532035933f..711d6e5598 100755\n> --- a/t/t5505-remote.sh\n> +++ b/t/t5505-remote.sh\n> @@ -434,7 +434,7 @@ test_expect_success 'set-head --auto has no problem w/multiple HEADs' '\n>  \t\tcd test &&\n>  \t\tgit fetch two \"refs/heads/*:refs/remotes/two/*\" &&\n>  \t\tgit remote set-head --auto two >output 2>&1 &&\n> -\t\techo \"two/HEAD set to main\" >expect &&\n> +\t\techo \"'\\''two/HEAD'\\'' is now created and points to '\\''main'\\''\" >expect &&\n>  \t\ttest_cmp expect output\n>  \t)\n>  '\n> @@ -453,6 +453,17 @@ test_expect_success 'set-head explicit' '\n>  \t)\n>  '\n>  \n> +\n> +test_expect_success 'set-head --auto reports change' '\n> +\t(\n> +\t\tcd test &&\n> +\t\tgit remote set-head origin side2 &&\n> +\t\tgit remote set-head --auto origin >output 2>&1 &&\n> +\t\techo \"'\\''origin/HEAD'\\'' has changed from '\\''side2'\\'' and now points to '\\''main'\\''\" >expect &&\n> +\t\ttest_cmp expect output\n> +\t)\n> +'\n> +\n>  cat >test/expect <<EOF\n>  Pruning origin\n>  URL: $(pwd)/one\n"},{"id":"504042","messageId":"xmqq1q0xhu13.fsf@gitster.g","threadId":"62096","inReplyTo":"20240930222025.2349008-1-bence@ferdinandy.com","subject":"Re: [PATCH v4 0/5] improve handling of remote/HEAD","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-10-03T19:21:28Z","receivedAt":"2024-10-03T19:21:31Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bence Ferdinandy <bence@ferdinandy.com> writes:\n\n> Bence Ferdinandy (5):\n>   refs_update_symref: atomically record overwritten ref\n>   set-head: better output for --auto\n>   transaction: add TRANSACTION_CREATE_EXISTS error\n>   refs_update_symref: add create_only option\n>   fetch: set remote/HEAD if it does not exist\n>\n>  builtin/branch.c          |  2 +-\n>  builtin/checkout.c        |  5 ++-\n>  builtin/clone.c           |  8 ++--\n>  builtin/fetch.c           | 83 +++++++++++++++++++++++++++++++++++++++\n>  builtin/notes.c           |  3 +-\n>  builtin/remote.c          | 43 ++++++++++++++++----\n>  builtin/symbolic-ref.c    |  2 +-\n>  builtin/worktree.c        |  2 +-\n>  refs.c                    | 35 ++++++++++++-----\n>  refs.h                    |  7 +++-\n>  refs/files-backend.c      | 29 ++++++++++----\n>  refs/refs-internal.h      |  8 ++++\n>  refs/reftable-backend.c   |  6 ++-\n>  reset.c                   |  2 +-\n>  sequencer.c               |  3 +-\n>  setup.c                   |  3 +-\n>  t/helper/test-ref-store.c |  2 +-\n>  t/t5505-remote.sh         | 13 +++++-\n>  t/t5514-fetch-multiple.sh |  9 +++++\n>  19 files changed, 222 insertions(+), 43 deletions(-)\n\nThese seem to break some of the tests, either standalone or when\nmerged to 'seen'.\n\nI have the topic queued directly on top of e9356ba3 (another batch\nafter 2.47-rc0, 2024-09-30); here is how the summary report looks\nlike.\n\nThanks.\n\nTest Summary Report\n-------------------\nt0410-partial-clone.sh                           (Wstat: 256 (exited 1) Tests: 38 Failed: 1)\n  Failed test:  32\n  Non-zero exit status: 1\nt4207-log-decoration-colors.sh                   (Wstat: 256 (exited 1) Tests: 4 Failed: 1)\n  Failed test:  2\n  Non-zero exit status: 1\nt5300-pack-object.sh                             (Wstat: 256 (exited 1) Tests: 56 Failed: 1)\n  Failed test:  51\n  Non-zero exit status: 1\nt5512-ls-remote.sh                               (Wstat: 256 (exited 1) Tests: 40 Failed: 1)\n  Failed test:  32\n  Non-zero exit status: 1\nt5527-fetch-odd-refs.sh                          (Wstat: 256 (exited 1) Tests: 5 Failed: 1)\n  Failed test:  4\n  Non-zero exit status: 1\nt5514-fetch-multiple.sh                          (Wstat: 256 (exited 1) Tests: 25 Failed: 4)\n  Failed tests:  9-10, 18, 24\n  Non-zero exit status: 1\nt5521-pull-options.sh                            (Wstat: 256 (exited 1) Tests: 22 Failed: 8)\n  Failed tests:  2-9\n  Non-zero exit status: 1\nt5505-remote.sh                                  (Wstat: 256 (exited 1) Tests: 114 Failed: 7)\n  Failed tests:  4, 31, 51-52, 54-56\n  Non-zero exit status: 1\nt5574-fetch-output.sh                            (Wstat: 256 (exited 1) Tests: 14 Failed: 3)\n  Failed tests:  11-13\n  Non-zero exit status: 1\nt5703-upload-pack-ref-in-want.sh                 (Wstat: 256 (exited 1) Tests: 26 Failed: 1)\n  Failed test:  12\n  Non-zero exit status: 1\nt5616-partial-clone.sh                           (Wstat: 256 (exited 1) Tests: 46 Failed: 2)\n  Failed tests:  10, 22\n  Non-zero exit status: 1\nt5516-fetch-push.sh                              (Wstat: 256 (exited 1) Tests: 120 Failed: 1)\n  Failed test:  102\n  Non-zero exit status: 1\nt7814-grep-recurse-submodules.sh                 (Wstat: 256 (exited 1) Tests: 34 Failed: 1)\n  Failed test:  33\n  Non-zero exit status: 1\nt7900-maintenance.sh                             (Wstat: 256 (exited 1) Tests: 53 Failed: 1)\n  Failed test:  23\n  Non-zero exit status: 1\nt7406-submodule-update.sh                        (Wstat: 256 (exited 1) Tests: 68 Failed: 1)\n  Failed test:  59\n  Non-zero exit status: 1\nt9211-scalar-clone.sh                            (Wstat: 256 (exited 1) Tests: 13 Failed: 2)\n  Failed tests:  8-9\n  Non-zero exit status: 1\nt9210-scalar.sh                                  (Wstat: 256 (exited 1) Tests: 21 Failed: 1)\n  Failed test:  11\n  Non-zero exit status: 1\nt9902-completion.sh                              (Wstat: 256 (exited 1) Tests: 256 Failed: 56)\n  Failed tests:  54-55, 59, 61, 63, 71-75, 77, 86, 88-89\n                92-93, 95, 101, 113, 116-117, 119-129, 138-141\n                143-157, 162-163, 168, 170-171\n  Non-zero exit status: 1\nFiles=1029, Tests=31709, 371 wallclock secs (11.74 usr  4.74 sys + 853.87 cusr 6465.88 csys = 7336.23 CPU)\nResult: FAIL\n"},{"id":"504043","messageId":"51b356df-d0a6-44e1-98b2-c579dd9465d8@ferdinandy.com","threadId":"62096","inReplyTo":"xmqq1q0xhu13.fsf@gitster.g","subject":"Re: [PATCH v4 0/5] improve handling of remote/HEAD","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-03T19:48:31Z","receivedAt":"2024-10-03T19:49:28Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"2024. okt. 3. 21:22:13 Junio C Hamano <gitster@pobox.com>:\n\n> Bence Ferdinandy <bence@ferdinandy.com> writes:\n>\n>> Bence Ferdinandy (5):\n>>   refs_update_symref: atomically record overwritten ref\n>>   set-head: better output for --auto\n>>   transaction: add TRANSACTION_CREATE_EXISTS error\n>>   refs_update_symref: add create_only option\n>>   fetch: set remote/HEAD if it does not exist\n>>\n>> builtin/branch.c          |  2 +-\n>> builtin/checkout.c        |  5 ++-\n>> builtin/clone.c           |  8 ++--\n>> builtin/fetch.c           | 83 +++++++++++++++++++++++++++++++++++++++\n>> builtin/notes.c           |  3 +-\n>> builtin/remote.c          | 43 ++++++++++++++++----\n>> builtin/symbolic-ref.c    |  2 +-\n>> builtin/worktree.c        |  2 +-\n>> refs.c                    | 35 ++++++++++++-----\n>> refs.h                    |  7 +++-\n>> refs/files-backend.c      | 29 ++++++++++----\n>> refs/refs-internal.h      |  8 ++++\n>> refs/reftable-backend.c   |  6 ++-\n>> reset.c                   |  2 +-\n>> sequencer.c               |  3 +-\n>> setup.c                   |  3 +-\n>> t/helper/test-ref-store.c |  2 +-\n>> t/t5505-remote.sh         | 13 +++++-\n>> t/t5514-fetch-multiple.sh |  9 +++++\n>> 19 files changed, 222 insertions(+), 43 deletions(-)\n>\n> These seem to break some of the tests, either standalone or when\n> merged to 'seen'.\n>\n> I have the topic queued directly on top of e9356ba3 (another batch\n> after 2.47-rc0, 2024-09-30); here is how the summary report looks\n> like.\n>\n> Thanks.\n\nSorry, I should have probably left in the RFC prefix to be more clear\nabout this... As I noted in the cover letter, the first four patches pass \nthe\ntests, the last one doesn't (yet), as I'm still not quite sure if even \nthe\napproach is fine. If those look good I'll send a v5 and also fix the \ntests\nas well. Although based on your review of the first 2 patches there's \nalready\nsome changes needed there, so I can just do a v5 with that and tests \nfixed\nand see if that version looks good or not.\n\nI'm currently on a holiday, so my ETA on this is early next week.\n\nThanks,\nBence\n\n\n>\n> Test Summary Report\n> -------------------\n> t0410-partial-clone.sh                           (Wstat: 256 (exited 1) \n> Tests: 38 Failed: 1)\n>   Failed test:  32\n>   Non-zero exit status: 1\n> t4207-log-decoration-colors.sh                   (Wstat: 256 (exited 1) \n> Tests: 4 Failed: 1)\n>   Failed test:  2\n>   Non-zero exit status: 1\n> t5300-pack-object.sh                             (Wstat: 256 (exited 1) \n> Tests: 56 Failed: 1)\n>   Failed test:  51\n>   Non-zero exit status: 1\n> t5512-ls-remote.sh                               (Wstat: 256 (exited 1) \n> Tests: 40 Failed: 1)\n>   Failed test:  32\n>   Non-zero exit status: 1\n> t5527-fetch-odd-refs.sh                          (Wstat: 256 (exited 1) \n> Tests: 5 Failed: 1)\n>   Failed test:  4\n>   Non-zero exit status: 1\n> t5514-fetch-multiple.sh                          (Wstat: 256 (exited 1) \n> Tests: 25 Failed: 4)\n>   Failed tests:  9-10, 18, 24\n>   Non-zero exit status: 1\n> t5521-pull-options.sh                            (Wstat: 256 (exited 1) \n> Tests: 22 Failed: 8)\n>   Failed tests:  2-9\n>   Non-zero exit status: 1\n> t5505-remote.sh                                  (Wstat: 256 (exited 1) \n> Tests: 114 Failed: 7)\n>   Failed tests:  4, 31, 51-52, 54-56\n>   Non-zero exit status: 1\n> t5574-fetch-output.sh                            (Wstat: 256 (exited 1) \n> Tests: 14 Failed: 3)\n>   Failed tests:  11-13\n>   Non-zero exit status: 1\n> t5703-upload-pack-ref-in-want.sh                 (Wstat: 256 (exited 1) \n> Tests: 26 Failed: 1)\n>   Failed test:  12\n>   Non-zero exit status: 1\n> t5616-partial-clone.sh                           (Wstat: 256 (exited 1) \n> Tests: 46 Failed: 2)\n>   Failed tests:  10, 22\n>   Non-zero exit status: 1\n> t5516-fetch-push.sh                              (Wstat: 256 (exited 1) \n> Tests: 120 Failed: 1)\n>   Failed test:  102\n>   Non-zero exit status: 1\n> t7814-grep-recurse-submodules.sh                 (Wstat: 256 (exited 1) \n> Tests: 34 Failed: 1)\n>   Failed test:  33\n>   Non-zero exit status: 1\n> t7900-maintenance.sh                             (Wstat: 256 (exited 1) \n> Tests: 53 Failed: 1)\n>   Failed test:  23\n>   Non-zero exit status: 1\n> t7406-submodule-update.sh                        (Wstat: 256 (exited 1) \n> Tests: 68 Failed: 1)\n>   Failed test:  59\n>   Non-zero exit status: 1\n> t9211-scalar-clone.sh                            (Wstat: 256 (exited 1) \n> Tests: 13 Failed: 2)\n>   Failed tests:  8-9\n>   Non-zero exit status: 1\n> t9210-scalar.sh                                  (Wstat: 256 (exited 1) \n> Tests: 21 Failed: 1)\n>   Failed test:  11\n>   Non-zero exit status: 1\n> t9902-completion.sh                              (Wstat: 256 (exited 1) \n> Tests: 256 Failed: 56)\n>   Failed tests:  54-55, 59, 61, 63, 71-75, 77, 86, 88-89\n>                 92-93, 95, 101, 113, 116-117, 119-129, 138-141\n>                 143-157, 162-163, 168, 170-171\n>   Non-zero exit status: 1\n> Files=1029, Tests=31709, 371 wallclock secs (11.74 usr  4.74 sys + \n> 853.87 cusr 6465.88 csys = 7336.23 CPU)\n> Result: FAIL\n"},{"id":"504563","messageId":"20241009135747.3563204-5-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241009135747.3563204-1-bence@ferdinandy.com","subject":"[PATCH v5 5/6] refs_update_symref: add create_only option","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-09T13:57:27Z","receivedAt":"2024-10-09T14:02:02Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Allow the caller to specify that it only wants to update the symref if\nit does not already exist. Silently ignore the error from the\ntransaction API if the symref already exists.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v4: new patch\n    v5: no change\n\n builtin/branch.c          |  2 +-\n builtin/checkout.c        |  5 +++--\n builtin/clone.c           |  8 +++++---\n builtin/notes.c           |  3 ++-\n builtin/remote.c          |  9 ++++++---\n builtin/symbolic-ref.c    |  2 +-\n builtin/worktree.c        |  2 +-\n refs.c                    | 29 +++++++++++++++++++++--------\n refs.h                    |  2 +-\n reset.c                   |  2 +-\n sequencer.c               |  3 ++-\n setup.c                   |  3 ++-\n t/helper/test-ref-store.c |  2 +-\n 13 files changed, 47 insertions(+), 25 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 6c87690b58..3c9bc39800 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -559,7 +559,7 @@ static int replace_each_worktree_head_symref(struct worktree **worktrees,\n \t\t\tcontinue;\n \n \t\trefs = get_worktree_ref_store(worktrees[i]);\n-\t\tif (refs_update_symref(refs, \"HEAD\", newref, logmsg, NULL))\n+\t\tif (refs_update_symref(refs, \"HEAD\", newref, logmsg, NULL, false))\n \t\t\tret = error(_(\"HEAD of working tree %s is not updated\"),\n \t\t\t\t    worktrees[i]->path);\n \t}\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 356ee9bcde..d9514e9b7a 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1015,7 +1015,8 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\t\tdescribe_detached_head(_(\"HEAD is now at\"), new_branch_info->commit);\n \t\t}\n \t} else if (new_branch_info->path) {\t/* Switch branches. */\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", new_branch_info->path, msg.buf, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", new_branch_info->path, \n+\t\t\t\t\tmsg.buf, NULL, false) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t\tif (!opts->quiet) {\n \t\t\tif (old_branch_info->path && !strcmp(new_branch_info->path, old_branch_info->path)) {\n@@ -1479,7 +1480,7 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts)\n \t\tdie(_(\"You are on a branch yet to be born\"));\n \tstrbuf_addf(&branch_ref, \"refs/heads/%s\", opts->new_branch);\n \tstatus = refs_update_symref(get_main_ref_store(the_repository),\n-\t\t\t\t    \"HEAD\", branch_ref.buf, \"checkout -b\", NULL);\n+\t\t\t\t    \"HEAD\", branch_ref.buf, \"checkout -b\", NULL, false);\n \tstrbuf_release(&branch_ref);\n \tif (!opts->quiet)\n \t\tfprintf(stderr, _(\"Switched to a new branch '%s'\\n\"),\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex ead2af20ea..25d0bcf3aa 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -661,7 +661,7 @@ static void update_remote_refs(const struct ref *refs,\n \t\tstrbuf_addstr(&head_ref, \"HEAD\");\n \t\tif (refs_update_symref(get_main_ref_store(the_repository), head_ref.buf,\n \t\t\t\t       remote_head_points_at->peer_ref->name,\n-\t\t\t\t       msg, NULL) < 0)\n+\t\t\t\t       msg, NULL, false) < 0)\n \t\t\tdie(_(\"unable to update %s\"), head_ref.buf);\n \t\tstrbuf_release(&head_ref);\n \t}\n@@ -673,7 +673,8 @@ static void update_head(const struct ref *our, const struct ref *remote,\n \tconst char *head;\n \tif (our && skip_prefix(our->name, \"refs/heads/\", &head)) {\n \t\t/* Local default branch link */\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", our->name, NULL, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", our->name, \n+\t\t\t\t\tNULL, NULL, false) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t\tif (!option_bare) {\n \t\t\trefs_update_ref(get_main_ref_store(the_repository),\n@@ -702,7 +703,8 @@ static void update_head(const struct ref *our, const struct ref *remote,\n \t\t * Unborn head from remote; same as \"our\" case above except\n \t\t * that we have no ref to update.\n \t\t */\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", unborn, NULL, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", unborn, \n+\t\t\t\t\tNULL, NULL, false) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t\tif (!option_bare)\n \t\t\tinstall_branch_config(0, head, remote_name, unborn);\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex ba646f06ff..fb49e491c3 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -980,7 +980,8 @@ static int merge(int argc, const char **argv, const char *prefix)\n \t\t\tdie(_(\"a notes merge into %s is already in-progress at %s\"),\n \t\t\t    notes_ref, wt->path);\n \t\tfree_worktrees(worktrees);\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"NOTES_MERGE_REF\", notes_ref, NULL, NULL))\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"NOTES_MERGE_REF\", notes_ref, \n+\t\t\t\t\tNULL, NULL, false))\n \t\t\tdie(_(\"failed to store link to current notes ref (%s)\"),\n \t\t\t    notes_ref);\n \t\tfprintf(stderr, _(\"Automatic notes merge failed. Fix conflicts in %s \"\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 2480128b88..79f22467f2 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -244,7 +244,8 @@ static int add(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_reset(&buf2);\n \t\tstrbuf_addf(&buf2, \"refs/remotes/%s/%s\", name, master);\n \n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote add\", NULL))\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf,\n+\t\t\t\t\t\"remote add\", NULL, false))\n \t\t\tresult = error(_(\"Could not setup master '%s'\"), master);\n \t}\n \n@@ -864,7 +865,8 @@ static int mv(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_reset(&buf3);\n \t\tstrbuf_addf(&buf3, \"remote: renamed %s to %s\",\n \t\t\t\titem->string, buf.buf);\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, buf3.buf, NULL))\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf,\n+\t\t\t\t\tbuf3.buf, NULL, false))\n \t\t\tdie(_(\"creating '%s' failed\"), buf.buf);\n \t\tdisplay_progress(progress, ++refs_renamed_nr);\n \t}\n@@ -1465,7 +1467,8 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\t/* make sure it's valid */\n \t\tif (!refs_ref_exists(refs, buf2.buf))\n \t\t\tresult |= error(_(\"Not a valid ref: %s\"), buf2.buf);\n-\t\telse if (refs_update_symref(refs, buf.buf, buf2.buf, \"remote set-head\", &buf_prev))\n+\t\telse if (refs_update_symref(refs, buf.buf, buf2.buf,\n+\t\t\t\t\t\"remote set-head\", &buf_prev, false))\n \t\t\tresult |= error(_(\"Could not setup %s\"), buf.buf);\n \t\telse if (opt_a) {\n \t\t\treport_auto(argv[0], head_name, &buf_prev);\ndiff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c\nindex 7728fbc3c1..169f5f5340 100644\n--- a/builtin/symbolic-ref.c\n+++ b/builtin/symbolic-ref.c\n@@ -88,7 +88,7 @@ int cmd_symbolic_ref(int argc,\n \t\tif (check_refname_format(argv[1], REFNAME_ALLOW_ONELEVEL) < 0)\n \t\t\tdie(\"Refusing to set '%s' to invalid ref '%s'\", argv[0], argv[1]);\n \t\tret = !!refs_update_symref(get_main_ref_store(the_repository),\n-\t\t\t\t\t   argv[0], argv[1], msg, NULL);\n+\t\t\t\t\t   argv[0], argv[1], msg, NULL, false);\n \t\tbreak;\n \tdefault:\n \t\tusage_with_options(git_symbolic_ref_usage, options);\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex a7ab4193c1..46b515a243 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -517,7 +517,7 @@ static int add_worktree(const char *path, const char *refname,\n \t\tret = refs_update_ref(wt_refs, NULL, \"HEAD\", &commit->object.oid,\n \t\t\t\t      NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n \telse\n-\t\tret = refs_update_symref(wt_refs, \"HEAD\", symref.buf, NULL, NULL);\n+\t\tret = refs_update_symref(wt_refs, \"HEAD\", symref.buf, NULL, NULL, false);\n \tif (ret)\n \t\tgoto done;\n \ndiff --git a/refs.c b/refs.c\nindex 91cacee6f9..3d2c07dd67 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2115,19 +2115,32 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct\n \n int refs_update_symref(struct ref_store *refs, const char *ref,\n \t\t       const char *target, const char *logmsg,\n-\t\t       struct strbuf *before_target)\n+\t\t       struct strbuf *before_target, bool create_only)\n {\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n-\tint ret = 0;\n+\tint ret = 0, create_ret = 0;\n \n \ttransaction = ref_store_transaction_begin(refs, &err);\n-\tif (!transaction ||\n-\t    ref_transaction_update(transaction, ref, NULL, NULL,\n-\t\t\t\t   target, NULL, REF_NO_DEREF,\n-\t\t\t\t   logmsg, &err) ||\n-\t    ref_transaction_commit(transaction, &err)) {\n-\t\tret = error(\"%s\", err.buf);\n+\tif (create_only) {\n+\t\tif (!transaction ||\n+\t\t    ref_transaction_create(transaction, ref, NULL, target,\n+\t\t\t\t\t   REF_NO_DEREF, logmsg, &err)) {\n+\t\t\tret = error(\"%s\", err.buf);\n+\t\t}\n+\t\telse {\n+\t\t    create_ret = ref_transaction_commit(transaction, &err);\n+\t\t    if (create_ret && create_ret != TRANSACTION_CREATE_EXISTS)\n+\t\t\tret = error(\"%s\", err.buf);\n+\t\t}\n+\t}\n+\telse\n+\t\tif (!transaction ||\n+\t\t    ref_transaction_update(transaction, ref, NULL, NULL,\n+\t\t\t\t\t   target, NULL, REF_NO_DEREF,\n+\t\t\t\t\t   logmsg, &err) ||\n+\t\t    ref_transaction_commit(transaction, &err)) {\n+\t\t\tret = error(\"%s\", err.buf);\n \t}\n \n \tstrbuf_release(&err);\ndiff --git a/refs.h b/refs.h\nindex 166affbc89..b64fd2318b 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -572,7 +572,7 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,\n \n int refs_update_symref(struct ref_store *refs, const char *refname,\n \t\t       const char *target, const char *logmsg,\n-\t\t       struct strbuf *before_target);\n+\t\t       struct strbuf *before_target, bool create_only);\n \n enum action_on_err {\n \tUPDATE_REFS_MSG_ON_ERR,\ndiff --git a/reset.c b/reset.c\nindex cc36a9ed56..674896fb61 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -76,7 +76,7 @@ static int update_refs(const struct reset_head_opts *opts,\n \t\tif (!ret)\n \t\t\tret = refs_update_symref(get_main_ref_store(the_repository),\n \t\t\t\t\t\t \"HEAD\", switch_to_branch,\n-\t\t\t\t\t\t reflog_head, NULL);\n+\t\t\t\t\t\t reflog_head, NULL, false);\n \t}\n \tif (!ret && run_hook)\n \t\trun_hooks_l(the_repository, \"post-checkout\",\ndiff --git a/sequencer.c b/sequencer.c\nindex 23b162924c..1a46ef56ba 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -5107,7 +5107,8 @@ static int pick_commits(struct repository *r,\n \t\t\t}\n \t\t\tmsg = reflog_message(opts, \"finish\", \"returning to %s\",\n \t\t\t\thead_ref.buf);\n-\t\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", head_ref.buf, msg, NULL)) {\n+\t\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", head_ref.buf, \n+\t\t\t\t\t\tmsg, NULL, false)) {\n \t\t\t\tres = error(_(\"could not update HEAD to %s\"),\n \t\t\t\t\thead_ref.buf);\n \t\t\t\tgoto cleanup_head_ref;\ndiff --git a/setup.c b/setup.c\nindex d95f051465..67abbfaf3c 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -2275,7 +2275,8 @@ void create_reference_database(enum ref_storage_format ref_storage_format,\n \t\t\tdie(_(\"invalid initial branch name: '%s'\"),\n \t\t\t    initial_branch);\n \n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", ref, NULL, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", ref, \n+\t\t\t\t\tNULL, NULL, false) < 0)\n \t\t\texit(1);\n \t\tfree(ref);\n \t}\ndiff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c\nindex a911302bea..b6b06bb2e9 100644\n--- a/t/helper/test-ref-store.c\n+++ b/t/helper/test-ref-store.c\n@@ -120,7 +120,7 @@ static int cmd_create_symref(struct ref_store *refs, const char **argv)\n \tconst char *target = notnull(*argv++, \"target\");\n \tconst char *logmsg = *argv++;\n \n-\treturn refs_update_symref(refs, refname, target, logmsg, NULL);\n+\treturn refs_update_symref(refs, refname, target, logmsg, NULL, false);\n }\n \n static struct flag_definition transaction_flags[] = {\n-- \n2.47.0.6.g9542df9767\n\n"},{"id":"504564","messageId":"20241009135747.3563204-6-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241009135747.3563204-1-bence@ferdinandy.com","subject":"[PATCH v5 6/6] fetch: set remote/HEAD if it does not exist","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-09T13:57:28Z","receivedAt":"2024-10-09T14:02:02Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"If the user has remote/HEAD set already and it looks like it has changed\non the server, then print a message, otherwise set it if we can.\nSilently pass if the user already has the same remote/HEAD set as\nreported by the server.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v3: - does not rely on remote set-head anymore so it only authenticates\n        once\n        - uses the new REF_CREATE_ONLY to atomically check if the ref exists\n          and only write it if it doesn't\n        - in all other cases the maximum it does is print a warning\n    \n    v4: - instead of the discarded REF_CREATE_ONLY, it uses the existing,\n          but updated transaction api to request a silent create only\n        - it now uses the atomic before_target to determine reporting\n        - refactored for legibility\n    \n    v5: - instead of printing a not too useful message, it now fails\n          silently, this in line with the objective to only set up\n          remote/HEAD automatically if the right thing is trivial, for\n          everything else there is remote set-head\n        - fixed all failing tests\n        - added to new tests, one for checking if remote/HEAD is set to the\n          correct one, and one to test that we do not override remote/HEAD\n          if it has changed on the server from what we have locally\n\n builtin/fetch.c                  |  82 +++++++++++\n t/t4207-log-decoration-colors.sh |   3 +-\n t/t5505-remote.sh                |  11 +-\n t/t5510-fetch.sh                 | 229 ++++++++++++++++---------------\n t/t5512-ls-remote.sh             |   2 +\n t/t5514-fetch-multiple.sh        |  17 ++-\n t/t5516-fetch-push.sh            |   3 +-\n t/t5527-fetch-odd-refs.sh        |   3 +-\n t/t7900-maintenance.sh           |   3 +-\n t/t9210-scalar.sh                |   5 +-\n t/t9211-scalar-clone.sh          |   6 +-\n t/t9902-completion.sh            |  67 ++++++++-\n 12 files changed, 308 insertions(+), 123 deletions(-)\n\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 80a64d0d26..c3d3c05950 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1578,6 +1578,82 @@ static int backfill_tags(struct display_state *display_state,\n \treturn retcode;\n }\n \n+static void report_set_head(const char *remote, const char *head_name,\n+\t\t\tstruct strbuf *buf_prev) {\n+\tstruct strbuf buf_prefix = STRBUF_INIT;\n+\tconst char *prev_head;\n+\n+\tstrbuf_addf(&buf_prefix, \"refs/remotes/%s/\", remote);\n+\tskip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);\n+\n+\tif (prev_head && !strcmp(prev_head, head_name))\n+\t\t;\n+\telse if (prev_head) {\n+\t\tprintf(\"'HEAD' at '%s' has changed from '%s' to '%s'\\n\",\n+\t\t\tremote, prev_head, head_name);\n+\t\tprintf(\"Run 'git remote set-head %s %s' to follow the change.\\n\",\n+\t\t\tremote, head_name);\n+\t}\n+}\n+\n+static const char *strip_refshead(const char *name){\n+\tskip_prefix(name, \"refs/heads/\", &name);\n+\treturn name;\n+}\n+\n+static int set_head(const struct ref *remote_refs)\n+{\n+\tint result = 0;\n+\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,\n+\t\tb_local_head = STRBUF_INIT;\n+\tconst char *remote = gtransport->remote->name;\n+\tchar *head_name = NULL;\n+\tstruct ref *ref, *matches;\n+\tstruct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;\n+\tstruct refspec_item refspec = {\n+\t\t.force = 0,\n+\t\t.pattern = 1,\n+\t\t.src = (char *) \"refs/heads/*\",\n+\t\t.dst = (char *) \"refs/heads/*\",\n+\t};\n+\tstruct string_list heads = STRING_LIST_INIT_DUP;\n+\tstruct ref_store *refs = get_main_ref_store(the_repository);\n+\n+\tget_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);\n+\tmatches = guess_remote_head(find_ref_by_name(remote_refs, \"HEAD\"),\n+\t\t\t\t    fetch_map, 1);\n+\tfor (ref = matches; ref; ref = ref->next) {\n+\t\tstring_list_append(&heads, strip_refshead(ref->name));\n+\t}\n+\n+\n+\tif (!heads.nr)\n+\t\tresult = 1;\n+\telse if (heads.nr > 1) {\n+\t\tresult = 1;\n+\t} else\n+\t\thead_name = xstrdup(heads.items[0].string);\n+\tif (head_name) {\n+\t\tstrbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", remote);\n+\t\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", remote, head_name);\n+\t\t/* make sure it's valid */\n+\t\tif (!refs_ref_exists(refs, b_remote_head.buf))\n+\t\t\tresult |= error(_(\"Not a valid ref: %s\"), b_remote_head.buf);\n+\t\telse if (refs_update_symref(refs, b_head.buf, b_remote_head.buf,\n+\t\t\t\t\t\"remote set-head\", &b_local_head, true))\n+\t\t\tresult |= error(_(\"Could not setup %s\"), b_head.buf);\n+\t\telse {\n+\t\t\treport_set_head(remote, head_name, &b_local_head);\n+\t\t}\n+\t\tfree(head_name);\n+\t}\n+\n+\tstrbuf_release(&b_head);\n+\tstrbuf_release(&b_local_head);\n+\tstrbuf_release(&b_remote_head);\n+\treturn result;\n+}\n+\n static int do_fetch(struct transport *transport,\n \t\t    struct refspec *rs,\n \t\t    const struct fetch_config *config)\n@@ -1647,6 +1723,8 @@ static int do_fetch(struct transport *transport,\n \t\t\t\t    \"refs/tags/\");\n \t}\n \n+\tstrvec_push(&transport_ls_refs_options.ref_prefixes,\"HEAD\");\n+\n \tif (must_list_refs) {\n \t\ttrace2_region_enter(\"fetch\", \"remote_refs\", the_repository);\n \t\tremote_refs = transport_get_remote_refs(transport,\n@@ -1791,6 +1869,9 @@ static int do_fetch(struct transport *transport,\n \t\t\t\t  \"you need to specify exactly one branch with the --set-upstream option\"));\n \t\t}\n \t}\n+\tif (set_head(remote_refs))\n+\t\t; // Way too many cases where this can go wrong.\n+\t\t  // Just fail silently.\n \n cleanup:\n \tif (retcode) {\n@@ -2021,6 +2102,7 @@ static int fetch_multiple(struct string_list *list, int max_children,\n \treturn !!result;\n }\n \n+\n /*\n  * Fetching from the promisor remote should use the given filter-spec\n  * or inherit the default filter-spec from the config.\ndiff --git a/t/t4207-log-decoration-colors.sh b/t/t4207-log-decoration-colors.sh\nindex 73ea9e5155..d55d22cb2f 100755\n--- a/t/t4207-log-decoration-colors.sh\n+++ b/t/t4207-log-decoration-colors.sh\n@@ -59,7 +59,8 @@ ${c_reset}${c_tag}tag: ${c_reset}${c_tag}v1.0${c_reset}${c_commit}, \\\n ${c_reset}${c_tag}tag: ${c_reset}${c_tag}B${c_reset}${c_commit})${c_reset} B\n ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\n ${c_tag}tag: ${c_reset}${c_tag}A1${c_reset}${c_commit}, \\\n-${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit})${c_reset} A1\n+${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit}, \\\n+${c_reset}${c_remoteBranch}other/HEAD${c_reset}${c_commit})${c_reset} A1\n \t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\n ${c_stash}refs/stash${c_reset}${c_commit})${c_reset} On main: Changes to A.t\n \t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex 262a4de0aa..399a501740 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -71,7 +71,7 @@ test_expect_success 'add another remote' '\n \t\tcd test &&\n \t\tgit remote add -f second ../two &&\n \t\ttokens_match \"origin second\" \"$(git remote)\" &&\n-\t\tcheck_tracking_branch second main side another &&\n+\t\tcheck_tracking_branch second main side another HEAD &&\n \t\tgit for-each-ref \"--format=%(refname)\" refs/remotes |\n \t\tsed -e \"/^refs\\/remotes\\/origin\\//d\" \\\n \t\t    -e \"/^refs\\/remotes\\/second\\//d\" >actual &&\n@@ -725,6 +725,7 @@ test_expect_success 'reject --no-no-tags' '\n cat >one/expect <<\\EOF\n   apis/main\n   apis/side\n+  drosophila/HEAD -> drosophila/main\n   drosophila/another\n   drosophila/main\n   drosophila/side\n@@ -742,6 +743,7 @@ test_expect_success 'update' '\n '\n \n cat >one/expect <<\\EOF\n+  drosophila/HEAD -> drosophila/main\n   drosophila/another\n   drosophila/main\n   drosophila/side\n@@ -754,7 +756,7 @@ EOF\n test_expect_success 'update with arguments' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\n@@ -797,7 +799,7 @@ EOF\n test_expect_success 'update default' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\n@@ -809,6 +811,7 @@ test_expect_success 'update default' '\n '\n \n cat >one/expect <<\\EOF\n+  drosophila/HEAD -> drosophila/main\n   drosophila/another\n   drosophila/main\n   drosophila/side\n@@ -831,7 +834,7 @@ test_expect_success 'update default (overridden, with funny whitespace)' '\n test_expect_success 'update (with remotes.default defined)' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex 0890b9f61c..dfc8d748ba 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -75,6 +75,30 @@ test_expect_success \"fetch test for-merge\" '\n \tcut -f -2 .git/FETCH_HEAD >actual &&\n \ttest_cmp expected actual'\n \n+test_expect_success \"fetch test remote HEAD\" '\n+\tcd \"$D\" &&\n+\tcd two &&\n+\tgit fetch &&\n+\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\tgit rev-parse --verify refs/remotes/origin/main &&\n+\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\tbranch=$(git rev-parse refs/remotes/origin/main) &&\n+\ttest \"z$head\" = \"z$branch\"'\n+\n+test_expect_success \"fetch test remote HEAD change\" '\n+\tcd \"$D\" &&\n+\tcd two &&\n+\tgit switch -c other &&\n+\tgit push -u origin other &&\n+\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\tgit rev-parse --verify refs/remotes/origin/main &&\n+\tgit rev-parse --verify refs/remotes/origin/other &&\n+\tgit remote set-head origin other &&\n+\tgit fetch &&\n+\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\tbranch=$(git rev-parse refs/remotes/origin/other) &&\n+\ttest \"z$head\" = \"z$branch\"'\n+\n test_expect_success 'fetch --prune on its own works as expected' '\n \tcd \"$D\" &&\n \tgit clone . prune &&\n@@ -478,7 +502,6 @@ test_expect_success 'unbundle 1' '\n \ttest_must_fail git fetch \"$D/bundle1\" main:main\n '\n \n-\n test_expect_success 'bundle 1 has only 3 files ' '\n \tcd \"$D\" &&\n \ttest_bundle_object_count bundle1 3\n@@ -819,7 +842,7 @@ test_expect_success 'fetch from multiple configured URLs in single remote' '\n \n # configured prune tests\n \n-set_config_tristate () {\n+set_config_tristate() {\n \t# var=$1 val=$2\n \tcase \"$2\" in\n \tunset)\n@@ -833,12 +856,12 @@ set_config_tristate () {\n \tesac\n }\n \n-test_configured_prune () {\n+test_configured_prune() {\n \ttest_configured_prune_type \"$@\" \"name\"\n \ttest_configured_prune_type \"$@\" \"link\"\n }\n \n-test_configured_prune_type () {\n+test_configured_prune_type() {\n \tfetch_prune=$1\n \tremote_origin_prune=$2\n \tfetch_prune_tags=$3\n@@ -848,8 +871,7 @@ test_configured_prune_type () {\n \tcmdline=$7\n \tmode=$8\n \n-\tif test -z \"$cmdline_setup\"\n-\tthen\n+\tif test -z \"$cmdline_setup\"; then\n \t\ttest_expect_success 'setup cmdline_setup variable for subsequent test' '\n \t\t\tremote_url=\"file://$(git -C one config remote.origin.url)\" &&\n \t\t\tremote_fetch=\"$(git -C one config remote.origin.fetch)\" &&\n@@ -857,12 +879,10 @@ test_configured_prune_type () {\n \t\t'\n \tfi\n \n-\tif test \"$mode\" = 'link'\n-\tthen\n+\tif test \"$mode\" = 'link'; then\n \t\tnew_cmdline=\"\"\n \n-\t\tif test \"$cmdline\" = \"\"\n-\t\tthen\n+\t\tif test \"$cmdline\" = \"\"; then\n \t\t\tnew_cmdline=$cmdline_setup\n \t\telse\n \t\t\tnew_cmdline=$(perl -e '\n@@ -873,10 +893,8 @@ test_configured_prune_type () {\n \t\tfi\n \n \t\tif test \"$fetch_prune_tags\" = 'true' ||\n-\t\t   test \"$remote_origin_prune_tags\" = 'true'\n-\t\tthen\n-\t\t\tif ! printf '%s' \"$cmdline\\n\" | grep -q refs/remotes/origin/\n-\t\t\tthen\n+\t\t\ttest \"$remote_origin_prune_tags\" = 'true'; then\n+\t\t\tif ! printf '%s' \"$cmdline\\n\" | grep -q refs/remotes/origin/; then\n \t\t\t\tnew_cmdline=\"$new_cmdline refs/tags/*:refs/tags/*\"\n \t\t\tfi\n \t\tfi\n@@ -946,100 +964,100 @@ test_configured_prune_type () {\n # $7 git-fetch $cmdline:\n #\n #                     $1    $2    $3    $4    $5     $6     $7\n-test_configured_prune unset unset unset unset kept   kept   \"\"\n-test_configured_prune unset unset unset unset kept   kept   \"--no-prune\"\n-test_configured_prune unset unset unset unset pruned kept   \"--prune\"\n-test_configured_prune unset unset unset unset kept   pruned \\\n+test_configured_prune unset unset unset unset kept kept \"\"\n+test_configured_prune unset unset unset unset kept kept \"--no-prune\"\n+test_configured_prune unset unset unset unset pruned kept \"--prune\"\n+test_configured_prune unset unset unset unset kept pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/*\"\n test_configured_prune unset unset unset unset pruned pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*\"\n \n-test_configured_prune false unset unset unset kept   kept   \"\"\n-test_configured_prune false unset unset unset kept   kept   \"--no-prune\"\n-test_configured_prune false unset unset unset pruned kept   \"--prune\"\n+test_configured_prune false unset unset unset kept kept \"\"\n+test_configured_prune false unset unset unset kept kept \"--no-prune\"\n+test_configured_prune false unset unset unset pruned kept \"--prune\"\n \n-test_configured_prune true  unset unset unset pruned kept   \"\"\n-test_configured_prune true  unset unset unset pruned kept   \"--prune\"\n-test_configured_prune true  unset unset unset kept   kept   \"--no-prune\"\n+test_configured_prune true unset unset unset pruned kept \"\"\n+test_configured_prune true unset unset unset pruned kept \"--prune\"\n+test_configured_prune true unset unset unset kept kept \"--no-prune\"\n \n-test_configured_prune unset false unset unset kept   kept   \"\"\n-test_configured_prune unset false unset unset kept   kept   \"--no-prune\"\n-test_configured_prune unset false unset unset pruned kept   \"--prune\"\n+test_configured_prune unset false unset unset kept kept \"\"\n+test_configured_prune unset false unset unset kept kept \"--no-prune\"\n+test_configured_prune unset false unset unset pruned kept \"--prune\"\n \n-test_configured_prune false false unset unset kept   kept   \"\"\n-test_configured_prune false false unset unset kept   kept   \"--no-prune\"\n-test_configured_prune false false unset unset pruned kept   \"--prune\"\n-test_configured_prune false false unset unset kept   pruned \\\n+test_configured_prune false false unset unset kept kept \"\"\n+test_configured_prune false false unset unset kept kept \"--no-prune\"\n+test_configured_prune false false unset unset pruned kept \"--prune\"\n+test_configured_prune false false unset unset kept pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/*\"\n test_configured_prune false false unset unset pruned pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*\"\n \n-test_configured_prune true  false unset unset kept   kept   \"\"\n-test_configured_prune true  false unset unset pruned kept   \"--prune\"\n-test_configured_prune true  false unset unset kept   kept   \"--no-prune\"\n+test_configured_prune true false unset unset kept kept \"\"\n+test_configured_prune true false unset unset pruned kept \"--prune\"\n+test_configured_prune true false unset unset kept kept \"--no-prune\"\n \n-test_configured_prune unset true  unset unset pruned kept   \"\"\n-test_configured_prune unset true  unset unset kept   kept   \"--no-prune\"\n-test_configured_prune unset true  unset unset pruned kept   \"--prune\"\n+test_configured_prune unset true unset unset pruned kept \"\"\n+test_configured_prune unset true unset unset kept kept \"--no-prune\"\n+test_configured_prune unset true unset unset pruned kept \"--prune\"\n \n-test_configured_prune false true  unset unset pruned kept   \"\"\n-test_configured_prune false true  unset unset kept   kept   \"--no-prune\"\n-test_configured_prune false true  unset unset pruned kept   \"--prune\"\n+test_configured_prune false true unset unset pruned kept \"\"\n+test_configured_prune false true unset unset kept kept \"--no-prune\"\n+test_configured_prune false true unset unset pruned kept \"--prune\"\n \n-test_configured_prune true  true  unset unset pruned kept   \"\"\n-test_configured_prune true  true  unset unset pruned kept   \"--prune\"\n-test_configured_prune true  true  unset unset kept   kept   \"--no-prune\"\n-test_configured_prune true  true  unset unset kept   pruned \\\n+test_configured_prune true true unset unset pruned kept \"\"\n+test_configured_prune true true unset unset pruned kept \"--prune\"\n+test_configured_prune true true unset unset kept kept \"--no-prune\"\n+test_configured_prune true true unset unset kept pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/*\"\n-test_configured_prune true  true  unset unset pruned pruned \\\n+test_configured_prune true true unset unset pruned pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*\"\n \n # --prune-tags on its own does nothing, needs --prune as well, same\n # for fetch.pruneTags without fetch.prune\n-test_configured_prune unset unset unset unset kept kept     \"--prune-tags\"\n-test_configured_prune unset unset true unset  kept kept     \"\"\n-test_configured_prune unset unset unset true  kept kept     \"\"\n+test_configured_prune unset unset unset unset kept kept \"--prune-tags\"\n+test_configured_prune unset unset true unset kept kept \"\"\n+test_configured_prune unset unset unset true kept kept \"\"\n \n # These will prune the tags\n test_configured_prune unset unset unset unset pruned pruned \"--prune --prune-tags\"\n-test_configured_prune true  unset true  unset pruned pruned \"\"\n-test_configured_prune unset true  unset true  pruned pruned \"\"\n+test_configured_prune true unset true unset pruned pruned \"\"\n+test_configured_prune unset true unset true pruned pruned \"\"\n \n # remote.<name>.pruneTags overrides fetch.pruneTags, just like\n # remote.<name>.prune overrides fetch.prune if set.\n-test_configured_prune true  unset true unset pruned pruned  \"\"\n-test_configured_prune false true  false true  pruned pruned \"\"\n-test_configured_prune true  false true  false kept   kept   \"\"\n+test_configured_prune true unset true unset pruned pruned \"\"\n+test_configured_prune false true false true pruned pruned \"\"\n+test_configured_prune true false true false kept kept \"\"\n \n # When --prune-tags is supplied it's ignored if an explicit refspec is\n # given, same for the configuration options.\n test_configured_prune unset unset unset unset pruned kept \\\n \t\"--prune --prune-tags origin +refs/heads/*:refs/remotes/origin/*\"\n-test_configured_prune unset unset true  unset pruned kept \\\n+test_configured_prune unset unset true unset pruned kept \\\n \t\"--prune origin +refs/heads/*:refs/remotes/origin/*\"\n-test_configured_prune unset unset unset true pruned  kept \\\n+test_configured_prune unset unset unset true pruned kept \\\n \t\"--prune origin +refs/heads/*:refs/remotes/origin/*\"\n \n # Pruning that also takes place if a file:// url replaces a named\n # remote. However, because there's no implicit\n # +refs/heads/*:refs/remotes/origin/* refspec and supplying it on the\n # command-line negates --prune-tags, the branches will not be pruned.\n-test_configured_prune_type unset unset unset unset kept   kept   \"origin --prune-tags\" \"name\"\n-test_configured_prune_type unset unset unset unset kept   kept   \"origin --prune-tags\" \"link\"\n+test_configured_prune_type unset unset unset unset kept kept \"origin --prune-tags\" \"name\"\n+test_configured_prune_type unset unset unset unset kept kept \"origin --prune-tags\" \"link\"\n test_configured_prune_type unset unset unset unset pruned pruned \"origin --prune --prune-tags\" \"name\"\n-test_configured_prune_type unset unset unset unset kept   pruned \"origin --prune --prune-tags\" \"link\"\n+test_configured_prune_type unset unset unset unset kept pruned \"origin --prune --prune-tags\" \"link\"\n test_configured_prune_type unset unset unset unset pruned pruned \"--prune --prune-tags origin\" \"name\"\n-test_configured_prune_type unset unset unset unset kept   pruned \"--prune --prune-tags origin\" \"link\"\n-test_configured_prune_type unset unset true  unset pruned pruned \"--prune origin\" \"name\"\n-test_configured_prune_type unset unset true  unset kept   pruned \"--prune origin\" \"link\"\n-test_configured_prune_type unset unset unset true  pruned pruned \"--prune origin\" \"name\"\n-test_configured_prune_type unset unset unset true  kept   pruned \"--prune origin\" \"link\"\n-test_configured_prune_type true  unset true  unset pruned pruned \"origin\" \"name\"\n-test_configured_prune_type true  unset true  unset kept   pruned \"origin\" \"link\"\n-test_configured_prune_type unset  true true  unset pruned pruned \"origin\" \"name\"\n-test_configured_prune_type unset  true true  unset kept   pruned \"origin\" \"link\"\n-test_configured_prune_type unset  true unset true  pruned pruned \"origin\" \"name\"\n-test_configured_prune_type unset  true unset true  kept   pruned \"origin\" \"link\"\n+test_configured_prune_type unset unset unset unset kept pruned \"--prune --prune-tags origin\" \"link\"\n+test_configured_prune_type unset unset true unset pruned pruned \"--prune origin\" \"name\"\n+test_configured_prune_type unset unset true unset kept pruned \"--prune origin\" \"link\"\n+test_configured_prune_type unset unset unset true pruned pruned \"--prune origin\" \"name\"\n+test_configured_prune_type unset unset unset true kept pruned \"--prune origin\" \"link\"\n+test_configured_prune_type true unset true unset pruned pruned \"origin\" \"name\"\n+test_configured_prune_type true unset true unset kept pruned \"origin\" \"link\"\n+test_configured_prune_type unset true true unset pruned pruned \"origin\" \"name\"\n+test_configured_prune_type unset true true unset kept pruned \"origin\" \"link\"\n+test_configured_prune_type unset true unset true pruned pruned \"origin\" \"name\"\n+test_configured_prune_type unset true unset true kept pruned \"origin\" \"link\"\n \n # When all remote.origin.fetch settings are deleted a --prune\n # --prune-tags still implicitly supplies refs/tags/*:refs/tags/* so\n@@ -1137,8 +1155,7 @@ test_expect_success 'fetching with auto-gc does not lock up' '\n \t)\n '\n \n-for section in fetch transfer\n-do\n+for section in fetch transfer; do\n \ttest_expect_success \"$section.hideRefs affects connectivity check\" '\n \t\tGIT_TRACE=\"$PWD\"/trace git -c $section.hideRefs=refs -c \\\n \t\t\t$section.hideRefs=\"!refs/tags/\" fetch &&\n@@ -1157,21 +1174,23 @@ test_expect_success 'prepare source branch' '\n \ttest 3 -le $(wc -l <actual)\n '\n \n-validate_store_type () {\n+validate_store_type() {\n \tgit -C dest count-objects -v >actual &&\n-\tcase \"$store_type\" in\n-\tpacked)\n-\t\tgrep \"^count: 0$\" actual ;;\n-\tloose)\n-\t\tgrep \"^packs: 0$\" actual ;;\n-\tesac || {\n+\t\tcase \"$store_type\" in\n+\t\tpacked)\n+\t\t\tgrep \"^count: 0$\" actual\n+\t\t\t;;\n+\t\tloose)\n+\t\t\tgrep \"^packs: 0$\" actual\n+\t\t\t;;\n+\t\tesac || {\n \t\techo \"store_type is $store_type\"\n \t\tcat actual\n \t\tfalse\n \t}\n }\n \n-test_unpack_limit () {\n+test_unpack_limit() {\n \tstore_type=$1\n \n \tcase \"$store_type\" in\n@@ -1192,43 +1211,39 @@ test_unpack_limit () {\n test_unpack_limit packed\n test_unpack_limit loose\n \n-setup_negotiation_tip () {\n+setup_negotiation_tip() {\n \tSERVER=\"$1\"\n \tURL=\"$2\"\n \tUSE_PROTOCOL_V2=\"$3\"\n \n \trm -rf \"$SERVER\" client trace &&\n-\tgit init -b main \"$SERVER\" &&\n-\ttest_commit -C \"$SERVER\" alpha_1 &&\n-\ttest_commit -C \"$SERVER\" alpha_2 &&\n-\tgit -C \"$SERVER\" checkout --orphan beta &&\n-\ttest_commit -C \"$SERVER\" beta_1 &&\n-\ttest_commit -C \"$SERVER\" beta_2 &&\n-\n-\tgit clone \"$URL\" client &&\n-\n-\tif test \"$USE_PROTOCOL_V2\" -eq 1\n-\tthen\n-\t\tgit -C \"$SERVER\" config protocol.version 2 &&\n-\t\tgit -C client config protocol.version 2\n-\tfi &&\n-\n-\ttest_commit -C \"$SERVER\" beta_s &&\n-\tgit -C \"$SERVER\" checkout main &&\n-\ttest_commit -C \"$SERVER\" alpha_s &&\n-\tgit -C \"$SERVER\" tag -d alpha_1 alpha_2 beta_1 beta_2\n+\t\tgit init -b main \"$SERVER\" &&\n+\t\ttest_commit -C \"$SERVER\" alpha_1 &&\n+\t\ttest_commit -C \"$SERVER\" alpha_2 &&\n+\t\tgit -C \"$SERVER\" checkout --orphan beta &&\n+\t\ttest_commit -C \"$SERVER\" beta_1 &&\n+\t\ttest_commit -C \"$SERVER\" beta_2 &&\n+\t\tgit clone \"$URL\" client &&\n+\t\tif test \"$USE_PROTOCOL_V2\" -eq 1; then\n+\t\t\tgit -C \"$SERVER\" config protocol.version 2 &&\n+\t\t\t\tgit -C client config protocol.version 2\n+\t\tfi &&\n+\t\ttest_commit -C \"$SERVER\" beta_s &&\n+\t\tgit -C \"$SERVER\" checkout main &&\n+\t\ttest_commit -C \"$SERVER\" alpha_s &&\n+\t\tgit -C \"$SERVER\" tag -d alpha_1 alpha_2 beta_1 beta_2\n }\n \n-check_negotiation_tip () {\n+check_negotiation_tip() {\n \t# Ensure that {alpha,beta}_1 are sent as \"have\", but not {alpha_beta}_2\n \tALPHA_1=$(git -C client rev-parse alpha_1) &&\n-\tgrep \"fetch> have $ALPHA_1\" trace &&\n-\tBETA_1=$(git -C client rev-parse beta_1) &&\n-\tgrep \"fetch> have $BETA_1\" trace &&\n-\tALPHA_2=$(git -C client rev-parse alpha_2) &&\n-\t! grep \"fetch> have $ALPHA_2\" trace &&\n-\tBETA_2=$(git -C client rev-parse beta_2) &&\n-\t! grep \"fetch> have $BETA_2\" trace\n+\t\tgrep \"fetch> have $ALPHA_1\" trace &&\n+\t\tBETA_1=$(git -C client rev-parse beta_1) &&\n+\t\tgrep \"fetch> have $BETA_1\" trace &&\n+\t\tALPHA_2=$(git -C client rev-parse alpha_2) &&\n+\t\t! grep \"fetch> have $ALPHA_2\" trace &&\n+\t\tBETA_2=$(git -C client rev-parse beta_2) &&\n+\t\t! grep \"fetch> have $BETA_2\" trace\n }\n \n test_expect_success '--negotiation-tip limits \"have\" lines sent' '\ndiff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh\nindex 64b3491e4e..1b3865e154 100755\n--- a/t/t5512-ls-remote.sh\n+++ b/t/t5512-ls-remote.sh\n@@ -293,6 +293,8 @@ test_expect_success 'ls-remote with filtered symref (refname)' '\n \tcat >expect <<-EOF &&\n \tref: refs/heads/main\tHEAD\n \t$rev\tHEAD\n+\tref: refs/remotes/origin/main\trefs/remotes/origin/HEAD\n+\t$rev\trefs/remotes/origin/HEAD\n \tEOF\n \tgit ls-remote --symref . HEAD >actual &&\n \ttest_cmp expect actual\ndiff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh\nindex 579872c258..e3482b27b2 100755\n--- a/t/t5514-fetch-multiple.sh\n+++ b/t/t5514-fetch-multiple.sh\n@@ -45,14 +45,17 @@ test_expect_success setup '\n '\n \n cat > test/expect << EOF\n+  one/HEAD -> one/main\n   one/main\n   one/side\n   origin/HEAD -> origin/main\n   origin/main\n   origin/side\n+  three/HEAD -> three/main\n   three/another\n   three/main\n   three/side\n+  two/HEAD -> two/main\n   two/another\n   two/main\n   two/side\n@@ -97,6 +100,7 @@ cat > expect << EOF\n   origin/HEAD -> origin/main\n   origin/main\n   origin/side\n+  three/HEAD -> three/main\n   three/another\n   three/main\n   three/side\n@@ -112,8 +116,10 @@ test_expect_success 'git fetch --multiple (but only one remote)' '\n '\n \n cat > expect << EOF\n+  one/HEAD -> one/main\n   one/main\n   one/side\n+  two/HEAD -> two/main\n   two/another\n   two/main\n   two/side\n@@ -141,7 +147,7 @@ test_expect_success 'git fetch --multiple (bad remote names)' '\n \n test_expect_success 'git fetch --all (skipFetchAll)' '\n \t(cd test4 &&\n-\t for b in $(git branch -r)\n+\t for b in $(git branch -r | grep -v HEAD)\n \t do\n \t\tgit branch -r -d $b || exit 1\n \t done &&\n@@ -153,11 +159,14 @@ test_expect_success 'git fetch --all (skipFetchAll)' '\n '\n \n cat > expect << EOF\n+  one/HEAD -> one/main\n   one/main\n   one/side\n+  three/HEAD -> three/main\n   three/another\n   three/main\n   three/side\n+  two/HEAD -> two/main\n   two/another\n   two/main\n   two/side\n@@ -165,7 +174,7 @@ EOF\n \n test_expect_success 'git fetch --multiple (ignoring skipFetchAll)' '\n \t(cd test4 &&\n-\t for b in $(git branch -r)\n+\t for b in $(git branch -r | grep -v HEAD)\n \t do\n \t\tgit branch -r -d $b || exit 1\n \t done &&\n@@ -221,14 +230,17 @@ test_expect_success 'git fetch --multiple --jobs=0 picks a default' '\n \n create_fetch_all_expect () {\n \tcat >expect <<-\\EOF\n+\t  one/HEAD -> one/main\n \t  one/main\n \t  one/side\n \t  origin/HEAD -> origin/main\n \t  origin/main\n \t  origin/side\n+\t  three/HEAD -> three/main\n \t  three/another\n \t  three/main\n \t  three/side\n+\t  two/HEAD -> two/main\n \t  two/another\n \t  two/main\n \t  two/side\n@@ -265,6 +277,7 @@ test_expect_success 'git fetch (fetch all remotes with fetch.all = true)' '\n \n create_fetch_one_expect () {\n \tcat >expect <<-\\EOF\n+\t  one/HEAD -> one/main\n \t  one/main\n \t  one/side\n \t  origin/HEAD -> origin/main\ndiff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\nindex 331778bd42..5a051aa0c7 100755\n--- a/t/t5516-fetch-push.sh\n+++ b/t/t5516-fetch-push.sh\n@@ -1395,7 +1395,8 @@ test_expect_success 'fetch follows tags by default' '\n \t\tgit tag -m \"annotated\" tag &&\n \t\tgit for-each-ref >tmp1 &&\n \t\tsed -n \"p; s|refs/heads/main$|refs/remotes/origin/main|p\" tmp1 |\n-\t\tsort -k 3 >../expect\n+\t\tsed -n \"p; s|refs/heads/main$|refs/remotes/origin/HEAD|p\"  |\n+\t\tsort -k 4 >../expect\n \t) &&\n \ttest_when_finished \"rm -rf dst\" &&\n \tgit init dst &&\ndiff --git a/t/t5527-fetch-odd-refs.sh b/t/t5527-fetch-odd-refs.sh\nindex 98ece27c6a..d3996af6ee 100755\n--- a/t/t5527-fetch-odd-refs.sh\n+++ b/t/t5527-fetch-odd-refs.sh\n@@ -52,7 +52,8 @@ test_expect_success LONG_REF 'fetch handles extremely long refname' '\n \tlong\n \tmain\n \tEOF\n-\tgit for-each-ref --format=\"%(subject)\" refs/remotes/long >actual &&\n+\tgit for-each-ref --format=\"%(subject)\" refs/remotes/long \\\n+\t\t--exclude=refs/remotes/long/HEAD >actual &&\n \ttest_cmp expect actual\n '\n \ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex a66d0e089d..88331d1ceb 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -329,7 +329,8 @@ test_expect_success 'incremental-repack task' '\n \n \t# Delete refs that have not been repacked in these packs.\n \tgit for-each-ref --format=\"delete %(refname)\" \\\n-\t\trefs/prefetch refs/tags refs/remotes >refs &&\n+\t\trefs/prefetch refs/tags refs/remotes \\\n+\t\t--exclude=refs/remotes/*/HEAD >refs &&\n \tgit update-ref --stdin <refs &&\n \n \t# Replace the object directory with this pack layout.\ndiff --git a/t/t9210-scalar.sh b/t/t9210-scalar.sh\nindex 027235d61a..a81662713e 100755\n--- a/t/t9210-scalar.sh\n+++ b/t/t9210-scalar.sh\n@@ -150,7 +150,8 @@ test_expect_success 'scalar clone' '\n \t\t\t\"$(pwd)\" &&\n \n \t\tgit for-each-ref --format=\"%(refname)\" refs/remotes/origin/ >actual &&\n-\t\techo \"refs/remotes/origin/parallel\" >expect &&\n+\t\techo \"refs/remotes/origin/HEAD\" >>expect &&\n+\t\techo \"refs/remotes/origin/parallel\" >>expect &&\n \t\ttest_cmp expect actual &&\n \n \t\ttest_path_is_missing 1/2 &&\n@@ -219,7 +220,7 @@ test_expect_success 'scalar reconfigure --all with includeIf.onbranch' '\n \tdone\n '\n \n- test_expect_success 'scalar reconfigure --all with detached HEADs' '\n+test_expect_success 'scalar reconfigure --all with detached HEADs' '\n \trepos=\"two three four\" &&\n \tfor num in $repos\n \tdo\ndiff --git a/t/t9211-scalar-clone.sh b/t/t9211-scalar-clone.sh\nindex 7869f45ee6..01f71910f5 100755\n--- a/t/t9211-scalar-clone.sh\n+++ b/t/t9211-scalar-clone.sh\n@@ -31,7 +31,7 @@ test_expect_success 'set up repository to clone' '\n \t)\n '\n \n-cleanup_clone () {\n+cleanup_clone() {\n \trm -rf \"$1\"\n }\n \n@@ -127,7 +127,7 @@ test_expect_success '--single-branch clones HEAD only' '\n \t(\n \t\tcd $enlistment/src &&\n \t\tgit for-each-ref refs/remotes/origin >out &&\n-\t\ttest_line_count = 1 out &&\n+\t\ttest_line_count = 2 out &&\n \t\tgrep \"refs/remotes/origin/base\" out\n \t) &&\n \n@@ -141,7 +141,7 @@ test_expect_success '--no-single-branch clones all branches' '\n \t(\n \t\tcd $enlistment/src &&\n \t\tgit for-each-ref refs/remotes/origin >out &&\n-\t\ttest_line_count = 2 out &&\n+\t\ttest_line_count = 3 out &&\n \t\tgrep \"refs/remotes/origin/base\" out &&\n \t\tgrep \"refs/remotes/origin/parallel\" out\n \t) &&\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex cc6aa9f0cd..be87e92e5c 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -658,6 +658,7 @@ test_expect_success '__git_refs - simple' '\n \tHEAD\n \tmain\n \tmatching-branch\n+\tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n \tmatching-tag\n@@ -673,6 +674,7 @@ test_expect_success '__git_refs - full refs' '\n \tcat >expected <<-EOF &&\n \trefs/heads/main\n \trefs/heads/matching-branch\n+\trefs/remotes/other/HEAD\n \trefs/remotes/other/branch-in-other\n \trefs/remotes/other/main-in-other\n \trefs/tags/matching-tag\n@@ -729,6 +731,7 @@ test_expect_success '__git_refs - remote on local file system - full refs' '\n test_expect_success '__git_refs - configured remote' '\n \tcat >expected <<-EOF &&\n \tHEAD\n+\tHEAD\n \tbranch-in-other\n \tmain-in-other\n \tEOF\n@@ -756,6 +759,7 @@ test_expect_success '__git_refs - configured remote - full refs' '\n test_expect_success '__git_refs - configured remote - repo given on the command line' '\n \tcat >expected <<-EOF &&\n \tHEAD\n+\tHEAD\n \tbranch-in-other\n \tmain-in-other\n \tEOF\n@@ -787,6 +791,7 @@ test_expect_success '__git_refs - configured remote - full refs - repo given on\n test_expect_success '__git_refs - configured remote - remote name matches a directory' '\n \tcat >expected <<-EOF &&\n \tHEAD\n+\tHEAD\n \tbranch-in-other\n \tmain-in-other\n \tEOF\n@@ -875,12 +880,14 @@ test_expect_success '__git_refs - unique remote branches for git checkout DWIMer\n \tHEAD\n \tmain\n \tmatching-branch\n+\tother/HEAD\n \tother/ambiguous\n \tother/branch-in-other\n \tother/main-in-other\n \tremote/ambiguous\n \tremote/branch-in-remote\n \tmatching-tag\n+\tHEAD\n \tbranch-in-other\n \tbranch-in-remote\n \tmain-in-other\n@@ -904,6 +911,7 @@ test_expect_success '__git_refs - after --opt=' '\n \tHEAD\n \tmain\n \tmatching-branch\n+\tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n \tmatching-tag\n@@ -919,6 +927,7 @@ test_expect_success '__git_refs - after --opt= - full refs' '\n \tcat >expected <<-EOF &&\n \trefs/heads/main\n \trefs/heads/matching-branch\n+\trefs/remotes/other/HEAD\n \trefs/remotes/other/branch-in-other\n \trefs/remotes/other/main-in-other\n \trefs/tags/matching-tag\n@@ -935,6 +944,7 @@ test_expect_success '__git refs - excluding refs' '\n \t^HEAD\n \t^main\n \t^matching-branch\n+\t^other/HEAD\n \t^other/branch-in-other\n \t^other/main-in-other\n \t^matching-tag\n@@ -950,6 +960,7 @@ test_expect_success '__git refs - excluding full refs' '\n \tcat >expected <<-EOF &&\n \t^refs/heads/main\n \t^refs/heads/matching-branch\n+\t^refs/remotes/other/HEAD\n \t^refs/remotes/other/branch-in-other\n \t^refs/remotes/other/main-in-other\n \t^refs/tags/matching-tag\n@@ -975,6 +986,7 @@ test_expect_success '__git_refs - do not filter refs unless told so' '\n \tmain\n \tmatching-branch\n \tmatching/branch\n+\tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n \tother/matching/branch-in-other\n@@ -1095,6 +1107,7 @@ test_expect_success '__git_complete_refs - simple' '\n \tHEAD Z\n \tmain Z\n \tmatching-branch Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tmatching-tag Z\n@@ -1123,6 +1136,7 @@ test_expect_success '__git_complete_refs - matching' '\n test_expect_success '__git_complete_refs - remote' '\n \tsed -e \"s/Z$//\" >expected <<-EOF &&\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain-in-other Z\n \tEOF\n@@ -1139,9 +1153,11 @@ test_expect_success '__git_complete_refs - track' '\n \tHEAD Z\n \tmain Z\n \tmatching-branch Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tmatching-tag Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain-in-other Z\n \tEOF\n@@ -1184,6 +1200,7 @@ test_expect_success '__git_complete_refs - suffix' '\n \tHEAD.\n \tmain.\n \tmatching-branch.\n+\tother/HEAD.\n \tother/branch-in-other.\n \tother/main-in-other.\n \tmatching-tag.\n@@ -1199,6 +1216,7 @@ test_expect_success '__git_complete_refs - suffix' '\n test_expect_success '__git_complete_fetch_refspecs - simple' '\n \tsed -e \"s/Z$//\" >expected <<-EOF &&\n \tHEAD:HEAD Z\n+\tHEAD:HEAD Z\n \tbranch-in-other:branch-in-other Z\n \tmain-in-other:main-in-other Z\n \tEOF\n@@ -1225,6 +1243,7 @@ test_expect_success '__git_complete_fetch_refspecs - matching' '\n test_expect_success '__git_complete_fetch_refspecs - prefix' '\n \tsed -e \"s/Z$//\" >expected <<-EOF &&\n \t+HEAD:HEAD Z\n+\t+HEAD:HEAD Z\n \t+branch-in-other:branch-in-other Z\n \t+main-in-other:main-in-other Z\n \tEOF\n@@ -1289,6 +1308,7 @@ test_expect_success '__git_complete_worktree_paths with -C' '\n \n test_expect_success 'git switch - with no options, complete local branches and unique remote branch names for DWIM logic' '\n \ttest_completion \"git switch \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1435,11 +1455,13 @@ test_expect_success 'git-bisect - existing view subcommand is recognized and ena\n test_expect_success 'git checkout - completes refs and unique remote branches for DWIM' '\n \ttest_completion \"git checkout \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1461,6 +1483,7 @@ test_expect_success 'git switch - with GIT_COMPLETION_CHECKOUT_NO_GUESS=1, compl\n \n test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete local branches and unique remote names for DWIM logic' '\n \tGIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git switch --guess \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1470,6 +1493,7 @@ test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_G\n \n test_expect_success 'git switch - a later --guess overrides previous --no-guess, complete local and remote unique branches for DWIM' '\n \ttest_completion \"git switch --no-guess --guess \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1490,6 +1514,7 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1498,11 +1523,13 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete\n test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1, complete refs and unique remote branches for DWIM' '\n \tGIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git checkout --guess \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1514,6 +1541,7 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1522,11 +1550,13 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '\n test_expect_success 'git checkout - a later --guess overrides previous --no-guess, complete refs and unique remote branches for DWIM' '\n \ttest_completion \"git checkout --no-guess --guess \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1538,6 +1568,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous --gues\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1550,6 +1581,7 @@ test_expect_success 'git checkout - with checkout.guess = false, only completes\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1559,11 +1591,13 @@ test_expect_success 'git checkout - with checkout.guess = true, completes refs a\n \ttest_config checkout.guess true &&\n \ttest_completion \"git checkout \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1573,11 +1607,13 @@ test_expect_success 'git checkout - a later --guess overrides previous checkout.\n \ttest_config checkout.guess false &&\n \ttest_completion \"git checkout --guess \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1590,6 +1626,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous checko\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1601,6 +1638,7 @@ test_expect_success 'git switch - with --detach, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1612,6 +1650,7 @@ test_expect_success 'git checkout - with --detach, complete only references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1783,6 +1822,7 @@ test_expect_success 'git switch - with -d, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1794,6 +1834,7 @@ test_expect_success 'git checkout - with -d, complete only references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1801,10 +1842,12 @@ test_expect_success 'git checkout - with -d, complete only references' '\n \n test_expect_success 'git switch - with --track, complete only remote branches' '\n \ttest_completion \"git switch --track \" <<-\\EOF &&\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n \ttest_completion \"git switch -t \" <<-\\EOF\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1812,10 +1855,12 @@ test_expect_success 'git switch - with --track, complete only remote branches' '\n \n test_expect_success 'git checkout - with --track, complete only remote branches' '\n \ttest_completion \"git checkout --track \" <<-\\EOF &&\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n \ttest_completion \"git checkout -t \" <<-\\EOF\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1834,6 +1879,7 @@ test_expect_success 'git checkout - with --no-track, complete only local referen\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1845,6 +1891,7 @@ test_expect_success 'git switch - with -c, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1856,6 +1903,7 @@ test_expect_success 'git switch - with -C, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1867,6 +1915,7 @@ test_expect_success 'git switch - with -c and --track, complete all references'\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1878,6 +1927,7 @@ test_expect_success 'git switch - with -C and --track, complete all references'\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1889,6 +1939,7 @@ test_expect_success 'git switch - with -c and --no-track, complete all reference\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1900,6 +1951,7 @@ test_expect_success 'git switch - with -C and --no-track, complete all reference\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1911,6 +1963,7 @@ test_expect_success 'git checkout - with -b, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1922,6 +1975,7 @@ test_expect_success 'git checkout - with -B, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1933,6 +1987,7 @@ test_expect_success 'git checkout - with -b and --track, complete all references\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1944,6 +1999,7 @@ test_expect_success 'git checkout - with -B and --track, complete all references\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1955,6 +2011,7 @@ test_expect_success 'git checkout - with -b and --no-track, complete all referen\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1966,6 +2023,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1973,6 +2031,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen\n \n test_expect_success 'git switch - for -c, complete local branches and unique remote branches' '\n \ttest_completion \"git switch -c \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1982,6 +2041,7 @@ test_expect_success 'git switch - for -c, complete local branches and unique rem\n \n test_expect_success 'git switch - for -C, complete local branches and unique remote branches' '\n \ttest_completion \"git switch -C \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2019,6 +2079,7 @@ test_expect_success 'git switch - for -C with --no-track, complete local branche\n \n test_expect_success 'git checkout - for -b, complete local branches and unique remote branches' '\n \ttest_completion \"git checkout -b \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2028,6 +2089,7 @@ test_expect_success 'git checkout - for -b, complete local branches and unique r\n \n test_expect_success 'git checkout - for -B, complete local branches and unique remote branches' '\n \ttest_completion \"git checkout -B \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2065,6 +2127,7 @@ test_expect_success 'git checkout - for -B with --no-track, complete local branc\n \n test_expect_success 'git switch - with --orphan completes local branch names and unique remote branch names' '\n \ttest_completion \"git switch --orphan \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2080,6 +2143,7 @@ test_expect_success 'git switch - --orphan with branch already provided complete\n \n test_expect_success 'git checkout - with --orphan completes local branch names and unique remote branch names' '\n \ttest_completion \"git checkout --orphan \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2093,6 +2157,7 @@ test_expect_success 'git checkout - --orphan with branch already provided comple\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -3005,7 +3070,7 @@ test_expect_success '__git_pseudoref_exists' '\n \t\t# HEAD should exist, even if it points to an unborn branch.\n \t\t__git_pseudoref_exists HEAD >output 2>&1 &&\n \t\ttest_must_be_empty output &&\n-\n+ \n \t\t# HEAD points to an existing branch, so it should exist.\n \t\ttest_commit A &&\n \t\t__git_pseudoref_exists HEAD >output 2>&1 &&\n-- \n2.47.0.6.g9542df9767\n\n\n"},{"id":"504565","messageId":"20241009135747.3563204-2-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241009135747.3563204-1-bence@ferdinandy.com","subject":"[PATCH v5 2/6] set-head: add new variable for readability","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-09T13:57:24Z","receivedAt":"2024-10-09T14:03:51Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Instead of calling get_main_ref_store(the_repository) multiple times,\ncall it once and store in a new refs variable. Although this change\nprobably offers some performance benefits, the main purpose is to\nshorten the line lengths of function calls using this variable for\nbetter readability.\n---\n\nNotes:\n    v5: new patch (split from the next patch as a preparatory step)\n\n builtin/remote.c | 7 ++++---\n 1 file changed, 4 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex d8ff440027..353ffd2c43 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1404,6 +1404,7 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \tint i, opt_a = 0, opt_d = 0, result = 0;\n \tstruct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;\n \tchar *head_name = NULL;\n+\tstruct ref_store *refs = get_main_ref_store(the_repository);\n \n \tstruct option options[] = {\n \t\tOPT_BOOL('a', \"auto\", &opt_a,\n@@ -1434,7 +1435,7 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\t\thead_name = xstrdup(states.heads.items[0].string);\n \t\tfree_remote_ref_states(&states);\n \t} else if (opt_d && !opt_a && argc == 1) {\n-\t\tif (refs_delete_ref(get_main_ref_store(the_repository), NULL, buf.buf, NULL, REF_NO_DEREF))\n+\t\tif (refs_delete_ref(refs, NULL, buf.buf, NULL, REF_NO_DEREF))\n \t\t\tresult |= error(_(\"Could not delete %s\"), buf.buf);\n \t} else\n \t\tusage_with_options(builtin_remote_sethead_usage, options);\n@@ -1442,9 +1443,9 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \tif (head_name) {\n \t\tstrbuf_addf(&buf2, \"refs/remotes/%s/%s\", argv[0], head_name);\n \t\t/* make sure it's valid */\n-\t\tif (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))\n+\t\tif (!refs_ref_exists(refs, buf2.buf))\n \t\t\tresult |= error(_(\"Not a valid ref: %s\"), buf2.buf);\n-\t\telse if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote set-head\", NULL))\n+\t\telse if (refs_update_symref(refs, buf.buf, buf2.buf, \"remote set-head\", NULL))\n \t\t\tresult |= error(_(\"Could not setup %s\"), buf.buf);\n \t\telse if (opt_a)\n \t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\n-- \n2.47.0.6.g9542df9767\n\n"},{"id":"504566","messageId":"20241009135747.3563204-1-bence@ferdinandy.com","threadId":"62096","inReplyTo":"xmqq1q0xhu13.fsf@gitster.g","subject":"[PATCH v5 1/6] refs_update_symref: atomically record overwritten ref","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-09T13:57:23Z","receivedAt":"2024-10-09T14:03:51Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"When updating a symref it's currently not possible to know for sure what\nwas the previous value that was overwritten. Make use of\nref_transaction's atomicity and record the previous value there. Add\na new variable to refs_update_symref's signature to be able to pass this\ninformation back up to callers.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v4: new patch\n    v5: - added before_target to reftables backend\n        - added an extra safety check for transaction's existence in refs.c\n\n builtin/branch.c          | 2 +-\n builtin/checkout.c        | 4 ++--\n builtin/clone.c           | 6 +++---\n builtin/notes.c           | 2 +-\n builtin/remote.c          | 6 +++---\n builtin/symbolic-ref.c    | 2 +-\n builtin/worktree.c        | 2 +-\n refs.c                    | 8 ++++++--\n refs.h                    | 3 ++-\n refs/files-backend.c      | 1 +\n refs/refs-internal.h      | 8 ++++++++\n refs/reftable-backend.c   | 1 +\n reset.c                   | 2 +-\n sequencer.c               | 2 +-\n setup.c                   | 2 +-\n t/helper/test-ref-store.c | 2 +-\n 16 files changed, 34 insertions(+), 19 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex fd1611ebf5..6c87690b58 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -559,7 +559,7 @@ static int replace_each_worktree_head_symref(struct worktree **worktrees,\n \t\t\tcontinue;\n \n \t\trefs = get_worktree_ref_store(worktrees[i]);\n-\t\tif (refs_update_symref(refs, \"HEAD\", newref, logmsg))\n+\t\tif (refs_update_symref(refs, \"HEAD\", newref, logmsg, NULL))\n \t\t\tret = error(_(\"HEAD of working tree %s is not updated\"),\n \t\t\t\t    worktrees[i]->path);\n \t}\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 9c30000d3a..356ee9bcde 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1015,7 +1015,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\t\tdescribe_detached_head(_(\"HEAD is now at\"), new_branch_info->commit);\n \t\t}\n \t} else if (new_branch_info->path) {\t/* Switch branches. */\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", new_branch_info->path, msg.buf) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", new_branch_info->path, msg.buf, NULL) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t\tif (!opts->quiet) {\n \t\t\tif (old_branch_info->path && !strcmp(new_branch_info->path, old_branch_info->path)) {\n@@ -1479,7 +1479,7 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts)\n \t\tdie(_(\"You are on a branch yet to be born\"));\n \tstrbuf_addf(&branch_ref, \"refs/heads/%s\", opts->new_branch);\n \tstatus = refs_update_symref(get_main_ref_store(the_repository),\n-\t\t\t\t    \"HEAD\", branch_ref.buf, \"checkout -b\");\n+\t\t\t\t    \"HEAD\", branch_ref.buf, \"checkout -b\", NULL);\n \tstrbuf_release(&branch_ref);\n \tif (!opts->quiet)\n \t\tfprintf(stderr, _(\"Switched to a new branch '%s'\\n\"),\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex e77339c847..ead2af20ea 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -661,7 +661,7 @@ static void update_remote_refs(const struct ref *refs,\n \t\tstrbuf_addstr(&head_ref, \"HEAD\");\n \t\tif (refs_update_symref(get_main_ref_store(the_repository), head_ref.buf,\n \t\t\t\t       remote_head_points_at->peer_ref->name,\n-\t\t\t\t       msg) < 0)\n+\t\t\t\t       msg, NULL) < 0)\n \t\t\tdie(_(\"unable to update %s\"), head_ref.buf);\n \t\tstrbuf_release(&head_ref);\n \t}\n@@ -673,7 +673,7 @@ static void update_head(const struct ref *our, const struct ref *remote,\n \tconst char *head;\n \tif (our && skip_prefix(our->name, \"refs/heads/\", &head)) {\n \t\t/* Local default branch link */\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", our->name, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", our->name, NULL, NULL) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t\tif (!option_bare) {\n \t\t\trefs_update_ref(get_main_ref_store(the_repository),\n@@ -702,7 +702,7 @@ static void update_head(const struct ref *our, const struct ref *remote,\n \t\t * Unborn head from remote; same as \"our\" case above except\n \t\t * that we have no ref to update.\n \t\t */\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", unborn, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", unborn, NULL, NULL) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t\tif (!option_bare)\n \t\t\tinstall_branch_config(0, head, remote_name, unborn);\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex 8c26e45526..ba646f06ff 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -980,7 +980,7 @@ static int merge(int argc, const char **argv, const char *prefix)\n \t\t\tdie(_(\"a notes merge into %s is already in-progress at %s\"),\n \t\t\t    notes_ref, wt->path);\n \t\tfree_worktrees(worktrees);\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"NOTES_MERGE_REF\", notes_ref, NULL))\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"NOTES_MERGE_REF\", notes_ref, NULL, NULL))\n \t\t\tdie(_(\"failed to store link to current notes ref (%s)\"),\n \t\t\t    notes_ref);\n \t\tfprintf(stderr, _(\"Automatic notes merge failed. Fix conflicts in %s \"\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 76670ddd8b..d8ff440027 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -244,7 +244,7 @@ static int add(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_reset(&buf2);\n \t\tstrbuf_addf(&buf2, \"refs/remotes/%s/%s\", name, master);\n \n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote add\"))\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote add\", NULL))\n \t\t\tresult = error(_(\"Could not setup master '%s'\"), master);\n \t}\n \n@@ -864,7 +864,7 @@ static int mv(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_reset(&buf3);\n \t\tstrbuf_addf(&buf3, \"remote: renamed %s to %s\",\n \t\t\t\titem->string, buf.buf);\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, buf3.buf))\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, buf3.buf, NULL))\n \t\t\tdie(_(\"creating '%s' failed\"), buf.buf);\n \t\tdisplay_progress(progress, ++refs_renamed_nr);\n \t}\n@@ -1444,7 +1444,7 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\t/* make sure it's valid */\n \t\tif (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))\n \t\t\tresult |= error(_(\"Not a valid ref: %s\"), buf2.buf);\n-\t\telse if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote set-head\"))\n+\t\telse if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote set-head\", NULL))\n \t\t\tresult |= error(_(\"Could not setup %s\"), buf.buf);\n \t\telse if (opt_a)\n \t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\ndiff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c\nindex 299d23d76a..7728fbc3c1 100644\n--- a/builtin/symbolic-ref.c\n+++ b/builtin/symbolic-ref.c\n@@ -88,7 +88,7 @@ int cmd_symbolic_ref(int argc,\n \t\tif (check_refname_format(argv[1], REFNAME_ALLOW_ONELEVEL) < 0)\n \t\t\tdie(\"Refusing to set '%s' to invalid ref '%s'\", argv[0], argv[1]);\n \t\tret = !!refs_update_symref(get_main_ref_store(the_repository),\n-\t\t\t\t\t   argv[0], argv[1], msg);\n+\t\t\t\t\t   argv[0], argv[1], msg, NULL);\n \t\tbreak;\n \tdefault:\n \t\tusage_with_options(git_symbolic_ref_usage, options);\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex fc31d072a6..a7ab4193c1 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -517,7 +517,7 @@ static int add_worktree(const char *path, const char *refname,\n \t\tret = refs_update_ref(wt_refs, NULL, \"HEAD\", &commit->object.oid,\n \t\t\t\t      NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n \telse\n-\t\tret = refs_update_symref(wt_refs, \"HEAD\", symref.buf, NULL);\n+\t\tret = refs_update_symref(wt_refs, \"HEAD\", symref.buf, NULL, NULL);\n \tif (ret)\n \t\tgoto done;\n \ndiff --git a/refs.c b/refs.c\nindex 5f729ed412..91cacee6f9 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2114,7 +2114,8 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct\n }\n \n int refs_update_symref(struct ref_store *refs, const char *ref,\n-\t\t       const char *target, const char *logmsg)\n+\t\t       const char *target, const char *logmsg,\n+\t\t       struct strbuf *before_target)\n {\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n@@ -2130,6 +2131,10 @@ int refs_update_symref(struct ref_store *refs, const char *ref,\n \t}\n \n \tstrbuf_release(&err);\n+\n+\tif (transaction && before_target && transaction->updates[0]->before_target)\n+\t\tstrbuf_addstr(before_target, transaction->updates[0]->before_target);\n+\n \tif (transaction)\n \t\tref_transaction_free(transaction);\n \n@@ -2948,4 +2953,3 @@ int ref_update_expects_existing_old_ref(struct ref_update *update)\n \treturn (update->flags & REF_HAVE_OLD) &&\n \t\t(!is_null_oid(&update->old_oid) || update->old_target);\n }\n-\ndiff --git a/refs.h b/refs.h\nindex 108dfc93b3..f38616db84 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -571,7 +571,8 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,\n \t\t    const char *newref, const char *logmsg);\n \n int refs_update_symref(struct ref_store *refs, const char *refname,\n-\t\t       const char *target, const char *logmsg);\n+\t\t       const char *target, const char *logmsg,\n+\t\t       struct strbuf *before_target);\n \n enum action_on_err {\n \tUPDATE_REFS_MSG_ON_ERR,\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 0824c0b8a9..8415f2d020 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2577,6 +2577,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t}\n \n \tupdate->backend_data = lock;\n+\tupdate->before_target = xstrdup_or_null(referent.buf);\n \n \tif (update->type & REF_ISSYMREF) {\n \t\tif (update->flags & REF_NO_DEREF) {\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 2313c830d8..7df3e6271e 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -104,6 +104,14 @@ struct ref_update {\n \t */\n \tconst char *old_target;\n \n+\t/*\n+\t * The previous target before applying new_target will be\n+\t * written here, to be used by callers when they do not want to\n+\t * check old_target during the transaction, but do want to know\n+\t * what it was.\n+\t */\n+\tconst char *before_target;\n+\n \t/*\n \t * One or more of REF_NO_DEREF, REF_FORCE_CREATE_REFLOG,\n \t * REF_HAVE_NEW, REF_HAVE_OLD, or backend-specific flags.\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 3c96fbf66f..32330b6bc6 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1244,6 +1244,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\tif (ret)\n \t\t\t\tgoto done;\n \t\t}\n+\t\tu->before_target = xstrdup_or_null(referent.buf);\n \t}\n \n \ttransaction->backend_data = tx_data;\ndiff --git a/reset.c b/reset.c\nindex b22b1be792..cc36a9ed56 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -76,7 +76,7 @@ static int update_refs(const struct reset_head_opts *opts,\n \t\tif (!ret)\n \t\t\tret = refs_update_symref(get_main_ref_store(the_repository),\n \t\t\t\t\t\t \"HEAD\", switch_to_branch,\n-\t\t\t\t\t\t reflog_head);\n+\t\t\t\t\t\t reflog_head, NULL);\n \t}\n \tif (!ret && run_hook)\n \t\trun_hooks_l(the_repository, \"post-checkout\",\ndiff --git a/sequencer.c b/sequencer.c\nindex 8d01cd50ac..23b162924c 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -5107,7 +5107,7 @@ static int pick_commits(struct repository *r,\n \t\t\t}\n \t\t\tmsg = reflog_message(opts, \"finish\", \"returning to %s\",\n \t\t\t\thead_ref.buf);\n-\t\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", head_ref.buf, msg)) {\n+\t\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", head_ref.buf, msg, NULL)) {\n \t\t\t\tres = error(_(\"could not update HEAD to %s\"),\n \t\t\t\t\thead_ref.buf);\n \t\t\t\tgoto cleanup_head_ref;\ndiff --git a/setup.c b/setup.c\nindex 94e79b2e48..d95f051465 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -2275,7 +2275,7 @@ void create_reference_database(enum ref_storage_format ref_storage_format,\n \t\t\tdie(_(\"invalid initial branch name: '%s'\"),\n \t\t\t    initial_branch);\n \n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", ref, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", ref, NULL, NULL) < 0)\n \t\t\texit(1);\n \t\tfree(ref);\n \t}\ndiff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c\nindex 65346dee55..a911302bea 100644\n--- a/t/helper/test-ref-store.c\n+++ b/t/helper/test-ref-store.c\n@@ -120,7 +120,7 @@ static int cmd_create_symref(struct ref_store *refs, const char **argv)\n \tconst char *target = notnull(*argv++, \"target\");\n \tconst char *logmsg = *argv++;\n \n-\treturn refs_update_symref(refs, refname, target, logmsg);\n+\treturn refs_update_symref(refs, refname, target, logmsg, NULL);\n }\n \n static struct flag_definition transaction_flags[] = {\n-- \n2.47.0.6.g9542df9767\n\n"},{"id":"504567","messageId":"20241009135747.3563204-4-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241009135747.3563204-1-bence@ferdinandy.com","subject":"[PATCH v5 4/6] transaction: add TRANSACTION_CREATE_EXISTS error","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-09T13:57:26Z","receivedAt":"2024-10-09T14:05:56Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Currently there is only one special error for transaction, for when\nthere is a naming conflict, all other errors are dumped under a generic\nerror. Add a new special error case for when the caller requests the\nreference to be updated only when it does not yet exist and the\nreference actually does exist.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v4: new patch\n    v5: no change\n\n refs.h                  |  4 +++-\n refs/files-backend.c    | 28 ++++++++++++++++++++--------\n refs/reftable-backend.c |  6 ++++--\n 3 files changed, 27 insertions(+), 11 deletions(-)\n\ndiff --git a/refs.h b/refs.h\nindex f38616db84..166affbc89 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -759,8 +759,10 @@ int ref_transaction_verify(struct ref_transaction *transaction,\n \n /* Naming conflict (for example, the ref names A and A/B conflict). */\n #define TRANSACTION_NAME_CONFLICT -1\n+/* When only creation was requested, but the ref already exists. */\n+#define TRANSACTION_CREATE_EXISTS -2\n /* All other errors. */\n-#define TRANSACTION_GENERIC_ERROR -2\n+#define TRANSACTION_GENERIC_ERROR -3\n \n /*\n  * Perform the preparatory stages of committing `transaction`. Acquire\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 8415f2d020..272ad81315 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2502,14 +2502,18 @@ static int split_symref_update(struct ref_update *update,\n static int check_old_oid(struct ref_update *update, struct object_id *oid,\n \t\t\t struct strbuf *err)\n {\n+\tint ret = TRANSACTION_GENERIC_ERROR;\n+\n \tif (!(update->flags & REF_HAVE_OLD) ||\n \t\t   oideq(oid, &update->old_oid))\n \t\treturn 0;\n \n-\tif (is_null_oid(&update->old_oid))\n+\tif (is_null_oid(&update->old_oid)) {\n \t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n \t\t\t    \"reference already exists\",\n \t\t\t    ref_update_original_update_refname(update));\n+\t\tret = TRANSACTION_CREATE_EXISTS;\n+\t}\n \telse if (is_null_oid(oid))\n \t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n \t\t\t    \"reference is missing but expected %s\",\n@@ -2522,7 +2526,7 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid,\n \t\t\t    oid_to_hex(oid),\n \t\t\t    oid_to_hex(&update->old_oid));\n \n-\treturn -1;\n+\treturn ret;\n }\n \n /*\n@@ -2603,9 +2607,13 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\t\tgoto out;\n \t\t\t\t}\n-\t\t\t} else if  (check_old_oid(update, &lock->old_oid, err)) {\n-\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n-\t\t\t\tgoto out;\n+\t\t\t} else {\n+\t\t\t\tint checkret;\n+\t\t\t\tcheckret = check_old_oid(update, &lock->old_oid, err);\n+\t\t\t\tif  (checkret) {\n+\t\t\t\t\tret = checkret;\n+\t\t\t\t\tgoto out;\n+\t\t\t\t}\n \t\t\t}\n \t\t} else {\n \t\t\t/*\n@@ -2636,9 +2644,13 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\t\t    update->old_target);\n \t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\tgoto out;\n-\t\t} else if  (check_old_oid(update, &lock->old_oid, err)) {\n-\t\t\tret = TRANSACTION_GENERIC_ERROR;\n-\t\t\tgoto out;\n+\t\t} else {\n+\t\t\tint checkret;\n+\t\t\tcheckret = check_old_oid(update, &lock->old_oid, err);\n+\t\t\tif  (checkret) {\n+\t\t\t\tret = checkret;\n+\t\t\t\tgoto out;\n+\t\t\t}\n \t\t}\n \n \t\t/*\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 32330b6bc6..c6b25ebac4 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1206,10 +1206,13 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\tgoto done;\n \t\t\t}\n \t\t} else if ((u->flags & REF_HAVE_OLD) && !oideq(&current_oid, &u->old_oid)) {\n-\t\t\tif (is_null_oid(&u->old_oid))\n+\t\t\tret = TRANSACTION_NAME_CONFLICT;\n+\t\t\tif (is_null_oid(&u->old_oid)) {\n \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n \t\t\t\t\t\t   \"reference already exists\"),\n \t\t\t\t\t    ref_update_original_update_refname(u));\n+\t\t\t\tret = TRANSACTION_CREATE_EXISTS;\n+\t\t\t}\n \t\t\telse if (is_null_oid(&current_oid))\n \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n \t\t\t\t\t\t   \"reference is missing but expected %s\"),\n@@ -1221,7 +1224,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\t\t    ref_update_original_update_refname(u),\n \t\t\t\t\t    oid_to_hex(&current_oid),\n \t\t\t\t\t    oid_to_hex(&u->old_oid));\n-\t\t\tret = -1;\n \t\t\tgoto done;\n \t\t}\n \n-- \n2.47.0.6.g9542df9767\n\n"},{"id":"504568","messageId":"20241009135747.3563204-3-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241009135747.3563204-1-bence@ferdinandy.com","subject":"[PATCH v5 3/6] set-head: better output for --auto","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-09T13:57:25Z","receivedAt":"2024-10-09T14:05:56Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Currently, set-head --auto will print a message saying \"remote/HEAD set\nto branch\", which implies something was changed.\n\nChange the output of --auto, so the output actually reflects what was\ndone: a) set a previously unset HEAD, b) change HEAD because remote\nchanged or c) no updates.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v1-v2:\n    \n        was RFC in https://lore.kernel.org/git/20240910203835.2288291-1-bence@ferdinandy.com/\n    \n    v3:\n    \n        This patch was originally sent along when I thought set-head was\n        going to be invoked by fetch, but the discussion on the RFC\n        concluded that it should be not. This opened the possibility to make\n        it more explicit.\n    \n        Note: although I feel both things the patch does are really just\n        cosmetic, an argument could be made for breaking it into two, one\n        for the no-op part and one for the --auto print update.\n    \n        Was sent in:\n        https://lore.kernel.org/git/20240915221055.904107-1-bence@ferdinandy.com/\n    \n    v4:\n        - changes are now handled atomically via the ref update transaction\n        - outputs have changed along the lines of Junio's suggestion\n        - minor refactor to set_head for improved legibility\n    \n    v5: - the minor refactor has been split out into its own patch\n\n builtin/remote.c  | 30 ++++++++++++++++++++++++++----\n t/t5505-remote.sh | 13 ++++++++++++-\n 2 files changed, 38 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 353ffd2c43..2480128b88 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1399,10 +1399,30 @@ static int show(int argc, const char **argv, const char *prefix)\n \treturn result;\n }\n \n+static void report_auto(const char *remote, const char *head_name,\n+\t\t\tstruct strbuf *buf_prev) {\n+\tstruct strbuf buf_prefix = STRBUF_INIT;\n+\tconst char *prev_head;\n+\n+\tstrbuf_addf(&buf_prefix, \"refs/remotes/%s/\", remote);\n+\tskip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);\n+\n+\tif (prev_head && !strcmp(prev_head, head_name))\n+\t\tprintf(\"'%s/HEAD' is unchanged and points to '%s'\\n\",\n+\t\t\tremote, head_name);\n+\telse if (prev_head)\n+\t\tprintf(\"'%s/HEAD' has changed from '%s' and now points to '%s'\\n\",\n+\t\t\tremote, prev_head, head_name);\n+\telse\n+\t\tprintf(\"'%s/HEAD' is now created and points to '%s'\\n\",\n+\t\t\tremote, head_name);\n+}\n+\n static int set_head(int argc, const char **argv, const char *prefix)\n {\n \tint i, opt_a = 0, opt_d = 0, result = 0;\n-\tstruct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;\n+\tstruct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT,\n+\t\tbuf_prev = STRBUF_INIT;\n \tchar *head_name = NULL;\n \tstruct ref_store *refs = get_main_ref_store(the_repository);\n \n@@ -1445,15 +1465,17 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\t/* make sure it's valid */\n \t\tif (!refs_ref_exists(refs, buf2.buf))\n \t\t\tresult |= error(_(\"Not a valid ref: %s\"), buf2.buf);\n-\t\telse if (refs_update_symref(refs, buf.buf, buf2.buf, \"remote set-head\", NULL))\n+\t\telse if (refs_update_symref(refs, buf.buf, buf2.buf, \"remote set-head\", &buf_prev))\n \t\t\tresult |= error(_(\"Could not setup %s\"), buf.buf);\n-\t\telse if (opt_a)\n-\t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\n+\t\telse if (opt_a) {\n+\t\t\treport_auto(argv[0], head_name, &buf_prev);\n+\t\t}\n \t\tfree(head_name);\n \t}\n \n \tstrbuf_release(&buf);\n \tstrbuf_release(&buf2);\n+\tstrbuf_release(&buf_prev);\n \treturn result;\n }\n \ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex 532035933f..262a4de0aa 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -434,7 +434,7 @@ test_expect_success 'set-head --auto has no problem w/multiple HEADs' '\n \t\tcd test &&\n \t\tgit fetch two \"refs/heads/*:refs/remotes/two/*\" &&\n \t\tgit remote set-head --auto two >output 2>&1 &&\n-\t\techo \"two/HEAD set to main\" >expect &&\n+\t\techo \"'\\''two/HEAD'\\'' is unchanged and points to '\\''main'\\''\" >expect &&\n \t\ttest_cmp expect output\n \t)\n '\n@@ -453,6 +453,17 @@ test_expect_success 'set-head explicit' '\n \t)\n '\n \n+\n+test_expect_success 'set-head --auto reports change' '\n+\t(\n+\t\tcd test &&\n+\t\tgit remote set-head origin side2 &&\n+\t\tgit remote set-head --auto origin >output 2>&1 &&\n+\t\techo \"'\\''origin/HEAD'\\'' has changed from '\\''side2'\\'' and now points to '\\''main'\\''\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n cat >test/expect <<EOF\n Pruning origin\n URL: $(pwd)/one\n-- \n2.47.0.6.g9542df9767\n\n"},{"id":"504633","messageId":"xmqqttdlqdr2.fsf@gitster.g","threadId":"62096","inReplyTo":"20241009135747.3563204-2-bence@ferdinandy.com","subject":"Re: [PATCH v5 2/6] set-head: add new variable for readability","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-10-09T19:26:25Z","receivedAt":"2024-10-09T19:26:28Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bence Ferdinandy <bence@ferdinandy.com> writes:\n\n> Instead of calling get_main_ref_store(the_repository) multiple times,\n> call it once and store in a new refs variable. Although this change\n> probably offers some performance benefits, the main purpose is to\n> shorten the line lengths of function calls using this variable for\n> better readability.\n> ---\n>\n> Notes:\n>     v5: new patch (split from the next patch as a preparatory step)\n\nIt is a good idea to help making it obvious that we are accessing\nthe same ref store.\n\nThe patch needs to be signed-off, though.\n\n\n\n>  builtin/remote.c | 7 ++++---\n>  1 file changed, 4 insertions(+), 3 deletions(-)\n>\n> diff --git a/builtin/remote.c b/builtin/remote.c\n> index d8ff440027..353ffd2c43 100644\n> --- a/builtin/remote.c\n> +++ b/builtin/remote.c\n> @@ -1404,6 +1404,7 @@ static int set_head(int argc, const char **argv, const char *prefix)\n>  \tint i, opt_a = 0, opt_d = 0, result = 0;\n>  \tstruct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;\n>  \tchar *head_name = NULL;\n> +\tstruct ref_store *refs = get_main_ref_store(the_repository);\n>  \n>  \tstruct option options[] = {\n>  \t\tOPT_BOOL('a', \"auto\", &opt_a,\n> @@ -1434,7 +1435,7 @@ static int set_head(int argc, const char **argv, const char *prefix)\n>  \t\t\thead_name = xstrdup(states.heads.items[0].string);\n>  \t\tfree_remote_ref_states(&states);\n>  \t} else if (opt_d && !opt_a && argc == 1) {\n> -\t\tif (refs_delete_ref(get_main_ref_store(the_repository), NULL, buf.buf, NULL, REF_NO_DEREF))\n> +\t\tif (refs_delete_ref(refs, NULL, buf.buf, NULL, REF_NO_DEREF))\n>  \t\t\tresult |= error(_(\"Could not delete %s\"), buf.buf);\n>  \t} else\n>  \t\tusage_with_options(builtin_remote_sethead_usage, options);\n> @@ -1442,9 +1443,9 @@ static int set_head(int argc, const char **argv, const char *prefix)\n>  \tif (head_name) {\n>  \t\tstrbuf_addf(&buf2, \"refs/remotes/%s/%s\", argv[0], head_name);\n>  \t\t/* make sure it's valid */\n> -\t\tif (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))\n> +\t\tif (!refs_ref_exists(refs, buf2.buf))\n>  \t\t\tresult |= error(_(\"Not a valid ref: %s\"), buf2.buf);\n> -\t\telse if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote set-head\", NULL))\n> +\t\telse if (refs_update_symref(refs, buf.buf, buf2.buf, \"remote set-head\", NULL))\n>  \t\t\tresult |= error(_(\"Could not setup %s\"), buf.buf);\n>  \t\telse if (opt_a)\n>  \t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\n"},{"id":"504636","messageId":"D4RJ9EZMHPHG.3BMTRPU3B2PMD@ferdinandy.com","threadId":"62096","inReplyTo":"xmqqttdlqdr2.fsf@gitster.g","subject":"Re: [PATCH v5 2/6] set-head: add new variable for readability","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-09T19:47:21Z","receivedAt":"2024-10-09T19:53:09Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Wed Oct 09, 2024 at 21:26, Junio C Hamano <gitster@pobox.com> wrote:\n> Bence Ferdinandy <bence@ferdinandy.com> writes:\n>\n>> Instead of calling get_main_ref_store(the_repository) multiple times,\n>> call it once and store in a new refs variable. Although this change\n>> probably offers some performance benefits, the main purpose is to\n>> shorten the line lengths of function calls using this variable for\n>> better readability.\n>> ---\n>>\n>> Notes:\n>>     v5: new patch (split from the next patch as a preparatory step)\n>\n> It is a good idea to help making it obvious that we are accessing\n> the same ref store.\n\nIndeed, although it was your idea ;)\n\n>\n> The patch needs to be signed-off, though.\n\nDamn :/ Sorry, another oversight ... Should I send a v6 for the series with\nthis fixed now or wait a bit to see if there are other comments?\n\nThanks again for the patience!\n\nBest,\nBence\n"},{"id":"504652","messageId":"xmqqjzehq9qk.fsf@gitster.g","threadId":"62096","inReplyTo":"20241009135747.3563204-3-bence@ferdinandy.com","subject":"Re: [PATCH v5 3/6] set-head: better output for --auto","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-10-09T20:53:07Z","receivedAt":"2024-10-09T20:53:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bence Ferdinandy <bence@ferdinandy.com> writes:\n\n> +static void report_auto(const char *remote, const char *head_name,\n> +\t\t\tstruct strbuf *buf_prev) {\n> +\tstruct strbuf buf_prefix = STRBUF_INIT;\n> +\tconst char *prev_head;\n\nI think we need to initialize prev_head to NULL.\n\n> +\tstrbuf_addf(&buf_prefix, \"refs/remotes/%s/\", remote);\n> +\tskip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);\n\nIf the symref was not pointing into the \"refs/remotes/<remote>/\"\nhierarchy previously, skip_prefix() comes back without touching\nprev_head (i.e. not starting with the prefix does not clear it).\n\nAssuming that we fix the initialization, the rest of the function\nlooks more or less correct.\n\n> +\tif (prev_head && !strcmp(prev_head, head_name))\n> +\t\tprintf(\"'%s/HEAD' is unchanged and points to '%s'\\n\",\n> +\t\t\tremote, head_name);\n> +\telse if (prev_head)\n> +\t\tprintf(\"'%s/HEAD' has changed from '%s' and now points to '%s'\\n\",\n> +\t\t\tremote, prev_head, head_name);\n> +\telse\n> +\t\tprintf(\"'%s/HEAD' is now created and points to '%s'\\n\",\n> +\t\t\tremote, head_name);\n\nThe \"more or less\" part is that the message does not let you tell\nbetween refs/remotes/<name>/HEAD that did not exist, and\nrefs/remotes/<name>/HEAD that used to point at somewhere unexpected,\noutside refs/remotes/<name>/ hierarchy.\n\nFor that, we can check if buf_prev->len is 0 (in which case, the\n\"now created and points at\" applies) or non-zero (in which case, we\nsay something like \"used to point at '%s' (which is unusual), but\nnow points at '%s' as requested\").\n\n"},{"id":"504653","messageId":"xmqq8quxq91a.fsf@gitster.g","threadId":"62096","inReplyTo":"20241009135747.3563204-4-bence@ferdinandy.com","subject":"Re: [PATCH v5 4/6] transaction: add TRANSACTION_CREATE_EXISTS error","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-10-09T21:08:17Z","receivedAt":"2024-10-09T21:08:20Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bence Ferdinandy <bence@ferdinandy.com> writes:\n\n>  /* Naming conflict (for example, the ref names A and A/B conflict). */\n>  #define TRANSACTION_NAME_CONFLICT -1\n> +/* When only creation was requested, but the ref already exists. */\n> +#define TRANSACTION_CREATE_EXISTS -2\n>  /* All other errors. */\n> -#define TRANSACTION_GENERIC_ERROR -2\n> +#define TRANSACTION_GENERIC_ERROR -3\n\nMy initial knee-jerk reaction to the name \"CREATE_EXISTS\" was that\nthe \"CREATE\" part is redundant, as the only case that the fact that\nsomething exists is a valid reason for an error is when we attempted\nto create it.  EEXIST in errno(3) is not ECREATE_EXIST for the same\nreason.\n\nBut let's let it pass; contrasting with the fact that what ought to\nbe named similarly to EISDIR is called NAME_CONFLICT, CREATE_EXISTS\nis fine.\n\n> diff --git a/refs/files-backend.c b/refs/files-backend.c\n> index 8415f2d020..272ad81315 100644\n> --- a/refs/files-backend.c\n> +++ b/refs/files-backend.c\n> @@ -2502,14 +2502,18 @@ static int split_symref_update(struct ref_update *update,\n>  static int check_old_oid(struct ref_update *update, struct object_id *oid,\n>  \t\t\t struct strbuf *err)\n>  {\n> +\tint ret = TRANSACTION_GENERIC_ERROR;\n> +\n>  \tif (!(update->flags & REF_HAVE_OLD) ||\n>  \t\t   oideq(oid, &update->old_oid))\n>  \t\treturn 0;\n>  \n> -\tif (is_null_oid(&update->old_oid))\n> +\tif (is_null_oid(&update->old_oid)) {\n>  \t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n>  \t\t\t    \"reference already exists\",\n>  \t\t\t    ref_update_original_update_refname(update));\n> +\t\tret = TRANSACTION_CREATE_EXISTS;\n> +\t}\n>  \telse if (is_null_oid(oid))\n>  \t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n>  \t\t\t    \"reference is missing but expected %s\",\n> @@ -2522,7 +2526,7 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid,\n>  \t\t\t    oid_to_hex(oid),\n>  \t\t\t    oid_to_hex(&update->old_oid));\n>  \n> -\treturn -1;\n> +\treturn ret;\n>  }\n\nOK.  So if a caller does not care which exact kind of error it got,\nthey will keep working just the way they used to work, and a caller\nthat cares can now check.  A nice API enhancement.\n\n> @@ -2603,9 +2607,13 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n>  \t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n>  \t\t\t\t\tgoto out;\n>  \t\t\t\t}\n> -\t\t\t} else if  (check_old_oid(update, &lock->old_oid, err)) {\n> -\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n> -\t\t\t\tgoto out;\n> +\t\t\t} else {\n> +\t\t\t\tint checkret;\n> +\t\t\t\tcheckret = check_old_oid(update, &lock->old_oid, err);\n> +\t\t\t\tif  (checkret) {\n> +\t\t\t\t\tret = checkret;\n> +\t\t\t\t\tgoto out;\n> +\t\t\t\t}\n>  \t\t\t}\n\nLikewise; this code is just propagating what check_old_oid() did\nto its callers.\n\n> @@ -2636,9 +2644,13 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n>  \t\t\t\t    update->old_target);\n>  \t\t\tret = TRANSACTION_GENERIC_ERROR;\n>  \t\t\tgoto out;\n> -\t\t} else if  (check_old_oid(update, &lock->old_oid, err)) {\n> -\t\t\tret = TRANSACTION_GENERIC_ERROR;\n> -\t\t\tgoto out;\n> +\t\t} else {\n> +\t\t\tint checkret;\n> +\t\t\tcheckret = check_old_oid(update, &lock->old_oid, err);\n> +\t\t\tif  (checkret) {\n> +\t\t\t\tret = checkret;\n> +\t\t\t\tgoto out;\n> +\t\t\t}\n\nDitto.\n\n> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\n> index 32330b6bc6..c6b25ebac4 100644\n> --- a/refs/reftable-backend.c\n> +++ b/refs/reftable-backend.c\n> @@ -1206,10 +1206,13 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n>  \t\t\t\tgoto done;\n>  \t\t\t}\n>  \t\t} else if ((u->flags & REF_HAVE_OLD) && !oideq(&current_oid, &u->old_oid)) {\n> -\t\t\tif (is_null_oid(&u->old_oid))\n> +\t\t\tret = TRANSACTION_NAME_CONFLICT;\n> +\t\t\tif (is_null_oid(&u->old_oid)) {\n>  \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n>  \t\t\t\t\t\t   \"reference already exists\"),\n>  \t\t\t\t\t    ref_update_original_update_refname(u));\n> +\t\t\t\tret = TRANSACTION_CREATE_EXISTS;\n> +\t\t\t}\n>  \t\t\telse if (is_null_oid(&current_oid))\n>  \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n>  \t\t\t\t\t\t   \"reference is missing but expected %s\"),\n> @@ -1221,7 +1224,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n>  \t\t\t\t\t    ref_update_original_update_refname(u),\n>  \t\t\t\t\t    oid_to_hex(&current_oid),\n>  \t\t\t\t\t    oid_to_hex(&u->old_oid));\n> -\t\t\tret = -1;\n>  \t\t\tgoto done;\n>  \t\t}\n\nSo it remains a little mystery how the ultimate caller uses this new\nbit of information.\n\nWhich is OK---the next step seems to have a new condition on the\nerror status.\n"},{"id":"504655","messageId":"xmqqiku1ot4u.fsf@gitster.g","threadId":"62096","inReplyTo":"20241009135747.3563204-5-bence@ferdinandy.com","subject":"Re: [PATCH v5 5/6] refs_update_symref: add create_only option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-10-09T21:37:05Z","receivedAt":"2024-10-09T21:37:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bence Ferdinandy <bence@ferdinandy.com> writes:\n\n> diff --git a/builtin/branch.c b/builtin/branch.c\n> index 6c87690b58..3c9bc39800 100644\n> --- a/builtin/branch.c\n> +++ b/builtin/branch.c\n> @@ -559,7 +559,7 @@ static int replace_each_worktree_head_symref(struct worktree **worktrees,\n>  \t\t\tcontinue;\n>  \n>  \t\trefs = get_worktree_ref_store(worktrees[i]);\n> -\t\tif (refs_update_symref(refs, \"HEAD\", newref, logmsg, NULL))\n> +\t\tif (refs_update_symref(refs, \"HEAD\", newref, logmsg, NULL, false))\n>  \t\t\tret = error(_(\"HEAD of working tree %s is not updated\"),\n>  \t\t\t\t    worktrees[i]->path);\n>  \t}\n\nDidn't somebody tell you not to use \"bool\" in previous rounds of\nreviews?\n\nWe are still smoking out platforms that have trouble with the type\nby using it only in a tightly controlled weather balloon we raised\nin 8277dbe9 (git-compat-util: convert skip_{prefix,suffix}{,_mem} to\nbool, 2023-12-16), if I recall correctly.\n\nAssuming either (1) I forgot that we are OK with bool everywhere, or\n(2) the patch will be updated to use plain 0 for false and\neverything else for true, let's keep reading.  I as a reader expect\nthat some *.h file will tell us what the new parameter means.\n\n> diff --git a/refs.c b/refs.c\n> index 91cacee6f9..3d2c07dd67 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -2115,19 +2115,32 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct\n>  \n>  int refs_update_symref(struct ref_store *refs, const char *ref,\n>  \t\t       const char *target, const char *logmsg,\n> -\t\t       struct strbuf *before_target)\n> +\t\t       struct strbuf *before_target, bool create_only)\n>  {\n>  \tstruct ref_transaction *transaction;\n>  \tstruct strbuf err = STRBUF_INIT;\n> -\tint ret = 0;\n> +\tint ret = 0, create_ret = 0;\n\nIsn't it sufficient for create_ret to be in the scope deep in\nthe nested if/else where it is used?\n\n>  \ttransaction = ref_store_transaction_begin(refs, &err);\n> +\tif (create_only) {\n> +\t\tif (!transaction ||\n> +\t\t    ref_transaction_create(transaction, ref, NULL, target,\n> +\t\t\t\t\t   REF_NO_DEREF, logmsg, &err)) {\n> +\t\t\tret = error(\"%s\", err.buf);\n> +\t\t}\n> +\t\telse {\n> +\t\t    create_ret = ref_transaction_commit(transaction, &err);\n> +\t\t    if (create_ret && create_ret != TRANSACTION_CREATE_EXISTS)\n> +\t\t\tret = error(\"%s\", err.buf);\n> +\t\t}\n> +\t}\n> +\telse\n> +\t\tif (!transaction ||\n> +\t\t    ref_transaction_update(transaction, ref, NULL, NULL,\n> +\t\t\t\t\t   target, NULL, REF_NO_DEREF,\n> +\t\t\t\t\t   logmsg, &err) ||\n> +\t\t    ref_transaction_commit(transaction, &err)) {\n> +\t\t\tret = error(\"%s\", err.buf);\n>  \t}\n\nStyle: in if/else if/... cascade, opening and closing {braces} sit\non the same line as these keywords, i.e.\n\n\tif (create_only) {\n\t\tif (...) {\n\t\t\tret = error(...);\n\t\t} else {\n\t\t\t...\n\t\t}\n\t} else if (!transaction ||\n\t\t\t...) {\n\t}\n\nBut more importantly, I think it is much cleaner to handle failure\nfrom transaction_begin outside the if/else cascade.  i.e.\n\n\ttransaction = ref_store_transaction_begin(refs, &err);\n\tif (!transaction) {\n        error_return:\n\t\tret = error(\"%s\", err.buf);\n        \tgoto cleanup;\n\t}\n\n        if (create_only) {\n        \tint create_ret;\n\t\tif (ref_transaction_create(...))\n\t\t\tgoto error_return;\n\t\tcreate_ret = ref_transaction_commit(...);\n                if (create_ret && create_ret != TRANSACTION_CREATE_EXISTS)\n\t\t\tgoto error_return;\n\t} else {\n\t\tif (ref_transaction_update(...) ||\n\t\t    ref_transaction_commit(...))\n\t\t\tgoto error_return;\n\t}\n\ncleanup:\n\tstrbuf_release(&err);\n\n\tif (transaction) {\n\t\tif (before_target && ...)\n\t\t\tstrbuf_addstr(before_target, ...);\n\t\tref_transaction_free(transaction);\n\t}\n\treturn ret;\n\nI didn't spot any caller that passes \"true\" to signal that it wants\nthe create_only behaviour in this step.\n\nWhich is OK---let's keep reading to find such a caller in the next\nstep.\n\n\n"},{"id":"504657","messageId":"xmqqzfndndfm.fsf@gitster.g","threadId":"62096","inReplyTo":"20241009135747.3563204-6-bence@ferdinandy.com","subject":"Re: [PATCH v5 6/6] fetch: set remote/HEAD if it does not exist","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-10-09T22:01:33Z","receivedAt":"2024-10-09T22:01:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bence Ferdinandy <bence@ferdinandy.com> writes:\n\n> diff --git a/builtin/fetch.c b/builtin/fetch.c\n> index 80a64d0d26..c3d3c05950 100644\n> --- a/builtin/fetch.c\n> +++ b/builtin/fetch.c\n> @@ -1578,6 +1578,82 @@ static int backfill_tags(struct display_state *display_state,\n>  \treturn retcode;\n>  }\n>  \n> +static void report_set_head(const char *remote, const char *head_name,\n> +\t\t\tstruct strbuf *buf_prev) {\n> +\tstruct strbuf buf_prefix = STRBUF_INIT;\n> +\tconst char *prev_head;\n> +\n> +\tstrbuf_addf(&buf_prefix, \"refs/remotes/%s/\", remote);\n> +\tskip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);\n\nThe same \"uninitialized prev_head\" problem I discussed earlier for a\nseparate patch is here, I think.\n\n> +\tif (prev_head && !strcmp(prev_head, head_name))\n> +\t\t;\n> +\telse if (prev_head) {\n> +\t\tprintf(\"'HEAD' at '%s' has changed from '%s' to '%s'\\n\",\n> +\t\t\tremote, prev_head, head_name);\n> +\t\tprintf(\"Run 'git remote set-head %s %s' to follow the change.\\n\",\n> +\t\t\tremote, head_name);\n> +\t}\n\nIf the condition is only for one arm, can't we do this without\nelse/if?  Would the condition become too contorted to read if we did\nso?  Let's see.\n\n\tif (prev_head && strcmp(prev_head, head_name)) {\n\t\tHEAD has changed from prev_head to head_name;\n\t}\n\nI guess it is a bit subjective, but as long as you are used to read\n\"strcmp(a,b)\" as \"a and b are different\", this should be easier to\nread than the original.\n\n> +static const char *strip_refshead(const char *name){\n> +\tskip_prefix(name, \"refs/heads/\", &name);\n> +\treturn name;\n> +}\n> +\n> +static int set_head(const struct ref *remote_refs)\n> +{\n> +\tint result = 0;\n> +\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,\n> +\t\tb_local_head = STRBUF_INIT;\n> +\tconst char *remote = gtransport->remote->name;\n> +\tchar *head_name = NULL;\n> +\tstruct ref *ref, *matches;\n> +\tstruct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;\n> +\tstruct refspec_item refspec = {\n> +\t\t.force = 0,\n> +\t\t.pattern = 1,\n> +\t\t.src = (char *) \"refs/heads/*\",\n> +\t\t.dst = (char *) \"refs/heads/*\",\n> +\t};\n> +\tstruct string_list heads = STRING_LIST_INIT_DUP;\n> +\tstruct ref_store *refs = get_main_ref_store(the_repository);\n> +\n> +\tget_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);\n> +\tmatches = guess_remote_head(find_ref_by_name(remote_refs, \"HEAD\"),\n> +\t\t\t\t    fetch_map, 1);\n> +\tfor (ref = matches; ref; ref = ref->next) {\n> +\t\tstring_list_append(&heads, strip_refshead(ref->name));\n> +\t}\n> +\n> +\n> +\tif (!heads.nr)\n> +\t\tresult = 1;\n> +\telse if (heads.nr > 1) {\n\nCurious use of extra {braces}.  In this case we can just lose them.\n\n> +\t\tresult = 1;\n> +\t} else\n> +\t\thead_name = xstrdup(heads.items[0].string);\n\n\n> +\tif (head_name) {\n> +\t\tstrbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", remote);\n> +\t\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", remote, head_name);\n> +\t\t/* make sure it's valid */\n> +\t\tif (!refs_ref_exists(refs, b_remote_head.buf))\n> +\t\t\tresult |= error(_(\"Not a valid ref: %s\"), b_remote_head.buf);\n> +\t\telse if (refs_update_symref(refs, b_head.buf, b_remote_head.buf,\n> +\t\t\t\t\t\"remote set-head\", &b_local_head, true))\n> +\t\t\tresult |= error(_(\"Could not setup %s\"), b_head.buf);\n\nTwo problems.\n\n * we do not capitalize the beginning of the sentence in an error()\n   message, i.e. \"Could not\" -> \"could not\" (or \"cannot\").\n\n * \"setup\" is not a verb.\n\nI know both were inherited from builtin/remote.c but existing\nbreakage is not a good excuse to make new code worse.\n\n> +\t\telse {\n\nCurious use of extra {braces}.  In this case we can just lose them.\n\n> +\t\t\treport_set_head(remote, head_name, &b_local_head);\n> +\t\t}\n> +\t\tfree(head_name);\n> +\t}\n> +\n> +\tstrbuf_release(&b_head);\n> +\tstrbuf_release(&b_local_head);\n> +\tstrbuf_release(&b_remote_head);\n> +\treturn result;\n> +}\n> +\n>  static int do_fetch(struct transport *transport,\n>  \t\t    struct refspec *rs,\n>  \t\t    const struct fetch_config *config)\n> @@ -1647,6 +1723,8 @@ static int do_fetch(struct transport *transport,\n>  \t\t\t\t    \"refs/tags/\");\n>  \t}\n>  \n> +\tstrvec_push(&transport_ls_refs_options.ref_prefixes,\"HEAD\");\n\nMissing SP after \",\".\n\n>  \tif (must_list_refs) {\n>  \t\ttrace2_region_enter(\"fetch\", \"remote_refs\", the_repository);\n>  \t\tremote_refs = transport_get_remote_refs(transport,\n> @@ -1791,6 +1869,9 @@ static int do_fetch(struct transport *transport,\n>  \t\t\t\t  \"you need to specify exactly one branch with the --set-upstream option\"));\n>  \t\t}\n>  \t}\n> +\tif (set_head(remote_refs))\n> +\t\t; // Way too many cases where this can go wrong.\n> +\t\t  // Just fail silently.\n\n\t/*\n\t * our multi-line comments\n\t * look like this.\n\t */\n\nMore importantly, we are not failing silently.  As we saw in the\nimplementation of set_head() above, a non-zero return came from ret\nthat was assigned the return value of error() in the function, so an\nerror message is already given, no?\n\n> @@ -2021,6 +2102,7 @@ static int fetch_multiple(struct string_list *list, int max_children,\n>  \treturn !!result;\n>  }\n>  \n> +\n>  /*\n>   * Fetching from the promisor remote should use the given filter-spec\n>   * or inherit the default filter-spec from the config.\n\nAn unintended change?\n\nThanks.\n"},{"id":"504692","messageId":"20241010133022.1733542-1-bence@ferdinandy.com","threadId":"62096","inReplyTo":"xmqqzfndndfm.fsf@gitster.g","subject":"[PATCH v6 1/6] refs_update_symref: atomically record overwritten ref","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-10T13:29:56Z","receivedAt":"2024-10-10T13:31:28Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"When updating a symref it's currently not possible to know for sure what\nwas the previous value that was overwritten. Make use of\nref_transaction's atomicity and record the previous value there. Add\na new variable to refs_update_symref's signature to be able to pass this\ninformation back up to callers.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v4: new patch\n    \n    v5: - added before_target to reftables backend\n        - added an extra safety check for transaction's existence in refs.c\n    \n    v6: - no change\n\n builtin/branch.c          | 2 +-\n builtin/checkout.c        | 4 ++--\n builtin/clone.c           | 6 +++---\n builtin/notes.c           | 2 +-\n builtin/remote.c          | 6 +++---\n builtin/symbolic-ref.c    | 2 +-\n builtin/worktree.c        | 2 +-\n refs.c                    | 8 ++++++--\n refs.h                    | 3 ++-\n refs/files-backend.c      | 1 +\n refs/refs-internal.h      | 8 ++++++++\n refs/reftable-backend.c   | 1 +\n reset.c                   | 2 +-\n sequencer.c               | 2 +-\n setup.c                   | 2 +-\n t/helper/test-ref-store.c | 2 +-\n 16 files changed, 34 insertions(+), 19 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex fd1611ebf5..6c87690b58 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -559,7 +559,7 @@ static int replace_each_worktree_head_symref(struct worktree **worktrees,\n \t\t\tcontinue;\n \n \t\trefs = get_worktree_ref_store(worktrees[i]);\n-\t\tif (refs_update_symref(refs, \"HEAD\", newref, logmsg))\n+\t\tif (refs_update_symref(refs, \"HEAD\", newref, logmsg, NULL))\n \t\t\tret = error(_(\"HEAD of working tree %s is not updated\"),\n \t\t\t\t    worktrees[i]->path);\n \t}\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 9c30000d3a..356ee9bcde 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1015,7 +1015,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\t\tdescribe_detached_head(_(\"HEAD is now at\"), new_branch_info->commit);\n \t\t}\n \t} else if (new_branch_info->path) {\t/* Switch branches. */\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", new_branch_info->path, msg.buf) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", new_branch_info->path, msg.buf, NULL) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t\tif (!opts->quiet) {\n \t\t\tif (old_branch_info->path && !strcmp(new_branch_info->path, old_branch_info->path)) {\n@@ -1479,7 +1479,7 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts)\n \t\tdie(_(\"You are on a branch yet to be born\"));\n \tstrbuf_addf(&branch_ref, \"refs/heads/%s\", opts->new_branch);\n \tstatus = refs_update_symref(get_main_ref_store(the_repository),\n-\t\t\t\t    \"HEAD\", branch_ref.buf, \"checkout -b\");\n+\t\t\t\t    \"HEAD\", branch_ref.buf, \"checkout -b\", NULL);\n \tstrbuf_release(&branch_ref);\n \tif (!opts->quiet)\n \t\tfprintf(stderr, _(\"Switched to a new branch '%s'\\n\"),\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex e77339c847..ead2af20ea 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -661,7 +661,7 @@ static void update_remote_refs(const struct ref *refs,\n \t\tstrbuf_addstr(&head_ref, \"HEAD\");\n \t\tif (refs_update_symref(get_main_ref_store(the_repository), head_ref.buf,\n \t\t\t\t       remote_head_points_at->peer_ref->name,\n-\t\t\t\t       msg) < 0)\n+\t\t\t\t       msg, NULL) < 0)\n \t\t\tdie(_(\"unable to update %s\"), head_ref.buf);\n \t\tstrbuf_release(&head_ref);\n \t}\n@@ -673,7 +673,7 @@ static void update_head(const struct ref *our, const struct ref *remote,\n \tconst char *head;\n \tif (our && skip_prefix(our->name, \"refs/heads/\", &head)) {\n \t\t/* Local default branch link */\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", our->name, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", our->name, NULL, NULL) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t\tif (!option_bare) {\n \t\t\trefs_update_ref(get_main_ref_store(the_repository),\n@@ -702,7 +702,7 @@ static void update_head(const struct ref *our, const struct ref *remote,\n \t\t * Unborn head from remote; same as \"our\" case above except\n \t\t * that we have no ref to update.\n \t\t */\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", unborn, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", unborn, NULL, NULL) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t\tif (!option_bare)\n \t\t\tinstall_branch_config(0, head, remote_name, unborn);\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex 8c26e45526..ba646f06ff 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -980,7 +980,7 @@ static int merge(int argc, const char **argv, const char *prefix)\n \t\t\tdie(_(\"a notes merge into %s is already in-progress at %s\"),\n \t\t\t    notes_ref, wt->path);\n \t\tfree_worktrees(worktrees);\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"NOTES_MERGE_REF\", notes_ref, NULL))\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"NOTES_MERGE_REF\", notes_ref, NULL, NULL))\n \t\t\tdie(_(\"failed to store link to current notes ref (%s)\"),\n \t\t\t    notes_ref);\n \t\tfprintf(stderr, _(\"Automatic notes merge failed. Fix conflicts in %s \"\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 76670ddd8b..d8ff440027 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -244,7 +244,7 @@ static int add(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_reset(&buf2);\n \t\tstrbuf_addf(&buf2, \"refs/remotes/%s/%s\", name, master);\n \n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote add\"))\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote add\", NULL))\n \t\t\tresult = error(_(\"Could not setup master '%s'\"), master);\n \t}\n \n@@ -864,7 +864,7 @@ static int mv(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_reset(&buf3);\n \t\tstrbuf_addf(&buf3, \"remote: renamed %s to %s\",\n \t\t\t\titem->string, buf.buf);\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, buf3.buf))\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, buf3.buf, NULL))\n \t\t\tdie(_(\"creating '%s' failed\"), buf.buf);\n \t\tdisplay_progress(progress, ++refs_renamed_nr);\n \t}\n@@ -1444,7 +1444,7 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\t/* make sure it's valid */\n \t\tif (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))\n \t\t\tresult |= error(_(\"Not a valid ref: %s\"), buf2.buf);\n-\t\telse if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote set-head\"))\n+\t\telse if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote set-head\", NULL))\n \t\t\tresult |= error(_(\"Could not setup %s\"), buf.buf);\n \t\telse if (opt_a)\n \t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\ndiff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c\nindex 299d23d76a..7728fbc3c1 100644\n--- a/builtin/symbolic-ref.c\n+++ b/builtin/symbolic-ref.c\n@@ -88,7 +88,7 @@ int cmd_symbolic_ref(int argc,\n \t\tif (check_refname_format(argv[1], REFNAME_ALLOW_ONELEVEL) < 0)\n \t\t\tdie(\"Refusing to set '%s' to invalid ref '%s'\", argv[0], argv[1]);\n \t\tret = !!refs_update_symref(get_main_ref_store(the_repository),\n-\t\t\t\t\t   argv[0], argv[1], msg);\n+\t\t\t\t\t   argv[0], argv[1], msg, NULL);\n \t\tbreak;\n \tdefault:\n \t\tusage_with_options(git_symbolic_ref_usage, options);\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex fc31d072a6..a7ab4193c1 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -517,7 +517,7 @@ static int add_worktree(const char *path, const char *refname,\n \t\tret = refs_update_ref(wt_refs, NULL, \"HEAD\", &commit->object.oid,\n \t\t\t\t      NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n \telse\n-\t\tret = refs_update_symref(wt_refs, \"HEAD\", symref.buf, NULL);\n+\t\tret = refs_update_symref(wt_refs, \"HEAD\", symref.buf, NULL, NULL);\n \tif (ret)\n \t\tgoto done;\n \ndiff --git a/refs.c b/refs.c\nindex 5f729ed412..91cacee6f9 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2114,7 +2114,8 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct\n }\n \n int refs_update_symref(struct ref_store *refs, const char *ref,\n-\t\t       const char *target, const char *logmsg)\n+\t\t       const char *target, const char *logmsg,\n+\t\t       struct strbuf *before_target)\n {\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n@@ -2130,6 +2131,10 @@ int refs_update_symref(struct ref_store *refs, const char *ref,\n \t}\n \n \tstrbuf_release(&err);\n+\n+\tif (transaction && before_target && transaction->updates[0]->before_target)\n+\t\tstrbuf_addstr(before_target, transaction->updates[0]->before_target);\n+\n \tif (transaction)\n \t\tref_transaction_free(transaction);\n \n@@ -2948,4 +2953,3 @@ int ref_update_expects_existing_old_ref(struct ref_update *update)\n \treturn (update->flags & REF_HAVE_OLD) &&\n \t\t(!is_null_oid(&update->old_oid) || update->old_target);\n }\n-\ndiff --git a/refs.h b/refs.h\nindex 108dfc93b3..f38616db84 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -571,7 +571,8 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,\n \t\t    const char *newref, const char *logmsg);\n \n int refs_update_symref(struct ref_store *refs, const char *refname,\n-\t\t       const char *target, const char *logmsg);\n+\t\t       const char *target, const char *logmsg,\n+\t\t       struct strbuf *before_target);\n \n enum action_on_err {\n \tUPDATE_REFS_MSG_ON_ERR,\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 0824c0b8a9..8415f2d020 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2577,6 +2577,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t}\n \n \tupdate->backend_data = lock;\n+\tupdate->before_target = xstrdup_or_null(referent.buf);\n \n \tif (update->type & REF_ISSYMREF) {\n \t\tif (update->flags & REF_NO_DEREF) {\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 2313c830d8..7df3e6271e 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -104,6 +104,14 @@ struct ref_update {\n \t */\n \tconst char *old_target;\n \n+\t/*\n+\t * The previous target before applying new_target will be\n+\t * written here, to be used by callers when they do not want to\n+\t * check old_target during the transaction, but do want to know\n+\t * what it was.\n+\t */\n+\tconst char *before_target;\n+\n \t/*\n \t * One or more of REF_NO_DEREF, REF_FORCE_CREATE_REFLOG,\n \t * REF_HAVE_NEW, REF_HAVE_OLD, or backend-specific flags.\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 3c96fbf66f..32330b6bc6 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1244,6 +1244,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\tif (ret)\n \t\t\t\tgoto done;\n \t\t}\n+\t\tu->before_target = xstrdup_or_null(referent.buf);\n \t}\n \n \ttransaction->backend_data = tx_data;\ndiff --git a/reset.c b/reset.c\nindex b22b1be792..cc36a9ed56 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -76,7 +76,7 @@ static int update_refs(const struct reset_head_opts *opts,\n \t\tif (!ret)\n \t\t\tret = refs_update_symref(get_main_ref_store(the_repository),\n \t\t\t\t\t\t \"HEAD\", switch_to_branch,\n-\t\t\t\t\t\t reflog_head);\n+\t\t\t\t\t\t reflog_head, NULL);\n \t}\n \tif (!ret && run_hook)\n \t\trun_hooks_l(the_repository, \"post-checkout\",\ndiff --git a/sequencer.c b/sequencer.c\nindex 8d01cd50ac..23b162924c 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -5107,7 +5107,7 @@ static int pick_commits(struct repository *r,\n \t\t\t}\n \t\t\tmsg = reflog_message(opts, \"finish\", \"returning to %s\",\n \t\t\t\thead_ref.buf);\n-\t\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", head_ref.buf, msg)) {\n+\t\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", head_ref.buf, msg, NULL)) {\n \t\t\t\tres = error(_(\"could not update HEAD to %s\"),\n \t\t\t\t\thead_ref.buf);\n \t\t\t\tgoto cleanup_head_ref;\ndiff --git a/setup.c b/setup.c\nindex 94e79b2e48..d95f051465 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -2275,7 +2275,7 @@ void create_reference_database(enum ref_storage_format ref_storage_format,\n \t\t\tdie(_(\"invalid initial branch name: '%s'\"),\n \t\t\t    initial_branch);\n \n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", ref, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", ref, NULL, NULL) < 0)\n \t\t\texit(1);\n \t\tfree(ref);\n \t}\ndiff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c\nindex 65346dee55..a911302bea 100644\n--- a/t/helper/test-ref-store.c\n+++ b/t/helper/test-ref-store.c\n@@ -120,7 +120,7 @@ static int cmd_create_symref(struct ref_store *refs, const char **argv)\n \tconst char *target = notnull(*argv++, \"target\");\n \tconst char *logmsg = *argv++;\n \n-\treturn refs_update_symref(refs, refname, target, logmsg);\n+\treturn refs_update_symref(refs, refname, target, logmsg, NULL);\n }\n \n static struct flag_definition transaction_flags[] = {\n-- \n2.47.0.6.g07cb02250a\n\n"},{"id":"504691","messageId":"20241010133022.1733542-2-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241010133022.1733542-1-bence@ferdinandy.com","subject":"[PATCH v6 2/6] set-head: add new variable for readability","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-10T13:29:57Z","receivedAt":"2024-10-10T13:31:29Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Instead of calling get_main_ref_store(the_repository) multiple times,\ncall it once and store in a new refs variable. Although this change\nprobably offers some performance benefits, the main purpose is to\nshorten the line lengths of function calls using this variable for\nbetter readability.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v5: new patch (split from the next patch as a preparatory step)\n    \n    v6: no change\n\n builtin/remote.c | 7 ++++---\n 1 file changed, 4 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex d8ff440027..353ffd2c43 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1404,6 +1404,7 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \tint i, opt_a = 0, opt_d = 0, result = 0;\n \tstruct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;\n \tchar *head_name = NULL;\n+\tstruct ref_store *refs = get_main_ref_store(the_repository);\n \n \tstruct option options[] = {\n \t\tOPT_BOOL('a', \"auto\", &opt_a,\n@@ -1434,7 +1435,7 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\t\thead_name = xstrdup(states.heads.items[0].string);\n \t\tfree_remote_ref_states(&states);\n \t} else if (opt_d && !opt_a && argc == 1) {\n-\t\tif (refs_delete_ref(get_main_ref_store(the_repository), NULL, buf.buf, NULL, REF_NO_DEREF))\n+\t\tif (refs_delete_ref(refs, NULL, buf.buf, NULL, REF_NO_DEREF))\n \t\t\tresult |= error(_(\"Could not delete %s\"), buf.buf);\n \t} else\n \t\tusage_with_options(builtin_remote_sethead_usage, options);\n@@ -1442,9 +1443,9 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \tif (head_name) {\n \t\tstrbuf_addf(&buf2, \"refs/remotes/%s/%s\", argv[0], head_name);\n \t\t/* make sure it's valid */\n-\t\tif (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))\n+\t\tif (!refs_ref_exists(refs, buf2.buf))\n \t\t\tresult |= error(_(\"Not a valid ref: %s\"), buf2.buf);\n-\t\telse if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote set-head\", NULL))\n+\t\telse if (refs_update_symref(refs, buf.buf, buf2.buf, \"remote set-head\", NULL))\n \t\t\tresult |= error(_(\"Could not setup %s\"), buf.buf);\n \t\telse if (opt_a)\n \t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\n-- \n2.47.0.6.g07cb02250a\n\n"},{"id":"504693","messageId":"20241010133022.1733542-4-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241010133022.1733542-1-bence@ferdinandy.com","subject":"[PATCH v6 4/6] transaction: add TRANSACTION_CREATE_EXISTS error","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-10T13:29:59Z","receivedAt":"2024-10-10T13:32:15Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Currently there is only one special error for transaction, for when\nthere is a naming conflict, all other errors are dumped under a generic\nerror. Add a new special error case for when the caller requests the\nreference to be updated only when it does not yet exist and the\nreference actually does exist.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v4: new patch\n    v5: no change\n    v6: no change\n\n refs.h                  |  4 +++-\n refs/files-backend.c    | 28 ++++++++++++++++++++--------\n refs/reftable-backend.c |  6 ++++--\n 3 files changed, 27 insertions(+), 11 deletions(-)\n\ndiff --git a/refs.h b/refs.h\nindex f38616db84..166affbc89 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -759,8 +759,10 @@ int ref_transaction_verify(struct ref_transaction *transaction,\n \n /* Naming conflict (for example, the ref names A and A/B conflict). */\n #define TRANSACTION_NAME_CONFLICT -1\n+/* When only creation was requested, but the ref already exists. */\n+#define TRANSACTION_CREATE_EXISTS -2\n /* All other errors. */\n-#define TRANSACTION_GENERIC_ERROR -2\n+#define TRANSACTION_GENERIC_ERROR -3\n \n /*\n  * Perform the preparatory stages of committing `transaction`. Acquire\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 8415f2d020..272ad81315 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2502,14 +2502,18 @@ static int split_symref_update(struct ref_update *update,\n static int check_old_oid(struct ref_update *update, struct object_id *oid,\n \t\t\t struct strbuf *err)\n {\n+\tint ret = TRANSACTION_GENERIC_ERROR;\n+\n \tif (!(update->flags & REF_HAVE_OLD) ||\n \t\t   oideq(oid, &update->old_oid))\n \t\treturn 0;\n \n-\tif (is_null_oid(&update->old_oid))\n+\tif (is_null_oid(&update->old_oid)) {\n \t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n \t\t\t    \"reference already exists\",\n \t\t\t    ref_update_original_update_refname(update));\n+\t\tret = TRANSACTION_CREATE_EXISTS;\n+\t}\n \telse if (is_null_oid(oid))\n \t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n \t\t\t    \"reference is missing but expected %s\",\n@@ -2522,7 +2526,7 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid,\n \t\t\t    oid_to_hex(oid),\n \t\t\t    oid_to_hex(&update->old_oid));\n \n-\treturn -1;\n+\treturn ret;\n }\n \n /*\n@@ -2603,9 +2607,13 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\t\tgoto out;\n \t\t\t\t}\n-\t\t\t} else if  (check_old_oid(update, &lock->old_oid, err)) {\n-\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n-\t\t\t\tgoto out;\n+\t\t\t} else {\n+\t\t\t\tint checkret;\n+\t\t\t\tcheckret = check_old_oid(update, &lock->old_oid, err);\n+\t\t\t\tif  (checkret) {\n+\t\t\t\t\tret = checkret;\n+\t\t\t\t\tgoto out;\n+\t\t\t\t}\n \t\t\t}\n \t\t} else {\n \t\t\t/*\n@@ -2636,9 +2644,13 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\t\t    update->old_target);\n \t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\tgoto out;\n-\t\t} else if  (check_old_oid(update, &lock->old_oid, err)) {\n-\t\t\tret = TRANSACTION_GENERIC_ERROR;\n-\t\t\tgoto out;\n+\t\t} else {\n+\t\t\tint checkret;\n+\t\t\tcheckret = check_old_oid(update, &lock->old_oid, err);\n+\t\t\tif  (checkret) {\n+\t\t\t\tret = checkret;\n+\t\t\t\tgoto out;\n+\t\t\t}\n \t\t}\n \n \t\t/*\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 32330b6bc6..c6b25ebac4 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1206,10 +1206,13 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\tgoto done;\n \t\t\t}\n \t\t} else if ((u->flags & REF_HAVE_OLD) && !oideq(&current_oid, &u->old_oid)) {\n-\t\t\tif (is_null_oid(&u->old_oid))\n+\t\t\tret = TRANSACTION_NAME_CONFLICT;\n+\t\t\tif (is_null_oid(&u->old_oid)) {\n \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n \t\t\t\t\t\t   \"reference already exists\"),\n \t\t\t\t\t    ref_update_original_update_refname(u));\n+\t\t\t\tret = TRANSACTION_CREATE_EXISTS;\n+\t\t\t}\n \t\t\telse if (is_null_oid(&current_oid))\n \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n \t\t\t\t\t\t   \"reference is missing but expected %s\"),\n@@ -1221,7 +1224,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\t\t    ref_update_original_update_refname(u),\n \t\t\t\t\t    oid_to_hex(&current_oid),\n \t\t\t\t\t    oid_to_hex(&u->old_oid));\n-\t\t\tret = -1;\n \t\t\tgoto done;\n \t\t}\n \n-- \n2.47.0.6.g07cb02250a\n\n"},{"id":"504694","messageId":"20241010133022.1733542-3-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241010133022.1733542-1-bence@ferdinandy.com","subject":"[PATCH v6 3/6] set-head: better output for --auto","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-10T13:29:58Z","receivedAt":"2024-10-10T13:32:15Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Currently, set-head --auto will print a message saying \"remote/HEAD set\nto branch\", which implies something was changed.\n\nChange the output of --auto, so the output actually reflects what was\ndone: a) set a previously unset HEAD, b) change HEAD because remote\nchanged or c) no updates. As a fourth output, if HEAD is changed from\na previous value that was not a remote branch, explicitly call attention\nto this fact.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v1-v2:\n    \n        was RFC in https://lore.kernel.org/git/20240910203835.2288291-1-bence@ferdinandy.com/\n    \n    v3:\n    \n        This patch was originally sent along when I thought set-head was\n        going to be invoked by fetch, but the discussion on the RFC\n        concluded that it should be not. This opened the possibility to make\n        it more explicit.\n    \n        Note: although I feel both things the patch does are really just\n        cosmetic, an argument could be made for breaking it into two, one\n        for the no-op part and one for the --auto print update.\n    \n        Was sent in:\n        https://lore.kernel.org/git/20240915221055.904107-1-bence@ferdinandy.com/\n    \n    v4:\n        - changes are now handled atomically via the ref update transaction\n        - outputs have changed along the lines of Junio's suggestion\n        - minor refactor to set_head for improved legibility\n    \n    v5: - the minor refactor has been split out into its own patch\n    \n    v6: - fixed uninitialized prev_head\n        - fixed case of unusual previous target\n        - fixed a test that would have been actually broken at this patch\n          (the output was only correct with the later update to fetch)\n        - added 4 tests for the 4 output cases\n\n builtin/remote.c  | 34 +++++++++++++++++++++++++++----\n t/t5505-remote.sh | 52 ++++++++++++++++++++++++++++++++++++++++++++++-\n 2 files changed, 81 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 353ffd2c43..24f9caf149 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1399,10 +1399,34 @@ static int show(int argc, const char **argv, const char *prefix)\n \treturn result;\n }\n \n+static void report_auto(const char *remote, const char *head_name,\n+\t\t\tstruct strbuf *buf_prev) {\n+\tstruct strbuf buf_prefix = STRBUF_INIT;\n+\tconst char *prev_head = NULL;\n+\n+\tstrbuf_addf(&buf_prefix, \"refs/remotes/%s/\", remote);\n+\tskip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);\n+\n+\tif (prev_head && !strcmp(prev_head, head_name))\n+\t\tprintf(\"'%s/HEAD' is unchanged and points to '%s'\\n\",\n+\t\t\tremote, head_name);\n+\telse if (prev_head)\n+\t\tprintf(\"'%s/HEAD' has changed from '%s' and now points to '%s'\\n\",\n+\t\t\tremote, prev_head, head_name);\n+\telse if (buf_prev->len == 0)\n+\t\tprintf(\"'%s/HEAD' is now created and points to '%s'\\n\",\n+\t\t\tremote, head_name);\n+\telse\n+\t\tprintf(\"'%s/HEAD' used to point to '%s' \"\n+\t\t\t\"(which is unusual), but now points to '%s'\\n\",\n+\t\t\tremote, buf_prev->buf, head_name);\n+}\n+\n static int set_head(int argc, const char **argv, const char *prefix)\n {\n \tint i, opt_a = 0, opt_d = 0, result = 0;\n-\tstruct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;\n+\tstruct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT,\n+\t\tbuf_prev = STRBUF_INIT;\n \tchar *head_name = NULL;\n \tstruct ref_store *refs = get_main_ref_store(the_repository);\n \n@@ -1445,15 +1469,17 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\t/* make sure it's valid */\n \t\tif (!refs_ref_exists(refs, buf2.buf))\n \t\t\tresult |= error(_(\"Not a valid ref: %s\"), buf2.buf);\n-\t\telse if (refs_update_symref(refs, buf.buf, buf2.buf, \"remote set-head\", NULL))\n+\t\telse if (refs_update_symref(refs, buf.buf, buf2.buf, \"remote set-head\", &buf_prev))\n \t\t\tresult |= error(_(\"Could not setup %s\"), buf.buf);\n-\t\telse if (opt_a)\n-\t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\n+\t\telse if (opt_a) {\n+\t\t\treport_auto(argv[0], head_name, &buf_prev);\n+\t\t}\n \t\tfree(head_name);\n \t}\n \n \tstrbuf_release(&buf);\n \tstrbuf_release(&buf2);\n+\tstrbuf_release(&buf_prev);\n \treturn result;\n }\n \ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex 532035933f..77c12b8709 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -429,12 +429,51 @@ test_expect_success 'set-head --auto' '\n \t)\n '\n \n+test_expect_success 'set-head --auto detects creation' '\n+\t(\n+\t\tcd test &&\n+\t\trm .git/refs/remotes/origin/HEAD &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"'\\''origin/HEAD'\\'' is now created and points to '\\''main'\\''\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n+test_expect_success 'set-head --auto detects no change' '\n+\t(\n+\t\tcd test &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"'\\''origin/HEAD'\\'' is unchanged and points to '\\''main'\\''\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n+test_expect_success 'set-head --auto detects change' '\n+\t(\n+\t\tcd test &&\n+\t\techo \"ref: refs/remotes/origin/ahead\" >.git/refs/remotes/origin/HEAD &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"'\\''origin/HEAD'\\'' has changed from '\\''ahead'\\'' and now points to '\\''main'\\''\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n+test_expect_success 'set-head --auto detects strange ref' '\n+\t(\n+\t\tcd test &&\n+\t\techo \"ref: refs/heads/main\" >.git/refs/remotes/origin/HEAD &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"'\\''origin/HEAD'\\'' used to point to '\\''refs/heads/main'\\'' (which is unusual), but now points to '\\''main'\\''\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n test_expect_success 'set-head --auto has no problem w/multiple HEADs' '\n \t(\n \t\tcd test &&\n \t\tgit fetch two \"refs/heads/*:refs/remotes/two/*\" &&\n \t\tgit remote set-head --auto two >output 2>&1 &&\n-\t\techo \"two/HEAD set to main\" >expect &&\n+\t\techo \"'\\''two/HEAD'\\'' is now created and points to '\\''main'\\''\" >expect &&\n \t\ttest_cmp expect output\n \t)\n '\n@@ -453,6 +492,17 @@ test_expect_success 'set-head explicit' '\n \t)\n '\n \n+\n+test_expect_success 'set-head --auto reports change' '\n+\t(\n+\t\tcd test &&\n+\t\tgit remote set-head origin side2 &&\n+\t\tgit remote set-head --auto origin >output 2>&1 &&\n+\t\techo \"'\\''origin/HEAD'\\'' has changed from '\\''side2'\\'' and now points to '\\''main'\\''\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n cat >test/expect <<EOF\n Pruning origin\n URL: $(pwd)/one\n-- \n2.47.0.6.g07cb02250a\n\n"},{"id":"504696","messageId":"20241010133022.1733542-6-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241010133022.1733542-1-bence@ferdinandy.com","subject":"[PATCH v6 6/6] fetch: set remote/HEAD if it does not exist","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-10T13:30:01Z","receivedAt":"2024-10-10T13:33:22Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"If the user has remote/HEAD set already and it looks like it has changed\non the server, then print a message, otherwise set it if we can.\nSilently pass if the user already has the same remote/HEAD set as\nreported by the server or if we encounter any errors along the way.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v3: - does not rely on remote set-head anymore so it only authenticates\n        once\n        - uses the new REF_CREATE_ONLY to atomically check if the ref exists\n          and only write it if it doesn't\n        - in all other cases the maximum it does is print a warning\n    \n    v4: - instead of the discarded REF_CREATE_ONLY, it uses the existing,\n          but updated transaction api to request a silent create only\n        - it now uses the atomic before_target to determine reporting\n        - refactored for legibility\n    \n    v5: - instead of printing a not too useful message, it now fails\n          silently, this in line with the objective to only set up\n          remote/HEAD automatically if the right thing is trivial, for\n          everything else there is remote set-head\n        - fixed all failing tests\n        - added two new tests, one for checking if remote/HEAD is set to the\n          correct one, and one to test that we do not override remote/HEAD\n          if it has changed on the server from what we have locally\n    \n    v6: - fixed style issues and unintended extra empty line\n        - updated function call with bool to int from previous patch's\n          change\n        - removed calls to error(...) inherited from builtin/remote.c so we\n          actually fail silently\n        - set the test for remote set-head --auto to the correct value here,\n          which was previously erronously set in the remote set-head patch\n\n builtin/fetch.c                  |  82 +++++++++++\n t/t4207-log-decoration-colors.sh |   3 +-\n t/t5505-remote.sh                |  13 +-\n t/t5510-fetch.sh                 | 229 ++++++++++++++++---------------\n t/t5512-ls-remote.sh             |   2 +\n t/t5514-fetch-multiple.sh        |  17 ++-\n t/t5516-fetch-push.sh            |   3 +-\n t/t5527-fetch-odd-refs.sh        |   3 +-\n t/t7900-maintenance.sh           |   3 +-\n t/t9210-scalar.sh                |   5 +-\n t/t9211-scalar-clone.sh          |   6 +-\n t/t9902-completion.sh            |  65 +++++++++\n 12 files changed, 308 insertions(+), 123 deletions(-)\n\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 80a64d0d26..4c68f21be1 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1578,6 +1578,80 @@ static int backfill_tags(struct display_state *display_state,\n \treturn retcode;\n }\n \n+static void report_set_head(const char *remote, const char *head_name,\n+\t\t\tstruct strbuf *buf_prev) {\n+\tstruct strbuf buf_prefix = STRBUF_INIT;\n+\tconst char *prev_head = NULL;\n+\n+\tstrbuf_addf(&buf_prefix, \"refs/remotes/%s/\", remote);\n+\tskip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);\n+\n+\tif (prev_head && strcmp(prev_head, head_name)) {\n+\t\tprintf(\"'HEAD' at '%s' has changed from '%s' to '%s'\\n\",\n+\t\t\tremote, prev_head, head_name);\n+\t\tprintf(\"Run 'git remote set-head %s %s' to follow the change.\\n\",\n+\t\t\tremote, head_name);\n+\t}\n+}\n+\n+static const char *strip_refshead(const char *name){\n+\tskip_prefix(name, \"refs/heads/\", &name);\n+\treturn name;\n+}\n+\n+static int set_head(const struct ref *remote_refs)\n+{\n+\tint result = 0;\n+\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,\n+\t\tb_local_head = STRBUF_INIT;\n+\tconst char *remote = gtransport->remote->name;\n+\tchar *head_name = NULL;\n+\tstruct ref *ref, *matches;\n+\tstruct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;\n+\tstruct refspec_item refspec = {\n+\t\t.force = 0,\n+\t\t.pattern = 1,\n+\t\t.src = (char *) \"refs/heads/*\",\n+\t\t.dst = (char *) \"refs/heads/*\",\n+\t};\n+\tstruct string_list heads = STRING_LIST_INIT_DUP;\n+\tstruct ref_store *refs = get_main_ref_store(the_repository);\n+\n+\tget_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);\n+\tmatches = guess_remote_head(find_ref_by_name(remote_refs, \"HEAD\"),\n+\t\t\t\t    fetch_map, 1);\n+\tfor (ref = matches; ref; ref = ref->next) {\n+\t\tstring_list_append(&heads, strip_refshead(ref->name));\n+\t}\n+\n+\n+\tif (!heads.nr)\n+\t\tresult = 1;\n+\telse if (heads.nr > 1)\n+\t\tresult = 1;\n+\telse\n+\t\thead_name = xstrdup(heads.items[0].string);\n+\tif (head_name) {\n+\t\tstrbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", remote);\n+\t\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", remote, head_name);\n+\t\t/* make sure it's valid */\n+\t\tif (!refs_ref_exists(refs, b_remote_head.buf))\n+\t\t\tresult = 1;\n+\t\telse if (refs_update_symref(refs, b_head.buf, b_remote_head.buf,\n+\t\t\t\t\t\"remote set-head\", &b_local_head, 1))\n+\t\t\tresult = 1;\n+\t\telse\n+\t\t\treport_set_head(remote, head_name, &b_local_head);\n+\n+\t\tfree(head_name);\n+\t}\n+\n+\tstrbuf_release(&b_head);\n+\tstrbuf_release(&b_local_head);\n+\tstrbuf_release(&b_remote_head);\n+\treturn result;\n+}\n+\n static int do_fetch(struct transport *transport,\n \t\t    struct refspec *rs,\n \t\t    const struct fetch_config *config)\n@@ -1647,6 +1721,8 @@ static int do_fetch(struct transport *transport,\n \t\t\t\t    \"refs/tags/\");\n \t}\n \n+\tstrvec_push(&transport_ls_refs_options.ref_prefixes, \"HEAD\");\n+\n \tif (must_list_refs) {\n \t\ttrace2_region_enter(\"fetch\", \"remote_refs\", the_repository);\n \t\tremote_refs = transport_get_remote_refs(transport,\n@@ -1791,6 +1867,12 @@ static int do_fetch(struct transport *transport,\n \t\t\t\t  \"you need to specify exactly one branch with the --set-upstream option\"));\n \t\t}\n \t}\n+\tif (set_head(remote_refs))\n+\t\t;\n+\t\t/*\n+\t\t * Way too many cases where this can go wrong\n+\t\t * so let's just fail silently for now.\n+\t\t */\n \n cleanup:\n \tif (retcode) {\ndiff --git a/t/t4207-log-decoration-colors.sh b/t/t4207-log-decoration-colors.sh\nindex 73ea9e5155..d55d22cb2f 100755\n--- a/t/t4207-log-decoration-colors.sh\n+++ b/t/t4207-log-decoration-colors.sh\n@@ -59,7 +59,8 @@ ${c_reset}${c_tag}tag: ${c_reset}${c_tag}v1.0${c_reset}${c_commit}, \\\n ${c_reset}${c_tag}tag: ${c_reset}${c_tag}B${c_reset}${c_commit})${c_reset} B\n ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\n ${c_tag}tag: ${c_reset}${c_tag}A1${c_reset}${c_commit}, \\\n-${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit})${c_reset} A1\n+${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit}, \\\n+${c_reset}${c_remoteBranch}other/HEAD${c_reset}${c_commit})${c_reset} A1\n \t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\n ${c_stash}refs/stash${c_reset}${c_commit})${c_reset} On main: Changes to A.t\n \t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex 77c12b8709..4a62753af3 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -71,7 +71,7 @@ test_expect_success 'add another remote' '\n \t\tcd test &&\n \t\tgit remote add -f second ../two &&\n \t\ttokens_match \"origin second\" \"$(git remote)\" &&\n-\t\tcheck_tracking_branch second main side another &&\n+\t\tcheck_tracking_branch second main side another HEAD &&\n \t\tgit for-each-ref \"--format=%(refname)\" refs/remotes |\n \t\tsed -e \"/^refs\\/remotes\\/origin\\//d\" \\\n \t\t    -e \"/^refs\\/remotes\\/second\\//d\" >actual &&\n@@ -473,7 +473,7 @@ test_expect_success 'set-head --auto has no problem w/multiple HEADs' '\n \t\tcd test &&\n \t\tgit fetch two \"refs/heads/*:refs/remotes/two/*\" &&\n \t\tgit remote set-head --auto two >output 2>&1 &&\n-\t\techo \"'\\''two/HEAD'\\'' is now created and points to '\\''main'\\''\" >expect &&\n+\t\techo \"'\\''two/HEAD'\\'' is unchanged and points to '\\''main'\\''\" >expect &&\n \t\ttest_cmp expect output\n \t)\n '\n@@ -764,6 +764,7 @@ test_expect_success 'reject --no-no-tags' '\n cat >one/expect <<\\EOF\n   apis/main\n   apis/side\n+  drosophila/HEAD -> drosophila/main\n   drosophila/another\n   drosophila/main\n   drosophila/side\n@@ -781,6 +782,7 @@ test_expect_success 'update' '\n '\n \n cat >one/expect <<\\EOF\n+  drosophila/HEAD -> drosophila/main\n   drosophila/another\n   drosophila/main\n   drosophila/side\n@@ -793,7 +795,7 @@ EOF\n test_expect_success 'update with arguments' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\n@@ -836,7 +838,7 @@ EOF\n test_expect_success 'update default' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\n@@ -848,6 +850,7 @@ test_expect_success 'update default' '\n '\n \n cat >one/expect <<\\EOF\n+  drosophila/HEAD -> drosophila/main\n   drosophila/another\n   drosophila/main\n   drosophila/side\n@@ -870,7 +873,7 @@ test_expect_success 'update default (overridden, with funny whitespace)' '\n test_expect_success 'update (with remotes.default defined)' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex 0890b9f61c..dfc8d748ba 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -75,6 +75,30 @@ test_expect_success \"fetch test for-merge\" '\n \tcut -f -2 .git/FETCH_HEAD >actual &&\n \ttest_cmp expected actual'\n \n+test_expect_success \"fetch test remote HEAD\" '\n+\tcd \"$D\" &&\n+\tcd two &&\n+\tgit fetch &&\n+\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\tgit rev-parse --verify refs/remotes/origin/main &&\n+\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\tbranch=$(git rev-parse refs/remotes/origin/main) &&\n+\ttest \"z$head\" = \"z$branch\"'\n+\n+test_expect_success \"fetch test remote HEAD change\" '\n+\tcd \"$D\" &&\n+\tcd two &&\n+\tgit switch -c other &&\n+\tgit push -u origin other &&\n+\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\tgit rev-parse --verify refs/remotes/origin/main &&\n+\tgit rev-parse --verify refs/remotes/origin/other &&\n+\tgit remote set-head origin other &&\n+\tgit fetch &&\n+\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\tbranch=$(git rev-parse refs/remotes/origin/other) &&\n+\ttest \"z$head\" = \"z$branch\"'\n+\n test_expect_success 'fetch --prune on its own works as expected' '\n \tcd \"$D\" &&\n \tgit clone . prune &&\n@@ -478,7 +502,6 @@ test_expect_success 'unbundle 1' '\n \ttest_must_fail git fetch \"$D/bundle1\" main:main\n '\n \n-\n test_expect_success 'bundle 1 has only 3 files ' '\n \tcd \"$D\" &&\n \ttest_bundle_object_count bundle1 3\n@@ -819,7 +842,7 @@ test_expect_success 'fetch from multiple configured URLs in single remote' '\n \n # configured prune tests\n \n-set_config_tristate () {\n+set_config_tristate() {\n \t# var=$1 val=$2\n \tcase \"$2\" in\n \tunset)\n@@ -833,12 +856,12 @@ set_config_tristate () {\n \tesac\n }\n \n-test_configured_prune () {\n+test_configured_prune() {\n \ttest_configured_prune_type \"$@\" \"name\"\n \ttest_configured_prune_type \"$@\" \"link\"\n }\n \n-test_configured_prune_type () {\n+test_configured_prune_type() {\n \tfetch_prune=$1\n \tremote_origin_prune=$2\n \tfetch_prune_tags=$3\n@@ -848,8 +871,7 @@ test_configured_prune_type () {\n \tcmdline=$7\n \tmode=$8\n \n-\tif test -z \"$cmdline_setup\"\n-\tthen\n+\tif test -z \"$cmdline_setup\"; then\n \t\ttest_expect_success 'setup cmdline_setup variable for subsequent test' '\n \t\t\tremote_url=\"file://$(git -C one config remote.origin.url)\" &&\n \t\t\tremote_fetch=\"$(git -C one config remote.origin.fetch)\" &&\n@@ -857,12 +879,10 @@ test_configured_prune_type () {\n \t\t'\n \tfi\n \n-\tif test \"$mode\" = 'link'\n-\tthen\n+\tif test \"$mode\" = 'link'; then\n \t\tnew_cmdline=\"\"\n \n-\t\tif test \"$cmdline\" = \"\"\n-\t\tthen\n+\t\tif test \"$cmdline\" = \"\"; then\n \t\t\tnew_cmdline=$cmdline_setup\n \t\telse\n \t\t\tnew_cmdline=$(perl -e '\n@@ -873,10 +893,8 @@ test_configured_prune_type () {\n \t\tfi\n \n \t\tif test \"$fetch_prune_tags\" = 'true' ||\n-\t\t   test \"$remote_origin_prune_tags\" = 'true'\n-\t\tthen\n-\t\t\tif ! printf '%s' \"$cmdline\\n\" | grep -q refs/remotes/origin/\n-\t\t\tthen\n+\t\t\ttest \"$remote_origin_prune_tags\" = 'true'; then\n+\t\t\tif ! printf '%s' \"$cmdline\\n\" | grep -q refs/remotes/origin/; then\n \t\t\t\tnew_cmdline=\"$new_cmdline refs/tags/*:refs/tags/*\"\n \t\t\tfi\n \t\tfi\n@@ -946,100 +964,100 @@ test_configured_prune_type () {\n # $7 git-fetch $cmdline:\n #\n #                     $1    $2    $3    $4    $5     $6     $7\n-test_configured_prune unset unset unset unset kept   kept   \"\"\n-test_configured_prune unset unset unset unset kept   kept   \"--no-prune\"\n-test_configured_prune unset unset unset unset pruned kept   \"--prune\"\n-test_configured_prune unset unset unset unset kept   pruned \\\n+test_configured_prune unset unset unset unset kept kept \"\"\n+test_configured_prune unset unset unset unset kept kept \"--no-prune\"\n+test_configured_prune unset unset unset unset pruned kept \"--prune\"\n+test_configured_prune unset unset unset unset kept pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/*\"\n test_configured_prune unset unset unset unset pruned pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*\"\n \n-test_configured_prune false unset unset unset kept   kept   \"\"\n-test_configured_prune false unset unset unset kept   kept   \"--no-prune\"\n-test_configured_prune false unset unset unset pruned kept   \"--prune\"\n+test_configured_prune false unset unset unset kept kept \"\"\n+test_configured_prune false unset unset unset kept kept \"--no-prune\"\n+test_configured_prune false unset unset unset pruned kept \"--prune\"\n \n-test_configured_prune true  unset unset unset pruned kept   \"\"\n-test_configured_prune true  unset unset unset pruned kept   \"--prune\"\n-test_configured_prune true  unset unset unset kept   kept   \"--no-prune\"\n+test_configured_prune true unset unset unset pruned kept \"\"\n+test_configured_prune true unset unset unset pruned kept \"--prune\"\n+test_configured_prune true unset unset unset kept kept \"--no-prune\"\n \n-test_configured_prune unset false unset unset kept   kept   \"\"\n-test_configured_prune unset false unset unset kept   kept   \"--no-prune\"\n-test_configured_prune unset false unset unset pruned kept   \"--prune\"\n+test_configured_prune unset false unset unset kept kept \"\"\n+test_configured_prune unset false unset unset kept kept \"--no-prune\"\n+test_configured_prune unset false unset unset pruned kept \"--prune\"\n \n-test_configured_prune false false unset unset kept   kept   \"\"\n-test_configured_prune false false unset unset kept   kept   \"--no-prune\"\n-test_configured_prune false false unset unset pruned kept   \"--prune\"\n-test_configured_prune false false unset unset kept   pruned \\\n+test_configured_prune false false unset unset kept kept \"\"\n+test_configured_prune false false unset unset kept kept \"--no-prune\"\n+test_configured_prune false false unset unset pruned kept \"--prune\"\n+test_configured_prune false false unset unset kept pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/*\"\n test_configured_prune false false unset unset pruned pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*\"\n \n-test_configured_prune true  false unset unset kept   kept   \"\"\n-test_configured_prune true  false unset unset pruned kept   \"--prune\"\n-test_configured_prune true  false unset unset kept   kept   \"--no-prune\"\n+test_configured_prune true false unset unset kept kept \"\"\n+test_configured_prune true false unset unset pruned kept \"--prune\"\n+test_configured_prune true false unset unset kept kept \"--no-prune\"\n \n-test_configured_prune unset true  unset unset pruned kept   \"\"\n-test_configured_prune unset true  unset unset kept   kept   \"--no-prune\"\n-test_configured_prune unset true  unset unset pruned kept   \"--prune\"\n+test_configured_prune unset true unset unset pruned kept \"\"\n+test_configured_prune unset true unset unset kept kept \"--no-prune\"\n+test_configured_prune unset true unset unset pruned kept \"--prune\"\n \n-test_configured_prune false true  unset unset pruned kept   \"\"\n-test_configured_prune false true  unset unset kept   kept   \"--no-prune\"\n-test_configured_prune false true  unset unset pruned kept   \"--prune\"\n+test_configured_prune false true unset unset pruned kept \"\"\n+test_configured_prune false true unset unset kept kept \"--no-prune\"\n+test_configured_prune false true unset unset pruned kept \"--prune\"\n \n-test_configured_prune true  true  unset unset pruned kept   \"\"\n-test_configured_prune true  true  unset unset pruned kept   \"--prune\"\n-test_configured_prune true  true  unset unset kept   kept   \"--no-prune\"\n-test_configured_prune true  true  unset unset kept   pruned \\\n+test_configured_prune true true unset unset pruned kept \"\"\n+test_configured_prune true true unset unset pruned kept \"--prune\"\n+test_configured_prune true true unset unset kept kept \"--no-prune\"\n+test_configured_prune true true unset unset kept pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/*\"\n-test_configured_prune true  true  unset unset pruned pruned \\\n+test_configured_prune true true unset unset pruned pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*\"\n \n # --prune-tags on its own does nothing, needs --prune as well, same\n # for fetch.pruneTags without fetch.prune\n-test_configured_prune unset unset unset unset kept kept     \"--prune-tags\"\n-test_configured_prune unset unset true unset  kept kept     \"\"\n-test_configured_prune unset unset unset true  kept kept     \"\"\n+test_configured_prune unset unset unset unset kept kept \"--prune-tags\"\n+test_configured_prune unset unset true unset kept kept \"\"\n+test_configured_prune unset unset unset true kept kept \"\"\n \n # These will prune the tags\n test_configured_prune unset unset unset unset pruned pruned \"--prune --prune-tags\"\n-test_configured_prune true  unset true  unset pruned pruned \"\"\n-test_configured_prune unset true  unset true  pruned pruned \"\"\n+test_configured_prune true unset true unset pruned pruned \"\"\n+test_configured_prune unset true unset true pruned pruned \"\"\n \n # remote.<name>.pruneTags overrides fetch.pruneTags, just like\n # remote.<name>.prune overrides fetch.prune if set.\n-test_configured_prune true  unset true unset pruned pruned  \"\"\n-test_configured_prune false true  false true  pruned pruned \"\"\n-test_configured_prune true  false true  false kept   kept   \"\"\n+test_configured_prune true unset true unset pruned pruned \"\"\n+test_configured_prune false true false true pruned pruned \"\"\n+test_configured_prune true false true false kept kept \"\"\n \n # When --prune-tags is supplied it's ignored if an explicit refspec is\n # given, same for the configuration options.\n test_configured_prune unset unset unset unset pruned kept \\\n \t\"--prune --prune-tags origin +refs/heads/*:refs/remotes/origin/*\"\n-test_configured_prune unset unset true  unset pruned kept \\\n+test_configured_prune unset unset true unset pruned kept \\\n \t\"--prune origin +refs/heads/*:refs/remotes/origin/*\"\n-test_configured_prune unset unset unset true pruned  kept \\\n+test_configured_prune unset unset unset true pruned kept \\\n \t\"--prune origin +refs/heads/*:refs/remotes/origin/*\"\n \n # Pruning that also takes place if a file:// url replaces a named\n # remote. However, because there's no implicit\n # +refs/heads/*:refs/remotes/origin/* refspec and supplying it on the\n # command-line negates --prune-tags, the branches will not be pruned.\n-test_configured_prune_type unset unset unset unset kept   kept   \"origin --prune-tags\" \"name\"\n-test_configured_prune_type unset unset unset unset kept   kept   \"origin --prune-tags\" \"link\"\n+test_configured_prune_type unset unset unset unset kept kept \"origin --prune-tags\" \"name\"\n+test_configured_prune_type unset unset unset unset kept kept \"origin --prune-tags\" \"link\"\n test_configured_prune_type unset unset unset unset pruned pruned \"origin --prune --prune-tags\" \"name\"\n-test_configured_prune_type unset unset unset unset kept   pruned \"origin --prune --prune-tags\" \"link\"\n+test_configured_prune_type unset unset unset unset kept pruned \"origin --prune --prune-tags\" \"link\"\n test_configured_prune_type unset unset unset unset pruned pruned \"--prune --prune-tags origin\" \"name\"\n-test_configured_prune_type unset unset unset unset kept   pruned \"--prune --prune-tags origin\" \"link\"\n-test_configured_prune_type unset unset true  unset pruned pruned \"--prune origin\" \"name\"\n-test_configured_prune_type unset unset true  unset kept   pruned \"--prune origin\" \"link\"\n-test_configured_prune_type unset unset unset true  pruned pruned \"--prune origin\" \"name\"\n-test_configured_prune_type unset unset unset true  kept   pruned \"--prune origin\" \"link\"\n-test_configured_prune_type true  unset true  unset pruned pruned \"origin\" \"name\"\n-test_configured_prune_type true  unset true  unset kept   pruned \"origin\" \"link\"\n-test_configured_prune_type unset  true true  unset pruned pruned \"origin\" \"name\"\n-test_configured_prune_type unset  true true  unset kept   pruned \"origin\" \"link\"\n-test_configured_prune_type unset  true unset true  pruned pruned \"origin\" \"name\"\n-test_configured_prune_type unset  true unset true  kept   pruned \"origin\" \"link\"\n+test_configured_prune_type unset unset unset unset kept pruned \"--prune --prune-tags origin\" \"link\"\n+test_configured_prune_type unset unset true unset pruned pruned \"--prune origin\" \"name\"\n+test_configured_prune_type unset unset true unset kept pruned \"--prune origin\" \"link\"\n+test_configured_prune_type unset unset unset true pruned pruned \"--prune origin\" \"name\"\n+test_configured_prune_type unset unset unset true kept pruned \"--prune origin\" \"link\"\n+test_configured_prune_type true unset true unset pruned pruned \"origin\" \"name\"\n+test_configured_prune_type true unset true unset kept pruned \"origin\" \"link\"\n+test_configured_prune_type unset true true unset pruned pruned \"origin\" \"name\"\n+test_configured_prune_type unset true true unset kept pruned \"origin\" \"link\"\n+test_configured_prune_type unset true unset true pruned pruned \"origin\" \"name\"\n+test_configured_prune_type unset true unset true kept pruned \"origin\" \"link\"\n \n # When all remote.origin.fetch settings are deleted a --prune\n # --prune-tags still implicitly supplies refs/tags/*:refs/tags/* so\n@@ -1137,8 +1155,7 @@ test_expect_success 'fetching with auto-gc does not lock up' '\n \t)\n '\n \n-for section in fetch transfer\n-do\n+for section in fetch transfer; do\n \ttest_expect_success \"$section.hideRefs affects connectivity check\" '\n \t\tGIT_TRACE=\"$PWD\"/trace git -c $section.hideRefs=refs -c \\\n \t\t\t$section.hideRefs=\"!refs/tags/\" fetch &&\n@@ -1157,21 +1174,23 @@ test_expect_success 'prepare source branch' '\n \ttest 3 -le $(wc -l <actual)\n '\n \n-validate_store_type () {\n+validate_store_type() {\n \tgit -C dest count-objects -v >actual &&\n-\tcase \"$store_type\" in\n-\tpacked)\n-\t\tgrep \"^count: 0$\" actual ;;\n-\tloose)\n-\t\tgrep \"^packs: 0$\" actual ;;\n-\tesac || {\n+\t\tcase \"$store_type\" in\n+\t\tpacked)\n+\t\t\tgrep \"^count: 0$\" actual\n+\t\t\t;;\n+\t\tloose)\n+\t\t\tgrep \"^packs: 0$\" actual\n+\t\t\t;;\n+\t\tesac || {\n \t\techo \"store_type is $store_type\"\n \t\tcat actual\n \t\tfalse\n \t}\n }\n \n-test_unpack_limit () {\n+test_unpack_limit() {\n \tstore_type=$1\n \n \tcase \"$store_type\" in\n@@ -1192,43 +1211,39 @@ test_unpack_limit () {\n test_unpack_limit packed\n test_unpack_limit loose\n \n-setup_negotiation_tip () {\n+setup_negotiation_tip() {\n \tSERVER=\"$1\"\n \tURL=\"$2\"\n \tUSE_PROTOCOL_V2=\"$3\"\n \n \trm -rf \"$SERVER\" client trace &&\n-\tgit init -b main \"$SERVER\" &&\n-\ttest_commit -C \"$SERVER\" alpha_1 &&\n-\ttest_commit -C \"$SERVER\" alpha_2 &&\n-\tgit -C \"$SERVER\" checkout --orphan beta &&\n-\ttest_commit -C \"$SERVER\" beta_1 &&\n-\ttest_commit -C \"$SERVER\" beta_2 &&\n-\n-\tgit clone \"$URL\" client &&\n-\n-\tif test \"$USE_PROTOCOL_V2\" -eq 1\n-\tthen\n-\t\tgit -C \"$SERVER\" config protocol.version 2 &&\n-\t\tgit -C client config protocol.version 2\n-\tfi &&\n-\n-\ttest_commit -C \"$SERVER\" beta_s &&\n-\tgit -C \"$SERVER\" checkout main &&\n-\ttest_commit -C \"$SERVER\" alpha_s &&\n-\tgit -C \"$SERVER\" tag -d alpha_1 alpha_2 beta_1 beta_2\n+\t\tgit init -b main \"$SERVER\" &&\n+\t\ttest_commit -C \"$SERVER\" alpha_1 &&\n+\t\ttest_commit -C \"$SERVER\" alpha_2 &&\n+\t\tgit -C \"$SERVER\" checkout --orphan beta &&\n+\t\ttest_commit -C \"$SERVER\" beta_1 &&\n+\t\ttest_commit -C \"$SERVER\" beta_2 &&\n+\t\tgit clone \"$URL\" client &&\n+\t\tif test \"$USE_PROTOCOL_V2\" -eq 1; then\n+\t\t\tgit -C \"$SERVER\" config protocol.version 2 &&\n+\t\t\t\tgit -C client config protocol.version 2\n+\t\tfi &&\n+\t\ttest_commit -C \"$SERVER\" beta_s &&\n+\t\tgit -C \"$SERVER\" checkout main &&\n+\t\ttest_commit -C \"$SERVER\" alpha_s &&\n+\t\tgit -C \"$SERVER\" tag -d alpha_1 alpha_2 beta_1 beta_2\n }\n \n-check_negotiation_tip () {\n+check_negotiation_tip() {\n \t# Ensure that {alpha,beta}_1 are sent as \"have\", but not {alpha_beta}_2\n \tALPHA_1=$(git -C client rev-parse alpha_1) &&\n-\tgrep \"fetch> have $ALPHA_1\" trace &&\n-\tBETA_1=$(git -C client rev-parse beta_1) &&\n-\tgrep \"fetch> have $BETA_1\" trace &&\n-\tALPHA_2=$(git -C client rev-parse alpha_2) &&\n-\t! grep \"fetch> have $ALPHA_2\" trace &&\n-\tBETA_2=$(git -C client rev-parse beta_2) &&\n-\t! grep \"fetch> have $BETA_2\" trace\n+\t\tgrep \"fetch> have $ALPHA_1\" trace &&\n+\t\tBETA_1=$(git -C client rev-parse beta_1) &&\n+\t\tgrep \"fetch> have $BETA_1\" trace &&\n+\t\tALPHA_2=$(git -C client rev-parse alpha_2) &&\n+\t\t! grep \"fetch> have $ALPHA_2\" trace &&\n+\t\tBETA_2=$(git -C client rev-parse beta_2) &&\n+\t\t! grep \"fetch> have $BETA_2\" trace\n }\n \n test_expect_success '--negotiation-tip limits \"have\" lines sent' '\ndiff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh\nindex 64b3491e4e..1b3865e154 100755\n--- a/t/t5512-ls-remote.sh\n+++ b/t/t5512-ls-remote.sh\n@@ -293,6 +293,8 @@ test_expect_success 'ls-remote with filtered symref (refname)' '\n \tcat >expect <<-EOF &&\n \tref: refs/heads/main\tHEAD\n \t$rev\tHEAD\n+\tref: refs/remotes/origin/main\trefs/remotes/origin/HEAD\n+\t$rev\trefs/remotes/origin/HEAD\n \tEOF\n \tgit ls-remote --symref . HEAD >actual &&\n \ttest_cmp expect actual\ndiff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh\nindex 579872c258..e3482b27b2 100755\n--- a/t/t5514-fetch-multiple.sh\n+++ b/t/t5514-fetch-multiple.sh\n@@ -45,14 +45,17 @@ test_expect_success setup '\n '\n \n cat > test/expect << EOF\n+  one/HEAD -> one/main\n   one/main\n   one/side\n   origin/HEAD -> origin/main\n   origin/main\n   origin/side\n+  three/HEAD -> three/main\n   three/another\n   three/main\n   three/side\n+  two/HEAD -> two/main\n   two/another\n   two/main\n   two/side\n@@ -97,6 +100,7 @@ cat > expect << EOF\n   origin/HEAD -> origin/main\n   origin/main\n   origin/side\n+  three/HEAD -> three/main\n   three/another\n   three/main\n   three/side\n@@ -112,8 +116,10 @@ test_expect_success 'git fetch --multiple (but only one remote)' '\n '\n \n cat > expect << EOF\n+  one/HEAD -> one/main\n   one/main\n   one/side\n+  two/HEAD -> two/main\n   two/another\n   two/main\n   two/side\n@@ -141,7 +147,7 @@ test_expect_success 'git fetch --multiple (bad remote names)' '\n \n test_expect_success 'git fetch --all (skipFetchAll)' '\n \t(cd test4 &&\n-\t for b in $(git branch -r)\n+\t for b in $(git branch -r | grep -v HEAD)\n \t do\n \t\tgit branch -r -d $b || exit 1\n \t done &&\n@@ -153,11 +159,14 @@ test_expect_success 'git fetch --all (skipFetchAll)' '\n '\n \n cat > expect << EOF\n+  one/HEAD -> one/main\n   one/main\n   one/side\n+  three/HEAD -> three/main\n   three/another\n   three/main\n   three/side\n+  two/HEAD -> two/main\n   two/another\n   two/main\n   two/side\n@@ -165,7 +174,7 @@ EOF\n \n test_expect_success 'git fetch --multiple (ignoring skipFetchAll)' '\n \t(cd test4 &&\n-\t for b in $(git branch -r)\n+\t for b in $(git branch -r | grep -v HEAD)\n \t do\n \t\tgit branch -r -d $b || exit 1\n \t done &&\n@@ -221,14 +230,17 @@ test_expect_success 'git fetch --multiple --jobs=0 picks a default' '\n \n create_fetch_all_expect () {\n \tcat >expect <<-\\EOF\n+\t  one/HEAD -> one/main\n \t  one/main\n \t  one/side\n \t  origin/HEAD -> origin/main\n \t  origin/main\n \t  origin/side\n+\t  three/HEAD -> three/main\n \t  three/another\n \t  three/main\n \t  three/side\n+\t  two/HEAD -> two/main\n \t  two/another\n \t  two/main\n \t  two/side\n@@ -265,6 +277,7 @@ test_expect_success 'git fetch (fetch all remotes with fetch.all = true)' '\n \n create_fetch_one_expect () {\n \tcat >expect <<-\\EOF\n+\t  one/HEAD -> one/main\n \t  one/main\n \t  one/side\n \t  origin/HEAD -> origin/main\ndiff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\nindex 331778bd42..5a051aa0c7 100755\n--- a/t/t5516-fetch-push.sh\n+++ b/t/t5516-fetch-push.sh\n@@ -1395,7 +1395,8 @@ test_expect_success 'fetch follows tags by default' '\n \t\tgit tag -m \"annotated\" tag &&\n \t\tgit for-each-ref >tmp1 &&\n \t\tsed -n \"p; s|refs/heads/main$|refs/remotes/origin/main|p\" tmp1 |\n-\t\tsort -k 3 >../expect\n+\t\tsed -n \"p; s|refs/heads/main$|refs/remotes/origin/HEAD|p\"  |\n+\t\tsort -k 4 >../expect\n \t) &&\n \ttest_when_finished \"rm -rf dst\" &&\n \tgit init dst &&\ndiff --git a/t/t5527-fetch-odd-refs.sh b/t/t5527-fetch-odd-refs.sh\nindex 98ece27c6a..d3996af6ee 100755\n--- a/t/t5527-fetch-odd-refs.sh\n+++ b/t/t5527-fetch-odd-refs.sh\n@@ -52,7 +52,8 @@ test_expect_success LONG_REF 'fetch handles extremely long refname' '\n \tlong\n \tmain\n \tEOF\n-\tgit for-each-ref --format=\"%(subject)\" refs/remotes/long >actual &&\n+\tgit for-each-ref --format=\"%(subject)\" refs/remotes/long \\\n+\t\t--exclude=refs/remotes/long/HEAD >actual &&\n \ttest_cmp expect actual\n '\n \ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex a66d0e089d..88331d1ceb 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -329,7 +329,8 @@ test_expect_success 'incremental-repack task' '\n \n \t# Delete refs that have not been repacked in these packs.\n \tgit for-each-ref --format=\"delete %(refname)\" \\\n-\t\trefs/prefetch refs/tags refs/remotes >refs &&\n+\t\trefs/prefetch refs/tags refs/remotes \\\n+\t\t--exclude=refs/remotes/*/HEAD >refs &&\n \tgit update-ref --stdin <refs &&\n \n \t# Replace the object directory with this pack layout.\ndiff --git a/t/t9210-scalar.sh b/t/t9210-scalar.sh\nindex 027235d61a..a81662713e 100755\n--- a/t/t9210-scalar.sh\n+++ b/t/t9210-scalar.sh\n@@ -150,7 +150,8 @@ test_expect_success 'scalar clone' '\n \t\t\t\"$(pwd)\" &&\n \n \t\tgit for-each-ref --format=\"%(refname)\" refs/remotes/origin/ >actual &&\n-\t\techo \"refs/remotes/origin/parallel\" >expect &&\n+\t\techo \"refs/remotes/origin/HEAD\" >>expect &&\n+\t\techo \"refs/remotes/origin/parallel\" >>expect &&\n \t\ttest_cmp expect actual &&\n \n \t\ttest_path_is_missing 1/2 &&\n@@ -219,7 +220,7 @@ test_expect_success 'scalar reconfigure --all with includeIf.onbranch' '\n \tdone\n '\n \n- test_expect_success 'scalar reconfigure --all with detached HEADs' '\n+test_expect_success 'scalar reconfigure --all with detached HEADs' '\n \trepos=\"two three four\" &&\n \tfor num in $repos\n \tdo\ndiff --git a/t/t9211-scalar-clone.sh b/t/t9211-scalar-clone.sh\nindex 7869f45ee6..01f71910f5 100755\n--- a/t/t9211-scalar-clone.sh\n+++ b/t/t9211-scalar-clone.sh\n@@ -31,7 +31,7 @@ test_expect_success 'set up repository to clone' '\n \t)\n '\n \n-cleanup_clone () {\n+cleanup_clone() {\n \trm -rf \"$1\"\n }\n \n@@ -127,7 +127,7 @@ test_expect_success '--single-branch clones HEAD only' '\n \t(\n \t\tcd $enlistment/src &&\n \t\tgit for-each-ref refs/remotes/origin >out &&\n-\t\ttest_line_count = 1 out &&\n+\t\ttest_line_count = 2 out &&\n \t\tgrep \"refs/remotes/origin/base\" out\n \t) &&\n \n@@ -141,7 +141,7 @@ test_expect_success '--no-single-branch clones all branches' '\n \t(\n \t\tcd $enlistment/src &&\n \t\tgit for-each-ref refs/remotes/origin >out &&\n-\t\ttest_line_count = 2 out &&\n+\t\ttest_line_count = 3 out &&\n \t\tgrep \"refs/remotes/origin/base\" out &&\n \t\tgrep \"refs/remotes/origin/parallel\" out\n \t) &&\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex cc6aa9f0cd..b663c4609e 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -658,6 +658,7 @@ test_expect_success '__git_refs - simple' '\n \tHEAD\n \tmain\n \tmatching-branch\n+\tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n \tmatching-tag\n@@ -673,6 +674,7 @@ test_expect_success '__git_refs - full refs' '\n \tcat >expected <<-EOF &&\n \trefs/heads/main\n \trefs/heads/matching-branch\n+\trefs/remotes/other/HEAD\n \trefs/remotes/other/branch-in-other\n \trefs/remotes/other/main-in-other\n \trefs/tags/matching-tag\n@@ -729,6 +731,7 @@ test_expect_success '__git_refs - remote on local file system - full refs' '\n test_expect_success '__git_refs - configured remote' '\n \tcat >expected <<-EOF &&\n \tHEAD\n+\tHEAD\n \tbranch-in-other\n \tmain-in-other\n \tEOF\n@@ -756,6 +759,7 @@ test_expect_success '__git_refs - configured remote - full refs' '\n test_expect_success '__git_refs - configured remote - repo given on the command line' '\n \tcat >expected <<-EOF &&\n \tHEAD\n+\tHEAD\n \tbranch-in-other\n \tmain-in-other\n \tEOF\n@@ -787,6 +791,7 @@ test_expect_success '__git_refs - configured remote - full refs - repo given on\n test_expect_success '__git_refs - configured remote - remote name matches a directory' '\n \tcat >expected <<-EOF &&\n \tHEAD\n+\tHEAD\n \tbranch-in-other\n \tmain-in-other\n \tEOF\n@@ -875,12 +880,14 @@ test_expect_success '__git_refs - unique remote branches for git checkout DWIMer\n \tHEAD\n \tmain\n \tmatching-branch\n+\tother/HEAD\n \tother/ambiguous\n \tother/branch-in-other\n \tother/main-in-other\n \tremote/ambiguous\n \tremote/branch-in-remote\n \tmatching-tag\n+\tHEAD\n \tbranch-in-other\n \tbranch-in-remote\n \tmain-in-other\n@@ -904,6 +911,7 @@ test_expect_success '__git_refs - after --opt=' '\n \tHEAD\n \tmain\n \tmatching-branch\n+\tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n \tmatching-tag\n@@ -919,6 +927,7 @@ test_expect_success '__git_refs - after --opt= - full refs' '\n \tcat >expected <<-EOF &&\n \trefs/heads/main\n \trefs/heads/matching-branch\n+\trefs/remotes/other/HEAD\n \trefs/remotes/other/branch-in-other\n \trefs/remotes/other/main-in-other\n \trefs/tags/matching-tag\n@@ -935,6 +944,7 @@ test_expect_success '__git refs - excluding refs' '\n \t^HEAD\n \t^main\n \t^matching-branch\n+\t^other/HEAD\n \t^other/branch-in-other\n \t^other/main-in-other\n \t^matching-tag\n@@ -950,6 +960,7 @@ test_expect_success '__git refs - excluding full refs' '\n \tcat >expected <<-EOF &&\n \t^refs/heads/main\n \t^refs/heads/matching-branch\n+\t^refs/remotes/other/HEAD\n \t^refs/remotes/other/branch-in-other\n \t^refs/remotes/other/main-in-other\n \t^refs/tags/matching-tag\n@@ -975,6 +986,7 @@ test_expect_success '__git_refs - do not filter refs unless told so' '\n \tmain\n \tmatching-branch\n \tmatching/branch\n+\tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n \tother/matching/branch-in-other\n@@ -1095,6 +1107,7 @@ test_expect_success '__git_complete_refs - simple' '\n \tHEAD Z\n \tmain Z\n \tmatching-branch Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tmatching-tag Z\n@@ -1123,6 +1136,7 @@ test_expect_success '__git_complete_refs - matching' '\n test_expect_success '__git_complete_refs - remote' '\n \tsed -e \"s/Z$//\" >expected <<-EOF &&\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain-in-other Z\n \tEOF\n@@ -1139,9 +1153,11 @@ test_expect_success '__git_complete_refs - track' '\n \tHEAD Z\n \tmain Z\n \tmatching-branch Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tmatching-tag Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain-in-other Z\n \tEOF\n@@ -1184,6 +1200,7 @@ test_expect_success '__git_complete_refs - suffix' '\n \tHEAD.\n \tmain.\n \tmatching-branch.\n+\tother/HEAD.\n \tother/branch-in-other.\n \tother/main-in-other.\n \tmatching-tag.\n@@ -1199,6 +1216,7 @@ test_expect_success '__git_complete_refs - suffix' '\n test_expect_success '__git_complete_fetch_refspecs - simple' '\n \tsed -e \"s/Z$//\" >expected <<-EOF &&\n \tHEAD:HEAD Z\n+\tHEAD:HEAD Z\n \tbranch-in-other:branch-in-other Z\n \tmain-in-other:main-in-other Z\n \tEOF\n@@ -1225,6 +1243,7 @@ test_expect_success '__git_complete_fetch_refspecs - matching' '\n test_expect_success '__git_complete_fetch_refspecs - prefix' '\n \tsed -e \"s/Z$//\" >expected <<-EOF &&\n \t+HEAD:HEAD Z\n+\t+HEAD:HEAD Z\n \t+branch-in-other:branch-in-other Z\n \t+main-in-other:main-in-other Z\n \tEOF\n@@ -1289,6 +1308,7 @@ test_expect_success '__git_complete_worktree_paths with -C' '\n \n test_expect_success 'git switch - with no options, complete local branches and unique remote branch names for DWIM logic' '\n \ttest_completion \"git switch \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1435,11 +1455,13 @@ test_expect_success 'git-bisect - existing view subcommand is recognized and ena\n test_expect_success 'git checkout - completes refs and unique remote branches for DWIM' '\n \ttest_completion \"git checkout \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1461,6 +1483,7 @@ test_expect_success 'git switch - with GIT_COMPLETION_CHECKOUT_NO_GUESS=1, compl\n \n test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete local branches and unique remote names for DWIM logic' '\n \tGIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git switch --guess \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1470,6 +1493,7 @@ test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_G\n \n test_expect_success 'git switch - a later --guess overrides previous --no-guess, complete local and remote unique branches for DWIM' '\n \ttest_completion \"git switch --no-guess --guess \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1490,6 +1514,7 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1498,11 +1523,13 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete\n test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1, complete refs and unique remote branches for DWIM' '\n \tGIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git checkout --guess \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1514,6 +1541,7 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1522,11 +1550,13 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '\n test_expect_success 'git checkout - a later --guess overrides previous --no-guess, complete refs and unique remote branches for DWIM' '\n \ttest_completion \"git checkout --no-guess --guess \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1538,6 +1568,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous --gues\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1550,6 +1581,7 @@ test_expect_success 'git checkout - with checkout.guess = false, only completes\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1559,11 +1591,13 @@ test_expect_success 'git checkout - with checkout.guess = true, completes refs a\n \ttest_config checkout.guess true &&\n \ttest_completion \"git checkout \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1573,11 +1607,13 @@ test_expect_success 'git checkout - a later --guess overrides previous checkout.\n \ttest_config checkout.guess false &&\n \ttest_completion \"git checkout --guess \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1590,6 +1626,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous checko\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1601,6 +1638,7 @@ test_expect_success 'git switch - with --detach, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1612,6 +1650,7 @@ test_expect_success 'git checkout - with --detach, complete only references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1783,6 +1822,7 @@ test_expect_success 'git switch - with -d, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1794,6 +1834,7 @@ test_expect_success 'git checkout - with -d, complete only references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1801,10 +1842,12 @@ test_expect_success 'git checkout - with -d, complete only references' '\n \n test_expect_success 'git switch - with --track, complete only remote branches' '\n \ttest_completion \"git switch --track \" <<-\\EOF &&\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n \ttest_completion \"git switch -t \" <<-\\EOF\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1812,10 +1855,12 @@ test_expect_success 'git switch - with --track, complete only remote branches' '\n \n test_expect_success 'git checkout - with --track, complete only remote branches' '\n \ttest_completion \"git checkout --track \" <<-\\EOF &&\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n \ttest_completion \"git checkout -t \" <<-\\EOF\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1834,6 +1879,7 @@ test_expect_success 'git checkout - with --no-track, complete only local referen\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1845,6 +1891,7 @@ test_expect_success 'git switch - with -c, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1856,6 +1903,7 @@ test_expect_success 'git switch - with -C, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1867,6 +1915,7 @@ test_expect_success 'git switch - with -c and --track, complete all references'\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1878,6 +1927,7 @@ test_expect_success 'git switch - with -C and --track, complete all references'\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1889,6 +1939,7 @@ test_expect_success 'git switch - with -c and --no-track, complete all reference\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1900,6 +1951,7 @@ test_expect_success 'git switch - with -C and --no-track, complete all reference\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1911,6 +1963,7 @@ test_expect_success 'git checkout - with -b, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1922,6 +1975,7 @@ test_expect_success 'git checkout - with -B, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1933,6 +1987,7 @@ test_expect_success 'git checkout - with -b and --track, complete all references\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1944,6 +1999,7 @@ test_expect_success 'git checkout - with -B and --track, complete all references\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1955,6 +2011,7 @@ test_expect_success 'git checkout - with -b and --no-track, complete all referen\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1966,6 +2023,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1973,6 +2031,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen\n \n test_expect_success 'git switch - for -c, complete local branches and unique remote branches' '\n \ttest_completion \"git switch -c \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1982,6 +2041,7 @@ test_expect_success 'git switch - for -c, complete local branches and unique rem\n \n test_expect_success 'git switch - for -C, complete local branches and unique remote branches' '\n \ttest_completion \"git switch -C \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2019,6 +2079,7 @@ test_expect_success 'git switch - for -C with --no-track, complete local branche\n \n test_expect_success 'git checkout - for -b, complete local branches and unique remote branches' '\n \ttest_completion \"git checkout -b \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2028,6 +2089,7 @@ test_expect_success 'git checkout - for -b, complete local branches and unique r\n \n test_expect_success 'git checkout - for -B, complete local branches and unique remote branches' '\n \ttest_completion \"git checkout -B \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2065,6 +2127,7 @@ test_expect_success 'git checkout - for -B with --no-track, complete local branc\n \n test_expect_success 'git switch - with --orphan completes local branch names and unique remote branch names' '\n \ttest_completion \"git switch --orphan \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2080,6 +2143,7 @@ test_expect_success 'git switch - --orphan with branch already provided complete\n \n test_expect_success 'git checkout - with --orphan completes local branch names and unique remote branch names' '\n \ttest_completion \"git checkout --orphan \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2093,6 +2157,7 @@ test_expect_success 'git checkout - --orphan with branch already provided comple\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n-- \n2.47.0.6.g07cb02250a\n\n\n"},{"id":"504695","messageId":"20241010133022.1733542-5-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241010133022.1733542-1-bence@ferdinandy.com","subject":"[PATCH v6 5/6] refs_update_symref: add create_only option","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-10T13:30:00Z","receivedAt":"2024-10-10T13:33:23Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Allow the caller to specify that it only wants to update the symref if\nit does not already exist. Silently ignore the error from the\ntransaction API if the symref already exists.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v4: new patch\n    v5: no change\n    \n    v6: - switched from bool to int for create_only\n        - refactored logic in refs_update_symref with goto as layed out by\n          Junio\n\n builtin/branch.c          |  2 +-\n builtin/checkout.c        |  5 +++--\n builtin/clone.c           |  8 +++++---\n builtin/notes.c           |  3 ++-\n builtin/remote.c          |  9 ++++++---\n builtin/symbolic-ref.c    |  2 +-\n builtin/worktree.c        |  2 +-\n refs.c                    | 36 +++++++++++++++++++++++++-----------\n refs.h                    |  2 +-\n reset.c                   |  2 +-\n sequencer.c               |  3 ++-\n setup.c                   |  3 ++-\n t/helper/test-ref-store.c |  2 +-\n 13 files changed, 51 insertions(+), 28 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 6c87690b58..2fcbcbfd59 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -559,7 +559,7 @@ static int replace_each_worktree_head_symref(struct worktree **worktrees,\n \t\t\tcontinue;\n \n \t\trefs = get_worktree_ref_store(worktrees[i]);\n-\t\tif (refs_update_symref(refs, \"HEAD\", newref, logmsg, NULL))\n+\t\tif (refs_update_symref(refs, \"HEAD\", newref, logmsg, NULL, 0))\n \t\t\tret = error(_(\"HEAD of working tree %s is not updated\"),\n \t\t\t\t    worktrees[i]->path);\n \t}\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 356ee9bcde..9abb71dc07 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1015,7 +1015,8 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\t\tdescribe_detached_head(_(\"HEAD is now at\"), new_branch_info->commit);\n \t\t}\n \t} else if (new_branch_info->path) {\t/* Switch branches. */\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", new_branch_info->path, msg.buf, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", new_branch_info->path,\n+\t\t\t\t\tmsg.buf, NULL, 0) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t\tif (!opts->quiet) {\n \t\t\tif (old_branch_info->path && !strcmp(new_branch_info->path, old_branch_info->path)) {\n@@ -1479,7 +1480,7 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts)\n \t\tdie(_(\"You are on a branch yet to be born\"));\n \tstrbuf_addf(&branch_ref, \"refs/heads/%s\", opts->new_branch);\n \tstatus = refs_update_symref(get_main_ref_store(the_repository),\n-\t\t\t\t    \"HEAD\", branch_ref.buf, \"checkout -b\", NULL);\n+\t\t\t\t    \"HEAD\", branch_ref.buf, \"checkout -b\", NULL, 0);\n \tstrbuf_release(&branch_ref);\n \tif (!opts->quiet)\n \t\tfprintf(stderr, _(\"Switched to a new branch '%s'\\n\"),\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex ead2af20ea..87e8f1421a 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -661,7 +661,7 @@ static void update_remote_refs(const struct ref *refs,\n \t\tstrbuf_addstr(&head_ref, \"HEAD\");\n \t\tif (refs_update_symref(get_main_ref_store(the_repository), head_ref.buf,\n \t\t\t\t       remote_head_points_at->peer_ref->name,\n-\t\t\t\t       msg, NULL) < 0)\n+\t\t\t\t       msg, NULL, 0) < 0)\n \t\t\tdie(_(\"unable to update %s\"), head_ref.buf);\n \t\tstrbuf_release(&head_ref);\n \t}\n@@ -673,7 +673,8 @@ static void update_head(const struct ref *our, const struct ref *remote,\n \tconst char *head;\n \tif (our && skip_prefix(our->name, \"refs/heads/\", &head)) {\n \t\t/* Local default branch link */\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", our->name, NULL, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", our->name,\n+\t\t\t\t\tNULL, NULL, 0) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t\tif (!option_bare) {\n \t\t\trefs_update_ref(get_main_ref_store(the_repository),\n@@ -702,7 +703,8 @@ static void update_head(const struct ref *our, const struct ref *remote,\n \t\t * Unborn head from remote; same as \"our\" case above except\n \t\t * that we have no ref to update.\n \t\t */\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", unborn, NULL, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", unborn,\n+\t\t\t\t\tNULL, NULL, 0) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t\tif (!option_bare)\n \t\t\tinstall_branch_config(0, head, remote_name, unborn);\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex ba646f06ff..7b3138e3c3 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -980,7 +980,8 @@ static int merge(int argc, const char **argv, const char *prefix)\n \t\t\tdie(_(\"a notes merge into %s is already in-progress at %s\"),\n \t\t\t    notes_ref, wt->path);\n \t\tfree_worktrees(worktrees);\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"NOTES_MERGE_REF\", notes_ref, NULL, NULL))\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"NOTES_MERGE_REF\", notes_ref,\n+\t\t\t\t\tNULL, NULL, 0))\n \t\t\tdie(_(\"failed to store link to current notes ref (%s)\"),\n \t\t\t    notes_ref);\n \t\tfprintf(stderr, _(\"Automatic notes merge failed. Fix conflicts in %s \"\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 24f9caf149..9774d8ffe3 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -244,7 +244,8 @@ static int add(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_reset(&buf2);\n \t\tstrbuf_addf(&buf2, \"refs/remotes/%s/%s\", name, master);\n \n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote add\", NULL))\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf,\n+\t\t\t\t\t\"remote add\", NULL, 0))\n \t\t\tresult = error(_(\"Could not setup master '%s'\"), master);\n \t}\n \n@@ -864,7 +865,8 @@ static int mv(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_reset(&buf3);\n \t\tstrbuf_addf(&buf3, \"remote: renamed %s to %s\",\n \t\t\t\titem->string, buf.buf);\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, buf3.buf, NULL))\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf,\n+\t\t\t\t\tbuf3.buf, NULL, 0))\n \t\t\tdie(_(\"creating '%s' failed\"), buf.buf);\n \t\tdisplay_progress(progress, ++refs_renamed_nr);\n \t}\n@@ -1469,7 +1471,8 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\t/* make sure it's valid */\n \t\tif (!refs_ref_exists(refs, buf2.buf))\n \t\t\tresult |= error(_(\"Not a valid ref: %s\"), buf2.buf);\n-\t\telse if (refs_update_symref(refs, buf.buf, buf2.buf, \"remote set-head\", &buf_prev))\n+\t\telse if (refs_update_symref(refs, buf.buf, buf2.buf,\n+\t\t\t\t\t\"remote set-head\", &buf_prev, 0))\n \t\t\tresult |= error(_(\"Could not setup %s\"), buf.buf);\n \t\telse if (opt_a) {\n \t\t\treport_auto(argv[0], head_name, &buf_prev);\ndiff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c\nindex 7728fbc3c1..cfbb53b30c 100644\n--- a/builtin/symbolic-ref.c\n+++ b/builtin/symbolic-ref.c\n@@ -88,7 +88,7 @@ int cmd_symbolic_ref(int argc,\n \t\tif (check_refname_format(argv[1], REFNAME_ALLOW_ONELEVEL) < 0)\n \t\t\tdie(\"Refusing to set '%s' to invalid ref '%s'\", argv[0], argv[1]);\n \t\tret = !!refs_update_symref(get_main_ref_store(the_repository),\n-\t\t\t\t\t   argv[0], argv[1], msg, NULL);\n+\t\t\t\t\t   argv[0], argv[1], msg, NULL, 0);\n \t\tbreak;\n \tdefault:\n \t\tusage_with_options(git_symbolic_ref_usage, options);\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex a7ab4193c1..2e95367235 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -517,7 +517,7 @@ static int add_worktree(const char *path, const char *refname,\n \t\tret = refs_update_ref(wt_refs, NULL, \"HEAD\", &commit->object.oid,\n \t\t\t\t      NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n \telse\n-\t\tret = refs_update_symref(wt_refs, \"HEAD\", symref.buf, NULL, NULL);\n+\t\tret = refs_update_symref(wt_refs, \"HEAD\", symref.buf, NULL, NULL, 0);\n \tif (ret)\n \t\tgoto done;\n \ndiff --git a/refs.c b/refs.c\nindex 91cacee6f9..e23288b817 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2115,29 +2115,43 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct\n \n int refs_update_symref(struct ref_store *refs, const char *ref,\n \t\t       const char *target, const char *logmsg,\n-\t\t       struct strbuf *before_target)\n+\t\t       struct strbuf *before_target, int create_only)\n {\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n \tint ret = 0;\n \n \ttransaction = ref_store_transaction_begin(refs, &err);\n-\tif (!transaction ||\n-\t    ref_transaction_update(transaction, ref, NULL, NULL,\n-\t\t\t\t   target, NULL, REF_NO_DEREF,\n-\t\t\t\t   logmsg, &err) ||\n-\t    ref_transaction_commit(transaction, &err)) {\n+\tif (!transaction) {\n+\terror_return:\n \t\tret = error(\"%s\", err.buf);\n+\t\tgoto cleanup;\n \t}\n+\tif (create_only) {\n+\t\tint create_ret = 0;\n+\t\tif (ref_transaction_create(transaction, ref, NULL, target,\n+\t\t\t\t\t   REF_NO_DEREF, logmsg, &err))\n+\t\t\tgoto error_return;\n+\t\tcreate_ret = ref_transaction_commit(transaction, &err);\n+\t\tif (create_ret && create_ret != TRANSACTION_CREATE_EXISTS)\n+\t\t\tret = error(\"%s\", err.buf);\n \n-\tstrbuf_release(&err);\n+\t} else {\n+\t\tif (ref_transaction_update(transaction, ref, NULL, NULL,\n+\t\t\t\t\t   target, NULL, REF_NO_DEREF,\n+\t\t\t\t\t   logmsg, &err) ||\n+\t\t    ref_transaction_commit(transaction, &err))\n+\t\t\tgoto error_return;\n+\t}\n \n-\tif (transaction && before_target && transaction->updates[0]->before_target)\n-\t\tstrbuf_addstr(before_target, transaction->updates[0]->before_target);\n+cleanup:\n+\tstrbuf_release(&err);\n \n-\tif (transaction)\n+\tif (transaction) {\n+\t\tif (before_target && transaction->updates[0]->before_target)\n+\t\t\tstrbuf_addstr(before_target, transaction->updates[0]->before_target);\n \t\tref_transaction_free(transaction);\n-\n+\t}\n \treturn ret;\n }\n \ndiff --git a/refs.h b/refs.h\nindex 166affbc89..35f5439cd1 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -572,7 +572,7 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,\n \n int refs_update_symref(struct ref_store *refs, const char *refname,\n \t\t       const char *target, const char *logmsg,\n-\t\t       struct strbuf *before_target);\n+\t\t       struct strbuf *before_target, int create_only);\n \n enum action_on_err {\n \tUPDATE_REFS_MSG_ON_ERR,\ndiff --git a/reset.c b/reset.c\nindex cc36a9ed56..919fb84f5f 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -76,7 +76,7 @@ static int update_refs(const struct reset_head_opts *opts,\n \t\tif (!ret)\n \t\t\tret = refs_update_symref(get_main_ref_store(the_repository),\n \t\t\t\t\t\t \"HEAD\", switch_to_branch,\n-\t\t\t\t\t\t reflog_head, NULL);\n+\t\t\t\t\t\t reflog_head, NULL, 0);\n \t}\n \tif (!ret && run_hook)\n \t\trun_hooks_l(the_repository, \"post-checkout\",\ndiff --git a/sequencer.c b/sequencer.c\nindex 23b162924c..103d0ddfb2 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -5107,7 +5107,8 @@ static int pick_commits(struct repository *r,\n \t\t\t}\n \t\t\tmsg = reflog_message(opts, \"finish\", \"returning to %s\",\n \t\t\t\thead_ref.buf);\n-\t\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", head_ref.buf, msg, NULL)) {\n+\t\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", head_ref.buf,\n+\t\t\t\t\t\tmsg, NULL, 0)) {\n \t\t\t\tres = error(_(\"could not update HEAD to %s\"),\n \t\t\t\t\thead_ref.buf);\n \t\t\t\tgoto cleanup_head_ref;\ndiff --git a/setup.c b/setup.c\nindex d95f051465..9bbc655ee4 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -2275,7 +2275,8 @@ void create_reference_database(enum ref_storage_format ref_storage_format,\n \t\t\tdie(_(\"invalid initial branch name: '%s'\"),\n \t\t\t    initial_branch);\n \n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", ref, NULL, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", ref,\n+\t\t\t\t\tNULL, NULL, 0) < 0)\n \t\t\texit(1);\n \t\tfree(ref);\n \t}\ndiff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c\nindex a911302bea..f72b029e51 100644\n--- a/t/helper/test-ref-store.c\n+++ b/t/helper/test-ref-store.c\n@@ -120,7 +120,7 @@ static int cmd_create_symref(struct ref_store *refs, const char **argv)\n \tconst char *target = notnull(*argv++, \"target\");\n \tconst char *logmsg = *argv++;\n \n-\treturn refs_update_symref(refs, refname, target, logmsg, NULL);\n+\treturn refs_update_symref(refs, refname, target, logmsg, NULL, 0);\n }\n \n static struct flag_definition transaction_flags[] = {\n-- \n2.47.0.6.g07cb02250a\n\n"},{"id":"504709","messageId":"xmqqv7y0lzn5.fsf@gitster.g","threadId":"62096","inReplyTo":"xmqqjzehq9qk.fsf@gitster.g","subject":"Re: [PATCH v5 3/6] set-head: better output for --auto","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-10-10T15:57:02Z","receivedAt":"2024-10-10T15:57:05Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Bence Ferdinandy <bence@ferdinandy.com> writes:\n>\n>> +static void report_auto(const char *remote, const char *head_name,\n>> +\t\t\tstruct strbuf *buf_prev) {\n>> +\tstruct strbuf buf_prefix = STRBUF_INIT;\n>> +\tconst char *prev_head;\n>\n> I think we need to initialize prev_head to NULL.\n>\n>> +\tstrbuf_addf(&buf_prefix, \"refs/remotes/%s/\", remote);\n>> +\tskip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);\n>\n> If the symref was not pointing into the \"refs/remotes/<remote>/\"\n> hierarchy previously, skip_prefix() comes back without touching\n> prev_head (i.e. not starting with the prefix does not clear it).\n\nThe two uninitialized prev_head were also noticed by compilers in\nmultiple jobs at GitHub CI.\n\nhttps://github.com/git/git/actions/runs/11265515664\n\n\n"},{"id":"504715","messageId":"ebd5e3d4-7299-4cfa-a209-9399a1cd79fd@ramsayjones.plus.com","threadId":"62096","inReplyTo":"xmqqv7y0lzn5.fsf@gitster.g","subject":"Re: [PATCH v5 3/6] set-head: better output for --auto","fromName":"Ramsay Jones","fromEmail":"ramsay@ramsayjones.plus.com","sentAt":"2024-10-10T16:54:04Z","receivedAt":"2024-10-10T16:57:15Z","isPatch":true,"sender":{"key":"ramsay@ramsayjones.plus.com","avatar":"https://avatars.githubusercontent.com/u/33702710?v=4"},"body":"\n\nOn 10/10/2024 16:57, Junio C Hamano wrote:\n> Junio C Hamano <gitster@pobox.com> writes:\n> \n>> Bence Ferdinandy <bence@ferdinandy.com> writes:\n>>\n>>> +static void report_auto(const char *remote, const char *head_name,\n>>> +\t\t\tstruct strbuf *buf_prev) {\n>>> +\tstruct strbuf buf_prefix = STRBUF_INIT;\n>>> +\tconst char *prev_head;\n>>\n>> I think we need to initialize prev_head to NULL.\n>>\n>>> +\tstrbuf_addf(&buf_prefix, \"refs/remotes/%s/\", remote);\n>>> +\tskip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);\n>>\n>> If the symref was not pointing into the \"refs/remotes/<remote>/\"\n>> hierarchy previously, skip_prefix() comes back without touching\n>> prev_head (i.e. not starting with the prefix does not clear it).\n> \n> The two uninitialized prev_head were also noticed by compilers in\n> multiple jobs at GitHub CI.\n> \n> https://github.com/git/git/actions/runs/11265515664\n\nHeh, I was just about to send a patch to fix up these two warnings\n(errors with -Werror), so that I could build 'seen'. However, it\nseems to be well in hand, so I won't bother! :)\n\nATB,\nRamsay Jones\n\n\n"},{"id":"504735","messageId":"D4SD24J4X5ET.34TQIQDDCX8E@ferdinandy.com","threadId":"62096","inReplyTo":"xmqqv7y0lzn5.fsf@gitster.g","subject":"Re: [PATCH v5 3/6] set-head: better output for --auto","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-10T19:08:23Z","receivedAt":"2024-10-10T19:08:49Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Thu Oct 10, 2024 at 17:57, Junio C Hamano <gitster@pobox.com> wrote:\n> Junio C Hamano <gitster@pobox.com> writes:\n>\n>> Bence Ferdinandy <bence@ferdinandy.com> writes:\n>>\n>>> +static void report_auto(const char *remote, const char *head_name,\n>>> +\t\t\tstruct strbuf *buf_prev) {\n>>> +\tstruct strbuf buf_prefix = STRBUF_INIT;\n>>> +\tconst char *prev_head;\n>>\n>> I think we need to initialize prev_head to NULL.\n>>\n>>> +\tstrbuf_addf(&buf_prefix, \"refs/remotes/%s/\", remote);\n>>> +\tskip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);\n>>\n>> If the symref was not pointing into the \"refs/remotes/<remote>/\"\n>> hierarchy previously, skip_prefix() comes back without touching\n>> prev_head (i.e. not starting with the prefix does not clear it).\n>\n> The two uninitialized prev_head were also noticed by compilers in\n> multiple jobs at GitHub CI.\n>\n> https://github.com/git/git/actions/runs/11265515664\n\nI wonder why that didn't fail the build for me locally. I was running\n\nmake -j8 install PREFIX=$HOME/.local\n\nfor testing. Should I have added a flag or ENV variable somewhere?\n\nAnyhow, the v6 I sent earlier today should fix this (and hopefully all your\nother comments as well).\n\nThanks,\nBence\n\n-- \nbence.ferdinandy.com\n\n"},{"id":"504744","messageId":"CAOLa=ZSV50OkDC2p5AHHW0u+fAtLMcCX36W1SuT8AnTkX7HNZg@mail.gmail.com","threadId":"62096","inReplyTo":"20241010133022.1733542-3-bence@ferdinandy.com","subject":"Re: [PATCH v6 3/6] set-head: better output for --auto","fromName":"karthik nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-10-10T21:05:09Z","receivedAt":"2024-10-10T21:05:11Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Bence Ferdinandy <bence@ferdinandy.com> writes:\n[snip]\n\n> diff --git a/builtin/remote.c b/builtin/remote.c\n> index 353ffd2c43..24f9caf149 100644\n> --- a/builtin/remote.c\n> +++ b/builtin/remote.c\n> @@ -1399,10 +1399,34 @@ static int show(int argc, const char **argv, const char *prefix)\n>  \treturn result;\n>  }\n>\n> +static void report_auto(const char *remote, const char *head_name,\n> +\t\t\tstruct strbuf *buf_prev) {\n\nNit: would be nicer if this was called 'report_set_head_auto'\n\n> +\tstruct strbuf buf_prefix = STRBUF_INIT;\n> +\tconst char *prev_head = NULL;\n> +\n> +\tstrbuf_addf(&buf_prefix, \"refs/remotes/%s/\", remote);\n> +\tskip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);\n> +\n> +\tif (prev_head && !strcmp(prev_head, head_name))\n> +\t\tprintf(\"'%s/HEAD' is unchanged and points to '%s'\\n\",\n> +\t\t\tremote, head_name);\n> +\telse if (prev_head)\n> +\t\tprintf(\"'%s/HEAD' has changed from '%s' and now points to '%s'\\n\",\n> +\t\t\tremote, prev_head, head_name);\n> +\telse if (buf_prev->len == 0)\n\nNit: we tend to do `if (!buf_prev->len)` in this project\n\n> +\t\tprintf(\"'%s/HEAD' is now created and points to '%s'\\n\",\n> +\t\t\tremote, head_name);\n> +\telse\n> +\t\tprintf(\"'%s/HEAD' used to point to '%s' \"\n> +\t\t\t\"(which is unusual), but now points to '%s'\\n\",\n\nIsn't \"which is unusual\" a bit vague? Wouldn't be nicer to say why it is\nunusual?\n\n> +\t\t\tremote, buf_prev->buf, head_name);\n\nLet's clear up buf_prefix by calling `strbuf_release` here.\n\n> +}\n> +\n>  static int set_head(int argc, const char **argv, const char *prefix)\n>  {\n>  \tint i, opt_a = 0, opt_d = 0, result = 0;\n> -\tstruct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;\n> +\tstruct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT,\n> +\t\tbuf_prev = STRBUF_INIT;\n>  \tchar *head_name = NULL;\n>  \tstruct ref_store *refs = get_main_ref_store(the_repository);\n>\n> @@ -1445,15 +1469,17 @@ static int set_head(int argc, const char **argv, const char *prefix)\n>  \t\t/* make sure it's valid */\n>  \t\tif (!refs_ref_exists(refs, buf2.buf))\n>  \t\t\tresult |= error(_(\"Not a valid ref: %s\"), buf2.buf);\n> -\t\telse if (refs_update_symref(refs, buf.buf, buf2.buf, \"remote set-head\", NULL))\n> +\t\telse if (refs_update_symref(refs, buf.buf, buf2.buf, \"remote set-head\", &buf_prev))\n>  \t\t\tresult |= error(_(\"Could not setup %s\"), buf.buf);\n> -\t\telse if (opt_a)\n> -\t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\n> +\t\telse if (opt_a) {\n> +\t\t\treport_auto(argv[0], head_name, &buf_prev);\n> +\t\t}\n>  \t\tfree(head_name);\n>  \t}\n>\n>  \tstrbuf_release(&buf);\n>  \tstrbuf_release(&buf2);\n> +\tstrbuf_release(&buf_prev);\n>  \treturn result;\n>  }\n>\n> diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\n> index 532035933f..77c12b8709 100755\n> --- a/t/t5505-remote.sh\n> +++ b/t/t5505-remote.sh\n> @@ -429,12 +429,51 @@ test_expect_success 'set-head --auto' '\n>  \t)\n>  '\n>\n> +test_expect_success 'set-head --auto detects creation' '\n> +\t(\n> +\t\tcd test &&\n> +\t\trm .git/refs/remotes/origin/HEAD &&\n\ndoes this work with reftables too?\n\n> +\t\tgit remote set-head --auto origin >output &&\n> +\t\techo \"'\\''origin/HEAD'\\'' is now created and points to '\\''main'\\''\" >expect &&\n\nNit: might be cleaner to use `${SQ}` here and below\n\n[snip]\n"},{"id":"504749","messageId":"CAOLa=ZQA6vc246LRxW8n+a5qNDGxsSLUAhT-x-9tdAKDb2LBCQ@mail.gmail.com","threadId":"62096","inReplyTo":"20241010133022.1733542-4-bence@ferdinandy.com","subject":"Re: [PATCH v6 4/6] transaction: add TRANSACTION_CREATE_EXISTS error","fromName":"karthik nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-10-10T21:12:38Z","receivedAt":"2024-10-10T21:12:40Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Bence Ferdinandy <bence@ferdinandy.com> writes:\n\n> transaction: add TRANSACTION_CREATE_EXISTS error\n\nNit: but it would be nice to have s/transaction/refs here and similarly\nin other patches. Phrasing 'Documentation/SubmittingPatches':\n\n  It is also conventional in most cases to prefix the first line with\n  \"area: \" where the area is a filename or identifier for the general\n  area of the code being modified, e.g.\n\n    * doc: clarify distinction between sign-off and pgp-signing\n    * githooks.txt: improve the intro section\n\nSo in this sense, it would be the refs area, no?\n\n> Currently there is only one special error for transaction, for when\n> there is a naming conflict, all other errors are dumped under a generic\n> error. Add a new special error case for when the caller requests the\n> reference to be updated only when it does not yet exist and the\n> reference actually does exist.\n>\n> Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n> ---\n>\n> Notes:\n>     v4: new patch\n>     v5: no change\n>     v6: no change\n>\n>  refs.h                  |  4 +++-\n>  refs/files-backend.c    | 28 ++++++++++++++++++++--------\n>  refs/reftable-backend.c |  6 ++++--\n>  3 files changed, 27 insertions(+), 11 deletions(-)\n>\n> diff --git a/refs.h b/refs.h\n> index f38616db84..166affbc89 100644\n> --- a/refs.h\n> +++ b/refs.h\n> @@ -759,8 +759,10 @@ int ref_transaction_verify(struct ref_transaction *transaction,\n>\n>  /* Naming conflict (for example, the ref names A and A/B conflict). */\n>  #define TRANSACTION_NAME_CONFLICT -1\n> +/* When only creation was requested, but the ref already exists. */\n> +#define TRANSACTION_CREATE_EXISTS -2\n>  /* All other errors. */\n> -#define TRANSACTION_GENERIC_ERROR -2\n> +#define TRANSACTION_GENERIC_ERROR -3\n>\n>  /*\n>   * Perform the preparatory stages of committing `transaction`. Acquire\n> diff --git a/refs/files-backend.c b/refs/files-backend.c\n> index 8415f2d020..272ad81315 100644\n> --- a/refs/files-backend.c\n> +++ b/refs/files-backend.c\n> @@ -2502,14 +2502,18 @@ static int split_symref_update(struct ref_update *update,\n>  static int check_old_oid(struct ref_update *update, struct object_id *oid,\n>  \t\t\t struct strbuf *err)\n>  {\n> +\tint ret = TRANSACTION_GENERIC_ERROR;\n> +\n>  \tif (!(update->flags & REF_HAVE_OLD) ||\n>  \t\t   oideq(oid, &update->old_oid))\n>  \t\treturn 0;\n>\n> -\tif (is_null_oid(&update->old_oid))\n> +\tif (is_null_oid(&update->old_oid)) {\n>  \t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n>  \t\t\t    \"reference already exists\",\n>  \t\t\t    ref_update_original_update_refname(update));\n> +\t\tret = TRANSACTION_CREATE_EXISTS;\n> +\t}\n>  \telse if (is_null_oid(oid))\n>  \t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n>  \t\t\t    \"reference is missing but expected %s\",\n> @@ -2522,7 +2526,7 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid,\n>  \t\t\t    oid_to_hex(oid),\n>  \t\t\t    oid_to_hex(&update->old_oid));\n>\n> -\treturn -1;\n> +\treturn ret;\n>  }\n>\n>  /*\n> @@ -2603,9 +2607,13 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n>  \t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n>  \t\t\t\t\tgoto out;\n>  \t\t\t\t}\n> -\t\t\t} else if  (check_old_oid(update, &lock->old_oid, err)) {\n> -\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n> -\t\t\t\tgoto out;\n> +\t\t\t} else {\n> +\t\t\t\tint checkret;\n> +\t\t\t\tcheckret = check_old_oid(update, &lock->old_oid, err);\n> +\t\t\t\tif  (checkret) {\n> +\t\t\t\t\tret = checkret;\n> +\t\t\t\t\tgoto out;\n> +\t\t\t\t}\n\nCan't we simply do:\n\n  ret = check_old_oid(update, &lock->old_oid, err);\n  if (ret) {\n     goto out\n  }\n\nif ret is '0', it shouldn't matter no?\n\n[snip]\n"},{"id":"504829","messageId":"D4SUTJDV0Y04.3Q8YED2MVOKBL@ferdinandy.com","threadId":"62096","inReplyTo":"CAOLa=ZSV50OkDC2p5AHHW0u+fAtLMcCX36W1SuT8AnTkX7HNZg@mail.gmail.com","subject":"Re: [PATCH v6 3/6] set-head: better output for --auto","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-11T09:03:30Z","receivedAt":"2024-10-11T09:04:09Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Thanks for the very helpful review on the series! Most of it is clear to me and\nI agree with, so for brevity I'll only comment where I have a question/comment.\n\nOn Thu Oct 10, 2024 at 23:05, karthik nayak <karthik.188@gmail.com> wrote:\n[snip]\n\n> > diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\n> > index 532035933f..77c12b8709 100755\n> > --- a/t/t5505-remote.sh\n> > +++ b/t/t5505-remote.sh\n> > @@ -429,12 +429,51 @@ test_expect_success 'set-head --auto' '\n> >  \t)\n> >  '\n> >\n> > +test_expect_success 'set-head --auto detects creation' '\n> > +\t(\n> > +\t\tcd test &&\n> > +\t\trm .git/refs/remotes/origin/HEAD &&\n>\n> does this work with reftables too?\n\nYou got me there, probably not. I'm guessing I should use git update-ref or\nsomething similar to set these values manually instead of editing the file.\n\nOn the other hand, is there a way to force the tests to run on a reftables\nbackend? t/README does not mention anything about this and since in a previous\nround it already came up that I forgot to update the reftables backend, it\nwould be nice if I could actually test everything with that as well.\n\n>\n> > +\t\tgit remote set-head --auto origin >output &&\n> > +\t\techo \"'\\''origin/HEAD'\\'' is now created and points to '\\''main'\\''\" >expect &&\n>\n> Nit: might be cleaner to use `${SQ}` here and below\nYou mean something like this?\n\n\tgit remote set-head --auto origin >output &&\n\tHEAD=\"'\\''origin/HEAD'\\''\" &&\n\techo \"${HEAD} is now created and points to '\\''main'\\''\" >expect &&\n\nI tried a few variations and this is what I could get working, but I may be\nsimply missing something with the backtick.\n\nThanks,\nBence\n\n-- \nbence.ferdinandy.com\n\n"},{"id":"504845","messageId":"xmqqttdiha8x.fsf@gitster.g","threadId":"62096","inReplyTo":"D4SUTJDV0Y04.3Q8YED2MVOKBL@ferdinandy.com","subject":"Re: [PATCH v6 3/6] set-head: better output for --auto","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-10-11T16:31:26Z","receivedAt":"2024-10-11T16:31:30Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Bence Ferdinandy\" <bence@ferdinandy.com> writes:\n\n> Thanks for the very helpful review on the series! Most of it is clear to me and\n> I agree with, so for brevity I'll only comment where I have a question/comment.\n>\n> On Thu Oct 10, 2024 at 23:05, karthik nayak <karthik.188@gmail.com> wrote:\n> [snip]\n>\n>> > diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\n>> > index 532035933f..77c12b8709 100755\n>> > --- a/t/t5505-remote.sh\n>> > +++ b/t/t5505-remote.sh\n>> > @@ -429,12 +429,51 @@ test_expect_success 'set-head --auto' '\n>> >  \t)\n>> >  '\n>> >\n>> > +test_expect_success 'set-head --auto detects creation' '\n>> > +\t(\n>> > +\t\tcd test &&\n>> > +\t\trm .git/refs/remotes/origin/HEAD &&\n>>\n>> does this work with reftables too?\n>\n> You got me there, probably not. I'm guessing I should use git update-ref or\n> something similar to set these values manually instead of editing the file.\n\nYup, \"git update-ref --no-deref -d refs/remotes/origin/HEAD\"\n\n> On the other hand, is there a way to force the tests to run on a reftables\n> backend? t/README does not mention anything about this and since in a previous\n> round it already came up that I forgot to update the reftables backend, it\n> would be nice if I could actually test everything with that as well.\n\n$ git grep GIT_TEST_DEFAULT_REF_FORMAT t/README\n\nThanks.\n"},{"id":"504846","messageId":"xmqqplo6ha4j.fsf@gitster.g","threadId":"62096","inReplyTo":"CAOLa=ZQA6vc246LRxW8n+a5qNDGxsSLUAhT-x-9tdAKDb2LBCQ@mail.gmail.com","subject":"Re: [PATCH v6 4/6] transaction: add TRANSACTION_CREATE_EXISTS error","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-10-11T16:34:04Z","receivedAt":"2024-10-11T16:34:07Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"karthik nayak <karthik.188@gmail.com> writes:\n\n>> +\t\t\t} else {\n>> +\t\t\t\tint checkret;\n>> +\t\t\t\tcheckret = check_old_oid(update, &lock->old_oid, err);\n>> +\t\t\t\tif  (checkret) {\n>> +\t\t\t\t\tret = checkret;\n>> +\t\t\t\t\tgoto out;\n>> +\t\t\t\t}\n>\n> Can't we simply do:\n>\n>   ret = check_old_oid(update, &lock->old_oid, err);\n>   if (ret) {\n>      goto out\n>   }\n>\n> if ret is '0', it shouldn't matter no?\n\nThat's nice.  Yes, as long as \"ret\" has no useful information when\nwe enter this \"else\" block, reusing it like you did is just fine.\n\nThis is one of these moments I tell myself \"oh, why didn't *I* think\nof that\" ;-).\n\nThanks.\n"},{"id":"504878","messageId":"CAOLa=ZQY_a_dEZQ2EcLVfE_5Kjy7b2R3OFbQHQbkzH0o9-q1JA@mail.gmail.com","threadId":"62096","inReplyTo":"D4SUTJDV0Y04.3Q8YED2MVOKBL@ferdinandy.com","subject":"Re: [PATCH v6 3/6] set-head: better output for --auto","fromName":"karthik nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-10-11T20:43:23Z","receivedAt":"2024-10-11T20:43:25Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"\"Bence Ferdinandy\" <bence@ferdinandy.com> writes:\n\n[snip]\n\n>>\n>> > +\t\tgit remote set-head --auto origin >output &&\n>> > +\t\techo \"'\\''origin/HEAD'\\'' is now created and points to '\\''main'\\''\" >expect &&\n>>\n>> Nit: might be cleaner to use `${SQ}` here and below\n> You mean something like this?\n>\n> \tgit remote set-head --auto origin >output &&\n> \tHEAD=\"'\\''origin/HEAD'\\''\" &&\n> \techo \"${HEAD} is now created and points to '\\''main'\\''\" >expect &&\n>\n> I tried a few variations and this is what I could get working, but I may be\n> simply missing something with the backtick.\n\nI mean simply this\n\n    git remote set-head --auto origin >output &&\n    echo \"${SQ}origin/HEAD${SQ} is now created and points to\n${SQ}main${SQ}\" >expect &&\n\n- Karthik\n"},{"id":"504929","messageId":"D4U6L3C2GHZG.2SN8P0C7ZH33M@ferdinandy.com","threadId":"62096","inReplyTo":"CAOLa=ZQY_a_dEZQ2EcLVfE_5Kjy7b2R3OFbQHQbkzH0o9-q1JA@mail.gmail.com","subject":"Re: [PATCH v6 3/6] set-head: better output for --auto","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-12T22:29:21Z","receivedAt":"2024-10-12T22:29:52Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Fri Oct 11, 2024 at 22:43, karthik nayak <karthik.188@gmail.com> wrote:\n> \"Bence Ferdinandy\" <bence@ferdinandy.com> writes:\n>\n> [snip]\n>\n>>>\n>>> > +\t\tgit remote set-head --auto origin >output &&\n>>> > +\t\techo \"'\\''origin/HEAD'\\'' is now created and points to '\\''main'\\''\" >expect &&\n>>>\n>>> Nit: might be cleaner to use `${SQ}` here and below\n>> You mean something like this?\n>>\n>> \tgit remote set-head --auto origin >output &&\n>> \tHEAD=\"'\\''origin/HEAD'\\''\" &&\n>> \techo \"${HEAD} is now created and points to '\\''main'\\''\" >expect &&\n>>\n>> I tried a few variations and this is what I could get working, but I may be\n>> simply missing something with the backtick.\n>\n> I mean simply this\n>\n>     git remote set-head --auto origin >output &&\n>     echo \"${SQ}origin/HEAD${SQ} is now created and points to\n> ${SQ}main${SQ}\" >expect &&\n\nAh, I see in other tests this is used, but not in this particular test file.\nIt's a bit hard to decide which is more cryptic, but ${SQ} is nicer on the\neyes. On the other hand I would either switch the entire file in a separate\npatch or leave in the '\\'' here as well. Or I guess one could go through the\nentire test base and switch everything to either one or the other for\nconsistency.\n"},{"id":"504934","messageId":"20241012230428.3259229-2-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241012230428.3259229-1-bence@ferdinandy.com","subject":"[PATCH v7 2/6] remote set-head: add new variable for readability","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-12T23:03:22Z","receivedAt":"2024-10-12T23:15:40Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Instead of calling get_main_ref_store(the_repository) multiple times,\ncall it once and store in a new refs variable. Although this change\nprobably offers some performance benefits, the main purpose is to\nshorten the line lengths of function calls using this variable for\nbetter readability.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v5: new patch (split from the next patch as a preparatory step)\n    \n    v6: no change\n    \n    v7: - change commit prefix to be more in line with project standards\n\n builtin/remote.c | 7 ++++---\n 1 file changed, 4 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex d8ff440027..353ffd2c43 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1404,6 +1404,7 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \tint i, opt_a = 0, opt_d = 0, result = 0;\n \tstruct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;\n \tchar *head_name = NULL;\n+\tstruct ref_store *refs = get_main_ref_store(the_repository);\n \n \tstruct option options[] = {\n \t\tOPT_BOOL('a', \"auto\", &opt_a,\n@@ -1434,7 +1435,7 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\t\thead_name = xstrdup(states.heads.items[0].string);\n \t\tfree_remote_ref_states(&states);\n \t} else if (opt_d && !opt_a && argc == 1) {\n-\t\tif (refs_delete_ref(get_main_ref_store(the_repository), NULL, buf.buf, NULL, REF_NO_DEREF))\n+\t\tif (refs_delete_ref(refs, NULL, buf.buf, NULL, REF_NO_DEREF))\n \t\t\tresult |= error(_(\"Could not delete %s\"), buf.buf);\n \t} else\n \t\tusage_with_options(builtin_remote_sethead_usage, options);\n@@ -1442,9 +1443,9 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \tif (head_name) {\n \t\tstrbuf_addf(&buf2, \"refs/remotes/%s/%s\", argv[0], head_name);\n \t\t/* make sure it's valid */\n-\t\tif (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))\n+\t\tif (!refs_ref_exists(refs, buf2.buf))\n \t\t\tresult |= error(_(\"Not a valid ref: %s\"), buf2.buf);\n-\t\telse if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote set-head\", NULL))\n+\t\telse if (refs_update_symref(refs, buf.buf, buf2.buf, \"remote set-head\", NULL))\n \t\t\tresult |= error(_(\"Could not setup %s\"), buf.buf);\n \t\telse if (opt_a)\n \t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\n-- \n2.47.0.7.g072c39eddb.dirty\n\n"},{"id":"504935","messageId":"20241012230428.3259229-1-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241010133022.1733542-1-bence@ferdinandy.com","subject":"[PATCH v7 1/6] refs: atomically record overwritten ref in update_symref","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-12T23:03:21Z","receivedAt":"2024-10-12T23:15:44Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"When updating a symref it's currently not possible to know for sure what\nwas the previous value that was overwritten. Record the value after the\nref has been locked if the caller of refs_update_symref requests it via\na new variable in the function call.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v4: new patch\n    \n    v5: - added before_target to reftables backend\n        - added an extra safety check for transaction's existence in refs.c\n    \n    v6: - no change\n    \n    v7: - remove the whole before_target concept from the backends and\n          handle checking it in refs.c instead (thanks Karthik)\n        - rename the before_target to referent which is how the same concept\n          is called in the backends\n        - change commit prefix to be more in line with project standards\n\n builtin/branch.c          |  2 +-\n builtin/checkout.c        |  4 ++--\n builtin/clone.c           |  6 +++---\n builtin/notes.c           |  2 +-\n builtin/remote.c          |  6 +++---\n builtin/symbolic-ref.c    |  2 +-\n builtin/worktree.c        |  2 +-\n refs.c                    | 16 +++++++++++-----\n refs.h                    |  3 ++-\n reset.c                   |  2 +-\n sequencer.c               |  2 +-\n setup.c                   |  2 +-\n t/helper/test-ref-store.c |  2 +-\n 13 files changed, 29 insertions(+), 22 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex fd1611ebf5..6c87690b58 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -559,7 +559,7 @@ static int replace_each_worktree_head_symref(struct worktree **worktrees,\n \t\t\tcontinue;\n \n \t\trefs = get_worktree_ref_store(worktrees[i]);\n-\t\tif (refs_update_symref(refs, \"HEAD\", newref, logmsg))\n+\t\tif (refs_update_symref(refs, \"HEAD\", newref, logmsg, NULL))\n \t\t\tret = error(_(\"HEAD of working tree %s is not updated\"),\n \t\t\t\t    worktrees[i]->path);\n \t}\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 9c30000d3a..356ee9bcde 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1015,7 +1015,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\t\tdescribe_detached_head(_(\"HEAD is now at\"), new_branch_info->commit);\n \t\t}\n \t} else if (new_branch_info->path) {\t/* Switch branches. */\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", new_branch_info->path, msg.buf) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", new_branch_info->path, msg.buf, NULL) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t\tif (!opts->quiet) {\n \t\t\tif (old_branch_info->path && !strcmp(new_branch_info->path, old_branch_info->path)) {\n@@ -1479,7 +1479,7 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts)\n \t\tdie(_(\"You are on a branch yet to be born\"));\n \tstrbuf_addf(&branch_ref, \"refs/heads/%s\", opts->new_branch);\n \tstatus = refs_update_symref(get_main_ref_store(the_repository),\n-\t\t\t\t    \"HEAD\", branch_ref.buf, \"checkout -b\");\n+\t\t\t\t    \"HEAD\", branch_ref.buf, \"checkout -b\", NULL);\n \tstrbuf_release(&branch_ref);\n \tif (!opts->quiet)\n \t\tfprintf(stderr, _(\"Switched to a new branch '%s'\\n\"),\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex e77339c847..ead2af20ea 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -661,7 +661,7 @@ static void update_remote_refs(const struct ref *refs,\n \t\tstrbuf_addstr(&head_ref, \"HEAD\");\n \t\tif (refs_update_symref(get_main_ref_store(the_repository), head_ref.buf,\n \t\t\t\t       remote_head_points_at->peer_ref->name,\n-\t\t\t\t       msg) < 0)\n+\t\t\t\t       msg, NULL) < 0)\n \t\t\tdie(_(\"unable to update %s\"), head_ref.buf);\n \t\tstrbuf_release(&head_ref);\n \t}\n@@ -673,7 +673,7 @@ static void update_head(const struct ref *our, const struct ref *remote,\n \tconst char *head;\n \tif (our && skip_prefix(our->name, \"refs/heads/\", &head)) {\n \t\t/* Local default branch link */\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", our->name, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", our->name, NULL, NULL) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t\tif (!option_bare) {\n \t\t\trefs_update_ref(get_main_ref_store(the_repository),\n@@ -702,7 +702,7 @@ static void update_head(const struct ref *our, const struct ref *remote,\n \t\t * Unborn head from remote; same as \"our\" case above except\n \t\t * that we have no ref to update.\n \t\t */\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", unborn, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", unborn, NULL, NULL) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t\tif (!option_bare)\n \t\t\tinstall_branch_config(0, head, remote_name, unborn);\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex 8c26e45526..ba646f06ff 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -980,7 +980,7 @@ static int merge(int argc, const char **argv, const char *prefix)\n \t\t\tdie(_(\"a notes merge into %s is already in-progress at %s\"),\n \t\t\t    notes_ref, wt->path);\n \t\tfree_worktrees(worktrees);\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"NOTES_MERGE_REF\", notes_ref, NULL))\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"NOTES_MERGE_REF\", notes_ref, NULL, NULL))\n \t\t\tdie(_(\"failed to store link to current notes ref (%s)\"),\n \t\t\t    notes_ref);\n \t\tfprintf(stderr, _(\"Automatic notes merge failed. Fix conflicts in %s \"\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 76670ddd8b..d8ff440027 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -244,7 +244,7 @@ static int add(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_reset(&buf2);\n \t\tstrbuf_addf(&buf2, \"refs/remotes/%s/%s\", name, master);\n \n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote add\"))\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote add\", NULL))\n \t\t\tresult = error(_(\"Could not setup master '%s'\"), master);\n \t}\n \n@@ -864,7 +864,7 @@ static int mv(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_reset(&buf3);\n \t\tstrbuf_addf(&buf3, \"remote: renamed %s to %s\",\n \t\t\t\titem->string, buf.buf);\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, buf3.buf))\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, buf3.buf, NULL))\n \t\t\tdie(_(\"creating '%s' failed\"), buf.buf);\n \t\tdisplay_progress(progress, ++refs_renamed_nr);\n \t}\n@@ -1444,7 +1444,7 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\t/* make sure it's valid */\n \t\tif (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))\n \t\t\tresult |= error(_(\"Not a valid ref: %s\"), buf2.buf);\n-\t\telse if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote set-head\"))\n+\t\telse if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote set-head\", NULL))\n \t\t\tresult |= error(_(\"Could not setup %s\"), buf.buf);\n \t\telse if (opt_a)\n \t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\ndiff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c\nindex 299d23d76a..7728fbc3c1 100644\n--- a/builtin/symbolic-ref.c\n+++ b/builtin/symbolic-ref.c\n@@ -88,7 +88,7 @@ int cmd_symbolic_ref(int argc,\n \t\tif (check_refname_format(argv[1], REFNAME_ALLOW_ONELEVEL) < 0)\n \t\t\tdie(\"Refusing to set '%s' to invalid ref '%s'\", argv[0], argv[1]);\n \t\tret = !!refs_update_symref(get_main_ref_store(the_repository),\n-\t\t\t\t\t   argv[0], argv[1], msg);\n+\t\t\t\t\t   argv[0], argv[1], msg, NULL);\n \t\tbreak;\n \tdefault:\n \t\tusage_with_options(git_symbolic_ref_usage, options);\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex fc31d072a6..a7ab4193c1 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -517,7 +517,7 @@ static int add_worktree(const char *path, const char *refname,\n \t\tret = refs_update_ref(wt_refs, NULL, \"HEAD\", &commit->object.oid,\n \t\t\t\t      NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n \telse\n-\t\tret = refs_update_symref(wt_refs, \"HEAD\", symref.buf, NULL);\n+\t\tret = refs_update_symref(wt_refs, \"HEAD\", symref.buf, NULL, NULL);\n \tif (ret)\n \t\tgoto done;\n \ndiff --git a/refs.c b/refs.c\nindex 5f729ed412..b964ac44d0 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2114,7 +2114,8 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct\n }\n \n int refs_update_symref(struct ref_store *refs, const char *ref,\n-\t\t       const char *target, const char *logmsg)\n+\t\t       const char *target, const char *logmsg,\n+\t\t       struct strbuf *referent)\n {\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n@@ -2122,17 +2123,23 @@ int refs_update_symref(struct ref_store *refs, const char *ref,\n \n \ttransaction = ref_store_transaction_begin(refs, &err);\n \tif (!transaction ||\n-\t    ref_transaction_update(transaction, ref, NULL, NULL,\n+\t\tref_transaction_update(transaction, ref, NULL, NULL,\n \t\t\t\t   target, NULL, REF_NO_DEREF,\n \t\t\t\t   logmsg, &err) ||\n-\t    ref_transaction_commit(transaction, &err)) {\n+\t\tref_transaction_prepare(transaction, &err)) {\n \t\tret = error(\"%s\", err.buf);\n+\t\tgoto cleanup;\n \t}\n+\tif (referent)\n+\t\trefs_read_symbolic_ref(refs, ref, referent);\n+\n+\tif (ref_transaction_commit(transaction, &err))\n+\t\tret = error(\"%s\", err.buf);\n \n+cleanup:\n \tstrbuf_release(&err);\n \tif (transaction)\n \t\tref_transaction_free(transaction);\n-\n \treturn ret;\n }\n \n@@ -2948,4 +2955,3 @@ int ref_update_expects_existing_old_ref(struct ref_update *update)\n \treturn (update->flags & REF_HAVE_OLD) &&\n \t\t(!is_null_oid(&update->old_oid) || update->old_target);\n }\n-\ndiff --git a/refs.h b/refs.h\nindex 108dfc93b3..b09a3a4384 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -571,7 +571,8 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,\n \t\t    const char *newref, const char *logmsg);\n \n int refs_update_symref(struct ref_store *refs, const char *refname,\n-\t\t       const char *target, const char *logmsg);\n+\t\t       const char *target, const char *logmsg,\n+\t\t       struct strbuf *referent);\n \n enum action_on_err {\n \tUPDATE_REFS_MSG_ON_ERR,\ndiff --git a/reset.c b/reset.c\nindex b22b1be792..cc36a9ed56 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -76,7 +76,7 @@ static int update_refs(const struct reset_head_opts *opts,\n \t\tif (!ret)\n \t\t\tret = refs_update_symref(get_main_ref_store(the_repository),\n \t\t\t\t\t\t \"HEAD\", switch_to_branch,\n-\t\t\t\t\t\t reflog_head);\n+\t\t\t\t\t\t reflog_head, NULL);\n \t}\n \tif (!ret && run_hook)\n \t\trun_hooks_l(the_repository, \"post-checkout\",\ndiff --git a/sequencer.c b/sequencer.c\nindex 8d01cd50ac..23b162924c 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -5107,7 +5107,7 @@ static int pick_commits(struct repository *r,\n \t\t\t}\n \t\t\tmsg = reflog_message(opts, \"finish\", \"returning to %s\",\n \t\t\t\thead_ref.buf);\n-\t\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", head_ref.buf, msg)) {\n+\t\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", head_ref.buf, msg, NULL)) {\n \t\t\t\tres = error(_(\"could not update HEAD to %s\"),\n \t\t\t\t\thead_ref.buf);\n \t\t\t\tgoto cleanup_head_ref;\ndiff --git a/setup.c b/setup.c\nindex 94e79b2e48..d95f051465 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -2275,7 +2275,7 @@ void create_reference_database(enum ref_storage_format ref_storage_format,\n \t\t\tdie(_(\"invalid initial branch name: '%s'\"),\n \t\t\t    initial_branch);\n \n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", ref, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", ref, NULL, NULL) < 0)\n \t\t\texit(1);\n \t\tfree(ref);\n \t}\ndiff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c\nindex 65346dee55..a911302bea 100644\n--- a/t/helper/test-ref-store.c\n+++ b/t/helper/test-ref-store.c\n@@ -120,7 +120,7 @@ static int cmd_create_symref(struct ref_store *refs, const char **argv)\n \tconst char *target = notnull(*argv++, \"target\");\n \tconst char *logmsg = *argv++;\n \n-\treturn refs_update_symref(refs, refname, target, logmsg);\n+\treturn refs_update_symref(refs, refname, target, logmsg, NULL);\n }\n \n static struct flag_definition transaction_flags[] = {\n-- \n2.47.0.7.g072c39eddb.dirty\n\n"},{"id":"504936","messageId":"20241012230428.3259229-4-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241012230428.3259229-1-bence@ferdinandy.com","subject":"[PATCH v7 4/6] refs: add TRANSACTION_CREATE_EXISTS error","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-12T23:03:24Z","receivedAt":"2024-10-12T23:15:45Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Currently there is only one special error for transaction, for when\nthere is a naming conflict, all other errors are dumped under a generic\nerror. Add a new special error case for when the caller requests the\nreference to be updated only when it does not yet exist and the\nreference actually does exist.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v4: new patch\n    v5: no change\n    v6: no change\n    \n    v7: - change commit prefix to be more in line with project standards\n        - changed error checking to Karthik's suggestion\n\n refs.h                  |  4 +++-\n refs/files-backend.c    | 24 ++++++++++++++++--------\n refs/reftable-backend.c |  6 ++++--\n 3 files changed, 23 insertions(+), 11 deletions(-)\n\ndiff --git a/refs.h b/refs.h\nindex b09a3a4384..c83b1ec76e 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -759,8 +759,10 @@ int ref_transaction_verify(struct ref_transaction *transaction,\n \n /* Naming conflict (for example, the ref names A and A/B conflict). */\n #define TRANSACTION_NAME_CONFLICT -1\n+/* When only creation was requested, but the ref already exists. */\n+#define TRANSACTION_CREATE_EXISTS -2\n /* All other errors. */\n-#define TRANSACTION_GENERIC_ERROR -2\n+#define TRANSACTION_GENERIC_ERROR -3\n \n /*\n  * Perform the preparatory stages of committing `transaction`. Acquire\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 0824c0b8a9..e743ec44b5 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2502,14 +2502,18 @@ static int split_symref_update(struct ref_update *update,\n static int check_old_oid(struct ref_update *update, struct object_id *oid,\n \t\t\t struct strbuf *err)\n {\n+\tint ret = TRANSACTION_GENERIC_ERROR;\n+\n \tif (!(update->flags & REF_HAVE_OLD) ||\n \t\t   oideq(oid, &update->old_oid))\n \t\treturn 0;\n \n-\tif (is_null_oid(&update->old_oid))\n+\tif (is_null_oid(&update->old_oid)) {\n \t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n \t\t\t    \"reference already exists\",\n \t\t\t    ref_update_original_update_refname(update));\n+\t\tret = TRANSACTION_CREATE_EXISTS;\n+\t}\n \telse if (is_null_oid(oid))\n \t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n \t\t\t    \"reference is missing but expected %s\",\n@@ -2522,7 +2526,7 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid,\n \t\t\t    oid_to_hex(oid),\n \t\t\t    oid_to_hex(&update->old_oid));\n \n-\treturn -1;\n+\treturn ret;\n }\n \n /*\n@@ -2602,9 +2606,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\t\tgoto out;\n \t\t\t\t}\n-\t\t\t} else if  (check_old_oid(update, &lock->old_oid, err)) {\n-\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n-\t\t\t\tgoto out;\n+\t\t\t} else {\n+\t\t\t\tret = check_old_oid(update, &lock->old_oid, err);\n+\t\t\t\tif  (ret) {\n+\t\t\t\t\tgoto out;\n+\t\t\t\t}\n \t\t\t}\n \t\t} else {\n \t\t\t/*\n@@ -2635,9 +2641,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\t\t    update->old_target);\n \t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\tgoto out;\n-\t\t} else if  (check_old_oid(update, &lock->old_oid, err)) {\n-\t\t\tret = TRANSACTION_GENERIC_ERROR;\n-\t\t\tgoto out;\n+\t\t} else {\n+\t\t\tret = check_old_oid(update, &lock->old_oid, err);\n+\t\t\tif  (ret) {\n+\t\t\t\tgoto out;\n+\t\t\t}\n \t\t}\n \n \t\t/*\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 3c96fbf66f..ebf8e57fbc 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1206,10 +1206,13 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\tgoto done;\n \t\t\t}\n \t\t} else if ((u->flags & REF_HAVE_OLD) && !oideq(&current_oid, &u->old_oid)) {\n-\t\t\tif (is_null_oid(&u->old_oid))\n+\t\t\tret = TRANSACTION_NAME_CONFLICT;\n+\t\t\tif (is_null_oid(&u->old_oid)) {\n \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n \t\t\t\t\t\t   \"reference already exists\"),\n \t\t\t\t\t    ref_update_original_update_refname(u));\n+\t\t\t\tret = TRANSACTION_CREATE_EXISTS;\n+\t\t\t}\n \t\t\telse if (is_null_oid(&current_oid))\n \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n \t\t\t\t\t\t   \"reference is missing but expected %s\"),\n@@ -1221,7 +1224,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\t\t    ref_update_original_update_refname(u),\n \t\t\t\t\t    oid_to_hex(&current_oid),\n \t\t\t\t\t    oid_to_hex(&u->old_oid));\n-\t\t\tret = -1;\n \t\t\tgoto done;\n \t\t}\n \n-- \n2.47.0.7.g072c39eddb.dirty\n\n"},{"id":"504937","messageId":"20241012230428.3259229-3-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241012230428.3259229-1-bence@ferdinandy.com","subject":"[PATCH v7 3/6] remote set-head: better output for --auto","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-12T23:03:23Z","receivedAt":"2024-10-12T23:15:49Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Currently, set-head --auto will print a message saying \"remote/HEAD set\nto branch\", which implies something was changed.\n\nChange the output of --auto, so the output actually reflects what was\ndone: a) set a previously unset HEAD, b) change HEAD because remote\nchanged or c) no updates. As a fourth output, if HEAD is changed from\na previous value that was not a remote branch, explicitly call attention\nto this fact.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v1-v2:\n    \n        was RFC in https://lore.kernel.org/git/20240910203835.2288291-1-bence@ferdinandy.com/\n    \n    v3:\n    \n        This patch was originally sent along when I thought set-head was\n        going to be invoked by fetch, but the discussion on the RFC\n        concluded that it should be not. This opened the possibility to make\n        it more explicit.\n    \n        Note: although I feel both things the patch does are really just\n        cosmetic, an argument could be made for breaking it into two, one\n        for the no-op part and one for the --auto print update.\n    \n        Was sent in:\n        https://lore.kernel.org/git/20240915221055.904107-1-bence@ferdinandy.com/\n    \n    v4:\n        - changes are now handled atomically via the ref update transaction\n        - outputs have changed along the lines of Junio's suggestion\n        - minor refactor to set_head for improved legibility\n    \n    v5: - the minor refactor has been split out into its own patch\n    \n    v6: - fixed uninitialized prev_head\n        - fixed case of unusual previous target\n        - fixed a test that would have been actually broken at this patch\n          (the output was only correct with the later update to fetch)\n        - added 4 tests for the 4 output cases\n    \n    v7: - change commit prefix to be more in line with project standards\n        - fixed tests to also work with the reftable backend\n        - renamed report function, fixed style issue with checking buf len\n        - fixed not releasing an strbuf\n\n builtin/remote.c  | 35 ++++++++++++++++++++++++++++----\n t/t5505-remote.sh | 51 ++++++++++++++++++++++++++++++++++++++++++++++-\n 2 files changed, 81 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 353ffd2c43..2b6948439f 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1399,10 +1399,35 @@ static int show(int argc, const char **argv, const char *prefix)\n \treturn result;\n }\n \n+static void report_set_head_auto(const char *remote, const char *head_name,\n+\t\t\tstruct strbuf *buf_prev) {\n+\tstruct strbuf buf_prefix = STRBUF_INIT;\n+\tconst char *prev_head = NULL;\n+\n+\tstrbuf_addf(&buf_prefix, \"refs/remotes/%s/\", remote);\n+\tskip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);\n+\n+\tif (prev_head && !strcmp(prev_head, head_name))\n+\t\tprintf(\"'%s/HEAD' is unchanged and points to '%s'\\n\",\n+\t\t\tremote, head_name);\n+\telse if (prev_head)\n+\t\tprintf(\"'%s/HEAD' has changed from '%s' and now points to '%s'\\n\",\n+\t\t\tremote, prev_head, head_name);\n+\telse if (!buf_prev->len)\n+\t\tprintf(\"'%s/HEAD' is now created and points to '%s'\\n\",\n+\t\t\tremote, head_name);\n+\telse\n+\t\tprintf(\"'%s/HEAD' used to point to '%s' \"\n+\t\t\t\"(which is not a remote branch), but now points to '%s'\\n\",\n+\t\t\tremote, buf_prev->buf, head_name);\n+\tstrbuf_release(&buf_prefix);\n+}\n+\n static int set_head(int argc, const char **argv, const char *prefix)\n {\n \tint i, opt_a = 0, opt_d = 0, result = 0;\n-\tstruct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;\n+\tstruct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT,\n+\t\tbuf_prev = STRBUF_INIT;\n \tchar *head_name = NULL;\n \tstruct ref_store *refs = get_main_ref_store(the_repository);\n \n@@ -1445,15 +1470,17 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\t/* make sure it's valid */\n \t\tif (!refs_ref_exists(refs, buf2.buf))\n \t\t\tresult |= error(_(\"Not a valid ref: %s\"), buf2.buf);\n-\t\telse if (refs_update_symref(refs, buf.buf, buf2.buf, \"remote set-head\", NULL))\n+\t\telse if (refs_update_symref(refs, buf.buf, buf2.buf, \"remote set-head\", &buf_prev))\n \t\t\tresult |= error(_(\"Could not setup %s\"), buf.buf);\n-\t\telse if (opt_a)\n-\t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\n+\t\telse if (opt_a) {\n+\t\t\treport_set_head_auto(argv[0], head_name, &buf_prev);\n+\t\t}\n \t\tfree(head_name);\n \t}\n \n \tstrbuf_release(&buf);\n \tstrbuf_release(&buf2);\n+\tstrbuf_release(&buf_prev);\n \treturn result;\n }\n \ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex 532035933f..d99cd1d0aa 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -429,12 +429,51 @@ test_expect_success 'set-head --auto' '\n \t)\n '\n \n+test_expect_success 'set-head --auto detects creation' '\n+\t(\n+\t\tcd test &&\n+\t\tgit symbolic-ref -d refs/remotes/origin/HEAD &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"'\\''origin/HEAD'\\'' is now created and points to '\\''main'\\''\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n+test_expect_success 'set-head --auto detects no change' '\n+\t(\n+\t\tcd test &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"'\\''origin/HEAD'\\'' is unchanged and points to '\\''main'\\''\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n+test_expect_success 'set-head --auto detects change' '\n+\t(\n+\t\tcd test &&\n+\t\tgit symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/ahead &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"'\\''origin/HEAD'\\'' has changed from '\\''ahead'\\'' and now points to '\\''main'\\''\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n+test_expect_success 'set-head --auto detects strange ref' '\n+\t(\n+\t\tcd test &&\n+\t\tgit symbolic-ref refs/remotes/origin/HEAD refs/heads/main &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"'\\''origin/HEAD'\\'' used to point to '\\''refs/heads/main'\\'' (which is not a remote branch), but now points to '\\''main'\\''\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n test_expect_success 'set-head --auto has no problem w/multiple HEADs' '\n \t(\n \t\tcd test &&\n \t\tgit fetch two \"refs/heads/*:refs/remotes/two/*\" &&\n \t\tgit remote set-head --auto two >output 2>&1 &&\n-\t\techo \"two/HEAD set to main\" >expect &&\n+\t\techo \"'\\''two/HEAD'\\'' is now created and points to '\\''main'\\''\" >expect &&\n \t\ttest_cmp expect output\n \t)\n '\n@@ -453,6 +492,16 @@ test_expect_success 'set-head explicit' '\n \t)\n '\n \n+test_expect_success 'set-head --auto reports change' '\n+\t(\n+\t\tcd test &&\n+\t\tgit remote set-head origin side2 &&\n+\t\tgit remote set-head --auto origin >output 2>&1 &&\n+\t\techo \"'\\''origin/HEAD'\\'' has changed from '\\''side2'\\'' and now points to '\\''main'\\''\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n cat >test/expect <<EOF\n Pruning origin\n URL: $(pwd)/one\n-- \n2.47.0.7.g072c39eddb.dirty\n\n"},{"id":"504938","messageId":"20241012230428.3259229-5-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241012230428.3259229-1-bence@ferdinandy.com","subject":"[PATCH v7 5/6] refs: add create_only option to refs_update_symref","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-12T23:03:25Z","receivedAt":"2024-10-12T23:15:58Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Allow the caller to specify that it only wants to update the symref if\nit does not already exist. Silently ignore the error from the\ntransaction API if the symref already exists.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v4: new patch\n    v5: no change\n    \n    v6: - switched from bool to int for create_only\n        - refactored logic in refs_update_symref with goto as layed out by\n          Junio\n    \n    v7: - change commit prefix to be more in line with project standards\n        - refactored code to accommodate changes in the first patch, but\n          otherwise no change\n\n builtin/branch.c          |  2 +-\n builtin/checkout.c        |  5 +++--\n builtin/clone.c           |  8 +++++---\n builtin/notes.c           |  3 ++-\n builtin/remote.c          |  9 ++++++---\n builtin/symbolic-ref.c    |  2 +-\n builtin/worktree.c        |  2 +-\n refs.c                    | 30 ++++++++++++++++++++++--------\n refs.h                    |  2 +-\n reset.c                   |  2 +-\n sequencer.c               |  3 ++-\n setup.c                   |  3 ++-\n t/helper/test-ref-store.c |  2 +-\n 13 files changed, 48 insertions(+), 25 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 6c87690b58..2fcbcbfd59 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -559,7 +559,7 @@ static int replace_each_worktree_head_symref(struct worktree **worktrees,\n \t\t\tcontinue;\n \n \t\trefs = get_worktree_ref_store(worktrees[i]);\n-\t\tif (refs_update_symref(refs, \"HEAD\", newref, logmsg, NULL))\n+\t\tif (refs_update_symref(refs, \"HEAD\", newref, logmsg, NULL, 0))\n \t\t\tret = error(_(\"HEAD of working tree %s is not updated\"),\n \t\t\t\t    worktrees[i]->path);\n \t}\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 356ee9bcde..9abb71dc07 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1015,7 +1015,8 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\t\tdescribe_detached_head(_(\"HEAD is now at\"), new_branch_info->commit);\n \t\t}\n \t} else if (new_branch_info->path) {\t/* Switch branches. */\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", new_branch_info->path, msg.buf, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", new_branch_info->path,\n+\t\t\t\t\tmsg.buf, NULL, 0) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t\tif (!opts->quiet) {\n \t\t\tif (old_branch_info->path && !strcmp(new_branch_info->path, old_branch_info->path)) {\n@@ -1479,7 +1480,7 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts)\n \t\tdie(_(\"You are on a branch yet to be born\"));\n \tstrbuf_addf(&branch_ref, \"refs/heads/%s\", opts->new_branch);\n \tstatus = refs_update_symref(get_main_ref_store(the_repository),\n-\t\t\t\t    \"HEAD\", branch_ref.buf, \"checkout -b\", NULL);\n+\t\t\t\t    \"HEAD\", branch_ref.buf, \"checkout -b\", NULL, 0);\n \tstrbuf_release(&branch_ref);\n \tif (!opts->quiet)\n \t\tfprintf(stderr, _(\"Switched to a new branch '%s'\\n\"),\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex ead2af20ea..87e8f1421a 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -661,7 +661,7 @@ static void update_remote_refs(const struct ref *refs,\n \t\tstrbuf_addstr(&head_ref, \"HEAD\");\n \t\tif (refs_update_symref(get_main_ref_store(the_repository), head_ref.buf,\n \t\t\t\t       remote_head_points_at->peer_ref->name,\n-\t\t\t\t       msg, NULL) < 0)\n+\t\t\t\t       msg, NULL, 0) < 0)\n \t\t\tdie(_(\"unable to update %s\"), head_ref.buf);\n \t\tstrbuf_release(&head_ref);\n \t}\n@@ -673,7 +673,8 @@ static void update_head(const struct ref *our, const struct ref *remote,\n \tconst char *head;\n \tif (our && skip_prefix(our->name, \"refs/heads/\", &head)) {\n \t\t/* Local default branch link */\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", our->name, NULL, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", our->name,\n+\t\t\t\t\tNULL, NULL, 0) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t\tif (!option_bare) {\n \t\t\trefs_update_ref(get_main_ref_store(the_repository),\n@@ -702,7 +703,8 @@ static void update_head(const struct ref *our, const struct ref *remote,\n \t\t * Unborn head from remote; same as \"our\" case above except\n \t\t * that we have no ref to update.\n \t\t */\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", unborn, NULL, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", unborn,\n+\t\t\t\t\tNULL, NULL, 0) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t\tif (!option_bare)\n \t\t\tinstall_branch_config(0, head, remote_name, unborn);\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex ba646f06ff..7b3138e3c3 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -980,7 +980,8 @@ static int merge(int argc, const char **argv, const char *prefix)\n \t\t\tdie(_(\"a notes merge into %s is already in-progress at %s\"),\n \t\t\t    notes_ref, wt->path);\n \t\tfree_worktrees(worktrees);\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"NOTES_MERGE_REF\", notes_ref, NULL, NULL))\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"NOTES_MERGE_REF\", notes_ref,\n+\t\t\t\t\tNULL, NULL, 0))\n \t\t\tdie(_(\"failed to store link to current notes ref (%s)\"),\n \t\t\t    notes_ref);\n \t\tfprintf(stderr, _(\"Automatic notes merge failed. Fix conflicts in %s \"\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 2b6948439f..d60ec6e907 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -244,7 +244,8 @@ static int add(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_reset(&buf2);\n \t\tstrbuf_addf(&buf2, \"refs/remotes/%s/%s\", name, master);\n \n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote add\", NULL))\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf,\n+\t\t\t\t\t\"remote add\", NULL, 0))\n \t\t\tresult = error(_(\"Could not setup master '%s'\"), master);\n \t}\n \n@@ -864,7 +865,8 @@ static int mv(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_reset(&buf3);\n \t\tstrbuf_addf(&buf3, \"remote: renamed %s to %s\",\n \t\t\t\titem->string, buf.buf);\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, buf3.buf, NULL))\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf,\n+\t\t\t\t\tbuf3.buf, NULL, 0))\n \t\t\tdie(_(\"creating '%s' failed\"), buf.buf);\n \t\tdisplay_progress(progress, ++refs_renamed_nr);\n \t}\n@@ -1470,7 +1472,8 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\t/* make sure it's valid */\n \t\tif (!refs_ref_exists(refs, buf2.buf))\n \t\t\tresult |= error(_(\"Not a valid ref: %s\"), buf2.buf);\n-\t\telse if (refs_update_symref(refs, buf.buf, buf2.buf, \"remote set-head\", &buf_prev))\n+\t\telse if (refs_update_symref(refs, buf.buf, buf2.buf,\n+\t\t\t\t\t\"remote set-head\", &buf_prev, 0))\n \t\t\tresult |= error(_(\"Could not setup %s\"), buf.buf);\n \t\telse if (opt_a) {\n \t\t\treport_set_head_auto(argv[0], head_name, &buf_prev);\ndiff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c\nindex 7728fbc3c1..cfbb53b30c 100644\n--- a/builtin/symbolic-ref.c\n+++ b/builtin/symbolic-ref.c\n@@ -88,7 +88,7 @@ int cmd_symbolic_ref(int argc,\n \t\tif (check_refname_format(argv[1], REFNAME_ALLOW_ONELEVEL) < 0)\n \t\t\tdie(\"Refusing to set '%s' to invalid ref '%s'\", argv[0], argv[1]);\n \t\tret = !!refs_update_symref(get_main_ref_store(the_repository),\n-\t\t\t\t\t   argv[0], argv[1], msg, NULL);\n+\t\t\t\t\t   argv[0], argv[1], msg, NULL, 0);\n \t\tbreak;\n \tdefault:\n \t\tusage_with_options(git_symbolic_ref_usage, options);\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex a7ab4193c1..2e95367235 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -517,7 +517,7 @@ static int add_worktree(const char *path, const char *refname,\n \t\tret = refs_update_ref(wt_refs, NULL, \"HEAD\", &commit->object.oid,\n \t\t\t\t      NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n \telse\n-\t\tret = refs_update_symref(wt_refs, \"HEAD\", symref.buf, NULL, NULL);\n+\t\tret = refs_update_symref(wt_refs, \"HEAD\", symref.buf, NULL, NULL, 0);\n \tif (ret)\n \t\tgoto done;\n \ndiff --git a/refs.c b/refs.c\nindex b964ac44d0..29c020e78e 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2115,26 +2115,40 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct\n \n int refs_update_symref(struct ref_store *refs, const char *ref,\n \t\t       const char *target, const char *logmsg,\n-\t\t       struct strbuf *referent)\n+\t\t       struct strbuf *referent, int create_only)\n {\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n-\tint ret = 0;\n+\tint ret = 0, prepret = 0;\n \n \ttransaction = ref_store_transaction_begin(refs, &err);\n-\tif (!transaction ||\n-\t\tref_transaction_update(transaction, ref, NULL, NULL,\n-\t\t\t\t   target, NULL, REF_NO_DEREF,\n-\t\t\t\t   logmsg, &err) ||\n-\t\tref_transaction_prepare(transaction, &err)) {\n+\tif (!transaction) {\n+\terror_return:\n \t\tret = error(\"%s\", err.buf);\n \t\tgoto cleanup;\n \t}\n+\tif (create_only) {\n+\t\tif (ref_transaction_create(transaction, ref, NULL, target,\n+\t\t\t\t\t   REF_NO_DEREF, logmsg, &err))\n+\t\t\tgoto error_return;\n+\t\tprepret = ref_transaction_prepare(transaction, &err);\n+\t\tif (prepret && prepret != TRANSACTION_CREATE_EXISTS)\n+\t\t\tgoto error_return;\n+\t} else {\n+\t\tif (ref_transaction_update(transaction, ref, NULL, NULL,\n+\t\t\t\t\t   target, NULL, REF_NO_DEREF,\n+\t\t\t\t\t   logmsg, &err) ||\n+\t\t\tref_transaction_prepare(transaction, &err))\n+\t\t\tgoto error_return;\n+\t}\n+\n \tif (referent)\n \t\trefs_read_symbolic_ref(refs, ref, referent);\n \n+\tif (prepret == TRANSACTION_CREATE_EXISTS)\n+\t\tgoto cleanup;\n \tif (ref_transaction_commit(transaction, &err))\n-\t\tret = error(\"%s\", err.buf);\n+\t\tgoto error_return;\n \n cleanup:\n \tstrbuf_release(&err);\ndiff --git a/refs.h b/refs.h\nindex c83b1ec76e..41ae384f11 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -572,7 +572,7 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,\n \n int refs_update_symref(struct ref_store *refs, const char *refname,\n \t\t       const char *target, const char *logmsg,\n-\t\t       struct strbuf *referent);\n+\t\t       struct strbuf *referent, int create_only);\n \n enum action_on_err {\n \tUPDATE_REFS_MSG_ON_ERR,\ndiff --git a/reset.c b/reset.c\nindex cc36a9ed56..919fb84f5f 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -76,7 +76,7 @@ static int update_refs(const struct reset_head_opts *opts,\n \t\tif (!ret)\n \t\t\tret = refs_update_symref(get_main_ref_store(the_repository),\n \t\t\t\t\t\t \"HEAD\", switch_to_branch,\n-\t\t\t\t\t\t reflog_head, NULL);\n+\t\t\t\t\t\t reflog_head, NULL, 0);\n \t}\n \tif (!ret && run_hook)\n \t\trun_hooks_l(the_repository, \"post-checkout\",\ndiff --git a/sequencer.c b/sequencer.c\nindex 23b162924c..103d0ddfb2 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -5107,7 +5107,8 @@ static int pick_commits(struct repository *r,\n \t\t\t}\n \t\t\tmsg = reflog_message(opts, \"finish\", \"returning to %s\",\n \t\t\t\thead_ref.buf);\n-\t\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", head_ref.buf, msg, NULL)) {\n+\t\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", head_ref.buf,\n+\t\t\t\t\t\tmsg, NULL, 0)) {\n \t\t\t\tres = error(_(\"could not update HEAD to %s\"),\n \t\t\t\t\thead_ref.buf);\n \t\t\t\tgoto cleanup_head_ref;\ndiff --git a/setup.c b/setup.c\nindex d95f051465..9bbc655ee4 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -2275,7 +2275,8 @@ void create_reference_database(enum ref_storage_format ref_storage_format,\n \t\t\tdie(_(\"invalid initial branch name: '%s'\"),\n \t\t\t    initial_branch);\n \n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", ref, NULL, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", ref,\n+\t\t\t\t\tNULL, NULL, 0) < 0)\n \t\t\texit(1);\n \t\tfree(ref);\n \t}\ndiff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c\nindex a911302bea..f72b029e51 100644\n--- a/t/helper/test-ref-store.c\n+++ b/t/helper/test-ref-store.c\n@@ -120,7 +120,7 @@ static int cmd_create_symref(struct ref_store *refs, const char **argv)\n \tconst char *target = notnull(*argv++, \"target\");\n \tconst char *logmsg = *argv++;\n \n-\treturn refs_update_symref(refs, refname, target, logmsg, NULL);\n+\treturn refs_update_symref(refs, refname, target, logmsg, NULL, 0);\n }\n \n static struct flag_definition transaction_flags[] = {\n-- \n2.47.0.7.g072c39eddb.dirty\n\n"},{"id":"504939","messageId":"20241012230428.3259229-6-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241012230428.3259229-1-bence@ferdinandy.com","subject":"[PATCH v7 6/6] fetch: set remote/HEAD if it does not exist","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-12T23:03:26Z","receivedAt":"2024-10-12T23:16:19Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"If the user has remote/HEAD set already and it looks like it has changed\non the server, then print a message, otherwise set it if we can.\nSilently pass if the user already has the same remote/HEAD set as\nreported by the server or if we encounter any errors along the way.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v3: - does not rely on remote set-head anymore so it only authenticates\n        once\n        - uses the new REF_CREATE_ONLY to atomically check if the ref exists\n          and only write it if it doesn't\n        - in all other cases the maximum it does is print a warning\n    \n    v4: - instead of the discarded REF_CREATE_ONLY, it uses the existing,\n          but updated transaction api to request a silent create only\n        - it now uses the atomic before_target to determine reporting\n        - refactored for legibility\n    \n    v5: - instead of printing a not too useful message, it now fails\n          silently, this in line with the objective to only set up\n          remote/HEAD automatically if the right thing is trivial, for\n          everything else there is remote set-head\n        - fixed all failing tests\n        - added two new tests, one for checking if remote/HEAD is set to the\n          correct one, and one to test that we do not override remote/HEAD\n          if it has changed on the server from what we have locally\n    \n    v6: - fixed style issues and unintended extra empty line\n        - updated function call with bool to int from previous patch's\n          change\n        - removed calls to error(...) inherited from builtin/remote.c so we\n          actually fail silently\n        - set the test for remote set-head --auto to the correct value here,\n          which was previously erronously set in the remote set-head patch\n    \n    v7: - no change\n\n builtin/fetch.c                  |  82 +++++++++++\n t/t4207-log-decoration-colors.sh |   3 +-\n t/t5505-remote.sh                |  13 +-\n t/t5510-fetch.sh                 | 229 ++++++++++++++++---------------\n t/t5512-ls-remote.sh             |   2 +\n t/t5514-fetch-multiple.sh        |  17 ++-\n t/t5516-fetch-push.sh            |   3 +-\n t/t5527-fetch-odd-refs.sh        |   3 +-\n t/t7900-maintenance.sh           |   3 +-\n t/t9210-scalar.sh                |   5 +-\n t/t9211-scalar-clone.sh          |   6 +-\n t/t9902-completion.sh            |  65 +++++++++\n 12 files changed, 308 insertions(+), 123 deletions(-)\n\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 80a64d0d26..4c68f21be1 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1578,6 +1578,80 @@ static int backfill_tags(struct display_state *display_state,\n \treturn retcode;\n }\n \n+static void report_set_head(const char *remote, const char *head_name,\n+\t\t\tstruct strbuf *buf_prev) {\n+\tstruct strbuf buf_prefix = STRBUF_INIT;\n+\tconst char *prev_head = NULL;\n+\n+\tstrbuf_addf(&buf_prefix, \"refs/remotes/%s/\", remote);\n+\tskip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);\n+\n+\tif (prev_head && strcmp(prev_head, head_name)) {\n+\t\tprintf(\"'HEAD' at '%s' has changed from '%s' to '%s'\\n\",\n+\t\t\tremote, prev_head, head_name);\n+\t\tprintf(\"Run 'git remote set-head %s %s' to follow the change.\\n\",\n+\t\t\tremote, head_name);\n+\t}\n+}\n+\n+static const char *strip_refshead(const char *name){\n+\tskip_prefix(name, \"refs/heads/\", &name);\n+\treturn name;\n+}\n+\n+static int set_head(const struct ref *remote_refs)\n+{\n+\tint result = 0;\n+\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,\n+\t\tb_local_head = STRBUF_INIT;\n+\tconst char *remote = gtransport->remote->name;\n+\tchar *head_name = NULL;\n+\tstruct ref *ref, *matches;\n+\tstruct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;\n+\tstruct refspec_item refspec = {\n+\t\t.force = 0,\n+\t\t.pattern = 1,\n+\t\t.src = (char *) \"refs/heads/*\",\n+\t\t.dst = (char *) \"refs/heads/*\",\n+\t};\n+\tstruct string_list heads = STRING_LIST_INIT_DUP;\n+\tstruct ref_store *refs = get_main_ref_store(the_repository);\n+\n+\tget_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);\n+\tmatches = guess_remote_head(find_ref_by_name(remote_refs, \"HEAD\"),\n+\t\t\t\t    fetch_map, 1);\n+\tfor (ref = matches; ref; ref = ref->next) {\n+\t\tstring_list_append(&heads, strip_refshead(ref->name));\n+\t}\n+\n+\n+\tif (!heads.nr)\n+\t\tresult = 1;\n+\telse if (heads.nr > 1)\n+\t\tresult = 1;\n+\telse\n+\t\thead_name = xstrdup(heads.items[0].string);\n+\tif (head_name) {\n+\t\tstrbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", remote);\n+\t\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", remote, head_name);\n+\t\t/* make sure it's valid */\n+\t\tif (!refs_ref_exists(refs, b_remote_head.buf))\n+\t\t\tresult = 1;\n+\t\telse if (refs_update_symref(refs, b_head.buf, b_remote_head.buf,\n+\t\t\t\t\t\"remote set-head\", &b_local_head, 1))\n+\t\t\tresult = 1;\n+\t\telse\n+\t\t\treport_set_head(remote, head_name, &b_local_head);\n+\n+\t\tfree(head_name);\n+\t}\n+\n+\tstrbuf_release(&b_head);\n+\tstrbuf_release(&b_local_head);\n+\tstrbuf_release(&b_remote_head);\n+\treturn result;\n+}\n+\n static int do_fetch(struct transport *transport,\n \t\t    struct refspec *rs,\n \t\t    const struct fetch_config *config)\n@@ -1647,6 +1721,8 @@ static int do_fetch(struct transport *transport,\n \t\t\t\t    \"refs/tags/\");\n \t}\n \n+\tstrvec_push(&transport_ls_refs_options.ref_prefixes, \"HEAD\");\n+\n \tif (must_list_refs) {\n \t\ttrace2_region_enter(\"fetch\", \"remote_refs\", the_repository);\n \t\tremote_refs = transport_get_remote_refs(transport,\n@@ -1791,6 +1867,12 @@ static int do_fetch(struct transport *transport,\n \t\t\t\t  \"you need to specify exactly one branch with the --set-upstream option\"));\n \t\t}\n \t}\n+\tif (set_head(remote_refs))\n+\t\t;\n+\t\t/*\n+\t\t * Way too many cases where this can go wrong\n+\t\t * so let's just fail silently for now.\n+\t\t */\n \n cleanup:\n \tif (retcode) {\ndiff --git a/t/t4207-log-decoration-colors.sh b/t/t4207-log-decoration-colors.sh\nindex 73ea9e5155..d55d22cb2f 100755\n--- a/t/t4207-log-decoration-colors.sh\n+++ b/t/t4207-log-decoration-colors.sh\n@@ -59,7 +59,8 @@ ${c_reset}${c_tag}tag: ${c_reset}${c_tag}v1.0${c_reset}${c_commit}, \\\n ${c_reset}${c_tag}tag: ${c_reset}${c_tag}B${c_reset}${c_commit})${c_reset} B\n ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\n ${c_tag}tag: ${c_reset}${c_tag}A1${c_reset}${c_commit}, \\\n-${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit})${c_reset} A1\n+${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit}, \\\n+${c_reset}${c_remoteBranch}other/HEAD${c_reset}${c_commit})${c_reset} A1\n \t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\n ${c_stash}refs/stash${c_reset}${c_commit})${c_reset} On main: Changes to A.t\n \t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex d99cd1d0aa..cb45f22232 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -71,7 +71,7 @@ test_expect_success 'add another remote' '\n \t\tcd test &&\n \t\tgit remote add -f second ../two &&\n \t\ttokens_match \"origin second\" \"$(git remote)\" &&\n-\t\tcheck_tracking_branch second main side another &&\n+\t\tcheck_tracking_branch second main side another HEAD &&\n \t\tgit for-each-ref \"--format=%(refname)\" refs/remotes |\n \t\tsed -e \"/^refs\\/remotes\\/origin\\//d\" \\\n \t\t    -e \"/^refs\\/remotes\\/second\\//d\" >actual &&\n@@ -473,7 +473,7 @@ test_expect_success 'set-head --auto has no problem w/multiple HEADs' '\n \t\tcd test &&\n \t\tgit fetch two \"refs/heads/*:refs/remotes/two/*\" &&\n \t\tgit remote set-head --auto two >output 2>&1 &&\n-\t\techo \"'\\''two/HEAD'\\'' is now created and points to '\\''main'\\''\" >expect &&\n+\t\techo \"'\\''two/HEAD'\\'' is unchanged and points to '\\''main'\\''\" >expect &&\n \t\ttest_cmp expect output\n \t)\n '\n@@ -763,6 +763,7 @@ test_expect_success 'reject --no-no-tags' '\n cat >one/expect <<\\EOF\n   apis/main\n   apis/side\n+  drosophila/HEAD -> drosophila/main\n   drosophila/another\n   drosophila/main\n   drosophila/side\n@@ -780,6 +781,7 @@ test_expect_success 'update' '\n '\n \n cat >one/expect <<\\EOF\n+  drosophila/HEAD -> drosophila/main\n   drosophila/another\n   drosophila/main\n   drosophila/side\n@@ -792,7 +794,7 @@ EOF\n test_expect_success 'update with arguments' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\n@@ -835,7 +837,7 @@ EOF\n test_expect_success 'update default' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\n@@ -847,6 +849,7 @@ test_expect_success 'update default' '\n '\n \n cat >one/expect <<\\EOF\n+  drosophila/HEAD -> drosophila/main\n   drosophila/another\n   drosophila/main\n   drosophila/side\n@@ -869,7 +872,7 @@ test_expect_success 'update default (overridden, with funny whitespace)' '\n test_expect_success 'update (with remotes.default defined)' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex 0890b9f61c..dfc8d748ba 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -75,6 +75,30 @@ test_expect_success \"fetch test for-merge\" '\n \tcut -f -2 .git/FETCH_HEAD >actual &&\n \ttest_cmp expected actual'\n \n+test_expect_success \"fetch test remote HEAD\" '\n+\tcd \"$D\" &&\n+\tcd two &&\n+\tgit fetch &&\n+\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\tgit rev-parse --verify refs/remotes/origin/main &&\n+\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\tbranch=$(git rev-parse refs/remotes/origin/main) &&\n+\ttest \"z$head\" = \"z$branch\"'\n+\n+test_expect_success \"fetch test remote HEAD change\" '\n+\tcd \"$D\" &&\n+\tcd two &&\n+\tgit switch -c other &&\n+\tgit push -u origin other &&\n+\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\tgit rev-parse --verify refs/remotes/origin/main &&\n+\tgit rev-parse --verify refs/remotes/origin/other &&\n+\tgit remote set-head origin other &&\n+\tgit fetch &&\n+\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\tbranch=$(git rev-parse refs/remotes/origin/other) &&\n+\ttest \"z$head\" = \"z$branch\"'\n+\n test_expect_success 'fetch --prune on its own works as expected' '\n \tcd \"$D\" &&\n \tgit clone . prune &&\n@@ -478,7 +502,6 @@ test_expect_success 'unbundle 1' '\n \ttest_must_fail git fetch \"$D/bundle1\" main:main\n '\n \n-\n test_expect_success 'bundle 1 has only 3 files ' '\n \tcd \"$D\" &&\n \ttest_bundle_object_count bundle1 3\n@@ -819,7 +842,7 @@ test_expect_success 'fetch from multiple configured URLs in single remote' '\n \n # configured prune tests\n \n-set_config_tristate () {\n+set_config_tristate() {\n \t# var=$1 val=$2\n \tcase \"$2\" in\n \tunset)\n@@ -833,12 +856,12 @@ set_config_tristate () {\n \tesac\n }\n \n-test_configured_prune () {\n+test_configured_prune() {\n \ttest_configured_prune_type \"$@\" \"name\"\n \ttest_configured_prune_type \"$@\" \"link\"\n }\n \n-test_configured_prune_type () {\n+test_configured_prune_type() {\n \tfetch_prune=$1\n \tremote_origin_prune=$2\n \tfetch_prune_tags=$3\n@@ -848,8 +871,7 @@ test_configured_prune_type () {\n \tcmdline=$7\n \tmode=$8\n \n-\tif test -z \"$cmdline_setup\"\n-\tthen\n+\tif test -z \"$cmdline_setup\"; then\n \t\ttest_expect_success 'setup cmdline_setup variable for subsequent test' '\n \t\t\tremote_url=\"file://$(git -C one config remote.origin.url)\" &&\n \t\t\tremote_fetch=\"$(git -C one config remote.origin.fetch)\" &&\n@@ -857,12 +879,10 @@ test_configured_prune_type () {\n \t\t'\n \tfi\n \n-\tif test \"$mode\" = 'link'\n-\tthen\n+\tif test \"$mode\" = 'link'; then\n \t\tnew_cmdline=\"\"\n \n-\t\tif test \"$cmdline\" = \"\"\n-\t\tthen\n+\t\tif test \"$cmdline\" = \"\"; then\n \t\t\tnew_cmdline=$cmdline_setup\n \t\telse\n \t\t\tnew_cmdline=$(perl -e '\n@@ -873,10 +893,8 @@ test_configured_prune_type () {\n \t\tfi\n \n \t\tif test \"$fetch_prune_tags\" = 'true' ||\n-\t\t   test \"$remote_origin_prune_tags\" = 'true'\n-\t\tthen\n-\t\t\tif ! printf '%s' \"$cmdline\\n\" | grep -q refs/remotes/origin/\n-\t\t\tthen\n+\t\t\ttest \"$remote_origin_prune_tags\" = 'true'; then\n+\t\t\tif ! printf '%s' \"$cmdline\\n\" | grep -q refs/remotes/origin/; then\n \t\t\t\tnew_cmdline=\"$new_cmdline refs/tags/*:refs/tags/*\"\n \t\t\tfi\n \t\tfi\n@@ -946,100 +964,100 @@ test_configured_prune_type () {\n # $7 git-fetch $cmdline:\n #\n #                     $1    $2    $3    $4    $5     $6     $7\n-test_configured_prune unset unset unset unset kept   kept   \"\"\n-test_configured_prune unset unset unset unset kept   kept   \"--no-prune\"\n-test_configured_prune unset unset unset unset pruned kept   \"--prune\"\n-test_configured_prune unset unset unset unset kept   pruned \\\n+test_configured_prune unset unset unset unset kept kept \"\"\n+test_configured_prune unset unset unset unset kept kept \"--no-prune\"\n+test_configured_prune unset unset unset unset pruned kept \"--prune\"\n+test_configured_prune unset unset unset unset kept pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/*\"\n test_configured_prune unset unset unset unset pruned pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*\"\n \n-test_configured_prune false unset unset unset kept   kept   \"\"\n-test_configured_prune false unset unset unset kept   kept   \"--no-prune\"\n-test_configured_prune false unset unset unset pruned kept   \"--prune\"\n+test_configured_prune false unset unset unset kept kept \"\"\n+test_configured_prune false unset unset unset kept kept \"--no-prune\"\n+test_configured_prune false unset unset unset pruned kept \"--prune\"\n \n-test_configured_prune true  unset unset unset pruned kept   \"\"\n-test_configured_prune true  unset unset unset pruned kept   \"--prune\"\n-test_configured_prune true  unset unset unset kept   kept   \"--no-prune\"\n+test_configured_prune true unset unset unset pruned kept \"\"\n+test_configured_prune true unset unset unset pruned kept \"--prune\"\n+test_configured_prune true unset unset unset kept kept \"--no-prune\"\n \n-test_configured_prune unset false unset unset kept   kept   \"\"\n-test_configured_prune unset false unset unset kept   kept   \"--no-prune\"\n-test_configured_prune unset false unset unset pruned kept   \"--prune\"\n+test_configured_prune unset false unset unset kept kept \"\"\n+test_configured_prune unset false unset unset kept kept \"--no-prune\"\n+test_configured_prune unset false unset unset pruned kept \"--prune\"\n \n-test_configured_prune false false unset unset kept   kept   \"\"\n-test_configured_prune false false unset unset kept   kept   \"--no-prune\"\n-test_configured_prune false false unset unset pruned kept   \"--prune\"\n-test_configured_prune false false unset unset kept   pruned \\\n+test_configured_prune false false unset unset kept kept \"\"\n+test_configured_prune false false unset unset kept kept \"--no-prune\"\n+test_configured_prune false false unset unset pruned kept \"--prune\"\n+test_configured_prune false false unset unset kept pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/*\"\n test_configured_prune false false unset unset pruned pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*\"\n \n-test_configured_prune true  false unset unset kept   kept   \"\"\n-test_configured_prune true  false unset unset pruned kept   \"--prune\"\n-test_configured_prune true  false unset unset kept   kept   \"--no-prune\"\n+test_configured_prune true false unset unset kept kept \"\"\n+test_configured_prune true false unset unset pruned kept \"--prune\"\n+test_configured_prune true false unset unset kept kept \"--no-prune\"\n \n-test_configured_prune unset true  unset unset pruned kept   \"\"\n-test_configured_prune unset true  unset unset kept   kept   \"--no-prune\"\n-test_configured_prune unset true  unset unset pruned kept   \"--prune\"\n+test_configured_prune unset true unset unset pruned kept \"\"\n+test_configured_prune unset true unset unset kept kept \"--no-prune\"\n+test_configured_prune unset true unset unset pruned kept \"--prune\"\n \n-test_configured_prune false true  unset unset pruned kept   \"\"\n-test_configured_prune false true  unset unset kept   kept   \"--no-prune\"\n-test_configured_prune false true  unset unset pruned kept   \"--prune\"\n+test_configured_prune false true unset unset pruned kept \"\"\n+test_configured_prune false true unset unset kept kept \"--no-prune\"\n+test_configured_prune false true unset unset pruned kept \"--prune\"\n \n-test_configured_prune true  true  unset unset pruned kept   \"\"\n-test_configured_prune true  true  unset unset pruned kept   \"--prune\"\n-test_configured_prune true  true  unset unset kept   kept   \"--no-prune\"\n-test_configured_prune true  true  unset unset kept   pruned \\\n+test_configured_prune true true unset unset pruned kept \"\"\n+test_configured_prune true true unset unset pruned kept \"--prune\"\n+test_configured_prune true true unset unset kept kept \"--no-prune\"\n+test_configured_prune true true unset unset kept pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/*\"\n-test_configured_prune true  true  unset unset pruned pruned \\\n+test_configured_prune true true unset unset pruned pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*\"\n \n # --prune-tags on its own does nothing, needs --prune as well, same\n # for fetch.pruneTags without fetch.prune\n-test_configured_prune unset unset unset unset kept kept     \"--prune-tags\"\n-test_configured_prune unset unset true unset  kept kept     \"\"\n-test_configured_prune unset unset unset true  kept kept     \"\"\n+test_configured_prune unset unset unset unset kept kept \"--prune-tags\"\n+test_configured_prune unset unset true unset kept kept \"\"\n+test_configured_prune unset unset unset true kept kept \"\"\n \n # These will prune the tags\n test_configured_prune unset unset unset unset pruned pruned \"--prune --prune-tags\"\n-test_configured_prune true  unset true  unset pruned pruned \"\"\n-test_configured_prune unset true  unset true  pruned pruned \"\"\n+test_configured_prune true unset true unset pruned pruned \"\"\n+test_configured_prune unset true unset true pruned pruned \"\"\n \n # remote.<name>.pruneTags overrides fetch.pruneTags, just like\n # remote.<name>.prune overrides fetch.prune if set.\n-test_configured_prune true  unset true unset pruned pruned  \"\"\n-test_configured_prune false true  false true  pruned pruned \"\"\n-test_configured_prune true  false true  false kept   kept   \"\"\n+test_configured_prune true unset true unset pruned pruned \"\"\n+test_configured_prune false true false true pruned pruned \"\"\n+test_configured_prune true false true false kept kept \"\"\n \n # When --prune-tags is supplied it's ignored if an explicit refspec is\n # given, same for the configuration options.\n test_configured_prune unset unset unset unset pruned kept \\\n \t\"--prune --prune-tags origin +refs/heads/*:refs/remotes/origin/*\"\n-test_configured_prune unset unset true  unset pruned kept \\\n+test_configured_prune unset unset true unset pruned kept \\\n \t\"--prune origin +refs/heads/*:refs/remotes/origin/*\"\n-test_configured_prune unset unset unset true pruned  kept \\\n+test_configured_prune unset unset unset true pruned kept \\\n \t\"--prune origin +refs/heads/*:refs/remotes/origin/*\"\n \n # Pruning that also takes place if a file:// url replaces a named\n # remote. However, because there's no implicit\n # +refs/heads/*:refs/remotes/origin/* refspec and supplying it on the\n # command-line negates --prune-tags, the branches will not be pruned.\n-test_configured_prune_type unset unset unset unset kept   kept   \"origin --prune-tags\" \"name\"\n-test_configured_prune_type unset unset unset unset kept   kept   \"origin --prune-tags\" \"link\"\n+test_configured_prune_type unset unset unset unset kept kept \"origin --prune-tags\" \"name\"\n+test_configured_prune_type unset unset unset unset kept kept \"origin --prune-tags\" \"link\"\n test_configured_prune_type unset unset unset unset pruned pruned \"origin --prune --prune-tags\" \"name\"\n-test_configured_prune_type unset unset unset unset kept   pruned \"origin --prune --prune-tags\" \"link\"\n+test_configured_prune_type unset unset unset unset kept pruned \"origin --prune --prune-tags\" \"link\"\n test_configured_prune_type unset unset unset unset pruned pruned \"--prune --prune-tags origin\" \"name\"\n-test_configured_prune_type unset unset unset unset kept   pruned \"--prune --prune-tags origin\" \"link\"\n-test_configured_prune_type unset unset true  unset pruned pruned \"--prune origin\" \"name\"\n-test_configured_prune_type unset unset true  unset kept   pruned \"--prune origin\" \"link\"\n-test_configured_prune_type unset unset unset true  pruned pruned \"--prune origin\" \"name\"\n-test_configured_prune_type unset unset unset true  kept   pruned \"--prune origin\" \"link\"\n-test_configured_prune_type true  unset true  unset pruned pruned \"origin\" \"name\"\n-test_configured_prune_type true  unset true  unset kept   pruned \"origin\" \"link\"\n-test_configured_prune_type unset  true true  unset pruned pruned \"origin\" \"name\"\n-test_configured_prune_type unset  true true  unset kept   pruned \"origin\" \"link\"\n-test_configured_prune_type unset  true unset true  pruned pruned \"origin\" \"name\"\n-test_configured_prune_type unset  true unset true  kept   pruned \"origin\" \"link\"\n+test_configured_prune_type unset unset unset unset kept pruned \"--prune --prune-tags origin\" \"link\"\n+test_configured_prune_type unset unset true unset pruned pruned \"--prune origin\" \"name\"\n+test_configured_prune_type unset unset true unset kept pruned \"--prune origin\" \"link\"\n+test_configured_prune_type unset unset unset true pruned pruned \"--prune origin\" \"name\"\n+test_configured_prune_type unset unset unset true kept pruned \"--prune origin\" \"link\"\n+test_configured_prune_type true unset true unset pruned pruned \"origin\" \"name\"\n+test_configured_prune_type true unset true unset kept pruned \"origin\" \"link\"\n+test_configured_prune_type unset true true unset pruned pruned \"origin\" \"name\"\n+test_configured_prune_type unset true true unset kept pruned \"origin\" \"link\"\n+test_configured_prune_type unset true unset true pruned pruned \"origin\" \"name\"\n+test_configured_prune_type unset true unset true kept pruned \"origin\" \"link\"\n \n # When all remote.origin.fetch settings are deleted a --prune\n # --prune-tags still implicitly supplies refs/tags/*:refs/tags/* so\n@@ -1137,8 +1155,7 @@ test_expect_success 'fetching with auto-gc does not lock up' '\n \t)\n '\n \n-for section in fetch transfer\n-do\n+for section in fetch transfer; do\n \ttest_expect_success \"$section.hideRefs affects connectivity check\" '\n \t\tGIT_TRACE=\"$PWD\"/trace git -c $section.hideRefs=refs -c \\\n \t\t\t$section.hideRefs=\"!refs/tags/\" fetch &&\n@@ -1157,21 +1174,23 @@ test_expect_success 'prepare source branch' '\n \ttest 3 -le $(wc -l <actual)\n '\n \n-validate_store_type () {\n+validate_store_type() {\n \tgit -C dest count-objects -v >actual &&\n-\tcase \"$store_type\" in\n-\tpacked)\n-\t\tgrep \"^count: 0$\" actual ;;\n-\tloose)\n-\t\tgrep \"^packs: 0$\" actual ;;\n-\tesac || {\n+\t\tcase \"$store_type\" in\n+\t\tpacked)\n+\t\t\tgrep \"^count: 0$\" actual\n+\t\t\t;;\n+\t\tloose)\n+\t\t\tgrep \"^packs: 0$\" actual\n+\t\t\t;;\n+\t\tesac || {\n \t\techo \"store_type is $store_type\"\n \t\tcat actual\n \t\tfalse\n \t}\n }\n \n-test_unpack_limit () {\n+test_unpack_limit() {\n \tstore_type=$1\n \n \tcase \"$store_type\" in\n@@ -1192,43 +1211,39 @@ test_unpack_limit () {\n test_unpack_limit packed\n test_unpack_limit loose\n \n-setup_negotiation_tip () {\n+setup_negotiation_tip() {\n \tSERVER=\"$1\"\n \tURL=\"$2\"\n \tUSE_PROTOCOL_V2=\"$3\"\n \n \trm -rf \"$SERVER\" client trace &&\n-\tgit init -b main \"$SERVER\" &&\n-\ttest_commit -C \"$SERVER\" alpha_1 &&\n-\ttest_commit -C \"$SERVER\" alpha_2 &&\n-\tgit -C \"$SERVER\" checkout --orphan beta &&\n-\ttest_commit -C \"$SERVER\" beta_1 &&\n-\ttest_commit -C \"$SERVER\" beta_2 &&\n-\n-\tgit clone \"$URL\" client &&\n-\n-\tif test \"$USE_PROTOCOL_V2\" -eq 1\n-\tthen\n-\t\tgit -C \"$SERVER\" config protocol.version 2 &&\n-\t\tgit -C client config protocol.version 2\n-\tfi &&\n-\n-\ttest_commit -C \"$SERVER\" beta_s &&\n-\tgit -C \"$SERVER\" checkout main &&\n-\ttest_commit -C \"$SERVER\" alpha_s &&\n-\tgit -C \"$SERVER\" tag -d alpha_1 alpha_2 beta_1 beta_2\n+\t\tgit init -b main \"$SERVER\" &&\n+\t\ttest_commit -C \"$SERVER\" alpha_1 &&\n+\t\ttest_commit -C \"$SERVER\" alpha_2 &&\n+\t\tgit -C \"$SERVER\" checkout --orphan beta &&\n+\t\ttest_commit -C \"$SERVER\" beta_1 &&\n+\t\ttest_commit -C \"$SERVER\" beta_2 &&\n+\t\tgit clone \"$URL\" client &&\n+\t\tif test \"$USE_PROTOCOL_V2\" -eq 1; then\n+\t\t\tgit -C \"$SERVER\" config protocol.version 2 &&\n+\t\t\t\tgit -C client config protocol.version 2\n+\t\tfi &&\n+\t\ttest_commit -C \"$SERVER\" beta_s &&\n+\t\tgit -C \"$SERVER\" checkout main &&\n+\t\ttest_commit -C \"$SERVER\" alpha_s &&\n+\t\tgit -C \"$SERVER\" tag -d alpha_1 alpha_2 beta_1 beta_2\n }\n \n-check_negotiation_tip () {\n+check_negotiation_tip() {\n \t# Ensure that {alpha,beta}_1 are sent as \"have\", but not {alpha_beta}_2\n \tALPHA_1=$(git -C client rev-parse alpha_1) &&\n-\tgrep \"fetch> have $ALPHA_1\" trace &&\n-\tBETA_1=$(git -C client rev-parse beta_1) &&\n-\tgrep \"fetch> have $BETA_1\" trace &&\n-\tALPHA_2=$(git -C client rev-parse alpha_2) &&\n-\t! grep \"fetch> have $ALPHA_2\" trace &&\n-\tBETA_2=$(git -C client rev-parse beta_2) &&\n-\t! grep \"fetch> have $BETA_2\" trace\n+\t\tgrep \"fetch> have $ALPHA_1\" trace &&\n+\t\tBETA_1=$(git -C client rev-parse beta_1) &&\n+\t\tgrep \"fetch> have $BETA_1\" trace &&\n+\t\tALPHA_2=$(git -C client rev-parse alpha_2) &&\n+\t\t! grep \"fetch> have $ALPHA_2\" trace &&\n+\t\tBETA_2=$(git -C client rev-parse beta_2) &&\n+\t\t! grep \"fetch> have $BETA_2\" trace\n }\n \n test_expect_success '--negotiation-tip limits \"have\" lines sent' '\ndiff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh\nindex 64b3491e4e..1b3865e154 100755\n--- a/t/t5512-ls-remote.sh\n+++ b/t/t5512-ls-remote.sh\n@@ -293,6 +293,8 @@ test_expect_success 'ls-remote with filtered symref (refname)' '\n \tcat >expect <<-EOF &&\n \tref: refs/heads/main\tHEAD\n \t$rev\tHEAD\n+\tref: refs/remotes/origin/main\trefs/remotes/origin/HEAD\n+\t$rev\trefs/remotes/origin/HEAD\n \tEOF\n \tgit ls-remote --symref . HEAD >actual &&\n \ttest_cmp expect actual\ndiff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh\nindex 579872c258..e3482b27b2 100755\n--- a/t/t5514-fetch-multiple.sh\n+++ b/t/t5514-fetch-multiple.sh\n@@ -45,14 +45,17 @@ test_expect_success setup '\n '\n \n cat > test/expect << EOF\n+  one/HEAD -> one/main\n   one/main\n   one/side\n   origin/HEAD -> origin/main\n   origin/main\n   origin/side\n+  three/HEAD -> three/main\n   three/another\n   three/main\n   three/side\n+  two/HEAD -> two/main\n   two/another\n   two/main\n   two/side\n@@ -97,6 +100,7 @@ cat > expect << EOF\n   origin/HEAD -> origin/main\n   origin/main\n   origin/side\n+  three/HEAD -> three/main\n   three/another\n   three/main\n   three/side\n@@ -112,8 +116,10 @@ test_expect_success 'git fetch --multiple (but only one remote)' '\n '\n \n cat > expect << EOF\n+  one/HEAD -> one/main\n   one/main\n   one/side\n+  two/HEAD -> two/main\n   two/another\n   two/main\n   two/side\n@@ -141,7 +147,7 @@ test_expect_success 'git fetch --multiple (bad remote names)' '\n \n test_expect_success 'git fetch --all (skipFetchAll)' '\n \t(cd test4 &&\n-\t for b in $(git branch -r)\n+\t for b in $(git branch -r | grep -v HEAD)\n \t do\n \t\tgit branch -r -d $b || exit 1\n \t done &&\n@@ -153,11 +159,14 @@ test_expect_success 'git fetch --all (skipFetchAll)' '\n '\n \n cat > expect << EOF\n+  one/HEAD -> one/main\n   one/main\n   one/side\n+  three/HEAD -> three/main\n   three/another\n   three/main\n   three/side\n+  two/HEAD -> two/main\n   two/another\n   two/main\n   two/side\n@@ -165,7 +174,7 @@ EOF\n \n test_expect_success 'git fetch --multiple (ignoring skipFetchAll)' '\n \t(cd test4 &&\n-\t for b in $(git branch -r)\n+\t for b in $(git branch -r | grep -v HEAD)\n \t do\n \t\tgit branch -r -d $b || exit 1\n \t done &&\n@@ -221,14 +230,17 @@ test_expect_success 'git fetch --multiple --jobs=0 picks a default' '\n \n create_fetch_all_expect () {\n \tcat >expect <<-\\EOF\n+\t  one/HEAD -> one/main\n \t  one/main\n \t  one/side\n \t  origin/HEAD -> origin/main\n \t  origin/main\n \t  origin/side\n+\t  three/HEAD -> three/main\n \t  three/another\n \t  three/main\n \t  three/side\n+\t  two/HEAD -> two/main\n \t  two/another\n \t  two/main\n \t  two/side\n@@ -265,6 +277,7 @@ test_expect_success 'git fetch (fetch all remotes with fetch.all = true)' '\n \n create_fetch_one_expect () {\n \tcat >expect <<-\\EOF\n+\t  one/HEAD -> one/main\n \t  one/main\n \t  one/side\n \t  origin/HEAD -> origin/main\ndiff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\nindex 331778bd42..5a051aa0c7 100755\n--- a/t/t5516-fetch-push.sh\n+++ b/t/t5516-fetch-push.sh\n@@ -1395,7 +1395,8 @@ test_expect_success 'fetch follows tags by default' '\n \t\tgit tag -m \"annotated\" tag &&\n \t\tgit for-each-ref >tmp1 &&\n \t\tsed -n \"p; s|refs/heads/main$|refs/remotes/origin/main|p\" tmp1 |\n-\t\tsort -k 3 >../expect\n+\t\tsed -n \"p; s|refs/heads/main$|refs/remotes/origin/HEAD|p\"  |\n+\t\tsort -k 4 >../expect\n \t) &&\n \ttest_when_finished \"rm -rf dst\" &&\n \tgit init dst &&\ndiff --git a/t/t5527-fetch-odd-refs.sh b/t/t5527-fetch-odd-refs.sh\nindex 98ece27c6a..d3996af6ee 100755\n--- a/t/t5527-fetch-odd-refs.sh\n+++ b/t/t5527-fetch-odd-refs.sh\n@@ -52,7 +52,8 @@ test_expect_success LONG_REF 'fetch handles extremely long refname' '\n \tlong\n \tmain\n \tEOF\n-\tgit for-each-ref --format=\"%(subject)\" refs/remotes/long >actual &&\n+\tgit for-each-ref --format=\"%(subject)\" refs/remotes/long \\\n+\t\t--exclude=refs/remotes/long/HEAD >actual &&\n \ttest_cmp expect actual\n '\n \ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex a66d0e089d..88331d1ceb 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -329,7 +329,8 @@ test_expect_success 'incremental-repack task' '\n \n \t# Delete refs that have not been repacked in these packs.\n \tgit for-each-ref --format=\"delete %(refname)\" \\\n-\t\trefs/prefetch refs/tags refs/remotes >refs &&\n+\t\trefs/prefetch refs/tags refs/remotes \\\n+\t\t--exclude=refs/remotes/*/HEAD >refs &&\n \tgit update-ref --stdin <refs &&\n \n \t# Replace the object directory with this pack layout.\ndiff --git a/t/t9210-scalar.sh b/t/t9210-scalar.sh\nindex 027235d61a..a81662713e 100755\n--- a/t/t9210-scalar.sh\n+++ b/t/t9210-scalar.sh\n@@ -150,7 +150,8 @@ test_expect_success 'scalar clone' '\n \t\t\t\"$(pwd)\" &&\n \n \t\tgit for-each-ref --format=\"%(refname)\" refs/remotes/origin/ >actual &&\n-\t\techo \"refs/remotes/origin/parallel\" >expect &&\n+\t\techo \"refs/remotes/origin/HEAD\" >>expect &&\n+\t\techo \"refs/remotes/origin/parallel\" >>expect &&\n \t\ttest_cmp expect actual &&\n \n \t\ttest_path_is_missing 1/2 &&\n@@ -219,7 +220,7 @@ test_expect_success 'scalar reconfigure --all with includeIf.onbranch' '\n \tdone\n '\n \n- test_expect_success 'scalar reconfigure --all with detached HEADs' '\n+test_expect_success 'scalar reconfigure --all with detached HEADs' '\n \trepos=\"two three four\" &&\n \tfor num in $repos\n \tdo\ndiff --git a/t/t9211-scalar-clone.sh b/t/t9211-scalar-clone.sh\nindex 7869f45ee6..01f71910f5 100755\n--- a/t/t9211-scalar-clone.sh\n+++ b/t/t9211-scalar-clone.sh\n@@ -31,7 +31,7 @@ test_expect_success 'set up repository to clone' '\n \t)\n '\n \n-cleanup_clone () {\n+cleanup_clone() {\n \trm -rf \"$1\"\n }\n \n@@ -127,7 +127,7 @@ test_expect_success '--single-branch clones HEAD only' '\n \t(\n \t\tcd $enlistment/src &&\n \t\tgit for-each-ref refs/remotes/origin >out &&\n-\t\ttest_line_count = 1 out &&\n+\t\ttest_line_count = 2 out &&\n \t\tgrep \"refs/remotes/origin/base\" out\n \t) &&\n \n@@ -141,7 +141,7 @@ test_expect_success '--no-single-branch clones all branches' '\n \t(\n \t\tcd $enlistment/src &&\n \t\tgit for-each-ref refs/remotes/origin >out &&\n-\t\ttest_line_count = 2 out &&\n+\t\ttest_line_count = 3 out &&\n \t\tgrep \"refs/remotes/origin/base\" out &&\n \t\tgrep \"refs/remotes/origin/parallel\" out\n \t) &&\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex cc6aa9f0cd..b663c4609e 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -658,6 +658,7 @@ test_expect_success '__git_refs - simple' '\n \tHEAD\n \tmain\n \tmatching-branch\n+\tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n \tmatching-tag\n@@ -673,6 +674,7 @@ test_expect_success '__git_refs - full refs' '\n \tcat >expected <<-EOF &&\n \trefs/heads/main\n \trefs/heads/matching-branch\n+\trefs/remotes/other/HEAD\n \trefs/remotes/other/branch-in-other\n \trefs/remotes/other/main-in-other\n \trefs/tags/matching-tag\n@@ -729,6 +731,7 @@ test_expect_success '__git_refs - remote on local file system - full refs' '\n test_expect_success '__git_refs - configured remote' '\n \tcat >expected <<-EOF &&\n \tHEAD\n+\tHEAD\n \tbranch-in-other\n \tmain-in-other\n \tEOF\n@@ -756,6 +759,7 @@ test_expect_success '__git_refs - configured remote - full refs' '\n test_expect_success '__git_refs - configured remote - repo given on the command line' '\n \tcat >expected <<-EOF &&\n \tHEAD\n+\tHEAD\n \tbranch-in-other\n \tmain-in-other\n \tEOF\n@@ -787,6 +791,7 @@ test_expect_success '__git_refs - configured remote - full refs - repo given on\n test_expect_success '__git_refs - configured remote - remote name matches a directory' '\n \tcat >expected <<-EOF &&\n \tHEAD\n+\tHEAD\n \tbranch-in-other\n \tmain-in-other\n \tEOF\n@@ -875,12 +880,14 @@ test_expect_success '__git_refs - unique remote branches for git checkout DWIMer\n \tHEAD\n \tmain\n \tmatching-branch\n+\tother/HEAD\n \tother/ambiguous\n \tother/branch-in-other\n \tother/main-in-other\n \tremote/ambiguous\n \tremote/branch-in-remote\n \tmatching-tag\n+\tHEAD\n \tbranch-in-other\n \tbranch-in-remote\n \tmain-in-other\n@@ -904,6 +911,7 @@ test_expect_success '__git_refs - after --opt=' '\n \tHEAD\n \tmain\n \tmatching-branch\n+\tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n \tmatching-tag\n@@ -919,6 +927,7 @@ test_expect_success '__git_refs - after --opt= - full refs' '\n \tcat >expected <<-EOF &&\n \trefs/heads/main\n \trefs/heads/matching-branch\n+\trefs/remotes/other/HEAD\n \trefs/remotes/other/branch-in-other\n \trefs/remotes/other/main-in-other\n \trefs/tags/matching-tag\n@@ -935,6 +944,7 @@ test_expect_success '__git refs - excluding refs' '\n \t^HEAD\n \t^main\n \t^matching-branch\n+\t^other/HEAD\n \t^other/branch-in-other\n \t^other/main-in-other\n \t^matching-tag\n@@ -950,6 +960,7 @@ test_expect_success '__git refs - excluding full refs' '\n \tcat >expected <<-EOF &&\n \t^refs/heads/main\n \t^refs/heads/matching-branch\n+\t^refs/remotes/other/HEAD\n \t^refs/remotes/other/branch-in-other\n \t^refs/remotes/other/main-in-other\n \t^refs/tags/matching-tag\n@@ -975,6 +986,7 @@ test_expect_success '__git_refs - do not filter refs unless told so' '\n \tmain\n \tmatching-branch\n \tmatching/branch\n+\tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n \tother/matching/branch-in-other\n@@ -1095,6 +1107,7 @@ test_expect_success '__git_complete_refs - simple' '\n \tHEAD Z\n \tmain Z\n \tmatching-branch Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tmatching-tag Z\n@@ -1123,6 +1136,7 @@ test_expect_success '__git_complete_refs - matching' '\n test_expect_success '__git_complete_refs - remote' '\n \tsed -e \"s/Z$//\" >expected <<-EOF &&\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain-in-other Z\n \tEOF\n@@ -1139,9 +1153,11 @@ test_expect_success '__git_complete_refs - track' '\n \tHEAD Z\n \tmain Z\n \tmatching-branch Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tmatching-tag Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain-in-other Z\n \tEOF\n@@ -1184,6 +1200,7 @@ test_expect_success '__git_complete_refs - suffix' '\n \tHEAD.\n \tmain.\n \tmatching-branch.\n+\tother/HEAD.\n \tother/branch-in-other.\n \tother/main-in-other.\n \tmatching-tag.\n@@ -1199,6 +1216,7 @@ test_expect_success '__git_complete_refs - suffix' '\n test_expect_success '__git_complete_fetch_refspecs - simple' '\n \tsed -e \"s/Z$//\" >expected <<-EOF &&\n \tHEAD:HEAD Z\n+\tHEAD:HEAD Z\n \tbranch-in-other:branch-in-other Z\n \tmain-in-other:main-in-other Z\n \tEOF\n@@ -1225,6 +1243,7 @@ test_expect_success '__git_complete_fetch_refspecs - matching' '\n test_expect_success '__git_complete_fetch_refspecs - prefix' '\n \tsed -e \"s/Z$//\" >expected <<-EOF &&\n \t+HEAD:HEAD Z\n+\t+HEAD:HEAD Z\n \t+branch-in-other:branch-in-other Z\n \t+main-in-other:main-in-other Z\n \tEOF\n@@ -1289,6 +1308,7 @@ test_expect_success '__git_complete_worktree_paths with -C' '\n \n test_expect_success 'git switch - with no options, complete local branches and unique remote branch names for DWIM logic' '\n \ttest_completion \"git switch \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1435,11 +1455,13 @@ test_expect_success 'git-bisect - existing view subcommand is recognized and ena\n test_expect_success 'git checkout - completes refs and unique remote branches for DWIM' '\n \ttest_completion \"git checkout \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1461,6 +1483,7 @@ test_expect_success 'git switch - with GIT_COMPLETION_CHECKOUT_NO_GUESS=1, compl\n \n test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete local branches and unique remote names for DWIM logic' '\n \tGIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git switch --guess \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1470,6 +1493,7 @@ test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_G\n \n test_expect_success 'git switch - a later --guess overrides previous --no-guess, complete local and remote unique branches for DWIM' '\n \ttest_completion \"git switch --no-guess --guess \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1490,6 +1514,7 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1498,11 +1523,13 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete\n test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1, complete refs and unique remote branches for DWIM' '\n \tGIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git checkout --guess \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1514,6 +1541,7 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1522,11 +1550,13 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '\n test_expect_success 'git checkout - a later --guess overrides previous --no-guess, complete refs and unique remote branches for DWIM' '\n \ttest_completion \"git checkout --no-guess --guess \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1538,6 +1568,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous --gues\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1550,6 +1581,7 @@ test_expect_success 'git checkout - with checkout.guess = false, only completes\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1559,11 +1591,13 @@ test_expect_success 'git checkout - with checkout.guess = true, completes refs a\n \ttest_config checkout.guess true &&\n \ttest_completion \"git checkout \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1573,11 +1607,13 @@ test_expect_success 'git checkout - a later --guess overrides previous checkout.\n \ttest_config checkout.guess false &&\n \ttest_completion \"git checkout --guess \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1590,6 +1626,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous checko\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1601,6 +1638,7 @@ test_expect_success 'git switch - with --detach, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1612,6 +1650,7 @@ test_expect_success 'git checkout - with --detach, complete only references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1783,6 +1822,7 @@ test_expect_success 'git switch - with -d, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1794,6 +1834,7 @@ test_expect_success 'git checkout - with -d, complete only references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1801,10 +1842,12 @@ test_expect_success 'git checkout - with -d, complete only references' '\n \n test_expect_success 'git switch - with --track, complete only remote branches' '\n \ttest_completion \"git switch --track \" <<-\\EOF &&\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n \ttest_completion \"git switch -t \" <<-\\EOF\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1812,10 +1855,12 @@ test_expect_success 'git switch - with --track, complete only remote branches' '\n \n test_expect_success 'git checkout - with --track, complete only remote branches' '\n \ttest_completion \"git checkout --track \" <<-\\EOF &&\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n \ttest_completion \"git checkout -t \" <<-\\EOF\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1834,6 +1879,7 @@ test_expect_success 'git checkout - with --no-track, complete only local referen\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1845,6 +1891,7 @@ test_expect_success 'git switch - with -c, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1856,6 +1903,7 @@ test_expect_success 'git switch - with -C, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1867,6 +1915,7 @@ test_expect_success 'git switch - with -c and --track, complete all references'\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1878,6 +1927,7 @@ test_expect_success 'git switch - with -C and --track, complete all references'\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1889,6 +1939,7 @@ test_expect_success 'git switch - with -c and --no-track, complete all reference\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1900,6 +1951,7 @@ test_expect_success 'git switch - with -C and --no-track, complete all reference\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1911,6 +1963,7 @@ test_expect_success 'git checkout - with -b, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1922,6 +1975,7 @@ test_expect_success 'git checkout - with -B, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1933,6 +1987,7 @@ test_expect_success 'git checkout - with -b and --track, complete all references\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1944,6 +1999,7 @@ test_expect_success 'git checkout - with -B and --track, complete all references\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1955,6 +2011,7 @@ test_expect_success 'git checkout - with -b and --no-track, complete all referen\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1966,6 +2023,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1973,6 +2031,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen\n \n test_expect_success 'git switch - for -c, complete local branches and unique remote branches' '\n \ttest_completion \"git switch -c \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1982,6 +2041,7 @@ test_expect_success 'git switch - for -c, complete local branches and unique rem\n \n test_expect_success 'git switch - for -C, complete local branches and unique remote branches' '\n \ttest_completion \"git switch -C \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2019,6 +2079,7 @@ test_expect_success 'git switch - for -C with --no-track, complete local branche\n \n test_expect_success 'git checkout - for -b, complete local branches and unique remote branches' '\n \ttest_completion \"git checkout -b \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2028,6 +2089,7 @@ test_expect_success 'git checkout - for -b, complete local branches and unique r\n \n test_expect_success 'git checkout - for -B, complete local branches and unique remote branches' '\n \ttest_completion \"git checkout -B \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2065,6 +2127,7 @@ test_expect_success 'git checkout - for -B with --no-track, complete local branc\n \n test_expect_success 'git switch - with --orphan completes local branch names and unique remote branch names' '\n \ttest_completion \"git switch --orphan \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2080,6 +2143,7 @@ test_expect_success 'git switch - --orphan with branch already provided complete\n \n test_expect_success 'git checkout - with --orphan completes local branch names and unique remote branch names' '\n \ttest_completion \"git checkout --orphan \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2093,6 +2157,7 @@ test_expect_success 'git checkout - --orphan with branch already provided comple\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n-- \n2.47.0.7.g072c39eddb.dirty\n\n\n"},{"id":"504947","messageId":"3c848f10-ac14-4cd7-8fbe-868da9599244@gmail.com","threadId":"62096","inReplyTo":"20241012230428.3259229-1-bence@ferdinandy.com","subject":"Re: [PATCH v7 1/6] refs: atomically record overwritten ref in update_symref","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-10-13T13:52:58Z","receivedAt":"2024-10-13T13:53:02Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Bence\n\nOn 13/10/2024 00:03, Bence Ferdinandy wrote:\n> When updating a symref it's currently not possible to know for sure what\n> was the previous value that was overwritten.\n\nIt is if you use a ref transaction rather than call refs_update_symref() \nand query the ref after calling ref_transaction_prepare() and before \ncalling ref_transaction_commit() which is what the code below does.\n\n> Record the value after the\n> ref has been locked if the caller of refs_update_symref requests it via\n> a new variable in the function call.\n\nTo me this patch and patch 5 feel quite disruptive to all the existing \ncallers which don't need this specialized functionality. I think it \nwould be less disruptive over all if you used a ref transaction rather \nthan calling refs_update_symref() in the final patch. That would enable \nus to keep the simpler interface for refs_update_symref().\n\nI'm also not sure about the proposed interface I would have thought it \nwould be simpler to take a \"char**\" rather than an \"struct strbuf*\" if \nwe do decide that it is useful for callers of refs_update_symref() to \nquery the old value.\n\nBest Wishes\n\nPhillip\n\n> Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n> ---\n> \n> Notes:\n>      v4: new patch\n>      \n>      v5: - added before_target to reftables backend\n>          - added an extra safety check for transaction's existence in refs.c\n>      \n>      v6: - no change\n>      \n>      v7: - remove the whole before_target concept from the backends and\n>            handle checking it in refs.c instead (thanks Karthik)\n>          - rename the before_target to referent which is how the same concept\n>            is called in the backends\n>          - change commit prefix to be more in line with project standards\n> \n>   builtin/branch.c          |  2 +-\n>   builtin/checkout.c        |  4 ++--\n>   builtin/clone.c           |  6 +++---\n>   builtin/notes.c           |  2 +-\n>   builtin/remote.c          |  6 +++---\n>   builtin/symbolic-ref.c    |  2 +-\n>   builtin/worktree.c        |  2 +-\n>   refs.c                    | 16 +++++++++++-----\n>   refs.h                    |  3 ++-\n>   reset.c                   |  2 +-\n>   sequencer.c               |  2 +-\n>   setup.c                   |  2 +-\n>   t/helper/test-ref-store.c |  2 +-\n>   13 files changed, 29 insertions(+), 22 deletions(-)\n> \n> diff --git a/builtin/branch.c b/builtin/branch.c\n> index fd1611ebf5..6c87690b58 100644\n> --- a/builtin/branch.c\n> +++ b/builtin/branch.c\n> @@ -559,7 +559,7 @@ static int replace_each_worktree_head_symref(struct worktree **worktrees,\n>   \t\t\tcontinue;\n>   \n>   \t\trefs = get_worktree_ref_store(worktrees[i]);\n> -\t\tif (refs_update_symref(refs, \"HEAD\", newref, logmsg))\n> +\t\tif (refs_update_symref(refs, \"HEAD\", newref, logmsg, NULL))\n>   \t\t\tret = error(_(\"HEAD of working tree %s is not updated\"),\n>   \t\t\t\t    worktrees[i]->path);\n>   \t}\n> diff --git a/builtin/checkout.c b/builtin/checkout.c\n> index 9c30000d3a..356ee9bcde 100644\n> --- a/builtin/checkout.c\n> +++ b/builtin/checkout.c\n> @@ -1015,7 +1015,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n>   \t\t\tdescribe_detached_head(_(\"HEAD is now at\"), new_branch_info->commit);\n>   \t\t}\n>   \t} else if (new_branch_info->path) {\t/* Switch branches. */\n> -\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", new_branch_info->path, msg.buf) < 0)\n> +\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", new_branch_info->path, msg.buf, NULL) < 0)\n>   \t\t\tdie(_(\"unable to update HEAD\"));\n>   \t\tif (!opts->quiet) {\n>   \t\t\tif (old_branch_info->path && !strcmp(new_branch_info->path, old_branch_info->path)) {\n> @@ -1479,7 +1479,7 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts)\n>   \t\tdie(_(\"You are on a branch yet to be born\"));\n>   \tstrbuf_addf(&branch_ref, \"refs/heads/%s\", opts->new_branch);\n>   \tstatus = refs_update_symref(get_main_ref_store(the_repository),\n> -\t\t\t\t    \"HEAD\", branch_ref.buf, \"checkout -b\");\n> +\t\t\t\t    \"HEAD\", branch_ref.buf, \"checkout -b\", NULL);\n>   \tstrbuf_release(&branch_ref);\n>   \tif (!opts->quiet)\n>   \t\tfprintf(stderr, _(\"Switched to a new branch '%s'\\n\"),\n> diff --git a/builtin/clone.c b/builtin/clone.c\n> index e77339c847..ead2af20ea 100644\n> --- a/builtin/clone.c\n> +++ b/builtin/clone.c\n> @@ -661,7 +661,7 @@ static void update_remote_refs(const struct ref *refs,\n>   \t\tstrbuf_addstr(&head_ref, \"HEAD\");\n>   \t\tif (refs_update_symref(get_main_ref_store(the_repository), head_ref.buf,\n>   \t\t\t\t       remote_head_points_at->peer_ref->name,\n> -\t\t\t\t       msg) < 0)\n> +\t\t\t\t       msg, NULL) < 0)\n>   \t\t\tdie(_(\"unable to update %s\"), head_ref.buf);\n>   \t\tstrbuf_release(&head_ref);\n>   \t}\n> @@ -673,7 +673,7 @@ static void update_head(const struct ref *our, const struct ref *remote,\n>   \tconst char *head;\n>   \tif (our && skip_prefix(our->name, \"refs/heads/\", &head)) {\n>   \t\t/* Local default branch link */\n> -\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", our->name, NULL) < 0)\n> +\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", our->name, NULL, NULL) < 0)\n>   \t\t\tdie(_(\"unable to update HEAD\"));\n>   \t\tif (!option_bare) {\n>   \t\t\trefs_update_ref(get_main_ref_store(the_repository),\n> @@ -702,7 +702,7 @@ static void update_head(const struct ref *our, const struct ref *remote,\n>   \t\t * Unborn head from remote; same as \"our\" case above except\n>   \t\t * that we have no ref to update.\n>   \t\t */\n> -\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", unborn, NULL) < 0)\n> +\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", unborn, NULL, NULL) < 0)\n>   \t\t\tdie(_(\"unable to update HEAD\"));\n>   \t\tif (!option_bare)\n>   \t\t\tinstall_branch_config(0, head, remote_name, unborn);\n> diff --git a/builtin/notes.c b/builtin/notes.c\n> index 8c26e45526..ba646f06ff 100644\n> --- a/builtin/notes.c\n> +++ b/builtin/notes.c\n> @@ -980,7 +980,7 @@ static int merge(int argc, const char **argv, const char *prefix)\n>   \t\t\tdie(_(\"a notes merge into %s is already in-progress at %s\"),\n>   \t\t\t    notes_ref, wt->path);\n>   \t\tfree_worktrees(worktrees);\n> -\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"NOTES_MERGE_REF\", notes_ref, NULL))\n> +\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"NOTES_MERGE_REF\", notes_ref, NULL, NULL))\n>   \t\t\tdie(_(\"failed to store link to current notes ref (%s)\"),\n>   \t\t\t    notes_ref);\n>   \t\tfprintf(stderr, _(\"Automatic notes merge failed. Fix conflicts in %s \"\n> diff --git a/builtin/remote.c b/builtin/remote.c\n> index 76670ddd8b..d8ff440027 100644\n> --- a/builtin/remote.c\n> +++ b/builtin/remote.c\n> @@ -244,7 +244,7 @@ static int add(int argc, const char **argv, const char *prefix)\n>   \t\tstrbuf_reset(&buf2);\n>   \t\tstrbuf_addf(&buf2, \"refs/remotes/%s/%s\", name, master);\n>   \n> -\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote add\"))\n> +\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote add\", NULL))\n>   \t\t\tresult = error(_(\"Could not setup master '%s'\"), master);\n>   \t}\n>   \n> @@ -864,7 +864,7 @@ static int mv(int argc, const char **argv, const char *prefix)\n>   \t\tstrbuf_reset(&buf3);\n>   \t\tstrbuf_addf(&buf3, \"remote: renamed %s to %s\",\n>   \t\t\t\titem->string, buf.buf);\n> -\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, buf3.buf))\n> +\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, buf3.buf, NULL))\n>   \t\t\tdie(_(\"creating '%s' failed\"), buf.buf);\n>   \t\tdisplay_progress(progress, ++refs_renamed_nr);\n>   \t}\n> @@ -1444,7 +1444,7 @@ static int set_head(int argc, const char **argv, const char *prefix)\n>   \t\t/* make sure it's valid */\n>   \t\tif (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))\n>   \t\t\tresult |= error(_(\"Not a valid ref: %s\"), buf2.buf);\n> -\t\telse if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote set-head\"))\n> +\t\telse if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote set-head\", NULL))\n>   \t\t\tresult |= error(_(\"Could not setup %s\"), buf.buf);\n>   \t\telse if (opt_a)\n>   \t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\n> diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c\n> index 299d23d76a..7728fbc3c1 100644\n> --- a/builtin/symbolic-ref.c\n> +++ b/builtin/symbolic-ref.c\n> @@ -88,7 +88,7 @@ int cmd_symbolic_ref(int argc,\n>   \t\tif (check_refname_format(argv[1], REFNAME_ALLOW_ONELEVEL) < 0)\n>   \t\t\tdie(\"Refusing to set '%s' to invalid ref '%s'\", argv[0], argv[1]);\n>   \t\tret = !!refs_update_symref(get_main_ref_store(the_repository),\n> -\t\t\t\t\t   argv[0], argv[1], msg);\n> +\t\t\t\t\t   argv[0], argv[1], msg, NULL);\n>   \t\tbreak;\n>   \tdefault:\n>   \t\tusage_with_options(git_symbolic_ref_usage, options);\n> diff --git a/builtin/worktree.c b/builtin/worktree.c\n> index fc31d072a6..a7ab4193c1 100644\n> --- a/builtin/worktree.c\n> +++ b/builtin/worktree.c\n> @@ -517,7 +517,7 @@ static int add_worktree(const char *path, const char *refname,\n>   \t\tret = refs_update_ref(wt_refs, NULL, \"HEAD\", &commit->object.oid,\n>   \t\t\t\t      NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n>   \telse\n> -\t\tret = refs_update_symref(wt_refs, \"HEAD\", symref.buf, NULL);\n> +\t\tret = refs_update_symref(wt_refs, \"HEAD\", symref.buf, NULL, NULL);\n>   \tif (ret)\n>   \t\tgoto done;\n>   \n> diff --git a/refs.c b/refs.c\n> index 5f729ed412..b964ac44d0 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -2114,7 +2114,8 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct\n>   }\n>   \n>   int refs_update_symref(struct ref_store *refs, const char *ref,\n> -\t\t       const char *target, const char *logmsg)\n> +\t\t       const char *target, const char *logmsg,\n> +\t\t       struct strbuf *referent)\n>   {\n>   \tstruct ref_transaction *transaction;\n>   \tstruct strbuf err = STRBUF_INIT;\n> @@ -2122,17 +2123,23 @@ int refs_update_symref(struct ref_store *refs, const char *ref,\n>   \n>   \ttransaction = ref_store_transaction_begin(refs, &err);\n>   \tif (!transaction ||\n> -\t    ref_transaction_update(transaction, ref, NULL, NULL,\n> +\t\tref_transaction_update(transaction, ref, NULL, NULL,\n>   \t\t\t\t   target, NULL, REF_NO_DEREF,\n>   \t\t\t\t   logmsg, &err) ||\n> -\t    ref_transaction_commit(transaction, &err)) {\n> +\t\tref_transaction_prepare(transaction, &err)) {\n>   \t\tret = error(\"%s\", err.buf);\n> +\t\tgoto cleanup;\n>   \t}\n> +\tif (referent)\n> +\t\trefs_read_symbolic_ref(refs, ref, referent);\n> +\n> +\tif (ref_transaction_commit(transaction, &err))\n> +\t\tret = error(\"%s\", err.buf);\n>   \n> +cleanup:\n>   \tstrbuf_release(&err);\n>   \tif (transaction)\n>   \t\tref_transaction_free(transaction);\n> -\n>   \treturn ret;\n>   }\n>   \n> @@ -2948,4 +2955,3 @@ int ref_update_expects_existing_old_ref(struct ref_update *update)\n>   \treturn (update->flags & REF_HAVE_OLD) &&\n>   \t\t(!is_null_oid(&update->old_oid) || update->old_target);\n>   }\n> -\n> diff --git a/refs.h b/refs.h\n> index 108dfc93b3..b09a3a4384 100644\n> --- a/refs.h\n> +++ b/refs.h\n> @@ -571,7 +571,8 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,\n>   \t\t    const char *newref, const char *logmsg);\n>   \n>   int refs_update_symref(struct ref_store *refs, const char *refname,\n> -\t\t       const char *target, const char *logmsg);\n> +\t\t       const char *target, const char *logmsg,\n> +\t\t       struct strbuf *referent);\n>   \n>   enum action_on_err {\n>   \tUPDATE_REFS_MSG_ON_ERR,\n> diff --git a/reset.c b/reset.c\n> index b22b1be792..cc36a9ed56 100644\n> --- a/reset.c\n> +++ b/reset.c\n> @@ -76,7 +76,7 @@ static int update_refs(const struct reset_head_opts *opts,\n>   \t\tif (!ret)\n>   \t\t\tret = refs_update_symref(get_main_ref_store(the_repository),\n>   \t\t\t\t\t\t \"HEAD\", switch_to_branch,\n> -\t\t\t\t\t\t reflog_head);\n> +\t\t\t\t\t\t reflog_head, NULL);\n>   \t}\n>   \tif (!ret && run_hook)\n>   \t\trun_hooks_l(the_repository, \"post-checkout\",\n> diff --git a/sequencer.c b/sequencer.c\n> index 8d01cd50ac..23b162924c 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -5107,7 +5107,7 @@ static int pick_commits(struct repository *r,\n>   \t\t\t}\n>   \t\t\tmsg = reflog_message(opts, \"finish\", \"returning to %s\",\n>   \t\t\t\thead_ref.buf);\n> -\t\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", head_ref.buf, msg)) {\n> +\t\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", head_ref.buf, msg, NULL)) {\n>   \t\t\t\tres = error(_(\"could not update HEAD to %s\"),\n>   \t\t\t\t\thead_ref.buf);\n>   \t\t\t\tgoto cleanup_head_ref;\n> diff --git a/setup.c b/setup.c\n> index 94e79b2e48..d95f051465 100644\n> --- a/setup.c\n> +++ b/setup.c\n> @@ -2275,7 +2275,7 @@ void create_reference_database(enum ref_storage_format ref_storage_format,\n>   \t\t\tdie(_(\"invalid initial branch name: '%s'\"),\n>   \t\t\t    initial_branch);\n>   \n> -\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", ref, NULL) < 0)\n> +\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", ref, NULL, NULL) < 0)\n>   \t\t\texit(1);\n>   \t\tfree(ref);\n>   \t}\n> diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c\n> index 65346dee55..a911302bea 100644\n> --- a/t/helper/test-ref-store.c\n> +++ b/t/helper/test-ref-store.c\n> @@ -120,7 +120,7 @@ static int cmd_create_symref(struct ref_store *refs, const char **argv)\n>   \tconst char *target = notnull(*argv++, \"target\");\n>   \tconst char *logmsg = *argv++;\n>   \n> -\treturn refs_update_symref(refs, refname, target, logmsg);\n> +\treturn refs_update_symref(refs, refname, target, logmsg, NULL);\n>   }\n>   \n>   static struct flag_definition transaction_flags[] = {\n\n"},{"id":"504948","messageId":"cf7c4831-a766-430c-aac6-5cd5c2ceabed@gmail.com","threadId":"62096","inReplyTo":"20241012230428.3259229-4-bence@ferdinandy.com","subject":"Re: [PATCH v7 4/6] refs: add TRANSACTION_CREATE_EXISTS error","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-10-13T14:03:45Z","receivedAt":"2024-10-13T14:03:49Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Bence\n\nOn 13/10/2024 00:03, Bence Ferdinandy wrote:\n> Currently there is only one special error for transaction, for when\n> there is a naming conflict, all other errors are dumped under a generic\n> error. Add a new special error case for when the caller requests the\n> reference to be updated only when it does not yet exist and the\n> reference actually does exist.\n\nThis looks like useful improvement. Are the changes to \nreftable-backend.c correct - it looks like where it previously returned \nTRANSACTION_GENERIC_ERR it now returns TRANSACTION_NAME_CONFLICT which I \nthink is used to indicate a file/directory conflict (e.g. trying to \ncreate refs/heads/topic/one when refs/heads/topic exists)\n\nBest Wishes\n\nPhillip\n\n> Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n> ---\n> \n> Notes:\n>      v4: new patch\n>      v5: no change\n>      v6: no change\n>      \n>      v7: - change commit prefix to be more in line with project standards\n>          - changed error checking to Karthik's suggestion\n> \n>   refs.h                  |  4 +++-\n>   refs/files-backend.c    | 24 ++++++++++++++++--------\n>   refs/reftable-backend.c |  6 ++++--\n>   3 files changed, 23 insertions(+), 11 deletions(-)\n> \n> diff --git a/refs.h b/refs.h\n> index b09a3a4384..c83b1ec76e 100644\n> --- a/refs.h\n> +++ b/refs.h\n> @@ -759,8 +759,10 @@ int ref_transaction_verify(struct ref_transaction *transaction,\n>   \n>   /* Naming conflict (for example, the ref names A and A/B conflict). */\n>   #define TRANSACTION_NAME_CONFLICT -1\n> +/* When only creation was requested, but the ref already exists. */\n> +#define TRANSACTION_CREATE_EXISTS -2\n>   /* All other errors. */\n> -#define TRANSACTION_GENERIC_ERROR -2\n> +#define TRANSACTION_GENERIC_ERROR -3\n>   \n>   /*\n>    * Perform the preparatory stages of committing `transaction`. Acquire\n> diff --git a/refs/files-backend.c b/refs/files-backend.c\n> index 0824c0b8a9..e743ec44b5 100644\n> --- a/refs/files-backend.c\n> +++ b/refs/files-backend.c\n> @@ -2502,14 +2502,18 @@ static int split_symref_update(struct ref_update *update,\n>   static int check_old_oid(struct ref_update *update, struct object_id *oid,\n>   \t\t\t struct strbuf *err)\n>   {\n> +\tint ret = TRANSACTION_GENERIC_ERROR;\n> +\n>   \tif (!(update->flags & REF_HAVE_OLD) ||\n>   \t\t   oideq(oid, &update->old_oid))\n>   \t\treturn 0;\n>   \n> -\tif (is_null_oid(&update->old_oid))\n> +\tif (is_null_oid(&update->old_oid)) {\n>   \t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n>   \t\t\t    \"reference already exists\",\n>   \t\t\t    ref_update_original_update_refname(update));\n> +\t\tret = TRANSACTION_CREATE_EXISTS;\n> +\t}\n>   \telse if (is_null_oid(oid))\n>   \t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n>   \t\t\t    \"reference is missing but expected %s\",\n> @@ -2522,7 +2526,7 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid,\n>   \t\t\t    oid_to_hex(oid),\n>   \t\t\t    oid_to_hex(&update->old_oid));\n>   \n> -\treturn -1;\n> +\treturn ret;\n>   }\n>   \n>   /*\n> @@ -2602,9 +2606,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n>   \t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n>   \t\t\t\t\tgoto out;\n>   \t\t\t\t}\n> -\t\t\t} else if  (check_old_oid(update, &lock->old_oid, err)) {\n> -\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n> -\t\t\t\tgoto out;\n> +\t\t\t} else {\n> +\t\t\t\tret = check_old_oid(update, &lock->old_oid, err);\n> +\t\t\t\tif  (ret) {\n> +\t\t\t\t\tgoto out;\n> +\t\t\t\t}\n>   \t\t\t}\n>   \t\t} else {\n>   \t\t\t/*\n> @@ -2635,9 +2641,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n>   \t\t\t\t    update->old_target);\n>   \t\t\tret = TRANSACTION_GENERIC_ERROR;\n>   \t\t\tgoto out;\n> -\t\t} else if  (check_old_oid(update, &lock->old_oid, err)) {\n> -\t\t\tret = TRANSACTION_GENERIC_ERROR;\n> -\t\t\tgoto out;\n> +\t\t} else {\n> +\t\t\tret = check_old_oid(update, &lock->old_oid, err);\n> +\t\t\tif  (ret) {\n> +\t\t\t\tgoto out;\n> +\t\t\t}\n>   \t\t}\n>   \n>   \t\t/*\n> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\n> index 3c96fbf66f..ebf8e57fbc 100644\n> --- a/refs/reftable-backend.c\n> +++ b/refs/reftable-backend.c\n> @@ -1206,10 +1206,13 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n>   \t\t\t\tgoto done;\n>   \t\t\t}\n>   \t\t} else if ((u->flags & REF_HAVE_OLD) && !oideq(&current_oid, &u->old_oid)) {\n> -\t\t\tif (is_null_oid(&u->old_oid))\n> +\t\t\tret = TRANSACTION_NAME_CONFLICT;\n> +\t\t\tif (is_null_oid(&u->old_oid)) {\n>   \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n>   \t\t\t\t\t\t   \"reference already exists\"),\n>   \t\t\t\t\t    ref_update_original_update_refname(u));\n> +\t\t\t\tret = TRANSACTION_CREATE_EXISTS;\n> +\t\t\t}\n>   \t\t\telse if (is_null_oid(&current_oid))\n>   \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n>   \t\t\t\t\t\t   \"reference is missing but expected %s\"),\n> @@ -1221,7 +1224,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n>   \t\t\t\t\t    ref_update_original_update_refname(u),\n>   \t\t\t\t\t    oid_to_hex(&current_oid),\n>   \t\t\t\t\t    oid_to_hex(&u->old_oid));\n> -\t\t\tret = -1;\n>   \t\t\tgoto done;\n>   \t\t}\n>   \n\n"},{"id":"504957","messageId":"D4UZ5DJWM3WM.MYD9YL6SLE@ferdinandy.com","threadId":"62096","inReplyTo":"cf7c4831-a766-430c-aac6-5cd5c2ceabed@gmail.com","subject":"Re: [PATCH v7 4/6] refs: add TRANSACTION_CREATE_EXISTS error","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-13T20:52:22Z","receivedAt":"2024-10-13T20:52:49Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Sun Oct 13, 2024 at 16:03, Phillip Wood <phillip.wood123@gmail.com> wrote:\n> Hi Bence\n>\n> On 13/10/2024 00:03, Bence Ferdinandy wrote:\n>> Currently there is only one special error for transaction, for when\n>> there is a naming conflict, all other errors are dumped under a generic\n>> error. Add a new special error case for when the caller requests the\n>> reference to be updated only when it does not yet exist and the\n>> reference actually does exist.\n>\n> This looks like useful improvement. Are the changes to \n> reftable-backend.c correct - it looks like where it previously returned \n> TRANSACTION_GENERIC_ERR it now returns TRANSACTION_NAME_CONFLICT which I \n> think is used to indicate a file/directory conflict (e.g. trying to \n> create refs/heads/topic/one when refs/heads/topic exists)\n\nThis passes:\nGIT_TEST_DEFAULT_REF_FORMAT=reftable prove --timer --jobs 8 ./t[0-9]*.sh\nso I'm hoping it's correct :)\n\n[snip]\n\nI guess you are referring to this part below:\n\n>> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\n>> index 3c96fbf66f..ebf8e57fbc 100644\n>> --- a/refs/reftable-backend.c\n>> +++ b/refs/reftable-backend.c\n>> @@ -1206,10 +1206,13 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n>>   \t\t\t\tgoto done;\n>>   \t\t\t}\n>>   \t\t} else if ((u->flags & REF_HAVE_OLD) && !oideq(&current_oid, &u->old_oid)) {\n>> -\t\t\tif (is_null_oid(&u->old_oid))\n>> +\t\t\tret = TRANSACTION_NAME_CONFLICT;\n>> +\t\t\tif (is_null_oid(&u->old_oid)) {\n>>   \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n>>   \t\t\t\t\t\t   \"reference already exists\"),\n>>   \t\t\t\t\t    ref_update_original_update_refname(u));\n>> +\t\t\t\tret = TRANSACTION_CREATE_EXISTS;\n>> +\t\t\t}\n>>   \t\t\telse if (is_null_oid(&current_oid))\n>>   \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n>>   \t\t\t\t\t\t   \"reference is missing but expected %s\"),\n>> @@ -1221,7 +1224,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n>>   \t\t\t\t\t    ref_update_original_update_refname(u),\n>>   \t\t\t\t\t    oid_to_hex(&current_oid),\n>>   \t\t\t\t\t    oid_to_hex(&u->old_oid));\n>> -\t\t\tret = -1;\n>>   \t\t\tgoto done;\n>>   \t\t}\n>>   \n\nThis originally returned -1, and it still returns that if it doesn't return -2,\nI just used the named variable instead of the integer itself. It might still be\nthat this should be -3 GENERIC_ERROR, but if that is the case I think fixing\nthat should be a different patch? I didn't check if changing that -1 to\nsomething else breaks anything or not.\n\nBest,\nBence\n\n-- \nbence.ferdinandy.com\n\n"},{"id":"504958","messageId":"D4UZTVHXXL1X.2KBQI9FYTAU72@ferdinandy.com","threadId":"62096","inReplyTo":"3c848f10-ac14-4cd7-8fbe-868da9599244@gmail.com","subject":"Re: [PATCH v7 1/6] refs: atomically record overwritten ref in update_symref","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-13T21:24:22Z","receivedAt":"2024-10-13T21:24:58Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Sun Oct 13, 2024 at 15:52, Phillip Wood <phillip.wood123@gmail.com> wrote:\n> Hi Bence\n>\n> On 13/10/2024 00:03, Bence Ferdinandy wrote:\n>> When updating a symref it's currently not possible to know for sure what\n>> was the previous value that was overwritten.\n>\n> It is if you use a ref transaction rather than call refs_update_symref() \n> and query the ref after calling ref_transaction_prepare() and before \n> calling ref_transaction_commit() which is what the code below does.\n\nYeah, it would be more clear if that sentence would say \"when using\nupdate_symref\".\n\n>\n>> Record the value after the\n>> ref has been locked if the caller of refs_update_symref requests it via\n>> a new variable in the function call.\n>\n> To me this patch and patch 5 feel quite disruptive to all the existing \n> callers which don't need this specialized functionality. I think it \n> would be less disruptive over all if you used a ref transaction rather \n> than calling refs_update_symref() in the final patch. That would enable \n> us to keep the simpler interface for refs_update_symref().\n\nThe extra parameter introduced here is actually used in two places by the end\nof the series, in remote set-head and fetch (of course you could make a similar\nargument for the functionality added in 5/6 which is only used in fetch by the\nfinal patch). To avoid code duplication I think even if we did not touch\nrefs_update_symref() it would make sense to create\na \"refs_update_symref_extended()\" and make refs_update_symref() a wrapper\naround that with a few less parameters. That would be similar to how\nrefs_update_symref() and refs_update_ref() predetermine a couple of parameters\nto say transaction_update().\n\nCurrently there are 15 calls to refs_update_symref() in total, of these \n5 do not use the complete functionality of the function (they pass NULL as\nlogmsg), so the current implementation would not be completely unprecedented.\n(This tally did make me catch an error on my side: the logmsg in fetch's\nset_head should be \"fetch\" not \"remote set-head\", I'll fix that in a v8).\n\nImho, even if I manage to come up with a better name than\n\"refs_update_symref_extended()\" wouldn't it be more confusing to have two ways\nto update symrefs via a function call, rather than have one, where _usually_\nyou pass two NULL-s at the end?\n\n>\n> I'm also not sure about the proposed interface I would have thought it \n> would be simpler to take a \"char**\" rather than an \"struct strbuf*\" if \n> we do decide that it is useful for callers of refs_update_symref() to \n> query the old value.\n\nrefs_read_symbolic_ref requires a strbuf, so one would need to be created\nanyway and I also sort of got the feeling that the project likes to handle refs\nin strbufs (which may be wrong). Are there any downsides I'm not seeing?\n\nThanks,\nBence\n\n"},{"id":"504974","messageId":"509dffdb-ea4a-4fbc-8769-0b2e181c952b@gmail.com","threadId":"62096","inReplyTo":"D4UZ5DJWM3WM.MYD9YL6SLE@ferdinandy.com","subject":"Re: [PATCH v7 4/6] refs: add TRANSACTION_CREATE_EXISTS error","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-10-14T08:48:10Z","receivedAt":"2024-10-14T08:48:16Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Bence\n\nOn 13/10/2024 21:52, Bence Ferdinandy wrote:\n> On Sun Oct 13, 2024 at 16:03, Phillip Wood <phillip.wood123@gmail.com> wrote:\n\n>>> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\n>>> index 3c96fbf66f..ebf8e57fbc 100644\n>>> --- a/refs/reftable-backend.c\n>>> +++ b/refs/reftable-backend.c\n>>> @@ -1206,10 +1206,13 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n>>>    \t\t\t\tgoto done;\n>>>    \t\t\t}\n>>>    \t\t} else if ((u->flags & REF_HAVE_OLD) && !oideq(&current_oid, &u->old_oid)) {\n>>> -\t\t\tif (is_null_oid(&u->old_oid))\n>>> +\t\t\tret = TRANSACTION_NAME_CONFLICT;\n>>> +\t\t\tif (is_null_oid(&u->old_oid)) {\n>>>    \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n>>>    \t\t\t\t\t\t   \"reference already exists\"),\n>>>    \t\t\t\t\t    ref_update_original_update_refname(u));\n>>> +\t\t\t\tret = TRANSACTION_CREATE_EXISTS;\n>>> +\t\t\t}\n>>>    \t\t\telse if (is_null_oid(&current_oid))\n>>>    \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n>>>    \t\t\t\t\t\t   \"reference is missing but expected %s\"),\n>>> @@ -1221,7 +1224,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n>>>    \t\t\t\t\t    ref_update_original_update_refname(u),\n>>>    \t\t\t\t\t    oid_to_hex(&current_oid),\n>>>    \t\t\t\t\t    oid_to_hex(&u->old_oid));\n>>> -\t\t\tret = -1;\n>>>    \t\t\tgoto done;\n>>>    \t\t}\n>>>    \n> \n> This originally returned -1, and it still returns that if it doesn't return -2,\n> I just used the named variable instead of the integer itself. It might still be\n> that this should be -3 GENERIC_ERROR, but if that is the case I think fixing\n> that should be a different patch? I didn't check if changing that -1 to\n> something else breaks anything or not.\n\nOh sorry I was confused and thought TRANSACTION_GENERIC_ERROR was -1.\n\nBest Wishes\n\nPhillip\n\n> Best,\n> Bence\n> \n\n"},{"id":"505091","messageId":"20241014225431.1394565-2-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241014225431.1394565-1-bence@ferdinandy.com","subject":"[PATCH v8 1/6] refs: atomically record overwritten ref in update_symref","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-14T22:53:10Z","receivedAt":"2024-10-14T22:55:30Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"When updating a symref it's currently not possible to know for sure what\nwas the previous value that was overwritten. Record the value after the\nref has been locked if the caller of refs_update_symref requests it via\na new variable in the function call.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v4: new patch\n    \n    v5: - added before_target to reftables backend\n        - added an extra safety check for transaction's existence in refs.c\n    \n    v6: - no change\n    \n    v7: - remove the whole before_target concept from the backends and\n          handle checking it in refs.c instead (thanks Karthik)\n        - rename the before_target to referent which is how the same concept\n          is called in the backends\n        - change commit prefix to be more in line with project standards\n    \n    v8: no change\n\n builtin/branch.c          |  2 +-\n builtin/checkout.c        |  4 ++--\n builtin/clone.c           |  6 +++---\n builtin/notes.c           |  2 +-\n builtin/remote.c          |  6 +++---\n builtin/symbolic-ref.c    |  2 +-\n builtin/worktree.c        |  2 +-\n refs.c                    | 16 +++++++++++-----\n refs.h                    |  3 ++-\n reset.c                   |  2 +-\n sequencer.c               |  2 +-\n setup.c                   |  2 +-\n t/helper/test-ref-store.c |  2 +-\n 13 files changed, 29 insertions(+), 22 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex fd1611ebf5..6c87690b58 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -559,7 +559,7 @@ static int replace_each_worktree_head_symref(struct worktree **worktrees,\n \t\t\tcontinue;\n \n \t\trefs = get_worktree_ref_store(worktrees[i]);\n-\t\tif (refs_update_symref(refs, \"HEAD\", newref, logmsg))\n+\t\tif (refs_update_symref(refs, \"HEAD\", newref, logmsg, NULL))\n \t\t\tret = error(_(\"HEAD of working tree %s is not updated\"),\n \t\t\t\t    worktrees[i]->path);\n \t}\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 9c30000d3a..356ee9bcde 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1015,7 +1015,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\t\tdescribe_detached_head(_(\"HEAD is now at\"), new_branch_info->commit);\n \t\t}\n \t} else if (new_branch_info->path) {\t/* Switch branches. */\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", new_branch_info->path, msg.buf) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", new_branch_info->path, msg.buf, NULL) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t\tif (!opts->quiet) {\n \t\t\tif (old_branch_info->path && !strcmp(new_branch_info->path, old_branch_info->path)) {\n@@ -1479,7 +1479,7 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts)\n \t\tdie(_(\"You are on a branch yet to be born\"));\n \tstrbuf_addf(&branch_ref, \"refs/heads/%s\", opts->new_branch);\n \tstatus = refs_update_symref(get_main_ref_store(the_repository),\n-\t\t\t\t    \"HEAD\", branch_ref.buf, \"checkout -b\");\n+\t\t\t\t    \"HEAD\", branch_ref.buf, \"checkout -b\", NULL);\n \tstrbuf_release(&branch_ref);\n \tif (!opts->quiet)\n \t\tfprintf(stderr, _(\"Switched to a new branch '%s'\\n\"),\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex e77339c847..ead2af20ea 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -661,7 +661,7 @@ static void update_remote_refs(const struct ref *refs,\n \t\tstrbuf_addstr(&head_ref, \"HEAD\");\n \t\tif (refs_update_symref(get_main_ref_store(the_repository), head_ref.buf,\n \t\t\t\t       remote_head_points_at->peer_ref->name,\n-\t\t\t\t       msg) < 0)\n+\t\t\t\t       msg, NULL) < 0)\n \t\t\tdie(_(\"unable to update %s\"), head_ref.buf);\n \t\tstrbuf_release(&head_ref);\n \t}\n@@ -673,7 +673,7 @@ static void update_head(const struct ref *our, const struct ref *remote,\n \tconst char *head;\n \tif (our && skip_prefix(our->name, \"refs/heads/\", &head)) {\n \t\t/* Local default branch link */\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", our->name, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", our->name, NULL, NULL) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t\tif (!option_bare) {\n \t\t\trefs_update_ref(get_main_ref_store(the_repository),\n@@ -702,7 +702,7 @@ static void update_head(const struct ref *our, const struct ref *remote,\n \t\t * Unborn head from remote; same as \"our\" case above except\n \t\t * that we have no ref to update.\n \t\t */\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", unborn, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", unborn, NULL, NULL) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t\tif (!option_bare)\n \t\t\tinstall_branch_config(0, head, remote_name, unborn);\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex 8c26e45526..ba646f06ff 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -980,7 +980,7 @@ static int merge(int argc, const char **argv, const char *prefix)\n \t\t\tdie(_(\"a notes merge into %s is already in-progress at %s\"),\n \t\t\t    notes_ref, wt->path);\n \t\tfree_worktrees(worktrees);\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"NOTES_MERGE_REF\", notes_ref, NULL))\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"NOTES_MERGE_REF\", notes_ref, NULL, NULL))\n \t\t\tdie(_(\"failed to store link to current notes ref (%s)\"),\n \t\t\t    notes_ref);\n \t\tfprintf(stderr, _(\"Automatic notes merge failed. Fix conflicts in %s \"\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 76670ddd8b..d8ff440027 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -244,7 +244,7 @@ static int add(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_reset(&buf2);\n \t\tstrbuf_addf(&buf2, \"refs/remotes/%s/%s\", name, master);\n \n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote add\"))\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote add\", NULL))\n \t\t\tresult = error(_(\"Could not setup master '%s'\"), master);\n \t}\n \n@@ -864,7 +864,7 @@ static int mv(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_reset(&buf3);\n \t\tstrbuf_addf(&buf3, \"remote: renamed %s to %s\",\n \t\t\t\titem->string, buf.buf);\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, buf3.buf))\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, buf3.buf, NULL))\n \t\t\tdie(_(\"creating '%s' failed\"), buf.buf);\n \t\tdisplay_progress(progress, ++refs_renamed_nr);\n \t}\n@@ -1444,7 +1444,7 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\t/* make sure it's valid */\n \t\tif (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))\n \t\t\tresult |= error(_(\"Not a valid ref: %s\"), buf2.buf);\n-\t\telse if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote set-head\"))\n+\t\telse if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote set-head\", NULL))\n \t\t\tresult |= error(_(\"Could not setup %s\"), buf.buf);\n \t\telse if (opt_a)\n \t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\ndiff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c\nindex 299d23d76a..7728fbc3c1 100644\n--- a/builtin/symbolic-ref.c\n+++ b/builtin/symbolic-ref.c\n@@ -88,7 +88,7 @@ int cmd_symbolic_ref(int argc,\n \t\tif (check_refname_format(argv[1], REFNAME_ALLOW_ONELEVEL) < 0)\n \t\t\tdie(\"Refusing to set '%s' to invalid ref '%s'\", argv[0], argv[1]);\n \t\tret = !!refs_update_symref(get_main_ref_store(the_repository),\n-\t\t\t\t\t   argv[0], argv[1], msg);\n+\t\t\t\t\t   argv[0], argv[1], msg, NULL);\n \t\tbreak;\n \tdefault:\n \t\tusage_with_options(git_symbolic_ref_usage, options);\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex fc31d072a6..a7ab4193c1 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -517,7 +517,7 @@ static int add_worktree(const char *path, const char *refname,\n \t\tret = refs_update_ref(wt_refs, NULL, \"HEAD\", &commit->object.oid,\n \t\t\t\t      NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n \telse\n-\t\tret = refs_update_symref(wt_refs, \"HEAD\", symref.buf, NULL);\n+\t\tret = refs_update_symref(wt_refs, \"HEAD\", symref.buf, NULL, NULL);\n \tif (ret)\n \t\tgoto done;\n \ndiff --git a/refs.c b/refs.c\nindex 5f729ed412..b964ac44d0 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2114,7 +2114,8 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct\n }\n \n int refs_update_symref(struct ref_store *refs, const char *ref,\n-\t\t       const char *target, const char *logmsg)\n+\t\t       const char *target, const char *logmsg,\n+\t\t       struct strbuf *referent)\n {\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n@@ -2122,17 +2123,23 @@ int refs_update_symref(struct ref_store *refs, const char *ref,\n \n \ttransaction = ref_store_transaction_begin(refs, &err);\n \tif (!transaction ||\n-\t    ref_transaction_update(transaction, ref, NULL, NULL,\n+\t\tref_transaction_update(transaction, ref, NULL, NULL,\n \t\t\t\t   target, NULL, REF_NO_DEREF,\n \t\t\t\t   logmsg, &err) ||\n-\t    ref_transaction_commit(transaction, &err)) {\n+\t\tref_transaction_prepare(transaction, &err)) {\n \t\tret = error(\"%s\", err.buf);\n+\t\tgoto cleanup;\n \t}\n+\tif (referent)\n+\t\trefs_read_symbolic_ref(refs, ref, referent);\n+\n+\tif (ref_transaction_commit(transaction, &err))\n+\t\tret = error(\"%s\", err.buf);\n \n+cleanup:\n \tstrbuf_release(&err);\n \tif (transaction)\n \t\tref_transaction_free(transaction);\n-\n \treturn ret;\n }\n \n@@ -2948,4 +2955,3 @@ int ref_update_expects_existing_old_ref(struct ref_update *update)\n \treturn (update->flags & REF_HAVE_OLD) &&\n \t\t(!is_null_oid(&update->old_oid) || update->old_target);\n }\n-\ndiff --git a/refs.h b/refs.h\nindex 108dfc93b3..b09a3a4384 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -571,7 +571,8 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,\n \t\t    const char *newref, const char *logmsg);\n \n int refs_update_symref(struct ref_store *refs, const char *refname,\n-\t\t       const char *target, const char *logmsg);\n+\t\t       const char *target, const char *logmsg,\n+\t\t       struct strbuf *referent);\n \n enum action_on_err {\n \tUPDATE_REFS_MSG_ON_ERR,\ndiff --git a/reset.c b/reset.c\nindex b22b1be792..cc36a9ed56 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -76,7 +76,7 @@ static int update_refs(const struct reset_head_opts *opts,\n \t\tif (!ret)\n \t\t\tret = refs_update_symref(get_main_ref_store(the_repository),\n \t\t\t\t\t\t \"HEAD\", switch_to_branch,\n-\t\t\t\t\t\t reflog_head);\n+\t\t\t\t\t\t reflog_head, NULL);\n \t}\n \tif (!ret && run_hook)\n \t\trun_hooks_l(the_repository, \"post-checkout\",\ndiff --git a/sequencer.c b/sequencer.c\nindex 8d01cd50ac..23b162924c 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -5107,7 +5107,7 @@ static int pick_commits(struct repository *r,\n \t\t\t}\n \t\t\tmsg = reflog_message(opts, \"finish\", \"returning to %s\",\n \t\t\t\thead_ref.buf);\n-\t\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", head_ref.buf, msg)) {\n+\t\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", head_ref.buf, msg, NULL)) {\n \t\t\t\tres = error(_(\"could not update HEAD to %s\"),\n \t\t\t\t\thead_ref.buf);\n \t\t\t\tgoto cleanup_head_ref;\ndiff --git a/setup.c b/setup.c\nindex 94e79b2e48..d95f051465 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -2275,7 +2275,7 @@ void create_reference_database(enum ref_storage_format ref_storage_format,\n \t\t\tdie(_(\"invalid initial branch name: '%s'\"),\n \t\t\t    initial_branch);\n \n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", ref, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", ref, NULL, NULL) < 0)\n \t\t\texit(1);\n \t\tfree(ref);\n \t}\ndiff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c\nindex 65346dee55..a911302bea 100644\n--- a/t/helper/test-ref-store.c\n+++ b/t/helper/test-ref-store.c\n@@ -120,7 +120,7 @@ static int cmd_create_symref(struct ref_store *refs, const char **argv)\n \tconst char *target = notnull(*argv++, \"target\");\n \tconst char *logmsg = *argv++;\n \n-\treturn refs_update_symref(refs, refname, target, logmsg);\n+\treturn refs_update_symref(refs, refname, target, logmsg, NULL);\n }\n \n static struct flag_definition transaction_flags[] = {\n-- \n2.47.0.7.g072c39eddb.dirty\n\n"},{"id":"505092","messageId":"20241014225431.1394565-1-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241012230428.3259229-1-bence@ferdinandy.com","subject":"[PATCH v8 0/6] set-head/fetch remote/HEAD updates, small change from v7","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-14T22:53:09Z","receivedAt":"2024-10-14T22:55:31Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"v8 is minor non-functional fix from v7\n\n\n❯ git range-diff v7~6..v7 v8~6..v8\n1:  e4a2d01bca = 1:  e4a2d01bca refs: atomically record overwritten ref in update_symref\n2:  e6c40ef04c = 2:  e6c40ef04c remote set-head: add new variable for readability\n3:  aefa49aa0d = 3:  aefa49aa0d remote set-head: better output for --auto\n4:  ad8907c6a3 = 4:  ad8907c6a3 refs: add TRANSACTION_CREATE_EXISTS error\n5:  ff616d8b77 = 5:  ff616d8b77 refs: add create_only option to refs_update_symref\n6:  35b93f454c ! 6:  7b844b1dbb fetch: set remote/HEAD if it does not exist\n    @@ Notes\n\n         v7: - no change\n\n    +    v8: - changed logmsg in call to refs_update_symref from \"remote\n    +          set-head\" to \"fetch\"\n    +\n      ## builtin/fetch.c ##\n     @@ builtin/fetch.c: static int backfill_tags(struct display_state *display_state,\n        return retcode;\n    @@ builtin/fetch.c: static int backfill_tags(struct display_state *display_state,\n     +          if (!refs_ref_exists(refs, b_remote_head.buf))\n     +                  result = 1;\n     +          else if (refs_update_symref(refs, b_head.buf, b_remote_head.buf,\n    -+                                  \"remote set-head\", &b_local_head, 1))\n    ++                                  \"fetch\", &b_local_head, 1))\n     +                  result = 1;\n     +          else\n     +                  report_set_head(remote, head_name, &b_local_head);\n\nBence Ferdinandy (6):\n  refs: atomically record overwritten ref in update_symref\n  remote set-head: add new variable for readability\n  remote set-head: better output for --auto\n  refs: add TRANSACTION_CREATE_EXISTS error\n  refs: add create_only option to refs_update_symref\n  fetch: set remote/HEAD if it does not exist\n\n builtin/branch.c                 |   2 +-\n builtin/checkout.c               |   5 +-\n builtin/clone.c                  |   8 +-\n builtin/fetch.c                  |  82 +++++++++++\n builtin/notes.c                  |   3 +-\n builtin/remote.c                 |  47 +++++--\n builtin/symbolic-ref.c           |   2 +-\n builtin/worktree.c               |   2 +-\n refs.c                           |  38 +++--\n refs.h                           |   7 +-\n refs/files-backend.c             |  24 ++--\n refs/reftable-backend.c          |   6 +-\n reset.c                          |   2 +-\n sequencer.c                      |   3 +-\n setup.c                          |   3 +-\n t/helper/test-ref-store.c        |   2 +-\n t/t4207-log-decoration-colors.sh |   3 +-\n t/t5505-remote.sh                |  62 ++++++++-\n t/t5510-fetch.sh                 | 229 ++++++++++++++++---------------\n t/t5512-ls-remote.sh             |   2 +\n t/t5514-fetch-multiple.sh        |  17 ++-\n t/t5516-fetch-push.sh            |   3 +-\n t/t5527-fetch-odd-refs.sh        |   3 +-\n t/t7900-maintenance.sh           |   3 +-\n t/t9210-scalar.sh                |   5 +-\n t/t9211-scalar-clone.sh          |   6 +-\n t/t9902-completion.sh            |  65 +++++++++\n 27 files changed, 469 insertions(+), 165 deletions(-)\n\n-- \n2.47.0.7.g072c39eddb.dirty\n\n"},{"id":"505093","messageId":"20241014225431.1394565-3-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241014225431.1394565-1-bence@ferdinandy.com","subject":"[PATCH v8 2/6] remote set-head: add new variable for readability","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-14T22:53:11Z","receivedAt":"2024-10-14T22:55:37Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Instead of calling get_main_ref_store(the_repository) multiple times,\ncall it once and store in a new refs variable. Although this change\nprobably offers some performance benefits, the main purpose is to\nshorten the line lengths of function calls using this variable for\nbetter readability.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v5: new patch (split from the next patch as a preparatory step)\n    \n    v6: no change\n    \n    v7: - change commit prefix to be more in line with project standards\n    \n    v8: no change\n\n builtin/remote.c | 7 ++++---\n 1 file changed, 4 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex d8ff440027..353ffd2c43 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1404,6 +1404,7 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \tint i, opt_a = 0, opt_d = 0, result = 0;\n \tstruct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;\n \tchar *head_name = NULL;\n+\tstruct ref_store *refs = get_main_ref_store(the_repository);\n \n \tstruct option options[] = {\n \t\tOPT_BOOL('a', \"auto\", &opt_a,\n@@ -1434,7 +1435,7 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\t\thead_name = xstrdup(states.heads.items[0].string);\n \t\tfree_remote_ref_states(&states);\n \t} else if (opt_d && !opt_a && argc == 1) {\n-\t\tif (refs_delete_ref(get_main_ref_store(the_repository), NULL, buf.buf, NULL, REF_NO_DEREF))\n+\t\tif (refs_delete_ref(refs, NULL, buf.buf, NULL, REF_NO_DEREF))\n \t\t\tresult |= error(_(\"Could not delete %s\"), buf.buf);\n \t} else\n \t\tusage_with_options(builtin_remote_sethead_usage, options);\n@@ -1442,9 +1443,9 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \tif (head_name) {\n \t\tstrbuf_addf(&buf2, \"refs/remotes/%s/%s\", argv[0], head_name);\n \t\t/* make sure it's valid */\n-\t\tif (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))\n+\t\tif (!refs_ref_exists(refs, buf2.buf))\n \t\t\tresult |= error(_(\"Not a valid ref: %s\"), buf2.buf);\n-\t\telse if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote set-head\", NULL))\n+\t\telse if (refs_update_symref(refs, buf.buf, buf2.buf, \"remote set-head\", NULL))\n \t\t\tresult |= error(_(\"Could not setup %s\"), buf.buf);\n \t\telse if (opt_a)\n \t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\n-- \n2.47.0.7.g072c39eddb.dirty\n\n"},{"id":"505094","messageId":"20241014225431.1394565-5-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241014225431.1394565-1-bence@ferdinandy.com","subject":"[PATCH v8 4/6] refs: add TRANSACTION_CREATE_EXISTS error","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-14T22:53:13Z","receivedAt":"2024-10-14T22:55:40Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Currently there is only one special error for transaction, for when\nthere is a naming conflict, all other errors are dumped under a generic\nerror. Add a new special error case for when the caller requests the\nreference to be updated only when it does not yet exist and the\nreference actually does exist.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v4: new patch\n    v5: no change\n    v6: no change\n    \n    v7: - change commit prefix to be more in line with project standards\n        - changed error checking to Karthik's suggestion\n    \n    v8: no change\n\n refs.h                  |  4 +++-\n refs/files-backend.c    | 24 ++++++++++++++++--------\n refs/reftable-backend.c |  6 ++++--\n 3 files changed, 23 insertions(+), 11 deletions(-)\n\ndiff --git a/refs.h b/refs.h\nindex b09a3a4384..c83b1ec76e 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -759,8 +759,10 @@ int ref_transaction_verify(struct ref_transaction *transaction,\n \n /* Naming conflict (for example, the ref names A and A/B conflict). */\n #define TRANSACTION_NAME_CONFLICT -1\n+/* When only creation was requested, but the ref already exists. */\n+#define TRANSACTION_CREATE_EXISTS -2\n /* All other errors. */\n-#define TRANSACTION_GENERIC_ERROR -2\n+#define TRANSACTION_GENERIC_ERROR -3\n \n /*\n  * Perform the preparatory stages of committing `transaction`. Acquire\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 0824c0b8a9..e743ec44b5 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2502,14 +2502,18 @@ static int split_symref_update(struct ref_update *update,\n static int check_old_oid(struct ref_update *update, struct object_id *oid,\n \t\t\t struct strbuf *err)\n {\n+\tint ret = TRANSACTION_GENERIC_ERROR;\n+\n \tif (!(update->flags & REF_HAVE_OLD) ||\n \t\t   oideq(oid, &update->old_oid))\n \t\treturn 0;\n \n-\tif (is_null_oid(&update->old_oid))\n+\tif (is_null_oid(&update->old_oid)) {\n \t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n \t\t\t    \"reference already exists\",\n \t\t\t    ref_update_original_update_refname(update));\n+\t\tret = TRANSACTION_CREATE_EXISTS;\n+\t}\n \telse if (is_null_oid(oid))\n \t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n \t\t\t    \"reference is missing but expected %s\",\n@@ -2522,7 +2526,7 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid,\n \t\t\t    oid_to_hex(oid),\n \t\t\t    oid_to_hex(&update->old_oid));\n \n-\treturn -1;\n+\treturn ret;\n }\n \n /*\n@@ -2602,9 +2606,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\t\tgoto out;\n \t\t\t\t}\n-\t\t\t} else if  (check_old_oid(update, &lock->old_oid, err)) {\n-\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n-\t\t\t\tgoto out;\n+\t\t\t} else {\n+\t\t\t\tret = check_old_oid(update, &lock->old_oid, err);\n+\t\t\t\tif  (ret) {\n+\t\t\t\t\tgoto out;\n+\t\t\t\t}\n \t\t\t}\n \t\t} else {\n \t\t\t/*\n@@ -2635,9 +2641,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\t\t    update->old_target);\n \t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\tgoto out;\n-\t\t} else if  (check_old_oid(update, &lock->old_oid, err)) {\n-\t\t\tret = TRANSACTION_GENERIC_ERROR;\n-\t\t\tgoto out;\n+\t\t} else {\n+\t\t\tret = check_old_oid(update, &lock->old_oid, err);\n+\t\t\tif  (ret) {\n+\t\t\t\tgoto out;\n+\t\t\t}\n \t\t}\n \n \t\t/*\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 3c96fbf66f..ebf8e57fbc 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1206,10 +1206,13 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\tgoto done;\n \t\t\t}\n \t\t} else if ((u->flags & REF_HAVE_OLD) && !oideq(&current_oid, &u->old_oid)) {\n-\t\t\tif (is_null_oid(&u->old_oid))\n+\t\t\tret = TRANSACTION_NAME_CONFLICT;\n+\t\t\tif (is_null_oid(&u->old_oid)) {\n \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n \t\t\t\t\t\t   \"reference already exists\"),\n \t\t\t\t\t    ref_update_original_update_refname(u));\n+\t\t\t\tret = TRANSACTION_CREATE_EXISTS;\n+\t\t\t}\n \t\t\telse if (is_null_oid(&current_oid))\n \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n \t\t\t\t\t\t   \"reference is missing but expected %s\"),\n@@ -1221,7 +1224,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\t\t    ref_update_original_update_refname(u),\n \t\t\t\t\t    oid_to_hex(&current_oid),\n \t\t\t\t\t    oid_to_hex(&u->old_oid));\n-\t\t\tret = -1;\n \t\t\tgoto done;\n \t\t}\n \n-- \n2.47.0.7.g072c39eddb.dirty\n\n"},{"id":"505095","messageId":"20241014225431.1394565-6-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241014225431.1394565-1-bence@ferdinandy.com","subject":"[PATCH v8 5/6] refs: add create_only option to refs_update_symref","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-14T22:53:14Z","receivedAt":"2024-10-14T22:55:48Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Allow the caller to specify that it only wants to update the symref if\nit does not already exist. Silently ignore the error from the\ntransaction API if the symref already exists.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v4: new patch\n    v5: no change\n    \n    v6: - switched from bool to int for create_only\n        - refactored logic in refs_update_symref with goto as layed out by\n          Junio\n    \n    v7: - change commit prefix to be more in line with project standards\n        - refactored code to accommodate changes in the first patch, but\n          otherwise no change\n    \n    v8: no change\n\n builtin/branch.c          |  2 +-\n builtin/checkout.c        |  5 +++--\n builtin/clone.c           |  8 +++++---\n builtin/notes.c           |  3 ++-\n builtin/remote.c          |  9 ++++++---\n builtin/symbolic-ref.c    |  2 +-\n builtin/worktree.c        |  2 +-\n refs.c                    | 30 ++++++++++++++++++++++--------\n refs.h                    |  2 +-\n reset.c                   |  2 +-\n sequencer.c               |  3 ++-\n setup.c                   |  3 ++-\n t/helper/test-ref-store.c |  2 +-\n 13 files changed, 48 insertions(+), 25 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 6c87690b58..2fcbcbfd59 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -559,7 +559,7 @@ static int replace_each_worktree_head_symref(struct worktree **worktrees,\n \t\t\tcontinue;\n \n \t\trefs = get_worktree_ref_store(worktrees[i]);\n-\t\tif (refs_update_symref(refs, \"HEAD\", newref, logmsg, NULL))\n+\t\tif (refs_update_symref(refs, \"HEAD\", newref, logmsg, NULL, 0))\n \t\t\tret = error(_(\"HEAD of working tree %s is not updated\"),\n \t\t\t\t    worktrees[i]->path);\n \t}\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 356ee9bcde..9abb71dc07 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1015,7 +1015,8 @@ static void update_refs_for_switch(const struct checkout_opts *opts,\n \t\t\tdescribe_detached_head(_(\"HEAD is now at\"), new_branch_info->commit);\n \t\t}\n \t} else if (new_branch_info->path) {\t/* Switch branches. */\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", new_branch_info->path, msg.buf, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", new_branch_info->path,\n+\t\t\t\t\tmsg.buf, NULL, 0) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t\tif (!opts->quiet) {\n \t\t\tif (old_branch_info->path && !strcmp(new_branch_info->path, old_branch_info->path)) {\n@@ -1479,7 +1480,7 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts)\n \t\tdie(_(\"You are on a branch yet to be born\"));\n \tstrbuf_addf(&branch_ref, \"refs/heads/%s\", opts->new_branch);\n \tstatus = refs_update_symref(get_main_ref_store(the_repository),\n-\t\t\t\t    \"HEAD\", branch_ref.buf, \"checkout -b\", NULL);\n+\t\t\t\t    \"HEAD\", branch_ref.buf, \"checkout -b\", NULL, 0);\n \tstrbuf_release(&branch_ref);\n \tif (!opts->quiet)\n \t\tfprintf(stderr, _(\"Switched to a new branch '%s'\\n\"),\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex ead2af20ea..87e8f1421a 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -661,7 +661,7 @@ static void update_remote_refs(const struct ref *refs,\n \t\tstrbuf_addstr(&head_ref, \"HEAD\");\n \t\tif (refs_update_symref(get_main_ref_store(the_repository), head_ref.buf,\n \t\t\t\t       remote_head_points_at->peer_ref->name,\n-\t\t\t\t       msg, NULL) < 0)\n+\t\t\t\t       msg, NULL, 0) < 0)\n \t\t\tdie(_(\"unable to update %s\"), head_ref.buf);\n \t\tstrbuf_release(&head_ref);\n \t}\n@@ -673,7 +673,8 @@ static void update_head(const struct ref *our, const struct ref *remote,\n \tconst char *head;\n \tif (our && skip_prefix(our->name, \"refs/heads/\", &head)) {\n \t\t/* Local default branch link */\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", our->name, NULL, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", our->name,\n+\t\t\t\t\tNULL, NULL, 0) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t\tif (!option_bare) {\n \t\t\trefs_update_ref(get_main_ref_store(the_repository),\n@@ -702,7 +703,8 @@ static void update_head(const struct ref *our, const struct ref *remote,\n \t\t * Unborn head from remote; same as \"our\" case above except\n \t\t * that we have no ref to update.\n \t\t */\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", unborn, NULL, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", unborn,\n+\t\t\t\t\tNULL, NULL, 0) < 0)\n \t\t\tdie(_(\"unable to update HEAD\"));\n \t\tif (!option_bare)\n \t\t\tinstall_branch_config(0, head, remote_name, unborn);\ndiff --git a/builtin/notes.c b/builtin/notes.c\nindex ba646f06ff..7b3138e3c3 100644\n--- a/builtin/notes.c\n+++ b/builtin/notes.c\n@@ -980,7 +980,8 @@ static int merge(int argc, const char **argv, const char *prefix)\n \t\t\tdie(_(\"a notes merge into %s is already in-progress at %s\"),\n \t\t\t    notes_ref, wt->path);\n \t\tfree_worktrees(worktrees);\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"NOTES_MERGE_REF\", notes_ref, NULL, NULL))\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"NOTES_MERGE_REF\", notes_ref,\n+\t\t\t\t\tNULL, NULL, 0))\n \t\t\tdie(_(\"failed to store link to current notes ref (%s)\"),\n \t\t\t    notes_ref);\n \t\tfprintf(stderr, _(\"Automatic notes merge failed. Fix conflicts in %s \"\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 2b6948439f..d60ec6e907 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -244,7 +244,8 @@ static int add(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_reset(&buf2);\n \t\tstrbuf_addf(&buf2, \"refs/remotes/%s/%s\", name, master);\n \n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote add\", NULL))\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf,\n+\t\t\t\t\t\"remote add\", NULL, 0))\n \t\t\tresult = error(_(\"Could not setup master '%s'\"), master);\n \t}\n \n@@ -864,7 +865,8 @@ static int mv(int argc, const char **argv, const char *prefix)\n \t\tstrbuf_reset(&buf3);\n \t\tstrbuf_addf(&buf3, \"remote: renamed %s to %s\",\n \t\t\t\titem->string, buf.buf);\n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, buf3.buf, NULL))\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf,\n+\t\t\t\t\tbuf3.buf, NULL, 0))\n \t\t\tdie(_(\"creating '%s' failed\"), buf.buf);\n \t\tdisplay_progress(progress, ++refs_renamed_nr);\n \t}\n@@ -1470,7 +1472,8 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\t/* make sure it's valid */\n \t\tif (!refs_ref_exists(refs, buf2.buf))\n \t\t\tresult |= error(_(\"Not a valid ref: %s\"), buf2.buf);\n-\t\telse if (refs_update_symref(refs, buf.buf, buf2.buf, \"remote set-head\", &buf_prev))\n+\t\telse if (refs_update_symref(refs, buf.buf, buf2.buf,\n+\t\t\t\t\t\"remote set-head\", &buf_prev, 0))\n \t\t\tresult |= error(_(\"Could not setup %s\"), buf.buf);\n \t\telse if (opt_a) {\n \t\t\treport_set_head_auto(argv[0], head_name, &buf_prev);\ndiff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c\nindex 7728fbc3c1..cfbb53b30c 100644\n--- a/builtin/symbolic-ref.c\n+++ b/builtin/symbolic-ref.c\n@@ -88,7 +88,7 @@ int cmd_symbolic_ref(int argc,\n \t\tif (check_refname_format(argv[1], REFNAME_ALLOW_ONELEVEL) < 0)\n \t\t\tdie(\"Refusing to set '%s' to invalid ref '%s'\", argv[0], argv[1]);\n \t\tret = !!refs_update_symref(get_main_ref_store(the_repository),\n-\t\t\t\t\t   argv[0], argv[1], msg, NULL);\n+\t\t\t\t\t   argv[0], argv[1], msg, NULL, 0);\n \t\tbreak;\n \tdefault:\n \t\tusage_with_options(git_symbolic_ref_usage, options);\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex a7ab4193c1..2e95367235 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -517,7 +517,7 @@ static int add_worktree(const char *path, const char *refname,\n \t\tret = refs_update_ref(wt_refs, NULL, \"HEAD\", &commit->object.oid,\n \t\t\t\t      NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n \telse\n-\t\tret = refs_update_symref(wt_refs, \"HEAD\", symref.buf, NULL, NULL);\n+\t\tret = refs_update_symref(wt_refs, \"HEAD\", symref.buf, NULL, NULL, 0);\n \tif (ret)\n \t\tgoto done;\n \ndiff --git a/refs.c b/refs.c\nindex b964ac44d0..29c020e78e 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2115,26 +2115,40 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct\n \n int refs_update_symref(struct ref_store *refs, const char *ref,\n \t\t       const char *target, const char *logmsg,\n-\t\t       struct strbuf *referent)\n+\t\t       struct strbuf *referent, int create_only)\n {\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n-\tint ret = 0;\n+\tint ret = 0, prepret = 0;\n \n \ttransaction = ref_store_transaction_begin(refs, &err);\n-\tif (!transaction ||\n-\t\tref_transaction_update(transaction, ref, NULL, NULL,\n-\t\t\t\t   target, NULL, REF_NO_DEREF,\n-\t\t\t\t   logmsg, &err) ||\n-\t\tref_transaction_prepare(transaction, &err)) {\n+\tif (!transaction) {\n+\terror_return:\n \t\tret = error(\"%s\", err.buf);\n \t\tgoto cleanup;\n \t}\n+\tif (create_only) {\n+\t\tif (ref_transaction_create(transaction, ref, NULL, target,\n+\t\t\t\t\t   REF_NO_DEREF, logmsg, &err))\n+\t\t\tgoto error_return;\n+\t\tprepret = ref_transaction_prepare(transaction, &err);\n+\t\tif (prepret && prepret != TRANSACTION_CREATE_EXISTS)\n+\t\t\tgoto error_return;\n+\t} else {\n+\t\tif (ref_transaction_update(transaction, ref, NULL, NULL,\n+\t\t\t\t\t   target, NULL, REF_NO_DEREF,\n+\t\t\t\t\t   logmsg, &err) ||\n+\t\t\tref_transaction_prepare(transaction, &err))\n+\t\t\tgoto error_return;\n+\t}\n+\n \tif (referent)\n \t\trefs_read_symbolic_ref(refs, ref, referent);\n \n+\tif (prepret == TRANSACTION_CREATE_EXISTS)\n+\t\tgoto cleanup;\n \tif (ref_transaction_commit(transaction, &err))\n-\t\tret = error(\"%s\", err.buf);\n+\t\tgoto error_return;\n \n cleanup:\n \tstrbuf_release(&err);\ndiff --git a/refs.h b/refs.h\nindex c83b1ec76e..41ae384f11 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -572,7 +572,7 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,\n \n int refs_update_symref(struct ref_store *refs, const char *refname,\n \t\t       const char *target, const char *logmsg,\n-\t\t       struct strbuf *referent);\n+\t\t       struct strbuf *referent, int create_only);\n \n enum action_on_err {\n \tUPDATE_REFS_MSG_ON_ERR,\ndiff --git a/reset.c b/reset.c\nindex cc36a9ed56..919fb84f5f 100644\n--- a/reset.c\n+++ b/reset.c\n@@ -76,7 +76,7 @@ static int update_refs(const struct reset_head_opts *opts,\n \t\tif (!ret)\n \t\t\tret = refs_update_symref(get_main_ref_store(the_repository),\n \t\t\t\t\t\t \"HEAD\", switch_to_branch,\n-\t\t\t\t\t\t reflog_head, NULL);\n+\t\t\t\t\t\t reflog_head, NULL, 0);\n \t}\n \tif (!ret && run_hook)\n \t\trun_hooks_l(the_repository, \"post-checkout\",\ndiff --git a/sequencer.c b/sequencer.c\nindex 23b162924c..103d0ddfb2 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -5107,7 +5107,8 @@ static int pick_commits(struct repository *r,\n \t\t\t}\n \t\t\tmsg = reflog_message(opts, \"finish\", \"returning to %s\",\n \t\t\t\thead_ref.buf);\n-\t\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", head_ref.buf, msg, NULL)) {\n+\t\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", head_ref.buf,\n+\t\t\t\t\t\tmsg, NULL, 0)) {\n \t\t\t\tres = error(_(\"could not update HEAD to %s\"),\n \t\t\t\t\thead_ref.buf);\n \t\t\t\tgoto cleanup_head_ref;\ndiff --git a/setup.c b/setup.c\nindex d95f051465..9bbc655ee4 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -2275,7 +2275,8 @@ void create_reference_database(enum ref_storage_format ref_storage_format,\n \t\t\tdie(_(\"invalid initial branch name: '%s'\"),\n \t\t\t    initial_branch);\n \n-\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", ref, NULL, NULL) < 0)\n+\t\tif (refs_update_symref(get_main_ref_store(the_repository), \"HEAD\", ref,\n+\t\t\t\t\tNULL, NULL, 0) < 0)\n \t\t\texit(1);\n \t\tfree(ref);\n \t}\ndiff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c\nindex a911302bea..f72b029e51 100644\n--- a/t/helper/test-ref-store.c\n+++ b/t/helper/test-ref-store.c\n@@ -120,7 +120,7 @@ static int cmd_create_symref(struct ref_store *refs, const char **argv)\n \tconst char *target = notnull(*argv++, \"target\");\n \tconst char *logmsg = *argv++;\n \n-\treturn refs_update_symref(refs, refname, target, logmsg, NULL);\n+\treturn refs_update_symref(refs, refname, target, logmsg, NULL, 0);\n }\n \n static struct flag_definition transaction_flags[] = {\n-- \n2.47.0.7.g072c39eddb.dirty\n\n"},{"id":"505096","messageId":"20241014225431.1394565-7-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241014225431.1394565-1-bence@ferdinandy.com","subject":"[PATCH v8 6/6] fetch: set remote/HEAD if it does not exist","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-14T22:53:15Z","receivedAt":"2024-10-14T22:56:07Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"If the user has remote/HEAD set already and it looks like it has changed\non the server, then print a message, otherwise set it if we can.\nSilently pass if the user already has the same remote/HEAD set as\nreported by the server or if we encounter any errors along the way.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v3: - does not rely on remote set-head anymore so it only authenticates\n        once\n        - uses the new REF_CREATE_ONLY to atomically check if the ref exists\n          and only write it if it doesn't\n        - in all other cases the maximum it does is print a warning\n    \n    v4: - instead of the discarded REF_CREATE_ONLY, it uses the existing,\n          but updated transaction api to request a silent create only\n        - it now uses the atomic before_target to determine reporting\n        - refactored for legibility\n    \n    v5: - instead of printing a not too useful message, it now fails\n          silently, this in line with the objective to only set up\n          remote/HEAD automatically if the right thing is trivial, for\n          everything else there is remote set-head\n        - fixed all failing tests\n        - added two new tests, one for checking if remote/HEAD is set to the\n          correct one, and one to test that we do not override remote/HEAD\n          if it has changed on the server from what we have locally\n    \n    v6: - fixed style issues and unintended extra empty line\n        - updated function call with bool to int from previous patch's\n          change\n        - removed calls to error(...) inherited from builtin/remote.c so we\n          actually fail silently\n        - set the test for remote set-head --auto to the correct value here,\n          which was previously erronously set in the remote set-head patch\n    \n    v7: - no change\n    \n    v8: - changed logmsg in call to refs_update_symref from \"remote\n          set-head\" to \"fetch\"\n\n builtin/fetch.c                  |  82 +++++++++++\n t/t4207-log-decoration-colors.sh |   3 +-\n t/t5505-remote.sh                |  13 +-\n t/t5510-fetch.sh                 | 229 ++++++++++++++++---------------\n t/t5512-ls-remote.sh             |   2 +\n t/t5514-fetch-multiple.sh        |  17 ++-\n t/t5516-fetch-push.sh            |   3 +-\n t/t5527-fetch-odd-refs.sh        |   3 +-\n t/t7900-maintenance.sh           |   3 +-\n t/t9210-scalar.sh                |   5 +-\n t/t9211-scalar-clone.sh          |   6 +-\n t/t9902-completion.sh            |  65 +++++++++\n 12 files changed, 308 insertions(+), 123 deletions(-)\n\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 80a64d0d26..88ebca8294 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1578,6 +1578,80 @@ static int backfill_tags(struct display_state *display_state,\n \treturn retcode;\n }\n \n+static void report_set_head(const char *remote, const char *head_name,\n+\t\t\tstruct strbuf *buf_prev) {\n+\tstruct strbuf buf_prefix = STRBUF_INIT;\n+\tconst char *prev_head = NULL;\n+\n+\tstrbuf_addf(&buf_prefix, \"refs/remotes/%s/\", remote);\n+\tskip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);\n+\n+\tif (prev_head && strcmp(prev_head, head_name)) {\n+\t\tprintf(\"'HEAD' at '%s' has changed from '%s' to '%s'\\n\",\n+\t\t\tremote, prev_head, head_name);\n+\t\tprintf(\"Run 'git remote set-head %s %s' to follow the change.\\n\",\n+\t\t\tremote, head_name);\n+\t}\n+}\n+\n+static const char *strip_refshead(const char *name){\n+\tskip_prefix(name, \"refs/heads/\", &name);\n+\treturn name;\n+}\n+\n+static int set_head(const struct ref *remote_refs)\n+{\n+\tint result = 0;\n+\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,\n+\t\tb_local_head = STRBUF_INIT;\n+\tconst char *remote = gtransport->remote->name;\n+\tchar *head_name = NULL;\n+\tstruct ref *ref, *matches;\n+\tstruct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;\n+\tstruct refspec_item refspec = {\n+\t\t.force = 0,\n+\t\t.pattern = 1,\n+\t\t.src = (char *) \"refs/heads/*\",\n+\t\t.dst = (char *) \"refs/heads/*\",\n+\t};\n+\tstruct string_list heads = STRING_LIST_INIT_DUP;\n+\tstruct ref_store *refs = get_main_ref_store(the_repository);\n+\n+\tget_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);\n+\tmatches = guess_remote_head(find_ref_by_name(remote_refs, \"HEAD\"),\n+\t\t\t\t    fetch_map, 1);\n+\tfor (ref = matches; ref; ref = ref->next) {\n+\t\tstring_list_append(&heads, strip_refshead(ref->name));\n+\t}\n+\n+\n+\tif (!heads.nr)\n+\t\tresult = 1;\n+\telse if (heads.nr > 1)\n+\t\tresult = 1;\n+\telse\n+\t\thead_name = xstrdup(heads.items[0].string);\n+\tif (head_name) {\n+\t\tstrbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", remote);\n+\t\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", remote, head_name);\n+\t\t/* make sure it's valid */\n+\t\tif (!refs_ref_exists(refs, b_remote_head.buf))\n+\t\t\tresult = 1;\n+\t\telse if (refs_update_symref(refs, b_head.buf, b_remote_head.buf,\n+\t\t\t\t\t\"fetch\", &b_local_head, 1))\n+\t\t\tresult = 1;\n+\t\telse\n+\t\t\treport_set_head(remote, head_name, &b_local_head);\n+\n+\t\tfree(head_name);\n+\t}\n+\n+\tstrbuf_release(&b_head);\n+\tstrbuf_release(&b_local_head);\n+\tstrbuf_release(&b_remote_head);\n+\treturn result;\n+}\n+\n static int do_fetch(struct transport *transport,\n \t\t    struct refspec *rs,\n \t\t    const struct fetch_config *config)\n@@ -1647,6 +1721,8 @@ static int do_fetch(struct transport *transport,\n \t\t\t\t    \"refs/tags/\");\n \t}\n \n+\tstrvec_push(&transport_ls_refs_options.ref_prefixes, \"HEAD\");\n+\n \tif (must_list_refs) {\n \t\ttrace2_region_enter(\"fetch\", \"remote_refs\", the_repository);\n \t\tremote_refs = transport_get_remote_refs(transport,\n@@ -1791,6 +1867,12 @@ static int do_fetch(struct transport *transport,\n \t\t\t\t  \"you need to specify exactly one branch with the --set-upstream option\"));\n \t\t}\n \t}\n+\tif (set_head(remote_refs))\n+\t\t;\n+\t\t/*\n+\t\t * Way too many cases where this can go wrong\n+\t\t * so let's just fail silently for now.\n+\t\t */\n \n cleanup:\n \tif (retcode) {\ndiff --git a/t/t4207-log-decoration-colors.sh b/t/t4207-log-decoration-colors.sh\nindex 73ea9e5155..d55d22cb2f 100755\n--- a/t/t4207-log-decoration-colors.sh\n+++ b/t/t4207-log-decoration-colors.sh\n@@ -59,7 +59,8 @@ ${c_reset}${c_tag}tag: ${c_reset}${c_tag}v1.0${c_reset}${c_commit}, \\\n ${c_reset}${c_tag}tag: ${c_reset}${c_tag}B${c_reset}${c_commit})${c_reset} B\n ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\n ${c_tag}tag: ${c_reset}${c_tag}A1${c_reset}${c_commit}, \\\n-${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit})${c_reset} A1\n+${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit}, \\\n+${c_reset}${c_remoteBranch}other/HEAD${c_reset}${c_commit})${c_reset} A1\n \t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\n ${c_stash}refs/stash${c_reset}${c_commit})${c_reset} On main: Changes to A.t\n \t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex d99cd1d0aa..cb45f22232 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -71,7 +71,7 @@ test_expect_success 'add another remote' '\n \t\tcd test &&\n \t\tgit remote add -f second ../two &&\n \t\ttokens_match \"origin second\" \"$(git remote)\" &&\n-\t\tcheck_tracking_branch second main side another &&\n+\t\tcheck_tracking_branch second main side another HEAD &&\n \t\tgit for-each-ref \"--format=%(refname)\" refs/remotes |\n \t\tsed -e \"/^refs\\/remotes\\/origin\\//d\" \\\n \t\t    -e \"/^refs\\/remotes\\/second\\//d\" >actual &&\n@@ -473,7 +473,7 @@ test_expect_success 'set-head --auto has no problem w/multiple HEADs' '\n \t\tcd test &&\n \t\tgit fetch two \"refs/heads/*:refs/remotes/two/*\" &&\n \t\tgit remote set-head --auto two >output 2>&1 &&\n-\t\techo \"'\\''two/HEAD'\\'' is now created and points to '\\''main'\\''\" >expect &&\n+\t\techo \"'\\''two/HEAD'\\'' is unchanged and points to '\\''main'\\''\" >expect &&\n \t\ttest_cmp expect output\n \t)\n '\n@@ -763,6 +763,7 @@ test_expect_success 'reject --no-no-tags' '\n cat >one/expect <<\\EOF\n   apis/main\n   apis/side\n+  drosophila/HEAD -> drosophila/main\n   drosophila/another\n   drosophila/main\n   drosophila/side\n@@ -780,6 +781,7 @@ test_expect_success 'update' '\n '\n \n cat >one/expect <<\\EOF\n+  drosophila/HEAD -> drosophila/main\n   drosophila/another\n   drosophila/main\n   drosophila/side\n@@ -792,7 +794,7 @@ EOF\n test_expect_success 'update with arguments' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\n@@ -835,7 +837,7 @@ EOF\n test_expect_success 'update default' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\n@@ -847,6 +849,7 @@ test_expect_success 'update default' '\n '\n \n cat >one/expect <<\\EOF\n+  drosophila/HEAD -> drosophila/main\n   drosophila/another\n   drosophila/main\n   drosophila/side\n@@ -869,7 +872,7 @@ test_expect_success 'update default (overridden, with funny whitespace)' '\n test_expect_success 'update (with remotes.default defined)' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex 0890b9f61c..dfc8d748ba 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -75,6 +75,30 @@ test_expect_success \"fetch test for-merge\" '\n \tcut -f -2 .git/FETCH_HEAD >actual &&\n \ttest_cmp expected actual'\n \n+test_expect_success \"fetch test remote HEAD\" '\n+\tcd \"$D\" &&\n+\tcd two &&\n+\tgit fetch &&\n+\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\tgit rev-parse --verify refs/remotes/origin/main &&\n+\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\tbranch=$(git rev-parse refs/remotes/origin/main) &&\n+\ttest \"z$head\" = \"z$branch\"'\n+\n+test_expect_success \"fetch test remote HEAD change\" '\n+\tcd \"$D\" &&\n+\tcd two &&\n+\tgit switch -c other &&\n+\tgit push -u origin other &&\n+\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\tgit rev-parse --verify refs/remotes/origin/main &&\n+\tgit rev-parse --verify refs/remotes/origin/other &&\n+\tgit remote set-head origin other &&\n+\tgit fetch &&\n+\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\tbranch=$(git rev-parse refs/remotes/origin/other) &&\n+\ttest \"z$head\" = \"z$branch\"'\n+\n test_expect_success 'fetch --prune on its own works as expected' '\n \tcd \"$D\" &&\n \tgit clone . prune &&\n@@ -478,7 +502,6 @@ test_expect_success 'unbundle 1' '\n \ttest_must_fail git fetch \"$D/bundle1\" main:main\n '\n \n-\n test_expect_success 'bundle 1 has only 3 files ' '\n \tcd \"$D\" &&\n \ttest_bundle_object_count bundle1 3\n@@ -819,7 +842,7 @@ test_expect_success 'fetch from multiple configured URLs in single remote' '\n \n # configured prune tests\n \n-set_config_tristate () {\n+set_config_tristate() {\n \t# var=$1 val=$2\n \tcase \"$2\" in\n \tunset)\n@@ -833,12 +856,12 @@ set_config_tristate () {\n \tesac\n }\n \n-test_configured_prune () {\n+test_configured_prune() {\n \ttest_configured_prune_type \"$@\" \"name\"\n \ttest_configured_prune_type \"$@\" \"link\"\n }\n \n-test_configured_prune_type () {\n+test_configured_prune_type() {\n \tfetch_prune=$1\n \tremote_origin_prune=$2\n \tfetch_prune_tags=$3\n@@ -848,8 +871,7 @@ test_configured_prune_type () {\n \tcmdline=$7\n \tmode=$8\n \n-\tif test -z \"$cmdline_setup\"\n-\tthen\n+\tif test -z \"$cmdline_setup\"; then\n \t\ttest_expect_success 'setup cmdline_setup variable for subsequent test' '\n \t\t\tremote_url=\"file://$(git -C one config remote.origin.url)\" &&\n \t\t\tremote_fetch=\"$(git -C one config remote.origin.fetch)\" &&\n@@ -857,12 +879,10 @@ test_configured_prune_type () {\n \t\t'\n \tfi\n \n-\tif test \"$mode\" = 'link'\n-\tthen\n+\tif test \"$mode\" = 'link'; then\n \t\tnew_cmdline=\"\"\n \n-\t\tif test \"$cmdline\" = \"\"\n-\t\tthen\n+\t\tif test \"$cmdline\" = \"\"; then\n \t\t\tnew_cmdline=$cmdline_setup\n \t\telse\n \t\t\tnew_cmdline=$(perl -e '\n@@ -873,10 +893,8 @@ test_configured_prune_type () {\n \t\tfi\n \n \t\tif test \"$fetch_prune_tags\" = 'true' ||\n-\t\t   test \"$remote_origin_prune_tags\" = 'true'\n-\t\tthen\n-\t\t\tif ! printf '%s' \"$cmdline\\n\" | grep -q refs/remotes/origin/\n-\t\t\tthen\n+\t\t\ttest \"$remote_origin_prune_tags\" = 'true'; then\n+\t\t\tif ! printf '%s' \"$cmdline\\n\" | grep -q refs/remotes/origin/; then\n \t\t\t\tnew_cmdline=\"$new_cmdline refs/tags/*:refs/tags/*\"\n \t\t\tfi\n \t\tfi\n@@ -946,100 +964,100 @@ test_configured_prune_type () {\n # $7 git-fetch $cmdline:\n #\n #                     $1    $2    $3    $4    $5     $6     $7\n-test_configured_prune unset unset unset unset kept   kept   \"\"\n-test_configured_prune unset unset unset unset kept   kept   \"--no-prune\"\n-test_configured_prune unset unset unset unset pruned kept   \"--prune\"\n-test_configured_prune unset unset unset unset kept   pruned \\\n+test_configured_prune unset unset unset unset kept kept \"\"\n+test_configured_prune unset unset unset unset kept kept \"--no-prune\"\n+test_configured_prune unset unset unset unset pruned kept \"--prune\"\n+test_configured_prune unset unset unset unset kept pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/*\"\n test_configured_prune unset unset unset unset pruned pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*\"\n \n-test_configured_prune false unset unset unset kept   kept   \"\"\n-test_configured_prune false unset unset unset kept   kept   \"--no-prune\"\n-test_configured_prune false unset unset unset pruned kept   \"--prune\"\n+test_configured_prune false unset unset unset kept kept \"\"\n+test_configured_prune false unset unset unset kept kept \"--no-prune\"\n+test_configured_prune false unset unset unset pruned kept \"--prune\"\n \n-test_configured_prune true  unset unset unset pruned kept   \"\"\n-test_configured_prune true  unset unset unset pruned kept   \"--prune\"\n-test_configured_prune true  unset unset unset kept   kept   \"--no-prune\"\n+test_configured_prune true unset unset unset pruned kept \"\"\n+test_configured_prune true unset unset unset pruned kept \"--prune\"\n+test_configured_prune true unset unset unset kept kept \"--no-prune\"\n \n-test_configured_prune unset false unset unset kept   kept   \"\"\n-test_configured_prune unset false unset unset kept   kept   \"--no-prune\"\n-test_configured_prune unset false unset unset pruned kept   \"--prune\"\n+test_configured_prune unset false unset unset kept kept \"\"\n+test_configured_prune unset false unset unset kept kept \"--no-prune\"\n+test_configured_prune unset false unset unset pruned kept \"--prune\"\n \n-test_configured_prune false false unset unset kept   kept   \"\"\n-test_configured_prune false false unset unset kept   kept   \"--no-prune\"\n-test_configured_prune false false unset unset pruned kept   \"--prune\"\n-test_configured_prune false false unset unset kept   pruned \\\n+test_configured_prune false false unset unset kept kept \"\"\n+test_configured_prune false false unset unset kept kept \"--no-prune\"\n+test_configured_prune false false unset unset pruned kept \"--prune\"\n+test_configured_prune false false unset unset kept pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/*\"\n test_configured_prune false false unset unset pruned pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*\"\n \n-test_configured_prune true  false unset unset kept   kept   \"\"\n-test_configured_prune true  false unset unset pruned kept   \"--prune\"\n-test_configured_prune true  false unset unset kept   kept   \"--no-prune\"\n+test_configured_prune true false unset unset kept kept \"\"\n+test_configured_prune true false unset unset pruned kept \"--prune\"\n+test_configured_prune true false unset unset kept kept \"--no-prune\"\n \n-test_configured_prune unset true  unset unset pruned kept   \"\"\n-test_configured_prune unset true  unset unset kept   kept   \"--no-prune\"\n-test_configured_prune unset true  unset unset pruned kept   \"--prune\"\n+test_configured_prune unset true unset unset pruned kept \"\"\n+test_configured_prune unset true unset unset kept kept \"--no-prune\"\n+test_configured_prune unset true unset unset pruned kept \"--prune\"\n \n-test_configured_prune false true  unset unset pruned kept   \"\"\n-test_configured_prune false true  unset unset kept   kept   \"--no-prune\"\n-test_configured_prune false true  unset unset pruned kept   \"--prune\"\n+test_configured_prune false true unset unset pruned kept \"\"\n+test_configured_prune false true unset unset kept kept \"--no-prune\"\n+test_configured_prune false true unset unset pruned kept \"--prune\"\n \n-test_configured_prune true  true  unset unset pruned kept   \"\"\n-test_configured_prune true  true  unset unset pruned kept   \"--prune\"\n-test_configured_prune true  true  unset unset kept   kept   \"--no-prune\"\n-test_configured_prune true  true  unset unset kept   pruned \\\n+test_configured_prune true true unset unset pruned kept \"\"\n+test_configured_prune true true unset unset pruned kept \"--prune\"\n+test_configured_prune true true unset unset kept kept \"--no-prune\"\n+test_configured_prune true true unset unset kept pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/*\"\n-test_configured_prune true  true  unset unset pruned pruned \\\n+test_configured_prune true true unset unset pruned pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*\"\n \n # --prune-tags on its own does nothing, needs --prune as well, same\n # for fetch.pruneTags without fetch.prune\n-test_configured_prune unset unset unset unset kept kept     \"--prune-tags\"\n-test_configured_prune unset unset true unset  kept kept     \"\"\n-test_configured_prune unset unset unset true  kept kept     \"\"\n+test_configured_prune unset unset unset unset kept kept \"--prune-tags\"\n+test_configured_prune unset unset true unset kept kept \"\"\n+test_configured_prune unset unset unset true kept kept \"\"\n \n # These will prune the tags\n test_configured_prune unset unset unset unset pruned pruned \"--prune --prune-tags\"\n-test_configured_prune true  unset true  unset pruned pruned \"\"\n-test_configured_prune unset true  unset true  pruned pruned \"\"\n+test_configured_prune true unset true unset pruned pruned \"\"\n+test_configured_prune unset true unset true pruned pruned \"\"\n \n # remote.<name>.pruneTags overrides fetch.pruneTags, just like\n # remote.<name>.prune overrides fetch.prune if set.\n-test_configured_prune true  unset true unset pruned pruned  \"\"\n-test_configured_prune false true  false true  pruned pruned \"\"\n-test_configured_prune true  false true  false kept   kept   \"\"\n+test_configured_prune true unset true unset pruned pruned \"\"\n+test_configured_prune false true false true pruned pruned \"\"\n+test_configured_prune true false true false kept kept \"\"\n \n # When --prune-tags is supplied it's ignored if an explicit refspec is\n # given, same for the configuration options.\n test_configured_prune unset unset unset unset pruned kept \\\n \t\"--prune --prune-tags origin +refs/heads/*:refs/remotes/origin/*\"\n-test_configured_prune unset unset true  unset pruned kept \\\n+test_configured_prune unset unset true unset pruned kept \\\n \t\"--prune origin +refs/heads/*:refs/remotes/origin/*\"\n-test_configured_prune unset unset unset true pruned  kept \\\n+test_configured_prune unset unset unset true pruned kept \\\n \t\"--prune origin +refs/heads/*:refs/remotes/origin/*\"\n \n # Pruning that also takes place if a file:// url replaces a named\n # remote. However, because there's no implicit\n # +refs/heads/*:refs/remotes/origin/* refspec and supplying it on the\n # command-line negates --prune-tags, the branches will not be pruned.\n-test_configured_prune_type unset unset unset unset kept   kept   \"origin --prune-tags\" \"name\"\n-test_configured_prune_type unset unset unset unset kept   kept   \"origin --prune-tags\" \"link\"\n+test_configured_prune_type unset unset unset unset kept kept \"origin --prune-tags\" \"name\"\n+test_configured_prune_type unset unset unset unset kept kept \"origin --prune-tags\" \"link\"\n test_configured_prune_type unset unset unset unset pruned pruned \"origin --prune --prune-tags\" \"name\"\n-test_configured_prune_type unset unset unset unset kept   pruned \"origin --prune --prune-tags\" \"link\"\n+test_configured_prune_type unset unset unset unset kept pruned \"origin --prune --prune-tags\" \"link\"\n test_configured_prune_type unset unset unset unset pruned pruned \"--prune --prune-tags origin\" \"name\"\n-test_configured_prune_type unset unset unset unset kept   pruned \"--prune --prune-tags origin\" \"link\"\n-test_configured_prune_type unset unset true  unset pruned pruned \"--prune origin\" \"name\"\n-test_configured_prune_type unset unset true  unset kept   pruned \"--prune origin\" \"link\"\n-test_configured_prune_type unset unset unset true  pruned pruned \"--prune origin\" \"name\"\n-test_configured_prune_type unset unset unset true  kept   pruned \"--prune origin\" \"link\"\n-test_configured_prune_type true  unset true  unset pruned pruned \"origin\" \"name\"\n-test_configured_prune_type true  unset true  unset kept   pruned \"origin\" \"link\"\n-test_configured_prune_type unset  true true  unset pruned pruned \"origin\" \"name\"\n-test_configured_prune_type unset  true true  unset kept   pruned \"origin\" \"link\"\n-test_configured_prune_type unset  true unset true  pruned pruned \"origin\" \"name\"\n-test_configured_prune_type unset  true unset true  kept   pruned \"origin\" \"link\"\n+test_configured_prune_type unset unset unset unset kept pruned \"--prune --prune-tags origin\" \"link\"\n+test_configured_prune_type unset unset true unset pruned pruned \"--prune origin\" \"name\"\n+test_configured_prune_type unset unset true unset kept pruned \"--prune origin\" \"link\"\n+test_configured_prune_type unset unset unset true pruned pruned \"--prune origin\" \"name\"\n+test_configured_prune_type unset unset unset true kept pruned \"--prune origin\" \"link\"\n+test_configured_prune_type true unset true unset pruned pruned \"origin\" \"name\"\n+test_configured_prune_type true unset true unset kept pruned \"origin\" \"link\"\n+test_configured_prune_type unset true true unset pruned pruned \"origin\" \"name\"\n+test_configured_prune_type unset true true unset kept pruned \"origin\" \"link\"\n+test_configured_prune_type unset true unset true pruned pruned \"origin\" \"name\"\n+test_configured_prune_type unset true unset true kept pruned \"origin\" \"link\"\n \n # When all remote.origin.fetch settings are deleted a --prune\n # --prune-tags still implicitly supplies refs/tags/*:refs/tags/* so\n@@ -1137,8 +1155,7 @@ test_expect_success 'fetching with auto-gc does not lock up' '\n \t)\n '\n \n-for section in fetch transfer\n-do\n+for section in fetch transfer; do\n \ttest_expect_success \"$section.hideRefs affects connectivity check\" '\n \t\tGIT_TRACE=\"$PWD\"/trace git -c $section.hideRefs=refs -c \\\n \t\t\t$section.hideRefs=\"!refs/tags/\" fetch &&\n@@ -1157,21 +1174,23 @@ test_expect_success 'prepare source branch' '\n \ttest 3 -le $(wc -l <actual)\n '\n \n-validate_store_type () {\n+validate_store_type() {\n \tgit -C dest count-objects -v >actual &&\n-\tcase \"$store_type\" in\n-\tpacked)\n-\t\tgrep \"^count: 0$\" actual ;;\n-\tloose)\n-\t\tgrep \"^packs: 0$\" actual ;;\n-\tesac || {\n+\t\tcase \"$store_type\" in\n+\t\tpacked)\n+\t\t\tgrep \"^count: 0$\" actual\n+\t\t\t;;\n+\t\tloose)\n+\t\t\tgrep \"^packs: 0$\" actual\n+\t\t\t;;\n+\t\tesac || {\n \t\techo \"store_type is $store_type\"\n \t\tcat actual\n \t\tfalse\n \t}\n }\n \n-test_unpack_limit () {\n+test_unpack_limit() {\n \tstore_type=$1\n \n \tcase \"$store_type\" in\n@@ -1192,43 +1211,39 @@ test_unpack_limit () {\n test_unpack_limit packed\n test_unpack_limit loose\n \n-setup_negotiation_tip () {\n+setup_negotiation_tip() {\n \tSERVER=\"$1\"\n \tURL=\"$2\"\n \tUSE_PROTOCOL_V2=\"$3\"\n \n \trm -rf \"$SERVER\" client trace &&\n-\tgit init -b main \"$SERVER\" &&\n-\ttest_commit -C \"$SERVER\" alpha_1 &&\n-\ttest_commit -C \"$SERVER\" alpha_2 &&\n-\tgit -C \"$SERVER\" checkout --orphan beta &&\n-\ttest_commit -C \"$SERVER\" beta_1 &&\n-\ttest_commit -C \"$SERVER\" beta_2 &&\n-\n-\tgit clone \"$URL\" client &&\n-\n-\tif test \"$USE_PROTOCOL_V2\" -eq 1\n-\tthen\n-\t\tgit -C \"$SERVER\" config protocol.version 2 &&\n-\t\tgit -C client config protocol.version 2\n-\tfi &&\n-\n-\ttest_commit -C \"$SERVER\" beta_s &&\n-\tgit -C \"$SERVER\" checkout main &&\n-\ttest_commit -C \"$SERVER\" alpha_s &&\n-\tgit -C \"$SERVER\" tag -d alpha_1 alpha_2 beta_1 beta_2\n+\t\tgit init -b main \"$SERVER\" &&\n+\t\ttest_commit -C \"$SERVER\" alpha_1 &&\n+\t\ttest_commit -C \"$SERVER\" alpha_2 &&\n+\t\tgit -C \"$SERVER\" checkout --orphan beta &&\n+\t\ttest_commit -C \"$SERVER\" beta_1 &&\n+\t\ttest_commit -C \"$SERVER\" beta_2 &&\n+\t\tgit clone \"$URL\" client &&\n+\t\tif test \"$USE_PROTOCOL_V2\" -eq 1; then\n+\t\t\tgit -C \"$SERVER\" config protocol.version 2 &&\n+\t\t\t\tgit -C client config protocol.version 2\n+\t\tfi &&\n+\t\ttest_commit -C \"$SERVER\" beta_s &&\n+\t\tgit -C \"$SERVER\" checkout main &&\n+\t\ttest_commit -C \"$SERVER\" alpha_s &&\n+\t\tgit -C \"$SERVER\" tag -d alpha_1 alpha_2 beta_1 beta_2\n }\n \n-check_negotiation_tip () {\n+check_negotiation_tip() {\n \t# Ensure that {alpha,beta}_1 are sent as \"have\", but not {alpha_beta}_2\n \tALPHA_1=$(git -C client rev-parse alpha_1) &&\n-\tgrep \"fetch> have $ALPHA_1\" trace &&\n-\tBETA_1=$(git -C client rev-parse beta_1) &&\n-\tgrep \"fetch> have $BETA_1\" trace &&\n-\tALPHA_2=$(git -C client rev-parse alpha_2) &&\n-\t! grep \"fetch> have $ALPHA_2\" trace &&\n-\tBETA_2=$(git -C client rev-parse beta_2) &&\n-\t! grep \"fetch> have $BETA_2\" trace\n+\t\tgrep \"fetch> have $ALPHA_1\" trace &&\n+\t\tBETA_1=$(git -C client rev-parse beta_1) &&\n+\t\tgrep \"fetch> have $BETA_1\" trace &&\n+\t\tALPHA_2=$(git -C client rev-parse alpha_2) &&\n+\t\t! grep \"fetch> have $ALPHA_2\" trace &&\n+\t\tBETA_2=$(git -C client rev-parse beta_2) &&\n+\t\t! grep \"fetch> have $BETA_2\" trace\n }\n \n test_expect_success '--negotiation-tip limits \"have\" lines sent' '\ndiff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh\nindex 64b3491e4e..1b3865e154 100755\n--- a/t/t5512-ls-remote.sh\n+++ b/t/t5512-ls-remote.sh\n@@ -293,6 +293,8 @@ test_expect_success 'ls-remote with filtered symref (refname)' '\n \tcat >expect <<-EOF &&\n \tref: refs/heads/main\tHEAD\n \t$rev\tHEAD\n+\tref: refs/remotes/origin/main\trefs/remotes/origin/HEAD\n+\t$rev\trefs/remotes/origin/HEAD\n \tEOF\n \tgit ls-remote --symref . HEAD >actual &&\n \ttest_cmp expect actual\ndiff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh\nindex 579872c258..e3482b27b2 100755\n--- a/t/t5514-fetch-multiple.sh\n+++ b/t/t5514-fetch-multiple.sh\n@@ -45,14 +45,17 @@ test_expect_success setup '\n '\n \n cat > test/expect << EOF\n+  one/HEAD -> one/main\n   one/main\n   one/side\n   origin/HEAD -> origin/main\n   origin/main\n   origin/side\n+  three/HEAD -> three/main\n   three/another\n   three/main\n   three/side\n+  two/HEAD -> two/main\n   two/another\n   two/main\n   two/side\n@@ -97,6 +100,7 @@ cat > expect << EOF\n   origin/HEAD -> origin/main\n   origin/main\n   origin/side\n+  three/HEAD -> three/main\n   three/another\n   three/main\n   three/side\n@@ -112,8 +116,10 @@ test_expect_success 'git fetch --multiple (but only one remote)' '\n '\n \n cat > expect << EOF\n+  one/HEAD -> one/main\n   one/main\n   one/side\n+  two/HEAD -> two/main\n   two/another\n   two/main\n   two/side\n@@ -141,7 +147,7 @@ test_expect_success 'git fetch --multiple (bad remote names)' '\n \n test_expect_success 'git fetch --all (skipFetchAll)' '\n \t(cd test4 &&\n-\t for b in $(git branch -r)\n+\t for b in $(git branch -r | grep -v HEAD)\n \t do\n \t\tgit branch -r -d $b || exit 1\n \t done &&\n@@ -153,11 +159,14 @@ test_expect_success 'git fetch --all (skipFetchAll)' '\n '\n \n cat > expect << EOF\n+  one/HEAD -> one/main\n   one/main\n   one/side\n+  three/HEAD -> three/main\n   three/another\n   three/main\n   three/side\n+  two/HEAD -> two/main\n   two/another\n   two/main\n   two/side\n@@ -165,7 +174,7 @@ EOF\n \n test_expect_success 'git fetch --multiple (ignoring skipFetchAll)' '\n \t(cd test4 &&\n-\t for b in $(git branch -r)\n+\t for b in $(git branch -r | grep -v HEAD)\n \t do\n \t\tgit branch -r -d $b || exit 1\n \t done &&\n@@ -221,14 +230,17 @@ test_expect_success 'git fetch --multiple --jobs=0 picks a default' '\n \n create_fetch_all_expect () {\n \tcat >expect <<-\\EOF\n+\t  one/HEAD -> one/main\n \t  one/main\n \t  one/side\n \t  origin/HEAD -> origin/main\n \t  origin/main\n \t  origin/side\n+\t  three/HEAD -> three/main\n \t  three/another\n \t  three/main\n \t  three/side\n+\t  two/HEAD -> two/main\n \t  two/another\n \t  two/main\n \t  two/side\n@@ -265,6 +277,7 @@ test_expect_success 'git fetch (fetch all remotes with fetch.all = true)' '\n \n create_fetch_one_expect () {\n \tcat >expect <<-\\EOF\n+\t  one/HEAD -> one/main\n \t  one/main\n \t  one/side\n \t  origin/HEAD -> origin/main\ndiff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\nindex 331778bd42..5a051aa0c7 100755\n--- a/t/t5516-fetch-push.sh\n+++ b/t/t5516-fetch-push.sh\n@@ -1395,7 +1395,8 @@ test_expect_success 'fetch follows tags by default' '\n \t\tgit tag -m \"annotated\" tag &&\n \t\tgit for-each-ref >tmp1 &&\n \t\tsed -n \"p; s|refs/heads/main$|refs/remotes/origin/main|p\" tmp1 |\n-\t\tsort -k 3 >../expect\n+\t\tsed -n \"p; s|refs/heads/main$|refs/remotes/origin/HEAD|p\"  |\n+\t\tsort -k 4 >../expect\n \t) &&\n \ttest_when_finished \"rm -rf dst\" &&\n \tgit init dst &&\ndiff --git a/t/t5527-fetch-odd-refs.sh b/t/t5527-fetch-odd-refs.sh\nindex 98ece27c6a..d3996af6ee 100755\n--- a/t/t5527-fetch-odd-refs.sh\n+++ b/t/t5527-fetch-odd-refs.sh\n@@ -52,7 +52,8 @@ test_expect_success LONG_REF 'fetch handles extremely long refname' '\n \tlong\n \tmain\n \tEOF\n-\tgit for-each-ref --format=\"%(subject)\" refs/remotes/long >actual &&\n+\tgit for-each-ref --format=\"%(subject)\" refs/remotes/long \\\n+\t\t--exclude=refs/remotes/long/HEAD >actual &&\n \ttest_cmp expect actual\n '\n \ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex a66d0e089d..88331d1ceb 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -329,7 +329,8 @@ test_expect_success 'incremental-repack task' '\n \n \t# Delete refs that have not been repacked in these packs.\n \tgit for-each-ref --format=\"delete %(refname)\" \\\n-\t\trefs/prefetch refs/tags refs/remotes >refs &&\n+\t\trefs/prefetch refs/tags refs/remotes \\\n+\t\t--exclude=refs/remotes/*/HEAD >refs &&\n \tgit update-ref --stdin <refs &&\n \n \t# Replace the object directory with this pack layout.\ndiff --git a/t/t9210-scalar.sh b/t/t9210-scalar.sh\nindex 027235d61a..a81662713e 100755\n--- a/t/t9210-scalar.sh\n+++ b/t/t9210-scalar.sh\n@@ -150,7 +150,8 @@ test_expect_success 'scalar clone' '\n \t\t\t\"$(pwd)\" &&\n \n \t\tgit for-each-ref --format=\"%(refname)\" refs/remotes/origin/ >actual &&\n-\t\techo \"refs/remotes/origin/parallel\" >expect &&\n+\t\techo \"refs/remotes/origin/HEAD\" >>expect &&\n+\t\techo \"refs/remotes/origin/parallel\" >>expect &&\n \t\ttest_cmp expect actual &&\n \n \t\ttest_path_is_missing 1/2 &&\n@@ -219,7 +220,7 @@ test_expect_success 'scalar reconfigure --all with includeIf.onbranch' '\n \tdone\n '\n \n- test_expect_success 'scalar reconfigure --all with detached HEADs' '\n+test_expect_success 'scalar reconfigure --all with detached HEADs' '\n \trepos=\"two three four\" &&\n \tfor num in $repos\n \tdo\ndiff --git a/t/t9211-scalar-clone.sh b/t/t9211-scalar-clone.sh\nindex 7869f45ee6..01f71910f5 100755\n--- a/t/t9211-scalar-clone.sh\n+++ b/t/t9211-scalar-clone.sh\n@@ -31,7 +31,7 @@ test_expect_success 'set up repository to clone' '\n \t)\n '\n \n-cleanup_clone () {\n+cleanup_clone() {\n \trm -rf \"$1\"\n }\n \n@@ -127,7 +127,7 @@ test_expect_success '--single-branch clones HEAD only' '\n \t(\n \t\tcd $enlistment/src &&\n \t\tgit for-each-ref refs/remotes/origin >out &&\n-\t\ttest_line_count = 1 out &&\n+\t\ttest_line_count = 2 out &&\n \t\tgrep \"refs/remotes/origin/base\" out\n \t) &&\n \n@@ -141,7 +141,7 @@ test_expect_success '--no-single-branch clones all branches' '\n \t(\n \t\tcd $enlistment/src &&\n \t\tgit for-each-ref refs/remotes/origin >out &&\n-\t\ttest_line_count = 2 out &&\n+\t\ttest_line_count = 3 out &&\n \t\tgrep \"refs/remotes/origin/base\" out &&\n \t\tgrep \"refs/remotes/origin/parallel\" out\n \t) &&\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex cc6aa9f0cd..b663c4609e 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -658,6 +658,7 @@ test_expect_success '__git_refs - simple' '\n \tHEAD\n \tmain\n \tmatching-branch\n+\tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n \tmatching-tag\n@@ -673,6 +674,7 @@ test_expect_success '__git_refs - full refs' '\n \tcat >expected <<-EOF &&\n \trefs/heads/main\n \trefs/heads/matching-branch\n+\trefs/remotes/other/HEAD\n \trefs/remotes/other/branch-in-other\n \trefs/remotes/other/main-in-other\n \trefs/tags/matching-tag\n@@ -729,6 +731,7 @@ test_expect_success '__git_refs - remote on local file system - full refs' '\n test_expect_success '__git_refs - configured remote' '\n \tcat >expected <<-EOF &&\n \tHEAD\n+\tHEAD\n \tbranch-in-other\n \tmain-in-other\n \tEOF\n@@ -756,6 +759,7 @@ test_expect_success '__git_refs - configured remote - full refs' '\n test_expect_success '__git_refs - configured remote - repo given on the command line' '\n \tcat >expected <<-EOF &&\n \tHEAD\n+\tHEAD\n \tbranch-in-other\n \tmain-in-other\n \tEOF\n@@ -787,6 +791,7 @@ test_expect_success '__git_refs - configured remote - full refs - repo given on\n test_expect_success '__git_refs - configured remote - remote name matches a directory' '\n \tcat >expected <<-EOF &&\n \tHEAD\n+\tHEAD\n \tbranch-in-other\n \tmain-in-other\n \tEOF\n@@ -875,12 +880,14 @@ test_expect_success '__git_refs - unique remote branches for git checkout DWIMer\n \tHEAD\n \tmain\n \tmatching-branch\n+\tother/HEAD\n \tother/ambiguous\n \tother/branch-in-other\n \tother/main-in-other\n \tremote/ambiguous\n \tremote/branch-in-remote\n \tmatching-tag\n+\tHEAD\n \tbranch-in-other\n \tbranch-in-remote\n \tmain-in-other\n@@ -904,6 +911,7 @@ test_expect_success '__git_refs - after --opt=' '\n \tHEAD\n \tmain\n \tmatching-branch\n+\tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n \tmatching-tag\n@@ -919,6 +927,7 @@ test_expect_success '__git_refs - after --opt= - full refs' '\n \tcat >expected <<-EOF &&\n \trefs/heads/main\n \trefs/heads/matching-branch\n+\trefs/remotes/other/HEAD\n \trefs/remotes/other/branch-in-other\n \trefs/remotes/other/main-in-other\n \trefs/tags/matching-tag\n@@ -935,6 +944,7 @@ test_expect_success '__git refs - excluding refs' '\n \t^HEAD\n \t^main\n \t^matching-branch\n+\t^other/HEAD\n \t^other/branch-in-other\n \t^other/main-in-other\n \t^matching-tag\n@@ -950,6 +960,7 @@ test_expect_success '__git refs - excluding full refs' '\n \tcat >expected <<-EOF &&\n \t^refs/heads/main\n \t^refs/heads/matching-branch\n+\t^refs/remotes/other/HEAD\n \t^refs/remotes/other/branch-in-other\n \t^refs/remotes/other/main-in-other\n \t^refs/tags/matching-tag\n@@ -975,6 +986,7 @@ test_expect_success '__git_refs - do not filter refs unless told so' '\n \tmain\n \tmatching-branch\n \tmatching/branch\n+\tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n \tother/matching/branch-in-other\n@@ -1095,6 +1107,7 @@ test_expect_success '__git_complete_refs - simple' '\n \tHEAD Z\n \tmain Z\n \tmatching-branch Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tmatching-tag Z\n@@ -1123,6 +1136,7 @@ test_expect_success '__git_complete_refs - matching' '\n test_expect_success '__git_complete_refs - remote' '\n \tsed -e \"s/Z$//\" >expected <<-EOF &&\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain-in-other Z\n \tEOF\n@@ -1139,9 +1153,11 @@ test_expect_success '__git_complete_refs - track' '\n \tHEAD Z\n \tmain Z\n \tmatching-branch Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tmatching-tag Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain-in-other Z\n \tEOF\n@@ -1184,6 +1200,7 @@ test_expect_success '__git_complete_refs - suffix' '\n \tHEAD.\n \tmain.\n \tmatching-branch.\n+\tother/HEAD.\n \tother/branch-in-other.\n \tother/main-in-other.\n \tmatching-tag.\n@@ -1199,6 +1216,7 @@ test_expect_success '__git_complete_refs - suffix' '\n test_expect_success '__git_complete_fetch_refspecs - simple' '\n \tsed -e \"s/Z$//\" >expected <<-EOF &&\n \tHEAD:HEAD Z\n+\tHEAD:HEAD Z\n \tbranch-in-other:branch-in-other Z\n \tmain-in-other:main-in-other Z\n \tEOF\n@@ -1225,6 +1243,7 @@ test_expect_success '__git_complete_fetch_refspecs - matching' '\n test_expect_success '__git_complete_fetch_refspecs - prefix' '\n \tsed -e \"s/Z$//\" >expected <<-EOF &&\n \t+HEAD:HEAD Z\n+\t+HEAD:HEAD Z\n \t+branch-in-other:branch-in-other Z\n \t+main-in-other:main-in-other Z\n \tEOF\n@@ -1289,6 +1308,7 @@ test_expect_success '__git_complete_worktree_paths with -C' '\n \n test_expect_success 'git switch - with no options, complete local branches and unique remote branch names for DWIM logic' '\n \ttest_completion \"git switch \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1435,11 +1455,13 @@ test_expect_success 'git-bisect - existing view subcommand is recognized and ena\n test_expect_success 'git checkout - completes refs and unique remote branches for DWIM' '\n \ttest_completion \"git checkout \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1461,6 +1483,7 @@ test_expect_success 'git switch - with GIT_COMPLETION_CHECKOUT_NO_GUESS=1, compl\n \n test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete local branches and unique remote names for DWIM logic' '\n \tGIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git switch --guess \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1470,6 +1493,7 @@ test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_G\n \n test_expect_success 'git switch - a later --guess overrides previous --no-guess, complete local and remote unique branches for DWIM' '\n \ttest_completion \"git switch --no-guess --guess \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1490,6 +1514,7 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1498,11 +1523,13 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete\n test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1, complete refs and unique remote branches for DWIM' '\n \tGIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git checkout --guess \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1514,6 +1541,7 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1522,11 +1550,13 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '\n test_expect_success 'git checkout - a later --guess overrides previous --no-guess, complete refs and unique remote branches for DWIM' '\n \ttest_completion \"git checkout --no-guess --guess \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1538,6 +1568,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous --gues\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1550,6 +1581,7 @@ test_expect_success 'git checkout - with checkout.guess = false, only completes\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1559,11 +1591,13 @@ test_expect_success 'git checkout - with checkout.guess = true, completes refs a\n \ttest_config checkout.guess true &&\n \ttest_completion \"git checkout \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1573,11 +1607,13 @@ test_expect_success 'git checkout - a later --guess overrides previous checkout.\n \ttest_config checkout.guess false &&\n \ttest_completion \"git checkout --guess \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1590,6 +1626,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous checko\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1601,6 +1638,7 @@ test_expect_success 'git switch - with --detach, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1612,6 +1650,7 @@ test_expect_success 'git checkout - with --detach, complete only references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1783,6 +1822,7 @@ test_expect_success 'git switch - with -d, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1794,6 +1834,7 @@ test_expect_success 'git checkout - with -d, complete only references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1801,10 +1842,12 @@ test_expect_success 'git checkout - with -d, complete only references' '\n \n test_expect_success 'git switch - with --track, complete only remote branches' '\n \ttest_completion \"git switch --track \" <<-\\EOF &&\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n \ttest_completion \"git switch -t \" <<-\\EOF\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1812,10 +1855,12 @@ test_expect_success 'git switch - with --track, complete only remote branches' '\n \n test_expect_success 'git checkout - with --track, complete only remote branches' '\n \ttest_completion \"git checkout --track \" <<-\\EOF &&\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n \ttest_completion \"git checkout -t \" <<-\\EOF\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1834,6 +1879,7 @@ test_expect_success 'git checkout - with --no-track, complete only local referen\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1845,6 +1891,7 @@ test_expect_success 'git switch - with -c, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1856,6 +1903,7 @@ test_expect_success 'git switch - with -C, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1867,6 +1915,7 @@ test_expect_success 'git switch - with -c and --track, complete all references'\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1878,6 +1927,7 @@ test_expect_success 'git switch - with -C and --track, complete all references'\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1889,6 +1939,7 @@ test_expect_success 'git switch - with -c and --no-track, complete all reference\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1900,6 +1951,7 @@ test_expect_success 'git switch - with -C and --no-track, complete all reference\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1911,6 +1963,7 @@ test_expect_success 'git checkout - with -b, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1922,6 +1975,7 @@ test_expect_success 'git checkout - with -B, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1933,6 +1987,7 @@ test_expect_success 'git checkout - with -b and --track, complete all references\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1944,6 +1999,7 @@ test_expect_success 'git checkout - with -B and --track, complete all references\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1955,6 +2011,7 @@ test_expect_success 'git checkout - with -b and --no-track, complete all referen\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1966,6 +2023,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1973,6 +2031,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen\n \n test_expect_success 'git switch - for -c, complete local branches and unique remote branches' '\n \ttest_completion \"git switch -c \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1982,6 +2041,7 @@ test_expect_success 'git switch - for -c, complete local branches and unique rem\n \n test_expect_success 'git switch - for -C, complete local branches and unique remote branches' '\n \ttest_completion \"git switch -C \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2019,6 +2079,7 @@ test_expect_success 'git switch - for -C with --no-track, complete local branche\n \n test_expect_success 'git checkout - for -b, complete local branches and unique remote branches' '\n \ttest_completion \"git checkout -b \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2028,6 +2089,7 @@ test_expect_success 'git checkout - for -b, complete local branches and unique r\n \n test_expect_success 'git checkout - for -B, complete local branches and unique remote branches' '\n \ttest_completion \"git checkout -B \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2065,6 +2127,7 @@ test_expect_success 'git checkout - for -B with --no-track, complete local branc\n \n test_expect_success 'git switch - with --orphan completes local branch names and unique remote branch names' '\n \ttest_completion \"git switch --orphan \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2080,6 +2143,7 @@ test_expect_success 'git switch - --orphan with branch already provided complete\n \n test_expect_success 'git checkout - with --orphan completes local branch names and unique remote branch names' '\n \ttest_completion \"git checkout --orphan \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2093,6 +2157,7 @@ test_expect_success 'git checkout - --orphan with branch already provided comple\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n-- \n2.47.0.7.g072c39eddb.dirty\n\n\n"},{"id":"505097","messageId":"20241014225431.1394565-4-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241014225431.1394565-1-bence@ferdinandy.com","subject":"[PATCH v8 3/6] remote set-head: better output for --auto","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-14T22:53:12Z","receivedAt":"2024-10-14T22:56:21Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Currently, set-head --auto will print a message saying \"remote/HEAD set\nto branch\", which implies something was changed.\n\nChange the output of --auto, so the output actually reflects what was\ndone: a) set a previously unset HEAD, b) change HEAD because remote\nchanged or c) no updates. As a fourth output, if HEAD is changed from\na previous value that was not a remote branch, explicitly call attention\nto this fact.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v1-v2:\n    \n        was RFC in https://lore.kernel.org/git/20240910203835.2288291-1-bence@ferdinandy.com/\n    \n    v3:\n    \n        This patch was originally sent along when I thought set-head was\n        going to be invoked by fetch, but the discussion on the RFC\n        concluded that it should be not. This opened the possibility to make\n        it more explicit.\n    \n        Note: although I feel both things the patch does are really just\n        cosmetic, an argument could be made for breaking it into two, one\n        for the no-op part and one for the --auto print update.\n    \n        Was sent in:\n        https://lore.kernel.org/git/20240915221055.904107-1-bence@ferdinandy.com/\n    \n    v4:\n        - changes are now handled atomically via the ref update transaction\n        - outputs have changed along the lines of Junio's suggestion\n        - minor refactor to set_head for improved legibility\n    \n    v5: - the minor refactor has been split out into its own patch\n    \n    v6: - fixed uninitialized prev_head\n        - fixed case of unusual previous target\n        - fixed a test that would have been actually broken at this patch\n          (the output was only correct with the later update to fetch)\n        - added 4 tests for the 4 output cases\n    \n    v7: - change commit prefix to be more in line with project standards\n        - fixed tests to also work with the reftable backend\n        - renamed report function, fixed style issue with checking buf len\n        - fixed not releasing an strbuf\n    \n    v8: no change\n\n builtin/remote.c  | 35 ++++++++++++++++++++++++++++----\n t/t5505-remote.sh | 51 ++++++++++++++++++++++++++++++++++++++++++++++-\n 2 files changed, 81 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 353ffd2c43..2b6948439f 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1399,10 +1399,35 @@ static int show(int argc, const char **argv, const char *prefix)\n \treturn result;\n }\n \n+static void report_set_head_auto(const char *remote, const char *head_name,\n+\t\t\tstruct strbuf *buf_prev) {\n+\tstruct strbuf buf_prefix = STRBUF_INIT;\n+\tconst char *prev_head = NULL;\n+\n+\tstrbuf_addf(&buf_prefix, \"refs/remotes/%s/\", remote);\n+\tskip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);\n+\n+\tif (prev_head && !strcmp(prev_head, head_name))\n+\t\tprintf(\"'%s/HEAD' is unchanged and points to '%s'\\n\",\n+\t\t\tremote, head_name);\n+\telse if (prev_head)\n+\t\tprintf(\"'%s/HEAD' has changed from '%s' and now points to '%s'\\n\",\n+\t\t\tremote, prev_head, head_name);\n+\telse if (!buf_prev->len)\n+\t\tprintf(\"'%s/HEAD' is now created and points to '%s'\\n\",\n+\t\t\tremote, head_name);\n+\telse\n+\t\tprintf(\"'%s/HEAD' used to point to '%s' \"\n+\t\t\t\"(which is not a remote branch), but now points to '%s'\\n\",\n+\t\t\tremote, buf_prev->buf, head_name);\n+\tstrbuf_release(&buf_prefix);\n+}\n+\n static int set_head(int argc, const char **argv, const char *prefix)\n {\n \tint i, opt_a = 0, opt_d = 0, result = 0;\n-\tstruct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;\n+\tstruct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT,\n+\t\tbuf_prev = STRBUF_INIT;\n \tchar *head_name = NULL;\n \tstruct ref_store *refs = get_main_ref_store(the_repository);\n \n@@ -1445,15 +1470,17 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\t/* make sure it's valid */\n \t\tif (!refs_ref_exists(refs, buf2.buf))\n \t\t\tresult |= error(_(\"Not a valid ref: %s\"), buf2.buf);\n-\t\telse if (refs_update_symref(refs, buf.buf, buf2.buf, \"remote set-head\", NULL))\n+\t\telse if (refs_update_symref(refs, buf.buf, buf2.buf, \"remote set-head\", &buf_prev))\n \t\t\tresult |= error(_(\"Could not setup %s\"), buf.buf);\n-\t\telse if (opt_a)\n-\t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\n+\t\telse if (opt_a) {\n+\t\t\treport_set_head_auto(argv[0], head_name, &buf_prev);\n+\t\t}\n \t\tfree(head_name);\n \t}\n \n \tstrbuf_release(&buf);\n \tstrbuf_release(&buf2);\n+\tstrbuf_release(&buf_prev);\n \treturn result;\n }\n \ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex 532035933f..d99cd1d0aa 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -429,12 +429,51 @@ test_expect_success 'set-head --auto' '\n \t)\n '\n \n+test_expect_success 'set-head --auto detects creation' '\n+\t(\n+\t\tcd test &&\n+\t\tgit symbolic-ref -d refs/remotes/origin/HEAD &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"'\\''origin/HEAD'\\'' is now created and points to '\\''main'\\''\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n+test_expect_success 'set-head --auto detects no change' '\n+\t(\n+\t\tcd test &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"'\\''origin/HEAD'\\'' is unchanged and points to '\\''main'\\''\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n+test_expect_success 'set-head --auto detects change' '\n+\t(\n+\t\tcd test &&\n+\t\tgit symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/ahead &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"'\\''origin/HEAD'\\'' has changed from '\\''ahead'\\'' and now points to '\\''main'\\''\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n+test_expect_success 'set-head --auto detects strange ref' '\n+\t(\n+\t\tcd test &&\n+\t\tgit symbolic-ref refs/remotes/origin/HEAD refs/heads/main &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"'\\''origin/HEAD'\\'' used to point to '\\''refs/heads/main'\\'' (which is not a remote branch), but now points to '\\''main'\\''\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n test_expect_success 'set-head --auto has no problem w/multiple HEADs' '\n \t(\n \t\tcd test &&\n \t\tgit fetch two \"refs/heads/*:refs/remotes/two/*\" &&\n \t\tgit remote set-head --auto two >output 2>&1 &&\n-\t\techo \"two/HEAD set to main\" >expect &&\n+\t\techo \"'\\''two/HEAD'\\'' is now created and points to '\\''main'\\''\" >expect &&\n \t\ttest_cmp expect output\n \t)\n '\n@@ -453,6 +492,16 @@ test_expect_success 'set-head explicit' '\n \t)\n '\n \n+test_expect_success 'set-head --auto reports change' '\n+\t(\n+\t\tcd test &&\n+\t\tgit remote set-head origin side2 &&\n+\t\tgit remote set-head --auto origin >output 2>&1 &&\n+\t\techo \"'\\''origin/HEAD'\\'' has changed from '\\''side2'\\'' and now points to '\\''main'\\''\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n cat >test/expect <<EOF\n Pruning origin\n URL: $(pwd)/one\n-- \n2.47.0.7.g072c39eddb.dirty\n\n"},{"id":"505121","messageId":"CAOLa=ZTMzz-9XE8AY8vk7Gpbm+U0T-5jWNJzKLyd621_URiDEA@mail.gmail.com","threadId":"62096","inReplyTo":"20241014225431.1394565-2-bence@ferdinandy.com","subject":"Re: [PATCH v8 1/6] refs: atomically record overwritten ref in update_symref","fromName":"karthik nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-10-15T07:41:23Z","receivedAt":"2024-10-15T07:41:27Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Bence Ferdinandy <bence@ferdinandy.com> writes:\n\n[snip]\n\n> diff --git a/refs.c b/refs.c\n> index 5f729ed412..b964ac44d0 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -2114,7 +2114,8 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct\n>  }\n>\n>  int refs_update_symref(struct ref_store *refs, const char *ref,\n> -\t\t       const char *target, const char *logmsg)\n> +\t\t       const char *target, const char *logmsg,\n> +\t\t       struct strbuf *referent)\n>  {\n>  \tstruct ref_transaction *transaction;\n>  \tstruct strbuf err = STRBUF_INIT;\n> @@ -2122,17 +2123,23 @@ int refs_update_symref(struct ref_store *refs, const char *ref,\n>\n>  \ttransaction = ref_store_transaction_begin(refs, &err);\n>  \tif (!transaction ||\n> -\t    ref_transaction_update(transaction, ref, NULL, NULL,\n> +\t\tref_transaction_update(transaction, ref, NULL, NULL,\n>  \t\t\t\t   target, NULL, REF_NO_DEREF,\n>  \t\t\t\t   logmsg, &err) ||\n> -\t    ref_transaction_commit(transaction, &err)) {\n> +\t\tref_transaction_prepare(transaction, &err)) {\n>  \t\tret = error(\"%s\", err.buf);\n> +\t\tgoto cleanup;\n>  \t}\n> +\tif (referent)\n> +\t\trefs_read_symbolic_ref(refs, ref, referent);\n\nShouldn't we also check the return value here?\n\n> +\n> +\tif (ref_transaction_commit(transaction, &err))\n> +\t\tret = error(\"%s\", err.buf);\n>\n> +cleanup:\n>  \tstrbuf_release(&err);\n>  \tif (transaction)\n>  \t\tref_transaction_free(transaction);\n> -\n>\n\nWhy remove this whiteline?\n\n>  \treturn ret;\n>  }\n>\n\n[snip]\n"},{"id":"505122","messageId":"CAOLa=ZT_m=H8pW9qkoOf+b0bLnzvT-3taQYFu+J6WT3DRi=6sQ@mail.gmail.com","threadId":"62096","inReplyTo":"20241014225431.1394565-4-bence@ferdinandy.com","subject":"Re: [PATCH v8 3/6] remote set-head: better output for --auto","fromName":"karthik nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-10-15T07:47:59Z","receivedAt":"2024-10-15T07:48:02Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Bence Ferdinandy <bence@ferdinandy.com> writes:\n\n[snip]\n\n> +static void report_set_head_auto(const char *remote, const char *head_name,\n> +\t\t\tstruct strbuf *buf_prev) {\n> +\tstruct strbuf buf_prefix = STRBUF_INIT;\n> +\tconst char *prev_head = NULL;\n> +\n> +\tstrbuf_addf(&buf_prefix, \"refs/remotes/%s/\", remote);\n> +\tskip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);\n> +\n> +\tif (prev_head && !strcmp(prev_head, head_name))\n> +\t\tprintf(\"'%s/HEAD' is unchanged and points to '%s'\\n\",\n> +\t\t\tremote, head_name);\n> +\telse if (prev_head)\n> +\t\tprintf(\"'%s/HEAD' has changed from '%s' and now points to '%s'\\n\",\n> +\t\t\tremote, prev_head, head_name);\n> +\telse if (!buf_prev->len)\n> +\t\tprintf(\"'%s/HEAD' is now created and points to '%s'\\n\",\n> +\t\t\tremote, head_name);\n> +\telse\n> +\t\tprintf(\"'%s/HEAD' used to point to '%s' \"\n> +\t\t\t\"(which is not a remote branch), but now points to '%s'\\n\",\n> +\t\t\tremote, buf_prev->buf, head_name);\n> +\tstrbuf_release(&buf_prefix);\n> +}\n> +\n\nShouldn't the messages above be marked for translation too? using `_()`?\n\n>  static int set_head(int argc, const char **argv, const char *prefix)\n>  {\n>  \tint i, opt_a = 0, opt_d = 0, result = 0;\n> -\tstruct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;\n> +\tstruct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT,\n> +\t\tbuf_prev = STRBUF_INIT;\n\nNit: these names could definitely be better.\n\n[snip]\n"},{"id":"505123","messageId":"CAOLa=ZRfku-J2HO8MOqu-dZspmCZDV1S56TD1X-zUy5=9+XSUQ@mail.gmail.com","threadId":"62096","inReplyTo":"D4U6L3C2GHZG.2SN8P0C7ZH33M@ferdinandy.com","subject":"Re: [PATCH v6 3/6] set-head: better output for --auto","fromName":"karthik nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-10-15T07:51:57Z","receivedAt":"2024-10-15T07:51:59Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"\"Bence Ferdinandy\" <bence@ferdinandy.com> writes:\n\n> On Fri Oct 11, 2024 at 22:43, karthik nayak <karthik.188@gmail.com> wrote:\n>> \"Bence Ferdinandy\" <bence@ferdinandy.com> writes:\n>>\n>> [snip]\n>>\n>>>>\n>>>> > +\t\tgit remote set-head --auto origin >output &&\n>>>> > +\t\techo \"'\\''origin/HEAD'\\'' is now created and points to '\\''main'\\''\" >expect &&\n>>>>\n>>>> Nit: might be cleaner to use `${SQ}` here and below\n>>> You mean something like this?\n>>>\n>>> \tgit remote set-head --auto origin >output &&\n>>> \tHEAD=\"'\\''origin/HEAD'\\''\" &&\n>>> \techo \"${HEAD} is now created and points to '\\''main'\\''\" >expect &&\n>>>\n>>> I tried a few variations and this is what I could get working, but I may be\n>>> simply missing something with the backtick.\n>>\n>> I mean simply this\n>>\n>>     git remote set-head --auto origin >output &&\n>>     echo \"${SQ}origin/HEAD${SQ} is now created and points to\n>> ${SQ}main${SQ}\" >expect &&\n>\n> Ah, I see in other tests this is used, but not in this particular test file.\n> It's a bit hard to decide which is more cryptic, but ${SQ} is nicer on the\n> eyes. On the other hand I would either switch the entire file in a separate\n> patch or leave in the '\\'' here as well. Or I guess one could go through the\n> entire test base and switch everything to either one or the other for\n> consistency.\n\nI'm not sure I entirely agree with this sentiment. Consistency is a\ngreat goal to target, but it shouldn't hinder changes that are\nbeneficial. In our case, if you make the first change, there is now\nreason for upcoming changes to the same file to also use '${SQ}' and\neventually we can reach consistency of using '${SQ}' throughout the file.\n\n- Karthik\n"},{"id":"505165","messageId":"a7cb48e5-d8ba-44c1-9dbe-d1e8f8a63e3c@gmail.com","threadId":"62096","inReplyTo":"D4UZTVHXXL1X.2KBQI9FYTAU72@ferdinandy.com","subject":"Re: [PATCH v7 1/6] refs: atomically record overwritten ref in update_symref","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-10-15T14:05:58Z","receivedAt":"2024-10-15T14:06:04Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Bence\n\nOn 13/10/2024 22:24, Bence Ferdinandy wrote:\n> \n> On Sun Oct 13, 2024 at 15:52, Phillip Wood <phillip.wood123@gmail.com> wrote:\n>> On 13/10/2024 00:03, Bence Ferdinandy wrote:\n>>> Record the value after the\n>>> ref has been locked if the caller of refs_update_symref requests it via\n>>> a new variable in the function call.\n>>\n>> To me this patch and patch 5 feel quite disruptive to all the existing\n>> callers which don't need this specialized functionality. I think it\n>> would be less disruptive over all if you used a ref transaction rather\n>> than calling refs_update_symref() in the final patch. That would enable\n>> us to keep the simpler interface for refs_update_symref().\n> \n> The extra parameter introduced here is actually used in two places by the end\n> of the series, in remote set-head and fetch (of course you could make a similar\n> argument for the functionality added in 5/6 which is only used in fetch by the\n> final patch). To avoid code duplication I think even if we did not touch\n> refs_update_symref() it would make sense to create\n> a \"refs_update_symref_extended()\" and make refs_update_symref() a wrapper\n> around that with a few less parameters. That would be similar to how\n> refs_update_symref() and refs_update_ref() predetermine a couple of parameters\n> to say transaction_update().\n> \n> Currently there are 15 calls to refs_update_symref() in total, of these\n> 5 do not use the complete functionality of the function (they pass NULL as\n> logmsg), so the current implementation would not be completely unprecedented.\n\nAs those figures show it's pretty unusual not to pass a reflog message \nwhen updating a ref, on the other hand it is very unusual to want the \nold value so I don't think the two are comparable. At a high level the \ntwo callers that want to be able to check the old value are both doing \nessentially the same thing so can we create a specialized function that \nencapsulates the functionality needed by --set-head and uses a ref \ntransaction?\n\n> (This tally did make me catch an error on my side: the logmsg in fetch's\n> set_head should be \"fetch\" not \"remote set-head\", I'll fix that in a v8).\n> \n> Imho, even if I manage to come up with a better name than\n> \"refs_update_symref_extended()\" wouldn't it be more confusing to have two ways\n> to update symrefs via a function call, rather than have one, where _usually_\n> you pass two NULL-s at the end?\n> \n>>\n>> I'm also not sure about the proposed interface I would have thought it\n>> would be simpler to take a \"char**\" rather than an \"struct strbuf*\" if\n>> we do decide that it is useful for callers of refs_update_symref() to\n>> query the old value.\n> \n> refs_read_symbolic_ref requires a strbuf, so one would need to be created\n> anyway and I also sort of got the feeling that the project likes to handle refs\n> in strbufs (which may be wrong). Are there any downsides I'm not seeing?\n\nIt's true that refs_read_symbolic_ref takes and strbuf. I'd argue that's \na mistake for a function that is just returning a string in an \"out\" \nparameter as I think it is more continent for the caller not to have to \ninitialize an strbuf just to retrieve the target of a symbolic ref. I \nalse think that it is inconsistent with functions like \nrefs_resolve_refdup() that return a string.\n\nBest Wishes\n\nPhillip\n"},{"id":"505166","messageId":"65dd8e08-d8b2-4abb-a141-002d8f0b0401@gmail.com","threadId":"62096","inReplyTo":"CAOLa=ZRfku-J2HO8MOqu-dZspmCZDV1S56TD1X-zUy5=9+XSUQ@mail.gmail.com","subject":"Re: [PATCH v6 3/6] set-head: better output for --auto","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-10-15T14:10:02Z","receivedAt":"2024-10-15T14:10:05Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 15/10/2024 08:51, karthik nayak wrote:\n> \"Bence Ferdinandy\" <bence@ferdinandy.com> writes:\n>> On Fri Oct 11, 2024 at 22:43, karthik nayak <karthik.188@gmail.com> wrote:\n>>> \"Bence Ferdinandy\" <bence@ferdinandy.com> writes:\n>>> I mean simply this\n>>>\n>>>      git remote set-head --auto origin >output &&\n>>>      echo \"${SQ}origin/HEAD${SQ} is now created and points to\n>>> ${SQ}main${SQ}\" >expect &&\n>>\n>> Ah, I see in other tests this is used, but not in this particular test file.\n>> It's a bit hard to decide which is more cryptic, but ${SQ} is nicer on the\n>> eyes. On the other hand I would either switch the entire file in a separate\n>> patch or leave in the '\\'' here as well. Or I guess one could go through the\n>> entire test base and switch everything to either one or the other for\n>> consistency.\n> \n> I'm not sure I entirely agree with this sentiment. Consistency is a\n> great goal to target, but it shouldn't hinder changes that are\n> beneficial.\n\nExactly - if we wait for an entire test file to be modernized before \nusing our modern test idioms in it we'll be waiting forever. It is much \nbetter to introduce the use of things like ${SQ} that improve \nreadability as we add new tests or modify existing ones.\n\nBest Wishes\n\nPhillip\n\n  In our case, if you make the first change, there is now\n> reason for upcoming changes to the same file to also use '${SQ}' and\n> eventually we can reach consistency of using '${SQ}' throughout the file.\n> \n> - Karthik\n\n"},{"id":"505174","messageId":"D4WIJYJHI6MX.1GVDAHVOLKGO7@ferdinandy.com","threadId":"62096","inReplyTo":"65dd8e08-d8b2-4abb-a141-002d8f0b0401@gmail.com","subject":"Re: [PATCH v6 3/6] set-head: better output for --auto","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-15T16:17:26Z","receivedAt":"2024-10-15T16:18:08Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Tue Oct 15, 2024 at 16:10, Phillip Wood <phillip.wood123@gmail.com> wrote:\n> On 15/10/2024 08:51, karthik nayak wrote:\n>> \"Bence Ferdinandy\" <bence@ferdinandy.com> writes:\n>>> On Fri Oct 11, 2024 at 22:43, karthik nayak <karthik.188@gmail.com> wrote:\n>> \n>> I'm not sure I entirely agree with this sentiment. Consistency is a\n>> great goal to target, but it shouldn't hinder changes that are\n>> beneficial.\n>\n> Exactly - if we wait for an entire test file to be modernized before \n> using our modern test idioms in it we'll be waiting forever. It is much \n> better to introduce the use of things like ${SQ} that improve \n> readability as we add new tests or modify existing ones.\n\nOk, I'll change the patch to use SQ. As I gather, it would be preferred if\n_all_ the tests would use ${SQ}? If this patch series is finally resolved I can\nsend a patch for this style change, shouldn't be too complicated.\n"},{"id":"505176","messageId":"D4WIP42OSINN.VGZI7YIMMK8S@ferdinandy.com","threadId":"62096","inReplyTo":"CAOLa=ZTMzz-9XE8AY8vk7Gpbm+U0T-5jWNJzKLyd621_URiDEA@mail.gmail.com","subject":"Re: [PATCH v8 1/6] refs: atomically record overwritten ref in update_symref","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-15T16:24:10Z","receivedAt":"2024-10-15T16:24:50Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Tue Oct 15, 2024 at 09:41, karthik nayak <karthik.188@gmail.com> wrote:\n> Bence Ferdinandy <bence@ferdinandy.com> writes:\n>\n> [snip]\n>\n>> diff --git a/refs.c b/refs.c\n>> index 5f729ed412..b964ac44d0 100644\n>> --- a/refs.c\n>> +++ b/refs.c\n>> @@ -2114,7 +2114,8 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct\n>>  }\n>>\n>>  int refs_update_symref(struct ref_store *refs, const char *ref,\n>> -\t\t       const char *target, const char *logmsg)\n>> +\t\t       const char *target, const char *logmsg,\n>> +\t\t       struct strbuf *referent)\n>>  {\n>>  \tstruct ref_transaction *transaction;\n>>  \tstruct strbuf err = STRBUF_INIT;\n>> @@ -2122,17 +2123,23 @@ int refs_update_symref(struct ref_store *refs, const char *ref,\n>>\n>>  \ttransaction = ref_store_transaction_begin(refs, &err);\n>>  \tif (!transaction ||\n>> -\t    ref_transaction_update(transaction, ref, NULL, NULL,\n>> +\t\tref_transaction_update(transaction, ref, NULL, NULL,\n>>  \t\t\t\t   target, NULL, REF_NO_DEREF,\n>>  \t\t\t\t   logmsg, &err) ||\n>> -\t    ref_transaction_commit(transaction, &err)) {\n>> +\t\tref_transaction_prepare(transaction, &err)) {\n>>  \t\tret = error(\"%s\", err.buf);\n>> +\t\tgoto cleanup;\n>>  \t}\n>> +\tif (referent)\n>> +\t\trefs_read_symbolic_ref(refs, ref, referent);\n>\n> Shouldn't we also check the return value here?\n\nMy reasoning was that if this fails referent will just look like as if it did\nnot exist. Since this is an addition to set-head and fetch failing to set the\nHEAD in a case which would have previously worked I did not think it prudent to\nnow fail on this for any reason.\n\n>\n>> +\n>> +\tif (ref_transaction_commit(transaction, &err))\n>> +\t\tret = error(\"%s\", err.buf);\n>>\n>> +cleanup:\n>>  \tstrbuf_release(&err);\n>>  \tif (transaction)\n>>  \t\tref_transaction_free(transaction);\n>> -\n>>\n>\n> Why remove this whiteline?\n\nLooks like a mistake made during rebase.\n\n\n\n-- \nbence.ferdinandy.com\n\n"},{"id":"505178","messageId":"D4WIUDMT92X8.31YFOS47V1B4E@ferdinandy.com","threadId":"62096","inReplyTo":"CAOLa=ZT_m=H8pW9qkoOf+b0bLnzvT-3taQYFu+J6WT3DRi=6sQ@mail.gmail.com","subject":"Re: [PATCH v8 3/6] remote set-head: better output for --auto","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-15T16:31:02Z","receivedAt":"2024-10-15T16:31:46Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Tue Oct 15, 2024 at 09:47, karthik nayak <karthik.188@gmail.com> wrote:\n> Bence Ferdinandy <bence@ferdinandy.com> writes:\n>\n> [snip]\n>\n>> +static void report_set_head_auto(const char *remote, const char *head_name,\n>> +\t\t\tstruct strbuf *buf_prev) {\n>> +\tstruct strbuf buf_prefix = STRBUF_INIT;\n>> +\tconst char *prev_head = NULL;\n>> +\n>> +\tstrbuf_addf(&buf_prefix, \"refs/remotes/%s/\", remote);\n>> +\tskip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);\n>> +\n>> +\tif (prev_head && !strcmp(prev_head, head_name))\n>> +\t\tprintf(\"'%s/HEAD' is unchanged and points to '%s'\\n\",\n>> +\t\t\tremote, head_name);\n>> +\telse if (prev_head)\n>> +\t\tprintf(\"'%s/HEAD' has changed from '%s' and now points to '%s'\\n\",\n>> +\t\t\tremote, prev_head, head_name);\n>> +\telse if (!buf_prev->len)\n>> +\t\tprintf(\"'%s/HEAD' is now created and points to '%s'\\n\",\n>> +\t\t\tremote, head_name);\n>> +\telse\n>> +\t\tprintf(\"'%s/HEAD' used to point to '%s' \"\n>> +\t\t\t\"(which is not a remote branch), but now points to '%s'\\n\",\n>> +\t\t\tremote, buf_prev->buf, head_name);\n>> +\tstrbuf_release(&buf_prefix);\n>> +}\n>> +\n>\n> Shouldn't the messages above be marked for translation too? using `_()`?\n\nI guess it is unlikely to fall under the \"should be machine readable\" category.\nI'll fix that up as well.\n\n>\n>>  static int set_head(int argc, const char **argv, const char *prefix)\n>>  {\n>>  \tint i, opt_a = 0, opt_d = 0, result = 0;\n>> -\tstruct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;\n>> +\tstruct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT,\n>> +\t\tbuf_prev = STRBUF_INIT;\n>\n> Nit: these names could definitely be better.\n\nIndeed ... The second patch is a refactor of remote set-head, I could include\na variable rename refactor there as well, and use the same names I used in the\nlast patch when adding the similar calls to fetch's set_head.\n\n"},{"id":"505180","messageId":"D4WJZVALZYZ6.1FR78333KZBAL@ferdinandy.com","threadId":"62096","inReplyTo":"a7cb48e5-d8ba-44c1-9dbe-d1e8f8a63e3c@gmail.com","subject":"Re: [PATCH v7 1/6] refs: atomically record overwritten ref in update_symref","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-15T17:25:14Z","receivedAt":"2024-10-15T17:25:36Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Tue Oct 15, 2024 at 16:05, Phillip Wood <phillip.wood123@gmail.com> wrote:\n> Hi Bence\n>\n> On 13/10/2024 22:24, Bence Ferdinandy wrote:\n>> \n>> On Sun Oct 13, 2024 at 15:52, Phillip Wood <phillip.wood123@gmail.com> wrote:\n>>> On 13/10/2024 00:03, Bence Ferdinandy wrote:\n>>>> Record the value after the\n>>>> ref has been locked if the caller of refs_update_symref requests it via\n>>>> a new variable in the function call.\n>>>\n>>> To me this patch and patch 5 feel quite disruptive to all the existing\n>>> callers which don't need this specialized functionality. I think it\n>>> would be less disruptive over all if you used a ref transaction rather\n>>> than calling refs_update_symref() in the final patch. That would enable\n>>> us to keep the simpler interface for refs_update_symref().\n>> \n>> The extra parameter introduced here is actually used in two places by the end\n>> of the series, in remote set-head and fetch (of course you could make a similar\n>> argument for the functionality added in 5/6 which is only used in fetch by the\n>> final patch). To avoid code duplication I think even if we did not touch\n>> refs_update_symref() it would make sense to create\n>> a \"refs_update_symref_extended()\" and make refs_update_symref() a wrapper\n>> around that with a few less parameters. That would be similar to how\n>> refs_update_symref() and refs_update_ref() predetermine a couple of parameters\n>> to say transaction_update().\n>> \n>> Currently there are 15 calls to refs_update_symref() in total, of these\n>> 5 do not use the complete functionality of the function (they pass NULL as\n>> logmsg), so the current implementation would not be completely unprecedented.\n>\n> As those figures show it's pretty unusual not to pass a reflog message \n> when updating a ref, on the other hand it is very unusual to want the \n> old value so I don't think the two are comparable. At a high level the \n> two callers that want to be able to check the old value are both doing \n> essentially the same thing so can we create a specialized function that \n> encapsulates the functionality needed by --set-head and uses a ref \n> transaction?\n\nOk, so let's not change the signature of refs_update_symref(). On the other\nhand the best approach here is still not quite clear to me. As it is now, the\nseries added two extra parameters to refs_update_symref(), the referent and the\ncreate_only option. Obviously any new function would also take all the\nparameters refs_update_symref takes. Should I rename the current (v8)\nrefs_update_symref to something else (name suggestion would be welcome,\nrefs_update_symref_extended?, refs_update_symref_check_current? checking the\ncurrently existing symref is the common theme for the two new parameters) and\nleave refs_update_symref as it is? Or is this really about just not having to\nchange all the already existing callers? Because in that case I'd just wrap the\nnew function, something like\n\nint refs_update_symref(struct ref_store *refs, const char *ref,\n\t\t       const char *target, const char *logmsg)\n\t\n\treturn refs_update_symref_extended(*refs, *ref, *target, *logmsg, NULL, 0);\n\nLet me know what you think, I'll hold off on a v9 until I see more clearly here\non what would be preferred.\n\n>\n>> (This tally did make me catch an error on my side: the logmsg in fetch's\n>> set_head should be \"fetch\" not \"remote set-head\", I'll fix that in a v8).\n>> \n>> Imho, even if I manage to come up with a better name than\n>> \"refs_update_symref_extended()\" wouldn't it be more confusing to have two ways\n>> to update symrefs via a function call, rather than have one, where _usually_\n>> you pass two NULL-s at the end?\n>> \n>>>\n>>> I'm also not sure about the proposed interface I would have thought it\n>>> would be simpler to take a \"char**\" rather than an \"struct strbuf*\" if\n>>> we do decide that it is useful for callers of refs_update_symref() to\n>>> query the old value.\n>> \n>> refs_read_symbolic_ref requires a strbuf, so one would need to be created\n>> anyway and I also sort of got the feeling that the project likes to handle refs\n>> in strbufs (which may be wrong). Are there any downsides I'm not seeing?\n>\n> It's true that refs_read_symbolic_ref takes and strbuf. I'd argue that's \n> a mistake for a function that is just returning a string in an \"out\" \n> parameter as I think it is more continent for the caller not to have to \n> initialize an strbuf just to retrieve the target of a symbolic ref. I \n> alse think that it is inconsistent with functions like \n> refs_resolve_refdup() that return a string.\n\nOk, I'll change this to use **char. On the other hand, if\nrefs_read_symbolic_ref is inconsistent would it make sense to change it to also\nuse a **char instead of strbuf? There's only four calls to it including the one\nI just added. Although I might rather do that _after_ this series is resolved :)\n\nThanks (to everyone) for your time!\n\nBest,\nBence\n\n\n-- \nbence.ferdinandy.com\n\n"},{"id":"505218","messageId":"Zw8IKyPkG0Hr6/5t@nand.local","threadId":"62096","inReplyTo":"20241014225431.1394565-1-bence@ferdinandy.com","subject":"Re: [PATCH v8 0/6] set-head/fetch remote/HEAD updates, small change from v7","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2024-10-16T00:26:19Z","receivedAt":"2024-10-16T00:26:27Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Tue, Oct 15, 2024 at 12:53:09AM +0200, Bence Ferdinandy wrote:\n> Bence Ferdinandy (6):\n>   refs: atomically record overwritten ref in update_symref\n>   remote set-head: add new variable for readability\n>   remote set-head: better output for --auto\n>   refs: add TRANSACTION_CREATE_EXISTS error\n>   refs: add create_only option to refs_update_symref\n>   fetch: set remote/HEAD if it does not exist\n\nI integrated this new round into my copy of 'seen' today and noticed\nsome test breakage in t5505 here:\n\n    https://github.com/ttaylorr/git/actions/runs/11356267070\n\nI'm going to temporarily eject this topic out of 'seen' until we can get\nan analysis of what's going on here.\n\nThanks,\nTaylor\n"},{"id":"505251","messageId":"D4X2ZZAT5LKS.1FZ375SIMBV2F@ferdinandy.com","threadId":"62096","inReplyTo":"Zw8IKyPkG0Hr6/5t@nand.local","subject":"Re: [PATCH v8 0/6] set-head/fetch remote/HEAD updates, small change from v7","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-16T08:18:44Z","receivedAt":"2024-10-16T08:19:20Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Wed Oct 16, 2024 at 02:26, Taylor Blau <me@ttaylorr.com> wrote:\n> On Tue, Oct 15, 2024 at 12:53:09AM +0200, Bence Ferdinandy wrote:\n>> Bence Ferdinandy (6):\n>>   refs: atomically record overwritten ref in update_symref\n>>   remote set-head: add new variable for readability\n>>   remote set-head: better output for --auto\n>>   refs: add TRANSACTION_CREATE_EXISTS error\n>>   refs: add create_only option to refs_update_symref\n>>   fetch: set remote/HEAD if it does not exist\n>\n> I integrated this new round into my copy of 'seen' today and noticed\n> some test breakage in t5505 here:\n>\n>     https://github.com/ttaylorr/git/actions/runs/11356267070\n>\n> I'm going to temporarily eject this topic out of 'seen' until we can get\n> an analysis of what's going on here.\n\nThanks for the heads up! I see that indeed the failing output is different than\nwhat I have locally (e.g. no \"apis/HEAD -> apis/main\" in any of the test files\nI have). On the other hand I can't reproduce it so I will need some help with\nthis I think.\n\nI tested with both a rebase on master and a rebase on your seen branch. I'm\nusing these to commands in t/ to run tests:\n\nprove --timer --jobs 15 ./t[0-9]*.sh\nGIT_TEST_DEFAULT_REF_FORMAT=reftable prove --timer --jobs 15 ./t[0-9]*.sh\n\nEverything passes. Should I be running this differently? I'm assuming the test\ndo not use any global or local git config I may have?\n\nI'm testing on Ubuntu 22.04 natively and in WSL, but the nature of the error\ndoesn't seem like it could or should be related to the OS.\n\nThanks,\nBence\n"},{"id":"505309","messageId":"ZxAqhqHwomsRq2Ck@nand.local","threadId":"62096","inReplyTo":"D4X2ZZAT5LKS.1FZ375SIMBV2F@ferdinandy.com","subject":"Re: [PATCH v8 0/6] set-head/fetch remote/HEAD updates, small change from v7","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2024-10-16T21:05:10Z","receivedAt":"2024-10-16T21:05:13Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Wed, Oct 16, 2024 at 10:18:44AM +0200, Bence Ferdinandy wrote:\n>\n> On Wed Oct 16, 2024 at 02:26, Taylor Blau <me@ttaylorr.com> wrote:\n> > On Tue, Oct 15, 2024 at 12:53:09AM +0200, Bence Ferdinandy wrote:\n> >> Bence Ferdinandy (6):\n> >>   refs: atomically record overwritten ref in update_symref\n> >>   remote set-head: add new variable for readability\n> >>   remote set-head: better output for --auto\n> >>   refs: add TRANSACTION_CREATE_EXISTS error\n> >>   refs: add create_only option to refs_update_symref\n> >>   fetch: set remote/HEAD if it does not exist\n> >\n> > I integrated this new round into my copy of 'seen' today and noticed\n> > some test breakage in t5505 here:\n> >\n> >     https://github.com/ttaylorr/git/actions/runs/11356267070\n> >\n> > I'm going to temporarily eject this topic out of 'seen' until we can get\n> > an analysis of what's going on here.\n>\n> Thanks for the heads up! I see that indeed the failing output is different than\n> what I have locally (e.g. no \"apis/HEAD -> apis/main\" in any of the test files\n> I have). On the other hand I can't reproduce it so I will need some help with\n> this I think.\n\nI similarly could not reproduce it (I ran 'make test' on this topic\nbefore integrating it into 'seen', and it passed, otherwise I wouldn't\nhave picked it up).\n\nI am not sure what the differences are. The 'ci' directory has some more\nbits on how the various suites are run, and the '.github/workflows'\ndirectory has some more bits still.\n\nThanks,\nTaylor\n"},{"id":"505374","messageId":"D4Y6NN4JPPLV.3JQ9OIPD3N6OZ@ferdinandy.com","threadId":"62096","inReplyTo":"ZxAqhqHwomsRq2Ck@nand.local","subject":"Re: [PATCH v8 0/6] set-head/fetch remote/HEAD updates, small change from v7","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-17T15:23:21Z","receivedAt":"2024-10-17T15:24:43Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Wed Oct 16, 2024 at 23:05, Taylor Blau <me@ttaylorr.com> wrote:\n> On Wed, Oct 16, 2024 at 10:18:44AM +0200, Bence Ferdinandy wrote:\n>> Thanks for the heads up! I see that indeed the failing output is different than\n>> what I have locally (e.g. no \"apis/HEAD -> apis/main\" in any of the test files\n>> I have). On the other hand I can't reproduce it so I will need some help with\n>> this I think.\n>\n> I similarly could not reproduce it (I ran 'make test' on this topic\n> before integrating it into 'seen', and it passed, otherwise I wouldn't\n> have picked it up).\n>\n> I am not sure what the differences are. The 'ci' directory has some more\n> bits on how the various suites are run, and the '.github/workflows'\n> directory has some more bits still.\n\nI managed to reproduce it in docker once, but I'm not sure how and I wasn't\nable to do it again ... On the other hand (using some printf) I managed to\nfigure out that `fetch --multiple --all` is running both in the CI and locally\nand the difference is actually what we see in the output (so no error per se).\nE.g. in t5505.55 update, if you run it locally refs/remotes/apis/HEAD does not\nexist, while in the CI it does.\n\nI'm not sure why this difference exists yet, so I'm hoping someone might have\nan idea. I'll keep poking around in the meanwhile.\n\nThanks,\nBence\n"},{"id":"505515","messageId":"20241019225330.4001477-2-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241019225330.4001477-1-bence@ferdinandy.com","subject":"[PATCH v9 1/7] t/t5505-remote: set default branch to main","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-19T22:53:08Z","receivedAt":"2024-10-19T22:54:14Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Consider the bare repository called \"mirror\" in the test.  Running `git\nremote add --mirror -f origin ../one` will not change HEAD, consequently\nif init.defaultBranch is not the same as what HEAD in the remote\n(\"one\"), HEAD in \"mirror\" will be pointing to a non-existent reference.\nHence if \"mirror\" is used as a remote by yet another repository,\nls-remote will not show HEAD. On the other hand, if init.defaultBranch\nhappens to match HEAD in \"one\", then ls-remote will show HEAD.\n\nSince the CI globally exports GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main,\nthere's a drift between how the test repositories are set up in the CI\nand during local testing. This issue does not manifest currently, as the\ntest does not do any remote HEAD manipulation where this would come up,\nbut should such things be added, a locally passing test would break the\nCI vice-versa.\n\nSet GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main in the test to be\nconsistent with the CI.\n---\n\nNotes:\n    v9: - new patch\n        - a bandaid for the CI issue noticed by Taylor (cf:\n          https://lore.kernel.org/git/Zw8IKyPkG0Hr6%2F5t@nand.local/), but\n          see\n          https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/ for the root cause in detail\n\n t/t5505-remote.sh | 3 +++\n 1 file changed, 3 insertions(+)\n\ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex 532035933f..9b50276646 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -2,6 +2,9 @@\n \n test_description='git remote porcelain-ish'\n \n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n-- \n2.47.0.92.g83fdbe24c3.dirty\n\n"},{"id":"505516","messageId":"20241019225330.4001477-1-bence@ferdinandy.com","threadId":"62096","inReplyTo":"Zw8IKyPkG0Hr6/5t@nand.local","subject":"[PATCH v9 0/7] set-head/fetch remote/HEAD updates","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-19T22:53:07Z","receivedAt":"2024-10-19T22:54:17Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Hi,\n\nthe two most notable changes are\n- the new patch 1/7 which address\n  https://lore.kernel.org/git/Zw8IKyPkG0Hr6%2F5t@nand.local/, but see\n  https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/\n- the refs_update_symref -> refs_update_symref_extended change in 2/7,\n  reflecting on Phillip's comments (see\n  https://lore.kernel.org/git/a7cb48e5-d8ba-44c1-9dbe-d1e8f8a63e3c@gmail.com/)\n\nHopefully with 1/7 the series is ready to move back to seen :)\n\nBest,\nBence\n\nBence Ferdinandy (7):\n  t/t5505-remote: set default branch to main\n  refs: atomically record overwritten ref in update_symref\n  remote set-head: refactor for readability\n  remote set-head: better output for --auto\n  refs: add TRANSACTION_CREATE_EXISTS error\n  refs: add create_only option to refs_update_symref_extended\n  fetch: set remote/HEAD if it does not exist\n\n builtin/fetch.c                  |  82 +++++++++++\n builtin/remote.c                 |  52 +++++--\n refs.c                           |  41 +++++-\n refs.h                           |   8 +-\n refs/files-backend.c             |  24 ++--\n refs/reftable-backend.c          |   6 +-\n t/t4207-log-decoration-colors.sh |   3 +-\n t/t5505-remote.sh                |  73 +++++++++-\n t/t5510-fetch.sh                 | 229 ++++++++++++++++---------------\n t/t5512-ls-remote.sh             |   2 +\n t/t5514-fetch-multiple.sh        |  17 ++-\n t/t5516-fetch-push.sh            |   3 +-\n t/t5527-fetch-odd-refs.sh        |   3 +-\n t/t7900-maintenance.sh           |   3 +-\n t/t9210-scalar.sh                |   5 +-\n t/t9211-scalar-clone.sh          |   6 +-\n t/t9902-completion.sh            |  65 +++++++++\n 17 files changed, 468 insertions(+), 154 deletions(-)\n\n-- \n2.47.0.92.g83fdbe24c3.dirty\n\n"},{"id":"505517","messageId":"20241019225330.4001477-3-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241019225330.4001477-1-bence@ferdinandy.com","subject":"[PATCH v9 2/7] refs: atomically record overwritten ref in update_symref","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-19T22:53:09Z","receivedAt":"2024-10-19T22:54:19Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"When updating a symref with update_symref it's currently not possible to\nknow for sure what was the previous value that was overwritten. Extend\nrefs_update_symref under a new function name, to record the value after\nthe ref has been locked if the caller of refs_update_symref_extended\nrequests it via a new variable in the function call. Keep the original\nrefs_update_symref function with the same signature, but now as\na wrapper around refs_update_symref_extended.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v4: new patch\n    \n    v5: - added before_target to reftables backend\n        - added an extra safety check for transaction's existence in refs.c\n    \n    v6: - no change\n    \n    v7: - remove the whole before_target concept from the backends and\n          handle checking it in refs.c instead (thanks Karthik)\n        - rename the before_target to referent which is how the same concept\n          is called in the backends\n        - change commit prefix to be more in line with project standards\n    \n    v8: no change\n    \n    v9: - instead of adding parameters to refs_update_symref, rename what\n          was in v8 as refs_update_symref_extended and make refs_update_symref\n          a wrapper for that. This significantly reduces the number of files\n          that need to be touched, and avoids adding a lot of dummy NULL-s\n          in unrelated places.\n\n refs.c | 19 ++++++++++++++++---\n refs.h |  4 ++++\n 2 files changed, 20 insertions(+), 3 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 5f729ed412..24a4172cd2 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2115,6 +2115,13 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct\n \n int refs_update_symref(struct ref_store *refs, const char *ref,\n \t\t       const char *target, const char *logmsg)\n+{\n+\treturn refs_update_symref_extended(refs, ref, target, logmsg, NULL);\n+}\n+\n+int refs_update_symref_extended(struct ref_store *refs, const char *ref,\n+\t\t       const char *target, const char *logmsg,\n+\t\t       struct strbuf *referent)\n {\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n@@ -2122,13 +2129,20 @@ int refs_update_symref(struct ref_store *refs, const char *ref,\n \n \ttransaction = ref_store_transaction_begin(refs, &err);\n \tif (!transaction ||\n-\t    ref_transaction_update(transaction, ref, NULL, NULL,\n+\t\tref_transaction_update(transaction, ref, NULL, NULL,\n \t\t\t\t   target, NULL, REF_NO_DEREF,\n \t\t\t\t   logmsg, &err) ||\n-\t    ref_transaction_commit(transaction, &err)) {\n+\t\tref_transaction_prepare(transaction, &err)) {\n \t\tret = error(\"%s\", err.buf);\n+\t\tgoto cleanup;\n \t}\n+\tif (referent)\n+\t\trefs_read_symbolic_ref(refs, ref, referent);\n \n+\tif (ref_transaction_commit(transaction, &err))\n+\t\tret = error(\"%s\", err.buf);\n+\n+cleanup:\n \tstrbuf_release(&err);\n \tif (transaction)\n \t\tref_transaction_free(transaction);\n@@ -2948,4 +2962,3 @@ int ref_update_expects_existing_old_ref(struct ref_update *update)\n \treturn (update->flags & REF_HAVE_OLD) &&\n \t\t(!is_null_oid(&update->old_oid) || update->old_target);\n }\n-\ndiff --git a/refs.h b/refs.h\nindex 108dfc93b3..259191a485 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -573,6 +573,10 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,\n int refs_update_symref(struct ref_store *refs, const char *refname,\n \t\t       const char *target, const char *logmsg);\n \n+int refs_update_symref_extended(struct ref_store *refs, const char *refname,\n+\t\t       const char *target, const char *logmsg,\n+\t\t       struct strbuf *referent);\n+\n enum action_on_err {\n \tUPDATE_REFS_MSG_ON_ERR,\n \tUPDATE_REFS_DIE_ON_ERR,\n-- \n2.47.0.92.g83fdbe24c3.dirty\n\n"},{"id":"505518","messageId":"20241019225330.4001477-4-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241019225330.4001477-1-bence@ferdinandy.com","subject":"[PATCH v9 3/7] remote set-head: refactor for readability","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-19T22:53:10Z","receivedAt":"2024-10-19T22:54:19Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Rename buf and buf2 to something more explanatory.\n\nInstead of calling get_main_ref_store(the_repository) multiple times,\ncall it once and store in a new refs variable. Although this change\nprobably offers some performance benefits, the main purpose is to\nshorten the line lengths of function calls using this variable.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v5: new patch (split from the next patch as a preparatory step)\n    \n    v6: no change\n    \n    v7: - change commit prefix to be more in line with project standards\n    \n    v8: no change\n    \n    v9: - further improve readability by renaming buf, and buf2 consistently\n          with how patch 6 was already done\n\n builtin/remote.c | 23 ++++++++++++-----------\n 1 file changed, 12 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 76670ddd8b..1d68c5b2ba 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1402,8 +1402,9 @@ static int show(int argc, const char **argv, const char *prefix)\n static int set_head(int argc, const char **argv, const char *prefix)\n {\n \tint i, opt_a = 0, opt_d = 0, result = 0;\n-\tstruct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;\n+\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;\n \tchar *head_name = NULL;\n+\tstruct ref_store *refs = get_main_ref_store(the_repository);\n \n \tstruct option options[] = {\n \t\tOPT_BOOL('a', \"auto\", &opt_a,\n@@ -1415,7 +1416,7 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \targc = parse_options(argc, argv, prefix, options,\n \t\t\t     builtin_remote_sethead_usage, 0);\n \tif (argc)\n-\t\tstrbuf_addf(&buf, \"refs/remotes/%s/HEAD\", argv[0]);\n+\t\tstrbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", argv[0]);\n \n \tif (!opt_a && !opt_d && argc == 2) {\n \t\thead_name = xstrdup(argv[1]);\n@@ -1434,25 +1435,25 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\t\thead_name = xstrdup(states.heads.items[0].string);\n \t\tfree_remote_ref_states(&states);\n \t} else if (opt_d && !opt_a && argc == 1) {\n-\t\tif (refs_delete_ref(get_main_ref_store(the_repository), NULL, buf.buf, NULL, REF_NO_DEREF))\n-\t\t\tresult |= error(_(\"Could not delete %s\"), buf.buf);\n+\t\tif (refs_delete_ref(refs, NULL, b_head.buf, NULL, REF_NO_DEREF))\n+\t\t\tresult |= error(_(\"Could not delete %s\"), b_head.buf);\n \t} else\n \t\tusage_with_options(builtin_remote_sethead_usage, options);\n \n \tif (head_name) {\n-\t\tstrbuf_addf(&buf2, \"refs/remotes/%s/%s\", argv[0], head_name);\n+\t\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", argv[0], head_name);\n \t\t/* make sure it's valid */\n-\t\tif (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))\n-\t\t\tresult |= error(_(\"Not a valid ref: %s\"), buf2.buf);\n-\t\telse if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote set-head\"))\n-\t\t\tresult |= error(_(\"Could not setup %s\"), buf.buf);\n+\t\tif (!refs_ref_exists(refs, b_remote_head.buf))\n+\t\t\tresult |= error(_(\"Not a valid ref: %s\"), b_remote_head.buf);\n+\t\telse if (refs_update_symref(refs, b_head.buf, b_remote_head.buf, \"remote set-head\"))\n+\t\t\tresult |= error(_(\"Could not setup %s\"), b_head.buf);\n \t\telse if (opt_a)\n \t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\n \t\tfree(head_name);\n \t}\n \n-\tstrbuf_release(&buf);\n-\tstrbuf_release(&buf2);\n+\tstrbuf_release(&b_head);\n+\tstrbuf_release(&b_remote_head);\n \treturn result;\n }\n \n-- \n2.47.0.92.g83fdbe24c3.dirty\n\n"},{"id":"505519","messageId":"20241019225330.4001477-6-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241019225330.4001477-1-bence@ferdinandy.com","subject":"[PATCH v9 5/7] refs: add TRANSACTION_CREATE_EXISTS error","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-19T22:53:12Z","receivedAt":"2024-10-19T22:54:23Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Currently there is only one special error for transaction, for when\nthere is a naming conflict, all other errors are dumped under a generic\nerror. Add a new special error case for when the caller requests the\nreference to be updated only when it does not yet exist and the\nreference actually does exist.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v4: new patch\n    v5: no change\n    v6: no change\n    \n    v7: - change commit prefix to be more in line with project standards\n        - changed error checking to Karthik's suggestion\n    \n    v8: no change\n    \n    v9: - no change\n\n refs.h                  |  4 +++-\n refs/files-backend.c    | 24 ++++++++++++++++--------\n refs/reftable-backend.c |  6 ++++--\n 3 files changed, 23 insertions(+), 11 deletions(-)\n\ndiff --git a/refs.h b/refs.h\nindex 259191a485..a5bc25442b 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -762,8 +762,10 @@ int ref_transaction_verify(struct ref_transaction *transaction,\n \n /* Naming conflict (for example, the ref names A and A/B conflict). */\n #define TRANSACTION_NAME_CONFLICT -1\n+/* When only creation was requested, but the ref already exists. */\n+#define TRANSACTION_CREATE_EXISTS -2\n /* All other errors. */\n-#define TRANSACTION_GENERIC_ERROR -2\n+#define TRANSACTION_GENERIC_ERROR -3\n \n /*\n  * Perform the preparatory stages of committing `transaction`. Acquire\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 0824c0b8a9..e743ec44b5 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2502,14 +2502,18 @@ static int split_symref_update(struct ref_update *update,\n static int check_old_oid(struct ref_update *update, struct object_id *oid,\n \t\t\t struct strbuf *err)\n {\n+\tint ret = TRANSACTION_GENERIC_ERROR;\n+\n \tif (!(update->flags & REF_HAVE_OLD) ||\n \t\t   oideq(oid, &update->old_oid))\n \t\treturn 0;\n \n-\tif (is_null_oid(&update->old_oid))\n+\tif (is_null_oid(&update->old_oid)) {\n \t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n \t\t\t    \"reference already exists\",\n \t\t\t    ref_update_original_update_refname(update));\n+\t\tret = TRANSACTION_CREATE_EXISTS;\n+\t}\n \telse if (is_null_oid(oid))\n \t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n \t\t\t    \"reference is missing but expected %s\",\n@@ -2522,7 +2526,7 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid,\n \t\t\t    oid_to_hex(oid),\n \t\t\t    oid_to_hex(&update->old_oid));\n \n-\treturn -1;\n+\treturn ret;\n }\n \n /*\n@@ -2602,9 +2606,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\t\tgoto out;\n \t\t\t\t}\n-\t\t\t} else if  (check_old_oid(update, &lock->old_oid, err)) {\n-\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n-\t\t\t\tgoto out;\n+\t\t\t} else {\n+\t\t\t\tret = check_old_oid(update, &lock->old_oid, err);\n+\t\t\t\tif  (ret) {\n+\t\t\t\t\tgoto out;\n+\t\t\t\t}\n \t\t\t}\n \t\t} else {\n \t\t\t/*\n@@ -2635,9 +2641,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\t\t    update->old_target);\n \t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\tgoto out;\n-\t\t} else if  (check_old_oid(update, &lock->old_oid, err)) {\n-\t\t\tret = TRANSACTION_GENERIC_ERROR;\n-\t\t\tgoto out;\n+\t\t} else {\n+\t\t\tret = check_old_oid(update, &lock->old_oid, err);\n+\t\t\tif  (ret) {\n+\t\t\t\tgoto out;\n+\t\t\t}\n \t\t}\n \n \t\t/*\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 3c6107c7ce..b3b5ce77dd 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1206,10 +1206,13 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\tgoto done;\n \t\t\t}\n \t\t} else if ((u->flags & REF_HAVE_OLD) && !oideq(&current_oid, &u->old_oid)) {\n-\t\t\tif (is_null_oid(&u->old_oid))\n+\t\t\tret = TRANSACTION_NAME_CONFLICT;\n+\t\t\tif (is_null_oid(&u->old_oid)) {\n \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n \t\t\t\t\t\t   \"reference already exists\"),\n \t\t\t\t\t    ref_update_original_update_refname(u));\n+\t\t\t\tret = TRANSACTION_CREATE_EXISTS;\n+\t\t\t}\n \t\t\telse if (is_null_oid(&current_oid))\n \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n \t\t\t\t\t\t   \"reference is missing but expected %s\"),\n@@ -1221,7 +1224,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\t\t    ref_update_original_update_refname(u),\n \t\t\t\t\t    oid_to_hex(&current_oid),\n \t\t\t\t\t    oid_to_hex(&u->old_oid));\n-\t\t\tret = -1;\n \t\t\tgoto done;\n \t\t}\n \n-- \n2.47.0.92.g83fdbe24c3.dirty\n\n"},{"id":"505520","messageId":"20241019225330.4001477-5-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241019225330.4001477-1-bence@ferdinandy.com","subject":"[PATCH v9 4/7] remote set-head: better output for --auto","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-19T22:53:11Z","receivedAt":"2024-10-19T22:54:26Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Currently, set-head --auto will print a message saying \"remote/HEAD set\nto branch\", which implies something was changed.\n\nChange the output of --auto, so the output actually reflects what was\ndone: a) set a previously unset HEAD, b) change HEAD because remote\nchanged or c) no updates. As a fourth output, if HEAD is changed from\na previous value that was not a remote branch, explicitly call attention\nto this fact.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v1-v2:\n    \n        was RFC in https://lore.kernel.org/git/20240910203835.2288291-1-bence@ferdinandy.com/\n    \n    v3:\n    \n        This patch was originally sent along when I thought set-head was\n        going to be invoked by fetch, but the discussion on the RFC\n        concluded that it should be not. This opened the possibility to make\n        it more explicit.\n    \n        Note: although I feel both things the patch does are really just\n        cosmetic, an argument could be made for breaking it into two, one\n        for the no-op part and one for the --auto print update.\n    \n        Was sent in:\n        https://lore.kernel.org/git/20240915221055.904107-1-bence@ferdinandy.com/\n    \n    v4:\n        - changes are now handled atomically via the ref update transaction\n        - outputs have changed along the lines of Junio's suggestion\n        - minor refactor to set_head for improved legibility\n    \n    v5: - the minor refactor has been split out into its own patch\n    \n    v6: - fixed uninitialized prev_head\n        - fixed case of unusual previous target\n        - fixed a test that would have been actually broken at this patch\n          (the output was only correct with the later update to fetch)\n        - added 4 tests for the 4 output cases\n    \n    v7: - change commit prefix to be more in line with project standards\n        - fixed tests to also work with the reftable backend\n        - renamed report function, fixed style issue with checking buf len\n        - fixed not releasing an strbuf\n    \n    v8: no change\n    \n    v9: - mark output strings in report_set_head_auto as translatable\n        - rename buf_prev to b_local_head for consistency\n        - use ${SQ} in tests instead of '\\''\n\n builtin/remote.c  | 33 +++++++++++++++++++++++++++---\n t/t5505-remote.sh | 51 ++++++++++++++++++++++++++++++++++++++++++++++-\n 2 files changed, 80 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 1d68c5b2ba..108f1271d3 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1399,10 +1399,35 @@ static int show(int argc, const char **argv, const char *prefix)\n \treturn result;\n }\n \n+static void report_set_head_auto(const char *remote, const char *head_name,\n+\t\t\tstruct strbuf *b_local_head) {\n+\tstruct strbuf buf_prefix = STRBUF_INIT;\n+\tconst char *prev_head = NULL;\n+\n+\tstrbuf_addf(&buf_prefix, \"refs/remotes/%s/\", remote);\n+\tskip_prefix(b_local_head->buf, buf_prefix.buf, &prev_head);\n+\n+\tif (prev_head && !strcmp(prev_head, head_name))\n+\t\tprintf(_(\"'%s/HEAD' is unchanged and points to '%s'\\n\"),\n+\t\t\tremote, head_name);\n+\telse if (prev_head)\n+\t\tprintf(_(\"'%s/HEAD' has changed from '%s' and now points to '%s'\\n\"),\n+\t\t\tremote, prev_head, head_name);\n+\telse if (!b_local_head->len)\n+\t\tprintf(_(\"'%s/HEAD' is now created and points to '%s'\\n\"),\n+\t\t\tremote, head_name);\n+\telse\n+\t\tprintf(_(\"'%s/HEAD' used to point to '%s' \"\n+\t\t\t\"(which is not a remote branch), but now points to '%s'\\n\"),\n+\t\t\tremote, b_local_head->buf, head_name);\n+\tstrbuf_release(&buf_prefix);\n+}\n+\n static int set_head(int argc, const char **argv, const char *prefix)\n {\n \tint i, opt_a = 0, opt_d = 0, result = 0;\n-\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;\n+\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,\n+\t\tb_local_head = STRBUF_INIT;\n \tchar *head_name = NULL;\n \tstruct ref_store *refs = get_main_ref_store(the_repository);\n \n@@ -1445,15 +1470,17 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\t/* make sure it's valid */\n \t\tif (!refs_ref_exists(refs, b_remote_head.buf))\n \t\t\tresult |= error(_(\"Not a valid ref: %s\"), b_remote_head.buf);\n-\t\telse if (refs_update_symref(refs, b_head.buf, b_remote_head.buf, \"remote set-head\"))\n+\t\telse if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n+\t\t\t\t\t\"remote set-head\", &b_local_head))\n \t\t\tresult |= error(_(\"Could not setup %s\"), b_head.buf);\n \t\telse if (opt_a)\n-\t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\n+\t\t\treport_set_head_auto(argv[0], head_name, &b_local_head);\n \t\tfree(head_name);\n \t}\n \n \tstrbuf_release(&b_head);\n \tstrbuf_release(&b_remote_head);\n+\tstrbuf_release(&b_local_head);\n \treturn result;\n }\n \ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex 9b50276646..0ea86d51a4 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -432,12 +432,51 @@ test_expect_success 'set-head --auto' '\n \t)\n '\n \n+test_expect_success 'set-head --auto detects creation' '\n+\t(\n+\t\tcd test &&\n+\t\tgit symbolic-ref -d refs/remotes/origin/HEAD &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"${SQ}origin/HEAD${SQ} is now created and points to ${SQ}main${SQ}\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n+test_expect_success 'set-head --auto detects no change' '\n+\t(\n+\t\tcd test &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"${SQ}origin/HEAD${SQ} is unchanged and points to ${SQ}main${SQ}\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n+test_expect_success 'set-head --auto detects change' '\n+\t(\n+\t\tcd test &&\n+\t\tgit symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/ahead &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"${SQ}origin/HEAD${SQ} has changed from ${SQ}ahead${SQ} and now points to ${SQ}main${SQ}\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n+test_expect_success 'set-head --auto detects strange ref' '\n+\t(\n+\t\tcd test &&\n+\t\tgit symbolic-ref refs/remotes/origin/HEAD refs/heads/main &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"${SQ}origin/HEAD${SQ} used to point to ${SQ}refs/heads/main${SQ} (which is not a remote branch), but now points to ${SQ}main${SQ}\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n test_expect_success 'set-head --auto has no problem w/multiple HEADs' '\n \t(\n \t\tcd test &&\n \t\tgit fetch two \"refs/heads/*:refs/remotes/two/*\" &&\n \t\tgit remote set-head --auto two >output 2>&1 &&\n-\t\techo \"two/HEAD set to main\" >expect &&\n+\t\techo \"${SQ}two/HEAD${SQ} is now created and points to ${SQ}main${SQ}\" >expect &&\n \t\ttest_cmp expect output\n \t)\n '\n@@ -456,6 +495,16 @@ test_expect_success 'set-head explicit' '\n \t)\n '\n \n+test_expect_success 'set-head --auto reports change' '\n+\t(\n+\t\tcd test &&\n+\t\tgit remote set-head origin side2 &&\n+\t\tgit remote set-head --auto origin >output 2>&1 &&\n+\t\techo \"${SQ}origin/HEAD${SQ} has changed from ${SQ}side2${SQ} and now points to ${SQ}main${SQ}\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n cat >test/expect <<EOF\n Pruning origin\n URL: $(pwd)/one\n-- \n2.47.0.92.g83fdbe24c3.dirty\n\n"},{"id":"505521","messageId":"20241019225330.4001477-7-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241019225330.4001477-1-bence@ferdinandy.com","subject":"[PATCH v9 6/7] refs: add create_only option to refs_update_symref_extended","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-19T22:53:13Z","receivedAt":"2024-10-19T22:54:30Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Allow the caller to specify that it only wants to update the symref if\nit does not already exist. Silently ignore the error from the\ntransaction API if the symref already exists.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v4: new patch\n    v5: no change\n    \n    v6: - switched from bool to int for create_only\n        - refactored logic in refs_update_symref with goto as layed out by\n          Junio\n    \n    v7: - change commit prefix to be more in line with project standards\n        - refactored code to accommodate changes in the first patch, but\n          otherwise no change\n    \n    v8: no change\n    \n    v9: - no change (except for following through the\n          refs_update_symref_extended refactoring)\n\n builtin/remote.c |  2 +-\n refs.c           | 32 +++++++++++++++++++++++---------\n refs.h           |  2 +-\n 3 files changed, 25 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 108f1271d3..b1eba75a2b 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1471,7 +1471,7 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\tif (!refs_ref_exists(refs, b_remote_head.buf))\n \t\t\tresult |= error(_(\"Not a valid ref: %s\"), b_remote_head.buf);\n \t\telse if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n-\t\t\t\t\t\"remote set-head\", &b_local_head))\n+\t\t\t\t\t\"remote set-head\", &b_local_head, 0))\n \t\t\tresult |= error(_(\"Could not setup %s\"), b_head.buf);\n \t\telse if (opt_a)\n \t\t\treport_set_head_auto(argv[0], head_name, &b_local_head);\ndiff --git a/refs.c b/refs.c\nindex 24a4172cd2..093ee11ab0 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2116,31 +2116,45 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct\n int refs_update_symref(struct ref_store *refs, const char *ref,\n \t\t       const char *target, const char *logmsg)\n {\n-\treturn refs_update_symref_extended(refs, ref, target, logmsg, NULL);\n+\treturn refs_update_symref_extended(refs, ref, target, logmsg, NULL, 0);\n }\n \n int refs_update_symref_extended(struct ref_store *refs, const char *ref,\n \t\t       const char *target, const char *logmsg,\n-\t\t       struct strbuf *referent)\n+\t\t       struct strbuf *referent, int create_only)\n {\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n-\tint ret = 0;\n+\tint ret = 0, prepret = 0;\n \n \ttransaction = ref_store_transaction_begin(refs, &err);\n-\tif (!transaction ||\n-\t\tref_transaction_update(transaction, ref, NULL, NULL,\n-\t\t\t\t   target, NULL, REF_NO_DEREF,\n-\t\t\t\t   logmsg, &err) ||\n-\t\tref_transaction_prepare(transaction, &err)) {\n+\tif (!transaction) {\n+\terror_return:\n \t\tret = error(\"%s\", err.buf);\n \t\tgoto cleanup;\n \t}\n+\tif (create_only) {\n+\t\tif (ref_transaction_create(transaction, ref, NULL, target,\n+\t\t\t\t\t   REF_NO_DEREF, logmsg, &err))\n+\t\t\tgoto error_return;\n+\t\tprepret = ref_transaction_prepare(transaction, &err);\n+\t\tif (prepret && prepret != TRANSACTION_CREATE_EXISTS)\n+\t\t\tgoto error_return;\n+\t} else {\n+\t\tif (ref_transaction_update(transaction, ref, NULL, NULL,\n+\t\t\t\t\t   target, NULL, REF_NO_DEREF,\n+\t\t\t\t\t   logmsg, &err) ||\n+\t\t\tref_transaction_prepare(transaction, &err))\n+\t\t\tgoto error_return;\n+\t}\n+\n \tif (referent)\n \t\trefs_read_symbolic_ref(refs, ref, referent);\n \n+\tif (prepret == TRANSACTION_CREATE_EXISTS)\n+\t\tgoto cleanup;\n \tif (ref_transaction_commit(transaction, &err))\n-\t\tret = error(\"%s\", err.buf);\n+\t\tgoto error_return;\n \n cleanup:\n \tstrbuf_release(&err);\ndiff --git a/refs.h b/refs.h\nindex a5bc25442b..458582ebcf 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -575,7 +575,7 @@ int refs_update_symref(struct ref_store *refs, const char *refname,\n \n int refs_update_symref_extended(struct ref_store *refs, const char *refname,\n \t\t       const char *target, const char *logmsg,\n-\t\t       struct strbuf *referent);\n+\t\t       struct strbuf *referent, int create_only);\n \n enum action_on_err {\n \tUPDATE_REFS_MSG_ON_ERR,\n-- \n2.47.0.92.g83fdbe24c3.dirty\n\n"},{"id":"505522","messageId":"D505HR2RH3C4.1VODDQ0I8IGMQ@ferdinandy.com","threadId":"62096","inReplyTo":"D4WJZVALZYZ6.1FR78333KZBAL@ferdinandy.com","subject":"Re: [PATCH v7 1/6] refs: atomically record overwritten ref in update_symref","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-19T22:53:58Z","receivedAt":"2024-10-19T22:54:36Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Tue Oct 15, 2024 at 19:25, Bence Ferdinandy <bence@ferdinandy.com> wrote:\n>\n> On Tue Oct 15, 2024 at 16:05, Phillip Wood <phillip.wood123@gmail.com> wrote:\n[snip]\n>>>>\n>>>> I'm also not sure about the proposed interface I would have thought it\n>>>> would be simpler to take a \"char**\" rather than an \"struct strbuf*\" if\n>>>> we do decide that it is useful for callers of refs_update_symref() to\n>>>> query the old value.\n>>> \n>>> refs_read_symbolic_ref requires a strbuf, so one would need to be created\n>>> anyway and I also sort of got the feeling that the project likes to handle refs\n>>> in strbufs (which may be wrong). Are there any downsides I'm not seeing?\n>>\n>> It's true that refs_read_symbolic_ref takes and strbuf. I'd argue that's \n>> a mistake for a function that is just returning a string in an \"out\" \n>> parameter as I think it is more continent for the caller not to have to \n>> initialize an strbuf just to retrieve the target of a symbolic ref. I \n>> alse think that it is inconsistent with functions like \n>> refs_resolve_refdup() that return a string.\n>\n> Ok, I'll change this to use **char. On the other hand, if\n> refs_read_symbolic_ref is inconsistent would it make sense to change it to also\n> use a **char instead of strbuf? There's only four calls to it including the one\n> I just added. Although I might rather do that _after_ this series is resolved :)\n\nI started doing this change, but ran into two things which I would yet again,\nbring up in defence of strbuf. So not only refs_read_symbolic_ref makes use of strbuf, but\n\na) I also use it in refs_update_symref[_extended] to check if the caller\nactually wants that referent or not (passing a NULL or a strbuf).\nb) the consumer, report_set_head_auto check for !buf->len\n\nBoth of these sound way more convenient with strbuf than with *char. Ofc I'm\nnot exactly a C guru so ... Anyhow, v9 does not have this change.\n\nBest,\nBence\n"},{"id":"505523","messageId":"20241019225330.4001477-8-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241019225330.4001477-1-bence@ferdinandy.com","subject":"[PATCH v9 7/7] fetch: set remote/HEAD if it does not exist","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-19T22:53:14Z","receivedAt":"2024-10-19T22:54:48Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"If the user has remote/HEAD set already and it looks like it has changed\non the server, then print a message, otherwise set it if we can.\nSilently pass if the user already has the same remote/HEAD set as\nreported by the server or if we encounter any errors along the way.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v3: - does not rely on remote set-head anymore so it only authenticates\n        once\n        - uses the new REF_CREATE_ONLY to atomically check if the ref exists\n          and only write it if it doesn't\n        - in all other cases the maximum it does is print a warning\n    \n    v4: - instead of the discarded REF_CREATE_ONLY, it uses the existing,\n          but updated transaction api to request a silent create only\n        - it now uses the atomic before_target to determine reporting\n        - refactored for legibility\n    \n    v5: - instead of printing a not too useful message, it now fails\n          silently, this in line with the objective to only set up\n          remote/HEAD automatically if the right thing is trivial, for\n          everything else there is remote set-head\n        - fixed all failing tests\n        - added two new tests, one for checking if remote/HEAD is set to the\n          correct one, and one to test that we do not override remote/HEAD\n          if it has changed on the server from what we have locally\n    \n    v6: - fixed style issues and unintended extra empty line\n        - updated function call with bool to int from previous patch's\n          change\n        - removed calls to error(...) inherited from builtin/remote.c so we\n          actually fail silently\n        - set the test for remote set-head --auto to the correct value here,\n          which was previously erronously set in the remote set-head patch\n    \n    v7: - no change\n    \n    v8: - changed logmsg in call to refs_update_symref from \"remote\n          set-head\" to \"fetch\"\n    \n    v9: - follow through with refs_update_symref_extended\n        - fix test errors uncovered by the new patch\n\n builtin/fetch.c                  |  82 +++++++++++\n t/t4207-log-decoration-colors.sh |   3 +-\n t/t5505-remote.sh                |  21 ++-\n t/t5510-fetch.sh                 | 229 ++++++++++++++++---------------\n t/t5512-ls-remote.sh             |   2 +\n t/t5514-fetch-multiple.sh        |  17 ++-\n t/t5516-fetch-push.sh            |   3 +-\n t/t5527-fetch-odd-refs.sh        |   3 +-\n t/t7900-maintenance.sh           |   3 +-\n t/t9210-scalar.sh                |   5 +-\n t/t9211-scalar-clone.sh          |   6 +-\n t/t9902-completion.sh            |  65 +++++++++\n 12 files changed, 315 insertions(+), 124 deletions(-)\n\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex d9027e4dc9..31edc67447 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1578,6 +1578,80 @@ static int backfill_tags(struct display_state *display_state,\n \treturn retcode;\n }\n \n+static void report_set_head(const char *remote, const char *head_name,\n+\t\t\tstruct strbuf *buf_prev) {\n+\tstruct strbuf buf_prefix = STRBUF_INIT;\n+\tconst char *prev_head = NULL;\n+\n+\tstrbuf_addf(&buf_prefix, \"refs/remotes/%s/\", remote);\n+\tskip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);\n+\n+\tif (prev_head && strcmp(prev_head, head_name)) {\n+\t\tprintf(\"'HEAD' at '%s' has changed from '%s' to '%s'\\n\",\n+\t\t\tremote, prev_head, head_name);\n+\t\tprintf(\"Run 'git remote set-head %s %s' to follow the change.\\n\",\n+\t\t\tremote, head_name);\n+\t}\n+}\n+\n+static const char *strip_refshead(const char *name){\n+\tskip_prefix(name, \"refs/heads/\", &name);\n+\treturn name;\n+}\n+\n+static int set_head(const struct ref *remote_refs)\n+{\n+\tint result = 0;\n+\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,\n+\t\tb_local_head = STRBUF_INIT;\n+\tconst char *remote = gtransport->remote->name;\n+\tchar *head_name = NULL;\n+\tstruct ref *ref, *matches;\n+\tstruct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;\n+\tstruct refspec_item refspec = {\n+\t\t.force = 0,\n+\t\t.pattern = 1,\n+\t\t.src = (char *) \"refs/heads/*\",\n+\t\t.dst = (char *) \"refs/heads/*\",\n+\t};\n+\tstruct string_list heads = STRING_LIST_INIT_DUP;\n+\tstruct ref_store *refs = get_main_ref_store(the_repository);\n+\n+\tget_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);\n+\tmatches = guess_remote_head(find_ref_by_name(remote_refs, \"HEAD\"),\n+\t\t\t\t    fetch_map, 1);\n+\tfor (ref = matches; ref; ref = ref->next) {\n+\t\tstring_list_append(&heads, strip_refshead(ref->name));\n+\t}\n+\n+\n+\tif (!heads.nr)\n+\t\tresult = 1;\n+\telse if (heads.nr > 1)\n+\t\tresult = 1;\n+\telse\n+\t\thead_name = xstrdup(heads.items[0].string);\n+\tif (head_name) {\n+\t\tstrbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", remote);\n+\t\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", remote, head_name);\n+\t\t/* make sure it's valid */\n+\t\tif (!refs_ref_exists(refs, b_remote_head.buf))\n+\t\t\tresult = 1;\n+\t\telse if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n+\t\t\t\t\t\"fetch\", &b_local_head, 1))\n+\t\t\tresult = 1;\n+\t\telse\n+\t\t\treport_set_head(remote, head_name, &b_local_head);\n+\n+\t\tfree(head_name);\n+\t}\n+\n+\tstrbuf_release(&b_head);\n+\tstrbuf_release(&b_local_head);\n+\tstrbuf_release(&b_remote_head);\n+\treturn result;\n+}\n+\n static int do_fetch(struct transport *transport,\n \t\t    struct refspec *rs,\n \t\t    const struct fetch_config *config)\n@@ -1647,6 +1721,8 @@ static int do_fetch(struct transport *transport,\n \t\t\t\t    \"refs/tags/\");\n \t}\n \n+\tstrvec_push(&transport_ls_refs_options.ref_prefixes, \"HEAD\");\n+\n \tif (must_list_refs) {\n \t\ttrace2_region_enter(\"fetch\", \"remote_refs\", the_repository);\n \t\tremote_refs = transport_get_remote_refs(transport,\n@@ -1791,6 +1867,12 @@ static int do_fetch(struct transport *transport,\n \t\t\t\t  \"you need to specify exactly one branch with the --set-upstream option\"));\n \t\t}\n \t}\n+\tif (set_head(remote_refs))\n+\t\t;\n+\t\t/*\n+\t\t * Way too many cases where this can go wrong\n+\t\t * so let's just fail silently for now.\n+\t\t */\n \n cleanup:\n \tif (retcode) {\ndiff --git a/t/t4207-log-decoration-colors.sh b/t/t4207-log-decoration-colors.sh\nindex 73ea9e5155..d55d22cb2f 100755\n--- a/t/t4207-log-decoration-colors.sh\n+++ b/t/t4207-log-decoration-colors.sh\n@@ -59,7 +59,8 @@ ${c_reset}${c_tag}tag: ${c_reset}${c_tag}v1.0${c_reset}${c_commit}, \\\n ${c_reset}${c_tag}tag: ${c_reset}${c_tag}B${c_reset}${c_commit})${c_reset} B\n ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\n ${c_tag}tag: ${c_reset}${c_tag}A1${c_reset}${c_commit}, \\\n-${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit})${c_reset} A1\n+${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit}, \\\n+${c_reset}${c_remoteBranch}other/HEAD${c_reset}${c_commit})${c_reset} A1\n \t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\n ${c_stash}refs/stash${c_reset}${c_commit})${c_reset} On main: Changes to A.t\n \t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex 0ea86d51a4..4990d00209 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -74,7 +74,7 @@ test_expect_success 'add another remote' '\n \t\tcd test &&\n \t\tgit remote add -f second ../two &&\n \t\ttokens_match \"origin second\" \"$(git remote)\" &&\n-\t\tcheck_tracking_branch second main side another &&\n+\t\tcheck_tracking_branch second main side another HEAD &&\n \t\tgit for-each-ref \"--format=%(refname)\" refs/remotes |\n \t\tsed -e \"/^refs\\/remotes\\/origin\\//d\" \\\n \t\t    -e \"/^refs\\/remotes\\/second\\//d\" >actual &&\n@@ -476,7 +476,7 @@ test_expect_success 'set-head --auto has no problem w/multiple HEADs' '\n \t\tcd test &&\n \t\tgit fetch two \"refs/heads/*:refs/remotes/two/*\" &&\n \t\tgit remote set-head --auto two >output 2>&1 &&\n-\t\techo \"${SQ}two/HEAD${SQ} is now created and points to ${SQ}main${SQ}\" >expect &&\n+\t\techo \"${SQ}two/HEAD${SQ} is unchanged and points to ${SQ}main${SQ}\" >expect &&\n \t\ttest_cmp expect output\n \t)\n '\n@@ -764,8 +764,10 @@ test_expect_success 'reject --no-no-tags' '\n '\n \n cat >one/expect <<\\EOF\n+  apis/HEAD -> apis/main\n   apis/main\n   apis/side\n+  drosophila/HEAD -> drosophila/main\n   drosophila/another\n   drosophila/main\n   drosophila/side\n@@ -783,11 +785,14 @@ test_expect_success 'update' '\n '\n \n cat >one/expect <<\\EOF\n+  drosophila/HEAD -> drosophila/main\n   drosophila/another\n   drosophila/main\n   drosophila/side\n+  manduca/HEAD -> manduca/main\n   manduca/main\n   manduca/side\n+  megaloprepus/HEAD -> megaloprepus/main\n   megaloprepus/main\n   megaloprepus/side\n EOF\n@@ -795,7 +800,7 @@ EOF\n test_expect_success 'update with arguments' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\n@@ -827,10 +832,13 @@ test_expect_success 'update --prune' '\n '\n \n cat >one/expect <<-\\EOF\n+  apis/HEAD -> apis/main\n   apis/main\n   apis/side\n+  manduca/HEAD -> manduca/main\n   manduca/main\n   manduca/side\n+  megaloprepus/HEAD -> megaloprepus/main\n   megaloprepus/main\n   megaloprepus/side\n EOF\n@@ -838,7 +846,7 @@ EOF\n test_expect_success 'update default' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\n@@ -850,6 +858,7 @@ test_expect_success 'update default' '\n '\n \n cat >one/expect <<\\EOF\n+  drosophila/HEAD -> drosophila/main\n   drosophila/another\n   drosophila/main\n   drosophila/side\n@@ -858,7 +867,7 @@ EOF\n test_expect_success 'update default (overridden, with funny whitespace)' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\n@@ -872,7 +881,7 @@ test_expect_success 'update default (overridden, with funny whitespace)' '\n test_expect_success 'update (with remotes.default defined)' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex 0890b9f61c..dfc8d748ba 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -75,6 +75,30 @@ test_expect_success \"fetch test for-merge\" '\n \tcut -f -2 .git/FETCH_HEAD >actual &&\n \ttest_cmp expected actual'\n \n+test_expect_success \"fetch test remote HEAD\" '\n+\tcd \"$D\" &&\n+\tcd two &&\n+\tgit fetch &&\n+\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\tgit rev-parse --verify refs/remotes/origin/main &&\n+\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\tbranch=$(git rev-parse refs/remotes/origin/main) &&\n+\ttest \"z$head\" = \"z$branch\"'\n+\n+test_expect_success \"fetch test remote HEAD change\" '\n+\tcd \"$D\" &&\n+\tcd two &&\n+\tgit switch -c other &&\n+\tgit push -u origin other &&\n+\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\tgit rev-parse --verify refs/remotes/origin/main &&\n+\tgit rev-parse --verify refs/remotes/origin/other &&\n+\tgit remote set-head origin other &&\n+\tgit fetch &&\n+\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\tbranch=$(git rev-parse refs/remotes/origin/other) &&\n+\ttest \"z$head\" = \"z$branch\"'\n+\n test_expect_success 'fetch --prune on its own works as expected' '\n \tcd \"$D\" &&\n \tgit clone . prune &&\n@@ -478,7 +502,6 @@ test_expect_success 'unbundle 1' '\n \ttest_must_fail git fetch \"$D/bundle1\" main:main\n '\n \n-\n test_expect_success 'bundle 1 has only 3 files ' '\n \tcd \"$D\" &&\n \ttest_bundle_object_count bundle1 3\n@@ -819,7 +842,7 @@ test_expect_success 'fetch from multiple configured URLs in single remote' '\n \n # configured prune tests\n \n-set_config_tristate () {\n+set_config_tristate() {\n \t# var=$1 val=$2\n \tcase \"$2\" in\n \tunset)\n@@ -833,12 +856,12 @@ set_config_tristate () {\n \tesac\n }\n \n-test_configured_prune () {\n+test_configured_prune() {\n \ttest_configured_prune_type \"$@\" \"name\"\n \ttest_configured_prune_type \"$@\" \"link\"\n }\n \n-test_configured_prune_type () {\n+test_configured_prune_type() {\n \tfetch_prune=$1\n \tremote_origin_prune=$2\n \tfetch_prune_tags=$3\n@@ -848,8 +871,7 @@ test_configured_prune_type () {\n \tcmdline=$7\n \tmode=$8\n \n-\tif test -z \"$cmdline_setup\"\n-\tthen\n+\tif test -z \"$cmdline_setup\"; then\n \t\ttest_expect_success 'setup cmdline_setup variable for subsequent test' '\n \t\t\tremote_url=\"file://$(git -C one config remote.origin.url)\" &&\n \t\t\tremote_fetch=\"$(git -C one config remote.origin.fetch)\" &&\n@@ -857,12 +879,10 @@ test_configured_prune_type () {\n \t\t'\n \tfi\n \n-\tif test \"$mode\" = 'link'\n-\tthen\n+\tif test \"$mode\" = 'link'; then\n \t\tnew_cmdline=\"\"\n \n-\t\tif test \"$cmdline\" = \"\"\n-\t\tthen\n+\t\tif test \"$cmdline\" = \"\"; then\n \t\t\tnew_cmdline=$cmdline_setup\n \t\telse\n \t\t\tnew_cmdline=$(perl -e '\n@@ -873,10 +893,8 @@ test_configured_prune_type () {\n \t\tfi\n \n \t\tif test \"$fetch_prune_tags\" = 'true' ||\n-\t\t   test \"$remote_origin_prune_tags\" = 'true'\n-\t\tthen\n-\t\t\tif ! printf '%s' \"$cmdline\\n\" | grep -q refs/remotes/origin/\n-\t\t\tthen\n+\t\t\ttest \"$remote_origin_prune_tags\" = 'true'; then\n+\t\t\tif ! printf '%s' \"$cmdline\\n\" | grep -q refs/remotes/origin/; then\n \t\t\t\tnew_cmdline=\"$new_cmdline refs/tags/*:refs/tags/*\"\n \t\t\tfi\n \t\tfi\n@@ -946,100 +964,100 @@ test_configured_prune_type () {\n # $7 git-fetch $cmdline:\n #\n #                     $1    $2    $3    $4    $5     $6     $7\n-test_configured_prune unset unset unset unset kept   kept   \"\"\n-test_configured_prune unset unset unset unset kept   kept   \"--no-prune\"\n-test_configured_prune unset unset unset unset pruned kept   \"--prune\"\n-test_configured_prune unset unset unset unset kept   pruned \\\n+test_configured_prune unset unset unset unset kept kept \"\"\n+test_configured_prune unset unset unset unset kept kept \"--no-prune\"\n+test_configured_prune unset unset unset unset pruned kept \"--prune\"\n+test_configured_prune unset unset unset unset kept pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/*\"\n test_configured_prune unset unset unset unset pruned pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*\"\n \n-test_configured_prune false unset unset unset kept   kept   \"\"\n-test_configured_prune false unset unset unset kept   kept   \"--no-prune\"\n-test_configured_prune false unset unset unset pruned kept   \"--prune\"\n+test_configured_prune false unset unset unset kept kept \"\"\n+test_configured_prune false unset unset unset kept kept \"--no-prune\"\n+test_configured_prune false unset unset unset pruned kept \"--prune\"\n \n-test_configured_prune true  unset unset unset pruned kept   \"\"\n-test_configured_prune true  unset unset unset pruned kept   \"--prune\"\n-test_configured_prune true  unset unset unset kept   kept   \"--no-prune\"\n+test_configured_prune true unset unset unset pruned kept \"\"\n+test_configured_prune true unset unset unset pruned kept \"--prune\"\n+test_configured_prune true unset unset unset kept kept \"--no-prune\"\n \n-test_configured_prune unset false unset unset kept   kept   \"\"\n-test_configured_prune unset false unset unset kept   kept   \"--no-prune\"\n-test_configured_prune unset false unset unset pruned kept   \"--prune\"\n+test_configured_prune unset false unset unset kept kept \"\"\n+test_configured_prune unset false unset unset kept kept \"--no-prune\"\n+test_configured_prune unset false unset unset pruned kept \"--prune\"\n \n-test_configured_prune false false unset unset kept   kept   \"\"\n-test_configured_prune false false unset unset kept   kept   \"--no-prune\"\n-test_configured_prune false false unset unset pruned kept   \"--prune\"\n-test_configured_prune false false unset unset kept   pruned \\\n+test_configured_prune false false unset unset kept kept \"\"\n+test_configured_prune false false unset unset kept kept \"--no-prune\"\n+test_configured_prune false false unset unset pruned kept \"--prune\"\n+test_configured_prune false false unset unset kept pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/*\"\n test_configured_prune false false unset unset pruned pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*\"\n \n-test_configured_prune true  false unset unset kept   kept   \"\"\n-test_configured_prune true  false unset unset pruned kept   \"--prune\"\n-test_configured_prune true  false unset unset kept   kept   \"--no-prune\"\n+test_configured_prune true false unset unset kept kept \"\"\n+test_configured_prune true false unset unset pruned kept \"--prune\"\n+test_configured_prune true false unset unset kept kept \"--no-prune\"\n \n-test_configured_prune unset true  unset unset pruned kept   \"\"\n-test_configured_prune unset true  unset unset kept   kept   \"--no-prune\"\n-test_configured_prune unset true  unset unset pruned kept   \"--prune\"\n+test_configured_prune unset true unset unset pruned kept \"\"\n+test_configured_prune unset true unset unset kept kept \"--no-prune\"\n+test_configured_prune unset true unset unset pruned kept \"--prune\"\n \n-test_configured_prune false true  unset unset pruned kept   \"\"\n-test_configured_prune false true  unset unset kept   kept   \"--no-prune\"\n-test_configured_prune false true  unset unset pruned kept   \"--prune\"\n+test_configured_prune false true unset unset pruned kept \"\"\n+test_configured_prune false true unset unset kept kept \"--no-prune\"\n+test_configured_prune false true unset unset pruned kept \"--prune\"\n \n-test_configured_prune true  true  unset unset pruned kept   \"\"\n-test_configured_prune true  true  unset unset pruned kept   \"--prune\"\n-test_configured_prune true  true  unset unset kept   kept   \"--no-prune\"\n-test_configured_prune true  true  unset unset kept   pruned \\\n+test_configured_prune true true unset unset pruned kept \"\"\n+test_configured_prune true true unset unset pruned kept \"--prune\"\n+test_configured_prune true true unset unset kept kept \"--no-prune\"\n+test_configured_prune true true unset unset kept pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/*\"\n-test_configured_prune true  true  unset unset pruned pruned \\\n+test_configured_prune true true unset unset pruned pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*\"\n \n # --prune-tags on its own does nothing, needs --prune as well, same\n # for fetch.pruneTags without fetch.prune\n-test_configured_prune unset unset unset unset kept kept     \"--prune-tags\"\n-test_configured_prune unset unset true unset  kept kept     \"\"\n-test_configured_prune unset unset unset true  kept kept     \"\"\n+test_configured_prune unset unset unset unset kept kept \"--prune-tags\"\n+test_configured_prune unset unset true unset kept kept \"\"\n+test_configured_prune unset unset unset true kept kept \"\"\n \n # These will prune the tags\n test_configured_prune unset unset unset unset pruned pruned \"--prune --prune-tags\"\n-test_configured_prune true  unset true  unset pruned pruned \"\"\n-test_configured_prune unset true  unset true  pruned pruned \"\"\n+test_configured_prune true unset true unset pruned pruned \"\"\n+test_configured_prune unset true unset true pruned pruned \"\"\n \n # remote.<name>.pruneTags overrides fetch.pruneTags, just like\n # remote.<name>.prune overrides fetch.prune if set.\n-test_configured_prune true  unset true unset pruned pruned  \"\"\n-test_configured_prune false true  false true  pruned pruned \"\"\n-test_configured_prune true  false true  false kept   kept   \"\"\n+test_configured_prune true unset true unset pruned pruned \"\"\n+test_configured_prune false true false true pruned pruned \"\"\n+test_configured_prune true false true false kept kept \"\"\n \n # When --prune-tags is supplied it's ignored if an explicit refspec is\n # given, same for the configuration options.\n test_configured_prune unset unset unset unset pruned kept \\\n \t\"--prune --prune-tags origin +refs/heads/*:refs/remotes/origin/*\"\n-test_configured_prune unset unset true  unset pruned kept \\\n+test_configured_prune unset unset true unset pruned kept \\\n \t\"--prune origin +refs/heads/*:refs/remotes/origin/*\"\n-test_configured_prune unset unset unset true pruned  kept \\\n+test_configured_prune unset unset unset true pruned kept \\\n \t\"--prune origin +refs/heads/*:refs/remotes/origin/*\"\n \n # Pruning that also takes place if a file:// url replaces a named\n # remote. However, because there's no implicit\n # +refs/heads/*:refs/remotes/origin/* refspec and supplying it on the\n # command-line negates --prune-tags, the branches will not be pruned.\n-test_configured_prune_type unset unset unset unset kept   kept   \"origin --prune-tags\" \"name\"\n-test_configured_prune_type unset unset unset unset kept   kept   \"origin --prune-tags\" \"link\"\n+test_configured_prune_type unset unset unset unset kept kept \"origin --prune-tags\" \"name\"\n+test_configured_prune_type unset unset unset unset kept kept \"origin --prune-tags\" \"link\"\n test_configured_prune_type unset unset unset unset pruned pruned \"origin --prune --prune-tags\" \"name\"\n-test_configured_prune_type unset unset unset unset kept   pruned \"origin --prune --prune-tags\" \"link\"\n+test_configured_prune_type unset unset unset unset kept pruned \"origin --prune --prune-tags\" \"link\"\n test_configured_prune_type unset unset unset unset pruned pruned \"--prune --prune-tags origin\" \"name\"\n-test_configured_prune_type unset unset unset unset kept   pruned \"--prune --prune-tags origin\" \"link\"\n-test_configured_prune_type unset unset true  unset pruned pruned \"--prune origin\" \"name\"\n-test_configured_prune_type unset unset true  unset kept   pruned \"--prune origin\" \"link\"\n-test_configured_prune_type unset unset unset true  pruned pruned \"--prune origin\" \"name\"\n-test_configured_prune_type unset unset unset true  kept   pruned \"--prune origin\" \"link\"\n-test_configured_prune_type true  unset true  unset pruned pruned \"origin\" \"name\"\n-test_configured_prune_type true  unset true  unset kept   pruned \"origin\" \"link\"\n-test_configured_prune_type unset  true true  unset pruned pruned \"origin\" \"name\"\n-test_configured_prune_type unset  true true  unset kept   pruned \"origin\" \"link\"\n-test_configured_prune_type unset  true unset true  pruned pruned \"origin\" \"name\"\n-test_configured_prune_type unset  true unset true  kept   pruned \"origin\" \"link\"\n+test_configured_prune_type unset unset unset unset kept pruned \"--prune --prune-tags origin\" \"link\"\n+test_configured_prune_type unset unset true unset pruned pruned \"--prune origin\" \"name\"\n+test_configured_prune_type unset unset true unset kept pruned \"--prune origin\" \"link\"\n+test_configured_prune_type unset unset unset true pruned pruned \"--prune origin\" \"name\"\n+test_configured_prune_type unset unset unset true kept pruned \"--prune origin\" \"link\"\n+test_configured_prune_type true unset true unset pruned pruned \"origin\" \"name\"\n+test_configured_prune_type true unset true unset kept pruned \"origin\" \"link\"\n+test_configured_prune_type unset true true unset pruned pruned \"origin\" \"name\"\n+test_configured_prune_type unset true true unset kept pruned \"origin\" \"link\"\n+test_configured_prune_type unset true unset true pruned pruned \"origin\" \"name\"\n+test_configured_prune_type unset true unset true kept pruned \"origin\" \"link\"\n \n # When all remote.origin.fetch settings are deleted a --prune\n # --prune-tags still implicitly supplies refs/tags/*:refs/tags/* so\n@@ -1137,8 +1155,7 @@ test_expect_success 'fetching with auto-gc does not lock up' '\n \t)\n '\n \n-for section in fetch transfer\n-do\n+for section in fetch transfer; do\n \ttest_expect_success \"$section.hideRefs affects connectivity check\" '\n \t\tGIT_TRACE=\"$PWD\"/trace git -c $section.hideRefs=refs -c \\\n \t\t\t$section.hideRefs=\"!refs/tags/\" fetch &&\n@@ -1157,21 +1174,23 @@ test_expect_success 'prepare source branch' '\n \ttest 3 -le $(wc -l <actual)\n '\n \n-validate_store_type () {\n+validate_store_type() {\n \tgit -C dest count-objects -v >actual &&\n-\tcase \"$store_type\" in\n-\tpacked)\n-\t\tgrep \"^count: 0$\" actual ;;\n-\tloose)\n-\t\tgrep \"^packs: 0$\" actual ;;\n-\tesac || {\n+\t\tcase \"$store_type\" in\n+\t\tpacked)\n+\t\t\tgrep \"^count: 0$\" actual\n+\t\t\t;;\n+\t\tloose)\n+\t\t\tgrep \"^packs: 0$\" actual\n+\t\t\t;;\n+\t\tesac || {\n \t\techo \"store_type is $store_type\"\n \t\tcat actual\n \t\tfalse\n \t}\n }\n \n-test_unpack_limit () {\n+test_unpack_limit() {\n \tstore_type=$1\n \n \tcase \"$store_type\" in\n@@ -1192,43 +1211,39 @@ test_unpack_limit () {\n test_unpack_limit packed\n test_unpack_limit loose\n \n-setup_negotiation_tip () {\n+setup_negotiation_tip() {\n \tSERVER=\"$1\"\n \tURL=\"$2\"\n \tUSE_PROTOCOL_V2=\"$3\"\n \n \trm -rf \"$SERVER\" client trace &&\n-\tgit init -b main \"$SERVER\" &&\n-\ttest_commit -C \"$SERVER\" alpha_1 &&\n-\ttest_commit -C \"$SERVER\" alpha_2 &&\n-\tgit -C \"$SERVER\" checkout --orphan beta &&\n-\ttest_commit -C \"$SERVER\" beta_1 &&\n-\ttest_commit -C \"$SERVER\" beta_2 &&\n-\n-\tgit clone \"$URL\" client &&\n-\n-\tif test \"$USE_PROTOCOL_V2\" -eq 1\n-\tthen\n-\t\tgit -C \"$SERVER\" config protocol.version 2 &&\n-\t\tgit -C client config protocol.version 2\n-\tfi &&\n-\n-\ttest_commit -C \"$SERVER\" beta_s &&\n-\tgit -C \"$SERVER\" checkout main &&\n-\ttest_commit -C \"$SERVER\" alpha_s &&\n-\tgit -C \"$SERVER\" tag -d alpha_1 alpha_2 beta_1 beta_2\n+\t\tgit init -b main \"$SERVER\" &&\n+\t\ttest_commit -C \"$SERVER\" alpha_1 &&\n+\t\ttest_commit -C \"$SERVER\" alpha_2 &&\n+\t\tgit -C \"$SERVER\" checkout --orphan beta &&\n+\t\ttest_commit -C \"$SERVER\" beta_1 &&\n+\t\ttest_commit -C \"$SERVER\" beta_2 &&\n+\t\tgit clone \"$URL\" client &&\n+\t\tif test \"$USE_PROTOCOL_V2\" -eq 1; then\n+\t\t\tgit -C \"$SERVER\" config protocol.version 2 &&\n+\t\t\t\tgit -C client config protocol.version 2\n+\t\tfi &&\n+\t\ttest_commit -C \"$SERVER\" beta_s &&\n+\t\tgit -C \"$SERVER\" checkout main &&\n+\t\ttest_commit -C \"$SERVER\" alpha_s &&\n+\t\tgit -C \"$SERVER\" tag -d alpha_1 alpha_2 beta_1 beta_2\n }\n \n-check_negotiation_tip () {\n+check_negotiation_tip() {\n \t# Ensure that {alpha,beta}_1 are sent as \"have\", but not {alpha_beta}_2\n \tALPHA_1=$(git -C client rev-parse alpha_1) &&\n-\tgrep \"fetch> have $ALPHA_1\" trace &&\n-\tBETA_1=$(git -C client rev-parse beta_1) &&\n-\tgrep \"fetch> have $BETA_1\" trace &&\n-\tALPHA_2=$(git -C client rev-parse alpha_2) &&\n-\t! grep \"fetch> have $ALPHA_2\" trace &&\n-\tBETA_2=$(git -C client rev-parse beta_2) &&\n-\t! grep \"fetch> have $BETA_2\" trace\n+\t\tgrep \"fetch> have $ALPHA_1\" trace &&\n+\t\tBETA_1=$(git -C client rev-parse beta_1) &&\n+\t\tgrep \"fetch> have $BETA_1\" trace &&\n+\t\tALPHA_2=$(git -C client rev-parse alpha_2) &&\n+\t\t! grep \"fetch> have $ALPHA_2\" trace &&\n+\t\tBETA_2=$(git -C client rev-parse beta_2) &&\n+\t\t! grep \"fetch> have $BETA_2\" trace\n }\n \n test_expect_success '--negotiation-tip limits \"have\" lines sent' '\ndiff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh\nindex 64b3491e4e..1b3865e154 100755\n--- a/t/t5512-ls-remote.sh\n+++ b/t/t5512-ls-remote.sh\n@@ -293,6 +293,8 @@ test_expect_success 'ls-remote with filtered symref (refname)' '\n \tcat >expect <<-EOF &&\n \tref: refs/heads/main\tHEAD\n \t$rev\tHEAD\n+\tref: refs/remotes/origin/main\trefs/remotes/origin/HEAD\n+\t$rev\trefs/remotes/origin/HEAD\n \tEOF\n \tgit ls-remote --symref . HEAD >actual &&\n \ttest_cmp expect actual\ndiff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh\nindex 579872c258..e3482b27b2 100755\n--- a/t/t5514-fetch-multiple.sh\n+++ b/t/t5514-fetch-multiple.sh\n@@ -45,14 +45,17 @@ test_expect_success setup '\n '\n \n cat > test/expect << EOF\n+  one/HEAD -> one/main\n   one/main\n   one/side\n   origin/HEAD -> origin/main\n   origin/main\n   origin/side\n+  three/HEAD -> three/main\n   three/another\n   three/main\n   three/side\n+  two/HEAD -> two/main\n   two/another\n   two/main\n   two/side\n@@ -97,6 +100,7 @@ cat > expect << EOF\n   origin/HEAD -> origin/main\n   origin/main\n   origin/side\n+  three/HEAD -> three/main\n   three/another\n   three/main\n   three/side\n@@ -112,8 +116,10 @@ test_expect_success 'git fetch --multiple (but only one remote)' '\n '\n \n cat > expect << EOF\n+  one/HEAD -> one/main\n   one/main\n   one/side\n+  two/HEAD -> two/main\n   two/another\n   two/main\n   two/side\n@@ -141,7 +147,7 @@ test_expect_success 'git fetch --multiple (bad remote names)' '\n \n test_expect_success 'git fetch --all (skipFetchAll)' '\n \t(cd test4 &&\n-\t for b in $(git branch -r)\n+\t for b in $(git branch -r | grep -v HEAD)\n \t do\n \t\tgit branch -r -d $b || exit 1\n \t done &&\n@@ -153,11 +159,14 @@ test_expect_success 'git fetch --all (skipFetchAll)' '\n '\n \n cat > expect << EOF\n+  one/HEAD -> one/main\n   one/main\n   one/side\n+  three/HEAD -> three/main\n   three/another\n   three/main\n   three/side\n+  two/HEAD -> two/main\n   two/another\n   two/main\n   two/side\n@@ -165,7 +174,7 @@ EOF\n \n test_expect_success 'git fetch --multiple (ignoring skipFetchAll)' '\n \t(cd test4 &&\n-\t for b in $(git branch -r)\n+\t for b in $(git branch -r | grep -v HEAD)\n \t do\n \t\tgit branch -r -d $b || exit 1\n \t done &&\n@@ -221,14 +230,17 @@ test_expect_success 'git fetch --multiple --jobs=0 picks a default' '\n \n create_fetch_all_expect () {\n \tcat >expect <<-\\EOF\n+\t  one/HEAD -> one/main\n \t  one/main\n \t  one/side\n \t  origin/HEAD -> origin/main\n \t  origin/main\n \t  origin/side\n+\t  three/HEAD -> three/main\n \t  three/another\n \t  three/main\n \t  three/side\n+\t  two/HEAD -> two/main\n \t  two/another\n \t  two/main\n \t  two/side\n@@ -265,6 +277,7 @@ test_expect_success 'git fetch (fetch all remotes with fetch.all = true)' '\n \n create_fetch_one_expect () {\n \tcat >expect <<-\\EOF\n+\t  one/HEAD -> one/main\n \t  one/main\n \t  one/side\n \t  origin/HEAD -> origin/main\ndiff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\nindex 331778bd42..5a051aa0c7 100755\n--- a/t/t5516-fetch-push.sh\n+++ b/t/t5516-fetch-push.sh\n@@ -1395,7 +1395,8 @@ test_expect_success 'fetch follows tags by default' '\n \t\tgit tag -m \"annotated\" tag &&\n \t\tgit for-each-ref >tmp1 &&\n \t\tsed -n \"p; s|refs/heads/main$|refs/remotes/origin/main|p\" tmp1 |\n-\t\tsort -k 3 >../expect\n+\t\tsed -n \"p; s|refs/heads/main$|refs/remotes/origin/HEAD|p\"  |\n+\t\tsort -k 4 >../expect\n \t) &&\n \ttest_when_finished \"rm -rf dst\" &&\n \tgit init dst &&\ndiff --git a/t/t5527-fetch-odd-refs.sh b/t/t5527-fetch-odd-refs.sh\nindex 98ece27c6a..d3996af6ee 100755\n--- a/t/t5527-fetch-odd-refs.sh\n+++ b/t/t5527-fetch-odd-refs.sh\n@@ -52,7 +52,8 @@ test_expect_success LONG_REF 'fetch handles extremely long refname' '\n \tlong\n \tmain\n \tEOF\n-\tgit for-each-ref --format=\"%(subject)\" refs/remotes/long >actual &&\n+\tgit for-each-ref --format=\"%(subject)\" refs/remotes/long \\\n+\t\t--exclude=refs/remotes/long/HEAD >actual &&\n \ttest_cmp expect actual\n '\n \ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex a66d0e089d..88331d1ceb 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -329,7 +329,8 @@ test_expect_success 'incremental-repack task' '\n \n \t# Delete refs that have not been repacked in these packs.\n \tgit for-each-ref --format=\"delete %(refname)\" \\\n-\t\trefs/prefetch refs/tags refs/remotes >refs &&\n+\t\trefs/prefetch refs/tags refs/remotes \\\n+\t\t--exclude=refs/remotes/*/HEAD >refs &&\n \tgit update-ref --stdin <refs &&\n \n \t# Replace the object directory with this pack layout.\ndiff --git a/t/t9210-scalar.sh b/t/t9210-scalar.sh\nindex a30b2c9f70..2237844550 100755\n--- a/t/t9210-scalar.sh\n+++ b/t/t9210-scalar.sh\n@@ -151,7 +151,8 @@ test_expect_success 'scalar clone' '\n \t\t\t\"$(pwd)\" &&\n \n \t\tgit for-each-ref --format=\"%(refname)\" refs/remotes/origin/ >actual &&\n-\t\techo \"refs/remotes/origin/parallel\" >expect &&\n+\t\techo \"refs/remotes/origin/HEAD\" >>expect &&\n+\t\techo \"refs/remotes/origin/parallel\" >>expect &&\n \t\ttest_cmp expect actual &&\n \n \t\ttest_path_is_missing 1/2 &&\n@@ -220,7 +221,7 @@ test_expect_success 'scalar reconfigure --all with includeIf.onbranch' '\n \tdone\n '\n \n- test_expect_success 'scalar reconfigure --all with detached HEADs' '\n+test_expect_success 'scalar reconfigure --all with detached HEADs' '\n \trepos=\"two three four\" &&\n \tfor num in $repos\n \tdo\ndiff --git a/t/t9211-scalar-clone.sh b/t/t9211-scalar-clone.sh\nindex c16ea67c1d..d9cb6b9a3e 100755\n--- a/t/t9211-scalar-clone.sh\n+++ b/t/t9211-scalar-clone.sh\n@@ -32,7 +32,7 @@ test_expect_success 'set up repository to clone' '\n \t)\n '\n \n-cleanup_clone () {\n+cleanup_clone() {\n \trm -rf \"$1\"\n }\n \n@@ -128,7 +128,7 @@ test_expect_success '--single-branch clones HEAD only' '\n \t(\n \t\tcd $enlistment/src &&\n \t\tgit for-each-ref refs/remotes/origin >out &&\n-\t\ttest_line_count = 1 out &&\n+\t\ttest_line_count = 2 out &&\n \t\tgrep \"refs/remotes/origin/base\" out\n \t) &&\n \n@@ -142,7 +142,7 @@ test_expect_success '--no-single-branch clones all branches' '\n \t(\n \t\tcd $enlistment/src &&\n \t\tgit for-each-ref refs/remotes/origin >out &&\n-\t\ttest_line_count = 2 out &&\n+\t\ttest_line_count = 3 out &&\n \t\tgrep \"refs/remotes/origin/base\" out &&\n \t\tgrep \"refs/remotes/origin/parallel\" out\n \t) &&\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex cc6aa9f0cd..b663c4609e 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -658,6 +658,7 @@ test_expect_success '__git_refs - simple' '\n \tHEAD\n \tmain\n \tmatching-branch\n+\tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n \tmatching-tag\n@@ -673,6 +674,7 @@ test_expect_success '__git_refs - full refs' '\n \tcat >expected <<-EOF &&\n \trefs/heads/main\n \trefs/heads/matching-branch\n+\trefs/remotes/other/HEAD\n \trefs/remotes/other/branch-in-other\n \trefs/remotes/other/main-in-other\n \trefs/tags/matching-tag\n@@ -729,6 +731,7 @@ test_expect_success '__git_refs - remote on local file system - full refs' '\n test_expect_success '__git_refs - configured remote' '\n \tcat >expected <<-EOF &&\n \tHEAD\n+\tHEAD\n \tbranch-in-other\n \tmain-in-other\n \tEOF\n@@ -756,6 +759,7 @@ test_expect_success '__git_refs - configured remote - full refs' '\n test_expect_success '__git_refs - configured remote - repo given on the command line' '\n \tcat >expected <<-EOF &&\n \tHEAD\n+\tHEAD\n \tbranch-in-other\n \tmain-in-other\n \tEOF\n@@ -787,6 +791,7 @@ test_expect_success '__git_refs - configured remote - full refs - repo given on\n test_expect_success '__git_refs - configured remote - remote name matches a directory' '\n \tcat >expected <<-EOF &&\n \tHEAD\n+\tHEAD\n \tbranch-in-other\n \tmain-in-other\n \tEOF\n@@ -875,12 +880,14 @@ test_expect_success '__git_refs - unique remote branches for git checkout DWIMer\n \tHEAD\n \tmain\n \tmatching-branch\n+\tother/HEAD\n \tother/ambiguous\n \tother/branch-in-other\n \tother/main-in-other\n \tremote/ambiguous\n \tremote/branch-in-remote\n \tmatching-tag\n+\tHEAD\n \tbranch-in-other\n \tbranch-in-remote\n \tmain-in-other\n@@ -904,6 +911,7 @@ test_expect_success '__git_refs - after --opt=' '\n \tHEAD\n \tmain\n \tmatching-branch\n+\tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n \tmatching-tag\n@@ -919,6 +927,7 @@ test_expect_success '__git_refs - after --opt= - full refs' '\n \tcat >expected <<-EOF &&\n \trefs/heads/main\n \trefs/heads/matching-branch\n+\trefs/remotes/other/HEAD\n \trefs/remotes/other/branch-in-other\n \trefs/remotes/other/main-in-other\n \trefs/tags/matching-tag\n@@ -935,6 +944,7 @@ test_expect_success '__git refs - excluding refs' '\n \t^HEAD\n \t^main\n \t^matching-branch\n+\t^other/HEAD\n \t^other/branch-in-other\n \t^other/main-in-other\n \t^matching-tag\n@@ -950,6 +960,7 @@ test_expect_success '__git refs - excluding full refs' '\n \tcat >expected <<-EOF &&\n \t^refs/heads/main\n \t^refs/heads/matching-branch\n+\t^refs/remotes/other/HEAD\n \t^refs/remotes/other/branch-in-other\n \t^refs/remotes/other/main-in-other\n \t^refs/tags/matching-tag\n@@ -975,6 +986,7 @@ test_expect_success '__git_refs - do not filter refs unless told so' '\n \tmain\n \tmatching-branch\n \tmatching/branch\n+\tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n \tother/matching/branch-in-other\n@@ -1095,6 +1107,7 @@ test_expect_success '__git_complete_refs - simple' '\n \tHEAD Z\n \tmain Z\n \tmatching-branch Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tmatching-tag Z\n@@ -1123,6 +1136,7 @@ test_expect_success '__git_complete_refs - matching' '\n test_expect_success '__git_complete_refs - remote' '\n \tsed -e \"s/Z$//\" >expected <<-EOF &&\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain-in-other Z\n \tEOF\n@@ -1139,9 +1153,11 @@ test_expect_success '__git_complete_refs - track' '\n \tHEAD Z\n \tmain Z\n \tmatching-branch Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tmatching-tag Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain-in-other Z\n \tEOF\n@@ -1184,6 +1200,7 @@ test_expect_success '__git_complete_refs - suffix' '\n \tHEAD.\n \tmain.\n \tmatching-branch.\n+\tother/HEAD.\n \tother/branch-in-other.\n \tother/main-in-other.\n \tmatching-tag.\n@@ -1199,6 +1216,7 @@ test_expect_success '__git_complete_refs - suffix' '\n test_expect_success '__git_complete_fetch_refspecs - simple' '\n \tsed -e \"s/Z$//\" >expected <<-EOF &&\n \tHEAD:HEAD Z\n+\tHEAD:HEAD Z\n \tbranch-in-other:branch-in-other Z\n \tmain-in-other:main-in-other Z\n \tEOF\n@@ -1225,6 +1243,7 @@ test_expect_success '__git_complete_fetch_refspecs - matching' '\n test_expect_success '__git_complete_fetch_refspecs - prefix' '\n \tsed -e \"s/Z$//\" >expected <<-EOF &&\n \t+HEAD:HEAD Z\n+\t+HEAD:HEAD Z\n \t+branch-in-other:branch-in-other Z\n \t+main-in-other:main-in-other Z\n \tEOF\n@@ -1289,6 +1308,7 @@ test_expect_success '__git_complete_worktree_paths with -C' '\n \n test_expect_success 'git switch - with no options, complete local branches and unique remote branch names for DWIM logic' '\n \ttest_completion \"git switch \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1435,11 +1455,13 @@ test_expect_success 'git-bisect - existing view subcommand is recognized and ena\n test_expect_success 'git checkout - completes refs and unique remote branches for DWIM' '\n \ttest_completion \"git checkout \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1461,6 +1483,7 @@ test_expect_success 'git switch - with GIT_COMPLETION_CHECKOUT_NO_GUESS=1, compl\n \n test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete local branches and unique remote names for DWIM logic' '\n \tGIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git switch --guess \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1470,6 +1493,7 @@ test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_G\n \n test_expect_success 'git switch - a later --guess overrides previous --no-guess, complete local and remote unique branches for DWIM' '\n \ttest_completion \"git switch --no-guess --guess \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1490,6 +1514,7 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1498,11 +1523,13 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete\n test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1, complete refs and unique remote branches for DWIM' '\n \tGIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git checkout --guess \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1514,6 +1541,7 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1522,11 +1550,13 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '\n test_expect_success 'git checkout - a later --guess overrides previous --no-guess, complete refs and unique remote branches for DWIM' '\n \ttest_completion \"git checkout --no-guess --guess \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1538,6 +1568,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous --gues\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1550,6 +1581,7 @@ test_expect_success 'git checkout - with checkout.guess = false, only completes\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1559,11 +1591,13 @@ test_expect_success 'git checkout - with checkout.guess = true, completes refs a\n \ttest_config checkout.guess true &&\n \ttest_completion \"git checkout \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1573,11 +1607,13 @@ test_expect_success 'git checkout - a later --guess overrides previous checkout.\n \ttest_config checkout.guess false &&\n \ttest_completion \"git checkout --guess \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1590,6 +1626,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous checko\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1601,6 +1638,7 @@ test_expect_success 'git switch - with --detach, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1612,6 +1650,7 @@ test_expect_success 'git checkout - with --detach, complete only references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1783,6 +1822,7 @@ test_expect_success 'git switch - with -d, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1794,6 +1834,7 @@ test_expect_success 'git checkout - with -d, complete only references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1801,10 +1842,12 @@ test_expect_success 'git checkout - with -d, complete only references' '\n \n test_expect_success 'git switch - with --track, complete only remote branches' '\n \ttest_completion \"git switch --track \" <<-\\EOF &&\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n \ttest_completion \"git switch -t \" <<-\\EOF\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1812,10 +1855,12 @@ test_expect_success 'git switch - with --track, complete only remote branches' '\n \n test_expect_success 'git checkout - with --track, complete only remote branches' '\n \ttest_completion \"git checkout --track \" <<-\\EOF &&\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n \ttest_completion \"git checkout -t \" <<-\\EOF\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1834,6 +1879,7 @@ test_expect_success 'git checkout - with --no-track, complete only local referen\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1845,6 +1891,7 @@ test_expect_success 'git switch - with -c, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1856,6 +1903,7 @@ test_expect_success 'git switch - with -C, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1867,6 +1915,7 @@ test_expect_success 'git switch - with -c and --track, complete all references'\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1878,6 +1927,7 @@ test_expect_success 'git switch - with -C and --track, complete all references'\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1889,6 +1939,7 @@ test_expect_success 'git switch - with -c and --no-track, complete all reference\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1900,6 +1951,7 @@ test_expect_success 'git switch - with -C and --no-track, complete all reference\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1911,6 +1963,7 @@ test_expect_success 'git checkout - with -b, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1922,6 +1975,7 @@ test_expect_success 'git checkout - with -B, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1933,6 +1987,7 @@ test_expect_success 'git checkout - with -b and --track, complete all references\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1944,6 +1999,7 @@ test_expect_success 'git checkout - with -B and --track, complete all references\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1955,6 +2011,7 @@ test_expect_success 'git checkout - with -b and --no-track, complete all referen\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1966,6 +2023,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1973,6 +2031,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen\n \n test_expect_success 'git switch - for -c, complete local branches and unique remote branches' '\n \ttest_completion \"git switch -c \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1982,6 +2041,7 @@ test_expect_success 'git switch - for -c, complete local branches and unique rem\n \n test_expect_success 'git switch - for -C, complete local branches and unique remote branches' '\n \ttest_completion \"git switch -C \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2019,6 +2079,7 @@ test_expect_success 'git switch - for -C with --no-track, complete local branche\n \n test_expect_success 'git checkout - for -b, complete local branches and unique remote branches' '\n \ttest_completion \"git checkout -b \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2028,6 +2089,7 @@ test_expect_success 'git checkout - for -b, complete local branches and unique r\n \n test_expect_success 'git checkout - for -B, complete local branches and unique remote branches' '\n \ttest_completion \"git checkout -B \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2065,6 +2127,7 @@ test_expect_success 'git checkout - for -B with --no-track, complete local branc\n \n test_expect_success 'git switch - with --orphan completes local branch names and unique remote branch names' '\n \ttest_completion \"git switch --orphan \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2080,6 +2143,7 @@ test_expect_success 'git switch - --orphan with branch already provided complete\n \n test_expect_success 'git checkout - with --orphan completes local branch names and unique remote branch names' '\n \ttest_completion \"git checkout --orphan \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2093,6 +2157,7 @@ test_expect_success 'git checkout - --orphan with branch already provided comple\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n-- \n2.47.0.92.g83fdbe24c3.dirty\n\n\n"},{"id":"505769","messageId":"43d44c82-b93d-4ac9-a5ac-ae5f70982cfd@ferdinandy.com","threadId":"62096","inReplyTo":"20241019225330.4001477-1-bence@ferdinandy.com","subject":"Re: [PATCH v9 0/7] set-head/fetch remote/HEAD updates","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-21T21:13:19Z","receivedAt":"2024-10-21T21:13:53Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Hi,\n\njust a heads-up that due to me not being careful, v10 is at a new thread:\n \nhttps://lore.kernel.org/git/4836c7c9-58d7-4e2a-ab78-be4e7296dc1f@ferdinandy.com/T/, \nwith a wrong noreply address in Cc.\n\nSorry for the noise.\n\nBest,\nBence\n"},{"id":"505854","messageId":"20241022194710.3743691-2-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241022194710.3743691-1-bence@ferdinandy.com","subject":"[PATCH v11 1/8] t/t5505-remote: set default branch to main","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-22T19:45:38Z","receivedAt":"2024-10-22T19:48:35Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Consider the bare repository called \"mirror\" in the test.  Running `git\nremote add --mirror -f origin ../one` will not change HEAD, consequently\nif init.defaultBranch is not the same as what HEAD in the remote\n(\"one\"), HEAD in \"mirror\" will be pointing to a non-existent reference.\nHence if \"mirror\" is used as a remote by yet another repository,\nls-remote will not show HEAD. On the other hand, if init.defaultBranch\nhappens to match HEAD in \"one\", then ls-remote will show HEAD.\n\nSince the CI globally exports GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main,\nthere's a drift between how the test repositories are set up in the CI\nand during local testing. This issue does not manifest currently, as the\ntest does not do any remote HEAD manipulation where this would come up,\nbut should such things be added, a locally passing test would break the\nCI vice-versa.\n\nSet GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main in the test to be\nconsistent with the CI.\n---\n\nNotes:\n    v9: - new patch\n        - a bandaid for the CI issue noticed by Taylor (cf:\n          https://lore.kernel.org/git/Zw8IKyPkG0Hr6%2F5t@nand.local/), but\n          see\n          https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/ for the root cause in detail\n    \n    v10: no change\n    \n    v11: no change\n\n t/t5505-remote.sh | 3 +++\n 1 file changed, 3 insertions(+)\n\ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex 532035933f..9b50276646 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -2,6 +2,9 @@\n \n test_description='git remote porcelain-ish'\n \n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n-- \n2.47.0.94.gb64850d498\n\n"},{"id":"505855","messageId":"20241022194710.3743691-3-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241022194710.3743691-1-bence@ferdinandy.com","subject":"[PATCH v11 2/8] refs: atomically record overwritten ref in update_symref","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-22T19:45:39Z","receivedAt":"2024-10-22T19:48:42Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"When updating a symref with update_symref it's currently not possible to\nknow for sure what was the previous value that was overwritten. Extend\nrefs_update_symref under a new function name, to record the value after\nthe ref has been locked if the caller of refs_update_symref_extended\nrequests it via a new variable in the function call. Keep the original\nrefs_update_symref function with the same signature, but now as\na wrapper around refs_update_symref_extended.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v4: new patch\n    \n    v5: - added before_target to reftables backend\n        - added an extra safety check for transaction's existence in refs.c\n    \n    v6: - no change\n    \n    v7: - remove the whole before_target concept from the backends and\n          handle checking it in refs.c instead (thanks Karthik)\n        - rename the before_target to referent which is how the same concept\n          is called in the backends\n        - change commit prefix to be more in line with project standards\n    \n    v8: no change\n    \n    v9: - instead of adding parameters to refs_update_symref, rename what\n          was in v8 as refs_update_symref_extended and make refs_update_symref\n          a wrapper for that. This significantly reduces the number of files\n          that need to be touched, and avoids adding a lot of dummy NULL-s\n          in unrelated places.\n    \n    v10: no change\n    \n    v11: no change\n\n refs.c | 19 ++++++++++++++++---\n refs.h |  4 ++++\n 2 files changed, 20 insertions(+), 3 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 5f729ed412..24a4172cd2 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2115,6 +2115,13 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct\n \n int refs_update_symref(struct ref_store *refs, const char *ref,\n \t\t       const char *target, const char *logmsg)\n+{\n+\treturn refs_update_symref_extended(refs, ref, target, logmsg, NULL);\n+}\n+\n+int refs_update_symref_extended(struct ref_store *refs, const char *ref,\n+\t\t       const char *target, const char *logmsg,\n+\t\t       struct strbuf *referent)\n {\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n@@ -2122,13 +2129,20 @@ int refs_update_symref(struct ref_store *refs, const char *ref,\n \n \ttransaction = ref_store_transaction_begin(refs, &err);\n \tif (!transaction ||\n-\t    ref_transaction_update(transaction, ref, NULL, NULL,\n+\t\tref_transaction_update(transaction, ref, NULL, NULL,\n \t\t\t\t   target, NULL, REF_NO_DEREF,\n \t\t\t\t   logmsg, &err) ||\n-\t    ref_transaction_commit(transaction, &err)) {\n+\t\tref_transaction_prepare(transaction, &err)) {\n \t\tret = error(\"%s\", err.buf);\n+\t\tgoto cleanup;\n \t}\n+\tif (referent)\n+\t\trefs_read_symbolic_ref(refs, ref, referent);\n \n+\tif (ref_transaction_commit(transaction, &err))\n+\t\tret = error(\"%s\", err.buf);\n+\n+cleanup:\n \tstrbuf_release(&err);\n \tif (transaction)\n \t\tref_transaction_free(transaction);\n@@ -2948,4 +2962,3 @@ int ref_update_expects_existing_old_ref(struct ref_update *update)\n \treturn (update->flags & REF_HAVE_OLD) &&\n \t\t(!is_null_oid(&update->old_oid) || update->old_target);\n }\n-\ndiff --git a/refs.h b/refs.h\nindex 108dfc93b3..259191a485 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -573,6 +573,10 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,\n int refs_update_symref(struct ref_store *refs, const char *refname,\n \t\t       const char *target, const char *logmsg);\n \n+int refs_update_symref_extended(struct ref_store *refs, const char *refname,\n+\t\t       const char *target, const char *logmsg,\n+\t\t       struct strbuf *referent);\n+\n enum action_on_err {\n \tUPDATE_REFS_MSG_ON_ERR,\n \tUPDATE_REFS_DIE_ON_ERR,\n-- \n2.47.0.94.gb64850d498\n\n"},{"id":"505856","messageId":"20241022194710.3743691-1-bence@ferdinandy.com","threadId":"62096","inReplyTo":"43d44c82-b93d-4ac9-a5ac-ae5f70982cfd@ferdinandy.com","subject":"[PATCH v11 0/8] set-head/fetch remote/HEAD updates","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-22T19:45:37Z","receivedAt":"2024-10-22T19:48:42Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"v11: fixed leaks found by running tests after `make SANITIZE=leak`\n    green pipeline:\n    https://github.com/ferdinandyb/git/actions/runs/11466803523\n\nv10: added a new patch (8/8) on top of v9 to handle HEAD in mirrored bare\nrepositories, rest of the patches are unchanged\n\nv9:\nthe two most notable changes are\n- the new patch 1/7 which address\n  https://lore.kernel.org/git/Zw8IKyPkG0Hr6%2F5t@nand.local/, but see\n  https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/\n- the refs_update_symref -> refs_update_symref_extended change in 2/7,\n  reflecting on Phillip's comments (see\n  https://lore.kernel.org/git/a7cb48e5-d8ba-44c1-9dbe-d1e8f8a63e3c@gmail.com/)\n\nBence Ferdinandy (8):\n  t/t5505-remote: set default branch to main\n  refs: atomically record overwritten ref in update_symref\n  remote set-head: refactor for readability\n  remote set-head: better output for --auto\n  refs: add TRANSACTION_CREATE_EXISTS error\n  refs: add create_only option to refs_update_symref_extended\n  fetch: set remote/HEAD if it does not exist\n  fetch set_head: handle mirrored bare repositories\n\n builtin/fetch.c                  |  94 +++++++++++++\n builtin/remote.c                 |  52 +++++--\n refs.c                           |  41 +++++-\n refs.h                           |   8 +-\n refs/files-backend.c             |  24 ++--\n refs/reftable-backend.c          |   6 +-\n t/t4207-log-decoration-colors.sh |   3 +-\n t/t5505-remote.sh                |  83 ++++++++++-\n t/t5510-fetch.sh                 | 229 ++++++++++++++++---------------\n t/t5512-ls-remote.sh             |   2 +\n t/t5514-fetch-multiple.sh        |  17 ++-\n t/t5516-fetch-push.sh            |   3 +-\n t/t5527-fetch-odd-refs.sh        |   3 +-\n t/t7900-maintenance.sh           |   3 +-\n t/t9210-scalar.sh                |   5 +-\n t/t9211-scalar-clone.sh          |   6 +-\n t/t9902-completion.sh            |  65 +++++++++\n 17 files changed, 490 insertions(+), 154 deletions(-)\n\n-- \n2.47.0.94.gb64850d498\n\n"},{"id":"505857","messageId":"20241022194710.3743691-5-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241022194710.3743691-1-bence@ferdinandy.com","subject":"[PATCH v11 4/8] remote set-head: better output for --auto","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-22T19:45:41Z","receivedAt":"2024-10-22T19:48:56Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Currently, set-head --auto will print a message saying \"remote/HEAD set\nto branch\", which implies something was changed.\n\nChange the output of --auto, so the output actually reflects what was\ndone: a) set a previously unset HEAD, b) change HEAD because remote\nchanged or c) no updates. As a fourth output, if HEAD is changed from\na previous value that was not a remote branch, explicitly call attention\nto this fact.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v1-v2:\n    \n        was RFC in https://lore.kernel.org/git/20240910203835.2288291-1-bence@ferdinandy.com/\n    \n    v3:\n    \n        This patch was originally sent along when I thought set-head was\n        going to be invoked by fetch, but the discussion on the RFC\n        concluded that it should be not. This opened the possibility to make\n        it more explicit.\n    \n        Note: although I feel both things the patch does are really just\n        cosmetic, an argument could be made for breaking it into two, one\n        for the no-op part and one for the --auto print update.\n    \n        Was sent in:\n        https://lore.kernel.org/git/20240915221055.904107-1-bence@ferdinandy.com/\n    \n    v4:\n        - changes are now handled atomically via the ref update transaction\n        - outputs have changed along the lines of Junio's suggestion\n        - minor refactor to set_head for improved legibility\n    \n    v5: - the minor refactor has been split out into its own patch\n    \n    v6: - fixed uninitialized prev_head\n        - fixed case of unusual previous target\n        - fixed a test that would have been actually broken at this patch\n          (the output was only correct with the later update to fetch)\n        - added 4 tests for the 4 output cases\n    \n    v7: - change commit prefix to be more in line with project standards\n        - fixed tests to also work with the reftable backend\n        - renamed report function, fixed style issue with checking buf len\n        - fixed not releasing an strbuf\n    \n    v8: no change\n    \n    v9: - mark output strings in report_set_head_auto as translatable\n        - rename buf_prev to b_local_head for consistency\n        - use ${SQ} in tests instead of '\\''\n    \n    v10: no change\n    \n    v11: no change\n\n builtin/remote.c  | 33 +++++++++++++++++++++++++++---\n t/t5505-remote.sh | 51 ++++++++++++++++++++++++++++++++++++++++++++++-\n 2 files changed, 80 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 1d68c5b2ba..108f1271d3 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1399,10 +1399,35 @@ static int show(int argc, const char **argv, const char *prefix)\n \treturn result;\n }\n \n+static void report_set_head_auto(const char *remote, const char *head_name,\n+\t\t\tstruct strbuf *b_local_head) {\n+\tstruct strbuf buf_prefix = STRBUF_INIT;\n+\tconst char *prev_head = NULL;\n+\n+\tstrbuf_addf(&buf_prefix, \"refs/remotes/%s/\", remote);\n+\tskip_prefix(b_local_head->buf, buf_prefix.buf, &prev_head);\n+\n+\tif (prev_head && !strcmp(prev_head, head_name))\n+\t\tprintf(_(\"'%s/HEAD' is unchanged and points to '%s'\\n\"),\n+\t\t\tremote, head_name);\n+\telse if (prev_head)\n+\t\tprintf(_(\"'%s/HEAD' has changed from '%s' and now points to '%s'\\n\"),\n+\t\t\tremote, prev_head, head_name);\n+\telse if (!b_local_head->len)\n+\t\tprintf(_(\"'%s/HEAD' is now created and points to '%s'\\n\"),\n+\t\t\tremote, head_name);\n+\telse\n+\t\tprintf(_(\"'%s/HEAD' used to point to '%s' \"\n+\t\t\t\"(which is not a remote branch), but now points to '%s'\\n\"),\n+\t\t\tremote, b_local_head->buf, head_name);\n+\tstrbuf_release(&buf_prefix);\n+}\n+\n static int set_head(int argc, const char **argv, const char *prefix)\n {\n \tint i, opt_a = 0, opt_d = 0, result = 0;\n-\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;\n+\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,\n+\t\tb_local_head = STRBUF_INIT;\n \tchar *head_name = NULL;\n \tstruct ref_store *refs = get_main_ref_store(the_repository);\n \n@@ -1445,15 +1470,17 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\t/* make sure it's valid */\n \t\tif (!refs_ref_exists(refs, b_remote_head.buf))\n \t\t\tresult |= error(_(\"Not a valid ref: %s\"), b_remote_head.buf);\n-\t\telse if (refs_update_symref(refs, b_head.buf, b_remote_head.buf, \"remote set-head\"))\n+\t\telse if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n+\t\t\t\t\t\"remote set-head\", &b_local_head))\n \t\t\tresult |= error(_(\"Could not setup %s\"), b_head.buf);\n \t\telse if (opt_a)\n-\t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\n+\t\t\treport_set_head_auto(argv[0], head_name, &b_local_head);\n \t\tfree(head_name);\n \t}\n \n \tstrbuf_release(&b_head);\n \tstrbuf_release(&b_remote_head);\n+\tstrbuf_release(&b_local_head);\n \treturn result;\n }\n \ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex 9b50276646..0ea86d51a4 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -432,12 +432,51 @@ test_expect_success 'set-head --auto' '\n \t)\n '\n \n+test_expect_success 'set-head --auto detects creation' '\n+\t(\n+\t\tcd test &&\n+\t\tgit symbolic-ref -d refs/remotes/origin/HEAD &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"${SQ}origin/HEAD${SQ} is now created and points to ${SQ}main${SQ}\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n+test_expect_success 'set-head --auto detects no change' '\n+\t(\n+\t\tcd test &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"${SQ}origin/HEAD${SQ} is unchanged and points to ${SQ}main${SQ}\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n+test_expect_success 'set-head --auto detects change' '\n+\t(\n+\t\tcd test &&\n+\t\tgit symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/ahead &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"${SQ}origin/HEAD${SQ} has changed from ${SQ}ahead${SQ} and now points to ${SQ}main${SQ}\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n+test_expect_success 'set-head --auto detects strange ref' '\n+\t(\n+\t\tcd test &&\n+\t\tgit symbolic-ref refs/remotes/origin/HEAD refs/heads/main &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"${SQ}origin/HEAD${SQ} used to point to ${SQ}refs/heads/main${SQ} (which is not a remote branch), but now points to ${SQ}main${SQ}\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n test_expect_success 'set-head --auto has no problem w/multiple HEADs' '\n \t(\n \t\tcd test &&\n \t\tgit fetch two \"refs/heads/*:refs/remotes/two/*\" &&\n \t\tgit remote set-head --auto two >output 2>&1 &&\n-\t\techo \"two/HEAD set to main\" >expect &&\n+\t\techo \"${SQ}two/HEAD${SQ} is now created and points to ${SQ}main${SQ}\" >expect &&\n \t\ttest_cmp expect output\n \t)\n '\n@@ -456,6 +495,16 @@ test_expect_success 'set-head explicit' '\n \t)\n '\n \n+test_expect_success 'set-head --auto reports change' '\n+\t(\n+\t\tcd test &&\n+\t\tgit remote set-head origin side2 &&\n+\t\tgit remote set-head --auto origin >output 2>&1 &&\n+\t\techo \"${SQ}origin/HEAD${SQ} has changed from ${SQ}side2${SQ} and now points to ${SQ}main${SQ}\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n cat >test/expect <<EOF\n Pruning origin\n URL: $(pwd)/one\n-- \n2.47.0.94.gb64850d498\n\n"},{"id":"505858","messageId":"20241022194710.3743691-6-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241022194710.3743691-1-bence@ferdinandy.com","subject":"[PATCH v11 5/8] refs: add TRANSACTION_CREATE_EXISTS error","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-22T19:45:42Z","receivedAt":"2024-10-22T19:48:58Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Currently there is only one special error for transaction, for when\nthere is a naming conflict, all other errors are dumped under a generic\nerror. Add a new special error case for when the caller requests the\nreference to be updated only when it does not yet exist and the\nreference actually does exist.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v4: new patch\n    v5: no change\n    v6: no change\n    \n    v7: - change commit prefix to be more in line with project standards\n        - changed error checking to Karthik's suggestion\n    \n    v8: no change\n    \n    v9: - no change\n    \n    v10: no change\n    \n    v11: no change\n\n refs.h                  |  4 +++-\n refs/files-backend.c    | 24 ++++++++++++++++--------\n refs/reftable-backend.c |  6 ++++--\n 3 files changed, 23 insertions(+), 11 deletions(-)\n\ndiff --git a/refs.h b/refs.h\nindex 259191a485..a5bc25442b 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -762,8 +762,10 @@ int ref_transaction_verify(struct ref_transaction *transaction,\n \n /* Naming conflict (for example, the ref names A and A/B conflict). */\n #define TRANSACTION_NAME_CONFLICT -1\n+/* When only creation was requested, but the ref already exists. */\n+#define TRANSACTION_CREATE_EXISTS -2\n /* All other errors. */\n-#define TRANSACTION_GENERIC_ERROR -2\n+#define TRANSACTION_GENERIC_ERROR -3\n \n /*\n  * Perform the preparatory stages of committing `transaction`. Acquire\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 0824c0b8a9..e743ec44b5 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2502,14 +2502,18 @@ static int split_symref_update(struct ref_update *update,\n static int check_old_oid(struct ref_update *update, struct object_id *oid,\n \t\t\t struct strbuf *err)\n {\n+\tint ret = TRANSACTION_GENERIC_ERROR;\n+\n \tif (!(update->flags & REF_HAVE_OLD) ||\n \t\t   oideq(oid, &update->old_oid))\n \t\treturn 0;\n \n-\tif (is_null_oid(&update->old_oid))\n+\tif (is_null_oid(&update->old_oid)) {\n \t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n \t\t\t    \"reference already exists\",\n \t\t\t    ref_update_original_update_refname(update));\n+\t\tret = TRANSACTION_CREATE_EXISTS;\n+\t}\n \telse if (is_null_oid(oid))\n \t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n \t\t\t    \"reference is missing but expected %s\",\n@@ -2522,7 +2526,7 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid,\n \t\t\t    oid_to_hex(oid),\n \t\t\t    oid_to_hex(&update->old_oid));\n \n-\treturn -1;\n+\treturn ret;\n }\n \n /*\n@@ -2602,9 +2606,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\t\tgoto out;\n \t\t\t\t}\n-\t\t\t} else if  (check_old_oid(update, &lock->old_oid, err)) {\n-\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n-\t\t\t\tgoto out;\n+\t\t\t} else {\n+\t\t\t\tret = check_old_oid(update, &lock->old_oid, err);\n+\t\t\t\tif  (ret) {\n+\t\t\t\t\tgoto out;\n+\t\t\t\t}\n \t\t\t}\n \t\t} else {\n \t\t\t/*\n@@ -2635,9 +2641,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\t\t    update->old_target);\n \t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\tgoto out;\n-\t\t} else if  (check_old_oid(update, &lock->old_oid, err)) {\n-\t\t\tret = TRANSACTION_GENERIC_ERROR;\n-\t\t\tgoto out;\n+\t\t} else {\n+\t\t\tret = check_old_oid(update, &lock->old_oid, err);\n+\t\t\tif  (ret) {\n+\t\t\t\tgoto out;\n+\t\t\t}\n \t\t}\n \n \t\t/*\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 3c6107c7ce..b3b5ce77dd 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1206,10 +1206,13 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\tgoto done;\n \t\t\t}\n \t\t} else if ((u->flags & REF_HAVE_OLD) && !oideq(&current_oid, &u->old_oid)) {\n-\t\t\tif (is_null_oid(&u->old_oid))\n+\t\t\tret = TRANSACTION_NAME_CONFLICT;\n+\t\t\tif (is_null_oid(&u->old_oid)) {\n \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n \t\t\t\t\t\t   \"reference already exists\"),\n \t\t\t\t\t    ref_update_original_update_refname(u));\n+\t\t\t\tret = TRANSACTION_CREATE_EXISTS;\n+\t\t\t}\n \t\t\telse if (is_null_oid(&current_oid))\n \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n \t\t\t\t\t\t   \"reference is missing but expected %s\"),\n@@ -1221,7 +1224,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\t\t    ref_update_original_update_refname(u),\n \t\t\t\t\t    oid_to_hex(&current_oid),\n \t\t\t\t\t    oid_to_hex(&u->old_oid));\n-\t\t\tret = -1;\n \t\t\tgoto done;\n \t\t}\n \n-- \n2.47.0.94.gb64850d498\n\n"},{"id":"505859","messageId":"20241022194710.3743691-7-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241022194710.3743691-1-bence@ferdinandy.com","subject":"[PATCH v11 6/8] refs: add create_only option to refs_update_symref_extended","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-22T19:45:43Z","receivedAt":"2024-10-22T19:49:03Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Allow the caller to specify that it only wants to update the symref if\nit does not already exist. Silently ignore the error from the\ntransaction API if the symref already exists.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v4: new patch\n    v5: no change\n    \n    v6: - switched from bool to int for create_only\n        - refactored logic in refs_update_symref with goto as layed out by\n          Junio\n    \n    v7: - change commit prefix to be more in line with project standards\n        - refactored code to accommodate changes in the first patch, but\n          otherwise no change\n    \n    v8: no change\n    \n    v9: - no change (except for following through the\n          refs_update_symref_extended refactoring)\n    \n    v10: no change\n    \n    v11: no change\n\n builtin/remote.c |  2 +-\n refs.c           | 32 +++++++++++++++++++++++---------\n refs.h           |  2 +-\n 3 files changed, 25 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 108f1271d3..b1eba75a2b 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1471,7 +1471,7 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\tif (!refs_ref_exists(refs, b_remote_head.buf))\n \t\t\tresult |= error(_(\"Not a valid ref: %s\"), b_remote_head.buf);\n \t\telse if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n-\t\t\t\t\t\"remote set-head\", &b_local_head))\n+\t\t\t\t\t\"remote set-head\", &b_local_head, 0))\n \t\t\tresult |= error(_(\"Could not setup %s\"), b_head.buf);\n \t\telse if (opt_a)\n \t\t\treport_set_head_auto(argv[0], head_name, &b_local_head);\ndiff --git a/refs.c b/refs.c\nindex 24a4172cd2..093ee11ab0 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2116,31 +2116,45 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct\n int refs_update_symref(struct ref_store *refs, const char *ref,\n \t\t       const char *target, const char *logmsg)\n {\n-\treturn refs_update_symref_extended(refs, ref, target, logmsg, NULL);\n+\treturn refs_update_symref_extended(refs, ref, target, logmsg, NULL, 0);\n }\n \n int refs_update_symref_extended(struct ref_store *refs, const char *ref,\n \t\t       const char *target, const char *logmsg,\n-\t\t       struct strbuf *referent)\n+\t\t       struct strbuf *referent, int create_only)\n {\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n-\tint ret = 0;\n+\tint ret = 0, prepret = 0;\n \n \ttransaction = ref_store_transaction_begin(refs, &err);\n-\tif (!transaction ||\n-\t\tref_transaction_update(transaction, ref, NULL, NULL,\n-\t\t\t\t   target, NULL, REF_NO_DEREF,\n-\t\t\t\t   logmsg, &err) ||\n-\t\tref_transaction_prepare(transaction, &err)) {\n+\tif (!transaction) {\n+\terror_return:\n \t\tret = error(\"%s\", err.buf);\n \t\tgoto cleanup;\n \t}\n+\tif (create_only) {\n+\t\tif (ref_transaction_create(transaction, ref, NULL, target,\n+\t\t\t\t\t   REF_NO_DEREF, logmsg, &err))\n+\t\t\tgoto error_return;\n+\t\tprepret = ref_transaction_prepare(transaction, &err);\n+\t\tif (prepret && prepret != TRANSACTION_CREATE_EXISTS)\n+\t\t\tgoto error_return;\n+\t} else {\n+\t\tif (ref_transaction_update(transaction, ref, NULL, NULL,\n+\t\t\t\t\t   target, NULL, REF_NO_DEREF,\n+\t\t\t\t\t   logmsg, &err) ||\n+\t\t\tref_transaction_prepare(transaction, &err))\n+\t\t\tgoto error_return;\n+\t}\n+\n \tif (referent)\n \t\trefs_read_symbolic_ref(refs, ref, referent);\n \n+\tif (prepret == TRANSACTION_CREATE_EXISTS)\n+\t\tgoto cleanup;\n \tif (ref_transaction_commit(transaction, &err))\n-\t\tret = error(\"%s\", err.buf);\n+\t\tgoto error_return;\n \n cleanup:\n \tstrbuf_release(&err);\ndiff --git a/refs.h b/refs.h\nindex a5bc25442b..458582ebcf 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -575,7 +575,7 @@ int refs_update_symref(struct ref_store *refs, const char *refname,\n \n int refs_update_symref_extended(struct ref_store *refs, const char *refname,\n \t\t       const char *target, const char *logmsg,\n-\t\t       struct strbuf *referent);\n+\t\t       struct strbuf *referent, int create_only);\n \n enum action_on_err {\n \tUPDATE_REFS_MSG_ON_ERR,\n-- \n2.47.0.94.gb64850d498\n\n"},{"id":"505860","messageId":"20241022194710.3743691-8-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241022194710.3743691-1-bence@ferdinandy.com","subject":"[PATCH v11 7/8] fetch: set remote/HEAD if it does not exist","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-22T19:45:44Z","receivedAt":"2024-10-22T19:49:35Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"If the user has remote/HEAD set already and it looks like it has changed\non the server, then print a message, otherwise set it if we can.\nSilently pass if the user already has the same remote/HEAD set as\nreported by the server or if we encounter any errors along the way.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v3: - does not rely on remote set-head anymore so it only authenticates\n        once\n        - uses the new REF_CREATE_ONLY to atomically check if the ref exists\n          and only write it if it doesn't\n        - in all other cases the maximum it does is print a warning\n    \n    v4: - instead of the discarded REF_CREATE_ONLY, it uses the existing,\n          but updated transaction api to request a silent create only\n        - it now uses the atomic before_target to determine reporting\n        - refactored for legibility\n    \n    v5: - instead of printing a not too useful message, it now fails\n          silently, this in line with the objective to only set up\n          remote/HEAD automatically if the right thing is trivial, for\n          everything else there is remote set-head\n        - fixed all failing tests\n        - added two new tests, one for checking if remote/HEAD is set to the\n          correct one, and one to test that we do not override remote/HEAD\n          if it has changed on the server from what we have locally\n    \n    v6: - fixed style issues and unintended extra empty line\n        - updated function call with bool to int from previous patch's\n          change\n        - removed calls to error(...) inherited from builtin/remote.c so we\n          actually fail silently\n        - set the test for remote set-head --auto to the correct value here,\n          which was previously erronously set in the remote set-head patch\n    \n    v7: - no change\n    \n    v8: - changed logmsg in call to refs_update_symref from \"remote\n          set-head\" to \"fetch\"\n    \n    v9: - follow through with refs_update_symref_extended\n        - fix test errors uncovered by the new patch\n    \n    v10: no change\n    \n    v11: fixed some memory leaks\n\n builtin/fetch.c                  |  86 ++++++++++++\n t/t4207-log-decoration-colors.sh |   3 +-\n t/t5505-remote.sh                |  21 ++-\n t/t5510-fetch.sh                 | 229 ++++++++++++++++---------------\n t/t5512-ls-remote.sh             |   2 +\n t/t5514-fetch-multiple.sh        |  17 ++-\n t/t5516-fetch-push.sh            |   3 +-\n t/t5527-fetch-odd-refs.sh        |   3 +-\n t/t7900-maintenance.sh           |   3 +-\n t/t9210-scalar.sh                |   5 +-\n t/t9211-scalar-clone.sh          |   6 +-\n t/t9902-completion.sh            |  65 +++++++++\n 12 files changed, 319 insertions(+), 124 deletions(-)\n\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex d9027e4dc9..cadfd2407a 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1578,6 +1578,84 @@ static int backfill_tags(struct display_state *display_state,\n \treturn retcode;\n }\n \n+static void report_set_head(const char *remote, const char *head_name,\n+\t\t\tstruct strbuf *buf_prev) {\n+\tstruct strbuf buf_prefix = STRBUF_INIT;\n+\tconst char *prev_head = NULL;\n+\n+\tstrbuf_addf(&buf_prefix, \"refs/remotes/%s/\", remote);\n+\tskip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);\n+\n+\tif (prev_head && strcmp(prev_head, head_name)) {\n+\t\tprintf(\"'HEAD' at '%s' has changed from '%s' to '%s'\\n\",\n+\t\t\tremote, prev_head, head_name);\n+\t\tprintf(\"Run 'git remote set-head %s %s' to follow the change.\\n\",\n+\t\t\tremote, head_name);\n+\t}\n+\tstrbuf_release(&buf_prefix);\n+}\n+\n+static const char *strip_refshead(const char *name){\n+\tskip_prefix(name, \"refs/heads/\", &name);\n+\treturn name;\n+}\n+\n+static int set_head(const struct ref *remote_refs)\n+{\n+\tint result = 0;\n+\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,\n+\t\tb_local_head = STRBUF_INIT;\n+\tconst char *remote = gtransport->remote->name;\n+\tchar *head_name = NULL;\n+\tstruct ref *ref, *matches;\n+\tstruct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;\n+\tstruct refspec_item refspec = {\n+\t\t.force = 0,\n+\t\t.pattern = 1,\n+\t\t.src = (char *) \"refs/heads/*\",\n+\t\t.dst = (char *) \"refs/heads/*\",\n+\t};\n+\tstruct string_list heads = STRING_LIST_INIT_DUP;\n+\tstruct ref_store *refs = get_main_ref_store(the_repository);\n+\n+\tget_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);\n+\tmatches = guess_remote_head(find_ref_by_name(remote_refs, \"HEAD\"),\n+\t\t\t\t    fetch_map, 1);\n+\tfor (ref = matches; ref; ref = ref->next) {\n+\t\tstring_list_append(&heads, strip_refshead(ref->name));\n+\t}\n+\n+\n+\tif (!heads.nr)\n+\t\tresult = 1;\n+\telse if (heads.nr > 1)\n+\t\tresult = 1;\n+\telse\n+\t\thead_name = xstrdup(heads.items[0].string);\n+\tif (head_name) {\n+\t\tstrbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", remote);\n+\t\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", remote, head_name);\n+\t\t/* make sure it's valid */\n+\t\tif (!refs_ref_exists(refs, b_remote_head.buf))\n+\t\t\tresult = 1;\n+\t\telse if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n+\t\t\t\t\t\"fetch\", &b_local_head, 1))\n+\t\t\tresult = 1;\n+\t\telse\n+\t\t\treport_set_head(remote, head_name, &b_local_head);\n+\n+\t\tfree(head_name);\n+\t}\n+\n+\tfree_refs(fetch_map);\n+\tfree_refs(matches);\n+\tstring_list_clear(&heads, 0);\n+\tstrbuf_release(&b_head);\n+\tstrbuf_release(&b_local_head);\n+\tstrbuf_release(&b_remote_head);\n+\treturn result;\n+}\n+\n static int do_fetch(struct transport *transport,\n \t\t    struct refspec *rs,\n \t\t    const struct fetch_config *config)\n@@ -1647,6 +1725,8 @@ static int do_fetch(struct transport *transport,\n \t\t\t\t    \"refs/tags/\");\n \t}\n \n+\tstrvec_push(&transport_ls_refs_options.ref_prefixes, \"HEAD\");\n+\n \tif (must_list_refs) {\n \t\ttrace2_region_enter(\"fetch\", \"remote_refs\", the_repository);\n \t\tremote_refs = transport_get_remote_refs(transport,\n@@ -1791,6 +1871,12 @@ static int do_fetch(struct transport *transport,\n \t\t\t\t  \"you need to specify exactly one branch with the --set-upstream option\"));\n \t\t}\n \t}\n+\tif (set_head(remote_refs))\n+\t\t;\n+\t\t/*\n+\t\t * Way too many cases where this can go wrong\n+\t\t * so let's just fail silently for now.\n+\t\t */\n \n cleanup:\n \tif (retcode) {\ndiff --git a/t/t4207-log-decoration-colors.sh b/t/t4207-log-decoration-colors.sh\nindex 73ea9e5155..d55d22cb2f 100755\n--- a/t/t4207-log-decoration-colors.sh\n+++ b/t/t4207-log-decoration-colors.sh\n@@ -59,7 +59,8 @@ ${c_reset}${c_tag}tag: ${c_reset}${c_tag}v1.0${c_reset}${c_commit}, \\\n ${c_reset}${c_tag}tag: ${c_reset}${c_tag}B${c_reset}${c_commit})${c_reset} B\n ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\n ${c_tag}tag: ${c_reset}${c_tag}A1${c_reset}${c_commit}, \\\n-${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit})${c_reset} A1\n+${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit}, \\\n+${c_reset}${c_remoteBranch}other/HEAD${c_reset}${c_commit})${c_reset} A1\n \t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\n ${c_stash}refs/stash${c_reset}${c_commit})${c_reset} On main: Changes to A.t\n \t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex 0ea86d51a4..4990d00209 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -74,7 +74,7 @@ test_expect_success 'add another remote' '\n \t\tcd test &&\n \t\tgit remote add -f second ../two &&\n \t\ttokens_match \"origin second\" \"$(git remote)\" &&\n-\t\tcheck_tracking_branch second main side another &&\n+\t\tcheck_tracking_branch second main side another HEAD &&\n \t\tgit for-each-ref \"--format=%(refname)\" refs/remotes |\n \t\tsed -e \"/^refs\\/remotes\\/origin\\//d\" \\\n \t\t    -e \"/^refs\\/remotes\\/second\\//d\" >actual &&\n@@ -476,7 +476,7 @@ test_expect_success 'set-head --auto has no problem w/multiple HEADs' '\n \t\tcd test &&\n \t\tgit fetch two \"refs/heads/*:refs/remotes/two/*\" &&\n \t\tgit remote set-head --auto two >output 2>&1 &&\n-\t\techo \"${SQ}two/HEAD${SQ} is now created and points to ${SQ}main${SQ}\" >expect &&\n+\t\techo \"${SQ}two/HEAD${SQ} is unchanged and points to ${SQ}main${SQ}\" >expect &&\n \t\ttest_cmp expect output\n \t)\n '\n@@ -764,8 +764,10 @@ test_expect_success 'reject --no-no-tags' '\n '\n \n cat >one/expect <<\\EOF\n+  apis/HEAD -> apis/main\n   apis/main\n   apis/side\n+  drosophila/HEAD -> drosophila/main\n   drosophila/another\n   drosophila/main\n   drosophila/side\n@@ -783,11 +785,14 @@ test_expect_success 'update' '\n '\n \n cat >one/expect <<\\EOF\n+  drosophila/HEAD -> drosophila/main\n   drosophila/another\n   drosophila/main\n   drosophila/side\n+  manduca/HEAD -> manduca/main\n   manduca/main\n   manduca/side\n+  megaloprepus/HEAD -> megaloprepus/main\n   megaloprepus/main\n   megaloprepus/side\n EOF\n@@ -795,7 +800,7 @@ EOF\n test_expect_success 'update with arguments' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\n@@ -827,10 +832,13 @@ test_expect_success 'update --prune' '\n '\n \n cat >one/expect <<-\\EOF\n+  apis/HEAD -> apis/main\n   apis/main\n   apis/side\n+  manduca/HEAD -> manduca/main\n   manduca/main\n   manduca/side\n+  megaloprepus/HEAD -> megaloprepus/main\n   megaloprepus/main\n   megaloprepus/side\n EOF\n@@ -838,7 +846,7 @@ EOF\n test_expect_success 'update default' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\n@@ -850,6 +858,7 @@ test_expect_success 'update default' '\n '\n \n cat >one/expect <<\\EOF\n+  drosophila/HEAD -> drosophila/main\n   drosophila/another\n   drosophila/main\n   drosophila/side\n@@ -858,7 +867,7 @@ EOF\n test_expect_success 'update default (overridden, with funny whitespace)' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\n@@ -872,7 +881,7 @@ test_expect_success 'update default (overridden, with funny whitespace)' '\n test_expect_success 'update (with remotes.default defined)' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex 0890b9f61c..dfc8d748ba 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -75,6 +75,30 @@ test_expect_success \"fetch test for-merge\" '\n \tcut -f -2 .git/FETCH_HEAD >actual &&\n \ttest_cmp expected actual'\n \n+test_expect_success \"fetch test remote HEAD\" '\n+\tcd \"$D\" &&\n+\tcd two &&\n+\tgit fetch &&\n+\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\tgit rev-parse --verify refs/remotes/origin/main &&\n+\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\tbranch=$(git rev-parse refs/remotes/origin/main) &&\n+\ttest \"z$head\" = \"z$branch\"'\n+\n+test_expect_success \"fetch test remote HEAD change\" '\n+\tcd \"$D\" &&\n+\tcd two &&\n+\tgit switch -c other &&\n+\tgit push -u origin other &&\n+\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\tgit rev-parse --verify refs/remotes/origin/main &&\n+\tgit rev-parse --verify refs/remotes/origin/other &&\n+\tgit remote set-head origin other &&\n+\tgit fetch &&\n+\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\tbranch=$(git rev-parse refs/remotes/origin/other) &&\n+\ttest \"z$head\" = \"z$branch\"'\n+\n test_expect_success 'fetch --prune on its own works as expected' '\n \tcd \"$D\" &&\n \tgit clone . prune &&\n@@ -478,7 +502,6 @@ test_expect_success 'unbundle 1' '\n \ttest_must_fail git fetch \"$D/bundle1\" main:main\n '\n \n-\n test_expect_success 'bundle 1 has only 3 files ' '\n \tcd \"$D\" &&\n \ttest_bundle_object_count bundle1 3\n@@ -819,7 +842,7 @@ test_expect_success 'fetch from multiple configured URLs in single remote' '\n \n # configured prune tests\n \n-set_config_tristate () {\n+set_config_tristate() {\n \t# var=$1 val=$2\n \tcase \"$2\" in\n \tunset)\n@@ -833,12 +856,12 @@ set_config_tristate () {\n \tesac\n }\n \n-test_configured_prune () {\n+test_configured_prune() {\n \ttest_configured_prune_type \"$@\" \"name\"\n \ttest_configured_prune_type \"$@\" \"link\"\n }\n \n-test_configured_prune_type () {\n+test_configured_prune_type() {\n \tfetch_prune=$1\n \tremote_origin_prune=$2\n \tfetch_prune_tags=$3\n@@ -848,8 +871,7 @@ test_configured_prune_type () {\n \tcmdline=$7\n \tmode=$8\n \n-\tif test -z \"$cmdline_setup\"\n-\tthen\n+\tif test -z \"$cmdline_setup\"; then\n \t\ttest_expect_success 'setup cmdline_setup variable for subsequent test' '\n \t\t\tremote_url=\"file://$(git -C one config remote.origin.url)\" &&\n \t\t\tremote_fetch=\"$(git -C one config remote.origin.fetch)\" &&\n@@ -857,12 +879,10 @@ test_configured_prune_type () {\n \t\t'\n \tfi\n \n-\tif test \"$mode\" = 'link'\n-\tthen\n+\tif test \"$mode\" = 'link'; then\n \t\tnew_cmdline=\"\"\n \n-\t\tif test \"$cmdline\" = \"\"\n-\t\tthen\n+\t\tif test \"$cmdline\" = \"\"; then\n \t\t\tnew_cmdline=$cmdline_setup\n \t\telse\n \t\t\tnew_cmdline=$(perl -e '\n@@ -873,10 +893,8 @@ test_configured_prune_type () {\n \t\tfi\n \n \t\tif test \"$fetch_prune_tags\" = 'true' ||\n-\t\t   test \"$remote_origin_prune_tags\" = 'true'\n-\t\tthen\n-\t\t\tif ! printf '%s' \"$cmdline\\n\" | grep -q refs/remotes/origin/\n-\t\t\tthen\n+\t\t\ttest \"$remote_origin_prune_tags\" = 'true'; then\n+\t\t\tif ! printf '%s' \"$cmdline\\n\" | grep -q refs/remotes/origin/; then\n \t\t\t\tnew_cmdline=\"$new_cmdline refs/tags/*:refs/tags/*\"\n \t\t\tfi\n \t\tfi\n@@ -946,100 +964,100 @@ test_configured_prune_type () {\n # $7 git-fetch $cmdline:\n #\n #                     $1    $2    $3    $4    $5     $6     $7\n-test_configured_prune unset unset unset unset kept   kept   \"\"\n-test_configured_prune unset unset unset unset kept   kept   \"--no-prune\"\n-test_configured_prune unset unset unset unset pruned kept   \"--prune\"\n-test_configured_prune unset unset unset unset kept   pruned \\\n+test_configured_prune unset unset unset unset kept kept \"\"\n+test_configured_prune unset unset unset unset kept kept \"--no-prune\"\n+test_configured_prune unset unset unset unset pruned kept \"--prune\"\n+test_configured_prune unset unset unset unset kept pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/*\"\n test_configured_prune unset unset unset unset pruned pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*\"\n \n-test_configured_prune false unset unset unset kept   kept   \"\"\n-test_configured_prune false unset unset unset kept   kept   \"--no-prune\"\n-test_configured_prune false unset unset unset pruned kept   \"--prune\"\n+test_configured_prune false unset unset unset kept kept \"\"\n+test_configured_prune false unset unset unset kept kept \"--no-prune\"\n+test_configured_prune false unset unset unset pruned kept \"--prune\"\n \n-test_configured_prune true  unset unset unset pruned kept   \"\"\n-test_configured_prune true  unset unset unset pruned kept   \"--prune\"\n-test_configured_prune true  unset unset unset kept   kept   \"--no-prune\"\n+test_configured_prune true unset unset unset pruned kept \"\"\n+test_configured_prune true unset unset unset pruned kept \"--prune\"\n+test_configured_prune true unset unset unset kept kept \"--no-prune\"\n \n-test_configured_prune unset false unset unset kept   kept   \"\"\n-test_configured_prune unset false unset unset kept   kept   \"--no-prune\"\n-test_configured_prune unset false unset unset pruned kept   \"--prune\"\n+test_configured_prune unset false unset unset kept kept \"\"\n+test_configured_prune unset false unset unset kept kept \"--no-prune\"\n+test_configured_prune unset false unset unset pruned kept \"--prune\"\n \n-test_configured_prune false false unset unset kept   kept   \"\"\n-test_configured_prune false false unset unset kept   kept   \"--no-prune\"\n-test_configured_prune false false unset unset pruned kept   \"--prune\"\n-test_configured_prune false false unset unset kept   pruned \\\n+test_configured_prune false false unset unset kept kept \"\"\n+test_configured_prune false false unset unset kept kept \"--no-prune\"\n+test_configured_prune false false unset unset pruned kept \"--prune\"\n+test_configured_prune false false unset unset kept pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/*\"\n test_configured_prune false false unset unset pruned pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*\"\n \n-test_configured_prune true  false unset unset kept   kept   \"\"\n-test_configured_prune true  false unset unset pruned kept   \"--prune\"\n-test_configured_prune true  false unset unset kept   kept   \"--no-prune\"\n+test_configured_prune true false unset unset kept kept \"\"\n+test_configured_prune true false unset unset pruned kept \"--prune\"\n+test_configured_prune true false unset unset kept kept \"--no-prune\"\n \n-test_configured_prune unset true  unset unset pruned kept   \"\"\n-test_configured_prune unset true  unset unset kept   kept   \"--no-prune\"\n-test_configured_prune unset true  unset unset pruned kept   \"--prune\"\n+test_configured_prune unset true unset unset pruned kept \"\"\n+test_configured_prune unset true unset unset kept kept \"--no-prune\"\n+test_configured_prune unset true unset unset pruned kept \"--prune\"\n \n-test_configured_prune false true  unset unset pruned kept   \"\"\n-test_configured_prune false true  unset unset kept   kept   \"--no-prune\"\n-test_configured_prune false true  unset unset pruned kept   \"--prune\"\n+test_configured_prune false true unset unset pruned kept \"\"\n+test_configured_prune false true unset unset kept kept \"--no-prune\"\n+test_configured_prune false true unset unset pruned kept \"--prune\"\n \n-test_configured_prune true  true  unset unset pruned kept   \"\"\n-test_configured_prune true  true  unset unset pruned kept   \"--prune\"\n-test_configured_prune true  true  unset unset kept   kept   \"--no-prune\"\n-test_configured_prune true  true  unset unset kept   pruned \\\n+test_configured_prune true true unset unset pruned kept \"\"\n+test_configured_prune true true unset unset pruned kept \"--prune\"\n+test_configured_prune true true unset unset kept kept \"--no-prune\"\n+test_configured_prune true true unset unset kept pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/*\"\n-test_configured_prune true  true  unset unset pruned pruned \\\n+test_configured_prune true true unset unset pruned pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*\"\n \n # --prune-tags on its own does nothing, needs --prune as well, same\n # for fetch.pruneTags without fetch.prune\n-test_configured_prune unset unset unset unset kept kept     \"--prune-tags\"\n-test_configured_prune unset unset true unset  kept kept     \"\"\n-test_configured_prune unset unset unset true  kept kept     \"\"\n+test_configured_prune unset unset unset unset kept kept \"--prune-tags\"\n+test_configured_prune unset unset true unset kept kept \"\"\n+test_configured_prune unset unset unset true kept kept \"\"\n \n # These will prune the tags\n test_configured_prune unset unset unset unset pruned pruned \"--prune --prune-tags\"\n-test_configured_prune true  unset true  unset pruned pruned \"\"\n-test_configured_prune unset true  unset true  pruned pruned \"\"\n+test_configured_prune true unset true unset pruned pruned \"\"\n+test_configured_prune unset true unset true pruned pruned \"\"\n \n # remote.<name>.pruneTags overrides fetch.pruneTags, just like\n # remote.<name>.prune overrides fetch.prune if set.\n-test_configured_prune true  unset true unset pruned pruned  \"\"\n-test_configured_prune false true  false true  pruned pruned \"\"\n-test_configured_prune true  false true  false kept   kept   \"\"\n+test_configured_prune true unset true unset pruned pruned \"\"\n+test_configured_prune false true false true pruned pruned \"\"\n+test_configured_prune true false true false kept kept \"\"\n \n # When --prune-tags is supplied it's ignored if an explicit refspec is\n # given, same for the configuration options.\n test_configured_prune unset unset unset unset pruned kept \\\n \t\"--prune --prune-tags origin +refs/heads/*:refs/remotes/origin/*\"\n-test_configured_prune unset unset true  unset pruned kept \\\n+test_configured_prune unset unset true unset pruned kept \\\n \t\"--prune origin +refs/heads/*:refs/remotes/origin/*\"\n-test_configured_prune unset unset unset true pruned  kept \\\n+test_configured_prune unset unset unset true pruned kept \\\n \t\"--prune origin +refs/heads/*:refs/remotes/origin/*\"\n \n # Pruning that also takes place if a file:// url replaces a named\n # remote. However, because there's no implicit\n # +refs/heads/*:refs/remotes/origin/* refspec and supplying it on the\n # command-line negates --prune-tags, the branches will not be pruned.\n-test_configured_prune_type unset unset unset unset kept   kept   \"origin --prune-tags\" \"name\"\n-test_configured_prune_type unset unset unset unset kept   kept   \"origin --prune-tags\" \"link\"\n+test_configured_prune_type unset unset unset unset kept kept \"origin --prune-tags\" \"name\"\n+test_configured_prune_type unset unset unset unset kept kept \"origin --prune-tags\" \"link\"\n test_configured_prune_type unset unset unset unset pruned pruned \"origin --prune --prune-tags\" \"name\"\n-test_configured_prune_type unset unset unset unset kept   pruned \"origin --prune --prune-tags\" \"link\"\n+test_configured_prune_type unset unset unset unset kept pruned \"origin --prune --prune-tags\" \"link\"\n test_configured_prune_type unset unset unset unset pruned pruned \"--prune --prune-tags origin\" \"name\"\n-test_configured_prune_type unset unset unset unset kept   pruned \"--prune --prune-tags origin\" \"link\"\n-test_configured_prune_type unset unset true  unset pruned pruned \"--prune origin\" \"name\"\n-test_configured_prune_type unset unset true  unset kept   pruned \"--prune origin\" \"link\"\n-test_configured_prune_type unset unset unset true  pruned pruned \"--prune origin\" \"name\"\n-test_configured_prune_type unset unset unset true  kept   pruned \"--prune origin\" \"link\"\n-test_configured_prune_type true  unset true  unset pruned pruned \"origin\" \"name\"\n-test_configured_prune_type true  unset true  unset kept   pruned \"origin\" \"link\"\n-test_configured_prune_type unset  true true  unset pruned pruned \"origin\" \"name\"\n-test_configured_prune_type unset  true true  unset kept   pruned \"origin\" \"link\"\n-test_configured_prune_type unset  true unset true  pruned pruned \"origin\" \"name\"\n-test_configured_prune_type unset  true unset true  kept   pruned \"origin\" \"link\"\n+test_configured_prune_type unset unset unset unset kept pruned \"--prune --prune-tags origin\" \"link\"\n+test_configured_prune_type unset unset true unset pruned pruned \"--prune origin\" \"name\"\n+test_configured_prune_type unset unset true unset kept pruned \"--prune origin\" \"link\"\n+test_configured_prune_type unset unset unset true pruned pruned \"--prune origin\" \"name\"\n+test_configured_prune_type unset unset unset true kept pruned \"--prune origin\" \"link\"\n+test_configured_prune_type true unset true unset pruned pruned \"origin\" \"name\"\n+test_configured_prune_type true unset true unset kept pruned \"origin\" \"link\"\n+test_configured_prune_type unset true true unset pruned pruned \"origin\" \"name\"\n+test_configured_prune_type unset true true unset kept pruned \"origin\" \"link\"\n+test_configured_prune_type unset true unset true pruned pruned \"origin\" \"name\"\n+test_configured_prune_type unset true unset true kept pruned \"origin\" \"link\"\n \n # When all remote.origin.fetch settings are deleted a --prune\n # --prune-tags still implicitly supplies refs/tags/*:refs/tags/* so\n@@ -1137,8 +1155,7 @@ test_expect_success 'fetching with auto-gc does not lock up' '\n \t)\n '\n \n-for section in fetch transfer\n-do\n+for section in fetch transfer; do\n \ttest_expect_success \"$section.hideRefs affects connectivity check\" '\n \t\tGIT_TRACE=\"$PWD\"/trace git -c $section.hideRefs=refs -c \\\n \t\t\t$section.hideRefs=\"!refs/tags/\" fetch &&\n@@ -1157,21 +1174,23 @@ test_expect_success 'prepare source branch' '\n \ttest 3 -le $(wc -l <actual)\n '\n \n-validate_store_type () {\n+validate_store_type() {\n \tgit -C dest count-objects -v >actual &&\n-\tcase \"$store_type\" in\n-\tpacked)\n-\t\tgrep \"^count: 0$\" actual ;;\n-\tloose)\n-\t\tgrep \"^packs: 0$\" actual ;;\n-\tesac || {\n+\t\tcase \"$store_type\" in\n+\t\tpacked)\n+\t\t\tgrep \"^count: 0$\" actual\n+\t\t\t;;\n+\t\tloose)\n+\t\t\tgrep \"^packs: 0$\" actual\n+\t\t\t;;\n+\t\tesac || {\n \t\techo \"store_type is $store_type\"\n \t\tcat actual\n \t\tfalse\n \t}\n }\n \n-test_unpack_limit () {\n+test_unpack_limit() {\n \tstore_type=$1\n \n \tcase \"$store_type\" in\n@@ -1192,43 +1211,39 @@ test_unpack_limit () {\n test_unpack_limit packed\n test_unpack_limit loose\n \n-setup_negotiation_tip () {\n+setup_negotiation_tip() {\n \tSERVER=\"$1\"\n \tURL=\"$2\"\n \tUSE_PROTOCOL_V2=\"$3\"\n \n \trm -rf \"$SERVER\" client trace &&\n-\tgit init -b main \"$SERVER\" &&\n-\ttest_commit -C \"$SERVER\" alpha_1 &&\n-\ttest_commit -C \"$SERVER\" alpha_2 &&\n-\tgit -C \"$SERVER\" checkout --orphan beta &&\n-\ttest_commit -C \"$SERVER\" beta_1 &&\n-\ttest_commit -C \"$SERVER\" beta_2 &&\n-\n-\tgit clone \"$URL\" client &&\n-\n-\tif test \"$USE_PROTOCOL_V2\" -eq 1\n-\tthen\n-\t\tgit -C \"$SERVER\" config protocol.version 2 &&\n-\t\tgit -C client config protocol.version 2\n-\tfi &&\n-\n-\ttest_commit -C \"$SERVER\" beta_s &&\n-\tgit -C \"$SERVER\" checkout main &&\n-\ttest_commit -C \"$SERVER\" alpha_s &&\n-\tgit -C \"$SERVER\" tag -d alpha_1 alpha_2 beta_1 beta_2\n+\t\tgit init -b main \"$SERVER\" &&\n+\t\ttest_commit -C \"$SERVER\" alpha_1 &&\n+\t\ttest_commit -C \"$SERVER\" alpha_2 &&\n+\t\tgit -C \"$SERVER\" checkout --orphan beta &&\n+\t\ttest_commit -C \"$SERVER\" beta_1 &&\n+\t\ttest_commit -C \"$SERVER\" beta_2 &&\n+\t\tgit clone \"$URL\" client &&\n+\t\tif test \"$USE_PROTOCOL_V2\" -eq 1; then\n+\t\t\tgit -C \"$SERVER\" config protocol.version 2 &&\n+\t\t\t\tgit -C client config protocol.version 2\n+\t\tfi &&\n+\t\ttest_commit -C \"$SERVER\" beta_s &&\n+\t\tgit -C \"$SERVER\" checkout main &&\n+\t\ttest_commit -C \"$SERVER\" alpha_s &&\n+\t\tgit -C \"$SERVER\" tag -d alpha_1 alpha_2 beta_1 beta_2\n }\n \n-check_negotiation_tip () {\n+check_negotiation_tip() {\n \t# Ensure that {alpha,beta}_1 are sent as \"have\", but not {alpha_beta}_2\n \tALPHA_1=$(git -C client rev-parse alpha_1) &&\n-\tgrep \"fetch> have $ALPHA_1\" trace &&\n-\tBETA_1=$(git -C client rev-parse beta_1) &&\n-\tgrep \"fetch> have $BETA_1\" trace &&\n-\tALPHA_2=$(git -C client rev-parse alpha_2) &&\n-\t! grep \"fetch> have $ALPHA_2\" trace &&\n-\tBETA_2=$(git -C client rev-parse beta_2) &&\n-\t! grep \"fetch> have $BETA_2\" trace\n+\t\tgrep \"fetch> have $ALPHA_1\" trace &&\n+\t\tBETA_1=$(git -C client rev-parse beta_1) &&\n+\t\tgrep \"fetch> have $BETA_1\" trace &&\n+\t\tALPHA_2=$(git -C client rev-parse alpha_2) &&\n+\t\t! grep \"fetch> have $ALPHA_2\" trace &&\n+\t\tBETA_2=$(git -C client rev-parse beta_2) &&\n+\t\t! grep \"fetch> have $BETA_2\" trace\n }\n \n test_expect_success '--negotiation-tip limits \"have\" lines sent' '\ndiff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh\nindex 64b3491e4e..1b3865e154 100755\n--- a/t/t5512-ls-remote.sh\n+++ b/t/t5512-ls-remote.sh\n@@ -293,6 +293,8 @@ test_expect_success 'ls-remote with filtered symref (refname)' '\n \tcat >expect <<-EOF &&\n \tref: refs/heads/main\tHEAD\n \t$rev\tHEAD\n+\tref: refs/remotes/origin/main\trefs/remotes/origin/HEAD\n+\t$rev\trefs/remotes/origin/HEAD\n \tEOF\n \tgit ls-remote --symref . HEAD >actual &&\n \ttest_cmp expect actual\ndiff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh\nindex 579872c258..e3482b27b2 100755\n--- a/t/t5514-fetch-multiple.sh\n+++ b/t/t5514-fetch-multiple.sh\n@@ -45,14 +45,17 @@ test_expect_success setup '\n '\n \n cat > test/expect << EOF\n+  one/HEAD -> one/main\n   one/main\n   one/side\n   origin/HEAD -> origin/main\n   origin/main\n   origin/side\n+  three/HEAD -> three/main\n   three/another\n   three/main\n   three/side\n+  two/HEAD -> two/main\n   two/another\n   two/main\n   two/side\n@@ -97,6 +100,7 @@ cat > expect << EOF\n   origin/HEAD -> origin/main\n   origin/main\n   origin/side\n+  three/HEAD -> three/main\n   three/another\n   three/main\n   three/side\n@@ -112,8 +116,10 @@ test_expect_success 'git fetch --multiple (but only one remote)' '\n '\n \n cat > expect << EOF\n+  one/HEAD -> one/main\n   one/main\n   one/side\n+  two/HEAD -> two/main\n   two/another\n   two/main\n   two/side\n@@ -141,7 +147,7 @@ test_expect_success 'git fetch --multiple (bad remote names)' '\n \n test_expect_success 'git fetch --all (skipFetchAll)' '\n \t(cd test4 &&\n-\t for b in $(git branch -r)\n+\t for b in $(git branch -r | grep -v HEAD)\n \t do\n \t\tgit branch -r -d $b || exit 1\n \t done &&\n@@ -153,11 +159,14 @@ test_expect_success 'git fetch --all (skipFetchAll)' '\n '\n \n cat > expect << EOF\n+  one/HEAD -> one/main\n   one/main\n   one/side\n+  three/HEAD -> three/main\n   three/another\n   three/main\n   three/side\n+  two/HEAD -> two/main\n   two/another\n   two/main\n   two/side\n@@ -165,7 +174,7 @@ EOF\n \n test_expect_success 'git fetch --multiple (ignoring skipFetchAll)' '\n \t(cd test4 &&\n-\t for b in $(git branch -r)\n+\t for b in $(git branch -r | grep -v HEAD)\n \t do\n \t\tgit branch -r -d $b || exit 1\n \t done &&\n@@ -221,14 +230,17 @@ test_expect_success 'git fetch --multiple --jobs=0 picks a default' '\n \n create_fetch_all_expect () {\n \tcat >expect <<-\\EOF\n+\t  one/HEAD -> one/main\n \t  one/main\n \t  one/side\n \t  origin/HEAD -> origin/main\n \t  origin/main\n \t  origin/side\n+\t  three/HEAD -> three/main\n \t  three/another\n \t  three/main\n \t  three/side\n+\t  two/HEAD -> two/main\n \t  two/another\n \t  two/main\n \t  two/side\n@@ -265,6 +277,7 @@ test_expect_success 'git fetch (fetch all remotes with fetch.all = true)' '\n \n create_fetch_one_expect () {\n \tcat >expect <<-\\EOF\n+\t  one/HEAD -> one/main\n \t  one/main\n \t  one/side\n \t  origin/HEAD -> origin/main\ndiff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\nindex 331778bd42..5a051aa0c7 100755\n--- a/t/t5516-fetch-push.sh\n+++ b/t/t5516-fetch-push.sh\n@@ -1395,7 +1395,8 @@ test_expect_success 'fetch follows tags by default' '\n \t\tgit tag -m \"annotated\" tag &&\n \t\tgit for-each-ref >tmp1 &&\n \t\tsed -n \"p; s|refs/heads/main$|refs/remotes/origin/main|p\" tmp1 |\n-\t\tsort -k 3 >../expect\n+\t\tsed -n \"p; s|refs/heads/main$|refs/remotes/origin/HEAD|p\"  |\n+\t\tsort -k 4 >../expect\n \t) &&\n \ttest_when_finished \"rm -rf dst\" &&\n \tgit init dst &&\ndiff --git a/t/t5527-fetch-odd-refs.sh b/t/t5527-fetch-odd-refs.sh\nindex 98ece27c6a..d3996af6ee 100755\n--- a/t/t5527-fetch-odd-refs.sh\n+++ b/t/t5527-fetch-odd-refs.sh\n@@ -52,7 +52,8 @@ test_expect_success LONG_REF 'fetch handles extremely long refname' '\n \tlong\n \tmain\n \tEOF\n-\tgit for-each-ref --format=\"%(subject)\" refs/remotes/long >actual &&\n+\tgit for-each-ref --format=\"%(subject)\" refs/remotes/long \\\n+\t\t--exclude=refs/remotes/long/HEAD >actual &&\n \ttest_cmp expect actual\n '\n \ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex a66d0e089d..88331d1ceb 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -329,7 +329,8 @@ test_expect_success 'incremental-repack task' '\n \n \t# Delete refs that have not been repacked in these packs.\n \tgit for-each-ref --format=\"delete %(refname)\" \\\n-\t\trefs/prefetch refs/tags refs/remotes >refs &&\n+\t\trefs/prefetch refs/tags refs/remotes \\\n+\t\t--exclude=refs/remotes/*/HEAD >refs &&\n \tgit update-ref --stdin <refs &&\n \n \t# Replace the object directory with this pack layout.\ndiff --git a/t/t9210-scalar.sh b/t/t9210-scalar.sh\nindex a30b2c9f70..2237844550 100755\n--- a/t/t9210-scalar.sh\n+++ b/t/t9210-scalar.sh\n@@ -151,7 +151,8 @@ test_expect_success 'scalar clone' '\n \t\t\t\"$(pwd)\" &&\n \n \t\tgit for-each-ref --format=\"%(refname)\" refs/remotes/origin/ >actual &&\n-\t\techo \"refs/remotes/origin/parallel\" >expect &&\n+\t\techo \"refs/remotes/origin/HEAD\" >>expect &&\n+\t\techo \"refs/remotes/origin/parallel\" >>expect &&\n \t\ttest_cmp expect actual &&\n \n \t\ttest_path_is_missing 1/2 &&\n@@ -220,7 +221,7 @@ test_expect_success 'scalar reconfigure --all with includeIf.onbranch' '\n \tdone\n '\n \n- test_expect_success 'scalar reconfigure --all with detached HEADs' '\n+test_expect_success 'scalar reconfigure --all with detached HEADs' '\n \trepos=\"two three four\" &&\n \tfor num in $repos\n \tdo\ndiff --git a/t/t9211-scalar-clone.sh b/t/t9211-scalar-clone.sh\nindex c16ea67c1d..d9cb6b9a3e 100755\n--- a/t/t9211-scalar-clone.sh\n+++ b/t/t9211-scalar-clone.sh\n@@ -32,7 +32,7 @@ test_expect_success 'set up repository to clone' '\n \t)\n '\n \n-cleanup_clone () {\n+cleanup_clone() {\n \trm -rf \"$1\"\n }\n \n@@ -128,7 +128,7 @@ test_expect_success '--single-branch clones HEAD only' '\n \t(\n \t\tcd $enlistment/src &&\n \t\tgit for-each-ref refs/remotes/origin >out &&\n-\t\ttest_line_count = 1 out &&\n+\t\ttest_line_count = 2 out &&\n \t\tgrep \"refs/remotes/origin/base\" out\n \t) &&\n \n@@ -142,7 +142,7 @@ test_expect_success '--no-single-branch clones all branches' '\n \t(\n \t\tcd $enlistment/src &&\n \t\tgit for-each-ref refs/remotes/origin >out &&\n-\t\ttest_line_count = 2 out &&\n+\t\ttest_line_count = 3 out &&\n \t\tgrep \"refs/remotes/origin/base\" out &&\n \t\tgrep \"refs/remotes/origin/parallel\" out\n \t) &&\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex cc6aa9f0cd..b663c4609e 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -658,6 +658,7 @@ test_expect_success '__git_refs - simple' '\n \tHEAD\n \tmain\n \tmatching-branch\n+\tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n \tmatching-tag\n@@ -673,6 +674,7 @@ test_expect_success '__git_refs - full refs' '\n \tcat >expected <<-EOF &&\n \trefs/heads/main\n \trefs/heads/matching-branch\n+\trefs/remotes/other/HEAD\n \trefs/remotes/other/branch-in-other\n \trefs/remotes/other/main-in-other\n \trefs/tags/matching-tag\n@@ -729,6 +731,7 @@ test_expect_success '__git_refs - remote on local file system - full refs' '\n test_expect_success '__git_refs - configured remote' '\n \tcat >expected <<-EOF &&\n \tHEAD\n+\tHEAD\n \tbranch-in-other\n \tmain-in-other\n \tEOF\n@@ -756,6 +759,7 @@ test_expect_success '__git_refs - configured remote - full refs' '\n test_expect_success '__git_refs - configured remote - repo given on the command line' '\n \tcat >expected <<-EOF &&\n \tHEAD\n+\tHEAD\n \tbranch-in-other\n \tmain-in-other\n \tEOF\n@@ -787,6 +791,7 @@ test_expect_success '__git_refs - configured remote - full refs - repo given on\n test_expect_success '__git_refs - configured remote - remote name matches a directory' '\n \tcat >expected <<-EOF &&\n \tHEAD\n+\tHEAD\n \tbranch-in-other\n \tmain-in-other\n \tEOF\n@@ -875,12 +880,14 @@ test_expect_success '__git_refs - unique remote branches for git checkout DWIMer\n \tHEAD\n \tmain\n \tmatching-branch\n+\tother/HEAD\n \tother/ambiguous\n \tother/branch-in-other\n \tother/main-in-other\n \tremote/ambiguous\n \tremote/branch-in-remote\n \tmatching-tag\n+\tHEAD\n \tbranch-in-other\n \tbranch-in-remote\n \tmain-in-other\n@@ -904,6 +911,7 @@ test_expect_success '__git_refs - after --opt=' '\n \tHEAD\n \tmain\n \tmatching-branch\n+\tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n \tmatching-tag\n@@ -919,6 +927,7 @@ test_expect_success '__git_refs - after --opt= - full refs' '\n \tcat >expected <<-EOF &&\n \trefs/heads/main\n \trefs/heads/matching-branch\n+\trefs/remotes/other/HEAD\n \trefs/remotes/other/branch-in-other\n \trefs/remotes/other/main-in-other\n \trefs/tags/matching-tag\n@@ -935,6 +944,7 @@ test_expect_success '__git refs - excluding refs' '\n \t^HEAD\n \t^main\n \t^matching-branch\n+\t^other/HEAD\n \t^other/branch-in-other\n \t^other/main-in-other\n \t^matching-tag\n@@ -950,6 +960,7 @@ test_expect_success '__git refs - excluding full refs' '\n \tcat >expected <<-EOF &&\n \t^refs/heads/main\n \t^refs/heads/matching-branch\n+\t^refs/remotes/other/HEAD\n \t^refs/remotes/other/branch-in-other\n \t^refs/remotes/other/main-in-other\n \t^refs/tags/matching-tag\n@@ -975,6 +986,7 @@ test_expect_success '__git_refs - do not filter refs unless told so' '\n \tmain\n \tmatching-branch\n \tmatching/branch\n+\tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n \tother/matching/branch-in-other\n@@ -1095,6 +1107,7 @@ test_expect_success '__git_complete_refs - simple' '\n \tHEAD Z\n \tmain Z\n \tmatching-branch Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tmatching-tag Z\n@@ -1123,6 +1136,7 @@ test_expect_success '__git_complete_refs - matching' '\n test_expect_success '__git_complete_refs - remote' '\n \tsed -e \"s/Z$//\" >expected <<-EOF &&\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain-in-other Z\n \tEOF\n@@ -1139,9 +1153,11 @@ test_expect_success '__git_complete_refs - track' '\n \tHEAD Z\n \tmain Z\n \tmatching-branch Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tmatching-tag Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain-in-other Z\n \tEOF\n@@ -1184,6 +1200,7 @@ test_expect_success '__git_complete_refs - suffix' '\n \tHEAD.\n \tmain.\n \tmatching-branch.\n+\tother/HEAD.\n \tother/branch-in-other.\n \tother/main-in-other.\n \tmatching-tag.\n@@ -1199,6 +1216,7 @@ test_expect_success '__git_complete_refs - suffix' '\n test_expect_success '__git_complete_fetch_refspecs - simple' '\n \tsed -e \"s/Z$//\" >expected <<-EOF &&\n \tHEAD:HEAD Z\n+\tHEAD:HEAD Z\n \tbranch-in-other:branch-in-other Z\n \tmain-in-other:main-in-other Z\n \tEOF\n@@ -1225,6 +1243,7 @@ test_expect_success '__git_complete_fetch_refspecs - matching' '\n test_expect_success '__git_complete_fetch_refspecs - prefix' '\n \tsed -e \"s/Z$//\" >expected <<-EOF &&\n \t+HEAD:HEAD Z\n+\t+HEAD:HEAD Z\n \t+branch-in-other:branch-in-other Z\n \t+main-in-other:main-in-other Z\n \tEOF\n@@ -1289,6 +1308,7 @@ test_expect_success '__git_complete_worktree_paths with -C' '\n \n test_expect_success 'git switch - with no options, complete local branches and unique remote branch names for DWIM logic' '\n \ttest_completion \"git switch \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1435,11 +1455,13 @@ test_expect_success 'git-bisect - existing view subcommand is recognized and ena\n test_expect_success 'git checkout - completes refs and unique remote branches for DWIM' '\n \ttest_completion \"git checkout \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1461,6 +1483,7 @@ test_expect_success 'git switch - with GIT_COMPLETION_CHECKOUT_NO_GUESS=1, compl\n \n test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete local branches and unique remote names for DWIM logic' '\n \tGIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git switch --guess \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1470,6 +1493,7 @@ test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_G\n \n test_expect_success 'git switch - a later --guess overrides previous --no-guess, complete local and remote unique branches for DWIM' '\n \ttest_completion \"git switch --no-guess --guess \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1490,6 +1514,7 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1498,11 +1523,13 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete\n test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1, complete refs and unique remote branches for DWIM' '\n \tGIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git checkout --guess \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1514,6 +1541,7 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1522,11 +1550,13 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '\n test_expect_success 'git checkout - a later --guess overrides previous --no-guess, complete refs and unique remote branches for DWIM' '\n \ttest_completion \"git checkout --no-guess --guess \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1538,6 +1568,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous --gues\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1550,6 +1581,7 @@ test_expect_success 'git checkout - with checkout.guess = false, only completes\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1559,11 +1591,13 @@ test_expect_success 'git checkout - with checkout.guess = true, completes refs a\n \ttest_config checkout.guess true &&\n \ttest_completion \"git checkout \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1573,11 +1607,13 @@ test_expect_success 'git checkout - a later --guess overrides previous checkout.\n \ttest_config checkout.guess false &&\n \ttest_completion \"git checkout --guess \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1590,6 +1626,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous checko\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1601,6 +1638,7 @@ test_expect_success 'git switch - with --detach, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1612,6 +1650,7 @@ test_expect_success 'git checkout - with --detach, complete only references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1783,6 +1822,7 @@ test_expect_success 'git switch - with -d, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1794,6 +1834,7 @@ test_expect_success 'git checkout - with -d, complete only references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1801,10 +1842,12 @@ test_expect_success 'git checkout - with -d, complete only references' '\n \n test_expect_success 'git switch - with --track, complete only remote branches' '\n \ttest_completion \"git switch --track \" <<-\\EOF &&\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n \ttest_completion \"git switch -t \" <<-\\EOF\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1812,10 +1855,12 @@ test_expect_success 'git switch - with --track, complete only remote branches' '\n \n test_expect_success 'git checkout - with --track, complete only remote branches' '\n \ttest_completion \"git checkout --track \" <<-\\EOF &&\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n \ttest_completion \"git checkout -t \" <<-\\EOF\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1834,6 +1879,7 @@ test_expect_success 'git checkout - with --no-track, complete only local referen\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1845,6 +1891,7 @@ test_expect_success 'git switch - with -c, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1856,6 +1903,7 @@ test_expect_success 'git switch - with -C, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1867,6 +1915,7 @@ test_expect_success 'git switch - with -c and --track, complete all references'\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1878,6 +1927,7 @@ test_expect_success 'git switch - with -C and --track, complete all references'\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1889,6 +1939,7 @@ test_expect_success 'git switch - with -c and --no-track, complete all reference\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1900,6 +1951,7 @@ test_expect_success 'git switch - with -C and --no-track, complete all reference\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1911,6 +1963,7 @@ test_expect_success 'git checkout - with -b, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1922,6 +1975,7 @@ test_expect_success 'git checkout - with -B, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1933,6 +1987,7 @@ test_expect_success 'git checkout - with -b and --track, complete all references\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1944,6 +1999,7 @@ test_expect_success 'git checkout - with -B and --track, complete all references\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1955,6 +2011,7 @@ test_expect_success 'git checkout - with -b and --no-track, complete all referen\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1966,6 +2023,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1973,6 +2031,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen\n \n test_expect_success 'git switch - for -c, complete local branches and unique remote branches' '\n \ttest_completion \"git switch -c \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1982,6 +2041,7 @@ test_expect_success 'git switch - for -c, complete local branches and unique rem\n \n test_expect_success 'git switch - for -C, complete local branches and unique remote branches' '\n \ttest_completion \"git switch -C \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2019,6 +2079,7 @@ test_expect_success 'git switch - for -C with --no-track, complete local branche\n \n test_expect_success 'git checkout - for -b, complete local branches and unique remote branches' '\n \ttest_completion \"git checkout -b \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2028,6 +2089,7 @@ test_expect_success 'git checkout - for -b, complete local branches and unique r\n \n test_expect_success 'git checkout - for -B, complete local branches and unique remote branches' '\n \ttest_completion \"git checkout -B \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2065,6 +2127,7 @@ test_expect_success 'git checkout - for -B with --no-track, complete local branc\n \n test_expect_success 'git switch - with --orphan completes local branch names and unique remote branch names' '\n \ttest_completion \"git switch --orphan \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2080,6 +2143,7 @@ test_expect_success 'git switch - --orphan with branch already provided complete\n \n test_expect_success 'git checkout - with --orphan completes local branch names and unique remote branch names' '\n \ttest_completion \"git checkout --orphan \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2093,6 +2157,7 @@ test_expect_success 'git checkout - --orphan with branch already provided comple\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n-- \n2.47.0.94.gb64850d498\n\n\n"},{"id":"505861","messageId":"20241022194710.3743691-9-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241022194710.3743691-1-bence@ferdinandy.com","subject":"[PATCH v11 8/8] fetch set_head: handle mirrored bare repositories","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-22T19:45:45Z","receivedAt":"2024-10-22T19:49:39Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"When adding a remote to bare repository with \"git remote add --mirror\",\nrunning fetch will fail to update HEAD to the remote's HEAD, since it\ndoes not know how to handle bare repositories. On the other hand HEAD\nalready has content, since \"git init --bare\" has already set HEAD to\nwhatever is the default branch set for the user. Unless this - by chance\n- is the same as the remote's HEAD, HEAD will be pointing to a bad\nsymref. Teach set_head to handle bare repositories, by overwriting HEAD\nso it mirrors the remote's HEAD.\n\nNote, that in this case overriding the local HEAD reference is\nnecessary, since HEAD will exist before fetch can be run, but this\nshould not be an issue, since the whole purpose of --mirror is to be an\nexact mirror of the remote, so following any changes to HEAD makes\nsense.\n\nAlso note, that although \"git remote set-head\" also fails when trying to\nupdate the remote's locally tracked HEAD in a mirrored bare repository,\nthe usage of the command does not make much sense after this patch:\nfetch will update the remote HEAD correctly, and setting it manually to\nsomething else is antithetical to the concept of mirroring.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v10: - new patch\n         - handles the issue discovered in\n           https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/T/\n    \n    v11: no change\n\n builtin/fetch.c   | 16 ++++++++++++----\n t/t5505-remote.sh | 10 ++++++++++\n 2 files changed, 22 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex cadfd2407a..8af3efd6ac 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1633,14 +1633,22 @@ static int set_head(const struct ref *remote_refs)\n \telse\n \t\thead_name = xstrdup(heads.items[0].string);\n \tif (head_name) {\n-\t\tstrbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", remote);\n-\t\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", remote, head_name);\n+\t\tint is_bare = is_bare_repository();\n+\t\tif (is_bare) {\n+\t\t\tstrbuf_addstr(&b_head, \"HEAD\");\n+\t\t\tstrbuf_addf(&b_remote_head, \"refs/heads/%s\", head_name);\n+\t\t} else {\n+\t\t\tstrbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", remote);\n+\t\t\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", remote, head_name);\n+\t\t}\n \t\t/* make sure it's valid */\n-\t\tif (!refs_ref_exists(refs, b_remote_head.buf))\n+\t\tif (!is_bare && !refs_ref_exists(refs, b_remote_head.buf)) {\n \t\t\tresult = 1;\n+\t\t}\n \t\telse if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n-\t\t\t\t\t\"fetch\", &b_local_head, 1))\n+\t\t\t\t\t\"fetch\", &b_local_head, !is_bare)) {\n \t\t\tresult = 1;\n+\t\t}\n \t\telse\n \t\t\treport_set_head(remote, head_name, &b_local_head);\n \ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex 4990d00209..dfa78f3e8d 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -545,6 +545,16 @@ test_expect_success 'add --mirror && prune' '\n \t)\n '\n \n+test_expect_success 'add --mirror setting HEAD' '\n+\tmkdir headmirror &&\n+\t(\n+\t\tcd headmirror &&\n+\t\tgit init --bare -b notmain &&\n+\t\tgit remote add --mirror -f origin ../one &&\n+\t\ttest \"$(git symbolic-ref HEAD)\" = \"refs/heads/main\"\n+\t)\n+'\n+\n test_expect_success 'add --mirror=fetch' '\n \tmkdir mirror-fetch &&\n \tgit init -b main mirror-fetch/parent &&\n-- \n2.47.0.94.gb64850d498\n\n"},{"id":"505862","messageId":"20241022194710.3743691-4-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241022194710.3743691-1-bence@ferdinandy.com","subject":"[PATCH v11 3/8] remote set-head: refactor for readability","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-22T19:45:40Z","receivedAt":"2024-10-22T19:49:42Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Rename buf and buf2 to something more explanatory.\n\nInstead of calling get_main_ref_store(the_repository) multiple times,\ncall it once and store in a new refs variable. Although this change\nprobably offers some performance benefits, the main purpose is to\nshorten the line lengths of function calls using this variable.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v5: new patch (split from the next patch as a preparatory step)\n    \n    v6: no change\n    \n    v7: - change commit prefix to be more in line with project standards\n    \n    v8: no change\n    \n    v9: - further improve readability by renaming buf, and buf2 consistently\n          with how patch 6 was already done\n    \n    v10: no change\n    \n    v11: no change\n\n builtin/remote.c | 23 ++++++++++++-----------\n 1 file changed, 12 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 76670ddd8b..1d68c5b2ba 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1402,8 +1402,9 @@ static int show(int argc, const char **argv, const char *prefix)\n static int set_head(int argc, const char **argv, const char *prefix)\n {\n \tint i, opt_a = 0, opt_d = 0, result = 0;\n-\tstruct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;\n+\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;\n \tchar *head_name = NULL;\n+\tstruct ref_store *refs = get_main_ref_store(the_repository);\n \n \tstruct option options[] = {\n \t\tOPT_BOOL('a', \"auto\", &opt_a,\n@@ -1415,7 +1416,7 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \targc = parse_options(argc, argv, prefix, options,\n \t\t\t     builtin_remote_sethead_usage, 0);\n \tif (argc)\n-\t\tstrbuf_addf(&buf, \"refs/remotes/%s/HEAD\", argv[0]);\n+\t\tstrbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", argv[0]);\n \n \tif (!opt_a && !opt_d && argc == 2) {\n \t\thead_name = xstrdup(argv[1]);\n@@ -1434,25 +1435,25 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\t\thead_name = xstrdup(states.heads.items[0].string);\n \t\tfree_remote_ref_states(&states);\n \t} else if (opt_d && !opt_a && argc == 1) {\n-\t\tif (refs_delete_ref(get_main_ref_store(the_repository), NULL, buf.buf, NULL, REF_NO_DEREF))\n-\t\t\tresult |= error(_(\"Could not delete %s\"), buf.buf);\n+\t\tif (refs_delete_ref(refs, NULL, b_head.buf, NULL, REF_NO_DEREF))\n+\t\t\tresult |= error(_(\"Could not delete %s\"), b_head.buf);\n \t} else\n \t\tusage_with_options(builtin_remote_sethead_usage, options);\n \n \tif (head_name) {\n-\t\tstrbuf_addf(&buf2, \"refs/remotes/%s/%s\", argv[0], head_name);\n+\t\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", argv[0], head_name);\n \t\t/* make sure it's valid */\n-\t\tif (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))\n-\t\t\tresult |= error(_(\"Not a valid ref: %s\"), buf2.buf);\n-\t\telse if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote set-head\"))\n-\t\t\tresult |= error(_(\"Could not setup %s\"), buf.buf);\n+\t\tif (!refs_ref_exists(refs, b_remote_head.buf))\n+\t\t\tresult |= error(_(\"Not a valid ref: %s\"), b_remote_head.buf);\n+\t\telse if (refs_update_symref(refs, b_head.buf, b_remote_head.buf, \"remote set-head\"))\n+\t\t\tresult |= error(_(\"Could not setup %s\"), b_head.buf);\n \t\telse if (opt_a)\n \t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\n \t\tfree(head_name);\n \t}\n \n-\tstrbuf_release(&buf);\n-\tstrbuf_release(&buf2);\n+\tstrbuf_release(&b_head);\n+\tstrbuf_release(&b_remote_head);\n \treturn result;\n }\n \n-- \n2.47.0.94.gb64850d498\n\n"},{"id":"505863","messageId":"79c9cd6d-7bcc-4014-91c0-149505f70136@app.fastmail.com","threadId":"62096","inReplyTo":"20241022194710.3743691-2-bence@ferdinandy.com","subject":"Re: [PATCH v11 1/8] t/t5505-remote: set default branch to main","fromName":"Kristoffer Haugsbakk","fromEmail":"kristofferhaugsbakk@fastmail.com","sentAt":"2024-10-22T19:53:41Z","receivedAt":"2024-10-22T19:54:04Z","isPatch":true,"sender":{"key":"kristofferhaugsbakk@fastmail.com","avatar":null},"body":"On Tue, Oct 22, 2024, at 21:45, Bence Ferdinandy wrote:\n> Consider the bare repository called \"mirror\" in the test.  Running `git\n> remote add --mirror -f origin ../one` will not change HEAD, consequently\n> if init.defaultBranch is not the same as what HEAD in the remote\n> (\"one\"), HEAD in \"mirror\" will be pointing to a non-existent reference.\n> Hence if \"mirror\" is used as a remote by yet another repository,\n> ls-remote will not show HEAD. On the other hand, if init.defaultBranch\n> happens to match HEAD in \"one\", then ls-remote will show HEAD.\n>\n> Since the CI globally exports GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main,\n> there's a drift between how the test repositories are set up in the CI\n> and during local testing. This issue does not manifest currently, as the\n> test does not do any remote HEAD manipulation where this would come up,\n> but should such things be added, a locally passing test would break the\n> CI vice-versa.\n>\n> Set GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main in the test to be\n> consistent with the CI.\n> ---\n>\n> Notes:\n>     v9: - new patch\n>         - a bandaid for the CI issue noticed by Taylor (cf:\n>           https://lore.kernel.org/git/Zw8IKyPkG0Hr6%2F5t@nand.local/), \n> but\n>           see\n>           \n> https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/ \n> for the root cause in detail\n\nWhy no signoff?\n-- \nKristoffer Haugsbakk\n"},{"id":"505864","messageId":"D52LZ1W0FH29.1GVLRMGN80HPB@ferdinandy.com","threadId":"62096","inReplyTo":"79c9cd6d-7bcc-4014-91c0-149505f70136@app.fastmail.com","subject":"Re: [PATCH v11 1/8] t/t5505-remote: set default branch to main","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-22T20:14:12Z","receivedAt":"2024-10-22T20:15:38Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Tue Oct 22, 2024 at 21:53, Kristoffer Haugsbakk <kristofferhaugsbakk@fastmail.com> wrote:\n> On Tue, Oct 22, 2024, at 21:45, Bence Ferdinandy wrote:\n>> Consider the bare repository called \"mirror\" in the test.  Running `git\n>> remote add --mirror -f origin ../one` will not change HEAD, consequently\n>> if init.defaultBranch is not the same as what HEAD in the remote\n>> (\"one\"), HEAD in \"mirror\" will be pointing to a non-existent reference.\n>> Hence if \"mirror\" is used as a remote by yet another repository,\n>> ls-remote will not show HEAD. On the other hand, if init.defaultBranch\n>> happens to match HEAD in \"one\", then ls-remote will show HEAD.\n>>\n>> Since the CI globally exports GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main,\n>> there's a drift between how the test repositories are set up in the CI\n>> and during local testing. This issue does not manifest currently, as the\n>> test does not do any remote HEAD manipulation where this would come up,\n>> but should such things be added, a locally passing test would break the\n>> CI vice-versa.\n>>\n>> Set GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main in the test to be\n>> consistent with the CI.\n>> ---\n>>\n>> Notes:\n>>     v9: - new patch\n>>         - a bandaid for the CI issue noticed by Taylor (cf:\n>>           https://lore.kernel.org/git/Zw8IKyPkG0Hr6%2F5t@nand.local/), \n>> but\n>>           see\n>>           \n>> https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/ \n>> for the root cause in detail\n>\n> Why no signoff?\n\nBecause I always forget :( I've been thinking on making it by default, but\nI didn't want to add it to all the work related commits, where I'd be the only\none doing it, but I just found the format.signoff setting, so I should be\ncovered for future patches.\n\nI'll wait some time before sending a fix for this :)\n\nThanks,\nBence\n\n-- \nbence.ferdinandy.com\n\n"},{"id":"505932","messageId":"ZxkRq3GNbrO1cqw3@nand.local","threadId":"62096","inReplyTo":"D52LZ1W0FH29.1GVLRMGN80HPB@ferdinandy.com","subject":"Re: [PATCH v11 1/8] t/t5505-remote: set default branch to main","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2024-10-23T15:09:31Z","receivedAt":"2024-10-23T15:09:37Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Tue, Oct 22, 2024 at 10:14:12PM +0200, Bence Ferdinandy wrote:\n>\n> On Tue Oct 22, 2024 at 21:53, Kristoffer Haugsbakk <kristofferhaugsbakk@fastmail.com> wrote:\n> > On Tue, Oct 22, 2024, at 21:45, Bence Ferdinandy wrote:\n> >> Consider the bare repository called \"mirror\" in the test.  Running `git\n> >> remote add --mirror -f origin ../one` will not change HEAD, consequently\n> >> if init.defaultBranch is not the same as what HEAD in the remote\n> >> (\"one\"), HEAD in \"mirror\" will be pointing to a non-existent reference.\n> >> Hence if \"mirror\" is used as a remote by yet another repository,\n> >> ls-remote will not show HEAD. On the other hand, if init.defaultBranch\n> >> happens to match HEAD in \"one\", then ls-remote will show HEAD.\n> >>\n> >> Since the CI globally exports GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main,\n> >> there's a drift between how the test repositories are set up in the CI\n> >> and during local testing. This issue does not manifest currently, as the\n> >> test does not do any remote HEAD manipulation where this would come up,\n> >> but should such things be added, a locally passing test would break the\n> >> CI vice-versa.\n> >>\n> >> Set GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main in the test to be\n> >> consistent with the CI.\n> >> ---\n> >>\n> >> Notes:\n> >>     v9: - new patch\n> >>         - a bandaid for the CI issue noticed by Taylor (cf:\n> >>           https://lore.kernel.org/git/Zw8IKyPkG0Hr6%2F5t@nand.local/),\n> >> but\n> >>           see\n> >>\n> >> https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/\n> >> for the root cause in detail\n> >\n> > Why no signoff?\n>\n> Because I always forget :( I've been thinking on making it by default, but\n> I didn't want to add it to all the work related commits, where I'd be the only\n> one doing it, but I just found the format.signoff setting, so I should be\n> covered for future patches.\n>\n> I'll wait some time before sending a fix for this :)\n\nYeah, I was going to suggest format.signOff as well. If you don't want\nto add your S-o-b to work commits but do want to add it to patches you\nsend to the Git list, you could just stop there (assuming that you don't\nuse format-patch for work-related patches).\n\nAlternatively, you could set it in your copy of git.git's own\nconfiguration in $GIT_DIR/config, so that it doesn't apply to work you\ndo outside of that repository.\n\nEither way, I'm going to avoid queueing this round since it is missing\nthe required Signed-off-by line.\n\nThanks,\nTaylor\n"},{"id":"505933","messageId":"D53ANCP0WMJF.2PG371ZJS1F95@ferdinandy.com","threadId":"62096","inReplyTo":"ZxkRq3GNbrO1cqw3@nand.local","subject":"Re: [PATCH v11 1/8] t/t5505-remote: set default branch to main","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-23T15:34:23Z","receivedAt":"2024-10-23T15:35:05Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Wed Oct 23, 2024 at 17:09, Taylor Blau <me@ttaylorr.com> wrote:\n> On Tue, Oct 22, 2024 at 10:14:12PM +0200, Bence Ferdinandy wrote:\n>>\n>> On Tue Oct 22, 2024 at 21:53, Kristoffer Haugsbakk <kristofferhaugsbakk@fastmail.com> wrote:\n>> > On Tue, Oct 22, 2024, at 21:45, Bence Ferdinandy wrote:\n>> >> Consider the bare repository called \"mirror\" in the test.  Running `git\n>> >> remote add --mirror -f origin ../one` will not change HEAD, consequently\n>> >> if init.defaultBranch is not the same as what HEAD in the remote\n>> >> (\"one\"), HEAD in \"mirror\" will be pointing to a non-existent reference.\n>> >> Hence if \"mirror\" is used as a remote by yet another repository,\n>> >> ls-remote will not show HEAD. On the other hand, if init.defaultBranch\n>> >> happens to match HEAD in \"one\", then ls-remote will show HEAD.\n>> >>\n>> >> Since the CI globally exports GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main,\n>> >> there's a drift between how the test repositories are set up in the CI\n>> >> and during local testing. This issue does not manifest currently, as the\n>> >> test does not do any remote HEAD manipulation where this would come up,\n>> >> but should such things be added, a locally passing test would break the\n>> >> CI vice-versa.\n>> >>\n>> >> Set GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main in the test to be\n>> >> consistent with the CI.\n>> >> ---\n>> >>\n>> >> Notes:\n>> >>     v9: - new patch\n>> >>         - a bandaid for the CI issue noticed by Taylor (cf:\n>> >>           https://lore.kernel.org/git/Zw8IKyPkG0Hr6%2F5t@nand.local/),\n>> >> but\n>> >>           see\n>> >>\n>> >> https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/\n>> >> for the root cause in detail\n>> >\n>> > Why no signoff?\n>>\n>> Because I always forget :( I've been thinking on making it by default, but\n>> I didn't want to add it to all the work related commits, where I'd be the only\n>> one doing it, but I just found the format.signoff setting, so I should be\n>> covered for future patches.\n>>\n>> I'll wait some time before sending a fix for this :)\n>\n> Yeah, I was going to suggest format.signOff as well. If you don't want\n> to add your S-o-b to work commits but do want to add it to patches you\n> send to the Git list, you could just stop there (assuming that you don't\n> use format-patch for work-related patches).\n\nThe projects I contribute to seem to have a bidirectional mapping of requiring\nS-o-b and format patch so I should be good with this :)\n\n>\n> Alternatively, you could set it in your copy of git.git's own\n> configuration in $GIT_DIR/config, so that it doesn't apply to work you\n> do outside of that repository.\n>\n> Either way, I'm going to avoid queueing this round since it is missing\n> the required Signed-off-by line.\n\nIn that case I'll send a v12 with the S-o-b so the series can get back into\ncirculation.\n\nBest,\nBence\n\n>\n> Thanks,\n> Taylor\n\n\n\n\n-- \nbence.ferdinandy.com\n\n"},{"id":"505935","messageId":"20241023153736.257733-2-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241023153736.257733-1-bence@ferdinandy.com","subject":"[PATCH v12 1/8] t/t5505-remote: set default branch to main","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-23T15:36:35Z","receivedAt":"2024-10-23T15:38:54Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Consider the bare repository called \"mirror\" in the test.  Running `git\nremote add --mirror -f origin ../one` will not change HEAD, consequently\nif init.defaultBranch is not the same as what HEAD in the remote\n(\"one\"), HEAD in \"mirror\" will be pointing to a non-existent reference.\nHence if \"mirror\" is used as a remote by yet another repository,\nls-remote will not show HEAD. On the other hand, if init.defaultBranch\nhappens to match HEAD in \"one\", then ls-remote will show HEAD.\n\nSince the CI globally exports GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main,\nthere's a drift between how the test repositories are set up in the CI\nand during local testing. This issue does not manifest currently, as the\ntest does not do any remote HEAD manipulation where this would come up,\nbut should such things be added, a locally passing test would break the\nCI vice-versa.\n\nSet GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main in the test to be\nconsistent with the CI.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v9: - new patch\n        - a bandaid for the CI issue noticed by Taylor (cf:\n          https://lore.kernel.org/git/Zw8IKyPkG0Hr6%2F5t@nand.local/), but\n          see\n          https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/ for the root cause in detail\n    \n    v10: no change\n    \n    v11: no change\n    \n    v12: added forgotten sign-off\n\n t/t5505-remote.sh | 3 +++\n 1 file changed, 3 insertions(+)\n\ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex 532035933f..9b50276646 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -2,6 +2,9 @@\n \n test_description='git remote porcelain-ish'\n \n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n-- \n2.47.0.94.g1247fb88fd.dirty\n\n"},{"id":"505936","messageId":"20241023153736.257733-1-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241022194710.3743691-1-bence@ferdinandy.com","subject":"[PATCH v12 0/8] set-head/fetch remote/HEAD updates","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-23T15:36:34Z","receivedAt":"2024-10-23T15:38:57Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"v12: added missing S-o-b\n\nv11: fixed leaks found by running tests after `make SANITIZE=leak`\n    green pipeline:\n    https://github.com/ferdinandyb/git/actions/runs/11466803523\n\nv10: added a new patch (8/8) on top of v9 to handle HEAD in mirrored bare\nrepositories, rest of the patches are unchanged\n\nv9:\nthe two most notable changes are\n- the new patch 1/7 which address\n  https://lore.kernel.org/git/Zw8IKyPkG0Hr6%2F5t@nand.local/, but see\n  https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/\n- the refs_update_symref -> refs_update_symref_extended change in 2/7,\n  reflecting on Phillip's comments (see\n  https://lore.kernel.org/git/a7cb48e5-d8ba-44c1-9dbe-d1e8f8a63e3c@gmail.com/)\n\nBence Ferdinandy (8):\n  t/t5505-remote: set default branch to main\n  refs: atomically record overwritten ref in update_symref\n  remote set-head: refactor for readability\n  remote set-head: better output for --auto\n  refs: add TRANSACTION_CREATE_EXISTS error\n  refs: add create_only option to refs_update_symref_extended\n  fetch: set remote/HEAD if it does not exist\n  fetch set_head: handle mirrored bare repositories\n\n builtin/fetch.c                  |  94 +++++++++++++\n builtin/remote.c                 |  52 +++++--\n refs.c                           |  41 +++++-\n refs.h                           |   8 +-\n refs/files-backend.c             |  24 ++--\n refs/reftable-backend.c          |   6 +-\n t/t4207-log-decoration-colors.sh |   3 +-\n t/t5505-remote.sh                |  83 ++++++++++-\n t/t5510-fetch.sh                 | 229 ++++++++++++++++---------------\n t/t5512-ls-remote.sh             |   2 +\n t/t5514-fetch-multiple.sh        |  17 ++-\n t/t5516-fetch-push.sh            |   3 +-\n t/t5527-fetch-odd-refs.sh        |   3 +-\n t/t7900-maintenance.sh           |   3 +-\n t/t9210-scalar.sh                |   5 +-\n t/t9211-scalar-clone.sh          |   6 +-\n t/t9902-completion.sh            |  65 +++++++++\n 17 files changed, 490 insertions(+), 154 deletions(-)\n\n-- \n2.47.0.94.g1247fb88fd.dirty\n\n"},{"id":"505937","messageId":"20241023153736.257733-3-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241023153736.257733-1-bence@ferdinandy.com","subject":"[PATCH v12 2/8] refs: atomically record overwritten ref in update_symref","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-23T15:36:36Z","receivedAt":"2024-10-23T15:38:59Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"When updating a symref with update_symref it's currently not possible to\nknow for sure what was the previous value that was overwritten. Extend\nrefs_update_symref under a new function name, to record the value after\nthe ref has been locked if the caller of refs_update_symref_extended\nrequests it via a new variable in the function call. Keep the original\nrefs_update_symref function with the same signature, but now as\na wrapper around refs_update_symref_extended.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v4: new patch\n    \n    v5: - added before_target to reftables backend\n        - added an extra safety check for transaction's existence in refs.c\n    \n    v6: - no change\n    \n    v7: - remove the whole before_target concept from the backends and\n          handle checking it in refs.c instead (thanks Karthik)\n        - rename the before_target to referent which is how the same concept\n          is called in the backends\n        - change commit prefix to be more in line with project standards\n    \n    v8: no change\n    \n    v9: - instead of adding parameters to refs_update_symref, rename what\n          was in v8 as refs_update_symref_extended and make refs_update_symref\n          a wrapper for that. This significantly reduces the number of files\n          that need to be touched, and avoids adding a lot of dummy NULL-s\n          in unrelated places.\n    \n    v10: no change\n    \n    v11: no change\n    \n    v12: no change\n\n refs.c | 19 ++++++++++++++++---\n refs.h |  4 ++++\n 2 files changed, 20 insertions(+), 3 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 5f729ed412..24a4172cd2 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2115,6 +2115,13 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct\n \n int refs_update_symref(struct ref_store *refs, const char *ref,\n \t\t       const char *target, const char *logmsg)\n+{\n+\treturn refs_update_symref_extended(refs, ref, target, logmsg, NULL);\n+}\n+\n+int refs_update_symref_extended(struct ref_store *refs, const char *ref,\n+\t\t       const char *target, const char *logmsg,\n+\t\t       struct strbuf *referent)\n {\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n@@ -2122,13 +2129,20 @@ int refs_update_symref(struct ref_store *refs, const char *ref,\n \n \ttransaction = ref_store_transaction_begin(refs, &err);\n \tif (!transaction ||\n-\t    ref_transaction_update(transaction, ref, NULL, NULL,\n+\t\tref_transaction_update(transaction, ref, NULL, NULL,\n \t\t\t\t   target, NULL, REF_NO_DEREF,\n \t\t\t\t   logmsg, &err) ||\n-\t    ref_transaction_commit(transaction, &err)) {\n+\t\tref_transaction_prepare(transaction, &err)) {\n \t\tret = error(\"%s\", err.buf);\n+\t\tgoto cleanup;\n \t}\n+\tif (referent)\n+\t\trefs_read_symbolic_ref(refs, ref, referent);\n \n+\tif (ref_transaction_commit(transaction, &err))\n+\t\tret = error(\"%s\", err.buf);\n+\n+cleanup:\n \tstrbuf_release(&err);\n \tif (transaction)\n \t\tref_transaction_free(transaction);\n@@ -2948,4 +2962,3 @@ int ref_update_expects_existing_old_ref(struct ref_update *update)\n \treturn (update->flags & REF_HAVE_OLD) &&\n \t\t(!is_null_oid(&update->old_oid) || update->old_target);\n }\n-\ndiff --git a/refs.h b/refs.h\nindex 108dfc93b3..259191a485 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -573,6 +573,10 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,\n int refs_update_symref(struct ref_store *refs, const char *refname,\n \t\t       const char *target, const char *logmsg);\n \n+int refs_update_symref_extended(struct ref_store *refs, const char *refname,\n+\t\t       const char *target, const char *logmsg,\n+\t\t       struct strbuf *referent);\n+\n enum action_on_err {\n \tUPDATE_REFS_MSG_ON_ERR,\n \tUPDATE_REFS_DIE_ON_ERR,\n-- \n2.47.0.94.g1247fb88fd.dirty\n\n"},{"id":"505938","messageId":"20241023153736.257733-4-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241023153736.257733-1-bence@ferdinandy.com","subject":"[PATCH v12 3/8] remote set-head: refactor for readability","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-23T15:36:37Z","receivedAt":"2024-10-23T15:39:01Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Rename buf and buf2 to something more explanatory.\n\nInstead of calling get_main_ref_store(the_repository) multiple times,\ncall it once and store in a new refs variable. Although this change\nprobably offers some performance benefits, the main purpose is to\nshorten the line lengths of function calls using this variable.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v5: new patch (split from the next patch as a preparatory step)\n    \n    v6: no change\n    \n    v7: - change commit prefix to be more in line with project standards\n    \n    v8: no change\n    \n    v9: - further improve readability by renaming buf, and buf2 consistently\n          with how patch 6 was already done\n    \n    v10: no change\n    \n    v11: no change\n    \n    v12: no change\n\n builtin/remote.c | 23 ++++++++++++-----------\n 1 file changed, 12 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 76670ddd8b..1d68c5b2ba 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1402,8 +1402,9 @@ static int show(int argc, const char **argv, const char *prefix)\n static int set_head(int argc, const char **argv, const char *prefix)\n {\n \tint i, opt_a = 0, opt_d = 0, result = 0;\n-\tstruct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;\n+\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;\n \tchar *head_name = NULL;\n+\tstruct ref_store *refs = get_main_ref_store(the_repository);\n \n \tstruct option options[] = {\n \t\tOPT_BOOL('a', \"auto\", &opt_a,\n@@ -1415,7 +1416,7 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \targc = parse_options(argc, argv, prefix, options,\n \t\t\t     builtin_remote_sethead_usage, 0);\n \tif (argc)\n-\t\tstrbuf_addf(&buf, \"refs/remotes/%s/HEAD\", argv[0]);\n+\t\tstrbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", argv[0]);\n \n \tif (!opt_a && !opt_d && argc == 2) {\n \t\thead_name = xstrdup(argv[1]);\n@@ -1434,25 +1435,25 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\t\thead_name = xstrdup(states.heads.items[0].string);\n \t\tfree_remote_ref_states(&states);\n \t} else if (opt_d && !opt_a && argc == 1) {\n-\t\tif (refs_delete_ref(get_main_ref_store(the_repository), NULL, buf.buf, NULL, REF_NO_DEREF))\n-\t\t\tresult |= error(_(\"Could not delete %s\"), buf.buf);\n+\t\tif (refs_delete_ref(refs, NULL, b_head.buf, NULL, REF_NO_DEREF))\n+\t\t\tresult |= error(_(\"Could not delete %s\"), b_head.buf);\n \t} else\n \t\tusage_with_options(builtin_remote_sethead_usage, options);\n \n \tif (head_name) {\n-\t\tstrbuf_addf(&buf2, \"refs/remotes/%s/%s\", argv[0], head_name);\n+\t\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", argv[0], head_name);\n \t\t/* make sure it's valid */\n-\t\tif (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))\n-\t\t\tresult |= error(_(\"Not a valid ref: %s\"), buf2.buf);\n-\t\telse if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote set-head\"))\n-\t\t\tresult |= error(_(\"Could not setup %s\"), buf.buf);\n+\t\tif (!refs_ref_exists(refs, b_remote_head.buf))\n+\t\t\tresult |= error(_(\"Not a valid ref: %s\"), b_remote_head.buf);\n+\t\telse if (refs_update_symref(refs, b_head.buf, b_remote_head.buf, \"remote set-head\"))\n+\t\t\tresult |= error(_(\"Could not setup %s\"), b_head.buf);\n \t\telse if (opt_a)\n \t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\n \t\tfree(head_name);\n \t}\n \n-\tstrbuf_release(&buf);\n-\tstrbuf_release(&buf2);\n+\tstrbuf_release(&b_head);\n+\tstrbuf_release(&b_remote_head);\n \treturn result;\n }\n \n-- \n2.47.0.94.g1247fb88fd.dirty\n\n"},{"id":"505939","messageId":"20241023153736.257733-5-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241023153736.257733-1-bence@ferdinandy.com","subject":"[PATCH v12 4/8] remote set-head: better output for --auto","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-23T15:36:38Z","receivedAt":"2024-10-23T15:39:05Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Currently, set-head --auto will print a message saying \"remote/HEAD set\nto branch\", which implies something was changed.\n\nChange the output of --auto, so the output actually reflects what was\ndone: a) set a previously unset HEAD, b) change HEAD because remote\nchanged or c) no updates. As a fourth output, if HEAD is changed from\na previous value that was not a remote branch, explicitly call attention\nto this fact.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v1-v2:\n    \n        was RFC in https://lore.kernel.org/git/20240910203835.2288291-1-bence@ferdinandy.com/\n    \n    v3:\n    \n        This patch was originally sent along when I thought set-head was\n        going to be invoked by fetch, but the discussion on the RFC\n        concluded that it should be not. This opened the possibility to make\n        it more explicit.\n    \n        Note: although I feel both things the patch does are really just\n        cosmetic, an argument could be made for breaking it into two, one\n        for the no-op part and one for the --auto print update.\n    \n        Was sent in:\n        https://lore.kernel.org/git/20240915221055.904107-1-bence@ferdinandy.com/\n    \n    v4:\n        - changes are now handled atomically via the ref update transaction\n        - outputs have changed along the lines of Junio's suggestion\n        - minor refactor to set_head for improved legibility\n    \n    v5: - the minor refactor has been split out into its own patch\n    \n    v6: - fixed uninitialized prev_head\n        - fixed case of unusual previous target\n        - fixed a test that would have been actually broken at this patch\n          (the output was only correct with the later update to fetch)\n        - added 4 tests for the 4 output cases\n    \n    v7: - change commit prefix to be more in line with project standards\n        - fixed tests to also work with the reftable backend\n        - renamed report function, fixed style issue with checking buf len\n        - fixed not releasing an strbuf\n    \n    v8: no change\n    \n    v9: - mark output strings in report_set_head_auto as translatable\n        - rename buf_prev to b_local_head for consistency\n        - use ${SQ} in tests instead of '\\''\n    \n    v10: no change\n    \n    v11: no change\n    \n    v12: no change\n\n builtin/remote.c  | 33 +++++++++++++++++++++++++++---\n t/t5505-remote.sh | 51 ++++++++++++++++++++++++++++++++++++++++++++++-\n 2 files changed, 80 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 1d68c5b2ba..108f1271d3 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1399,10 +1399,35 @@ static int show(int argc, const char **argv, const char *prefix)\n \treturn result;\n }\n \n+static void report_set_head_auto(const char *remote, const char *head_name,\n+\t\t\tstruct strbuf *b_local_head) {\n+\tstruct strbuf buf_prefix = STRBUF_INIT;\n+\tconst char *prev_head = NULL;\n+\n+\tstrbuf_addf(&buf_prefix, \"refs/remotes/%s/\", remote);\n+\tskip_prefix(b_local_head->buf, buf_prefix.buf, &prev_head);\n+\n+\tif (prev_head && !strcmp(prev_head, head_name))\n+\t\tprintf(_(\"'%s/HEAD' is unchanged and points to '%s'\\n\"),\n+\t\t\tremote, head_name);\n+\telse if (prev_head)\n+\t\tprintf(_(\"'%s/HEAD' has changed from '%s' and now points to '%s'\\n\"),\n+\t\t\tremote, prev_head, head_name);\n+\telse if (!b_local_head->len)\n+\t\tprintf(_(\"'%s/HEAD' is now created and points to '%s'\\n\"),\n+\t\t\tremote, head_name);\n+\telse\n+\t\tprintf(_(\"'%s/HEAD' used to point to '%s' \"\n+\t\t\t\"(which is not a remote branch), but now points to '%s'\\n\"),\n+\t\t\tremote, b_local_head->buf, head_name);\n+\tstrbuf_release(&buf_prefix);\n+}\n+\n static int set_head(int argc, const char **argv, const char *prefix)\n {\n \tint i, opt_a = 0, opt_d = 0, result = 0;\n-\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;\n+\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,\n+\t\tb_local_head = STRBUF_INIT;\n \tchar *head_name = NULL;\n \tstruct ref_store *refs = get_main_ref_store(the_repository);\n \n@@ -1445,15 +1470,17 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\t/* make sure it's valid */\n \t\tif (!refs_ref_exists(refs, b_remote_head.buf))\n \t\t\tresult |= error(_(\"Not a valid ref: %s\"), b_remote_head.buf);\n-\t\telse if (refs_update_symref(refs, b_head.buf, b_remote_head.buf, \"remote set-head\"))\n+\t\telse if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n+\t\t\t\t\t\"remote set-head\", &b_local_head))\n \t\t\tresult |= error(_(\"Could not setup %s\"), b_head.buf);\n \t\telse if (opt_a)\n-\t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\n+\t\t\treport_set_head_auto(argv[0], head_name, &b_local_head);\n \t\tfree(head_name);\n \t}\n \n \tstrbuf_release(&b_head);\n \tstrbuf_release(&b_remote_head);\n+\tstrbuf_release(&b_local_head);\n \treturn result;\n }\n \ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex 9b50276646..0ea86d51a4 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -432,12 +432,51 @@ test_expect_success 'set-head --auto' '\n \t)\n '\n \n+test_expect_success 'set-head --auto detects creation' '\n+\t(\n+\t\tcd test &&\n+\t\tgit symbolic-ref -d refs/remotes/origin/HEAD &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"${SQ}origin/HEAD${SQ} is now created and points to ${SQ}main${SQ}\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n+test_expect_success 'set-head --auto detects no change' '\n+\t(\n+\t\tcd test &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"${SQ}origin/HEAD${SQ} is unchanged and points to ${SQ}main${SQ}\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n+test_expect_success 'set-head --auto detects change' '\n+\t(\n+\t\tcd test &&\n+\t\tgit symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/ahead &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"${SQ}origin/HEAD${SQ} has changed from ${SQ}ahead${SQ} and now points to ${SQ}main${SQ}\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n+test_expect_success 'set-head --auto detects strange ref' '\n+\t(\n+\t\tcd test &&\n+\t\tgit symbolic-ref refs/remotes/origin/HEAD refs/heads/main &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"${SQ}origin/HEAD${SQ} used to point to ${SQ}refs/heads/main${SQ} (which is not a remote branch), but now points to ${SQ}main${SQ}\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n test_expect_success 'set-head --auto has no problem w/multiple HEADs' '\n \t(\n \t\tcd test &&\n \t\tgit fetch two \"refs/heads/*:refs/remotes/two/*\" &&\n \t\tgit remote set-head --auto two >output 2>&1 &&\n-\t\techo \"two/HEAD set to main\" >expect &&\n+\t\techo \"${SQ}two/HEAD${SQ} is now created and points to ${SQ}main${SQ}\" >expect &&\n \t\ttest_cmp expect output\n \t)\n '\n@@ -456,6 +495,16 @@ test_expect_success 'set-head explicit' '\n \t)\n '\n \n+test_expect_success 'set-head --auto reports change' '\n+\t(\n+\t\tcd test &&\n+\t\tgit remote set-head origin side2 &&\n+\t\tgit remote set-head --auto origin >output 2>&1 &&\n+\t\techo \"${SQ}origin/HEAD${SQ} has changed from ${SQ}side2${SQ} and now points to ${SQ}main${SQ}\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n cat >test/expect <<EOF\n Pruning origin\n URL: $(pwd)/one\n-- \n2.47.0.94.g1247fb88fd.dirty\n\n"},{"id":"505940","messageId":"20241023153736.257733-6-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241023153736.257733-1-bence@ferdinandy.com","subject":"[PATCH v12 5/8] refs: add TRANSACTION_CREATE_EXISTS error","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-23T15:36:39Z","receivedAt":"2024-10-23T15:39:05Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Currently there is only one special error for transaction, for when\nthere is a naming conflict, all other errors are dumped under a generic\nerror. Add a new special error case for when the caller requests the\nreference to be updated only when it does not yet exist and the\nreference actually does exist.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v4: new patch\n    v5: no change\n    v6: no change\n    \n    v7: - change commit prefix to be more in line with project standards\n        - changed error checking to Karthik's suggestion\n    \n    v8: no change\n    \n    v9: - no change\n    \n    v10: no change\n    \n    v11: no change\n    \n    v12: no change\n\n refs.h                  |  4 +++-\n refs/files-backend.c    | 24 ++++++++++++++++--------\n refs/reftable-backend.c |  6 ++++--\n 3 files changed, 23 insertions(+), 11 deletions(-)\n\ndiff --git a/refs.h b/refs.h\nindex 259191a485..a5bc25442b 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -762,8 +762,10 @@ int ref_transaction_verify(struct ref_transaction *transaction,\n \n /* Naming conflict (for example, the ref names A and A/B conflict). */\n #define TRANSACTION_NAME_CONFLICT -1\n+/* When only creation was requested, but the ref already exists. */\n+#define TRANSACTION_CREATE_EXISTS -2\n /* All other errors. */\n-#define TRANSACTION_GENERIC_ERROR -2\n+#define TRANSACTION_GENERIC_ERROR -3\n \n /*\n  * Perform the preparatory stages of committing `transaction`. Acquire\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 0824c0b8a9..e743ec44b5 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2502,14 +2502,18 @@ static int split_symref_update(struct ref_update *update,\n static int check_old_oid(struct ref_update *update, struct object_id *oid,\n \t\t\t struct strbuf *err)\n {\n+\tint ret = TRANSACTION_GENERIC_ERROR;\n+\n \tif (!(update->flags & REF_HAVE_OLD) ||\n \t\t   oideq(oid, &update->old_oid))\n \t\treturn 0;\n \n-\tif (is_null_oid(&update->old_oid))\n+\tif (is_null_oid(&update->old_oid)) {\n \t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n \t\t\t    \"reference already exists\",\n \t\t\t    ref_update_original_update_refname(update));\n+\t\tret = TRANSACTION_CREATE_EXISTS;\n+\t}\n \telse if (is_null_oid(oid))\n \t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n \t\t\t    \"reference is missing but expected %s\",\n@@ -2522,7 +2526,7 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid,\n \t\t\t    oid_to_hex(oid),\n \t\t\t    oid_to_hex(&update->old_oid));\n \n-\treturn -1;\n+\treturn ret;\n }\n \n /*\n@@ -2602,9 +2606,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\t\tgoto out;\n \t\t\t\t}\n-\t\t\t} else if  (check_old_oid(update, &lock->old_oid, err)) {\n-\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n-\t\t\t\tgoto out;\n+\t\t\t} else {\n+\t\t\t\tret = check_old_oid(update, &lock->old_oid, err);\n+\t\t\t\tif  (ret) {\n+\t\t\t\t\tgoto out;\n+\t\t\t\t}\n \t\t\t}\n \t\t} else {\n \t\t\t/*\n@@ -2635,9 +2641,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\t\t    update->old_target);\n \t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\tgoto out;\n-\t\t} else if  (check_old_oid(update, &lock->old_oid, err)) {\n-\t\t\tret = TRANSACTION_GENERIC_ERROR;\n-\t\t\tgoto out;\n+\t\t} else {\n+\t\t\tret = check_old_oid(update, &lock->old_oid, err);\n+\t\t\tif  (ret) {\n+\t\t\t\tgoto out;\n+\t\t\t}\n \t\t}\n \n \t\t/*\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 3c6107c7ce..b3b5ce77dd 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1206,10 +1206,13 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\tgoto done;\n \t\t\t}\n \t\t} else if ((u->flags & REF_HAVE_OLD) && !oideq(&current_oid, &u->old_oid)) {\n-\t\t\tif (is_null_oid(&u->old_oid))\n+\t\t\tret = TRANSACTION_NAME_CONFLICT;\n+\t\t\tif (is_null_oid(&u->old_oid)) {\n \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n \t\t\t\t\t\t   \"reference already exists\"),\n \t\t\t\t\t    ref_update_original_update_refname(u));\n+\t\t\t\tret = TRANSACTION_CREATE_EXISTS;\n+\t\t\t}\n \t\t\telse if (is_null_oid(&current_oid))\n \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n \t\t\t\t\t\t   \"reference is missing but expected %s\"),\n@@ -1221,7 +1224,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\t\t    ref_update_original_update_refname(u),\n \t\t\t\t\t    oid_to_hex(&current_oid),\n \t\t\t\t\t    oid_to_hex(&u->old_oid));\n-\t\t\tret = -1;\n \t\t\tgoto done;\n \t\t}\n \n-- \n2.47.0.94.g1247fb88fd.dirty\n\n"},{"id":"505941","messageId":"20241023153736.257733-7-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241023153736.257733-1-bence@ferdinandy.com","subject":"[PATCH v12 6/8] refs: add create_only option to refs_update_symref_extended","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-23T15:36:40Z","receivedAt":"2024-10-23T15:39:13Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Allow the caller to specify that it only wants to update the symref if\nit does not already exist. Silently ignore the error from the\ntransaction API if the symref already exists.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v4: new patch\n    v5: no change\n    \n    v6: - switched from bool to int for create_only\n        - refactored logic in refs_update_symref with goto as layed out by\n          Junio\n    \n    v7: - change commit prefix to be more in line with project standards\n        - refactored code to accommodate changes in the first patch, but\n          otherwise no change\n    \n    v8: no change\n    \n    v9: - no change (except for following through the\n          refs_update_symref_extended refactoring)\n    \n    v10: no change\n    \n    v11: no change\n    \n    v12: no change\n\n builtin/remote.c |  2 +-\n refs.c           | 32 +++++++++++++++++++++++---------\n refs.h           |  2 +-\n 3 files changed, 25 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 108f1271d3..b1eba75a2b 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1471,7 +1471,7 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\tif (!refs_ref_exists(refs, b_remote_head.buf))\n \t\t\tresult |= error(_(\"Not a valid ref: %s\"), b_remote_head.buf);\n \t\telse if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n-\t\t\t\t\t\"remote set-head\", &b_local_head))\n+\t\t\t\t\t\"remote set-head\", &b_local_head, 0))\n \t\t\tresult |= error(_(\"Could not setup %s\"), b_head.buf);\n \t\telse if (opt_a)\n \t\t\treport_set_head_auto(argv[0], head_name, &b_local_head);\ndiff --git a/refs.c b/refs.c\nindex 24a4172cd2..093ee11ab0 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2116,31 +2116,45 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct\n int refs_update_symref(struct ref_store *refs, const char *ref,\n \t\t       const char *target, const char *logmsg)\n {\n-\treturn refs_update_symref_extended(refs, ref, target, logmsg, NULL);\n+\treturn refs_update_symref_extended(refs, ref, target, logmsg, NULL, 0);\n }\n \n int refs_update_symref_extended(struct ref_store *refs, const char *ref,\n \t\t       const char *target, const char *logmsg,\n-\t\t       struct strbuf *referent)\n+\t\t       struct strbuf *referent, int create_only)\n {\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n-\tint ret = 0;\n+\tint ret = 0, prepret = 0;\n \n \ttransaction = ref_store_transaction_begin(refs, &err);\n-\tif (!transaction ||\n-\t\tref_transaction_update(transaction, ref, NULL, NULL,\n-\t\t\t\t   target, NULL, REF_NO_DEREF,\n-\t\t\t\t   logmsg, &err) ||\n-\t\tref_transaction_prepare(transaction, &err)) {\n+\tif (!transaction) {\n+\terror_return:\n \t\tret = error(\"%s\", err.buf);\n \t\tgoto cleanup;\n \t}\n+\tif (create_only) {\n+\t\tif (ref_transaction_create(transaction, ref, NULL, target,\n+\t\t\t\t\t   REF_NO_DEREF, logmsg, &err))\n+\t\t\tgoto error_return;\n+\t\tprepret = ref_transaction_prepare(transaction, &err);\n+\t\tif (prepret && prepret != TRANSACTION_CREATE_EXISTS)\n+\t\t\tgoto error_return;\n+\t} else {\n+\t\tif (ref_transaction_update(transaction, ref, NULL, NULL,\n+\t\t\t\t\t   target, NULL, REF_NO_DEREF,\n+\t\t\t\t\t   logmsg, &err) ||\n+\t\t\tref_transaction_prepare(transaction, &err))\n+\t\t\tgoto error_return;\n+\t}\n+\n \tif (referent)\n \t\trefs_read_symbolic_ref(refs, ref, referent);\n \n+\tif (prepret == TRANSACTION_CREATE_EXISTS)\n+\t\tgoto cleanup;\n \tif (ref_transaction_commit(transaction, &err))\n-\t\tret = error(\"%s\", err.buf);\n+\t\tgoto error_return;\n \n cleanup:\n \tstrbuf_release(&err);\ndiff --git a/refs.h b/refs.h\nindex a5bc25442b..458582ebcf 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -575,7 +575,7 @@ int refs_update_symref(struct ref_store *refs, const char *refname,\n \n int refs_update_symref_extended(struct ref_store *refs, const char *refname,\n \t\t       const char *target, const char *logmsg,\n-\t\t       struct strbuf *referent);\n+\t\t       struct strbuf *referent, int create_only);\n \n enum action_on_err {\n \tUPDATE_REFS_MSG_ON_ERR,\n-- \n2.47.0.94.g1247fb88fd.dirty\n\n"},{"id":"505942","messageId":"20241023153736.257733-9-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241023153736.257733-1-bence@ferdinandy.com","subject":"[PATCH v12 8/8] fetch set_head: handle mirrored bare repositories","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-23T15:36:42Z","receivedAt":"2024-10-23T15:39:18Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"When adding a remote to bare repository with \"git remote add --mirror\",\nrunning fetch will fail to update HEAD to the remote's HEAD, since it\ndoes not know how to handle bare repositories. On the other hand HEAD\nalready has content, since \"git init --bare\" has already set HEAD to\nwhatever is the default branch set for the user. Unless this - by chance\n- is the same as the remote's HEAD, HEAD will be pointing to a bad\nsymref. Teach set_head to handle bare repositories, by overwriting HEAD\nso it mirrors the remote's HEAD.\n\nNote, that in this case overriding the local HEAD reference is\nnecessary, since HEAD will exist before fetch can be run, but this\nshould not be an issue, since the whole purpose of --mirror is to be an\nexact mirror of the remote, so following any changes to HEAD makes\nsense.\n\nAlso note, that although \"git remote set-head\" also fails when trying to\nupdate the remote's locally tracked HEAD in a mirrored bare repository,\nthe usage of the command does not make much sense after this patch:\nfetch will update the remote HEAD correctly, and setting it manually to\nsomething else is antithetical to the concept of mirroring.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v10: - new patch\n         - handles the issue discovered in\n           https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/T/\n    \n    v11: no change\n    \n    v12: no change\n\n builtin/fetch.c   | 16 ++++++++++++----\n t/t5505-remote.sh | 10 ++++++++++\n 2 files changed, 22 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex cadfd2407a..8af3efd6ac 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1633,14 +1633,22 @@ static int set_head(const struct ref *remote_refs)\n \telse\n \t\thead_name = xstrdup(heads.items[0].string);\n \tif (head_name) {\n-\t\tstrbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", remote);\n-\t\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", remote, head_name);\n+\t\tint is_bare = is_bare_repository();\n+\t\tif (is_bare) {\n+\t\t\tstrbuf_addstr(&b_head, \"HEAD\");\n+\t\t\tstrbuf_addf(&b_remote_head, \"refs/heads/%s\", head_name);\n+\t\t} else {\n+\t\t\tstrbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", remote);\n+\t\t\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", remote, head_name);\n+\t\t}\n \t\t/* make sure it's valid */\n-\t\tif (!refs_ref_exists(refs, b_remote_head.buf))\n+\t\tif (!is_bare && !refs_ref_exists(refs, b_remote_head.buf)) {\n \t\t\tresult = 1;\n+\t\t}\n \t\telse if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n-\t\t\t\t\t\"fetch\", &b_local_head, 1))\n+\t\t\t\t\t\"fetch\", &b_local_head, !is_bare)) {\n \t\t\tresult = 1;\n+\t\t}\n \t\telse\n \t\t\treport_set_head(remote, head_name, &b_local_head);\n \ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex 4990d00209..dfa78f3e8d 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -545,6 +545,16 @@ test_expect_success 'add --mirror && prune' '\n \t)\n '\n \n+test_expect_success 'add --mirror setting HEAD' '\n+\tmkdir headmirror &&\n+\t(\n+\t\tcd headmirror &&\n+\t\tgit init --bare -b notmain &&\n+\t\tgit remote add --mirror -f origin ../one &&\n+\t\ttest \"$(git symbolic-ref HEAD)\" = \"refs/heads/main\"\n+\t)\n+'\n+\n test_expect_success 'add --mirror=fetch' '\n \tmkdir mirror-fetch &&\n \tgit init -b main mirror-fetch/parent &&\n-- \n2.47.0.94.g1247fb88fd.dirty\n\n"},{"id":"505943","messageId":"20241023153736.257733-8-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241023153736.257733-1-bence@ferdinandy.com","subject":"[PATCH v12 7/8] fetch: set remote/HEAD if it does not exist","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-23T15:36:41Z","receivedAt":"2024-10-23T15:39:25Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"If the user has remote/HEAD set already and it looks like it has changed\non the server, then print a message, otherwise set it if we can.\nSilently pass if the user already has the same remote/HEAD set as\nreported by the server or if we encounter any errors along the way.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v3: - does not rely on remote set-head anymore so it only authenticates\n        once\n        - uses the new REF_CREATE_ONLY to atomically check if the ref exists\n          and only write it if it doesn't\n        - in all other cases the maximum it does is print a warning\n    \n    v4: - instead of the discarded REF_CREATE_ONLY, it uses the existing,\n          but updated transaction api to request a silent create only\n        - it now uses the atomic before_target to determine reporting\n        - refactored for legibility\n    \n    v5: - instead of printing a not too useful message, it now fails\n          silently, this in line with the objective to only set up\n          remote/HEAD automatically if the right thing is trivial, for\n          everything else there is remote set-head\n        - fixed all failing tests\n        - added two new tests, one for checking if remote/HEAD is set to the\n          correct one, and one to test that we do not override remote/HEAD\n          if it has changed on the server from what we have locally\n    \n    v6: - fixed style issues and unintended extra empty line\n        - updated function call with bool to int from previous patch's\n          change\n        - removed calls to error(...) inherited from builtin/remote.c so we\n          actually fail silently\n        - set the test for remote set-head --auto to the correct value here,\n          which was previously erronously set in the remote set-head patch\n    \n    v7: - no change\n    \n    v8: - changed logmsg in call to refs_update_symref from \"remote\n          set-head\" to \"fetch\"\n    \n    v9: - follow through with refs_update_symref_extended\n        - fix test errors uncovered by the new patch\n    \n    v10: no change\n    \n    v11: fixed some memory leaks\n    \n    v12: no change\n\n builtin/fetch.c                  |  86 ++++++++++++\n t/t4207-log-decoration-colors.sh |   3 +-\n t/t5505-remote.sh                |  21 ++-\n t/t5510-fetch.sh                 | 229 ++++++++++++++++---------------\n t/t5512-ls-remote.sh             |   2 +\n t/t5514-fetch-multiple.sh        |  17 ++-\n t/t5516-fetch-push.sh            |   3 +-\n t/t5527-fetch-odd-refs.sh        |   3 +-\n t/t7900-maintenance.sh           |   3 +-\n t/t9210-scalar.sh                |   5 +-\n t/t9211-scalar-clone.sh          |   6 +-\n t/t9902-completion.sh            |  65 +++++++++\n 12 files changed, 319 insertions(+), 124 deletions(-)\n\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex d9027e4dc9..cadfd2407a 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1578,6 +1578,84 @@ static int backfill_tags(struct display_state *display_state,\n \treturn retcode;\n }\n \n+static void report_set_head(const char *remote, const char *head_name,\n+\t\t\tstruct strbuf *buf_prev) {\n+\tstruct strbuf buf_prefix = STRBUF_INIT;\n+\tconst char *prev_head = NULL;\n+\n+\tstrbuf_addf(&buf_prefix, \"refs/remotes/%s/\", remote);\n+\tskip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);\n+\n+\tif (prev_head && strcmp(prev_head, head_name)) {\n+\t\tprintf(\"'HEAD' at '%s' has changed from '%s' to '%s'\\n\",\n+\t\t\tremote, prev_head, head_name);\n+\t\tprintf(\"Run 'git remote set-head %s %s' to follow the change.\\n\",\n+\t\t\tremote, head_name);\n+\t}\n+\tstrbuf_release(&buf_prefix);\n+}\n+\n+static const char *strip_refshead(const char *name){\n+\tskip_prefix(name, \"refs/heads/\", &name);\n+\treturn name;\n+}\n+\n+static int set_head(const struct ref *remote_refs)\n+{\n+\tint result = 0;\n+\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,\n+\t\tb_local_head = STRBUF_INIT;\n+\tconst char *remote = gtransport->remote->name;\n+\tchar *head_name = NULL;\n+\tstruct ref *ref, *matches;\n+\tstruct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;\n+\tstruct refspec_item refspec = {\n+\t\t.force = 0,\n+\t\t.pattern = 1,\n+\t\t.src = (char *) \"refs/heads/*\",\n+\t\t.dst = (char *) \"refs/heads/*\",\n+\t};\n+\tstruct string_list heads = STRING_LIST_INIT_DUP;\n+\tstruct ref_store *refs = get_main_ref_store(the_repository);\n+\n+\tget_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);\n+\tmatches = guess_remote_head(find_ref_by_name(remote_refs, \"HEAD\"),\n+\t\t\t\t    fetch_map, 1);\n+\tfor (ref = matches; ref; ref = ref->next) {\n+\t\tstring_list_append(&heads, strip_refshead(ref->name));\n+\t}\n+\n+\n+\tif (!heads.nr)\n+\t\tresult = 1;\n+\telse if (heads.nr > 1)\n+\t\tresult = 1;\n+\telse\n+\t\thead_name = xstrdup(heads.items[0].string);\n+\tif (head_name) {\n+\t\tstrbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", remote);\n+\t\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", remote, head_name);\n+\t\t/* make sure it's valid */\n+\t\tif (!refs_ref_exists(refs, b_remote_head.buf))\n+\t\t\tresult = 1;\n+\t\telse if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n+\t\t\t\t\t\"fetch\", &b_local_head, 1))\n+\t\t\tresult = 1;\n+\t\telse\n+\t\t\treport_set_head(remote, head_name, &b_local_head);\n+\n+\t\tfree(head_name);\n+\t}\n+\n+\tfree_refs(fetch_map);\n+\tfree_refs(matches);\n+\tstring_list_clear(&heads, 0);\n+\tstrbuf_release(&b_head);\n+\tstrbuf_release(&b_local_head);\n+\tstrbuf_release(&b_remote_head);\n+\treturn result;\n+}\n+\n static int do_fetch(struct transport *transport,\n \t\t    struct refspec *rs,\n \t\t    const struct fetch_config *config)\n@@ -1647,6 +1725,8 @@ static int do_fetch(struct transport *transport,\n \t\t\t\t    \"refs/tags/\");\n \t}\n \n+\tstrvec_push(&transport_ls_refs_options.ref_prefixes, \"HEAD\");\n+\n \tif (must_list_refs) {\n \t\ttrace2_region_enter(\"fetch\", \"remote_refs\", the_repository);\n \t\tremote_refs = transport_get_remote_refs(transport,\n@@ -1791,6 +1871,12 @@ static int do_fetch(struct transport *transport,\n \t\t\t\t  \"you need to specify exactly one branch with the --set-upstream option\"));\n \t\t}\n \t}\n+\tif (set_head(remote_refs))\n+\t\t;\n+\t\t/*\n+\t\t * Way too many cases where this can go wrong\n+\t\t * so let's just fail silently for now.\n+\t\t */\n \n cleanup:\n \tif (retcode) {\ndiff --git a/t/t4207-log-decoration-colors.sh b/t/t4207-log-decoration-colors.sh\nindex 73ea9e5155..d55d22cb2f 100755\n--- a/t/t4207-log-decoration-colors.sh\n+++ b/t/t4207-log-decoration-colors.sh\n@@ -59,7 +59,8 @@ ${c_reset}${c_tag}tag: ${c_reset}${c_tag}v1.0${c_reset}${c_commit}, \\\n ${c_reset}${c_tag}tag: ${c_reset}${c_tag}B${c_reset}${c_commit})${c_reset} B\n ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\n ${c_tag}tag: ${c_reset}${c_tag}A1${c_reset}${c_commit}, \\\n-${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit})${c_reset} A1\n+${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit}, \\\n+${c_reset}${c_remoteBranch}other/HEAD${c_reset}${c_commit})${c_reset} A1\n \t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\n ${c_stash}refs/stash${c_reset}${c_commit})${c_reset} On main: Changes to A.t\n \t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex 0ea86d51a4..4990d00209 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -74,7 +74,7 @@ test_expect_success 'add another remote' '\n \t\tcd test &&\n \t\tgit remote add -f second ../two &&\n \t\ttokens_match \"origin second\" \"$(git remote)\" &&\n-\t\tcheck_tracking_branch second main side another &&\n+\t\tcheck_tracking_branch second main side another HEAD &&\n \t\tgit for-each-ref \"--format=%(refname)\" refs/remotes |\n \t\tsed -e \"/^refs\\/remotes\\/origin\\//d\" \\\n \t\t    -e \"/^refs\\/remotes\\/second\\//d\" >actual &&\n@@ -476,7 +476,7 @@ test_expect_success 'set-head --auto has no problem w/multiple HEADs' '\n \t\tcd test &&\n \t\tgit fetch two \"refs/heads/*:refs/remotes/two/*\" &&\n \t\tgit remote set-head --auto two >output 2>&1 &&\n-\t\techo \"${SQ}two/HEAD${SQ} is now created and points to ${SQ}main${SQ}\" >expect &&\n+\t\techo \"${SQ}two/HEAD${SQ} is unchanged and points to ${SQ}main${SQ}\" >expect &&\n \t\ttest_cmp expect output\n \t)\n '\n@@ -764,8 +764,10 @@ test_expect_success 'reject --no-no-tags' '\n '\n \n cat >one/expect <<\\EOF\n+  apis/HEAD -> apis/main\n   apis/main\n   apis/side\n+  drosophila/HEAD -> drosophila/main\n   drosophila/another\n   drosophila/main\n   drosophila/side\n@@ -783,11 +785,14 @@ test_expect_success 'update' '\n '\n \n cat >one/expect <<\\EOF\n+  drosophila/HEAD -> drosophila/main\n   drosophila/another\n   drosophila/main\n   drosophila/side\n+  manduca/HEAD -> manduca/main\n   manduca/main\n   manduca/side\n+  megaloprepus/HEAD -> megaloprepus/main\n   megaloprepus/main\n   megaloprepus/side\n EOF\n@@ -795,7 +800,7 @@ EOF\n test_expect_success 'update with arguments' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\n@@ -827,10 +832,13 @@ test_expect_success 'update --prune' '\n '\n \n cat >one/expect <<-\\EOF\n+  apis/HEAD -> apis/main\n   apis/main\n   apis/side\n+  manduca/HEAD -> manduca/main\n   manduca/main\n   manduca/side\n+  megaloprepus/HEAD -> megaloprepus/main\n   megaloprepus/main\n   megaloprepus/side\n EOF\n@@ -838,7 +846,7 @@ EOF\n test_expect_success 'update default' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\n@@ -850,6 +858,7 @@ test_expect_success 'update default' '\n '\n \n cat >one/expect <<\\EOF\n+  drosophila/HEAD -> drosophila/main\n   drosophila/another\n   drosophila/main\n   drosophila/side\n@@ -858,7 +867,7 @@ EOF\n test_expect_success 'update default (overridden, with funny whitespace)' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\n@@ -872,7 +881,7 @@ test_expect_success 'update default (overridden, with funny whitespace)' '\n test_expect_success 'update (with remotes.default defined)' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex 0890b9f61c..dfc8d748ba 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -75,6 +75,30 @@ test_expect_success \"fetch test for-merge\" '\n \tcut -f -2 .git/FETCH_HEAD >actual &&\n \ttest_cmp expected actual'\n \n+test_expect_success \"fetch test remote HEAD\" '\n+\tcd \"$D\" &&\n+\tcd two &&\n+\tgit fetch &&\n+\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\tgit rev-parse --verify refs/remotes/origin/main &&\n+\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\tbranch=$(git rev-parse refs/remotes/origin/main) &&\n+\ttest \"z$head\" = \"z$branch\"'\n+\n+test_expect_success \"fetch test remote HEAD change\" '\n+\tcd \"$D\" &&\n+\tcd two &&\n+\tgit switch -c other &&\n+\tgit push -u origin other &&\n+\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\tgit rev-parse --verify refs/remotes/origin/main &&\n+\tgit rev-parse --verify refs/remotes/origin/other &&\n+\tgit remote set-head origin other &&\n+\tgit fetch &&\n+\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\tbranch=$(git rev-parse refs/remotes/origin/other) &&\n+\ttest \"z$head\" = \"z$branch\"'\n+\n test_expect_success 'fetch --prune on its own works as expected' '\n \tcd \"$D\" &&\n \tgit clone . prune &&\n@@ -478,7 +502,6 @@ test_expect_success 'unbundle 1' '\n \ttest_must_fail git fetch \"$D/bundle1\" main:main\n '\n \n-\n test_expect_success 'bundle 1 has only 3 files ' '\n \tcd \"$D\" &&\n \ttest_bundle_object_count bundle1 3\n@@ -819,7 +842,7 @@ test_expect_success 'fetch from multiple configured URLs in single remote' '\n \n # configured prune tests\n \n-set_config_tristate () {\n+set_config_tristate() {\n \t# var=$1 val=$2\n \tcase \"$2\" in\n \tunset)\n@@ -833,12 +856,12 @@ set_config_tristate () {\n \tesac\n }\n \n-test_configured_prune () {\n+test_configured_prune() {\n \ttest_configured_prune_type \"$@\" \"name\"\n \ttest_configured_prune_type \"$@\" \"link\"\n }\n \n-test_configured_prune_type () {\n+test_configured_prune_type() {\n \tfetch_prune=$1\n \tremote_origin_prune=$2\n \tfetch_prune_tags=$3\n@@ -848,8 +871,7 @@ test_configured_prune_type () {\n \tcmdline=$7\n \tmode=$8\n \n-\tif test -z \"$cmdline_setup\"\n-\tthen\n+\tif test -z \"$cmdline_setup\"; then\n \t\ttest_expect_success 'setup cmdline_setup variable for subsequent test' '\n \t\t\tremote_url=\"file://$(git -C one config remote.origin.url)\" &&\n \t\t\tremote_fetch=\"$(git -C one config remote.origin.fetch)\" &&\n@@ -857,12 +879,10 @@ test_configured_prune_type () {\n \t\t'\n \tfi\n \n-\tif test \"$mode\" = 'link'\n-\tthen\n+\tif test \"$mode\" = 'link'; then\n \t\tnew_cmdline=\"\"\n \n-\t\tif test \"$cmdline\" = \"\"\n-\t\tthen\n+\t\tif test \"$cmdline\" = \"\"; then\n \t\t\tnew_cmdline=$cmdline_setup\n \t\telse\n \t\t\tnew_cmdline=$(perl -e '\n@@ -873,10 +893,8 @@ test_configured_prune_type () {\n \t\tfi\n \n \t\tif test \"$fetch_prune_tags\" = 'true' ||\n-\t\t   test \"$remote_origin_prune_tags\" = 'true'\n-\t\tthen\n-\t\t\tif ! printf '%s' \"$cmdline\\n\" | grep -q refs/remotes/origin/\n-\t\t\tthen\n+\t\t\ttest \"$remote_origin_prune_tags\" = 'true'; then\n+\t\t\tif ! printf '%s' \"$cmdline\\n\" | grep -q refs/remotes/origin/; then\n \t\t\t\tnew_cmdline=\"$new_cmdline refs/tags/*:refs/tags/*\"\n \t\t\tfi\n \t\tfi\n@@ -946,100 +964,100 @@ test_configured_prune_type () {\n # $7 git-fetch $cmdline:\n #\n #                     $1    $2    $3    $4    $5     $6     $7\n-test_configured_prune unset unset unset unset kept   kept   \"\"\n-test_configured_prune unset unset unset unset kept   kept   \"--no-prune\"\n-test_configured_prune unset unset unset unset pruned kept   \"--prune\"\n-test_configured_prune unset unset unset unset kept   pruned \\\n+test_configured_prune unset unset unset unset kept kept \"\"\n+test_configured_prune unset unset unset unset kept kept \"--no-prune\"\n+test_configured_prune unset unset unset unset pruned kept \"--prune\"\n+test_configured_prune unset unset unset unset kept pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/*\"\n test_configured_prune unset unset unset unset pruned pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*\"\n \n-test_configured_prune false unset unset unset kept   kept   \"\"\n-test_configured_prune false unset unset unset kept   kept   \"--no-prune\"\n-test_configured_prune false unset unset unset pruned kept   \"--prune\"\n+test_configured_prune false unset unset unset kept kept \"\"\n+test_configured_prune false unset unset unset kept kept \"--no-prune\"\n+test_configured_prune false unset unset unset pruned kept \"--prune\"\n \n-test_configured_prune true  unset unset unset pruned kept   \"\"\n-test_configured_prune true  unset unset unset pruned kept   \"--prune\"\n-test_configured_prune true  unset unset unset kept   kept   \"--no-prune\"\n+test_configured_prune true unset unset unset pruned kept \"\"\n+test_configured_prune true unset unset unset pruned kept \"--prune\"\n+test_configured_prune true unset unset unset kept kept \"--no-prune\"\n \n-test_configured_prune unset false unset unset kept   kept   \"\"\n-test_configured_prune unset false unset unset kept   kept   \"--no-prune\"\n-test_configured_prune unset false unset unset pruned kept   \"--prune\"\n+test_configured_prune unset false unset unset kept kept \"\"\n+test_configured_prune unset false unset unset kept kept \"--no-prune\"\n+test_configured_prune unset false unset unset pruned kept \"--prune\"\n \n-test_configured_prune false false unset unset kept   kept   \"\"\n-test_configured_prune false false unset unset kept   kept   \"--no-prune\"\n-test_configured_prune false false unset unset pruned kept   \"--prune\"\n-test_configured_prune false false unset unset kept   pruned \\\n+test_configured_prune false false unset unset kept kept \"\"\n+test_configured_prune false false unset unset kept kept \"--no-prune\"\n+test_configured_prune false false unset unset pruned kept \"--prune\"\n+test_configured_prune false false unset unset kept pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/*\"\n test_configured_prune false false unset unset pruned pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*\"\n \n-test_configured_prune true  false unset unset kept   kept   \"\"\n-test_configured_prune true  false unset unset pruned kept   \"--prune\"\n-test_configured_prune true  false unset unset kept   kept   \"--no-prune\"\n+test_configured_prune true false unset unset kept kept \"\"\n+test_configured_prune true false unset unset pruned kept \"--prune\"\n+test_configured_prune true false unset unset kept kept \"--no-prune\"\n \n-test_configured_prune unset true  unset unset pruned kept   \"\"\n-test_configured_prune unset true  unset unset kept   kept   \"--no-prune\"\n-test_configured_prune unset true  unset unset pruned kept   \"--prune\"\n+test_configured_prune unset true unset unset pruned kept \"\"\n+test_configured_prune unset true unset unset kept kept \"--no-prune\"\n+test_configured_prune unset true unset unset pruned kept \"--prune\"\n \n-test_configured_prune false true  unset unset pruned kept   \"\"\n-test_configured_prune false true  unset unset kept   kept   \"--no-prune\"\n-test_configured_prune false true  unset unset pruned kept   \"--prune\"\n+test_configured_prune false true unset unset pruned kept \"\"\n+test_configured_prune false true unset unset kept kept \"--no-prune\"\n+test_configured_prune false true unset unset pruned kept \"--prune\"\n \n-test_configured_prune true  true  unset unset pruned kept   \"\"\n-test_configured_prune true  true  unset unset pruned kept   \"--prune\"\n-test_configured_prune true  true  unset unset kept   kept   \"--no-prune\"\n-test_configured_prune true  true  unset unset kept   pruned \\\n+test_configured_prune true true unset unset pruned kept \"\"\n+test_configured_prune true true unset unset pruned kept \"--prune\"\n+test_configured_prune true true unset unset kept kept \"--no-prune\"\n+test_configured_prune true true unset unset kept pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/*\"\n-test_configured_prune true  true  unset unset pruned pruned \\\n+test_configured_prune true true unset unset pruned pruned \\\n \t\"--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*\"\n \n # --prune-tags on its own does nothing, needs --prune as well, same\n # for fetch.pruneTags without fetch.prune\n-test_configured_prune unset unset unset unset kept kept     \"--prune-tags\"\n-test_configured_prune unset unset true unset  kept kept     \"\"\n-test_configured_prune unset unset unset true  kept kept     \"\"\n+test_configured_prune unset unset unset unset kept kept \"--prune-tags\"\n+test_configured_prune unset unset true unset kept kept \"\"\n+test_configured_prune unset unset unset true kept kept \"\"\n \n # These will prune the tags\n test_configured_prune unset unset unset unset pruned pruned \"--prune --prune-tags\"\n-test_configured_prune true  unset true  unset pruned pruned \"\"\n-test_configured_prune unset true  unset true  pruned pruned \"\"\n+test_configured_prune true unset true unset pruned pruned \"\"\n+test_configured_prune unset true unset true pruned pruned \"\"\n \n # remote.<name>.pruneTags overrides fetch.pruneTags, just like\n # remote.<name>.prune overrides fetch.prune if set.\n-test_configured_prune true  unset true unset pruned pruned  \"\"\n-test_configured_prune false true  false true  pruned pruned \"\"\n-test_configured_prune true  false true  false kept   kept   \"\"\n+test_configured_prune true unset true unset pruned pruned \"\"\n+test_configured_prune false true false true pruned pruned \"\"\n+test_configured_prune true false true false kept kept \"\"\n \n # When --prune-tags is supplied it's ignored if an explicit refspec is\n # given, same for the configuration options.\n test_configured_prune unset unset unset unset pruned kept \\\n \t\"--prune --prune-tags origin +refs/heads/*:refs/remotes/origin/*\"\n-test_configured_prune unset unset true  unset pruned kept \\\n+test_configured_prune unset unset true unset pruned kept \\\n \t\"--prune origin +refs/heads/*:refs/remotes/origin/*\"\n-test_configured_prune unset unset unset true pruned  kept \\\n+test_configured_prune unset unset unset true pruned kept \\\n \t\"--prune origin +refs/heads/*:refs/remotes/origin/*\"\n \n # Pruning that also takes place if a file:// url replaces a named\n # remote. However, because there's no implicit\n # +refs/heads/*:refs/remotes/origin/* refspec and supplying it on the\n # command-line negates --prune-tags, the branches will not be pruned.\n-test_configured_prune_type unset unset unset unset kept   kept   \"origin --prune-tags\" \"name\"\n-test_configured_prune_type unset unset unset unset kept   kept   \"origin --prune-tags\" \"link\"\n+test_configured_prune_type unset unset unset unset kept kept \"origin --prune-tags\" \"name\"\n+test_configured_prune_type unset unset unset unset kept kept \"origin --prune-tags\" \"link\"\n test_configured_prune_type unset unset unset unset pruned pruned \"origin --prune --prune-tags\" \"name\"\n-test_configured_prune_type unset unset unset unset kept   pruned \"origin --prune --prune-tags\" \"link\"\n+test_configured_prune_type unset unset unset unset kept pruned \"origin --prune --prune-tags\" \"link\"\n test_configured_prune_type unset unset unset unset pruned pruned \"--prune --prune-tags origin\" \"name\"\n-test_configured_prune_type unset unset unset unset kept   pruned \"--prune --prune-tags origin\" \"link\"\n-test_configured_prune_type unset unset true  unset pruned pruned \"--prune origin\" \"name\"\n-test_configured_prune_type unset unset true  unset kept   pruned \"--prune origin\" \"link\"\n-test_configured_prune_type unset unset unset true  pruned pruned \"--prune origin\" \"name\"\n-test_configured_prune_type unset unset unset true  kept   pruned \"--prune origin\" \"link\"\n-test_configured_prune_type true  unset true  unset pruned pruned \"origin\" \"name\"\n-test_configured_prune_type true  unset true  unset kept   pruned \"origin\" \"link\"\n-test_configured_prune_type unset  true true  unset pruned pruned \"origin\" \"name\"\n-test_configured_prune_type unset  true true  unset kept   pruned \"origin\" \"link\"\n-test_configured_prune_type unset  true unset true  pruned pruned \"origin\" \"name\"\n-test_configured_prune_type unset  true unset true  kept   pruned \"origin\" \"link\"\n+test_configured_prune_type unset unset unset unset kept pruned \"--prune --prune-tags origin\" \"link\"\n+test_configured_prune_type unset unset true unset pruned pruned \"--prune origin\" \"name\"\n+test_configured_prune_type unset unset true unset kept pruned \"--prune origin\" \"link\"\n+test_configured_prune_type unset unset unset true pruned pruned \"--prune origin\" \"name\"\n+test_configured_prune_type unset unset unset true kept pruned \"--prune origin\" \"link\"\n+test_configured_prune_type true unset true unset pruned pruned \"origin\" \"name\"\n+test_configured_prune_type true unset true unset kept pruned \"origin\" \"link\"\n+test_configured_prune_type unset true true unset pruned pruned \"origin\" \"name\"\n+test_configured_prune_type unset true true unset kept pruned \"origin\" \"link\"\n+test_configured_prune_type unset true unset true pruned pruned \"origin\" \"name\"\n+test_configured_prune_type unset true unset true kept pruned \"origin\" \"link\"\n \n # When all remote.origin.fetch settings are deleted a --prune\n # --prune-tags still implicitly supplies refs/tags/*:refs/tags/* so\n@@ -1137,8 +1155,7 @@ test_expect_success 'fetching with auto-gc does not lock up' '\n \t)\n '\n \n-for section in fetch transfer\n-do\n+for section in fetch transfer; do\n \ttest_expect_success \"$section.hideRefs affects connectivity check\" '\n \t\tGIT_TRACE=\"$PWD\"/trace git -c $section.hideRefs=refs -c \\\n \t\t\t$section.hideRefs=\"!refs/tags/\" fetch &&\n@@ -1157,21 +1174,23 @@ test_expect_success 'prepare source branch' '\n \ttest 3 -le $(wc -l <actual)\n '\n \n-validate_store_type () {\n+validate_store_type() {\n \tgit -C dest count-objects -v >actual &&\n-\tcase \"$store_type\" in\n-\tpacked)\n-\t\tgrep \"^count: 0$\" actual ;;\n-\tloose)\n-\t\tgrep \"^packs: 0$\" actual ;;\n-\tesac || {\n+\t\tcase \"$store_type\" in\n+\t\tpacked)\n+\t\t\tgrep \"^count: 0$\" actual\n+\t\t\t;;\n+\t\tloose)\n+\t\t\tgrep \"^packs: 0$\" actual\n+\t\t\t;;\n+\t\tesac || {\n \t\techo \"store_type is $store_type\"\n \t\tcat actual\n \t\tfalse\n \t}\n }\n \n-test_unpack_limit () {\n+test_unpack_limit() {\n \tstore_type=$1\n \n \tcase \"$store_type\" in\n@@ -1192,43 +1211,39 @@ test_unpack_limit () {\n test_unpack_limit packed\n test_unpack_limit loose\n \n-setup_negotiation_tip () {\n+setup_negotiation_tip() {\n \tSERVER=\"$1\"\n \tURL=\"$2\"\n \tUSE_PROTOCOL_V2=\"$3\"\n \n \trm -rf \"$SERVER\" client trace &&\n-\tgit init -b main \"$SERVER\" &&\n-\ttest_commit -C \"$SERVER\" alpha_1 &&\n-\ttest_commit -C \"$SERVER\" alpha_2 &&\n-\tgit -C \"$SERVER\" checkout --orphan beta &&\n-\ttest_commit -C \"$SERVER\" beta_1 &&\n-\ttest_commit -C \"$SERVER\" beta_2 &&\n-\n-\tgit clone \"$URL\" client &&\n-\n-\tif test \"$USE_PROTOCOL_V2\" -eq 1\n-\tthen\n-\t\tgit -C \"$SERVER\" config protocol.version 2 &&\n-\t\tgit -C client config protocol.version 2\n-\tfi &&\n-\n-\ttest_commit -C \"$SERVER\" beta_s &&\n-\tgit -C \"$SERVER\" checkout main &&\n-\ttest_commit -C \"$SERVER\" alpha_s &&\n-\tgit -C \"$SERVER\" tag -d alpha_1 alpha_2 beta_1 beta_2\n+\t\tgit init -b main \"$SERVER\" &&\n+\t\ttest_commit -C \"$SERVER\" alpha_1 &&\n+\t\ttest_commit -C \"$SERVER\" alpha_2 &&\n+\t\tgit -C \"$SERVER\" checkout --orphan beta &&\n+\t\ttest_commit -C \"$SERVER\" beta_1 &&\n+\t\ttest_commit -C \"$SERVER\" beta_2 &&\n+\t\tgit clone \"$URL\" client &&\n+\t\tif test \"$USE_PROTOCOL_V2\" -eq 1; then\n+\t\t\tgit -C \"$SERVER\" config protocol.version 2 &&\n+\t\t\t\tgit -C client config protocol.version 2\n+\t\tfi &&\n+\t\ttest_commit -C \"$SERVER\" beta_s &&\n+\t\tgit -C \"$SERVER\" checkout main &&\n+\t\ttest_commit -C \"$SERVER\" alpha_s &&\n+\t\tgit -C \"$SERVER\" tag -d alpha_1 alpha_2 beta_1 beta_2\n }\n \n-check_negotiation_tip () {\n+check_negotiation_tip() {\n \t# Ensure that {alpha,beta}_1 are sent as \"have\", but not {alpha_beta}_2\n \tALPHA_1=$(git -C client rev-parse alpha_1) &&\n-\tgrep \"fetch> have $ALPHA_1\" trace &&\n-\tBETA_1=$(git -C client rev-parse beta_1) &&\n-\tgrep \"fetch> have $BETA_1\" trace &&\n-\tALPHA_2=$(git -C client rev-parse alpha_2) &&\n-\t! grep \"fetch> have $ALPHA_2\" trace &&\n-\tBETA_2=$(git -C client rev-parse beta_2) &&\n-\t! grep \"fetch> have $BETA_2\" trace\n+\t\tgrep \"fetch> have $ALPHA_1\" trace &&\n+\t\tBETA_1=$(git -C client rev-parse beta_1) &&\n+\t\tgrep \"fetch> have $BETA_1\" trace &&\n+\t\tALPHA_2=$(git -C client rev-parse alpha_2) &&\n+\t\t! grep \"fetch> have $ALPHA_2\" trace &&\n+\t\tBETA_2=$(git -C client rev-parse beta_2) &&\n+\t\t! grep \"fetch> have $BETA_2\" trace\n }\n \n test_expect_success '--negotiation-tip limits \"have\" lines sent' '\ndiff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh\nindex 64b3491e4e..1b3865e154 100755\n--- a/t/t5512-ls-remote.sh\n+++ b/t/t5512-ls-remote.sh\n@@ -293,6 +293,8 @@ test_expect_success 'ls-remote with filtered symref (refname)' '\n \tcat >expect <<-EOF &&\n \tref: refs/heads/main\tHEAD\n \t$rev\tHEAD\n+\tref: refs/remotes/origin/main\trefs/remotes/origin/HEAD\n+\t$rev\trefs/remotes/origin/HEAD\n \tEOF\n \tgit ls-remote --symref . HEAD >actual &&\n \ttest_cmp expect actual\ndiff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh\nindex 579872c258..e3482b27b2 100755\n--- a/t/t5514-fetch-multiple.sh\n+++ b/t/t5514-fetch-multiple.sh\n@@ -45,14 +45,17 @@ test_expect_success setup '\n '\n \n cat > test/expect << EOF\n+  one/HEAD -> one/main\n   one/main\n   one/side\n   origin/HEAD -> origin/main\n   origin/main\n   origin/side\n+  three/HEAD -> three/main\n   three/another\n   three/main\n   three/side\n+  two/HEAD -> two/main\n   two/another\n   two/main\n   two/side\n@@ -97,6 +100,7 @@ cat > expect << EOF\n   origin/HEAD -> origin/main\n   origin/main\n   origin/side\n+  three/HEAD -> three/main\n   three/another\n   three/main\n   three/side\n@@ -112,8 +116,10 @@ test_expect_success 'git fetch --multiple (but only one remote)' '\n '\n \n cat > expect << EOF\n+  one/HEAD -> one/main\n   one/main\n   one/side\n+  two/HEAD -> two/main\n   two/another\n   two/main\n   two/side\n@@ -141,7 +147,7 @@ test_expect_success 'git fetch --multiple (bad remote names)' '\n \n test_expect_success 'git fetch --all (skipFetchAll)' '\n \t(cd test4 &&\n-\t for b in $(git branch -r)\n+\t for b in $(git branch -r | grep -v HEAD)\n \t do\n \t\tgit branch -r -d $b || exit 1\n \t done &&\n@@ -153,11 +159,14 @@ test_expect_success 'git fetch --all (skipFetchAll)' '\n '\n \n cat > expect << EOF\n+  one/HEAD -> one/main\n   one/main\n   one/side\n+  three/HEAD -> three/main\n   three/another\n   three/main\n   three/side\n+  two/HEAD -> two/main\n   two/another\n   two/main\n   two/side\n@@ -165,7 +174,7 @@ EOF\n \n test_expect_success 'git fetch --multiple (ignoring skipFetchAll)' '\n \t(cd test4 &&\n-\t for b in $(git branch -r)\n+\t for b in $(git branch -r | grep -v HEAD)\n \t do\n \t\tgit branch -r -d $b || exit 1\n \t done &&\n@@ -221,14 +230,17 @@ test_expect_success 'git fetch --multiple --jobs=0 picks a default' '\n \n create_fetch_all_expect () {\n \tcat >expect <<-\\EOF\n+\t  one/HEAD -> one/main\n \t  one/main\n \t  one/side\n \t  origin/HEAD -> origin/main\n \t  origin/main\n \t  origin/side\n+\t  three/HEAD -> three/main\n \t  three/another\n \t  three/main\n \t  three/side\n+\t  two/HEAD -> two/main\n \t  two/another\n \t  two/main\n \t  two/side\n@@ -265,6 +277,7 @@ test_expect_success 'git fetch (fetch all remotes with fetch.all = true)' '\n \n create_fetch_one_expect () {\n \tcat >expect <<-\\EOF\n+\t  one/HEAD -> one/main\n \t  one/main\n \t  one/side\n \t  origin/HEAD -> origin/main\ndiff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\nindex 331778bd42..5a051aa0c7 100755\n--- a/t/t5516-fetch-push.sh\n+++ b/t/t5516-fetch-push.sh\n@@ -1395,7 +1395,8 @@ test_expect_success 'fetch follows tags by default' '\n \t\tgit tag -m \"annotated\" tag &&\n \t\tgit for-each-ref >tmp1 &&\n \t\tsed -n \"p; s|refs/heads/main$|refs/remotes/origin/main|p\" tmp1 |\n-\t\tsort -k 3 >../expect\n+\t\tsed -n \"p; s|refs/heads/main$|refs/remotes/origin/HEAD|p\"  |\n+\t\tsort -k 4 >../expect\n \t) &&\n \ttest_when_finished \"rm -rf dst\" &&\n \tgit init dst &&\ndiff --git a/t/t5527-fetch-odd-refs.sh b/t/t5527-fetch-odd-refs.sh\nindex 98ece27c6a..d3996af6ee 100755\n--- a/t/t5527-fetch-odd-refs.sh\n+++ b/t/t5527-fetch-odd-refs.sh\n@@ -52,7 +52,8 @@ test_expect_success LONG_REF 'fetch handles extremely long refname' '\n \tlong\n \tmain\n \tEOF\n-\tgit for-each-ref --format=\"%(subject)\" refs/remotes/long >actual &&\n+\tgit for-each-ref --format=\"%(subject)\" refs/remotes/long \\\n+\t\t--exclude=refs/remotes/long/HEAD >actual &&\n \ttest_cmp expect actual\n '\n \ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex a66d0e089d..88331d1ceb 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -329,7 +329,8 @@ test_expect_success 'incremental-repack task' '\n \n \t# Delete refs that have not been repacked in these packs.\n \tgit for-each-ref --format=\"delete %(refname)\" \\\n-\t\trefs/prefetch refs/tags refs/remotes >refs &&\n+\t\trefs/prefetch refs/tags refs/remotes \\\n+\t\t--exclude=refs/remotes/*/HEAD >refs &&\n \tgit update-ref --stdin <refs &&\n \n \t# Replace the object directory with this pack layout.\ndiff --git a/t/t9210-scalar.sh b/t/t9210-scalar.sh\nindex a30b2c9f70..2237844550 100755\n--- a/t/t9210-scalar.sh\n+++ b/t/t9210-scalar.sh\n@@ -151,7 +151,8 @@ test_expect_success 'scalar clone' '\n \t\t\t\"$(pwd)\" &&\n \n \t\tgit for-each-ref --format=\"%(refname)\" refs/remotes/origin/ >actual &&\n-\t\techo \"refs/remotes/origin/parallel\" >expect &&\n+\t\techo \"refs/remotes/origin/HEAD\" >>expect &&\n+\t\techo \"refs/remotes/origin/parallel\" >>expect &&\n \t\ttest_cmp expect actual &&\n \n \t\ttest_path_is_missing 1/2 &&\n@@ -220,7 +221,7 @@ test_expect_success 'scalar reconfigure --all with includeIf.onbranch' '\n \tdone\n '\n \n- test_expect_success 'scalar reconfigure --all with detached HEADs' '\n+test_expect_success 'scalar reconfigure --all with detached HEADs' '\n \trepos=\"two three four\" &&\n \tfor num in $repos\n \tdo\ndiff --git a/t/t9211-scalar-clone.sh b/t/t9211-scalar-clone.sh\nindex c16ea67c1d..d9cb6b9a3e 100755\n--- a/t/t9211-scalar-clone.sh\n+++ b/t/t9211-scalar-clone.sh\n@@ -32,7 +32,7 @@ test_expect_success 'set up repository to clone' '\n \t)\n '\n \n-cleanup_clone () {\n+cleanup_clone() {\n \trm -rf \"$1\"\n }\n \n@@ -128,7 +128,7 @@ test_expect_success '--single-branch clones HEAD only' '\n \t(\n \t\tcd $enlistment/src &&\n \t\tgit for-each-ref refs/remotes/origin >out &&\n-\t\ttest_line_count = 1 out &&\n+\t\ttest_line_count = 2 out &&\n \t\tgrep \"refs/remotes/origin/base\" out\n \t) &&\n \n@@ -142,7 +142,7 @@ test_expect_success '--no-single-branch clones all branches' '\n \t(\n \t\tcd $enlistment/src &&\n \t\tgit for-each-ref refs/remotes/origin >out &&\n-\t\ttest_line_count = 2 out &&\n+\t\ttest_line_count = 3 out &&\n \t\tgrep \"refs/remotes/origin/base\" out &&\n \t\tgrep \"refs/remotes/origin/parallel\" out\n \t) &&\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex cc6aa9f0cd..b663c4609e 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -658,6 +658,7 @@ test_expect_success '__git_refs - simple' '\n \tHEAD\n \tmain\n \tmatching-branch\n+\tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n \tmatching-tag\n@@ -673,6 +674,7 @@ test_expect_success '__git_refs - full refs' '\n \tcat >expected <<-EOF &&\n \trefs/heads/main\n \trefs/heads/matching-branch\n+\trefs/remotes/other/HEAD\n \trefs/remotes/other/branch-in-other\n \trefs/remotes/other/main-in-other\n \trefs/tags/matching-tag\n@@ -729,6 +731,7 @@ test_expect_success '__git_refs - remote on local file system - full refs' '\n test_expect_success '__git_refs - configured remote' '\n \tcat >expected <<-EOF &&\n \tHEAD\n+\tHEAD\n \tbranch-in-other\n \tmain-in-other\n \tEOF\n@@ -756,6 +759,7 @@ test_expect_success '__git_refs - configured remote - full refs' '\n test_expect_success '__git_refs - configured remote - repo given on the command line' '\n \tcat >expected <<-EOF &&\n \tHEAD\n+\tHEAD\n \tbranch-in-other\n \tmain-in-other\n \tEOF\n@@ -787,6 +791,7 @@ test_expect_success '__git_refs - configured remote - full refs - repo given on\n test_expect_success '__git_refs - configured remote - remote name matches a directory' '\n \tcat >expected <<-EOF &&\n \tHEAD\n+\tHEAD\n \tbranch-in-other\n \tmain-in-other\n \tEOF\n@@ -875,12 +880,14 @@ test_expect_success '__git_refs - unique remote branches for git checkout DWIMer\n \tHEAD\n \tmain\n \tmatching-branch\n+\tother/HEAD\n \tother/ambiguous\n \tother/branch-in-other\n \tother/main-in-other\n \tremote/ambiguous\n \tremote/branch-in-remote\n \tmatching-tag\n+\tHEAD\n \tbranch-in-other\n \tbranch-in-remote\n \tmain-in-other\n@@ -904,6 +911,7 @@ test_expect_success '__git_refs - after --opt=' '\n \tHEAD\n \tmain\n \tmatching-branch\n+\tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n \tmatching-tag\n@@ -919,6 +927,7 @@ test_expect_success '__git_refs - after --opt= - full refs' '\n \tcat >expected <<-EOF &&\n \trefs/heads/main\n \trefs/heads/matching-branch\n+\trefs/remotes/other/HEAD\n \trefs/remotes/other/branch-in-other\n \trefs/remotes/other/main-in-other\n \trefs/tags/matching-tag\n@@ -935,6 +944,7 @@ test_expect_success '__git refs - excluding refs' '\n \t^HEAD\n \t^main\n \t^matching-branch\n+\t^other/HEAD\n \t^other/branch-in-other\n \t^other/main-in-other\n \t^matching-tag\n@@ -950,6 +960,7 @@ test_expect_success '__git refs - excluding full refs' '\n \tcat >expected <<-EOF &&\n \t^refs/heads/main\n \t^refs/heads/matching-branch\n+\t^refs/remotes/other/HEAD\n \t^refs/remotes/other/branch-in-other\n \t^refs/remotes/other/main-in-other\n \t^refs/tags/matching-tag\n@@ -975,6 +986,7 @@ test_expect_success '__git_refs - do not filter refs unless told so' '\n \tmain\n \tmatching-branch\n \tmatching/branch\n+\tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n \tother/matching/branch-in-other\n@@ -1095,6 +1107,7 @@ test_expect_success '__git_complete_refs - simple' '\n \tHEAD Z\n \tmain Z\n \tmatching-branch Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tmatching-tag Z\n@@ -1123,6 +1136,7 @@ test_expect_success '__git_complete_refs - matching' '\n test_expect_success '__git_complete_refs - remote' '\n \tsed -e \"s/Z$//\" >expected <<-EOF &&\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain-in-other Z\n \tEOF\n@@ -1139,9 +1153,11 @@ test_expect_success '__git_complete_refs - track' '\n \tHEAD Z\n \tmain Z\n \tmatching-branch Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tmatching-tag Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain-in-other Z\n \tEOF\n@@ -1184,6 +1200,7 @@ test_expect_success '__git_complete_refs - suffix' '\n \tHEAD.\n \tmain.\n \tmatching-branch.\n+\tother/HEAD.\n \tother/branch-in-other.\n \tother/main-in-other.\n \tmatching-tag.\n@@ -1199,6 +1216,7 @@ test_expect_success '__git_complete_refs - suffix' '\n test_expect_success '__git_complete_fetch_refspecs - simple' '\n \tsed -e \"s/Z$//\" >expected <<-EOF &&\n \tHEAD:HEAD Z\n+\tHEAD:HEAD Z\n \tbranch-in-other:branch-in-other Z\n \tmain-in-other:main-in-other Z\n \tEOF\n@@ -1225,6 +1243,7 @@ test_expect_success '__git_complete_fetch_refspecs - matching' '\n test_expect_success '__git_complete_fetch_refspecs - prefix' '\n \tsed -e \"s/Z$//\" >expected <<-EOF &&\n \t+HEAD:HEAD Z\n+\t+HEAD:HEAD Z\n \t+branch-in-other:branch-in-other Z\n \t+main-in-other:main-in-other Z\n \tEOF\n@@ -1289,6 +1308,7 @@ test_expect_success '__git_complete_worktree_paths with -C' '\n \n test_expect_success 'git switch - with no options, complete local branches and unique remote branch names for DWIM logic' '\n \ttest_completion \"git switch \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1435,11 +1455,13 @@ test_expect_success 'git-bisect - existing view subcommand is recognized and ena\n test_expect_success 'git checkout - completes refs and unique remote branches for DWIM' '\n \ttest_completion \"git checkout \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1461,6 +1483,7 @@ test_expect_success 'git switch - with GIT_COMPLETION_CHECKOUT_NO_GUESS=1, compl\n \n test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete local branches and unique remote names for DWIM logic' '\n \tGIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git switch --guess \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1470,6 +1493,7 @@ test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_G\n \n test_expect_success 'git switch - a later --guess overrides previous --no-guess, complete local and remote unique branches for DWIM' '\n \ttest_completion \"git switch --no-guess --guess \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1490,6 +1514,7 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1498,11 +1523,13 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete\n test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1, complete refs and unique remote branches for DWIM' '\n \tGIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git checkout --guess \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1514,6 +1541,7 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1522,11 +1550,13 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '\n test_expect_success 'git checkout - a later --guess overrides previous --no-guess, complete refs and unique remote branches for DWIM' '\n \ttest_completion \"git checkout --no-guess --guess \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1538,6 +1568,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous --gues\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1550,6 +1581,7 @@ test_expect_success 'git checkout - with checkout.guess = false, only completes\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1559,11 +1591,13 @@ test_expect_success 'git checkout - with checkout.guess = true, completes refs a\n \ttest_config checkout.guess true &&\n \ttest_completion \"git checkout \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1573,11 +1607,13 @@ test_expect_success 'git checkout - a later --guess overrides previous checkout.\n \ttest_config checkout.guess false &&\n \ttest_completion \"git checkout --guess \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1590,6 +1626,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous checko\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1601,6 +1638,7 @@ test_expect_success 'git switch - with --detach, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1612,6 +1650,7 @@ test_expect_success 'git checkout - with --detach, complete only references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1783,6 +1822,7 @@ test_expect_success 'git switch - with -d, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1794,6 +1834,7 @@ test_expect_success 'git checkout - with -d, complete only references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1801,10 +1842,12 @@ test_expect_success 'git checkout - with -d, complete only references' '\n \n test_expect_success 'git switch - with --track, complete only remote branches' '\n \ttest_completion \"git switch --track \" <<-\\EOF &&\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n \ttest_completion \"git switch -t \" <<-\\EOF\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1812,10 +1855,12 @@ test_expect_success 'git switch - with --track, complete only remote branches' '\n \n test_expect_success 'git checkout - with --track, complete only remote branches' '\n \ttest_completion \"git checkout --track \" <<-\\EOF &&\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n \ttest_completion \"git checkout -t \" <<-\\EOF\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1834,6 +1879,7 @@ test_expect_success 'git checkout - with --no-track, complete only local referen\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1845,6 +1891,7 @@ test_expect_success 'git switch - with -c, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1856,6 +1903,7 @@ test_expect_success 'git switch - with -C, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1867,6 +1915,7 @@ test_expect_success 'git switch - with -c and --track, complete all references'\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1878,6 +1927,7 @@ test_expect_success 'git switch - with -C and --track, complete all references'\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1889,6 +1939,7 @@ test_expect_success 'git switch - with -c and --no-track, complete all reference\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1900,6 +1951,7 @@ test_expect_success 'git switch - with -C and --no-track, complete all reference\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1911,6 +1963,7 @@ test_expect_success 'git checkout - with -b, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1922,6 +1975,7 @@ test_expect_success 'git checkout - with -B, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1933,6 +1987,7 @@ test_expect_success 'git checkout - with -b and --track, complete all references\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1944,6 +1999,7 @@ test_expect_success 'git checkout - with -B and --track, complete all references\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1955,6 +2011,7 @@ test_expect_success 'git checkout - with -b and --no-track, complete all referen\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1966,6 +2023,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1973,6 +2031,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen\n \n test_expect_success 'git switch - for -c, complete local branches and unique remote branches' '\n \ttest_completion \"git switch -c \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1982,6 +2041,7 @@ test_expect_success 'git switch - for -c, complete local branches and unique rem\n \n test_expect_success 'git switch - for -C, complete local branches and unique remote branches' '\n \ttest_completion \"git switch -C \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2019,6 +2079,7 @@ test_expect_success 'git switch - for -C with --no-track, complete local branche\n \n test_expect_success 'git checkout - for -b, complete local branches and unique remote branches' '\n \ttest_completion \"git checkout -b \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2028,6 +2089,7 @@ test_expect_success 'git checkout - for -b, complete local branches and unique r\n \n test_expect_success 'git checkout - for -B, complete local branches and unique remote branches' '\n \ttest_completion \"git checkout -B \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2065,6 +2127,7 @@ test_expect_success 'git checkout - for -B with --no-track, complete local branc\n \n test_expect_success 'git switch - with --orphan completes local branch names and unique remote branch names' '\n \ttest_completion \"git switch --orphan \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2080,6 +2143,7 @@ test_expect_success 'git switch - --orphan with branch already provided complete\n \n test_expect_success 'git checkout - with --orphan completes local branch names and unique remote branch names' '\n \ttest_completion \"git checkout --orphan \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2093,6 +2157,7 @@ test_expect_success 'git checkout - --orphan with branch already provided comple\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n-- \n2.47.0.94.g1247fb88fd.dirty\n\n\n"},{"id":"505949","messageId":"d715d76b-1dd2-46e7-a16b-b2cf30940470@app.fastmail.com","threadId":"62096","inReplyTo":"20241023153736.257733-8-bence@ferdinandy.com","subject":"Re: [PATCH v12 7/8] fetch: set remote/HEAD if it does not exist","fromName":"Kristoffer Haugsbakk","fromEmail":"kristofferhaugsbakk@fastmail.com","sentAt":"2024-10-23T16:50:02Z","receivedAt":"2024-10-23T16:50:33Z","isPatch":true,"sender":{"key":"kristofferhaugsbakk@fastmail.com","avatar":null},"body":"On Wed, Oct 23, 2024, at 17:36, Bence Ferdinandy wrote:\n> If the user has remote/HEAD set already and it looks like it has changed\n> on the server, then print a message, otherwise set it if we can.\n> Silently pass if the user already has the same remote/HEAD set as\n> reported by the server or if we encounter any errors along the way.\n>\n> Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n> ---\n>\n> Notes:\n>     v3: - does not rely on remote set-head anymore so it only authenticates\n>         once\n>         - uses the new REF_CREATE_ONLY to atomically check if the ref exists\n>           and only write it if it doesn't\n>         - in all other cases the maximum it does is print a warning\n>\n>     v4: - instead of the discarded REF_CREATE_ONLY, it uses the existing,\n>           but updated transaction api to request a silent create only\n>         - it now uses the atomic before_target to determine reporting\n>         - refactored for legibility\n>\n>     v5: - instead of printing a not too useful message, it now fails\n>           silently, this in line with the objective to only set up\n>           remote/HEAD automatically if the right thing is trivial, for\n>           everything else there is remote set-head\n>         - fixed all failing tests\n>         - added two new tests, one for checking if remote/HEAD is set to the\n>           correct one, and one to test that we do not override remote/HEAD\n>           if it has changed on the server from what we have locally\n>\n>     v6: - fixed style issues and unintended extra empty line\n>         - updated function call with bool to int from previous patch's\n>           change\n>         - removed calls to error(...) inherited from builtin/remote.c so we\n>           actually fail silently\n>         - set the test for remote set-head --auto to the correct value here,\n>           which was previously erronously set in the remote set-head patch\n>\n>     v7: - no change\n>\n>     v8: - changed logmsg in call to refs_update_symref from \"remote\n>           set-head\" to \"fetch\"\n>\n>     v9: - follow through with refs_update_symref_extended\n>         - fix test errors uncovered by the new patch\n>\n>     v10: no change\n>\n>     v11: fixed some memory leaks\n>\n>     v12: no change\n\nI think it would be better to reverse-order these patch changelog\ncomments so that the newest is on top/first.  (for next time)\n\nThanks for the careful versioning here.\n\n-- \nKristoffer Haugsbakk\n"},{"id":"505953","messageId":"D53CMCABS8FO.374EKJJ6LN0MJ@ferdinandy.com","threadId":"62096","inReplyTo":"d715d76b-1dd2-46e7-a16b-b2cf30940470@app.fastmail.com","subject":"Re: [PATCH v12 7/8] fetch: set remote/HEAD if it does not exist","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-23T17:07:06Z","receivedAt":"2024-10-23T17:07:46Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Wed Oct 23, 2024 at 18:50, Kristoffer Haugsbakk <kristofferhaugsbakk@fastmail.com> wrote:\n> On Wed, Oct 23, 2024, at 17:36, Bence Ferdinandy wrote:\n>> If the user has remote/HEAD set already and it looks like it has changed\n>> on the server, then print a message, otherwise set it if we can.\n>> Silently pass if the user already has the same remote/HEAD set as\n>> reported by the server or if we encounter any errors along the way.\n>>\n>> Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n>> ---\n>>\n>> Notes:\n>>     v3: - does not rely on remote set-head anymore so it only authenticates\n>>         once\n>>         - uses the new REF_CREATE_ONLY to atomically check if the ref exists\n>>           and only write it if it doesn't\n>>         - in all other cases the maximum it does is print a warning\n>>\n>>     v4: - instead of the discarded REF_CREATE_ONLY, it uses the existing,\n>>           but updated transaction api to request a silent create only\n>>         - it now uses the atomic before_target to determine reporting\n>>         - refactored for legibility\n>>\n>>     v5: - instead of printing a not too useful message, it now fails\n>>           silently, this in line with the objective to only set up\n>>           remote/HEAD automatically if the right thing is trivial, for\n>>           everything else there is remote set-head\n>>         - fixed all failing tests\n>>         - added two new tests, one for checking if remote/HEAD is set to the\n>>           correct one, and one to test that we do not override remote/HEAD\n>>           if it has changed on the server from what we have locally\n>>\n>>     v6: - fixed style issues and unintended extra empty line\n>>         - updated function call with bool to int from previous patch's\n>>           change\n>>         - removed calls to error(...) inherited from builtin/remote.c so we\n>>           actually fail silently\n>>         - set the test for remote set-head --auto to the correct value here,\n>>           which was previously erronously set in the remote set-head patch\n>>\n>>     v7: - no change\n>>\n>>     v8: - changed logmsg in call to refs_update_symref from \"remote\n>>           set-head\" to \"fetch\"\n>>\n>>     v9: - follow through with refs_update_symref_extended\n>>         - fix test errors uncovered by the new patch\n>>\n>>     v10: no change\n>>\n>>     v11: fixed some memory leaks\n>>\n>>     v12: no change\n>\n> I think it would be better to reverse-order these patch changelog\n> comments so that the newest is on top/first.  (for next time)\n>\n> Thanks for the careful versioning here.\n\nYeah, this works fine when you only go up to v3 and it all fits on a screen :D\nThis is definitely the longest patch series I've made thus far, both in number\nof commits and versions. I also noticed this today when I realized that I did the\ncover letter in reverse and how much better readable that was than some of the\npatches ...\n\nOne thing that would not be easy is that with so many patches, how I start out\nthe notes is\n\n\tgit rev-list HEAD~8..HEAD | xargs -i git notes append {} -m \"v12: no change\"\n\nand there's no \"git notes prepend\". That could be another patch for another\ntime :) It also bothers me, that the branch description that can be used to\nsave cover letters is not a note, just a local configuration, but that's\na third issue.\n\nAnyhow, thanks for calling it out, if we do come to a v13 I might just spend\nsome time on reversing (or be lazy and just continue at the top ...).\n\nBest,\nBence\n\n-- \nbence.ferdinandy.com\n\n"},{"id":"505964","messageId":"ZxlEYm3zb5K+gksy@nand.local","threadId":"62096","inReplyTo":"D53ANCP0WMJF.2PG371ZJS1F95@ferdinandy.com","subject":"Re: [PATCH v11 1/8] t/t5505-remote: set default branch to main","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2024-10-23T18:45:54Z","receivedAt":"2024-10-23T18:45:57Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Wed, Oct 23, 2024 at 05:34:23PM +0200, Bence Ferdinandy wrote:\n> > Alternatively, you could set it in your copy of git.git's own\n> > configuration in $GIT_DIR/config, so that it doesn't apply to work you\n> > do outside of that repository.\n> >\n> > Either way, I'm going to avoid queueing this round since it is missing\n> > the required Signed-off-by line.\n>\n> In that case I'll send a v12 with the S-o-b so the series can get back into\n> circulation.\n\nThanks, will queue.\n\nThanks,\nTaylor\n"},{"id":"507296","messageId":"D5M6L00O65SC.RBYI3PG7KBDX@ttk.elte.hu","threadId":"62096","inReplyTo":"20241023153736.257733-1-bence@ferdinandy.com","subject":"Re: [PATCH v12 0/8] set-head/fetch remote/HEAD updates","fromName":"Bence Ferdinandy","fromEmail":"ferdinandy.bence@ttk.elte.hu","sentAt":"2024-11-14T20:23:53Z","receivedAt":"2024-11-14T20:24:01Z","isPatch":true,"sender":{"key":"ferdinandy.bence@ttk.elte.hu","avatar":null},"body":"Hi all,\n\nI just wanted to ping this thread in hopes of another round of reviews.\n\nAside from some smaller fixes, the main things that have not yet been reviewed are:\n\n- the new patches 1/8 and 8/8\n  (https://lore.kernel.org/git/Zw8IKyPkG0Hr6%2F5t@nand.local/, but see\n  https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/)\n- changes to 2/8 (refs_update_symref -> refs_update_symref_extended change\n  reflecting on Phillip's comments see\n  https://lore.kernel.org/git/a7cb48e5-d8ba-44c1-9dbe-d1e8f8a63e3c@gmail.com/)\n\nRegarding the latter name: reading another thread about code style here I was\nwondering if refs_update_symref_1 would be a better choice.\n\nI'm hoping that otherwise the series is mature now.\n\nThanks,\nBence\n"},{"id":"507312","messageId":"xmqqfrnt3xpd.fsf@gitster.g","threadId":"62096","inReplyTo":"20241023153736.257733-2-bence@ferdinandy.com","subject":"Re: [PATCH v12 1/8] t/t5505-remote: set default branch to main","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-11-15T04:52:46Z","receivedAt":"2024-11-15T04:52:50Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bence Ferdinandy <bence@ferdinandy.com> writes:\n\n> Consider the bare repository called \"mirror\" in the test.  Running `git\n> remote add --mirror -f origin ../one` will not change HEAD, consequently\n> if init.defaultBranch is not the same as what HEAD in the remote\n> (\"one\"), HEAD in \"mirror\" will be pointing to a non-existent reference.\n> Hence if \"mirror\" is used as a remote by yet another repository,\n> ls-remote will not show HEAD. On the other hand, if init.defaultBranch\n> happens to match HEAD in \"one\", then ls-remote will show HEAD.\n\nMaking sure that the hardcoded (in the Git binary) default branch\nname would not affect the outcome of the test is a good thing to do.\nI like the patch text, but ...\n\n> Since the CI globally exports GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main,\n> there's a drift between how the test repositories are set up in the CI\n> and during local testing. This issue does not manifest currently, as the\n> test does not do any remote HEAD manipulation where this would come up,\n> but should such things be added, a locally passing test would break the\n> CI vice-versa.\n\n... this description may not quite be accurate.  Don't some jobs of\nCI use 'main' while the rest use 'master'?\n"},{"id":"507315","messageId":"xmqqr07d11wt.fsf@gitster.g","threadId":"62096","inReplyTo":"20241023153736.257733-3-bence@ferdinandy.com","subject":"Re: [PATCH v12 2/8] refs: atomically record overwritten ref in update_symref","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-11-15T05:50:10Z","receivedAt":"2024-11-15T05:50:13Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bence Ferdinandy <bence@ferdinandy.com> writes:\n\n>  int refs_update_symref(struct ref_store *refs, const char *ref,\n>  \t\t       const char *target, const char *logmsg)\n> +{\n> +\treturn refs_update_symref_extended(refs, ref, target, logmsg, NULL);\n> +}\n\nOK.  As the enhanced and renamed function is also external, we do\nnot have to worry about reordering the old one to come after the new\none.\n\n> +int refs_update_symref_extended(struct ref_store *refs, const char *ref,\n> +\t\t       const char *target, const char *logmsg,\n> +\t\t       struct strbuf *referent)\n>  {\n>  \tstruct ref_transaction *transaction;\n>  \tstruct strbuf err = STRBUF_INIT;\n> @@ -2122,13 +2129,20 @@ int refs_update_symref(struct ref_store *refs, const char *ref,\n>  \n>  \ttransaction = ref_store_transaction_begin(refs, &err);\n>  \tif (!transaction ||\n> -\t    ref_transaction_update(transaction, ref, NULL, NULL,\n> +\t\tref_transaction_update(transaction, ref, NULL, NULL,\n\nAn unwanted patch noise?\n\n>  \t\t\t\t   target, NULL, REF_NO_DEREF,\n>  \t\t\t\t   logmsg, &err) ||\n> -\t    ref_transaction_commit(transaction, &err)) {\n> +\t\tref_transaction_prepare(transaction, &err)) {\n\nLikewise, but the noise distracts from the real change made on this\nline, which is even worse.\n\nThe real change here is to only call _prepare(), which also asks the\ntransaction hook if we are OK to proceed.  If we fail, we stop here\n\n>  \t\tret = error(\"%s\", err.buf);\n> +\t\tgoto cleanup;\n\nThis is also a real change.  We could instead make the additional\ncode below into the else clause (see below).\n\n>  \t}\n> +\tif (referent)\n> +\t\trefs_read_symbolic_ref(refs, ref, referent);\n\nAnd if we were asked to give the value of the symbolic ref, we make\nthis call.  What should this code do when reading fails (I know it\nignores, as written, but I am asking what it _should_ do)?\n\n> +\tif (ref_transaction_commit(transaction, &err))\n> +\t\tret = error(\"%s\", err.buf);\n\nAnd then we commit, or we fail to commit.\n\n> +cleanup:\n\nWe could write the whole thing as a single \"do these and leave as\nsoon as we see any failure\" ||-cascade,\n\n\tif (!(transaction = ref_store_transaction_begin(refs, &err)) ||\n\n\t    ref_transaction_update(transaction, ref, NULL, NULL,\n\t\t\t\t   target, NULL, REF_NO_DEREF,\n\t\t\t\t   logmsg, &err) ||\n\n\t    ref_transaction_prepare(transaction, &err)) ||\n\n\t    (referent\n\t    ? refs_read_symbolic_ref(refs, ref, referent)\n\t    : 0) ||\n\n\t    ref_transaction_commit(transaction, &err)) {\n\t\tif (!err.len)\n\t\t\t... stuff default error message to err ...;\n\t\tret = error(\"%s\", err.buf);\n\t}\n\nwhich may not necessarily easier to follow (and in fact it is rather\nugly), but at least, the resulting code does not have to special\ncase the \"optionally peek into the symref\" step.\n"},{"id":"507316","messageId":"xmqqjzd511wd.fsf@gitster.g","threadId":"62096","inReplyTo":"20241023153736.257733-4-bence@ferdinandy.com","subject":"Re: [PATCH v12 3/8] remote set-head: refactor for readability","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-11-15T05:50:26Z","receivedAt":"2024-11-15T05:50:28Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bence Ferdinandy <bence@ferdinandy.com> writes:\n\n> Rename buf and buf2 to something more explanatory.\n>\n> Instead of calling get_main_ref_store(the_repository) multiple times,\n> call it once and store in a new refs variable. Although this change\n> probably offers some performance benefits, the main purpose is to\n> shorten the line lengths of function calls using this variable.\n\nAlso two strbufs are renamed, which is in line with the objective of\nthe other change---to make the resulting code easier to follow.\n\nLooking good.\n\n>     v9: - further improve readability by renaming buf, and buf2 consistently\n>           with how patch 6 was already done\n\n"},{"id":"507317","messageId":"xmqqcyix11w8.fsf@gitster.g","threadId":"62096","inReplyTo":"20241023153736.257733-5-bence@ferdinandy.com","subject":"Re: [PATCH v12 4/8] remote set-head: better output for --auto","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-11-15T05:50:31Z","receivedAt":"2024-11-15T05:50:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bence Ferdinandy <bence@ferdinandy.com> writes:\n\n> Currently, set-head --auto will print a message saying \"remote/HEAD set\n> to branch\", which implies something was changed.\n>\n> Change the output of --auto, so the output actually reflects what was\n> done: a) set a previously unset HEAD, b) change HEAD because remote\n> changed or c) no updates. As a fourth output, if HEAD is changed from\n> a previous value that was not a remote branch, explicitly call attention\n> to this fact.\n\nOK.  That's sensible.\n\nThere is a slight variant of the fourth case.  HEAD may have been a\nsymbolic ref that pointed at an unexpected place (which you\naddressed), or HEAD may have been a non-symbolic ref (which the new\ncode would mistakenly say \"HEAD is now created\", if I am reading the\npatch correctly).\n\n> diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\n> index 9b50276646..0ea86d51a4 100755\n> --- a/t/t5505-remote.sh\n> +++ b/t/t5505-remote.sh\n> @@ -432,12 +432,51 @@ test_expect_success 'set-head --auto' '\n>  \t)\n>  '\n>  \n> +test_expect_success 'set-head --auto detects creation' '\n> +\t(\n> +\t\tcd test &&\n> +\t\tgit symbolic-ref -d refs/remotes/origin/HEAD &&\n\nAre we sure refs/remotes/origin/HEAD exists at this point in the\ntest, regardless of which earlier tests were skipped or failed?  If\nnot, perhaps\n\n\t\tgit update-ref --no-deref -d refs/remotes/origin/HEAD &&\n\nis a better alternative.\n\n> +\t\tgit remote set-head --auto origin >output &&\n> +\t\techo \"${SQ}origin/HEAD${SQ} is now created and points to ${SQ}main${SQ}\" >expect &&\n> +\t\ttest_cmp expect output\n> +\t)\n> +'\n\nHere, we could insert another one:\n\ntest_expect_success 'set-head --auto to update a non symbolic ref' '\n\t(\n\t\tcd test &&\n\t\tgit update-ref --no-deref -d refs/remotes/origin/HEAD &&\n\t\tgit update-ref refs/remotes/origin/HEAD HEAD &&\n\t\tgit remote set-head --auto origin >output &&\n\nI'd imagine \"output\" should at least say that we are setting up a\nsymref origin/HEAD to point at some ref the --auto option figured\nout, and if we wanted to report its previous state, it was a non\nsymbolic ref that pointed at some commit.  In any case,\n\n\t\techo \"${SQ}origin/HEAD${SQ} is now created and points to ${SQ}main${SQ}\" >expect &&\n\nis not what we want to see here, I suspect.\n\nCan we detect the case where we overwrite a non symref with a symref\nwithout going back to step 2/8 and doing a major surgery?\n\n\t\ttest_cmp expect output\n\t)\n'\n"},{"id":"507379","messageId":"D5N3BZOUUFPH.CV9OQ54BVYK@ferdinandy.com","threadId":"62096","inReplyTo":"xmqqfrnt3xpd.fsf@gitster.g","subject":"Re: [PATCH v12 1/8] t/t5505-remote: set default branch to main","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-15T22:03:44Z","receivedAt":"2024-11-15T22:09:21Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Fri Nov 15, 2024 at 05:52, Junio C Hamano <gitster@pobox.com> wrote:\n> Bence Ferdinandy <bence@ferdinandy.com> writes:\n>\n>> Consider the bare repository called \"mirror\" in the test.  Running `git\n>> remote add --mirror -f origin ../one` will not change HEAD, consequently\n>> if init.defaultBranch is not the same as what HEAD in the remote\n>> (\"one\"), HEAD in \"mirror\" will be pointing to a non-existent reference.\n>> Hence if \"mirror\" is used as a remote by yet another repository,\n>> ls-remote will not show HEAD. On the other hand, if init.defaultBranch\n>> happens to match HEAD in \"one\", then ls-remote will show HEAD.\n>\n> Making sure that the hardcoded (in the Git binary) default branch\n> name would not affect the outcome of the test is a good thing to do.\n> I like the patch text, but ...\n>\n>> Since the CI globally exports GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main,\n>> there's a drift between how the test repositories are set up in the CI\n>> and during local testing. This issue does not manifest currently, as the\n>> test does not do any remote HEAD manipulation where this would come up,\n>> but should such things be added, a locally passing test would break the\n>> CI vice-versa.\n>\n> ... this description may not quite be accurate.  Don't some jobs of\n> CI use 'main' while the rest use 'master'?\n\nTrue, it's that one specific job, and of course \"there may be a drift depending\non the particular test\" is more accurate. I'll update for a v13.\n\n"},{"id":"507380","messageId":"D5N3N1RMMR9Q.28GSGYYBWZSOS@ferdinandy.com","threadId":"62096","inReplyTo":"xmqqr07d11wt.fsf@gitster.g","subject":"Re: [PATCH v12 2/8] refs: atomically record overwritten ref in update_symref","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-15T22:18:10Z","receivedAt":"2024-11-15T22:18:34Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Fri Nov 15, 2024 at 06:50, Junio C Hamano <gitster@pobox.com> wrote:\n> Bence Ferdinandy <bence@ferdinandy.com> writes:\n>\n>>  int refs_update_symref(struct ref_store *refs, const char *ref,\n>>  \t\t       const char *target, const char *logmsg)\n>> +{\n>> +\treturn refs_update_symref_extended(refs, ref, target, logmsg, NULL);\n>> +}\n>\n> OK.  As the enhanced and renamed function is also external, we do\n> not have to worry about reordering the old one to come after the new\n> one.\n\nI guess this also decides that the name \"_extended\" is fine :)\n\n>\n>> +int refs_update_symref_extended(struct ref_store *refs, const char *ref,\n>> +\t\t       const char *target, const char *logmsg,\n>> +\t\t       struct strbuf *referent)\n>>  {\n>>  \tstruct ref_transaction *transaction;\n>>  \tstruct strbuf err = STRBUF_INIT;\n>> @@ -2122,13 +2129,20 @@ int refs_update_symref(struct ref_store *refs, const char *ref,\n>>  \n>>  \ttransaction = ref_store_transaction_begin(refs, &err);\n>>  \tif (!transaction ||\n>> -\t    ref_transaction_update(transaction, ref, NULL, NULL,\n>> +\t\tref_transaction_update(transaction, ref, NULL, NULL,\n>\n> An unwanted patch noise?\n>\n>>  \t\t\t\t   target, NULL, REF_NO_DEREF,\n>>  \t\t\t\t   logmsg, &err) ||\n>> -\t    ref_transaction_commit(transaction, &err)) {\n>> +\t\tref_transaction_prepare(transaction, &err)) {\n>\n> Likewise, but the noise distracts from the real change made on this\n> line, which is even worse.\n\nMea culpa, I'll get this cleaned up.\n\n>\n> The real change here is to only call _prepare(), which also asks the\n> transaction hook if we are OK to proceed.  If we fail, we stop here\n>\n>>  \t\tret = error(\"%s\", err.buf);\n>> +\t\tgoto cleanup;\n>\n> This is also a real change.  We could instead make the additional\n> code below into the else clause (see below).\n>\n>>  \t}\n>> +\tif (referent)\n>> +\t\trefs_read_symbolic_ref(refs, ref, referent);\n>\n> And if we were asked to give the value of the symbolic ref, we make\n> this call.  What should this code do when reading fails (I know it\n> ignores, as written, but I am asking what it _should_ do)?\n\nI think this should do _nothing_ if it fails (although should it stay this way,\nI guess it should be marked with a comment that this is on purpose). My\nreasoning is that running `git fetch` will be running this part of the code,\nwhich means that should reading the symbolic ref fail for any reason, a `fetch`\nthat previously ran without error would now fail. We now pass up an empty\nstring as the previous which does mask that there was an error here. What\nI think we could maybe do is pass up a special string that means there was an\nerror? Something that for sure cannot be a valid value for an existing\nreference? I'm not sure how much sense that makes.\n\n>\n>> +\tif (ref_transaction_commit(transaction, &err))\n>> +\t\tret = error(\"%s\", err.buf);\n>\n> And then we commit, or we fail to commit.\n>\n>> +cleanup:\n>\n> We could write the whole thing as a single \"do these and leave as\n> soon as we see any failure\" ||-cascade,\n>\n> \tif (!(transaction = ref_store_transaction_begin(refs, &err)) ||\n>\n> \t    ref_transaction_update(transaction, ref, NULL, NULL,\n> \t\t\t\t   target, NULL, REF_NO_DEREF,\n> \t\t\t\t   logmsg, &err) ||\n>\n> \t    ref_transaction_prepare(transaction, &err)) ||\n>\n> \t    (referent\n> \t    ? refs_read_symbolic_ref(refs, ref, referent)\n> \t    : 0) ||\n>\n> \t    ref_transaction_commit(transaction, &err)) {\n> \t\tif (!err.len)\n> \t\t\t... stuff default error message to err ...;\n> \t\tret = error(\"%s\", err.buf);\n> \t}\n>\n> which may not necessarily easier to follow (and in fact it is rather\n> ugly), but at least, the resulting code does not have to special\n> case the \"optionally peek into the symref\" step.\n\nAs I said above, I don't think we actually want to fail the update even if the\nsymbolic ref reading fails, so I think the special casing should stay. I'll\nwait here to see more clearly on what to do here.\n\n\n"},{"id":"507381","messageId":"D5N4B2SSZAG4.1O5CERCP825X2@ferdinandy.com","threadId":"62096","inReplyTo":"xmqqcyix11w8.fsf@gitster.g","subject":"Re: [PATCH v12 4/8] remote set-head: better output for --auto","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-15T22:49:33Z","receivedAt":"2024-11-15T22:55:07Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Fri Nov 15, 2024 at 06:50, Junio C Hamano <gitster@pobox.com> wrote:\n> Bence Ferdinandy <bence@ferdinandy.com> writes:\n>\n>> Currently, set-head --auto will print a message saying \"remote/HEAD set\n>> to branch\", which implies something was changed.\n>>\n>> Change the output of --auto, so the output actually reflects what was\n>> done: a) set a previously unset HEAD, b) change HEAD because remote\n>> changed or c) no updates. As a fourth output, if HEAD is changed from\n>> a previous value that was not a remote branch, explicitly call attention\n>> to this fact.\n>\n> OK.  That's sensible.\n>\n> There is a slight variant of the fourth case.  HEAD may have been a\n> symbolic ref that pointed at an unexpected place (which you\n> addressed), or HEAD may have been a non-symbolic ref (which the new\n> code would mistakenly say \"HEAD is now created\", if I am reading the\n> patch correctly).\n\nGood point, and yes, that is what happens. (Although I'm not quite sure how\nvalid that state is where a remote's HEAD is not a branch).\n\n>\n>> diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\n>> index 9b50276646..0ea86d51a4 100755\n>> --- a/t/t5505-remote.sh\n>> +++ b/t/t5505-remote.sh\n>> @@ -432,12 +432,51 @@ test_expect_success 'set-head --auto' '\n>>  \t)\n>>  '\n>>  \n>> +test_expect_success 'set-head --auto detects creation' '\n>> +\t(\n>> +\t\tcd test &&\n>> +\t\tgit symbolic-ref -d refs/remotes/origin/HEAD &&\n>\n> Are we sure refs/remotes/origin/HEAD exists at this point in the\n> test, regardless of which earlier tests were skipped or failed?  If\n> not, perhaps\n>\n> \t\tgit update-ref --no-deref -d refs/remotes/origin/HEAD &&\n>\n> is a better alternative.\n\nAck.\n\n>\n>> +\t\tgit remote set-head --auto origin >output &&\n>> +\t\techo \"${SQ}origin/HEAD${SQ} is now created and points to ${SQ}main${SQ}\" >expect &&\n>> +\t\ttest_cmp expect output\n>> +\t)\n>> +'\n>\n> Here, we could insert another one:\n>\n> test_expect_success 'set-head --auto to update a non symbolic ref' '\n> \t(\n> \t\tcd test &&\n> \t\tgit update-ref --no-deref -d refs/remotes/origin/HEAD &&\n> \t\tgit update-ref refs/remotes/origin/HEAD HEAD &&\n> \t\tgit remote set-head --auto origin >output &&\n>\n> I'd imagine \"output\" should at least say that we are setting up a\n> symref origin/HEAD to point at some ref the --auto option figured\n> out, and if we wanted to report its previous state, it was a non\n> symbolic ref that pointed at some commit.  In any case,\n>\n> \t\techo \"${SQ}origin/HEAD${SQ} is now created and points to ${SQ}main${SQ}\" >expect &&\n>\n> is not what we want to see here, I suspect.\n>\n> Can we detect the case where we overwrite a non symref with a symref\n> without going back to step 2/8 and doing a major surgery?\n>\n> \t\ttest_cmp expect output\n> \t)\n> '\n\nI agree, adding this makes sense. And this also takes us back to the question\nof what we should do in 2/8 when refs_read_symbolic_ref exits with 1. I now\ntested the behaviour and if origin/HEAD is gibberish, git already dies before\nwith \n\nerror: cannot lock ref 'refs/remotes/origin/HEAD': unable to resolve reference 'refs/remotes/origin/HEAD': reference broken\n\nso refs_read_symbolic_ref -> 1 only happens if there's a valid non-symbolic ref\nin origin/HEAD. So maybe if we put \"Not a symbolic reference.\" in the referent\n(which should be an invalid symref), the caller could check for that and then\nshould be able to distinguish this special case?\n\nThanks,\nBence\n\n-- \nbence.ferdinandy.com\n\n"},{"id":"507383","messageId":"D5N4TN6WX65V.JJKLX5ROS8NN@ferdinandy.com","threadId":"62096","inReplyTo":"D5N4B2SSZAG4.1O5CERCP825X2@ferdinandy.com","subject":"Re: [PATCH v12 4/8] remote set-head: better output for --auto","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-15T23:13:48Z","receivedAt":"2024-11-15T23:14:49Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Fri Nov 15, 2024 at 23:49, Bence Ferdinandy <bence@ferdinandy.com> wrote:\n>\n> On Fri Nov 15, 2024 at 06:50, Junio C Hamano <gitster@pobox.com> wrote:\n>> Bence Ferdinandy <bence@ferdinandy.com> writes:\n>>\n>>> Currently, set-head --auto will print a message saying \"remote/HEAD set\n>>> to branch\", which implies something was changed.\n>>>\n>>> Change the output of --auto, so the output actually reflects what was\n>>> done: a) set a previously unset HEAD, b) change HEAD because remote\n>>> changed or c) no updates. As a fourth output, if HEAD is changed from\n>>> a previous value that was not a remote branch, explicitly call attention\n>>> to this fact.\n>>\n>> OK.  That's sensible.\n>>\n>> There is a slight variant of the fourth case.  HEAD may have been a\n>> symbolic ref that pointed at an unexpected place (which you\n>> addressed), or HEAD may have been a non-symbolic ref (which the new\n>> code would mistakenly say \"HEAD is now created\", if I am reading the\n>> patch correctly).\n>\n> Good point, and yes, that is what happens. (Although I'm not quite sure how\n> valid that state is where a remote's HEAD is not a branch).\n>\n>>\n>>> diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\n>>> index 9b50276646..0ea86d51a4 100755\n>>> --- a/t/t5505-remote.sh\n>>> +++ b/t/t5505-remote.sh\n>>> @@ -432,12 +432,51 @@ test_expect_success 'set-head --auto' '\n>>>  \t)\n>>>  '\n>>>  \n>>> +test_expect_success 'set-head --auto detects creation' '\n>>> +\t(\n>>> +\t\tcd test &&\n>>> +\t\tgit symbolic-ref -d refs/remotes/origin/HEAD &&\n>>\n>> Are we sure refs/remotes/origin/HEAD exists at this point in the\n>> test, regardless of which earlier tests were skipped or failed?  If\n>> not, perhaps\n>>\n>> \t\tgit update-ref --no-deref -d refs/remotes/origin/HEAD &&\n>>\n>> is a better alternative.\n>\n> Ack.\n>\n>>\n>>> +\t\tgit remote set-head --auto origin >output &&\n>>> +\t\techo \"${SQ}origin/HEAD${SQ} is now created and points to ${SQ}main${SQ}\" >expect &&\n>>> +\t\ttest_cmp expect output\n>>> +\t)\n>>> +'\n>>\n>> Here, we could insert another one:\n>>\n>> test_expect_success 'set-head --auto to update a non symbolic ref' '\n>> \t(\n>> \t\tcd test &&\n>> \t\tgit update-ref --no-deref -d refs/remotes/origin/HEAD &&\n>> \t\tgit update-ref refs/remotes/origin/HEAD HEAD &&\n>> \t\tgit remote set-head --auto origin >output &&\n>>\n>> I'd imagine \"output\" should at least say that we are setting up a\n>> symref origin/HEAD to point at some ref the --auto option figured\n>> out, and if we wanted to report its previous state, it was a non\n>> symbolic ref that pointed at some commit.  In any case,\n>>\n>> \t\techo \"${SQ}origin/HEAD${SQ} is now created and points to ${SQ}main${SQ}\" >expect &&\n>>\n>> is not what we want to see here, I suspect.\n>>\n>> Can we detect the case where we overwrite a non symref with a symref\n>> without going back to step 2/8 and doing a major surgery?\n>>\n>> \t\ttest_cmp expect output\n>> \t)\n>> '\n>\n> I agree, adding this makes sense. And this also takes us back to the question\n> of what we should do in 2/8 when refs_read_symbolic_ref exits with 1. I now\n> tested the behaviour and if origin/HEAD is gibberish, git already dies before\n> with \n>\n> error: cannot lock ref 'refs/remotes/origin/HEAD': unable to resolve reference 'refs/remotes/origin/HEAD': reference broken\n>\n> so refs_read_symbolic_ref -> 1 only happens if there's a valid non-symbolic ref\n> in origin/HEAD. So maybe if we put \"Not a symbolic reference.\" in the referent\n> (which should be an invalid symref), the caller could check for that and then\n> should be able to distinguish this special case?\n\nOn second thought, it would maybe make even more sense to get the reference\nhash and put that into referent. In that case the output could still be\n\n\"'%s/HEAD' has changed from '%s' and now points to '%s'\\n\"\n\nbut with a non-symref after from.\n\n\n"},{"id":"507384","messageId":"54677502-a08c-4920-9b08-13517c8f027a@ferdinandy.com","threadId":"62096","inReplyTo":"D5N3N1RMMR9Q.28GSGYYBWZSOS@ferdinandy.com","subject":"Re: [PATCH v12 2/8] refs: atomically record overwritten ref in update_symref","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-15T23:27:44Z","receivedAt":"2024-11-15T23:33:12Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"2024. nov. 15. 23:18:10 Bence Ferdinandy <bence@ferdinandy.com>:\n\n>\n> On Fri Nov 15, 2024 at 06:50, Junio C Hamano <gitster@pobox.com> wrote:\n>> Bence Ferdinandy <bence@ferdinandy.com> writes:\n>>\n>>> int refs_update_symref(struct ref_store *refs, const char *ref,\n>>>                const char *target, const char *logmsg)\n>>> +{\n>>> +   return refs_update_symref_extended(refs, ref, target, logmsg, \n>>> NULL);\n>>> +}\n>>\n>> OK.  As the enhanced and renamed function is also external, we do\n>> not have to worry about reordering the old one to come after the new\n>> one.\n>\n> I guess this also decides that the name \"_extended\" is fine :)\n>\n>>\n>>> +int refs_update_symref_extended(struct ref_store *refs, const char \n>>> *ref,\n>>> +              const char *target, const char *logmsg,\n>>> +              struct strbuf *referent)\n>>> {\n>>>     struct ref_transaction *transaction;\n>>>     struct strbuf err = STRBUF_INIT;\n>>> @@ -2122,13 +2129,20 @@ int refs_update_symref(struct ref_store \n>>> *refs, const char *ref,\n>>>\n>>>     transaction = ref_store_transaction_begin(refs, &err);\n>>>     if (!transaction ||\n>>> -       ref_transaction_update(transaction, ref, NULL, NULL,\n>>> +       ref_transaction_update(transaction, ref, NULL, NULL,\n>>\n>> An unwanted patch noise?\n>>\n>>>                    target, NULL, REF_NO_DEREF,\n>>>                    logmsg, &err) ||\n>>> -       ref_transaction_commit(transaction, &err)) {\n>>> +       ref_transaction_prepare(transaction, &err)) {\n>>\n>> Likewise, but the noise distracts from the real change made on this\n>> line, which is even worse.\n>\n> Mea culpa, I'll get this cleaned up.\n>\n>>\n>> The real change here is to only call _prepare(), which also asks the\n>> transaction hook if we are OK to proceed.  If we fail, we stop here\n>>\n>>>         ret = error(\"%s\", err.buf);\n>>> +       goto cleanup;\n>>\n>> This is also a real change.  We could instead make the additional\n>> code below into the else clause (see below).\n>>\n>>>     }\n>>> +   if (referent)\n>>> +       refs_read_symbolic_ref(refs, ref, referent);\n>>\n>> And if we were asked to give the value of the symbolic ref, we make\n>> this call.  What should this code do when reading fails (I know it\n>> ignores, as written, but I am asking what it _should_ do)?\n>\n> I think this should do _nothing_ if it fails (although should it stay \n> this way,\n> I guess it should be marked with a comment that this is on purpose). My\n> reasoning is that running `git fetch` will be running this part of the \n> code,\n> which means that should reading the symbolic ref fail for any reason, a \n> `fetch`\n> that previously ran without error would now fail. We now pass up an \n> empty\n> string as the previous which does mask that there was an error here. \n> What\n> I think we could maybe do is pass up a special string that means there \n> was an\n> error? Something that for sure cannot be a valid value for an existing\n> reference? I'm not sure how much sense that makes.\n\nSorry, it's late. The above is slightly bollocks since fetch ignores any \nset_head errors later :)\nBut the idea stands that if we can set the head, let's do it.\nThe previous head is not important enough to die on.\n\nAs mentioned on the other patch, we could try to read a non-symref \ninstead also, if symref fails.\n>\n>>\n>>> +   if (ref_transaction_commit(transaction, &err))\n>>> +       ret = error(\"%s\", err.buf);\n>>\n>> And then we commit, or we fail to commit.\n>>\n>>> +cleanup:\n>>\n>> We could write the whole thing as a single \"do these and leave as\n>> soon as we see any failure\" ||-cascade,\n>>\n>>     if (!(transaction = ref_store_transaction_begin(refs, &err)) ||\n>>\n>>         ref_transaction_update(transaction, ref, NULL, NULL,\n>>                    target, NULL, REF_NO_DEREF,\n>>                    logmsg, &err) ||\n>>\n>>         ref_transaction_prepare(transaction, &err)) ||\n>>\n>>         (referent\n>>         ? refs_read_symbolic_ref(refs, ref, referent)\n>>         : 0) ||\n>>\n>>         ref_transaction_commit(transaction, &err)) {\n>>         if (!err.len)\n>>             ... stuff default error message to err ...;\n>>         ret = error(\"%s\", err.buf);\n>>     }\n>>\n>> which may not necessarily easier to follow (and in fact it is rather\n>> ugly), but at least, the resulting code does not have to special\n>> case the \"optionally peek into the symref\" step.\n>\n> As I said above, I don't think we actually want to fail the update even \n> if the\n> symbolic ref reading fails, so I think the special casing should stay. \n> I'll\n> wait here to see more clearly on what to do here.\n"},{"id":"507387","messageId":"xmqqo72gyqy7.fsf@gitster.g","threadId":"62096","inReplyTo":"D5N4B2SSZAG4.1O5CERCP825X2@ferdinandy.com","subject":"Re: [PATCH v12 4/8] remote set-head: better output for --auto","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-11-16T00:15:12Z","receivedAt":"2024-11-16T00:15:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Bence Ferdinandy\" <bence@ferdinandy.com> writes:\n\n> Good point, and yes, that is what happens. (Although I'm not quite sure how\n> valid that state is where a remote's HEAD is not a branch).\n\nNot often when you cloned from a publishing repository, but if you\ncloned from a repository for your own work with an worktree, the\nHEAD at such an origin repository may have been detached, so it\nwould be sensible (I think \"git clone\" historically guesses over\neagerly to hide the detached HEAD state of the other side, though)\nif the clone's remote-tracking HEAD reflects it, I would think.\n\n> ... So maybe if we put \"Not a symbolic reference.\" in the referent\n> (which should be an invalid symref), the caller could check for that and then\n> should be able to distinguish this special case?\n\nYuck.\n\nAre we limited by the narrow interface that only passes \"referent\",\nor are you in the position that allows you to extend the interface\nto \"do it right\"?\n"},{"id":"507388","messageId":"xmqqjzd4yqlv.fsf@gitster.g","threadId":"62096","inReplyTo":"D5N4TN6WX65V.JJKLX5ROS8NN@ferdinandy.com","subject":"Re: [PATCH v12 4/8] remote set-head: better output for --auto","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-11-16T00:22:36Z","receivedAt":"2024-11-16T00:22:39Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Bence Ferdinandy\" <bence@ferdinandy.com> writes:\n\n>> so refs_read_symbolic_ref -> 1 only happens if there's a valid non-symbolic ref\n>> in origin/HEAD. So maybe if we put \"Not a symbolic reference.\" in the referent\n>> (which should be an invalid symref), the caller could check for that and then\n>> should be able to distinguish this special case?\n>\n> On second thought, it would maybe make even more sense to get the reference\n> hash and put that into referent. In that case the output could still be\n>\n> \"'%s/HEAD' has changed from '%s' and now points to '%s'\\n\"\n>\n> but with a non-symref after from.\n\nThe output may look like the above, but people would certainly wish\nto clarify and/or translate \"from '%s'\" part.  And to allow that, we\nmay need to signal a bit more explicitly that we saw a non-symref\nHEAD there so that we can tell between a detached HEAD and a\nconfusingly named branch whose name is 40-hex.\n\nOtherwise, one who tries to update that \"has changed from '%s'\"\nmessage to clarify between \"pointed at branch '%s'\" and \"pointed at\ncommit '%s'\" (or translate into a language where the distinction\nmatters, perhaps because branch and commit have different gender\nthere that affects conjugation for \"has changed from\" part, or\nsomething hand-wavy) have insufficient information to work with.\n\nThanks.\n\n\n\n"},{"id":"507393","messageId":"xmqqbjyfzk2u.fsf@gitster.g","threadId":"62096","inReplyTo":"54677502-a08c-4920-9b08-13517c8f027a@ferdinandy.com","subject":"Re: [PATCH v12 2/8] refs: atomically record overwritten ref in update_symref","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-11-16T07:58:17Z","receivedAt":"2024-11-16T07:58:21Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bence Ferdinandy <bence@ferdinandy.com> writes:\n\n> Sorry, it's late. The above is slightly bollocks since fetch ignores\n> any set_head errors later :)\n> But the idea stands that if we can set the head, let's do it.\n> The previous head is not important enough to die on.\n\nYes, I didn't mean to suggest aborting the fetch.  I just wanted to\nmake sure we _react_ correctly to a failing call to read a symref,\npossibly because the refs/remotes/$there/HEAD (1) does not exist, or\n(2) is not a symbolic ref but signals that the other side has its\nHEAD detached.  Treating any failure to read a symref as if the\nsymref does not exist would miss the latter case.\n\nThanks.\n\n"},{"id":"507401","messageId":"D5NOLJO397LO.3RS986OX5GIZ6@ferdinandy.com","threadId":"62096","inReplyTo":"xmqqo72gyqy7.fsf@gitster.g","subject":"Re: [PATCH v12 4/8] remote set-head: better output for --auto","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-16T14:43:36Z","receivedAt":"2024-11-16T14:49:26Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Sat Nov 16, 2024 at 01:15, Junio C Hamano <gitster@pobox.com> wrote:\n> \"Bence Ferdinandy\" <bence@ferdinandy.com> writes:\n>\n>> Good point, and yes, that is what happens. (Although I'm not quite sure how\n>> valid that state is where a remote's HEAD is not a branch).\n>\n> Not often when you cloned from a publishing repository, but if you\n> cloned from a repository for your own work with an worktree, the\n> HEAD at such an origin repository may have been detached, so it\n> would be sensible (I think \"git clone\" historically guesses over\n> eagerly to hide the detached HEAD state of the other side, though)\n> if the clone's remote-tracking HEAD reflects it, I would think.\n>\n>> ... So maybe if we put \"Not a symbolic reference.\" in the referent\n>> (which should be an invalid symref), the caller could check for that and then\n>> should be able to distinguish this special case?\n>\n> Yuck.\n>\n> Are we limited by the narrow interface that only passes \"referent\",\n> or are you in the position that allows you to extend the interface\n> to \"do it right\"?\n\nHow about we do the following if we can't read a symref:\n\n1) remember to return a new exit code for refs_update_symref_extended (I guess 2),\n   but continue on with updating HEAD\n\n2) read the non-symref ref into \"referent\" (I think this should not be able to\n   fail since, otherwise locking HEAD will already fail earlier)\n\nthis way the caller can check the exit code, see that it's 2, so not entirely\na success, but HEAD has been updated and can report things accordingly.\n\nDoes that sounds more reasonable?\n\nThanks,\nBence\n\n"},{"id":"507448","messageId":"D5OUMKNX6UU5.23Y4V7NHKGUWX@ttk.elte.hu","threadId":"62096","inReplyTo":"xmqqr07d11wt.fsf@gitster.g","subject":"Re: [PATCH v12 2/8] refs: atomically record overwritten ref in update_symref","fromName":"Bence Ferdinandy","fromEmail":"ferdinandy.bence@ttk.elte.hu","sentAt":"2024-11-17T23:39:43Z","receivedAt":"2024-11-17T23:39:52Z","isPatch":true,"sender":{"key":"ferdinandy.bence@ttk.elte.hu","avatar":null},"body":">\n>>  \t} +\tif (referent) +\t\trefs_read_symbolic_ref(refs, ref,\n>>  \treferent);\n\n\nSo I've been working on detecting a detached remote/HEAD, and it seems that\n\"refs_read_symbolic_ref\" behaves differently for the files and the reftables\nbackend. These are the exit codes in the various states:\n\n\n\t     reftables files\ndetached\t-1\t 1\t\ndoesn't exist   -1\t-1\n\nI would assume this is a bug in reftables? At least the behaviour of files is\nmore useful for this case ...\n\nThis now works fine with the files backend:\n\n\tif (referent && refs_read_symbolic_ref(refs, ref, referent) == 1) {\n\t\tstruct object_id oid;\n\t\trefs_read_ref(refs, ref, &oid);\n\t\tstrbuf_addstr(referent, oid_to_hex(&oid));\n\t\tret = -1;\n\t}\n\nAnd 4/8 can now also detect being detached, by checking the return value using\nthe test you suggested, but this fails for reftables. Just in case it might be\nsomething about the test not being correct:\n\n\ntest_expect_success 'set-head --auto to update a non symbolic ref' '\n\t(\n\t\tcd test &&\n\t\tgit update-ref --no-deref -d refs/remotes/origin/HEAD &&\n\t\tgit update-ref refs/remotes/origin/HEAD HEAD &&\n\t\tHEAD=$(git log --pretty=\"%H\") &&\n\t\tgit remote set-head --auto origin >output &&\n\t\techo \"${SQ}origin/HEAD${SQ} was detached at ${SQ}${HEAD}${SQ} and now points to ${SQ}main${SQ}\" >expect &&\n\t\ttest_cmp expect output\n\t)\n'\n\nShould I fix the reftables problem or should I mark the above test as a known\nbreakage? Can that be done for just reftables somehow? The only problem\nreftables suffers this way is to incorrectly report \"creation\" instead of\ncorrectly saying it was a detached head.\n\n\n> We could write the whole thing as a single \"do these and leave as\n> soon as we see any failure\" ||-cascade,\n>\n> \tif (!(transaction = ref_store_transaction_begin(refs, &err)) ||\n>\n> \t    ref_transaction_update(transaction, ref, NULL, NULL,\n> \t\t\t\t   target, NULL, REF_NO_DEREF,\n> \t\t\t\t   logmsg, &err) ||\n>\n> \t    ref_transaction_prepare(transaction, &err)) ||\n>\n> \t    (referent\n> \t    ? refs_read_symbolic_ref(refs, ref, referent)\n> \t    : 0) ||\n>\n> \t    ref_transaction_commit(transaction, &err)) {\n> \t\tif (!err.len)\n> \t\t\t... stuff default error message to err ...;\n> \t\tret = error(\"%s\", err.buf);\n> \t}\n>\n> which may not necessarily easier to follow (and in fact it is rather\n> ugly), but at least, the resulting code does not have to special\n> case the \"optionally peek into the symref\" step.\n\nI realised I misread the above snippet the last time I looked at it, but at\na first glance that would make 6/8 painful.\n\nThanks,\nBence\n\n-- \nbence.ferdinandy.com\n\n"},{"id":"507452","messageId":"xmqqh685xtmg.fsf@gitster.g","threadId":"62096","inReplyTo":"D5OUMKNX6UU5.23Y4V7NHKGUWX@ttk.elte.hu","subject":"Re: [PATCH v12 2/8] refs: atomically record overwritten ref in update_symref","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-11-18T00:39:35Z","receivedAt":"2024-11-18T00:39:39Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Bence Ferdinandy\" <ferdinandy.bence@ttk.elte.hu> writes:\n\n>>\n>>>  \t} +\tif (referent) +\t\trefs_read_symbolic_ref(refs, ref,\n>>>  \treferent);\n>\n>\n> So I've been working on detecting a detached remote/HEAD, and it seems that\n> \"refs_read_symbolic_ref\" behaves differently for the files and the reftables\n> backend. These are the exit codes in the various states:\n>\n>\n> \t     reftables files\n> detached\t-1\t 1\t\n> doesn't exist   -1\t-1\n>\n> I would assume this is a bug in reftables? At least the behaviour of files is\n> more useful for this case ...\n\nInteresting.  Patrick, comments?\n\n> This now works fine with the files backend:\n>\n> \tif (referent && refs_read_symbolic_ref(refs, ref, referent) == 1) {\n> \t\tstruct object_id oid;\n> \t\trefs_read_ref(refs, ref, &oid);\n> \t\tstrbuf_addstr(referent, oid_to_hex(&oid));\n> \t\tret = -1;\n> \t}\n>\n> And 4/8 can now also detect being detached, by checking the return value using\n> the test you suggested, but this fails for reftables. Just in case it might be\n> something about the test not being correct:\n>\n>\n> test_expect_success 'set-head --auto to update a non symbolic ref' '\n> \t(\n> \t\tcd test &&\n> \t\tgit update-ref --no-deref -d refs/remotes/origin/HEAD &&\n> \t\tgit update-ref refs/remotes/origin/HEAD HEAD &&\n> \t\tHEAD=$(git log --pretty=\"%H\") &&\n> \t\tgit remote set-head --auto origin >output &&\n> \t\techo \"${SQ}origin/HEAD${SQ} was detached at ${SQ}${HEAD}${SQ} and now points to ${SQ}main${SQ}\" >expect &&\n> \t\ttest_cmp expect output\n> \t)\n> '\n>\n> Should I fix the reftables problem or should I mark the above test as a known\n> breakage? Can that be done for just reftables somehow? The only problem\n> reftables suffers this way is to incorrectly report \"creation\" instead of\n> correctly saying it was a detached head.\n\nThanks.\n"},{"id":"507460","messageId":"ZzrrMrhRxOuB6QKH@pks.im","threadId":"62096","inReplyTo":"D5OUMKNX6UU5.23Y4V7NHKGUWX@ttk.elte.hu","subject":"Re: [PATCH v12 2/8] refs: atomically record overwritten ref in update_symref","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-11-18T07:22:26Z","receivedAt":"2024-11-18T07:22:42Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, Nov 18, 2024 at 12:39:43AM +0100, Bence Ferdinandy wrote:\n> >\n> >>  \t} +\tif (referent) +\t\trefs_read_symbolic_ref(refs, ref,\n> >>  \treferent);\n> \n> \n> So I've been working on detecting a detached remote/HEAD, and it seems that\n> \"refs_read_symbolic_ref\" behaves differently for the files and the reftables\n> backend. These are the exit codes in the various states:\n> \n> \n> \t     reftables files\n> detached\t-1\t 1\t\n> doesn't exist   -1\t-1\n> \n> I would assume this is a bug in reftables? At least the behaviour of files is\n> more useful for this case ...\n> \n> This now works fine with the files backend:\n> \n> \tif (referent && refs_read_symbolic_ref(refs, ref, referent) == 1) {\n> \t\tstruct object_id oid;\n> \t\trefs_read_ref(refs, ref, &oid);\n> \t\tstrbuf_addstr(referent, oid_to_hex(&oid));\n> \t\tret = -1;\n> \t}\n> \n> And 4/8 can now also detect being detached, by checking the return value using\n> the test you suggested, but this fails for reftables. Just in case it might be\n> something about the test not being correct:\n\nSo from what I understand you try to execute `refs_read_symbolic_ref()`\non a non-symbolic-ref, and your expectation is:\n\n  - It returns -1 when reading the ref has failed.\n\n  - It returns 0 when reading the ref was successful and it was a\n    symref.\n\n  - It retuns 1 when reading the ref was successful, but it was a\n    regular ref.\n\nThis behaviour isn't documented anywhere, so I wouldn't declare it a bug\nin the reftable backend. But what is a bug is that the two backends\nbehave differently, and that should be fixed indeed.\n\nI couldn't find any callsites of `refs_read_symbolic_ref()` where we\nrely on the current behaviour of either of the backends. We do have a\ncheck whether `refs_read_symbolic_ref()` returns negative in \"refs.c\" in\n`migrate_one_ref()`, but that one should be mostly fine given that we\ncheck for the type of the ref beforehand. \"Mostly\" though because it can\nhappen that we race with another writer that happened to convert the ref\nwe are about to migrate from a symbolic ref into a normal ref. Unlikely,\nbut it can happen in theory.\n\nI think it's an easy mistake to make to check for a negative return\ncode. So maybe we should adapt both backends to return -1 for generic\nfailures and -2 in case the ref is a regular ref?\n\nPatrick\n"},{"id":"507470","messageId":"D5P5FWL8I3SY.DPYSUTU8GAYJ@ferdinandy.com","threadId":"62096","inReplyTo":"ZzrrMrhRxOuB6QKH@pks.im","subject":"Re: [PATCH v12 2/8] refs: atomically record overwritten ref in update_symref","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-18T08:08:13Z","receivedAt":"2024-11-18T08:13:56Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Mon Nov 18, 2024 at 08:22, Patrick Steinhardt <ps@pks.im> wrote:\n>\n> So from what I understand you try to execute `refs_read_symbolic_ref()`\n> on a non-symbolic-ref, and your expectation is:\n>\n>   - It returns -1 when reading the ref has failed.\n>\n>   - It returns 0 when reading the ref was successful and it was a\n>     symref.\n>\n>   - It retuns 1 when reading the ref was successful, but it was a\n>     regular ref.\n\nWell, the other way around (it's how files backend does it), but I guess that\nis irrelevant at this point.\n\n>\n> This behaviour isn't documented anywhere, so I wouldn't declare it a bug\n> in the reftable backend. But what is a bug is that the two backends\n> behave differently, and that should be fixed indeed.\n>\n> I couldn't find any callsites of `refs_read_symbolic_ref()` where we\n> rely on the current behaviour of either of the backends. We do have a\n> check whether `refs_read_symbolic_ref()` returns negative in \"refs.c\" in\n> `migrate_one_ref()`, but that one should be mostly fine given that we\n> check for the type of the ref beforehand. \"Mostly\" though because it can\n> happen that we race with another writer that happened to convert the ref\n> we are about to migrate from a symbolic ref into a normal ref. Unlikely,\n> but it can happen in theory.\n>\n> I think it's an easy mistake to make to check for a negative return\n> code. So maybe we should adapt both backends to return -1 for generic\n> failures and -2 in case the ref is a regular ref?\n\nI've been wondering about this when writing other parts of the series and now\nis a good a time as any to ask: I've already seen this pattern of returning\nvarious negative integers as error codes, but never quite got the logic behind\nit. Why not just return the same numbers but positive?\n\nAnyhow, the proposed solution sounds good and as far as I see how things are\ndone in the code. I guess if I want the series to land I should just fix that\nas well, there are already a couple of not-entirely-related fixes in there :)\n\nTwo questions about that:\n\n- what would be the ideal place to document this behaviour? In refs.c with\n  `refs_read_symbolic_ref` or with the `struct ref_storage_be` in\n  refs/refs-internal.h?\n\n- should I look into adding specific tests for this? Since the rest of the\n  series will depend on this behaviour it will be implicit tested anyway, so\n  I don't particularly think it would be necessary, but I don't know what the\n  general approach is.\n\nThanks,\nBence\n"},{"id":"507473","messageId":"Zzr50O7AkczbqIo7@pks.im","threadId":"62096","inReplyTo":"D5P5FWL8I3SY.DPYSUTU8GAYJ@ferdinandy.com","subject":"Re: [PATCH v12 2/8] refs: atomically record overwritten ref in update_symref","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-11-18T08:24:56Z","receivedAt":"2024-11-18T08:25:11Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, Nov 18, 2024 at 09:08:13AM +0100, Bence Ferdinandy wrote:\n> On Mon Nov 18, 2024 at 08:22, Patrick Steinhardt <ps@pks.im> wrote:\n> > This behaviour isn't documented anywhere, so I wouldn't declare it a bug\n> > in the reftable backend. But what is a bug is that the two backends\n> > behave differently, and that should be fixed indeed.\n> >\n> > I couldn't find any callsites of `refs_read_symbolic_ref()` where we\n> > rely on the current behaviour of either of the backends. We do have a\n> > check whether `refs_read_symbolic_ref()` returns negative in \"refs.c\" in\n> > `migrate_one_ref()`, but that one should be mostly fine given that we\n> > check for the type of the ref beforehand. \"Mostly\" though because it can\n> > happen that we race with another writer that happened to convert the ref\n> > we are about to migrate from a symbolic ref into a normal ref. Unlikely,\n> > but it can happen in theory.\n> >\n> > I think it's an easy mistake to make to check for a negative return\n> > code. So maybe we should adapt both backends to return -1 for generic\n> > failures and -2 in case the ref is a regular ref?\n> \n> I've been wondering about this when writing other parts of the series and now\n> is a good a time as any to ask: I've already seen this pattern of returning\n> various negative integers as error codes, but never quite got the logic behind\n> it. Why not just return the same numbers but positive?\n\nIt's a matter of style, I guess. Many functions use the return value as\nboth an indicator for error and as the actual returned value. Think e.g.\nfunction calls like open(3p), where a negative value indicates an error\nand everything else is an actual file descriptor. This carries over into\nour codebase for many functions, but we're not consistent.\n\n> Anyhow, the proposed solution sounds good and as far as I see how things are\n> done in the code. I guess if I want the series to land I should just fix that\n> as well, there are already a couple of not-entirely-related fixes in there :)\n> \n> Two questions about that:\n> \n> - what would be the ideal place to document this behaviour? In refs.c with\n>   `refs_read_symbolic_ref` or with the `struct ref_storage_be` in\n>   refs/refs-internal.h?\n\nI'd document this in \"refs.h\", where the user-facing function is\ndeclared, and in \"refs-internal.h\", where the callback is defined.\n\n> - should I look into adding specific tests for this? Since the rest of the\n>   series will depend on this behaviour it will be implicit tested anyway, so\n>   I don't particularly think it would be necessary, but I don't know what the\n>   general approach is.\n\nI had a look and couldn't find another way to test the behaviour because\nwe use `refs_read_symbolic_ref()` sparingly, only. So I think it's okay\nto implicitly test this, only.\n\nPatrick\n"},{"id":"507491","messageId":"D5P9VVO3AN4K.3DKPEVZW6C911@ferdinandy.com","threadId":"62096","inReplyTo":"Zzr50O7AkczbqIo7@pks.im","subject":"Re: [PATCH v12 2/8] refs: atomically record overwritten ref in update_symref","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-18T11:37:09Z","receivedAt":"2024-11-18T11:38:20Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Mon Nov 18, 2024 at 09:24, Patrick Steinhardt <ps@pks.im> wrote:\n> On Mon, Nov 18, 2024 at 09:08:13AM +0100, Bence Ferdinandy wrote:\n>> On Mon Nov 18, 2024 at 08:22, Patrick Steinhardt <ps@pks.im> wrote:\n>> > This behaviour isn't documented anywhere, so I wouldn't declare it a bug\n>> > in the reftable backend. But what is a bug is that the two backends\n>> > behave differently, and that should be fixed indeed.\n>> >\n>> > I couldn't find any callsites of `refs_read_symbolic_ref()` where we\n>> > rely on the current behaviour of either of the backends. We do have a\n>> > check whether `refs_read_symbolic_ref()` returns negative in \"refs.c\" in\n>> > `migrate_one_ref()`, but that one should be mostly fine given that we\n>> > check for the type of the ref beforehand. \"Mostly\" though because it can\n>> > happen that we race with another writer that happened to convert the ref\n>> > we are about to migrate from a symbolic ref into a normal ref. Unlikely,\n>> > but it can happen in theory.\n>> >\n>> > I think it's an easy mistake to make to check for a negative return\n>> > code. So maybe we should adapt both backends to return -1 for generic\n>> > failures and -2 in case the ref is a regular ref?\n>> \n>> I've been wondering about this when writing other parts of the series and now\n>> is a good a time as any to ask: I've already seen this pattern of returning\n>> various negative integers as error codes, but never quite got the logic behind\n>> it. Why not just return the same numbers but positive?\n>\n> It's a matter of style, I guess. Many functions use the return value as\n> both an indicator for error and as the actual returned value. Think e.g.\n> function calls like open(3p), where a negative value indicates an error\n> and everything else is an actual file descriptor. This carries over into\n> our codebase for many functions, but we're not consistent.\n>\n>> Anyhow, the proposed solution sounds good and as far as I see how things are\n>> done in the code. I guess if I want the series to land I should just fix that\n>> as well, there are already a couple of not-entirely-related fixes in there :)\n>> \n>> Two questions about that:\n>> \n>> - what would be the ideal place to document this behaviour? In refs.c with\n>>   `refs_read_symbolic_ref` or with the `struct ref_storage_be` in\n>>   refs/refs-internal.h?\n>\n> I'd document this in \"refs.h\", where the user-facing function is\n> declared, and in \"refs-internal.h\", where the callback is defined.\n>\n>> - should I look into adding specific tests for this? Since the rest of the\n>>   series will depend on this behaviour it will be implicit tested anyway, so\n>>   I don't particularly think it would be necessary, but I don't know what the\n>>   general approach is.\n>\n> I had a look and couldn't find another way to test the behaviour because\n> we use `refs_read_symbolic_ref()` sparingly, only. So I think it's okay\n> to implicitly test this, only.\n\nThanks, it was surprisingly easy to do, I'll do a touchup of the other patches\nand will send a v13 today with the fix.\n\nBest,\nBence\n\n"},{"id":"507507","messageId":"20241118151755.756265-2-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241118151755.756265-1-bence@ferdinandy.com","subject":"[PATCH v13 1/9] t/t5505-remote: set default branch to main","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-18T15:09:20Z","receivedAt":"2024-11-18T15:33:49Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Consider the bare repository called \"mirror\" in the test. Running `git\nremote add --mirror -f origin ../one` will not change HEAD, consequently\nif init.defaultBranch is not the same as what HEAD in the remote\n(\"one\"), HEAD in \"mirror\" will be pointing to a non-existent reference.\nHence if \"mirror\" is used as a remote by yet another repository,\nls-remote will not show HEAD. On the other hand, if init.defaultBranch\nhappens to match HEAD in \"one\", then ls-remote will show HEAD.\n\nSince the \"ci/run-build-and-tests.sh\" script globally exports\nGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main, there may be a drift in some\ntests between how the test repositories are set up in the CI and during\nlocal testing, if the test itself uses \"master\" as default instead of\n\"main\". In particular, this happens in t5505-remote.sh. This issue does\nnot manifest currently, as the test does not do any remote HEAD\nmanipulation where this would come up, but should such things be added,\na locally passing test would break the CI and vice-versa.\n\nSet GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main in t5505-remote to be\nconsistent with the CI.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v9: - new patch\n        - a bandaid for the CI issue noticed by Taylor (cf:\n          https://lore.kernel.org/git/Zw8IKyPkG0Hr6%2F5t@nand.local/), but\n          see\n          https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/ for the root cause in detail\n    \n    v10: no change\n    \n    v11: no change\n    \n    v12: added forgotten sign-off\n    \n    v13: commit message udpated to be more precise\n\n t/t5505-remote.sh | 3 +++\n 1 file changed, 3 insertions(+)\n\ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex 532035933f..9b50276646 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -2,6 +2,9 @@\n \n test_description='git remote porcelain-ish'\n \n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n-- \n2.47.0.296.gda1ecfef29.dirty\n\n"},{"id":"507508","messageId":"20241118151755.756265-1-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241023153736.257733-1-bence@ferdinandy.com","subject":"[PATCH v13 0/9] set-head/fetch remote/HEAD updates","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-18T15:09:19Z","receivedAt":"2024-11-18T15:33:50Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"This iteration handles the special case when the previous HEAD was detached.\nThis case is now specifically printed by both remote set-head -a and fetch.\nThere were also some small cleanups in commit messages and unintended\nformattings.\n\nAlthough the patches themselves are versioned:\n\n- 2/9 is completely new (the above uncovered a discrepancy between backends)\n- 3/9 had smaller changes to accomodate this\n- 5/9 and 8/9 needed more substantial changes\n\n\nBence Ferdinandy (9):\n  t/t5505-remote: set default branch to main\n  refs: standardize output of refs_read_symbolic_ref\n  refs: atomically record overwritten ref in update_symref\n  remote set-head: refactor for readability\n  remote set-head: better output for --auto\n  refs: add TRANSACTION_CREATE_EXISTS error\n  refs: add create_only option to refs_update_symref_extended\n  fetch: set remote/HEAD if it does not exist\n  fetch set_head: handle mirrored bare repositories\n\n builtin/fetch.c                  | 105 +++++++++++++++++++++++++++++++\n builtin/remote.c                 |  72 +++++++++++++++------\n refs.c                           |  49 ++++++++++++---\n refs.h                           |  14 ++++-\n refs/files-backend.c             |  31 +++++----\n refs/refs-internal.h             |   6 ++\n refs/reftable-backend.c          |  10 ++-\n t/t4207-log-decoration-colors.sh |   3 +-\n t/t5505-remote.sh                |  95 ++++++++++++++++++++++++++--\n t/t5510-fetch.sh                 |  24 +++++++\n t/t5512-ls-remote.sh             |   2 +\n t/t5514-fetch-multiple.sh        |  17 ++++-\n t/t5516-fetch-push.sh            |   3 +-\n t/t5527-fetch-odd-refs.sh        |   3 +-\n t/t7900-maintenance.sh           |   3 +-\n t/t9210-scalar.sh                |   5 +-\n t/t9211-scalar-clone.sh          |   6 +-\n t/t9902-completion.sh            |  65 +++++++++++++++++++\n 18 files changed, 455 insertions(+), 58 deletions(-)\n\n-- \n2.47.0.296.gda1ecfef29.dirty\n\n"},{"id":"507509","messageId":"20241118151755.756265-4-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241118151755.756265-1-bence@ferdinandy.com","subject":"[PATCH v13 3/9] refs: atomically record overwritten ref in update_symref","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-18T15:09:22Z","receivedAt":"2024-11-18T15:33:51Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"When updating a symref with update_symref it's currently not possible to\nknow for sure what was the previous value that was overwritten. Extend\nrefs_update_symref under a new function name, to record the value after\nthe ref has been locked if the caller of refs_update_symref_extended\nrequests it via a new variable in the function call. Keep the original\nrefs_update_symref function with the same signature, but now as\na wrapper around refs_update_symref_extended.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v4: new patch\n    \n    v5: - added before_target to reftables backend\n        - added an extra safety check for transaction's existence in refs.c\n    \n    v6: - no change\n    \n    v7: - remove the whole before_target concept from the backends and\n          handle checking it in refs.c instead (thanks Karthik)\n        - rename the before_target to referent which is how the same concept\n          is called in the backends\n        - change commit prefix to be more in line with project standards\n    \n    v8: no change\n    \n    v9: - instead of adding parameters to refs_update_symref, rename what\n          was in v8 as refs_update_symref_extended and make refs_update_symref\n          a wrapper for that. This significantly reduces the number of files\n          that need to be touched, and avoids adding a lot of dummy NULL-s\n          in unrelated places.\n    \n    v10: no change\n    \n    v11: no change\n    \n    v12: no change\n    \n    v13: if referent is a non-symbolic ref, record the hash in referent and\n        signal this with a return value of -1\n\n refs.c | 24 ++++++++++++++++++++++--\n refs.h |  4 ++++\n 2 files changed, 26 insertions(+), 2 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 5f729ed412..c4500a7582 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2115,6 +2115,13 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct\n \n int refs_update_symref(struct ref_store *refs, const char *ref,\n \t\t       const char *target, const char *logmsg)\n+{\n+\treturn refs_update_symref_extended(refs, ref, target, logmsg, NULL);\n+}\n+\n+int refs_update_symref_extended(struct ref_store *refs, const char *ref,\n+\t\t       const char *target, const char *logmsg,\n+\t\t       struct strbuf *referent)\n {\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n@@ -2125,10 +2132,24 @@ int refs_update_symref(struct ref_store *refs, const char *ref,\n \t    ref_transaction_update(transaction, ref, NULL, NULL,\n \t\t\t\t   target, NULL, REF_NO_DEREF,\n \t\t\t\t   logmsg, &err) ||\n-\t    ref_transaction_commit(transaction, &err)) {\n+\t    ref_transaction_prepare(transaction, &err)) {\n \t\tret = error(\"%s\", err.buf);\n+\t\tgoto cleanup;\n+\t}\n+\tif (referent && refs_read_symbolic_ref(refs, ref, referent) == -2) {\n+\t\tstruct object_id oid;\n+\t\tif (!refs_read_ref(refs, ref, &oid)) {\n+\t\t\tstrbuf_addstr(referent, oid_to_hex(&oid));\n+\t\t\tret = -1;\n+\t\t} else {\n+\t\t\tret = 1;\n+\t\t}\n \t}\n \n+\tif (ref_transaction_commit(transaction, &err))\n+\t\tret = error(\"%s\", err.buf);\n+\n+cleanup:\n \tstrbuf_release(&err);\n \tif (transaction)\n \t\tref_transaction_free(transaction);\n@@ -2948,4 +2969,3 @@ int ref_update_expects_existing_old_ref(struct ref_update *update)\n \treturn (update->flags & REF_HAVE_OLD) &&\n \t\t(!is_null_oid(&update->old_oid) || update->old_target);\n }\n-\ndiff --git a/refs.h b/refs.h\nindex f8b714ca1d..92622e807d 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -579,6 +579,10 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,\n int refs_update_symref(struct ref_store *refs, const char *refname,\n \t\t       const char *target, const char *logmsg);\n \n+int refs_update_symref_extended(struct ref_store *refs, const char *refname,\n+\t\t       const char *target, const char *logmsg,\n+\t\t       struct strbuf *referent);\n+\n enum action_on_err {\n \tUPDATE_REFS_MSG_ON_ERR,\n \tUPDATE_REFS_DIE_ON_ERR,\n-- \n2.47.0.296.gda1ecfef29.dirty\n\n"},{"id":"507510","messageId":"20241118151755.756265-3-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241118151755.756265-1-bence@ferdinandy.com","subject":"[PATCH v13 2/9] refs: standardize output of refs_read_symbolic_ref","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-18T15:09:21Z","receivedAt":"2024-11-18T15:33:53Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"When the symbolic reference we want to read with refs_read_symbolic_ref\nis actually not a symbolic reference, the files and the reftable\nbackends return different values (1 and -1 respectively). Standardize\nthe returned values so that 0 is success, -1 is a generic error and -2\nis that the reference was actually non-symbolic.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v13: new patch\n\n refs.h                  | 6 ++++++\n refs/files-backend.c    | 7 +++----\n refs/refs-internal.h    | 6 ++++++\n refs/reftable-backend.c | 4 +++-\n 4 files changed, 18 insertions(+), 5 deletions(-)\n\ndiff --git a/refs.h b/refs.h\nindex 108dfc93b3..f8b714ca1d 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -83,6 +83,12 @@ int refs_read_ref_full(struct ref_store *refs, const char *refname,\n \n int refs_read_ref(struct ref_store *refs, const char *refname, struct object_id *oid);\n \n+/*\n+ * Return 0 if the symbolic reference could be read without error.\n+ * Return -1 for generic errors.\n+ * Return -2 if the reference was actually non-symbolic.\n+ */\n+\n int refs_read_symbolic_ref(struct ref_store *ref_store, const char *refname,\n \t\t\t   struct strbuf *referent);\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 0824c0b8a9..81e650ec48 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -596,10 +596,9 @@ static int files_read_symbolic_ref(struct ref_store *ref_store, const char *refn\n \tunsigned int type;\n \n \tret = read_ref_internal(ref_store, refname, &oid, referent, &type, &failure_errno, 1);\n-\tif (ret)\n-\t\treturn ret;\n-\n-\treturn !(type & REF_ISSYMREF);\n+\tif (!ret && !(type & REF_ISSYMREF))\n+\t\treturn -2;\n+\treturn ret;\n }\n \n int parse_loose_ref_contents(const struct git_hash_algo *algop,\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 2313c830d8..f0ef354bce 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -673,6 +673,12 @@ struct ref_storage_be {\n \n \tref_iterator_begin_fn *iterator_begin;\n \tread_raw_ref_fn *read_raw_ref;\n+\n+\t/*\n+\t * Return 0 if the symbolic reference could be read without error.\n+\t * Return -1 for generic errors.\n+\t * Return -2 if the reference was actually non-symbolic.\n+\t */\n \tread_symbolic_ref_fn *read_symbolic_ref;\n \n \treflog_iterator_begin_fn *reflog_iterator_begin;\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 38eb14d591..60cb83f23a 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -830,7 +830,9 @@ static int reftable_be_read_symbolic_ref(struct ref_store *ref_store,\n \t\treturn ret;\n \n \tret = reftable_stack_read_ref(stack, refname, &ref);\n-\tif (ret == 0 && ref.value_type == REFTABLE_REF_SYMREF)\n+\tif (!ret && (ref.value_type != REFTABLE_REF_SYMREF))\n+\t\tret = -2;\n+\telse if (!ret && (ref.value_type == REFTABLE_REF_SYMREF))\n \t\tstrbuf_addstr(referent, ref.value.symref);\n \telse\n \t\tret = -1;\n-- \n2.47.0.296.gda1ecfef29.dirty\n\n"},{"id":"507511","messageId":"20241118151755.756265-6-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241118151755.756265-1-bence@ferdinandy.com","subject":"[PATCH v13 5/9] remote set-head: better output for --auto","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-18T15:09:24Z","receivedAt":"2024-11-18T15:34:00Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Currently, set-head --auto will print a message saying \"remote/HEAD set\nto branch\", which implies something was changed.\n\nChange the output of --auto, so the output actually reflects what was\ndone: a) set a previously unset HEAD, b) change HEAD because remote\nchanged or c) no updates. As edge cases, if HEAD is changed from\na previous symbolic reference that was not a remote branch, explicitly\ncall attention to this fact, and also notify the user if the previous\nreference was not a symbolic reference.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v1-v2:\n    \n        was RFC in https://lore.kernel.org/git/20240910203835.2288291-1-bence@ferdinandy.com/\n    \n    v3:\n    \n        This patch was originally sent along when I thought set-head was\n        going to be invoked by fetch, but the discussion on the RFC\n        concluded that it should be not. This opened the possibility to make\n        it more explicit.\n    \n        Note: although I feel both things the patch does are really just\n        cosmetic, an argument could be made for breaking it into two, one\n        for the no-op part and one for the --auto print update.\n    \n        Was sent in:\n        https://lore.kernel.org/git/20240915221055.904107-1-bence@ferdinandy.com/\n    \n    v4:\n        - changes are now handled atomically via the ref update transaction\n        - outputs have changed along the lines of Junio's suggestion\n        - minor refactor to set_head for improved legibility\n    \n    v5: - the minor refactor has been split out into its own patch\n    \n    v6: - fixed uninitialized prev_head\n        - fixed case of unusual previous target\n        - fixed a test that would have been actually broken at this patch\n          (the output was only correct with the later update to fetch)\n        - added 4 tests for the 4 output cases\n    \n    v7: - change commit prefix to be more in line with project standards\n        - fixed tests to also work with the reftable backend\n        - renamed report function, fixed style issue with checking buf len\n        - fixed not releasing an strbuf\n    \n    v8: no change\n    \n    v9: - mark output strings in report_set_head_auto as translatable\n        - rename buf_prev to b_local_head for consistency\n        - use ${SQ} in tests instead of '\\''\n    \n    v10: no change\n    \n    v11: no change\n    \n    v12: no change\n    \n    v13: added handling the edge case of previous remote/HEAD being\n    a detached HEAD\n\n builtin/remote.c  | 59 +++++++++++++++++++++++++++++++++++---------\n t/t5505-remote.sh | 63 ++++++++++++++++++++++++++++++++++++++++++++++-\n 2 files changed, 109 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 1d68c5b2ba..a682ef5df2 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1399,10 +1399,38 @@ static int show(int argc, const char **argv, const char *prefix)\n \treturn result;\n }\n \n+static void report_set_head_auto(const char *remote, const char *head_name,\n+\t\t\tstruct strbuf *b_local_head, int updateres) {\n+\tstruct strbuf buf_prefix = STRBUF_INIT;\n+\tconst char *prev_head = NULL;\n+\n+\tstrbuf_addf(&buf_prefix, \"refs/remotes/%s/\", remote);\n+\tskip_prefix(b_local_head->buf, buf_prefix.buf, &prev_head);\n+\n+\tif (prev_head && !strcmp(prev_head, head_name))\n+\t\tprintf(_(\"'%s/HEAD' is unchanged and points to '%s'\\n\"),\n+\t\t\tremote, head_name);\n+\telse if (prev_head)\n+\t\tprintf(_(\"'%s/HEAD' has changed from '%s' and now points to '%s'\\n\"),\n+\t\t\tremote, prev_head, head_name);\n+\telse if (!b_local_head->len)\n+\t\tprintf(_(\"'%s/HEAD' is now created and points to '%s'\\n\"),\n+\t\t\tremote, head_name);\n+\telse if (!!updateres && b_local_head->len)\n+\t\tprintf(_(\"'%s/HEAD' was detached at '%s' and now points to '%s'\\n\"),\n+\t\t\tremote, b_local_head->buf, head_name);\n+\telse\n+\t\tprintf(_(\"'%s/HEAD' used to point to '%s' \"\n+\t\t\t\"(which is not a remote branch), but now points to '%s'\\n\"),\n+\t\t\tremote, b_local_head->buf, head_name);\n+\tstrbuf_release(&buf_prefix);\n+}\n+\n static int set_head(int argc, const char **argv, const char *prefix)\n {\n-\tint i, opt_a = 0, opt_d = 0, result = 0;\n-\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;\n+\tint i, opt_a = 0, opt_d = 0, result = 0, updateres;\n+\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,\n+\t\tb_local_head = STRBUF_INIT;\n \tchar *head_name = NULL;\n \tstruct ref_store *refs = get_main_ref_store(the_repository);\n \n@@ -1440,20 +1468,27 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t} else\n \t\tusage_with_options(builtin_remote_sethead_usage, options);\n \n-\tif (head_name) {\n-\t\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", argv[0], head_name);\n-\t\t/* make sure it's valid */\n-\t\tif (!refs_ref_exists(refs, b_remote_head.buf))\n-\t\t\tresult |= error(_(\"Not a valid ref: %s\"), b_remote_head.buf);\n-\t\telse if (refs_update_symref(refs, b_head.buf, b_remote_head.buf, \"remote set-head\"))\n-\t\t\tresult |= error(_(\"Could not setup %s\"), b_head.buf);\n-\t\telse if (opt_a)\n-\t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\n-\t\tfree(head_name);\n+\tif (!head_name)\n+\t\tgoto cleanup;\n+\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", argv[0], head_name);\n+\tif (!refs_ref_exists(refs, b_remote_head.buf)) {\n+\t\tresult |= error(_(\"Not a valid ref: %s\"), b_remote_head.buf);\n+\t\tgoto cleanup;\n+\t}\n+\tupdateres = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n+\t\t\t\"remote set-head\", &b_local_head);\n+\tif (updateres == -2) {\n+\t\tresult |= error(_(\"Could not setup %s\"), b_head.buf);\n+\t\tgoto cleanup;\n \t}\n+\tif (opt_a)\n+\t\treport_set_head_auto(argv[0], head_name, &b_local_head, updateres);\n \n+cleanup:\n+\tfree(head_name);\n \tstrbuf_release(&b_head);\n \tstrbuf_release(&b_remote_head);\n+\tstrbuf_release(&b_local_head);\n \treturn result;\n }\n \ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex 9b50276646..807df00ba7 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -432,12 +432,63 @@ test_expect_success 'set-head --auto' '\n \t)\n '\n \n+test_expect_success 'set-head --auto detects creation' '\n+\t(\n+\t\tcd test &&\n+\t\tgit update-ref --no-deref -d refs/remotes/origin/HEAD &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"${SQ}origin/HEAD${SQ} is now created and points to ${SQ}main${SQ}\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n+test_expect_success 'set-head --auto to update a non symbolic ref' '\n+\t(\n+\t\tcd test &&\n+\t\tgit update-ref --no-deref -d refs/remotes/origin/HEAD &&\n+\t\tgit update-ref refs/remotes/origin/HEAD HEAD &&\n+\t\tHEAD=$(git log --pretty=\"%H\") &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"${SQ}origin/HEAD${SQ} was detached at ${SQ}${HEAD}${SQ} and now points to ${SQ}main${SQ}\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n+test_expect_success 'set-head --auto detects no change' '\n+\t(\n+\t\tcd test &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"${SQ}origin/HEAD${SQ} is unchanged and points to ${SQ}main${SQ}\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n+test_expect_success 'set-head --auto detects change' '\n+\t(\n+\t\tcd test &&\n+\t\tgit symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/ahead &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"${SQ}origin/HEAD${SQ} has changed from ${SQ}ahead${SQ} and now points to ${SQ}main${SQ}\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n+test_expect_success 'set-head --auto detects strange ref' '\n+\t(\n+\t\tcd test &&\n+\t\tgit symbolic-ref refs/remotes/origin/HEAD refs/heads/main &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"${SQ}origin/HEAD${SQ} used to point to ${SQ}refs/heads/main${SQ} (which is not a remote branch), but now points to ${SQ}main${SQ}\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n test_expect_success 'set-head --auto has no problem w/multiple HEADs' '\n \t(\n \t\tcd test &&\n \t\tgit fetch two \"refs/heads/*:refs/remotes/two/*\" &&\n \t\tgit remote set-head --auto two >output 2>&1 &&\n-\t\techo \"two/HEAD set to main\" >expect &&\n+\t\techo \"${SQ}two/HEAD${SQ} is now created and points to ${SQ}main${SQ}\" >expect &&\n \t\ttest_cmp expect output\n \t)\n '\n@@ -456,6 +507,16 @@ test_expect_success 'set-head explicit' '\n \t)\n '\n \n+test_expect_success 'set-head --auto reports change' '\n+\t(\n+\t\tcd test &&\n+\t\tgit remote set-head origin side2 &&\n+\t\tgit remote set-head --auto origin >output 2>&1 &&\n+\t\techo \"${SQ}origin/HEAD${SQ} has changed from ${SQ}side2${SQ} and now points to ${SQ}main${SQ}\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n cat >test/expect <<EOF\n Pruning origin\n URL: $(pwd)/one\n-- \n2.47.0.296.gda1ecfef29.dirty\n\n"},{"id":"507512","messageId":"20241118151755.756265-5-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241118151755.756265-1-bence@ferdinandy.com","subject":"[PATCH v13 4/9] remote set-head: refactor for readability","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-18T15:09:23Z","receivedAt":"2024-11-18T15:34:01Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Make two different readability refactors:\n\nRename strbufs \"buf\" and \"buf2\" to something more explanatory.\n\nInstead of calling get_main_ref_store(the_repository) multiple times,\ncall it once and store the result in a new refs variable. Although this\nchange probably offers some performance benefits, the main purpose is to\nshorten the line lengths of function calls using this variable.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v5: new patch (split from the next patch as a preparatory step)\n    \n    v6: no change\n    \n    v7: - change commit prefix to be more in line with project standards\n    \n    v8: no change\n    \n    v9: - further improve readability by renaming buf, and buf2 consistently\n          with how patch 6 was already done\n    \n    v10: no change\n    \n    v11: no change\n    \n    v12: no change\n    \n    v13: more precise wording for commit message\n\n builtin/remote.c | 23 ++++++++++++-----------\n 1 file changed, 12 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 76670ddd8b..1d68c5b2ba 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1402,8 +1402,9 @@ static int show(int argc, const char **argv, const char *prefix)\n static int set_head(int argc, const char **argv, const char *prefix)\n {\n \tint i, opt_a = 0, opt_d = 0, result = 0;\n-\tstruct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;\n+\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;\n \tchar *head_name = NULL;\n+\tstruct ref_store *refs = get_main_ref_store(the_repository);\n \n \tstruct option options[] = {\n \t\tOPT_BOOL('a', \"auto\", &opt_a,\n@@ -1415,7 +1416,7 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \targc = parse_options(argc, argv, prefix, options,\n \t\t\t     builtin_remote_sethead_usage, 0);\n \tif (argc)\n-\t\tstrbuf_addf(&buf, \"refs/remotes/%s/HEAD\", argv[0]);\n+\t\tstrbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", argv[0]);\n \n \tif (!opt_a && !opt_d && argc == 2) {\n \t\thead_name = xstrdup(argv[1]);\n@@ -1434,25 +1435,25 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\t\thead_name = xstrdup(states.heads.items[0].string);\n \t\tfree_remote_ref_states(&states);\n \t} else if (opt_d && !opt_a && argc == 1) {\n-\t\tif (refs_delete_ref(get_main_ref_store(the_repository), NULL, buf.buf, NULL, REF_NO_DEREF))\n-\t\t\tresult |= error(_(\"Could not delete %s\"), buf.buf);\n+\t\tif (refs_delete_ref(refs, NULL, b_head.buf, NULL, REF_NO_DEREF))\n+\t\t\tresult |= error(_(\"Could not delete %s\"), b_head.buf);\n \t} else\n \t\tusage_with_options(builtin_remote_sethead_usage, options);\n \n \tif (head_name) {\n-\t\tstrbuf_addf(&buf2, \"refs/remotes/%s/%s\", argv[0], head_name);\n+\t\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", argv[0], head_name);\n \t\t/* make sure it's valid */\n-\t\tif (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))\n-\t\t\tresult |= error(_(\"Not a valid ref: %s\"), buf2.buf);\n-\t\telse if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote set-head\"))\n-\t\t\tresult |= error(_(\"Could not setup %s\"), buf.buf);\n+\t\tif (!refs_ref_exists(refs, b_remote_head.buf))\n+\t\t\tresult |= error(_(\"Not a valid ref: %s\"), b_remote_head.buf);\n+\t\telse if (refs_update_symref(refs, b_head.buf, b_remote_head.buf, \"remote set-head\"))\n+\t\t\tresult |= error(_(\"Could not setup %s\"), b_head.buf);\n \t\telse if (opt_a)\n \t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\n \t\tfree(head_name);\n \t}\n \n-\tstrbuf_release(&buf);\n-\tstrbuf_release(&buf2);\n+\tstrbuf_release(&b_head);\n+\tstrbuf_release(&b_remote_head);\n \treturn result;\n }\n \n-- \n2.47.0.296.gda1ecfef29.dirty\n\n"},{"id":"507513","messageId":"20241118151755.756265-7-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241118151755.756265-1-bence@ferdinandy.com","subject":"[PATCH v13 6/9] refs: add TRANSACTION_CREATE_EXISTS error","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-18T15:09:25Z","receivedAt":"2024-11-18T15:34:01Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Currently there is only one special error for transaction, for when\nthere is a naming conflict, all other errors are dumped under a generic\nerror. Add a new special error case for when the caller requests the\nreference to be updated only when it does not yet exist and the\nreference actually does exist.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v4: new patch\n    v5: no change\n    v6: no change\n    \n    v7: - change commit prefix to be more in line with project standards\n        - changed error checking to Karthik's suggestion\n    \n    v8: no change\n    \n    v9: - no change\n    \n    v10: no change\n    \n    v11: no change\n    \n    v12: no change\n    \n    v13: no change\n\n refs.h                  |  4 +++-\n refs/files-backend.c    | 24 ++++++++++++++++--------\n refs/reftable-backend.c |  6 ++++--\n 3 files changed, 23 insertions(+), 11 deletions(-)\n\ndiff --git a/refs.h b/refs.h\nindex 92622e807d..6a32f82f8c 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -768,8 +768,10 @@ int ref_transaction_verify(struct ref_transaction *transaction,\n \n /* Naming conflict (for example, the ref names A and A/B conflict). */\n #define TRANSACTION_NAME_CONFLICT -1\n+/* When only creation was requested, but the ref already exists. */\n+#define TRANSACTION_CREATE_EXISTS -2\n /* All other errors. */\n-#define TRANSACTION_GENERIC_ERROR -2\n+#define TRANSACTION_GENERIC_ERROR -3\n \n /*\n  * Perform the preparatory stages of committing `transaction`. Acquire\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 81e650ec48..86674705a7 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2501,14 +2501,18 @@ static int split_symref_update(struct ref_update *update,\n static int check_old_oid(struct ref_update *update, struct object_id *oid,\n \t\t\t struct strbuf *err)\n {\n+\tint ret = TRANSACTION_GENERIC_ERROR;\n+\n \tif (!(update->flags & REF_HAVE_OLD) ||\n \t\t   oideq(oid, &update->old_oid))\n \t\treturn 0;\n \n-\tif (is_null_oid(&update->old_oid))\n+\tif (is_null_oid(&update->old_oid)) {\n \t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n \t\t\t    \"reference already exists\",\n \t\t\t    ref_update_original_update_refname(update));\n+\t\tret = TRANSACTION_CREATE_EXISTS;\n+\t}\n \telse if (is_null_oid(oid))\n \t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n \t\t\t    \"reference is missing but expected %s\",\n@@ -2521,7 +2525,7 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid,\n \t\t\t    oid_to_hex(oid),\n \t\t\t    oid_to_hex(&update->old_oid));\n \n-\treturn -1;\n+\treturn ret;\n }\n \n /*\n@@ -2601,9 +2605,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\t\tgoto out;\n \t\t\t\t}\n-\t\t\t} else if  (check_old_oid(update, &lock->old_oid, err)) {\n-\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n-\t\t\t\tgoto out;\n+\t\t\t} else {\n+\t\t\t\tret = check_old_oid(update, &lock->old_oid, err);\n+\t\t\t\tif  (ret) {\n+\t\t\t\t\tgoto out;\n+\t\t\t\t}\n \t\t\t}\n \t\t} else {\n \t\t\t/*\n@@ -2634,9 +2640,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\t\t    update->old_target);\n \t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\tgoto out;\n-\t\t} else if  (check_old_oid(update, &lock->old_oid, err)) {\n-\t\t\tret = TRANSACTION_GENERIC_ERROR;\n-\t\t\tgoto out;\n+\t\t} else {\n+\t\t\tret = check_old_oid(update, &lock->old_oid, err);\n+\t\t\tif  (ret) {\n+\t\t\t\tgoto out;\n+\t\t\t}\n \t\t}\n \n \t\t/*\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 60cb83f23a..4f2e615eae 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1208,10 +1208,13 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\tgoto done;\n \t\t\t}\n \t\t} else if ((u->flags & REF_HAVE_OLD) && !oideq(&current_oid, &u->old_oid)) {\n-\t\t\tif (is_null_oid(&u->old_oid))\n+\t\t\tret = TRANSACTION_NAME_CONFLICT;\n+\t\t\tif (is_null_oid(&u->old_oid)) {\n \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n \t\t\t\t\t\t   \"reference already exists\"),\n \t\t\t\t\t    ref_update_original_update_refname(u));\n+\t\t\t\tret = TRANSACTION_CREATE_EXISTS;\n+\t\t\t}\n \t\t\telse if (is_null_oid(&current_oid))\n \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n \t\t\t\t\t\t   \"reference is missing but expected %s\"),\n@@ -1223,7 +1226,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\t\t    ref_update_original_update_refname(u),\n \t\t\t\t\t    oid_to_hex(&current_oid),\n \t\t\t\t\t    oid_to_hex(&u->old_oid));\n-\t\t\tret = -1;\n \t\t\tgoto done;\n \t\t}\n \n-- \n2.47.0.296.gda1ecfef29.dirty\n\n"},{"id":"507517","messageId":"20241118151755.756265-8-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241118151755.756265-1-bence@ferdinandy.com","subject":"[PATCH v13 7/9] refs: add create_only option to refs_update_symref_extended","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-18T15:09:26Z","receivedAt":"2024-11-18T15:34:08Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Allow the caller to specify that it only wants to update the symref if\nit does not already exist. Silently ignore the error from the\ntransaction API if the symref already exists.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v4: new patch\n    v5: no change\n    \n    v6: - switched from bool to int for create_only\n        - refactored logic in refs_update_symref with goto as layed out by\n          Junio\n    \n    v7: - change commit prefix to be more in line with project standards\n        - refactored code to accommodate changes in the first patch, but\n          otherwise no change\n    \n    v8: no change\n    \n    v9: - no change (except for following through the\n          refs_update_symref_extended refactoring)\n    \n    v10: no change\n    \n    v11: no change\n    \n    v12: no change\n    \n    v13: changes only due to changes in previous patch\n\n builtin/remote.c |  2 +-\n refs.c           | 33 ++++++++++++++++++++++++---------\n refs.h           |  2 +-\n 3 files changed, 26 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex a682ef5df2..7b4eb0e30b 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1476,7 +1476,7 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\tgoto cleanup;\n \t}\n \tupdateres = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n-\t\t\t\"remote set-head\", &b_local_head);\n+\t\t\t\"remote set-head\", &b_local_head, 0);\n \tif (updateres == -2) {\n \t\tresult |= error(_(\"Could not setup %s\"), b_head.buf);\n \t\tgoto cleanup;\ndiff --git a/refs.c b/refs.c\nindex c4500a7582..f4d8a8dcb1 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2116,26 +2116,38 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct\n int refs_update_symref(struct ref_store *refs, const char *ref,\n \t\t       const char *target, const char *logmsg)\n {\n-\treturn refs_update_symref_extended(refs, ref, target, logmsg, NULL);\n+\treturn refs_update_symref_extended(refs, ref, target, logmsg, NULL, 0);\n }\n \n int refs_update_symref_extended(struct ref_store *refs, const char *ref,\n \t\t       const char *target, const char *logmsg,\n-\t\t       struct strbuf *referent)\n+\t\t       struct strbuf *referent, int create_only)\n {\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n-\tint ret = 0;\n+\tint ret = 0, prepret = 0;\n \n \ttransaction = ref_store_transaction_begin(refs, &err);\n-\tif (!transaction ||\n-\t    ref_transaction_update(transaction, ref, NULL, NULL,\n-\t\t\t\t   target, NULL, REF_NO_DEREF,\n-\t\t\t\t   logmsg, &err) ||\n-\t    ref_transaction_prepare(transaction, &err)) {\n+\tif (!transaction) {\n+\terror_return:\n \t\tret = error(\"%s\", err.buf);\n \t\tgoto cleanup;\n \t}\n+\tif (create_only) {\n+\t\tif (ref_transaction_create(transaction, ref, NULL, target,\n+\t\t\t\t\t   REF_NO_DEREF, logmsg, &err))\n+\t\t\tgoto error_return;\n+\t\tprepret = ref_transaction_prepare(transaction, &err);\n+\t\tif (prepret && prepret != TRANSACTION_CREATE_EXISTS)\n+\t\t\tgoto error_return;\n+\t} else {\n+\t\tif (ref_transaction_update(transaction, ref, NULL, NULL,\n+\t\t\t\t\t   target, NULL, REF_NO_DEREF,\n+\t\t\t\t\t   logmsg, &err) ||\n+\t\t\tref_transaction_prepare(transaction, &err))\n+\t\t\tgoto error_return;\n+\t}\n+\n \tif (referent && refs_read_symbolic_ref(refs, ref, referent) == -2) {\n \t\tstruct object_id oid;\n \t\tif (!refs_read_ref(refs, ref, &oid)) {\n@@ -2146,8 +2158,11 @@ int refs_update_symref_extended(struct ref_store *refs, const char *ref,\n \t\t}\n \t}\n \n+\tif (prepret == TRANSACTION_CREATE_EXISTS)\n+\t\tgoto cleanup;\n+\n \tif (ref_transaction_commit(transaction, &err))\n-\t\tret = error(\"%s\", err.buf);\n+\t\tgoto error_return;\n \n cleanup:\n \tstrbuf_release(&err);\ndiff --git a/refs.h b/refs.h\nindex 6a32f82f8c..f131e89fe6 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -581,7 +581,7 @@ int refs_update_symref(struct ref_store *refs, const char *refname,\n \n int refs_update_symref_extended(struct ref_store *refs, const char *refname,\n \t\t       const char *target, const char *logmsg,\n-\t\t       struct strbuf *referent);\n+\t\t       struct strbuf *referent, int create_only);\n \n enum action_on_err {\n \tUPDATE_REFS_MSG_ON_ERR,\n-- \n2.47.0.296.gda1ecfef29.dirty\n\n"},{"id":"507522","messageId":"20241118151755.756265-10-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241118151755.756265-1-bence@ferdinandy.com","subject":"[PATCH v13 9/9] fetch set_head: handle mirrored bare repositories","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-18T15:09:28Z","receivedAt":"2024-11-18T15:34:17Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"When adding a remote to bare repository with \"git remote add --mirror\",\nrunning fetch will fail to update HEAD to the remote's HEAD, since it\ndoes not know how to handle bare repositories. On the other hand HEAD\nalready has content, since \"git init --bare\" has already set HEAD to\nwhatever is the default branch set for the user. Unless this - by chance\n- is the same as the remote's HEAD, HEAD will be pointing to a bad\nsymref. Teach set_head to handle bare repositories, by overwriting HEAD\nso it mirrors the remote's HEAD.\n\nNote, that in this case overriding the local HEAD reference is\nnecessary, since HEAD will exist before fetch can be run, but this\nshould not be an issue, since the whole purpose of --mirror is to be an\nexact mirror of the remote, so following any changes to HEAD makes\nsense.\n\nAlso note, that although \"git remote set-head\" also fails when trying to\nupdate the remote's locally tracked HEAD in a mirrored bare repository,\nthe usage of the command does not make much sense after this patch:\nfetch will update the remote HEAD correctly, and setting it manually to\nsomething else is antithetical to the concept of mirroring.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v10: - new patch\n         - handles the issue discovered in\n           https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/T/\n    \n    v11: no change\n    \n    v12: no change\n    \n    v13: properly print the previous head if it was detached\n\n builtin/fetch.c   | 16 +++++++++++-----\n t/t5505-remote.sh | 10 ++++++++++\n 2 files changed, 21 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 3d70cd1add..5e37b9c00f 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1608,7 +1608,7 @@ static const char *strip_refshead(const char *name){\n \n static int set_head(const struct ref *remote_refs)\n {\n-\tint result = 0, updateres;\n+\tint result = 0, updateres, is_bare;\n \tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,\n \t\tb_local_head = STRBUF_INIT;\n \tconst char *remote = gtransport->remote->name;\n@@ -1641,15 +1641,21 @@ static int set_head(const struct ref *remote_refs)\n \n \tif (!head_name)\n \t\tgoto cleanup;\n-\tstrbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", remote);\n-\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", remote, head_name);\n+\tis_bare = is_bare_repository();\n+\tif (is_bare) {\n+\t\tstrbuf_addstr(&b_head, \"HEAD\");\n+\t\tstrbuf_addf(&b_remote_head, \"refs/heads/%s\", head_name);\n+\t} else {\n+\t\tstrbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", remote);\n+\t\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", remote, head_name);\n+\t}\n \t\t/* make sure it's valid */\n-\tif (!refs_ref_exists(refs, b_remote_head.buf)) {\n+\tif (!is_bare && !refs_ref_exists(refs, b_remote_head.buf)) {\n \t\tresult = 1;\n \t\tgoto cleanup;\n \t}\n \tupdateres = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n-\t\t\t\t\t\"fetch\", &b_local_head, 1);\n+\t\t\t\t\t\"fetch\", &b_local_head, !is_bare);\n \tif (updateres == -1) {\n \t\tresult = 1;\n \t\tgoto cleanup;\ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex 660310239c..4cc5df2e61 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -557,6 +557,16 @@ test_expect_success 'add --mirror && prune' '\n \t)\n '\n \n+test_expect_success 'add --mirror setting HEAD' '\n+\tmkdir headmirror &&\n+\t(\n+\t\tcd headmirror &&\n+\t\tgit init --bare -b notmain &&\n+\t\tgit remote add --mirror -f origin ../one &&\n+\t\ttest \"$(git symbolic-ref HEAD)\" = \"refs/heads/main\"\n+\t)\n+'\n+\n test_expect_success 'add --mirror=fetch' '\n \tmkdir mirror-fetch &&\n \tgit init -b main mirror-fetch/parent &&\n-- \n2.47.0.296.gda1ecfef29.dirty\n\n"},{"id":"507524","messageId":"20241118151755.756265-9-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241118151755.756265-1-bence@ferdinandy.com","subject":"[PATCH v13 8/9] fetch: set remote/HEAD if it does not exist","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-18T15:09:27Z","receivedAt":"2024-11-18T15:34:25Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"If the user has remote/HEAD set already and it looks like it has changed\non the server, then print a message, otherwise set it if we can.\nSilently pass if the user already has the same remote/HEAD set as\nreported by the server or if we encounter any errors along the way.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v3: - does not rely on remote set-head anymore so it only authenticates\n        once\n        - uses the new REF_CREATE_ONLY to atomically check if the ref exists\n          and only write it if it doesn't\n        - in all other cases the maximum it does is print a warning\n    \n    v4: - instead of the discarded REF_CREATE_ONLY, it uses the existing,\n          but updated transaction api to request a silent create only\n        - it now uses the atomic before_target to determine reporting\n        - refactored for legibility\n    \n    v5: - instead of printing a not too useful message, it now fails\n          silently, this in line with the objective to only set up\n          remote/HEAD automatically if the right thing is trivial, for\n          everything else there is remote set-head\n        - fixed all failing tests\n        - added two new tests, one for checking if remote/HEAD is set to the\n          correct one, and one to test that we do not override remote/HEAD\n          if it has changed on the server from what we have locally\n    \n    v6: - fixed style issues and unintended extra empty line\n        - updated function call with bool to int from previous patch's\n          change\n        - removed calls to error(...) inherited from builtin/remote.c so we\n          actually fail silently\n        - set the test for remote set-head --auto to the correct value here,\n          which was previously erronously set in the remote set-head patch\n    \n    v7: - no change\n    \n    v8: - changed logmsg in call to refs_update_symref from \"remote\n          set-head\" to \"fetch\"\n    \n    v9: - follow through with refs_update_symref_extended\n        - fix test errors uncovered by the new patch\n    \n    v10: no change\n    \n    v11: fixed some memory leaks\n    \n    v12: no change\n    \n    v13: - fix printed information if the local HEAD is detached\n         - remove accidental formatting noise in a test\n\n builtin/fetch.c                  | 99 ++++++++++++++++++++++++++++++++\n t/t4207-log-decoration-colors.sh |  3 +-\n t/t5505-remote.sh                | 21 +++++--\n t/t5510-fetch.sh                 | 24 ++++++++\n t/t5512-ls-remote.sh             |  2 +\n t/t5514-fetch-multiple.sh        | 17 +++++-\n t/t5516-fetch-push.sh            |  3 +-\n t/t5527-fetch-odd-refs.sh        |  3 +-\n t/t7900-maintenance.sh           |  3 +-\n t/t9210-scalar.sh                |  5 +-\n t/t9211-scalar-clone.sh          |  6 +-\n t/t9902-completion.sh            | 65 +++++++++++++++++++++\n 12 files changed, 234 insertions(+), 17 deletions(-)\n\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 18eff4e5fa..3d70cd1add 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1578,6 +1578,97 @@ static int backfill_tags(struct display_state *display_state,\n \treturn retcode;\n }\n \n+static void report_set_head(const char *remote, const char *head_name,\n+\t\t\tstruct strbuf *buf_prev, int updateres) {\n+\tstruct strbuf buf_prefix = STRBUF_INIT;\n+\tconst char *prev_head = NULL;\n+\n+\tstrbuf_addf(&buf_prefix, \"refs/remotes/%s/\", remote);\n+\tskip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);\n+\n+\tif (prev_head && strcmp(prev_head, head_name)) {\n+\t\tprintf(\"'HEAD' at '%s' has changed from '%s' to '%s'\\n\",\n+\t\t\tremote, prev_head, head_name);\n+\t\tprintf(\"Run 'git remote set-head %s %s' to follow the change.\\n\",\n+\t\t\tremote, head_name);\n+\t}\n+\telse if (!!updateres && buf_prev->len) {\n+\t\tprintf(\"detached 'HEAD' at '%s' has changed from '%s' to '%s'\\n\",\n+\t\t\tremote, buf_prev->buf, head_name);\n+\t\tprintf(\"Run 'git remote set-head %s %s' to follow the change.\\n\",\n+\t\t\tremote, head_name);\n+\t}\n+\tstrbuf_release(&buf_prefix);\n+}\n+\n+static const char *strip_refshead(const char *name){\n+\tskip_prefix(name, \"refs/heads/\", &name);\n+\treturn name;\n+}\n+\n+static int set_head(const struct ref *remote_refs)\n+{\n+\tint result = 0, updateres;\n+\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,\n+\t\tb_local_head = STRBUF_INIT;\n+\tconst char *remote = gtransport->remote->name;\n+\tchar *head_name = NULL;\n+\tstruct ref *ref, *matches;\n+\tstruct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;\n+\tstruct refspec_item refspec = {\n+\t\t.force = 0,\n+\t\t.pattern = 1,\n+\t\t.src = (char *) \"refs/heads/*\",\n+\t\t.dst = (char *) \"refs/heads/*\",\n+\t};\n+\tstruct string_list heads = STRING_LIST_INIT_DUP;\n+\tstruct ref_store *refs = get_main_ref_store(the_repository);\n+\n+\tget_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);\n+\tmatches = guess_remote_head(find_ref_by_name(remote_refs, \"HEAD\"),\n+\t\t\t\t    fetch_map, 1);\n+\tfor (ref = matches; ref; ref = ref->next) {\n+\t\tstring_list_append(&heads, strip_refshead(ref->name));\n+\t}\n+\n+\n+\tif (!heads.nr)\n+\t\tresult = 1;\n+\telse if (heads.nr > 1)\n+\t\tresult = 1;\n+\telse\n+\t\thead_name = xstrdup(heads.items[0].string);\n+\n+\tif (!head_name)\n+\t\tgoto cleanup;\n+\tstrbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", remote);\n+\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", remote, head_name);\n+\t\t/* make sure it's valid */\n+\tif (!refs_ref_exists(refs, b_remote_head.buf)) {\n+\t\tresult = 1;\n+\t\tgoto cleanup;\n+\t}\n+\tupdateres = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n+\t\t\t\t\t\"fetch\", &b_local_head, 1);\n+\tif (updateres == -1) {\n+\t\tresult = 1;\n+\t\tgoto cleanup;\n+\t}\n+\treport_set_head(remote, head_name, &b_local_head, updateres);\n+\n+\n+\n+cleanup:\n+\tfree(head_name);\n+\tfree_refs(fetch_map);\n+\tfree_refs(matches);\n+\tstring_list_clear(&heads, 0);\n+\tstrbuf_release(&b_head);\n+\tstrbuf_release(&b_local_head);\n+\tstrbuf_release(&b_remote_head);\n+\treturn result;\n+}\n+\n static int do_fetch(struct transport *transport,\n \t\t    struct refspec *rs,\n \t\t    const struct fetch_config *config)\n@@ -1647,6 +1738,8 @@ static int do_fetch(struct transport *transport,\n \t\t\t\t    \"refs/tags/\");\n \t}\n \n+\tstrvec_push(&transport_ls_refs_options.ref_prefixes, \"HEAD\");\n+\n \tif (must_list_refs) {\n \t\ttrace2_region_enter(\"fetch\", \"remote_refs\", the_repository);\n \t\tremote_refs = transport_get_remote_refs(transport,\n@@ -1791,6 +1884,12 @@ static int do_fetch(struct transport *transport,\n \t\t\t\t  \"you need to specify exactly one branch with the --set-upstream option\"));\n \t\t}\n \t}\n+\tif (set_head(remote_refs))\n+\t\t;\n+\t\t/*\n+\t\t * Way too many cases where this can go wrong\n+\t\t * so let's just fail silently for now.\n+\t\t */\n \n cleanup:\n \tif (retcode) {\ndiff --git a/t/t4207-log-decoration-colors.sh b/t/t4207-log-decoration-colors.sh\nindex 73ea9e5155..d55d22cb2f 100755\n--- a/t/t4207-log-decoration-colors.sh\n+++ b/t/t4207-log-decoration-colors.sh\n@@ -59,7 +59,8 @@ ${c_reset}${c_tag}tag: ${c_reset}${c_tag}v1.0${c_reset}${c_commit}, \\\n ${c_reset}${c_tag}tag: ${c_reset}${c_tag}B${c_reset}${c_commit})${c_reset} B\n ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\n ${c_tag}tag: ${c_reset}${c_tag}A1${c_reset}${c_commit}, \\\n-${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit})${c_reset} A1\n+${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit}, \\\n+${c_reset}${c_remoteBranch}other/HEAD${c_reset}${c_commit})${c_reset} A1\n \t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\n ${c_stash}refs/stash${c_reset}${c_commit})${c_reset} On main: Changes to A.t\n \t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex 807df00ba7..660310239c 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -74,7 +74,7 @@ test_expect_success 'add another remote' '\n \t\tcd test &&\n \t\tgit remote add -f second ../two &&\n \t\ttokens_match \"origin second\" \"$(git remote)\" &&\n-\t\tcheck_tracking_branch second main side another &&\n+\t\tcheck_tracking_branch second main side another HEAD &&\n \t\tgit for-each-ref \"--format=%(refname)\" refs/remotes |\n \t\tsed -e \"/^refs\\/remotes\\/origin\\//d\" \\\n \t\t    -e \"/^refs\\/remotes\\/second\\//d\" >actual &&\n@@ -488,7 +488,7 @@ test_expect_success 'set-head --auto has no problem w/multiple HEADs' '\n \t\tcd test &&\n \t\tgit fetch two \"refs/heads/*:refs/remotes/two/*\" &&\n \t\tgit remote set-head --auto two >output 2>&1 &&\n-\t\techo \"${SQ}two/HEAD${SQ} is now created and points to ${SQ}main${SQ}\" >expect &&\n+\t\techo \"${SQ}two/HEAD${SQ} is unchanged and points to ${SQ}main${SQ}\" >expect &&\n \t\ttest_cmp expect output\n \t)\n '\n@@ -776,8 +776,10 @@ test_expect_success 'reject --no-no-tags' '\n '\n \n cat >one/expect <<\\EOF\n+  apis/HEAD -> apis/main\n   apis/main\n   apis/side\n+  drosophila/HEAD -> drosophila/main\n   drosophila/another\n   drosophila/main\n   drosophila/side\n@@ -795,11 +797,14 @@ test_expect_success 'update' '\n '\n \n cat >one/expect <<\\EOF\n+  drosophila/HEAD -> drosophila/main\n   drosophila/another\n   drosophila/main\n   drosophila/side\n+  manduca/HEAD -> manduca/main\n   manduca/main\n   manduca/side\n+  megaloprepus/HEAD -> megaloprepus/main\n   megaloprepus/main\n   megaloprepus/side\n EOF\n@@ -807,7 +812,7 @@ EOF\n test_expect_success 'update with arguments' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\n@@ -839,10 +844,13 @@ test_expect_success 'update --prune' '\n '\n \n cat >one/expect <<-\\EOF\n+  apis/HEAD -> apis/main\n   apis/main\n   apis/side\n+  manduca/HEAD -> manduca/main\n   manduca/main\n   manduca/side\n+  megaloprepus/HEAD -> megaloprepus/main\n   megaloprepus/main\n   megaloprepus/side\n EOF\n@@ -850,7 +858,7 @@ EOF\n test_expect_success 'update default' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\n@@ -862,6 +870,7 @@ test_expect_success 'update default' '\n '\n \n cat >one/expect <<\\EOF\n+  drosophila/HEAD -> drosophila/main\n   drosophila/another\n   drosophila/main\n   drosophila/side\n@@ -870,7 +879,7 @@ EOF\n test_expect_success 'update default (overridden, with funny whitespace)' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\n@@ -884,7 +893,7 @@ test_expect_success 'update default (overridden, with funny whitespace)' '\n test_expect_success 'update (with remotes.default defined)' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex 0890b9f61c..87698341f5 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -75,6 +75,30 @@ test_expect_success \"fetch test for-merge\" '\n \tcut -f -2 .git/FETCH_HEAD >actual &&\n \ttest_cmp expected actual'\n \n+test_expect_success \"fetch test remote HEAD\" '\n+\tcd \"$D\" &&\n+\tcd two &&\n+\tgit fetch &&\n+\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\tgit rev-parse --verify refs/remotes/origin/main &&\n+\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\tbranch=$(git rev-parse refs/remotes/origin/main) &&\n+\ttest \"z$head\" = \"z$branch\"'\n+\n+test_expect_success \"fetch test remote HEAD change\" '\n+\tcd \"$D\" &&\n+\tcd two &&\n+\tgit switch -c other &&\n+\tgit push -u origin other &&\n+\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\tgit rev-parse --verify refs/remotes/origin/main &&\n+\tgit rev-parse --verify refs/remotes/origin/other &&\n+\tgit remote set-head origin other &&\n+\tgit fetch &&\n+\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\tbranch=$(git rev-parse refs/remotes/origin/other) &&\n+\ttest \"z$head\" = \"z$branch\"'\n+\n test_expect_success 'fetch --prune on its own works as expected' '\n \tcd \"$D\" &&\n \tgit clone . prune &&\ndiff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh\nindex 64b3491e4e..1b3865e154 100755\n--- a/t/t5512-ls-remote.sh\n+++ b/t/t5512-ls-remote.sh\n@@ -293,6 +293,8 @@ test_expect_success 'ls-remote with filtered symref (refname)' '\n \tcat >expect <<-EOF &&\n \tref: refs/heads/main\tHEAD\n \t$rev\tHEAD\n+\tref: refs/remotes/origin/main\trefs/remotes/origin/HEAD\n+\t$rev\trefs/remotes/origin/HEAD\n \tEOF\n \tgit ls-remote --symref . HEAD >actual &&\n \ttest_cmp expect actual\ndiff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh\nindex 579872c258..e3482b27b2 100755\n--- a/t/t5514-fetch-multiple.sh\n+++ b/t/t5514-fetch-multiple.sh\n@@ -45,14 +45,17 @@ test_expect_success setup '\n '\n \n cat > test/expect << EOF\n+  one/HEAD -> one/main\n   one/main\n   one/side\n   origin/HEAD -> origin/main\n   origin/main\n   origin/side\n+  three/HEAD -> three/main\n   three/another\n   three/main\n   three/side\n+  two/HEAD -> two/main\n   two/another\n   two/main\n   two/side\n@@ -97,6 +100,7 @@ cat > expect << EOF\n   origin/HEAD -> origin/main\n   origin/main\n   origin/side\n+  three/HEAD -> three/main\n   three/another\n   three/main\n   three/side\n@@ -112,8 +116,10 @@ test_expect_success 'git fetch --multiple (but only one remote)' '\n '\n \n cat > expect << EOF\n+  one/HEAD -> one/main\n   one/main\n   one/side\n+  two/HEAD -> two/main\n   two/another\n   two/main\n   two/side\n@@ -141,7 +147,7 @@ test_expect_success 'git fetch --multiple (bad remote names)' '\n \n test_expect_success 'git fetch --all (skipFetchAll)' '\n \t(cd test4 &&\n-\t for b in $(git branch -r)\n+\t for b in $(git branch -r | grep -v HEAD)\n \t do\n \t\tgit branch -r -d $b || exit 1\n \t done &&\n@@ -153,11 +159,14 @@ test_expect_success 'git fetch --all (skipFetchAll)' '\n '\n \n cat > expect << EOF\n+  one/HEAD -> one/main\n   one/main\n   one/side\n+  three/HEAD -> three/main\n   three/another\n   three/main\n   three/side\n+  two/HEAD -> two/main\n   two/another\n   two/main\n   two/side\n@@ -165,7 +174,7 @@ EOF\n \n test_expect_success 'git fetch --multiple (ignoring skipFetchAll)' '\n \t(cd test4 &&\n-\t for b in $(git branch -r)\n+\t for b in $(git branch -r | grep -v HEAD)\n \t do\n \t\tgit branch -r -d $b || exit 1\n \t done &&\n@@ -221,14 +230,17 @@ test_expect_success 'git fetch --multiple --jobs=0 picks a default' '\n \n create_fetch_all_expect () {\n \tcat >expect <<-\\EOF\n+\t  one/HEAD -> one/main\n \t  one/main\n \t  one/side\n \t  origin/HEAD -> origin/main\n \t  origin/main\n \t  origin/side\n+\t  three/HEAD -> three/main\n \t  three/another\n \t  three/main\n \t  three/side\n+\t  two/HEAD -> two/main\n \t  two/another\n \t  two/main\n \t  two/side\n@@ -265,6 +277,7 @@ test_expect_success 'git fetch (fetch all remotes with fetch.all = true)' '\n \n create_fetch_one_expect () {\n \tcat >expect <<-\\EOF\n+\t  one/HEAD -> one/main\n \t  one/main\n \t  one/side\n \t  origin/HEAD -> origin/main\ndiff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\nindex 331778bd42..5a051aa0c7 100755\n--- a/t/t5516-fetch-push.sh\n+++ b/t/t5516-fetch-push.sh\n@@ -1395,7 +1395,8 @@ test_expect_success 'fetch follows tags by default' '\n \t\tgit tag -m \"annotated\" tag &&\n \t\tgit for-each-ref >tmp1 &&\n \t\tsed -n \"p; s|refs/heads/main$|refs/remotes/origin/main|p\" tmp1 |\n-\t\tsort -k 3 >../expect\n+\t\tsed -n \"p; s|refs/heads/main$|refs/remotes/origin/HEAD|p\"  |\n+\t\tsort -k 4 >../expect\n \t) &&\n \ttest_when_finished \"rm -rf dst\" &&\n \tgit init dst &&\ndiff --git a/t/t5527-fetch-odd-refs.sh b/t/t5527-fetch-odd-refs.sh\nindex 98ece27c6a..d3996af6ee 100755\n--- a/t/t5527-fetch-odd-refs.sh\n+++ b/t/t5527-fetch-odd-refs.sh\n@@ -52,7 +52,8 @@ test_expect_success LONG_REF 'fetch handles extremely long refname' '\n \tlong\n \tmain\n \tEOF\n-\tgit for-each-ref --format=\"%(subject)\" refs/remotes/long >actual &&\n+\tgit for-each-ref --format=\"%(subject)\" refs/remotes/long \\\n+\t\t--exclude=refs/remotes/long/HEAD >actual &&\n \ttest_cmp expect actual\n '\n \ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex c224c8450c..edb85b7145 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -329,7 +329,8 @@ test_expect_success 'incremental-repack task' '\n \n \t# Delete refs that have not been repacked in these packs.\n \tgit for-each-ref --format=\"delete %(refname)\" \\\n-\t\trefs/prefetch refs/tags refs/remotes >refs &&\n+\t\trefs/prefetch refs/tags refs/remotes \\\n+\t\t--exclude=refs/remotes/*/HEAD >refs &&\n \tgit update-ref --stdin <refs &&\n \n \t# Replace the object directory with this pack layout.\ndiff --git a/t/t9210-scalar.sh b/t/t9210-scalar.sh\nindex a30b2c9f70..2237844550 100755\n--- a/t/t9210-scalar.sh\n+++ b/t/t9210-scalar.sh\n@@ -151,7 +151,8 @@ test_expect_success 'scalar clone' '\n \t\t\t\"$(pwd)\" &&\n \n \t\tgit for-each-ref --format=\"%(refname)\" refs/remotes/origin/ >actual &&\n-\t\techo \"refs/remotes/origin/parallel\" >expect &&\n+\t\techo \"refs/remotes/origin/HEAD\" >>expect &&\n+\t\techo \"refs/remotes/origin/parallel\" >>expect &&\n \t\ttest_cmp expect actual &&\n \n \t\ttest_path_is_missing 1/2 &&\n@@ -220,7 +221,7 @@ test_expect_success 'scalar reconfigure --all with includeIf.onbranch' '\n \tdone\n '\n \n- test_expect_success 'scalar reconfigure --all with detached HEADs' '\n+test_expect_success 'scalar reconfigure --all with detached HEADs' '\n \trepos=\"two three four\" &&\n \tfor num in $repos\n \tdo\ndiff --git a/t/t9211-scalar-clone.sh b/t/t9211-scalar-clone.sh\nindex c16ea67c1d..d9cb6b9a3e 100755\n--- a/t/t9211-scalar-clone.sh\n+++ b/t/t9211-scalar-clone.sh\n@@ -32,7 +32,7 @@ test_expect_success 'set up repository to clone' '\n \t)\n '\n \n-cleanup_clone () {\n+cleanup_clone() {\n \trm -rf \"$1\"\n }\n \n@@ -128,7 +128,7 @@ test_expect_success '--single-branch clones HEAD only' '\n \t(\n \t\tcd $enlistment/src &&\n \t\tgit for-each-ref refs/remotes/origin >out &&\n-\t\ttest_line_count = 1 out &&\n+\t\ttest_line_count = 2 out &&\n \t\tgrep \"refs/remotes/origin/base\" out\n \t) &&\n \n@@ -142,7 +142,7 @@ test_expect_success '--no-single-branch clones all branches' '\n \t(\n \t\tcd $enlistment/src &&\n \t\tgit for-each-ref refs/remotes/origin >out &&\n-\t\ttest_line_count = 2 out &&\n+\t\ttest_line_count = 3 out &&\n \t\tgrep \"refs/remotes/origin/base\" out &&\n \t\tgrep \"refs/remotes/origin/parallel\" out\n \t) &&\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex cc6aa9f0cd..b663c4609e 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -658,6 +658,7 @@ test_expect_success '__git_refs - simple' '\n \tHEAD\n \tmain\n \tmatching-branch\n+\tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n \tmatching-tag\n@@ -673,6 +674,7 @@ test_expect_success '__git_refs - full refs' '\n \tcat >expected <<-EOF &&\n \trefs/heads/main\n \trefs/heads/matching-branch\n+\trefs/remotes/other/HEAD\n \trefs/remotes/other/branch-in-other\n \trefs/remotes/other/main-in-other\n \trefs/tags/matching-tag\n@@ -729,6 +731,7 @@ test_expect_success '__git_refs - remote on local file system - full refs' '\n test_expect_success '__git_refs - configured remote' '\n \tcat >expected <<-EOF &&\n \tHEAD\n+\tHEAD\n \tbranch-in-other\n \tmain-in-other\n \tEOF\n@@ -756,6 +759,7 @@ test_expect_success '__git_refs - configured remote - full refs' '\n test_expect_success '__git_refs - configured remote - repo given on the command line' '\n \tcat >expected <<-EOF &&\n \tHEAD\n+\tHEAD\n \tbranch-in-other\n \tmain-in-other\n \tEOF\n@@ -787,6 +791,7 @@ test_expect_success '__git_refs - configured remote - full refs - repo given on\n test_expect_success '__git_refs - configured remote - remote name matches a directory' '\n \tcat >expected <<-EOF &&\n \tHEAD\n+\tHEAD\n \tbranch-in-other\n \tmain-in-other\n \tEOF\n@@ -875,12 +880,14 @@ test_expect_success '__git_refs - unique remote branches for git checkout DWIMer\n \tHEAD\n \tmain\n \tmatching-branch\n+\tother/HEAD\n \tother/ambiguous\n \tother/branch-in-other\n \tother/main-in-other\n \tremote/ambiguous\n \tremote/branch-in-remote\n \tmatching-tag\n+\tHEAD\n \tbranch-in-other\n \tbranch-in-remote\n \tmain-in-other\n@@ -904,6 +911,7 @@ test_expect_success '__git_refs - after --opt=' '\n \tHEAD\n \tmain\n \tmatching-branch\n+\tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n \tmatching-tag\n@@ -919,6 +927,7 @@ test_expect_success '__git_refs - after --opt= - full refs' '\n \tcat >expected <<-EOF &&\n \trefs/heads/main\n \trefs/heads/matching-branch\n+\trefs/remotes/other/HEAD\n \trefs/remotes/other/branch-in-other\n \trefs/remotes/other/main-in-other\n \trefs/tags/matching-tag\n@@ -935,6 +944,7 @@ test_expect_success '__git refs - excluding refs' '\n \t^HEAD\n \t^main\n \t^matching-branch\n+\t^other/HEAD\n \t^other/branch-in-other\n \t^other/main-in-other\n \t^matching-tag\n@@ -950,6 +960,7 @@ test_expect_success '__git refs - excluding full refs' '\n \tcat >expected <<-EOF &&\n \t^refs/heads/main\n \t^refs/heads/matching-branch\n+\t^refs/remotes/other/HEAD\n \t^refs/remotes/other/branch-in-other\n \t^refs/remotes/other/main-in-other\n \t^refs/tags/matching-tag\n@@ -975,6 +986,7 @@ test_expect_success '__git_refs - do not filter refs unless told so' '\n \tmain\n \tmatching-branch\n \tmatching/branch\n+\tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n \tother/matching/branch-in-other\n@@ -1095,6 +1107,7 @@ test_expect_success '__git_complete_refs - simple' '\n \tHEAD Z\n \tmain Z\n \tmatching-branch Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tmatching-tag Z\n@@ -1123,6 +1136,7 @@ test_expect_success '__git_complete_refs - matching' '\n test_expect_success '__git_complete_refs - remote' '\n \tsed -e \"s/Z$//\" >expected <<-EOF &&\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain-in-other Z\n \tEOF\n@@ -1139,9 +1153,11 @@ test_expect_success '__git_complete_refs - track' '\n \tHEAD Z\n \tmain Z\n \tmatching-branch Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tmatching-tag Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain-in-other Z\n \tEOF\n@@ -1184,6 +1200,7 @@ test_expect_success '__git_complete_refs - suffix' '\n \tHEAD.\n \tmain.\n \tmatching-branch.\n+\tother/HEAD.\n \tother/branch-in-other.\n \tother/main-in-other.\n \tmatching-tag.\n@@ -1199,6 +1216,7 @@ test_expect_success '__git_complete_refs - suffix' '\n test_expect_success '__git_complete_fetch_refspecs - simple' '\n \tsed -e \"s/Z$//\" >expected <<-EOF &&\n \tHEAD:HEAD Z\n+\tHEAD:HEAD Z\n \tbranch-in-other:branch-in-other Z\n \tmain-in-other:main-in-other Z\n \tEOF\n@@ -1225,6 +1243,7 @@ test_expect_success '__git_complete_fetch_refspecs - matching' '\n test_expect_success '__git_complete_fetch_refspecs - prefix' '\n \tsed -e \"s/Z$//\" >expected <<-EOF &&\n \t+HEAD:HEAD Z\n+\t+HEAD:HEAD Z\n \t+branch-in-other:branch-in-other Z\n \t+main-in-other:main-in-other Z\n \tEOF\n@@ -1289,6 +1308,7 @@ test_expect_success '__git_complete_worktree_paths with -C' '\n \n test_expect_success 'git switch - with no options, complete local branches and unique remote branch names for DWIM logic' '\n \ttest_completion \"git switch \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1435,11 +1455,13 @@ test_expect_success 'git-bisect - existing view subcommand is recognized and ena\n test_expect_success 'git checkout - completes refs and unique remote branches for DWIM' '\n \ttest_completion \"git checkout \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1461,6 +1483,7 @@ test_expect_success 'git switch - with GIT_COMPLETION_CHECKOUT_NO_GUESS=1, compl\n \n test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete local branches and unique remote names for DWIM logic' '\n \tGIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git switch --guess \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1470,6 +1493,7 @@ test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_G\n \n test_expect_success 'git switch - a later --guess overrides previous --no-guess, complete local and remote unique branches for DWIM' '\n \ttest_completion \"git switch --no-guess --guess \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1490,6 +1514,7 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1498,11 +1523,13 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete\n test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1, complete refs and unique remote branches for DWIM' '\n \tGIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git checkout --guess \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1514,6 +1541,7 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1522,11 +1550,13 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '\n test_expect_success 'git checkout - a later --guess overrides previous --no-guess, complete refs and unique remote branches for DWIM' '\n \ttest_completion \"git checkout --no-guess --guess \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1538,6 +1568,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous --gues\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1550,6 +1581,7 @@ test_expect_success 'git checkout - with checkout.guess = false, only completes\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1559,11 +1591,13 @@ test_expect_success 'git checkout - with checkout.guess = true, completes refs a\n \ttest_config checkout.guess true &&\n \ttest_completion \"git checkout \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1573,11 +1607,13 @@ test_expect_success 'git checkout - a later --guess overrides previous checkout.\n \ttest_config checkout.guess false &&\n \ttest_completion \"git checkout --guess \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1590,6 +1626,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous checko\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1601,6 +1638,7 @@ test_expect_success 'git switch - with --detach, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1612,6 +1650,7 @@ test_expect_success 'git checkout - with --detach, complete only references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1783,6 +1822,7 @@ test_expect_success 'git switch - with -d, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1794,6 +1834,7 @@ test_expect_success 'git checkout - with -d, complete only references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1801,10 +1842,12 @@ test_expect_success 'git checkout - with -d, complete only references' '\n \n test_expect_success 'git switch - with --track, complete only remote branches' '\n \ttest_completion \"git switch --track \" <<-\\EOF &&\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n \ttest_completion \"git switch -t \" <<-\\EOF\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1812,10 +1855,12 @@ test_expect_success 'git switch - with --track, complete only remote branches' '\n \n test_expect_success 'git checkout - with --track, complete only remote branches' '\n \ttest_completion \"git checkout --track \" <<-\\EOF &&\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n \ttest_completion \"git checkout -t \" <<-\\EOF\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1834,6 +1879,7 @@ test_expect_success 'git checkout - with --no-track, complete only local referen\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1845,6 +1891,7 @@ test_expect_success 'git switch - with -c, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1856,6 +1903,7 @@ test_expect_success 'git switch - with -C, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1867,6 +1915,7 @@ test_expect_success 'git switch - with -c and --track, complete all references'\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1878,6 +1927,7 @@ test_expect_success 'git switch - with -C and --track, complete all references'\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1889,6 +1939,7 @@ test_expect_success 'git switch - with -c and --no-track, complete all reference\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1900,6 +1951,7 @@ test_expect_success 'git switch - with -C and --no-track, complete all reference\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1911,6 +1963,7 @@ test_expect_success 'git checkout - with -b, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1922,6 +1975,7 @@ test_expect_success 'git checkout - with -B, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1933,6 +1987,7 @@ test_expect_success 'git checkout - with -b and --track, complete all references\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1944,6 +1999,7 @@ test_expect_success 'git checkout - with -B and --track, complete all references\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1955,6 +2011,7 @@ test_expect_success 'git checkout - with -b and --no-track, complete all referen\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1966,6 +2023,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1973,6 +2031,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen\n \n test_expect_success 'git switch - for -c, complete local branches and unique remote branches' '\n \ttest_completion \"git switch -c \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1982,6 +2041,7 @@ test_expect_success 'git switch - for -c, complete local branches and unique rem\n \n test_expect_success 'git switch - for -C, complete local branches and unique remote branches' '\n \ttest_completion \"git switch -C \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2019,6 +2079,7 @@ test_expect_success 'git switch - for -C with --no-track, complete local branche\n \n test_expect_success 'git checkout - for -b, complete local branches and unique remote branches' '\n \ttest_completion \"git checkout -b \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2028,6 +2089,7 @@ test_expect_success 'git checkout - for -b, complete local branches and unique r\n \n test_expect_success 'git checkout - for -B, complete local branches and unique remote branches' '\n \ttest_completion \"git checkout -B \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2065,6 +2127,7 @@ test_expect_success 'git checkout - for -B with --no-track, complete local branc\n \n test_expect_success 'git switch - with --orphan completes local branch names and unique remote branch names' '\n \ttest_completion \"git switch --orphan \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2080,6 +2143,7 @@ test_expect_success 'git switch - --orphan with branch already provided complete\n \n test_expect_success 'git checkout - with --orphan completes local branch names and unique remote branch names' '\n \ttest_completion \"git checkout --orphan \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2093,6 +2157,7 @@ test_expect_success 'git checkout - --orphan with branch already provided comple\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n-- \n2.47.0.296.gda1ecfef29.dirty\n\n"},{"id":"507559","messageId":"xmqq4j44ggq0.fsf@gitster.g","threadId":"62096","inReplyTo":"20241118151755.756265-3-bence@ferdinandy.com","subject":"Re: [PATCH v13 2/9] refs: standardize output of refs_read_symbolic_ref","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-11-19T01:22:31Z","receivedAt":"2024-11-19T01:22:34Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bence Ferdinandy <bence@ferdinandy.com> writes:\n\n> Subject: Re: [PATCH v13 2/9] refs: standardize output of refs_read_symbolic_ref\n\n\"output\" -> \"return values\", or something.\n\n> When the symbolic reference we want to read with refs_read_symbolic_ref\n> is actually not a symbolic reference, the files and the reftable\n> backends return different values (1 and -1 respectively). Standardize\n> the returned values so that 0 is success, -1 is a generic error and -2\n> is that the reference was actually non-symbolic.\n\nAre all the existing callers OK with this switch from 1 to -2?\n\nIOW, if a caller using the ref-files backend start behaving\ndifferently with this change upon seeing a return value of -2 (where\nit previously was expecting to see 1), that would not be nice.\n\nBecause \"reftable was already broken\" is not a good excuse to\nintroduce a separate regression to ref-files users, we'd want to be\ncareful if we want to do this kind of \"while at it\" change.\n\n> +/*\n> + * Return 0 if the symbolic reference could be read without error.\n> + * Return -1 for generic errors.\n> + * Return -2 if the reference was actually non-symbolic.\n> + */\n\nAs this is an implementation of ref_stroage_be.read_symbolic_ref,\nthe above comment must stay in sync with the comment over there (and\na comment before the corresponding function in the other backend).\n\nI personally would not add the above comment for that reason, but as\nlong as we are consistent, I am OK either way.  So if we add this,\nwe should do the same to the reftable side as well.\n\nBy the way, it is arguable that it is an error when a caller asks\n\"here is a ref, please read it as a symbolic ref\" and the ref turns\nout to be not a symbolic ref.  I'd say that it is a valid view that\nthe caller asked the question to find out if the ref was a symbolic\nbefore making the call, and \"that ref is not symbolic\" is one of the\nvalid and expected results.  So if you wanted to change the value\nfrom 1 to -2 only because \"you called read_symbolic_ref() without\nchecking if it is a symbolic to begin with, which is your fault and\nyou deserve to get an error\", I am not sure if I agree with that\nview and reasoning.\n\nIn any case, this \"not a symbolic ref\" may want to have a symbolic\nconstant.  With NOT_A_SYMREF being a non-error, you could structure\nthe caller like so:\n\n\tstatus = read_symref()\n\tif (status < 0)\n\t\t... we got an error so we stop here ...\n\t\t... we do not behave differently on error type ...\n\n\tswitch (status) {\n\tcase 0:\n\t\t... everything is peachy ...\n\t\tbreak ;;\n\tcase NOT_A_SYMREF:\n\t\t... we want to handle non-symref here ...\n\t\tbreak ;;\n\tdefault:\n\t\tBUG(\"unhandled case\");\n\t}\n\nAlso, even if we decide that we want to treat this as an error,\nlumping everything else as \"generic error\" might make it awkward to\nevolve the API, I suspect.\n\n> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\n> index 38eb14d591..60cb83f23a 100644\n> --- a/refs/reftable-backend.c\n> +++ b/refs/reftable-backend.c\n> @@ -830,7 +830,9 @@ static int reftable_be_read_symbolic_ref(struct ref_store *ref_store,\n>  \t\treturn ret;\n>  \n>  \tret = reftable_stack_read_ref(stack, refname, &ref);\n> -\tif (ret == 0 && ref.value_type == REFTABLE_REF_SYMREF)\n> +\tif (!ret && (ref.value_type != REFTABLE_REF_SYMREF))\n> +\t\tret = -2;\n> +\telse if (!ret && (ref.value_type == REFTABLE_REF_SYMREF))\n>  \t\tstrbuf_addstr(referent, ref.value.symref);\n>  \telse\n>  \t\tret = -1;\n"},{"id":"507560","messageId":"xmqqldxgez4l.fsf@gitster.g","threadId":"62096","inReplyTo":"20241118151755.756265-6-bence@ferdinandy.com","subject":"Re: [PATCH v13 5/9] remote set-head: better output for --auto","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-11-19T02:27:54Z","receivedAt":"2024-11-19T02:27:57Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bence Ferdinandy <bence@ferdinandy.com> writes:\n\n> +static void report_set_head_auto(const char *remote, const char *head_name,\n> +\t\t\tstruct strbuf *b_local_head, int updateres) {\n\n\"updateres\" was too mysterious a name.  \"res\" stands for what,\n\"resource\"?\n\nLooking at the way the parameter is used by the code, it seems to\nindicate that the remote HEAD originally was in a detached state, so\n\"was_detached\" may be a better name, perhaps?\n\n> +\telse if (!!updateres && b_local_head->len)\n> +\t\tprintf(_(\"'%s/HEAD' was detached at '%s' and now points to '%s'\\n\"),\n> +\t\t\tremote, b_local_head->buf, head_name);\n\nThere is no need for !!; any non-zero integer is true.  !! is useful\nonly in a context that takes only 0 and 1 (like when you are making\nan assignment to a variable or a structure member that takes only 0\nor 1).\n\n>  static int set_head(int argc, const char **argv, const char *prefix)\n>  {\n> -\tint i, opt_a = 0, opt_d = 0, result = 0;\n> -\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;\n> +\tint i, opt_a = 0, opt_d = 0, result = 0, updateres;\n> +\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,\n> +\t\tb_local_head = STRBUF_INIT;\n\n> @@ -1440,20 +1468,27 @@ static int set_head(int argc, const char **argv, const char *prefix)\n>  \t} else\n>  \t\tusage_with_options(builtin_remote_sethead_usage, options);\n>  \n> -\tif (head_name) {\n> -\t\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", argv[0], head_name);\n> -\t\t/* make sure it's valid */\n> -\t\tif (!refs_ref_exists(refs, b_remote_head.buf))\n> -\t\t\tresult |= error(_(\"Not a valid ref: %s\"), b_remote_head.buf);\n> -\t\telse if (refs_update_symref(refs, b_head.buf, b_remote_head.buf, \"remote set-head\"))\n> -\t\t\tresult |= error(_(\"Could not setup %s\"), b_head.buf);\n> -\t\telse if (opt_a)\n> -\t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\n> -\t\tfree(head_name);\n> +\tif (!head_name)\n> +\t\tgoto cleanup;\n> +\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", argv[0], head_name);\n> +\tif (!refs_ref_exists(refs, b_remote_head.buf)) {\n> +\t\tresult |= error(_(\"Not a valid ref: %s\"), b_remote_head.buf);\n> +\t\tgoto cleanup;\n> +\t}\n\nOK, we refuse to allow a manual \"remote set-head\" to create a\ndangling symref, which is a faithful rewrite from the original.\n\n> +\tupdateres = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n> +\t\t\t\"remote set-head\", &b_local_head);\n\n> +\tif (updateres == -2) {\n\nWhere does this -2 come from?  It is not the \"you asked to read it\nas a symref but it wasn't a symref\" thing, which was mapped to -1\nwith [PATCH 3/9].\n\nIt is an unusual way to construct an extensible API function to say\n\"all different kinds of errors we happen to know when this\nparticular caller was written return -2, but some special cases are\nnot -2\".\n\nRather, \"all negatives, other than these selected few values we\nspecial-case and handle, are errors\" is more natural, isn't it?\n\nMaybe I am misreading the code and missing where the -2 comes from\nor the significance of the value?  I dunno.\n"},{"id":"507561","messageId":"xmqqh684exw3.fsf@gitster.g","threadId":"62096","inReplyTo":"20241118151755.756265-8-bence@ferdinandy.com","subject":"Re: [PATCH v13 7/9] refs: add create_only option to refs_update_symref_extended","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-11-19T02:54:36Z","receivedAt":"2024-11-19T02:54:39Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bence Ferdinandy <bence@ferdinandy.com> writes:\n\n> Allow the caller to specify that it only wants to update the symref if\n> it does not already exist. Silently ignore the error from the\n> transaction API if the symref already exists.\n\nBecause it is not even an error, from the point of view of \"create\nonly if it does not exist\", checking the current condition and\nfinding that there already is one, this makes perfect sense.\n\n"},{"id":"507562","messageId":"xmqq5xojgbfl.fsf@gitster.g","threadId":"62096","inReplyTo":"20241118151755.756265-9-bence@ferdinandy.com","subject":"Re: [PATCH v13 8/9] fetch: set remote/HEAD if it does not exist","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-11-19T03:16:46Z","receivedAt":"2024-11-19T03:16:50Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bence Ferdinandy <bence@ferdinandy.com> writes:\n\n> If the user has remote/HEAD set already and it looks like it has changed\n> on the server, then print a message, otherwise set it if we can.\n\nThe user uses remote/origin/HEAD to point at the \"primary thing the\nuser is interested in at the remote 'origin'\".  \"git clone\"\npropagates remote's HEAD to local because their HEAD is the opinion\nof the remote which branch they want the users to consider the\nprimary thing.  But the user can have valid reasons to consider a\nbranch different from what the remote suggests as the primary thing,\nand an explicit \"set-head\" is a way to do so.\n\nAfter such a set-up is made and the user is perfectly happy, would\nit make sense to repeatedly remind that their HEAD points at\nsomething the user is not interested in?\n\nPerhaps it may make sense when given the \"--verbose\" option, or\nafter the first time the difference was noticed, but otherwise, I\nsuspect it becomes annoying to those who keep them deliberately\ndifferent from each other.\n\nI wonder if it is more sensible to automate for these two different\nclasses of users with a new configuration per remote.\n\n - When \"git clone\" initially sets up, or \"git remote set-head\n   --auto\" matches the local to the remote, we set the new\n   \"remote.$name.autoUpdateHEAD\" configuration variable to true.\n\n - When \"git remote set-head\" explicitly sets remote/$name/HEAD\n   to some value (if we can detect the case where it is different\n   from what the remote has, that would be a plus, but it incurrs\n   network traffic, so care must be taken to design this part), we\n   drop the \"remote.$name.autoUpdateHEAD\" configuration variable,\n   if and only if it is set to true.\n\n - When remote.$name.autoUpdateHEAD configuration is true, \"git\n   fetch\" automatically matches remote/$name/HEAD to what the HEAD\n   at the remote points at.  If remote.$name.autoUpdateHEAD is set\n   to some other value (e.g., \"warn\"), show the warning so that the\n   user can choose to do \"git remote set-head\" to a specific value\n   (which would not drop the remote.$name.autoUpdateHEAD set to\n   \"warn\") or \"git remote set-head --auto\" (which would start to\n   follow whatever remote uses).\n\nor something like that?\n\nWith something like the above, those who just want to follow along\nwould get remote.$name.autoUpdateHEAD=true and silently follow\nalong, while those who want to explicitly control would be able to\ndo so.  I dunno, but I am fairly negative on a persistent warning\nmessage that tells the user that the only normal case is to have\nremote/origin/HEAD point at the same branch the remote's HEAD points\nat.\n\nThanks.\n"},{"id":"507566","messageId":"xmqq34jnerlm.fsf@gitster.g","threadId":"62096","inReplyTo":"20241118151755.756265-3-bence@ferdinandy.com","subject":"Re: [PATCH v13 2/9] refs: standardize output of refs_read_symbolic_ref","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-11-19T05:10:29Z","receivedAt":"2024-11-19T05:10:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bence Ferdinandy <bence@ferdinandy.com> writes:\n\n> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\n> index 38eb14d591..60cb83f23a 100644\n> --- a/refs/reftable-backend.c\n> +++ b/refs/reftable-backend.c\n> @@ -830,7 +830,9 @@ static int reftable_be_read_symbolic_ref(struct ref_store *ref_store,\n>  \t\treturn ret;\n>  \n>  \tret = reftable_stack_read_ref(stack, refname, &ref);\n> -\tif (ret == 0 && ref.value_type == REFTABLE_REF_SYMREF)\n> +\tif (!ret && (ref.value_type != REFTABLE_REF_SYMREF))\n> +\t\tret = -2;\n> +\telse if (!ret && (ref.value_type == REFTABLE_REF_SYMREF))\n>  \t\tstrbuf_addstr(referent, ref.value.symref);\n>  \telse\n>  \t\tret = -1;\n\nThe ref.value_type can be either equal to REFTABLE_REF_SYMREF or not\nequal to it, and there is no other choice.\n\nWouldn't it be easier to reason about if the above code were written\nmore like this:\n\n        if (ret)\n\t\tret = -1;\n\telse if (ref.value_type == REFTABLE_REF_SYMREF)\n\t\tstrbuf_addstr(...);\n\telse\n\t\tret = -2;\n\nI found it curious when I read it again while attempting to resolve\nconflicts with 5413d69f (refs/reftable: refactor reading symbolic\nrefs to use reftable backend, 2024-11-05).  The resolution has to\nupdate this part of code to use the new implementation that asks\nreftable_backend_read_ref() and becomes a lot simpler, so the way it\nis written in your topic does not make much difference in the longer\nterm when both topics graduate.\n\nIOW, if we were rebuilding your topic on top of Patrick's topoic\nthat includes 5413d69f, this part would read like so, I think.\n\ndiff --git c/refs/reftable-backend.c w/refs/reftable-backend.c\nindex 6298991da7..b6bc3039a5 100644\n--- c/refs/reftable-backend.c\n+++ w/refs/reftable-backend.c\n@@ -920,8 +920,10 @@ static int reftable_be_read_symbolic_ref(struct ref_store *ref_store,\n \t\treturn ret;\n \n \tret = reftable_backend_read_ref(be, refname, &oid, referent, &type);\n-\tif (type != REF_ISSYMREF)\n+\tif (ret)\n \t\tret = -1;\n+\telse if (type != REF_ISSYMREF)\n+\t\tret = -2;\n \treturn ret;\n }\n \n"},{"id":"507569","messageId":"ZzwzrI6wJjY2dpp0@pks.im","threadId":"62096","inReplyTo":"xmqq4j44ggq0.fsf@gitster.g","subject":"Re: [PATCH v13 2/9] refs: standardize output of refs_read_symbolic_ref","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-11-19T06:44:04Z","receivedAt":"2024-11-19T06:44:18Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Nov 19, 2024 at 10:22:31AM +0900, Junio C Hamano wrote:\n> Bence Ferdinandy <bence@ferdinandy.com> writes:\n> \n> > Subject: Re: [PATCH v13 2/9] refs: standardize output of refs_read_symbolic_ref\n> \n> \"output\" -> \"return values\", or something.\n> \n> > When the symbolic reference we want to read with refs_read_symbolic_ref\n> > is actually not a symbolic reference, the files and the reftable\n> > backends return different values (1 and -1 respectively). Standardize\n> > the returned values so that 0 is success, -1 is a generic error and -2\n> > is that the reference was actually non-symbolic.\n> \n> Are all the existing callers OK with this switch from 1 to -2?\n> \n> IOW, if a caller using the ref-files backend start behaving\n> differently with this change upon seeing a return value of -2 (where\n> it previously was expecting to see 1), that would not be nice.\n> \n> Because \"reftable was already broken\" is not a good excuse to\n> introduce a separate regression to ref-files users, we'd want to be\n> careful if we want to do this kind of \"while at it\" change.\n\nThere are only three callers:\n\n  - \"remote.c:ignore_symref_update()\" only cares whether the return\n    value is 0 or not.\n\n  - \"builtin/remote.c:mv()\" is the same.\n\n  - \"refs.c:migrate_one_ref()\" assumes the behaviour of the reftable\n    backend and only checks for negative error codes.\n\nSo you could argue that it's the \"files\" backend that is broken, not the\n\"reftable\" backend. Doesn't matter ultimately though, the real problem\nis that this wasn't ever documented anywhere.\n\nI agree that this should be part of the commit message.\n\n> > +/*\n> > + * Return 0 if the symbolic reference could be read without error.\n> > + * Return -1 for generic errors.\n> > + * Return -2 if the reference was actually non-symbolic.\n> > + */\n> \n> As this is an implementation of ref_stroage_be.read_symbolic_ref,\n> the above comment must stay in sync with the comment over there (and\n> a comment before the corresponding function in the other backend).\n> \n> I personally would not add the above comment for that reason, but as\n> long as we are consistent, I am OK either way.  So if we add this,\n> we should do the same to the reftable side as well.\n\nAnother solution could be to have the comment in \"refs.h\" for the\ncaller-facing function, while the backend pointer simply says \"Please\nrefer to the documentation of `refs_read_symbolic_ref()`.\"\n\n> By the way, it is arguable that it is an error when a caller asks\n> \"here is a ref, please read it as a symbolic ref\" and the ref turns\n> out to be not a symbolic ref.  I'd say that it is a valid view that\n> the caller asked the question to find out if the ref was a symbolic\n> before making the call, and \"that ref is not symbolic\" is one of the\n> valid and expected results.  So if you wanted to change the value\n> from 1 to -2 only because \"you called read_symbolic_ref() without\n> checking if it is a symbolic to begin with, which is your fault and\n> you deserve to get an error\", I am not sure if I agree with that\n> view and reasoning.\n\nThe reason why I've been proposing to return negative is because we have\nthe idiom of checking `err < 0` in many places, so a function that\nreturns a positive value in the case where it didn't return the expected\nresult can easily lead to bugs.\n\n> In any case, this \"not a symbolic ref\" may want to have a symbolic\n> constant.  With NOT_A_SYMREF being a non-error, you could structure\n> the caller like so:\n\nThat'd be a good idea.\n\n> \tstatus = read_symref()\n> \tif (status < 0)\n> \t\t... we got an error so we stop here ...\n> \t\t... we do not behave differently on error type ...\n> \n> \tswitch (status) {\n> \tcase 0:\n> \t\t... everything is peachy ...\n> \t\tbreak ;;\n> \tcase NOT_A_SYMREF:\n> \t\t... we want to handle non-symref here ...\n> \t\tbreak ;;\n> \tdefault:\n> \t\tBUG(\"unhandled case\");\n> \t}\n> \n> Also, even if we decide that we want to treat this as an error,\n> lumping everything else as \"generic error\" might make it awkward to\n> evolve the API, I suspect.\n\nI guess most callers don't care about the exact error code, they only\ncare about whether or not they have been able to read the symref. I\nthink this idiom would easily be extensible in the future if all callers\nknow to check for `err < 0` by default, because introducing a new error\ncode would continue to work for them. And if they want to handle that\nnew error code they can adapt as you propose above with the switch.\n\nPatrick\n"},{"id":"507570","messageId":"Zzw0ooX0HFwSit7v@pks.im","threadId":"62096","inReplyTo":"20241118151755.756265-3-bence@ferdinandy.com","subject":"Re: [PATCH v13 2/9] refs: standardize output of refs_read_symbolic_ref","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-11-19T06:48:02Z","receivedAt":"2024-11-19T06:48:15Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, Nov 18, 2024 at 04:09:21PM +0100, Bence Ferdinandy wrote:\n> diff --git a/refs.h b/refs.h\n> index 108dfc93b3..f8b714ca1d 100644\n> --- a/refs.h\n> +++ b/refs.h\n> @@ -83,6 +83,12 @@ int refs_read_ref_full(struct ref_store *refs, const char *refname,\n>  \n>  int refs_read_ref(struct ref_store *refs, const char *refname, struct object_id *oid);\n>  \n> +/*\n> + * Return 0 if the symbolic reference could be read without error.\n> + * Return -1 for generic errors.\n> + * Return -2 if the reference was actually non-symbolic.\n> + */\n> +\n\nExtraneous empty newline.\n\nAlso, how about the following:\n\n    /*\n     * Read the symbolic ref named \"refname\" and write its immediate\n     * referent into the provided buffer. This does not resolve the\n     * symbolic ref recursively in case the target is a symbolic ref, as\n     * well.\n     *\n     * Returns 0 on success, -2 if the \"refname\" is not a symbolic ref,\n     * -1 otherwise.\n     */\n\n> diff --git a/refs/refs-internal.h b/refs/refs-internal.h\n> index 2313c830d8..f0ef354bce 100644\n> --- a/refs/refs-internal.h\n> +++ b/refs/refs-internal.h\n> @@ -673,6 +673,12 @@ struct ref_storage_be {\n>  \n>  \tref_iterator_begin_fn *iterator_begin;\n>  \tread_raw_ref_fn *read_raw_ref;\n> +\n> +\t/*\n> +\t * Return 0 if the symbolic reference could be read without error.\n> +\t * Return -1 for generic errors.\n> +\t * Return -2 if the reference was actually non-symbolic.\n> +\t */\n>  \tread_symbolic_ref_fn *read_symbolic_ref;\n\nAs proposed in the other thread, this could instead be:\n\n    /*\n     * Please refer to `refs_read_symbolic_ref()` for the expected\n     * behaviour.\n     /\n\nPatrick\n"},{"id":"507572","messageId":"xmqqr077d88i.fsf@gitster.g","threadId":"62096","inReplyTo":"ZzwzrI6wJjY2dpp0@pks.im","subject":"Re: [PATCH v13 2/9] refs: standardize output of refs_read_symbolic_ref","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-11-19T06:54:05Z","receivedAt":"2024-11-19T06:54:08Z","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> There are only three callers:\n>\n>   - \"remote.c:ignore_symref_update()\" only cares whether the return\n>     value is 0 or not.\n>\n>   - \"builtin/remote.c:mv()\" is the same.\n>\n>   - \"refs.c:migrate_one_ref()\" assumes the behaviour of the reftable\n>     backend and only checks for negative error codes.\n>\n> So you could argue that it's the \"files\" backend that is broken, not the\n> \"reftable\" backend. Doesn't matter ultimately though, the real problem\n> is that this wasn't ever documented anywhere.\n\nYou're correct that it does not matter ultimately.  But as a general\nrule, which also applies here, anything newly introduced one does\ndifferently without having a good reason is a bug in the new thing,\nI would say.\n\n> Another solution could be to have the comment in \"refs.h\" for the\n> caller-facing function, while the backend pointer simply says \"Please\n> refer to the documentation of `refs_read_symbolic_ref()`.\"\n\nYup, avoiding unnecessary duplication is a good thing.\n\n> The reason why I've been proposing to return negative is because we have\n> the idiom of checking `err < 0` in many places, so a function that\n> returns a positive value in the case where it didn't return the expected\n> result can easily lead to bugs.\n\nI agree with the general reasoning.  I am saying this may or may not\nbe an error, and if it turns out that it is not an error but is just\none of the generally expected outcome, treating it as an error and\nhaving \"if (status < 0)\" to lump the case together with other error\ncases may not be nice to the callers.\n"},{"id":"507573","messageId":"Zzw9iqf5bcj5_lV8@pks.im","threadId":"62096","inReplyTo":"xmqqr077d88i.fsf@gitster.g","subject":"Re: [PATCH v13 2/9] refs: standardize output of refs_read_symbolic_ref","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-11-19T07:26:08Z","receivedAt":"2024-11-19T07:26:24Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Nov 19, 2024 at 03:54:05PM +0900, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> > The reason why I've been proposing to return negative is because we have\n> > the idiom of checking `err < 0` in many places, so a function that\n> > returns a positive value in the case where it didn't return the expected\n> > result can easily lead to bugs.\n> \n> I agree with the general reasoning.  I am saying this may or may not\n> be an error, and if it turns out that it is not an error but is just\n> one of the generally expected outcome, treating it as an error and\n> having \"if (status < 0)\" to lump the case together with other error\n> cases may not be nice to the callers.\n\nThe question to me is whether the function returns something sensible in\nall non-error cases that a caller can use properly without having to\nexplicitly check for the value. And I'd say that this is not the case\nwith `refs_read_symbolic_ref()`, which wouldn't end up setting the value\nof `referent`.\n\nSo regardless of whether we define this as error or non-error, the\ncaller would have to exlicitly handle the case where it's not a symref\nin order to make sense of it because the result is not well-defined.\n\nPatrick\n"},{"id":"507578","messageId":"D5Q2JDB367NI.2N8AQFKUC2533@ferdinandy.com","threadId":"62096","inReplyTo":"xmqq34jnerlm.fsf@gitster.g","subject":"Re: [PATCH v13 2/9] refs: standardize output of refs_read_symbolic_ref","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-19T10:04:21Z","receivedAt":"2024-11-19T10:04:43Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Tue Nov 19, 2024 at 06:10, Junio C Hamano <gitster@pobox.com> wrote:\n> Bence Ferdinandy <bence@ferdinandy.com> writes:\n>\n>> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\n>> index 38eb14d591..60cb83f23a 100644\n>> --- a/refs/reftable-backend.c\n>> +++ b/refs/reftable-backend.c\n>> @@ -830,7 +830,9 @@ static int reftable_be_read_symbolic_ref(struct ref_store *ref_store,\n>>  \t\treturn ret;\n>>  \n>>  \tret = reftable_stack_read_ref(stack, refname, &ref);\n>> -\tif (ret == 0 && ref.value_type == REFTABLE_REF_SYMREF)\n>> +\tif (!ret && (ref.value_type != REFTABLE_REF_SYMREF))\n>> +\t\tret = -2;\n>> +\telse if (!ret && (ref.value_type == REFTABLE_REF_SYMREF))\n>>  \t\tstrbuf_addstr(referent, ref.value.symref);\n>>  \telse\n>>  \t\tret = -1;\n>\n> The ref.value_type can be either equal to REFTABLE_REF_SYMREF or not\n> equal to it, and there is no other choice.\n\nAh, ok, I didn't realize this.\n\n>\n> Wouldn't it be easier to reason about if the above code were written\n> more like this:\n>\n>         if (ret)\n> \t\tret = -1;\n> \telse if (ref.value_type == REFTABLE_REF_SYMREF)\n> \t\tstrbuf_addstr(...);\n> \telse\n> \t\tret = -2;\n>\n> I found it curious when I read it again while attempting to resolve\n> conflicts with 5413d69f (refs/reftable: refactor reading symbolic\n> refs to use reftable backend, 2024-11-05).  The resolution has to\n> update this part of code to use the new implementation that asks\n> reftable_backend_read_ref() and becomes a lot simpler, so the way it\n> is written in your topic does not make much difference in the longer\n> term when both topics graduate.\n\nI'll update the patch with the above, \n\n>\n> IOW, if we were rebuilding your topic on top of Patrick's topoic\n> that includes 5413d69f, this part would read like so, I think.\n>\n> diff --git c/refs/reftable-backend.c w/refs/reftable-backend.c\n> index 6298991da7..b6bc3039a5 100644\n> --- c/refs/reftable-backend.c\n> +++ w/refs/reftable-backend.c\n> @@ -920,8 +920,10 @@ static int reftable_be_read_symbolic_ref(struct ref_store *ref_store,\n>  \t\treturn ret;\n>  \n>  \tret = reftable_backend_read_ref(be, refname, &oid, referent, &type);\n> -\tif (type != REF_ISSYMREF)\n> +\tif (ret)\n>  \t\tret = -1;\n> +\telse if (type != REF_ISSYMREF)\n> +\t\tret = -2;\n>  \treturn ret;\n>  }\n>  \n\nbut I'll save this as well, I would not be completely surprised if Patrick's\ntopic makes it in sooner :)\n\nThanks,\nBence\n"},{"id":"507580","messageId":"D5Q2NU9LJIVC.23QKQ2GWHUVFQ@ferdinandy.com","threadId":"62096","inReplyTo":"Zzw9iqf5bcj5_lV8@pks.im","subject":"Re: [PATCH v13 2/9] refs: standardize output of refs_read_symbolic_ref","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-19T10:10:11Z","receivedAt":"2024-11-19T10:11:22Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Tue Nov 19, 2024 at 08:26, Patrick Steinhardt <ps@pks.im> wrote:\n> On Tue, Nov 19, 2024 at 03:54:05PM +0900, Junio C Hamano wrote:\n>> Patrick Steinhardt <ps@pks.im> writes:\n>> > The reason why I've been proposing to return negative is because we have\n>> > the idiom of checking `err < 0` in many places, so a function that\n>> > returns a positive value in the case where it didn't return the expected\n>> > result can easily lead to bugs.\n>> \n>> I agree with the general reasoning.  I am saying this may or may not\n>> be an error, and if it turns out that it is not an error but is just\n>> one of the generally expected outcome, treating it as an error and\n>> having \"if (status < 0)\" to lump the case together with other error\n>> cases may not be nice to the callers.\n>\n> The question to me is whether the function returns something sensible in\n> all non-error cases that a caller can use properly without having to\n> explicitly check for the value. And I'd say that this is not the case\n> with `refs_read_symbolic_ref()`, which wouldn't end up setting the value\n> of `referent`.\n>\n> So regardless of whether we define this as error or non-error, the\n> caller would have to exlicitly handle the case where it's not a symref\n> in order to make sense of it because the result is not well-defined.\n\nI agree with Patrick re the -1, -2 return values. The non-error behavior should\nbe when referent is set, anything else is something the caller would need to\nconsider if they want to do something with it or not.\n\n"},{"id":"507581","messageId":"D5Q2TEEZQYFC.2CH8JBLF3PHZT@ferdinandy.com","threadId":"62096","inReplyTo":"Zzw0ooX0HFwSit7v@pks.im","subject":"Re: [PATCH v13 2/9] refs: standardize output of refs_read_symbolic_ref","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-19T10:17:27Z","receivedAt":"2024-11-19T10:17:47Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Tue Nov 19, 2024 at 07:48, Patrick Steinhardt <ps@pks.im> wrote:\n> On Mon, Nov 18, 2024 at 04:09:21PM +0100, Bence Ferdinandy wrote:\n>> diff --git a/refs.h b/refs.h\n>> index 108dfc93b3..f8b714ca1d 100644\n>> --- a/refs.h\n>> +++ b/refs.h\n>> @@ -83,6 +83,12 @@ int refs_read_ref_full(struct ref_store *refs, const char *refname,\n>>  \n>>  int refs_read_ref(struct ref_store *refs, const char *refname, struct object_id *oid);\n>>  \n>> +/*\n>> + * Return 0 if the symbolic reference could be read without error.\n>> + * Return -1 for generic errors.\n>> + * Return -2 if the reference was actually non-symbolic.\n>> + */\n>> +\n>\n> Extraneous empty newline.\n>\n> Also, how about the following:\n>\n>     /*\n>      * Read the symbolic ref named \"refname\" and write its immediate\n>      * referent into the provided buffer. This does not resolve the\n>      * symbolic ref recursively in case the target is a symbolic ref, as\n>      * well.\n>      *\n>      * Returns 0 on success, -2 if the \"refname\" is not a symbolic ref,\n>      * -1 otherwise.\n>      */\n>\n>> diff --git a/refs/refs-internal.h b/refs/refs-internal.h\n>> index 2313c830d8..f0ef354bce 100644\n>> --- a/refs/refs-internal.h\n>> +++ b/refs/refs-internal.h\n>> @@ -673,6 +673,12 @@ struct ref_storage_be {\n>>  \n>>  \tref_iterator_begin_fn *iterator_begin;\n>>  \tread_raw_ref_fn *read_raw_ref;\n>> +\n>> +\t/*\n>> +\t * Return 0 if the symbolic reference could be read without error.\n>> +\t * Return -1 for generic errors.\n>> +\t * Return -2 if the reference was actually non-symbolic.\n>> +\t */\n>>  \tread_symbolic_ref_fn *read_symbolic_ref;\n>\n> As proposed in the other thread, this could instead be:\n>\n>     /*\n>      * Please refer to `refs_read_symbolic_ref()` for the expected\n>      * behaviour.\n>      /\n\nThanks, that makes sense. So  as a summary, I'll update the comments and also\nthe implementation detail Junio pointed out.\n"},{"id":"507582","messageId":"D5Q32ODJM8P5.6VQ08LDQHPXS@ferdinandy.com","threadId":"62096","inReplyTo":"xmqqldxgez4l.fsf@gitster.g","subject":"Re: [PATCH v13 5/9] remote set-head: better output for --auto","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-19T10:29:34Z","receivedAt":"2024-11-19T10:29:52Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Tue Nov 19, 2024 at 03:27, Junio C Hamano <gitster@pobox.com> wrote:\n> Bence Ferdinandy <bence@ferdinandy.com> writes:\n>\n>> +static void report_set_head_auto(const char *remote, const char *head_name,\n>> +\t\t\tstruct strbuf *b_local_head, int updateres) {\n>\n> \"updateres\" was too mysterious a name.  \"res\" stands for what,\n> \"resource\"?\n>\n> Looking at the way the parameter is used by the code, it seems to\n> indicate that the remote HEAD originally was in a detached state, so\n> \"was_detached\" may be a better name, perhaps?\n\n\"res\" wanted to be short for result, but \"was_detached\" is definitely more readable.\n\n>\n>> +\telse if (!!updateres && b_local_head->len)\n>> +\t\tprintf(_(\"'%s/HEAD' was detached at '%s' and now points to '%s'\\n\"),\n>> +\t\t\tremote, b_local_head->buf, head_name);\n>\n> There is no need for !!; any non-zero integer is true.  !! is useful\n> only in a context that takes only 0 and 1 (like when you are making\n> an assignment to a variable or a structure member that takes only 0\n> or 1).\n>\n>>  static int set_head(int argc, const char **argv, const char *prefix)\n>>  {\n>> -\tint i, opt_a = 0, opt_d = 0, result = 0;\n>> -\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;\n>> +\tint i, opt_a = 0, opt_d = 0, result = 0, updateres;\n>> +\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,\n>> +\t\tb_local_head = STRBUF_INIT;\n>\n>> @@ -1440,20 +1468,27 @@ static int set_head(int argc, const char **argv, const char *prefix)\n>>  \t} else\n>>  \t\tusage_with_options(builtin_remote_sethead_usage, options);\n>>  \n>> -\tif (head_name) {\n>> -\t\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", argv[0], head_name);\n>> -\t\t/* make sure it's valid */\n>> -\t\tif (!refs_ref_exists(refs, b_remote_head.buf))\n>> -\t\t\tresult |= error(_(\"Not a valid ref: %s\"), b_remote_head.buf);\n>> -\t\telse if (refs_update_symref(refs, b_head.buf, b_remote_head.buf, \"remote set-head\"))\n>> -\t\t\tresult |= error(_(\"Could not setup %s\"), b_head.buf);\n>> -\t\telse if (opt_a)\n>> -\t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\n>> -\t\tfree(head_name);\n>> +\tif (!head_name)\n>> +\t\tgoto cleanup;\n>> +\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", argv[0], head_name);\n>> +\tif (!refs_ref_exists(refs, b_remote_head.buf)) {\n>> +\t\tresult |= error(_(\"Not a valid ref: %s\"), b_remote_head.buf);\n>> +\t\tgoto cleanup;\n>> +\t}\n>\n> OK, we refuse to allow a manual \"remote set-head\" to create a\n> dangling symref, which is a faithful rewrite from the original.\n>\n>> +\tupdateres = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n>> +\t\t\t\"remote set-head\", &b_local_head);\n>\n>> +\tif (updateres == -2) {\n>\n> Where does this -2 come from?  It is not the \"you asked to read it\n> as a symref but it wasn't a symref\" thing, which was mapped to -1\n> with [PATCH 3/9].\n\nNo, it is not, but it's also a mistake. It should be `updateres == 1`.\nrefs_update_symref_extended outputs -1 for \"not a symref\" and 1 for any other\nerror currently. Before I touched the code it was 1 for any error, so I left\nthat as is. So we want to error out on set_head if we get a 1 and continue if\nwe get 0 or -1 (and handle the difference in the report_set_head_auto).\n\nThanks for noticing, I'll get that fixed in v14.\n\n\n\n"},{"id":"507584","messageId":"xmqqiksjcx4e.fsf@gitster.g","threadId":"62096","inReplyTo":"D5Q32ODJM8P5.6VQ08LDQHPXS@ferdinandy.com","subject":"Re: [PATCH v13 5/9] remote set-head: better output for --auto","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-11-19T10:54:09Z","receivedAt":"2024-11-19T10:54:12Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Bence Ferdinandy\" <bence@ferdinandy.com> writes:\n\n> No, it is not, but it's also a mistake. It should be `updateres == 1`.\n> refs_update_symref_extended outputs -1 for \"not a symref\" and 1 for any other\n> error currently. Before I touched the code it was 1 for any error, so I left\n> that as is. So we want to error out on set_head if we get a 1 and continue if\n> we get 0 or -1 (and handle the difference in the report_set_head_auto).\n>\n> Thanks for noticing, I'll get that fixed in v14.\n\nIt is good that somebody noticed it (and it may have happened to be\nme), but if it is a \"mistake\" as you said, I wonder why none of your\ntests caught it.  Do we have a gap in test coverage?\n\nThanks.\n"},{"id":"507585","messageId":"D5Q4AYZ9WSPP.3KOENYK0R7XB5@ferdinandy.com","threadId":"62096","inReplyTo":"xmqq5xojgbfl.fsf@gitster.g","subject":"Re: [PATCH v13 8/9] fetch: set remote/HEAD if it does not exist","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-19T11:27:25Z","receivedAt":"2024-11-19T11:28:19Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Tue Nov 19, 2024 at 04:16, Junio C Hamano <gitster@pobox.com> wrote:\n> Bence Ferdinandy <bence@ferdinandy.com> writes:\n>\n>> If the user has remote/HEAD set already and it looks like it has changed\n>> on the server, then print a message, otherwise set it if we can.\n>\n> The user uses remote/origin/HEAD to point at the \"primary thing the\n> user is interested in at the remote 'origin'\".  \"git clone\"\n> propagates remote's HEAD to local because their HEAD is the opinion\n> of the remote which branch they want the users to consider the\n> primary thing.  But the user can have valid reasons to consider a\n> branch different from what the remote suggests as the primary thing,\n> and an explicit \"set-head\" is a way to do so.\n>\n> After such a set-up is made and the user is perfectly happy, would\n> it make sense to repeatedly remind that their HEAD points at\n> something the user is not interested in?\n>\n> Perhaps it may make sense when given the \"--verbose\" option, or\n> after the first time the difference was noticed, but otherwise, I\n> suspect it becomes annoying to those who keep them deliberately\n> different from each other.\n>\n> I wonder if it is more sensible to automate for these two different\n> classes of users with a new configuration per remote.\n\nSince this discussion reaches a bit back I looked up the first message where\nthe current behaviour was discussed, and where in the end it was said we don't\nneed configuration (the very first implementation always updated HEAD).\nStarting here:\n\nhttps://lore.kernel.org/git/xmqqseu7cfsl.fsf@gitster.g/\n\nThat being said:\n\nYour suggestion seems to be the original tri-state configuration that came up\nin one of the original discussions. It was recently requested again for fetch\nto just do this automatically\n(https://lore.kernel.org/git/CAAHKNRGv19rhnqCkJMpE2FomNQBHvSS36aC=fh0UwO+9-6RRfA@mail.gmail.com),\nso at least some people would like to have this fully automated.\n\n>\n>  - When \"git clone\" initially sets up, or \"git remote set-head\n>    --auto\" matches the local to the remote, we set the new\n>    \"remote.$name.autoUpdateHEAD\" configuration variable to true.\n\nI think this is a good default. -> Coming back here I think \"warn\" is a better\ndefault see below.\n\n>\n>  - When \"git remote set-head\" explicitly sets remote/$name/HEAD\n>    to some value (if we can detect the case where it is different\n>    from what the remote has, that would be a plus, but it incurrs\n>    network traffic, so care must be taken to design this part), we\n>    drop the \"remote.$name.autoUpdateHEAD\" configuration variable,\n>    if and only if it is set to true.\n\nI also agree with this. I don't think it would be necessary to do a network\ncheck at this point. If somebody is deliberately changing the remote head\nmanually, they are probably aware of the current situation and can always set\ntheir configuration to \"warn\" if they care, or just check manually once in\na while. At least I feel needing network connection to do this would be more\na hindrance than useful.\n\n>\n>  - When remote.$name.autoUpdateHEAD configuration is true, \"git\n>    fetch\" automatically matches remote/$name/HEAD to what the HEAD\n>    at the remote points at.  If remote.$name.autoUpdateHEAD is set\n>    to some other value (e.g., \"warn\"), show the warning so that the\n>    user can choose to do \"git remote set-head\" to a specific value\n>    (which would not drop the remote.$name.autoUpdateHEAD set to\n>    \"warn\") or \"git remote set-head --auto\" (which would start to\n>    follow whatever remote uses).\n\nMake sense. \n\nOne thing you haven't mentioned is when you start locally with git init && git\nremote add && git fetch. But since this whole series started by wanting to make\nthis the same as cloning I assume it should be \"true\" as well. In fact, if the\ndefault behaviour is true this would just work. \n\nOn the other hand: considering your points in the linked very early discussion,\nmaybe \"warn\" would be a better default? I'm not sure. Most people would not see\nwarnings anyway since I doubt a) many users change their remote/HEAD manually\nor b) many projects change their HEAD at all or if they do, more than once.\nGaving \"true\" as default would change the current behaviour with silently\nupdating existing remote/HEADs. The warning could also tell them that `remote\nset-head --auto` will set this to automatic in the future as well and thus\nremove any warnings or that if they know what they want the can set it to false.\n\n>\n> With something like the above, those who just want to follow along\n> would get remote.$name.autoUpdateHEAD=true and silently follow\n> along, while those who want to explicitly control would be able to\n> do so.  I dunno, but I am fairly negative on a persistent warning\n> message that tells the user that the only normal case is to have\n> remote/origin/HEAD point at the same branch the remote's HEAD points\n> at.\n\nFair point.\n\nThis series has a tendency for growing in complication in each iteration :)\n\nBest,\nBence\n\n"},{"id":"507586","messageId":"D5Q4FTSLI0V9.OWVEG3K8AKRN@ferdinandy.com","threadId":"62096","inReplyTo":"xmqqiksjcx4e.fsf@gitster.g","subject":"Re: [PATCH v13 5/9] remote set-head: better output for --auto","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-19T11:33:45Z","receivedAt":"2024-11-19T11:34:18Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Tue Nov 19, 2024 at 11:54, Junio C Hamano <gitster@pobox.com> wrote:\n> \"Bence Ferdinandy\" <bence@ferdinandy.com> writes:\n>\n>> No, it is not, but it's also a mistake. It should be `updateres == 1`.\n>> refs_update_symref_extended outputs -1 for \"not a symref\" and 1 for any other\n>> error currently. Before I touched the code it was 1 for any error, so I left\n>> that as is. So we want to error out on set_head if we get a 1 and continue if\n>> we get 0 or -1 (and handle the difference in the report_set_head_auto).\n>>\n>> Thanks for noticing, I'll get that fixed in v14.\n>\n> It is good that somebody noticed it (and it may have happened to be\n> me), but if it is a \"mistake\" as you said, I wonder why none of your\n> tests caught it.  Do we have a gap in test coverage?\n\nI think there is no test that is testing this branch:\n\n\tupdateres = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n\t\t\t\"remote set-head\", &b_local_head, 0);\n\tif (updateres == 1) {\n\t\tresult |= error(_(\"Could not setup %s\"), b_head.buf);\n\t\tgoto cleanup;\n\nRunning this in t/ \n\n\tgrep -r \"Could not setup\"\n\nalso yield nothing, so that's probably true. I'm wondering what would be the\nbest way to trigger this error, refs_update_symref needs to fail for this.\n\n\n"},{"id":"507667","messageId":"xmqqed36btxm.fsf@gitster.g","threadId":"62096","inReplyTo":"D5Q4AYZ9WSPP.3KOENYK0R7XB5@ferdinandy.com","subject":"Re: [PATCH v13 8/9] fetch: set remote/HEAD if it does not exist","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-11-20T01:00:37Z","receivedAt":"2024-11-20T01:00:42Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Bence Ferdinandy\" <bence@ferdinandy.com> writes:\n\n> Your suggestion seems to be the original tri-state configuration that came up\n> in one of the original discussions. It was recently requested again for fetch\n> to just do this automatically\n> (https://lore.kernel.org/git/CAAHKNRGv19rhnqCkJMpE2FomNQBHvSS36aC=fh0UwO+9-6RRfA@mail.gmail.com),\n> so at least some people would like to have this fully automated.\n\nAnd that is *not* the only option, and I am trying to help others\nby preventing unconditional warning messages from annoying them.\n\n> On the other hand: considering your points in the linked very early discussion,\n> maybe \"warn\" would be a better default?\n\nThose who want to stick to their manual choice would have to do an\nexplicit set-head once, whether the default is \"true\" or \"warn\", so\ntheir inconvenience would not affect the choice of the default.\n\nI think \"warn\" is a good default _only_ for folks who want to verify\nand then expect they will follow what the upstream does 99% of the\ntime.  For them, when they do not want to switch to what the\nupstream says (which is the rest 1%), there is no good way to\nsquelch the warning other than dropping the configuration, but then\nthey will lose the ability to notice further flips at the remote\nafter that, which may turn out to be a problem.\n\nInstead of \"true\", \"warn\", and \"false\", we might need \"if it is set\nto _this_ value, do not warn and keep the current setting\" to help\nthose who cautiously only allow the remote affect what they use\nafter inspecting, but expect that the choice made by the remote will\nnot be outrageous most of the time for them.\n\nIf we want to solve this completely for them, that is.  I dunno.\n\nThose who want to blindly follow what the upstream says 100% of the\ntime will have to switch to \"true\" just once manually, if the\ndefault were \"warn\", but once may be one time too many for them.\n\nAnd that is why I said \"clone\" (and \"fetch\" setting a non-existing\nHEAD automatically) may want to default to \"true\".\n\n> Fair point.\n>\n> This series has a tendency for growing in complication in each iteration :)\n\nAnytime somebody gets swayed by an opinion to add unconditional\nwarning or unconditional futzing without considering that such a\nmove may annoy some users that are different from the opinion giver,\nwe need to make sure there are escape hatches.  Under the assumption\nthat most projects only flip HEAD very rarely, and considering the\nfact that we have lived with manual setting with set-head and\nnothing else, we could drop the automatic setting by \"fetch\"\naltogether to reduce the scope of the series without losing much\n*and* more importantly without harming existing users.\n\nI think we could make it a multi-step transition in order to keep\nchanges to \"git fetch\" we make in each step smaller, which may go\nlike so:\n\n 1) Let \"git fetch\" create missing remotes/$name/HEAD.  No other\n    changes.\n\n 2) Introduce a remote.$name.followRemoteHEAD that can say \"the last\n    branch we observed that they point at is 'main'\".  Set it when\n    we clone, When \"git fetch\" notices that the remote flipped its\n    HEAD to a different value, warn and update (which would give us\n    the \"warn only once, do not annoy users by constantly warning\").\n    The user can delete the configuration to squelch \"git fetch\" (in\n    other words, the repository does not remember the last setting,\n    so we refrain from warning \"it has changed since the last time\").\n    No other changes.\n\n 3) Allow remote.$name.followRemoteHEAD to be set to\n\n    3-a) \"always\", which would squelch the warning and causes \"git\n    fetch\" to always repoint the local remotes/$name/HEAD to\n    whatever the remote says,\n\n    3-b) \"warn\", which would cause \"git fetch\" to warn when the\n    remote's HEAD is different from local remotes/$name/HEAD,\n\n    3-c) \"never\", which would cause \"git fetch\" to even omit the\n    \"copy if it does not exist\" we add in 1) to help those who\n    deliberately want to keep remotes/$name/HEAD missing (to force\n    themselves to be more explicit, saying things like \"git checkout\n    -b topic origin/maint\" and \"git rebase origin/master\", but never\n    \"git checkout --detach origin\").\n\nAnd we can stop this round at the first step (plus any other changes\noutside \"git fetch\", like \"set-head --auto\").\n\nThanks.\n"},{"id":"507675","messageId":"xmqqttc28wqm.fsf@gitster.g","threadId":"62096","inReplyTo":"xmqqed36btxm.fsf@gitster.g","subject":"Re: [PATCH v13 8/9] fetch: set remote/HEAD if it does not exist","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-11-20T02:28:17Z","receivedAt":"2024-11-20T02:40:15Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> \"Bence Ferdinandy\" <bence@ferdinandy.com> writes:\n>\n>> Your suggestion seems to be the original tri-state configuration that came up\n>> in one of the original discussions. It was recently requested again for fetch\n>> to just do this automatically\n>> (https://lore.kernel.org/git/CAAHKNRGv19rhnqCkJMpE2FomNQBHvSS36aC=fh0UwO+9-6RRfA@mail.gmail.com),\n>> so at least some people would like to have this fully automated.\n>\n> And that is *not* the only option, and I am trying to help others\n> by preventing unconditional warning messages from annoying them.\n\nBy the way, because I've left it out from my messages, some folks\nwho are reading from sideways might not realize this, so let's make\nit a bit more explicit.  One thing to note is that the assumption\nthat HEAD at the remote rarely changes _only_ holds for forges and\nthe like, a typically bare repository used for publishing without a\nworking tree.\n\nIf you have a clone from a repository with a working tree (this\nhappens when you have two places to work, either on two machines or\ntwo separate repositories on a same machine) to allow you to work\nhere during the day and work there after work, the HEAD at the clone\nsource may change to the branch you happen to be working on when\nleft there.  In such a setting, it is reasonable to make the\nrepositories aware of each other (i.e. the after-work repository may\nhave refs/remotes/at-work/* remote-tracking branches while the\nat-work repository keeps refs/remotes/after-work/* remote-tracking\nbranches, to keep track of each other), and \"git fetch\" that warns\nevery time it notices the remote end has HEAD pointing at a\ndifferent branch would be annoying, as they would likely be working\non different \"project\" when they leave after work.\n\nUsers who fall outside of a typical \"we have a central repository\nand the only interaction with the repository is we clone and\nfetch/pull from it and nothing else\" hosting site setting, are why\nI'll encourage folks to tread carefully when they are tempted to\nmake anything unconditional.\n\nThanks.\n"},{"id":"507677","messageId":"xmqqr0767ej6.fsf@gitster.g","threadId":"62096","inReplyTo":"20241118151755.756265-2-bence@ferdinandy.com","subject":"Re: [PATCH v13 1/9] t/t5505-remote: set default branch to main","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-11-20T03:46:53Z","receivedAt":"2024-11-20T03:46:57Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bence Ferdinandy <bence@ferdinandy.com> writes:\n\n> Consider the bare repository called \"mirror\" in the test. Running `git\n> remote add --mirror -f origin ../one` will not change HEAD, consequently\n> if init.defaultBranch is not the same as what HEAD in the remote\n> (\"one\"), HEAD in \"mirror\" will be pointing to a non-existent reference.\n> Hence if \"mirror\" is used as a remote by yet another repository,\n> ls-remote will not show HEAD. On the other hand, if init.defaultBranch\n> happens to match HEAD in \"one\", then ls-remote will show HEAD.\n>\n> Since the \"ci/run-build-and-tests.sh\" script globally exports\n> GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main, there may be a drift in some\n\n\"main,\" -> \"main for some (but not all) jobs,\" or something.  IOW,\nthe point of that forced export is to test both.\n\n> tests between how the test repositories are set up in the CI and during\n> local testing, if the test itself uses \"master\" as default instead of\n> \"main\".\n"},{"id":"507716","messageId":"D5QY1HE55OLC.1RI15N4DC2O9A@ferdinandy.com","threadId":"62096","inReplyTo":"xmqqed36btxm.fsf@gitster.g","subject":"Re: [PATCH v13 8/9] fetch: set remote/HEAD if it does not exist","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-20T10:45:34Z","receivedAt":"2024-11-20T10:47:25Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Wed Nov 20, 2024 at 02:00, Junio C Hamano <gitster@pobox.com> wrote:\n> \"Bence Ferdinandy\" <bence@ferdinandy.com> writes:\n>\n>> Your suggestion seems to be the original tri-state configuration that came up\n>> in one of the original discussions. It was recently requested again for fetch\n>> to just do this automatically\n>> (https://lore.kernel.org/git/CAAHKNRGv19rhnqCkJMpE2FomNQBHvSS36aC=fh0UwO+9-6RRfA@mail.gmail.com),\n>> so at least some people would like to have this fully automated.\n>\n> And that is *not* the only option, and I am trying to help others\n> by preventing unconditional warning messages from annoying them.\n>\n>> On the other hand: considering your points in the linked very early discussion,\n>> maybe \"warn\" would be a better default?\n>\n> Those who want to stick to their manual choice would have to do an\n> explicit set-head once, whether the default is \"true\" or \"warn\", so\n> their inconvenience would not affect the choice of the default.\n>\n> I think \"warn\" is a good default _only_ for folks who want to verify\n> and then expect they will follow what the upstream does 99% of the\n> time.  For them, when they do not want to switch to what the\n> upstream says (which is the rest 1%), there is no good way to\n> squelch the warning other than dropping the configuration, but then\n> they will lose the ability to notice further flips at the remote\n> after that, which may turn out to be a problem.\n>\n> Instead of \"true\", \"warn\", and \"false\", we might need \"if it is set\n> to _this_ value, do not warn and keep the current setting\" to help\n> those who cautiously only allow the remote affect what they use\n> after inspecting, but expect that the choice made by the remote will\n> not be outrageous most of the time for them.\n>\n> If we want to solve this completely for them, that is.  I dunno.\n\nI initially thought you meant a fourth setting here, but what you are\nsuggesting here is that in case the user has \"remote.origin.followremotehead\n= never\" set even in case of \"init && remote add && fetch\" HEAD should not be\ncreated, which is a bit different, because originally we discussed only not\nchanging after the initial creation. This makes a lot of sense.\n\n>\n> Those who want to blindly follow what the upstream says 100% of the\n> time will have to switch to \"true\" just once manually, if the\n> default were \"warn\", but once may be one time too many for them.\n>\n> And that is why I said \"clone\" (and \"fetch\" setting a non-existing\n> HEAD automatically) may want to default to \"true\".\n\nFair point again.\n\n>\n>> Fair point.\n>>\n>> This series has a tendency for growing in complication in each iteration :)\n>\n> Anytime somebody gets swayed by an opinion to add unconditional\n> warning or unconditional futzing without considering that such a\n> move may annoy some users that are different from the opinion giver,\n> we need to make sure there are escape hatches.  Under the assumption\n> that most projects only flip HEAD very rarely, and considering the\n> fact that we have lived with manual setting with set-head and\n> nothing else, we could drop the automatic setting by \"fetch\"\n> altogether to reduce the scope of the series without losing much\n> *and* more importantly without harming existing users.\n\nDon't get me wrong, I'd like to do the right thing here, and appreciate that my\nperspective is rather limited. I was just reflecting on the fact that the first\n\"working\" version of the series was only two patches. Writing this is fun and\neducational for me.\n\n>\n> I think we could make it a multi-step transition in order to keep\n> changes to \"git fetch\" we make in each step smaller, which may go\n> like so:\n\nThat being said, if it's possible to graduate a part of the series when it's\nready and where it makes sense to split, that would be welcome. As you write\nbelow the first such place would be remote set-head --auto. Not that that is\nready yet, I need to fix the mistake you spotted, update a commit message, and\nadd a test-coverage. But in that case I might send a v14 with just these\nupdates and work on getting the fetch settings in parallel for a v15.\nTechnically I would just need to build the series off next for a while, right?\n\n>\n>  1) Let \"git fetch\" create missing remotes/$name/HEAD.  No other\n>     changes.\n\nSo this is a bit simpler version of what we have now (as long as it doesn't\nbother for example you, that there is a state which will create HEAD without\nbeing able to disable it :)) Should this even print anything? Probably not,\nclone also doesn't tell you that the HEAD was set.\n\n>\n>  2) Introduce a remote.$name.followRemoteHEAD that can say \"the last\n>     branch we observed that they point at is 'main'\".  Set it when\n>     we clone, When \"git fetch\" notices that the remote flipped its\n>     HEAD to a different value, warn and update (which would give us\n>     the \"warn only once, do not annoy users by constantly warning\").\n>     The user can delete the configuration to squelch \"git fetch\" (in\n>     other words, the repository does not remember the last setting,\n>     so we refrain from warning \"it has changed since the last time\").\n>     No other changes.\n>\n>  3) Allow remote.$name.followRemoteHEAD to be set to\n>\n>     3-a) \"always\", which would squelch the warning and causes \"git\n>     fetch\" to always repoint the local remotes/$name/HEAD to\n>     whatever the remote says,\n\nBut this should print similar messages as remote set-head --auto when there is\nan actual change done, right? And should probably respect --quiet?\n\n>\n>     3-b) \"warn\", which would cause \"git fetch\" to warn when the\n>     remote's HEAD is different from local remotes/$name/HEAD,\n\nThis should also respect --quite I assume.\n\n>\n>     3-c) \"never\", which would cause \"git fetch\" to even omit the\n>     \"copy if it does not exist\" we add in 1) to help those who\n>     deliberately want to keep remotes/$name/HEAD missing (to force\n>     themselves to be more explicit, saying things like \"git checkout\n>     -b topic origin/maint\" and \"git rebase origin/master\", but never\n>     \"git checkout --detach origin\").\n\nI think skipping 2 and jumping straight to the final configuration would be\neasier, no need to wrap one's head around a configuration state that will\nessentially never really exist and most of the extra complexity (at least from\nmy perspective) comes from having to have a configuration at all. \n\nThanks,\nBence\n\n"},{"id":"507731","messageId":"D5R0ONTYT1EG.K1XD9FZQO6R6@ferdinandy.com","threadId":"62096","inReplyTo":"xmqqiksjcx4e.fsf@gitster.g","subject":"Re: [PATCH v13 5/9] remote set-head: better output for --auto","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-20T12:49:53Z","receivedAt":"2024-11-20T12:50:43Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Tue Nov 19, 2024 at 11:54, Junio C Hamano <gitster@pobox.com> wrote:\n> \"Bence Ferdinandy\" <bence@ferdinandy.com> writes:\n>\n>> No, it is not, but it's also a mistake. It should be `updateres == 1`.\n>> refs_update_symref_extended outputs -1 for \"not a symref\" and 1 for any other\n>> error currently. Before I touched the code it was 1 for any error, so I left\n>> that as is. So we want to error out on set_head if we get a 1 and continue if\n>> we get 0 or -1 (and handle the difference in the report_set_head_auto).\n>>\n>> Thanks for noticing, I'll get that fixed in v14.\n>\n> It is good that somebody noticed it (and it may have happened to be\n> me), but if it is a \"mistake\" as you said, I wonder why none of your\n> tests caught it.  Do we have a gap in test coverage?\n>\n> Thanks.\n\nI've been looking into how I can force an error on remote set-head to test the\nerror branch. For the files backend it seems to be pretty easy\n\ntouch .git/refs/remotes/origin/HEAD.lock\ngit remote set-head [something]\nrm .git/refs/remotes/origin/HEAD.lock\n\nMy problem is that in this case, since I want to force an error on a user\ncommand, I don't see a way that is possible with git commands and I'm not sure\nhow I could manipulate the reftable file in a way that can be reversed and only\nerrors out the particular thing I need.\n\nI've been looking through the functions called here and it seems other errors\nall depend passing wrong parameters but that is not possible (and should not be\npossible) to trigger with remote set-head, so I think something \"manual\" needs\nto be done, like the above.\n\nSince this particular test just wants to test what happens if\n`refs_update_symref_extended` returns with 1 and not testing correct behaviour\nof backends and such, would it be acceptable if this particular test case would\ncheck for the backend and if it is reftables it will just pass without actually\nchecking and do the manually locking thing above for the files backend?\n\nThanks,\nBence\n"},{"id":"507798","messageId":"xmqqmshth32w.fsf@gitster.g","threadId":"62096","inReplyTo":"D5R0ONTYT1EG.K1XD9FZQO6R6@ferdinandy.com","subject":"Re: [PATCH v13 5/9] remote set-head: better output for --auto","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-11-20T23:56:23Z","receivedAt":"2024-11-20T23:56:26Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Bence Ferdinandy\" <bence@ferdinandy.com> writes:\n\n> Since this particular test just wants to test what happens if\n> `refs_update_symref_extended` returns with 1 and not testing correct behaviour\n> of backends and such, would it be acceptable if this particular test case would\n> check for the backend and if it is reftables it will just pass without actually\n> checking and do the manually locking thing above for the files backend?\n\nI think we have some pre-made test prerequisite to skip tests unless\nrun with a particular ref backend, exactly for that.  Perhaps\n\n    test_expect_success REFFILES 'block update to check error' '\n\t... manually block update and see how the operation\n\t... errors out\n    '\n\nor something?\n\n\n"},{"id":"507853","messageId":"20241121225757.3877852-1-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241118151755.756265-1-bence@ferdinandy.com","subject":"[PATCH v14 00/10] set-head/fetch remote/HEAD updates","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-21T22:55:00Z","receivedAt":"2024-11-21T23:10:07Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Notable changes since v13:\n\n- As discussed in\n  https://lore.kernel.org/git/xmqqed36btxm.fsf@gitster.g/ this iteration\n  has dropped printing any warning during fetch and now only sets HEAD\n  if it doesn't already exist, otherwise it doesn't do anything.\n\n- A new patch adds a new test case for testing the failure branch of\n  remote set-head. This also allowed to clear up a bug in a later patch.\n\nI will start working on a follow up patch that will add configuration\noptions to control how fetch sets HEAD or not. The current state should\nbe fine for most people, except those who explicitly wish NOT to have\nremote/HEAD set. Other than that the current state is now hopefully\nmature enough to graduate (unless I made yet another mistake :)).\n\nBence Ferdinandy (10):\n  t/t5505-remote: set default branch to main\n  t/t5505-remote: test failure of set-head\n  refs: standardize output of refs_read_symbolic_ref\n  refs: atomically record overwritten ref in update_symref\n  remote set-head: refactor for readability\n  remote set-head: better output for --auto\n  refs: add TRANSACTION_CREATE_EXISTS error\n  refs: add create_only option to refs_update_symref_extended\n  fetch: set remote/HEAD if it does not exist\n  fetch set_head: handle mirrored bare repositories\n\n builtin/fetch.c                  |  74 +++++++++++++++++++++\n builtin/remote.c                 |  72 +++++++++++++++------\n refs.c                           |  47 ++++++++++++--\n refs.h                           |  19 +++++-\n refs/files-backend.c             |  31 +++++----\n refs/refs-internal.h             |   5 ++\n refs/reftable-backend.c          |  14 ++--\n t/t4207-log-decoration-colors.sh |   3 +-\n t/t5505-remote.sh                | 107 +++++++++++++++++++++++++++++--\n t/t5510-fetch.sh                 |  24 +++++++\n t/t5512-ls-remote.sh             |   2 +\n t/t5514-fetch-multiple.sh        |  17 ++++-\n t/t5516-fetch-push.sh            |   3 +-\n t/t5527-fetch-odd-refs.sh        |   3 +-\n t/t7900-maintenance.sh           |   3 +-\n t/t9210-scalar.sh                |   5 +-\n t/t9211-scalar-clone.sh          |   6 +-\n t/t9902-completion.sh            |  65 +++++++++++++++++++\n 18 files changed, 440 insertions(+), 60 deletions(-)\n\n-- \n2.47.0.298.g52a96ec17b\n\n"},{"id":"507854","messageId":"20241121225757.3877852-2-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241121225757.3877852-1-bence@ferdinandy.com","subject":"[PATCH v14 01/10] t/t5505-remote: set default branch to main","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-21T22:55:01Z","receivedAt":"2024-11-21T23:10:09Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Consider the bare repository called \"mirror\" in the test. Running `git\nremote add --mirror -f origin ../one` will not change HEAD, consequently\nif init.defaultBranch is not the same as what HEAD in the remote\n(\"one\"), HEAD in \"mirror\" will be pointing to a non-existent reference.\nHence if \"mirror\" is used as a remote by yet another repository,\nls-remote will not show HEAD. On the other hand, if init.defaultBranch\nhappens to match HEAD in \"one\", then ls-remote will show HEAD.\n\nSince the \"ci/run-build-and-tests.sh\" script globally exports\nGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main for some (but not all) jobs,\nthere may be a drift in some tests between how the test repositories are\nset up in the CI and during local testing, if the test itself uses\n\"master\" as default instead of \"main\". In particular, this happens in\nt5505-remote.sh. This issue does not manifest currently, as the test\ndoes not do any remote HEAD manipulation where this would come up, but\nshould such things be added, a locally passing test would break the CI\nand vice-versa.\n\nSet GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main in t5505-remote to be\nconsistent with the CI.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v9: - new patch\n        - a bandaid for the CI issue noticed by Taylor (cf:\n          https://lore.kernel.org/git/Zw8IKyPkG0Hr6%2F5t@nand.local/), but\n          see\n          https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/ for the root cause in detail\n    \n    v10: no change\n    \n    v11: no change\n    \n    v12: added forgotten sign-off\n    \n    v13: commit message udpated to be more precise\n    \n    v14: made explicit in the commit message that the default branch is only\n         set for some CI jobs\n\n t/t5505-remote.sh | 3 +++\n 1 file changed, 3 insertions(+)\n\ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex 532035933f..9b50276646 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -2,6 +2,9 @@\n \n test_description='git remote porcelain-ish'\n \n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n-- \n2.47.0.298.g52a96ec17b\n\n"},{"id":"507855","messageId":"20241121225757.3877852-4-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241121225757.3877852-1-bence@ferdinandy.com","subject":"[PATCH v14 03/10] refs: standardize output of refs_read_symbolic_ref","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-21T22:55:03Z","receivedAt":"2024-11-21T23:10:11Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"When the symbolic reference we want to read with refs_read_symbolic_ref\nis actually not a symbolic reference, the files and the reftable\nbackends return different values (1 and -1 respectively). Standardize\nthe returned values so that 0 is success, -1 is a generic error and -2\nis that the reference was actually non-symbolic.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v13: new patch\n    \n    v14: - simplified the logic in reftables backend (thanks Junio)\n         - update the comment in refs.h (thanks Patrick)\n         - rewrote comment in refs-internal.h to point to the one in refs.h\n         - created NOT_A_SYMREF=-2 constant\n\n refs.h                  | 11 +++++++++++\n refs/files-backend.c    |  7 +++----\n refs/refs-internal.h    |  5 +++++\n refs/reftable-backend.c |  8 +++++---\n 4 files changed, 24 insertions(+), 7 deletions(-)\n\ndiff --git a/refs.h b/refs.h\nindex 108dfc93b3..22c2b45b8b 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -83,6 +83,17 @@ int refs_read_ref_full(struct ref_store *refs, const char *refname,\n \n int refs_read_ref(struct ref_store *refs, const char *refname, struct object_id *oid);\n \n+#define NOT_A_SYMREF -2\n+\n+/*\n+ * Read the symbolic ref named \"refname\" and write its immediate referent into\n+ * the provided buffer. Referent is left empty if \"refname\" is not a symbolic\n+ * ref. It does not resolve the symbolic reference recursively in case the\n+ * target is also a symbolic ref.\n+ *\n+ * Returns 0 on success, -2 if the \"refname\" is not a symbolic ref,\n+ * -1 otherwise.\n+ */\n int refs_read_symbolic_ref(struct ref_store *ref_store, const char *refname,\n \t\t\t   struct strbuf *referent);\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 0824c0b8a9..4cc43c32f2 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -596,10 +596,9 @@ static int files_read_symbolic_ref(struct ref_store *ref_store, const char *refn\n \tunsigned int type;\n \n \tret = read_ref_internal(ref_store, refname, &oid, referent, &type, &failure_errno, 1);\n-\tif (ret)\n-\t\treturn ret;\n-\n-\treturn !(type & REF_ISSYMREF);\n+\tif (!ret && !(type & REF_ISSYMREF))\n+\t\treturn NOT_A_SYMREF;\n+\treturn ret;\n }\n \n int parse_loose_ref_contents(const struct git_hash_algo *algop,\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 2313c830d8..1399fee61c 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -673,6 +673,11 @@ struct ref_storage_be {\n \n \tref_iterator_begin_fn *iterator_begin;\n \tread_raw_ref_fn *read_raw_ref;\n+\n+\t/*\n+\t * Please refer to `refs_read_symbolic_ref()` for the expected\n+\t * behaviour.\n+\t */\n \tread_symbolic_ref_fn *read_symbolic_ref;\n \n \treflog_iterator_begin_fn *reflog_iterator_begin;\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 38eb14d591..1809e3426a 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -830,10 +830,12 @@ static int reftable_be_read_symbolic_ref(struct ref_store *ref_store,\n \t\treturn ret;\n \n \tret = reftable_stack_read_ref(stack, refname, &ref);\n-\tif (ret == 0 && ref.value_type == REFTABLE_REF_SYMREF)\n+        if (ret)\n+                ret = -1;\n+        else if (ref.value_type == REFTABLE_REF_SYMREF)\n \t\tstrbuf_addstr(referent, ref.value.symref);\n-\telse\n-\t\tret = -1;\n+        else\n+                ret = NOT_A_SYMREF;\n \n \treftable_ref_record_release(&ref);\n \treturn ret;\n-- \n2.47.0.298.g52a96ec17b\n\n"},{"id":"507856","messageId":"20241121225757.3877852-3-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241121225757.3877852-1-bence@ferdinandy.com","subject":"[PATCH v14 02/10] t/t5505-remote: test failure of set-head","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-21T22:55:02Z","receivedAt":"2024-11-21T23:10:14Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"The test coverage was missing a test for the failure branch of remote\nset-head auto's output. Add the missing text and while we are at it,\ncorrect a small grammatical mistake in the error's output (\"setup\" is\nthe noun, \"set up\" is the verb).\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v14: new patch\n\n builtin/remote.c  |  2 +-\n t/t5505-remote.sh | 12 ++++++++++++\n 2 files changed, 13 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 76670ddd8b..8a182439f2 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1445,7 +1445,7 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\tif (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))\n \t\t\tresult |= error(_(\"Not a valid ref: %s\"), buf2.buf);\n \t\telse if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote set-head\"))\n-\t\t\tresult |= error(_(\"Could not setup %s\"), buf.buf);\n+\t\t\tresult |= error(_(\"Could not set up %s\"), buf.buf);\n \t\telse if (opt_a)\n \t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\n \t\tfree(head_name);\ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex 9b50276646..4e127bf5b8 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -432,6 +432,18 @@ test_expect_success 'set-head --auto' '\n \t)\n '\n \n+test_expect_success REFFILES 'set-head --auto failure' '\n+\t(\n+\t\tcd test &&\n+\t\ttouch .git/refs/remotes/origin/HEAD.lock &&\n+\t\tgit remote set-head --auto origin 2>errormsg ||\n+\t\ttail -n1 errormsg >output &&\n+\t\trm .git/refs/remotes/origin/HEAD.lock &&\n+\t\techo \"error: Could not set up refs/remotes/origin/HEAD\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n test_expect_success 'set-head --auto has no problem w/multiple HEADs' '\n \t(\n \t\tcd test &&\n-- \n2.47.0.298.g52a96ec17b\n\n"},{"id":"507857","messageId":"20241121225757.3877852-5-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241121225757.3877852-1-bence@ferdinandy.com","subject":"[PATCH v14 04/10] refs: atomically record overwritten ref in update_symref","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-21T22:55:04Z","receivedAt":"2024-11-21T23:10:25Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"When updating a symref with update_symref it's currently not possible to\nknow for sure what was the previous value that was overwritten. Extend\nrefs_update_symref under a new function name, to record the value after\nthe ref has been locked if the caller of refs_update_symref_extended\nrequests it via a new variable in the function call. Make the return\nvalue of the function notify the caller, if the previous value was\nactually not a symbolic reference. Keep the original refs_update_symref\nfunction with the same signature, but now as a wrapper around\nrefs_update_symref_extended.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v4: new patch\n    \n    v5: - added before_target to reftables backend\n        - added an extra safety check for transaction's existence in refs.c\n    \n    v6: - no change\n    \n    v7: - remove the whole before_target concept from the backends and\n          handle checking it in refs.c instead (thanks Karthik)\n        - rename the before_target to referent which is how the same concept\n          is called in the backends\n        - change commit prefix to be more in line with project standards\n    \n    v8: no change\n    \n    v9: - instead of adding parameters to refs_update_symref, rename what\n          was in v8 as refs_update_symref_extended and make refs_update_symref\n          a wrapper for that. This significantly reduces the number of files\n          that need to be touched, and avoids adding a lot of dummy NULL-s\n          in unrelated places.\n    \n    v10: no change\n    \n    v11: no change\n    \n    v12: no change\n    \n    v13: if referent is a non-symbolic ref, record the hash in referent and\n        signal this with a return value of -1\n    \n    v14: - mistakes were made: trying to read the referent should NOT lead to\n         an actual error, even if it doesn't work out in the end, that part of\n         the code should only set the return to -2 or not touch it\n         - the returned error code by the above was also incorrect and now\n           is -2 (and uses NOT_A_SYMREF now)\n\n refs.c | 22 ++++++++++++++++++++--\n refs.h |  4 ++++\n 2 files changed, 24 insertions(+), 2 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 5f729ed412..d80efd58f0 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2115,6 +2115,13 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct\n \n int refs_update_symref(struct ref_store *refs, const char *ref,\n \t\t       const char *target, const char *logmsg)\n+{\n+\treturn refs_update_symref_extended(refs, ref, target, logmsg, NULL);\n+}\n+\n+int refs_update_symref_extended(struct ref_store *refs, const char *ref,\n+\t\t       const char *target, const char *logmsg,\n+\t\t       struct strbuf *referent)\n {\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n@@ -2125,10 +2132,22 @@ int refs_update_symref(struct ref_store *refs, const char *ref,\n \t    ref_transaction_update(transaction, ref, NULL, NULL,\n \t\t\t\t   target, NULL, REF_NO_DEREF,\n \t\t\t\t   logmsg, &err) ||\n-\t    ref_transaction_commit(transaction, &err)) {\n+\t    ref_transaction_prepare(transaction, &err)) {\n \t\tret = error(\"%s\", err.buf);\n+\t\tgoto cleanup;\n \t}\n+\tif (referent && refs_read_symbolic_ref(refs, ref, referent) == NOT_A_SYMREF) {\n+\t\tstruct object_id oid;\n+\t\tif (!refs_read_ref(refs, ref, &oid)) {\n+\t\t\tstrbuf_addstr(referent, oid_to_hex(&oid));\n+\t\t\tret = NOT_A_SYMREF;\n+\t\t}\n+\t}\n+\n+\tif (ref_transaction_commit(transaction, &err))\n+\t\tret = error(\"%s\", err.buf);\n \n+cleanup:\n \tstrbuf_release(&err);\n \tif (transaction)\n \t\tref_transaction_free(transaction);\n@@ -2948,4 +2967,3 @@ int ref_update_expects_existing_old_ref(struct ref_update *update)\n \treturn (update->flags & REF_HAVE_OLD) &&\n \t\t(!is_null_oid(&update->old_oid) || update->old_target);\n }\n-\ndiff --git a/refs.h b/refs.h\nindex 22c2b45b8b..5c46ac9f34 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -584,6 +584,10 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,\n int refs_update_symref(struct ref_store *refs, const char *refname,\n \t\t       const char *target, const char *logmsg);\n \n+int refs_update_symref_extended(struct ref_store *refs, const char *refname,\n+\t\t       const char *target, const char *logmsg,\n+\t\t       struct strbuf *referent);\n+\n enum action_on_err {\n \tUPDATE_REFS_MSG_ON_ERR,\n \tUPDATE_REFS_DIE_ON_ERR,\n-- \n2.47.0.298.g52a96ec17b\n\n"},{"id":"507858","messageId":"20241121225757.3877852-6-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241121225757.3877852-1-bence@ferdinandy.com","subject":"[PATCH v14 05/10] remote set-head: refactor for readability","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-21T22:55:05Z","receivedAt":"2024-11-21T23:10:25Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Make two different readability refactors:\n\nRename strbufs \"buf\" and \"buf2\" to something more explanatory.\n\nInstead of calling get_main_ref_store(the_repository) multiple times,\ncall it once and store the result in a new refs variable. Although this\nchange probably offers some performance benefits, the main purpose is to\nshorten the line lengths of function calls using this variable.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v5: new patch (split from the next patch as a preparatory step)\n    \n    v6: no change\n    \n    v7: - change commit prefix to be more in line with project standards\n    \n    v8: no change\n    \n    v9: - further improve readability by renaming buf, and buf2 consistently\n          with how patch 6 was already done\n    \n    v10: no change\n    \n    v11: no change\n    \n    v12: no change\n    \n    v13: more precise wording for commit message\n    \n    v14: no change\n\n builtin/remote.c | 23 ++++++++++++-----------\n 1 file changed, 12 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 8a182439f2..bcc3ee91ef 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1402,8 +1402,9 @@ static int show(int argc, const char **argv, const char *prefix)\n static int set_head(int argc, const char **argv, const char *prefix)\n {\n \tint i, opt_a = 0, opt_d = 0, result = 0;\n-\tstruct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;\n+\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;\n \tchar *head_name = NULL;\n+\tstruct ref_store *refs = get_main_ref_store(the_repository);\n \n \tstruct option options[] = {\n \t\tOPT_BOOL('a', \"auto\", &opt_a,\n@@ -1415,7 +1416,7 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \targc = parse_options(argc, argv, prefix, options,\n \t\t\t     builtin_remote_sethead_usage, 0);\n \tif (argc)\n-\t\tstrbuf_addf(&buf, \"refs/remotes/%s/HEAD\", argv[0]);\n+\t\tstrbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", argv[0]);\n \n \tif (!opt_a && !opt_d && argc == 2) {\n \t\thead_name = xstrdup(argv[1]);\n@@ -1434,25 +1435,25 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\t\thead_name = xstrdup(states.heads.items[0].string);\n \t\tfree_remote_ref_states(&states);\n \t} else if (opt_d && !opt_a && argc == 1) {\n-\t\tif (refs_delete_ref(get_main_ref_store(the_repository), NULL, buf.buf, NULL, REF_NO_DEREF))\n-\t\t\tresult |= error(_(\"Could not delete %s\"), buf.buf);\n+\t\tif (refs_delete_ref(refs, NULL, b_head.buf, NULL, REF_NO_DEREF))\n+\t\t\tresult |= error(_(\"Could not delete %s\"), b_head.buf);\n \t} else\n \t\tusage_with_options(builtin_remote_sethead_usage, options);\n \n \tif (head_name) {\n-\t\tstrbuf_addf(&buf2, \"refs/remotes/%s/%s\", argv[0], head_name);\n+\t\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", argv[0], head_name);\n \t\t/* make sure it's valid */\n-\t\tif (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))\n-\t\t\tresult |= error(_(\"Not a valid ref: %s\"), buf2.buf);\n-\t\telse if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote set-head\"))\n-\t\t\tresult |= error(_(\"Could not set up %s\"), buf.buf);\n+\t\tif (!refs_ref_exists(refs, b_remote_head.buf))\n+\t\t\tresult |= error(_(\"Not a valid ref: %s\"), b_remote_head.buf);\n+\t\telse if (refs_update_symref(refs, b_head.buf, b_remote_head.buf, \"remote set-head\"))\n+\t\t\tresult |= error(_(\"Could not set up %s\"), b_head.buf);\n \t\telse if (opt_a)\n \t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\n \t\tfree(head_name);\n \t}\n \n-\tstrbuf_release(&buf);\n-\tstrbuf_release(&buf2);\n+\tstrbuf_release(&b_head);\n+\tstrbuf_release(&b_remote_head);\n \treturn result;\n }\n \n-- \n2.47.0.298.g52a96ec17b\n\n"},{"id":"507859","messageId":"20241121225757.3877852-8-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241121225757.3877852-1-bence@ferdinandy.com","subject":"[PATCH v14 07/10] refs: add TRANSACTION_CREATE_EXISTS error","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-21T22:55:07Z","receivedAt":"2024-11-21T23:10:36Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Currently there is only one special error for transaction, for when\nthere is a naming conflict, all other errors are dumped under a generic\nerror. Add a new special error case for when the caller requests the\nreference to be updated only when it does not yet exist and the\nreference actually does exist.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v4: new patch\n    v5: no change\n    v6: no change\n    \n    v7: - change commit prefix to be more in line with project standards\n        - changed error checking to Karthik's suggestion\n    \n    v8: no change\n    \n    v9: - no change\n    \n    v10: no change\n    \n    v11: no change\n    \n    v12: no change\n    \n    v13: no change\n    \n    v14: no change\n\n refs.h                  |  4 +++-\n refs/files-backend.c    | 24 ++++++++++++++++--------\n refs/reftable-backend.c |  6 ++++--\n 3 files changed, 23 insertions(+), 11 deletions(-)\n\ndiff --git a/refs.h b/refs.h\nindex 5c46ac9f34..b243739e4b 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -773,8 +773,10 @@ int ref_transaction_verify(struct ref_transaction *transaction,\n \n /* Naming conflict (for example, the ref names A and A/B conflict). */\n #define TRANSACTION_NAME_CONFLICT -1\n+/* When only creation was requested, but the ref already exists. */\n+#define TRANSACTION_CREATE_EXISTS -2\n /* All other errors. */\n-#define TRANSACTION_GENERIC_ERROR -2\n+#define TRANSACTION_GENERIC_ERROR -3\n \n /*\n  * Perform the preparatory stages of committing `transaction`. Acquire\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 4cc43c32f2..23ae74089d 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2501,14 +2501,18 @@ static int split_symref_update(struct ref_update *update,\n static int check_old_oid(struct ref_update *update, struct object_id *oid,\n \t\t\t struct strbuf *err)\n {\n+\tint ret = TRANSACTION_GENERIC_ERROR;\n+\n \tif (!(update->flags & REF_HAVE_OLD) ||\n \t\t   oideq(oid, &update->old_oid))\n \t\treturn 0;\n \n-\tif (is_null_oid(&update->old_oid))\n+\tif (is_null_oid(&update->old_oid)) {\n \t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n \t\t\t    \"reference already exists\",\n \t\t\t    ref_update_original_update_refname(update));\n+\t\tret = TRANSACTION_CREATE_EXISTS;\n+\t}\n \telse if (is_null_oid(oid))\n \t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n \t\t\t    \"reference is missing but expected %s\",\n@@ -2521,7 +2525,7 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid,\n \t\t\t    oid_to_hex(oid),\n \t\t\t    oid_to_hex(&update->old_oid));\n \n-\treturn -1;\n+\treturn ret;\n }\n \n /*\n@@ -2601,9 +2605,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\t\tgoto out;\n \t\t\t\t}\n-\t\t\t} else if  (check_old_oid(update, &lock->old_oid, err)) {\n-\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n-\t\t\t\tgoto out;\n+\t\t\t} else {\n+\t\t\t\tret = check_old_oid(update, &lock->old_oid, err);\n+\t\t\t\tif  (ret) {\n+\t\t\t\t\tgoto out;\n+\t\t\t\t}\n \t\t\t}\n \t\t} else {\n \t\t\t/*\n@@ -2634,9 +2640,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\t\t    update->old_target);\n \t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\tgoto out;\n-\t\t} else if  (check_old_oid(update, &lock->old_oid, err)) {\n-\t\t\tret = TRANSACTION_GENERIC_ERROR;\n-\t\t\tgoto out;\n+\t\t} else {\n+\t\t\tret = check_old_oid(update, &lock->old_oid, err);\n+\t\t\tif  (ret) {\n+\t\t\t\tgoto out;\n+\t\t\t}\n \t\t}\n \n \t\t/*\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 1809e3426a..75b04e84a2 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1208,10 +1208,13 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\tgoto done;\n \t\t\t}\n \t\t} else if ((u->flags & REF_HAVE_OLD) && !oideq(&current_oid, &u->old_oid)) {\n-\t\t\tif (is_null_oid(&u->old_oid))\n+\t\t\tret = TRANSACTION_NAME_CONFLICT;\n+\t\t\tif (is_null_oid(&u->old_oid)) {\n \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n \t\t\t\t\t\t   \"reference already exists\"),\n \t\t\t\t\t    ref_update_original_update_refname(u));\n+\t\t\t\tret = TRANSACTION_CREATE_EXISTS;\n+\t\t\t}\n \t\t\telse if (is_null_oid(&current_oid))\n \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n \t\t\t\t\t\t   \"reference is missing but expected %s\"),\n@@ -1223,7 +1226,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\t\t    ref_update_original_update_refname(u),\n \t\t\t\t\t    oid_to_hex(&current_oid),\n \t\t\t\t\t    oid_to_hex(&u->old_oid));\n-\t\t\tret = -1;\n \t\t\tgoto done;\n \t\t}\n \n-- \n2.47.0.298.g52a96ec17b\n\n"},{"id":"507860","messageId":"20241121225757.3877852-9-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241121225757.3877852-1-bence@ferdinandy.com","subject":"[PATCH v14 08/10] refs: add create_only option to refs_update_symref_extended","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-21T22:55:08Z","receivedAt":"2024-11-21T23:10:43Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Allow the caller to specify that it only wants to update the symref if\nit does not already exist. Silently ignore the error from the\ntransaction API if the symref already exists.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v4: new patch\n    v5: no change\n    \n    v6: - switched from bool to int for create_only\n        - refactored logic in refs_update_symref with goto as layed out by\n          Junio\n    \n    v7: - change commit prefix to be more in line with project standards\n        - refactored code to accommodate changes in the first patch, but\n          otherwise no change\n    \n    v8: no change\n    \n    v9: - no change (except for following through the\n          refs_update_symref_extended refactoring)\n    \n    v10: no change\n    \n    v11: no change\n    \n    v12: no change\n    \n    v13: changes only due to changes in previous patch\n    \n    v14: no change\n\n builtin/remote.c |  2 +-\n refs.c           | 33 ++++++++++++++++++++++++---------\n refs.h           |  2 +-\n 3 files changed, 26 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 6d659d63ca..3dc1135b5c 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1476,7 +1476,7 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\tgoto cleanup;\n \t}\n \twas_detached = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n-\t\t\t\"remote set-head\", &b_local_head);\n+\t\t\t\"remote set-head\", &b_local_head, 0);\n \tif (was_detached == -1) {\n \t\tresult |= error(_(\"Could not set up %s\"), b_head.buf);\n \t\tgoto cleanup;\ndiff --git a/refs.c b/refs.c\nindex d80efd58f0..2efa6bcc5c 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2116,26 +2116,38 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct\n int refs_update_symref(struct ref_store *refs, const char *ref,\n \t\t       const char *target, const char *logmsg)\n {\n-\treturn refs_update_symref_extended(refs, ref, target, logmsg, NULL);\n+\treturn refs_update_symref_extended(refs, ref, target, logmsg, NULL, 0);\n }\n \n int refs_update_symref_extended(struct ref_store *refs, const char *ref,\n \t\t       const char *target, const char *logmsg,\n-\t\t       struct strbuf *referent)\n+\t\t       struct strbuf *referent, int create_only)\n {\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n-\tint ret = 0;\n+\tint ret = 0, prepret = 0;\n \n \ttransaction = ref_store_transaction_begin(refs, &err);\n-\tif (!transaction ||\n-\t    ref_transaction_update(transaction, ref, NULL, NULL,\n-\t\t\t\t   target, NULL, REF_NO_DEREF,\n-\t\t\t\t   logmsg, &err) ||\n-\t    ref_transaction_prepare(transaction, &err)) {\n+\tif (!transaction) {\n+\terror_return:\n \t\tret = error(\"%s\", err.buf);\n \t\tgoto cleanup;\n \t}\n+\tif (create_only) {\n+\t\tif (ref_transaction_create(transaction, ref, NULL, target,\n+\t\t\t\t\t   REF_NO_DEREF, logmsg, &err))\n+\t\t\tgoto error_return;\n+\t\tprepret = ref_transaction_prepare(transaction, &err);\n+\t\tif (prepret && prepret != TRANSACTION_CREATE_EXISTS)\n+\t\t\tgoto error_return;\n+\t} else {\n+\t\tif (ref_transaction_update(transaction, ref, NULL, NULL,\n+\t\t\t\t\t   target, NULL, REF_NO_DEREF,\n+\t\t\t\t\t   logmsg, &err) ||\n+\t\t\tref_transaction_prepare(transaction, &err))\n+\t\t\tgoto error_return;\n+\t}\n+\n \tif (referent && refs_read_symbolic_ref(refs, ref, referent) == NOT_A_SYMREF) {\n \t\tstruct object_id oid;\n \t\tif (!refs_read_ref(refs, ref, &oid)) {\n@@ -2144,8 +2156,11 @@ int refs_update_symref_extended(struct ref_store *refs, const char *ref,\n \t\t}\n \t}\n \n+\tif (prepret == TRANSACTION_CREATE_EXISTS)\n+\t\tgoto cleanup;\n+\n \tif (ref_transaction_commit(transaction, &err))\n-\t\tret = error(\"%s\", err.buf);\n+\t\tgoto error_return;\n \n cleanup:\n \tstrbuf_release(&err);\ndiff --git a/refs.h b/refs.h\nindex b243739e4b..be38377b1f 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -586,7 +586,7 @@ int refs_update_symref(struct ref_store *refs, const char *refname,\n \n int refs_update_symref_extended(struct ref_store *refs, const char *refname,\n \t\t       const char *target, const char *logmsg,\n-\t\t       struct strbuf *referent);\n+\t\t       struct strbuf *referent, int create_only);\n \n enum action_on_err {\n \tUPDATE_REFS_MSG_ON_ERR,\n-- \n2.47.0.298.g52a96ec17b\n\n"},{"id":"507861","messageId":"20241121225757.3877852-7-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241121225757.3877852-1-bence@ferdinandy.com","subject":"[PATCH v14 06/10] remote set-head: better output for --auto","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-21T22:55:06Z","receivedAt":"2024-11-21T23:10:45Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Currently, set-head --auto will print a message saying \"remote/HEAD set\nto branch\", which implies something was changed.\n\nChange the output of --auto, so the output actually reflects what was\ndone: a) set a previously unset HEAD, b) change HEAD because remote\nchanged or c) no updates. As edge cases, if HEAD is changed from\na previous symbolic reference that was not a remote branch, explicitly\ncall attention to this fact, and also notify the user if the previous\nreference was not a symbolic reference.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v1-v2:\n    \n        was RFC in https://lore.kernel.org/git/20240910203835.2288291-1-bence@ferdinandy.com/\n    \n    v3:\n    \n        This patch was originally sent along when I thought set-head was\n        going to be invoked by fetch, but the discussion on the RFC\n        concluded that it should be not. This opened the possibility to make\n        it more explicit.\n    \n        Note: although I feel both things the patch does are really just\n        cosmetic, an argument could be made for breaking it into two, one\n        for the no-op part and one for the --auto print update.\n    \n        Was sent in:\n        https://lore.kernel.org/git/20240915221055.904107-1-bence@ferdinandy.com/\n    \n    v4:\n        - changes are now handled atomically via the ref update transaction\n        - outputs have changed along the lines of Junio's suggestion\n        - minor refactor to set_head for improved legibility\n    \n    v5: - the minor refactor has been split out into its own patch\n    \n    v6: - fixed uninitialized prev_head\n        - fixed case of unusual previous target\n        - fixed a test that would have been actually broken at this patch\n          (the output was only correct with the later update to fetch)\n        - added 4 tests for the 4 output cases\n    \n    v7: - change commit prefix to be more in line with project standards\n        - fixed tests to also work with the reftable backend\n        - renamed report function, fixed style issue with checking buf len\n        - fixed not releasing an strbuf\n    \n    v8: no change\n    \n    v9: - mark output strings in report_set_head_auto as translatable\n        - rename buf_prev to b_local_head for consistency\n        - use ${SQ} in tests instead of '\\''\n    \n    v10: no change\n    \n    v11: no change\n    \n    v12: no change\n    \n    v13: added handling the edge case of previous remote/HEAD being\n    a detached HEAD\n    \n    v14: - fixed badly named variable\n         - fixed not reporting errors correctly\n\n builtin/remote.c  | 59 +++++++++++++++++++++++++++++++++++---------\n t/t5505-remote.sh | 63 ++++++++++++++++++++++++++++++++++++++++++++++-\n 2 files changed, 109 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex bcc3ee91ef..6d659d63ca 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1399,10 +1399,38 @@ static int show(int argc, const char **argv, const char *prefix)\n \treturn result;\n }\n \n+static void report_set_head_auto(const char *remote, const char *head_name,\n+\t\t\tstruct strbuf *b_local_head, int was_detached) {\n+\tstruct strbuf buf_prefix = STRBUF_INIT;\n+\tconst char *prev_head = NULL;\n+\n+\tstrbuf_addf(&buf_prefix, \"refs/remotes/%s/\", remote);\n+\tskip_prefix(b_local_head->buf, buf_prefix.buf, &prev_head);\n+\n+\tif (prev_head && !strcmp(prev_head, head_name))\n+\t\tprintf(_(\"'%s/HEAD' is unchanged and points to '%s'\\n\"),\n+\t\t\tremote, head_name);\n+\telse if (prev_head)\n+\t\tprintf(_(\"'%s/HEAD' has changed from '%s' and now points to '%s'\\n\"),\n+\t\t\tremote, prev_head, head_name);\n+\telse if (!b_local_head->len)\n+\t\tprintf(_(\"'%s/HEAD' is now created and points to '%s'\\n\"),\n+\t\t\tremote, head_name);\n+\telse if (was_detached && b_local_head->len)\n+\t\tprintf(_(\"'%s/HEAD' was detached at '%s' and now points to '%s'\\n\"),\n+\t\t\tremote, b_local_head->buf, head_name);\n+\telse\n+\t\tprintf(_(\"'%s/HEAD' used to point to '%s' \"\n+\t\t\t\"(which is not a remote branch), but now points to '%s'\\n\"),\n+\t\t\tremote, b_local_head->buf, head_name);\n+\tstrbuf_release(&buf_prefix);\n+}\n+\n static int set_head(int argc, const char **argv, const char *prefix)\n {\n-\tint i, opt_a = 0, opt_d = 0, result = 0;\n-\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;\n+\tint i, opt_a = 0, opt_d = 0, result = 0, was_detached;\n+\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,\n+\t\tb_local_head = STRBUF_INIT;\n \tchar *head_name = NULL;\n \tstruct ref_store *refs = get_main_ref_store(the_repository);\n \n@@ -1440,20 +1468,27 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t} else\n \t\tusage_with_options(builtin_remote_sethead_usage, options);\n \n-\tif (head_name) {\n-\t\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", argv[0], head_name);\n-\t\t/* make sure it's valid */\n-\t\tif (!refs_ref_exists(refs, b_remote_head.buf))\n-\t\t\tresult |= error(_(\"Not a valid ref: %s\"), b_remote_head.buf);\n-\t\telse if (refs_update_symref(refs, b_head.buf, b_remote_head.buf, \"remote set-head\"))\n-\t\t\tresult |= error(_(\"Could not set up %s\"), b_head.buf);\n-\t\telse if (opt_a)\n-\t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\n-\t\tfree(head_name);\n+\tif (!head_name)\n+\t\tgoto cleanup;\n+\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", argv[0], head_name);\n+\tif (!refs_ref_exists(refs, b_remote_head.buf)) {\n+\t\tresult |= error(_(\"Not a valid ref: %s\"), b_remote_head.buf);\n+\t\tgoto cleanup;\n+\t}\n+\twas_detached = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n+\t\t\t\"remote set-head\", &b_local_head);\n+\tif (was_detached == -1) {\n+\t\tresult |= error(_(\"Could not set up %s\"), b_head.buf);\n+\t\tgoto cleanup;\n \t}\n+\tif (opt_a)\n+\t\treport_set_head_auto(argv[0], head_name, &b_local_head, was_detached);\n \n+cleanup:\n+\tfree(head_name);\n \tstrbuf_release(&b_head);\n \tstrbuf_release(&b_remote_head);\n+\tstrbuf_release(&b_local_head);\n \treturn result;\n }\n \ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex 4e127bf5b8..e6acff4afe 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -444,12 +444,63 @@ test_expect_success REFFILES 'set-head --auto failure' '\n \t)\n '\n \n+test_expect_success 'set-head --auto detects creation' '\n+\t(\n+\t\tcd test &&\n+\t\tgit update-ref --no-deref -d refs/remotes/origin/HEAD &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"${SQ}origin/HEAD${SQ} is now created and points to ${SQ}main${SQ}\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n+test_expect_success 'set-head --auto to update a non symbolic ref' '\n+\t(\n+\t\tcd test &&\n+\t\tgit update-ref --no-deref -d refs/remotes/origin/HEAD &&\n+\t\tgit update-ref refs/remotes/origin/HEAD HEAD &&\n+\t\tHEAD=$(git log --pretty=\"%H\") &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"${SQ}origin/HEAD${SQ} was detached at ${SQ}${HEAD}${SQ} and now points to ${SQ}main${SQ}\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n+test_expect_success 'set-head --auto detects no change' '\n+\t(\n+\t\tcd test &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"${SQ}origin/HEAD${SQ} is unchanged and points to ${SQ}main${SQ}\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n+test_expect_success 'set-head --auto detects change' '\n+\t(\n+\t\tcd test &&\n+\t\tgit symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/ahead &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"${SQ}origin/HEAD${SQ} has changed from ${SQ}ahead${SQ} and now points to ${SQ}main${SQ}\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n+test_expect_success 'set-head --auto detects strange ref' '\n+\t(\n+\t\tcd test &&\n+\t\tgit symbolic-ref refs/remotes/origin/HEAD refs/heads/main &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"${SQ}origin/HEAD${SQ} used to point to ${SQ}refs/heads/main${SQ} (which is not a remote branch), but now points to ${SQ}main${SQ}\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n test_expect_success 'set-head --auto has no problem w/multiple HEADs' '\n \t(\n \t\tcd test &&\n \t\tgit fetch two \"refs/heads/*:refs/remotes/two/*\" &&\n \t\tgit remote set-head --auto two >output 2>&1 &&\n-\t\techo \"two/HEAD set to main\" >expect &&\n+\t\techo \"${SQ}two/HEAD${SQ} is now created and points to ${SQ}main${SQ}\" >expect &&\n \t\ttest_cmp expect output\n \t)\n '\n@@ -468,6 +519,16 @@ test_expect_success 'set-head explicit' '\n \t)\n '\n \n+test_expect_success 'set-head --auto reports change' '\n+\t(\n+\t\tcd test &&\n+\t\tgit remote set-head origin side2 &&\n+\t\tgit remote set-head --auto origin >output 2>&1 &&\n+\t\techo \"${SQ}origin/HEAD${SQ} has changed from ${SQ}side2${SQ} and now points to ${SQ}main${SQ}\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n cat >test/expect <<EOF\n Pruning origin\n URL: $(pwd)/one\n-- \n2.47.0.298.g52a96ec17b\n\n"},{"id":"507863","messageId":"20241121225757.3877852-11-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241121225757.3877852-1-bence@ferdinandy.com","subject":"[PATCH v14 10/10] fetch set_head: handle mirrored bare repositories","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-21T22:55:10Z","receivedAt":"2024-11-21T23:11:41Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"When adding a remote to bare repository with \"git remote add --mirror\",\nrunning fetch will fail to update HEAD to the remote's HEAD, since it\ndoes not know how to handle bare repositories. On the other hand HEAD\nalready has content, since \"git init --bare\" has already set HEAD to\nwhatever is the default branch set for the user. Unless this - by chance\n- is the same as the remote's HEAD, HEAD will be pointing to a bad\nsymref. Teach set_head to handle bare repositories, by overwriting HEAD\nso it mirrors the remote's HEAD.\n\nNote, that in this case overriding the local HEAD reference is\nnecessary, since HEAD will exist before fetch can be run, but this\nshould not be an issue, since the whole purpose of --mirror is to be an\nexact mirror of the remote, so following any changes to HEAD makes\nsense.\n\nAlso note, that although \"git remote set-head\" also fails when trying to\nupdate the remote's locally tracked HEAD in a mirrored bare repository,\nthe usage of the command does not make much sense after this patch:\nfetch will update the remote HEAD correctly, and setting it manually to\nsomething else is antithetical to the concept of mirroring.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    state:\n        * last review: none\n    \n    v10: - new patch\n         - handles the issue discovered in\n           https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/T/\n    \n    v11: no change\n    \n    v12: no change\n    \n    v13: properly print the previous head if it was detached\n    \n    v14: no change\n\n builtin/fetch.c   | 16 +++++++++++-----\n t/t5505-remote.sh | 10 ++++++++++\n 2 files changed, 21 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex b569c87f97..5103793191 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1585,7 +1585,7 @@ static const char *strip_refshead(const char *name){\n \n static int set_head(const struct ref *remote_refs)\n {\n-\tint result = 0;\n+\tint result = 0, is_bare;\n \tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;\n \tconst char *remote = gtransport->remote->name;\n \tchar *head_name = NULL;\n@@ -1617,15 +1617,21 @@ static int set_head(const struct ref *remote_refs)\n \n \tif (!head_name)\n \t\tgoto cleanup;\n-\tstrbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", remote);\n-\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", remote, head_name);\n+\tis_bare = is_bare_repository();\n+\tif (is_bare) {\n+\t\tstrbuf_addstr(&b_head, \"HEAD\");\n+\t\tstrbuf_addf(&b_remote_head, \"refs/heads/%s\", head_name);\n+\t} else {\n+\t\tstrbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", remote);\n+\t\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", remote, head_name);\n+\t}\n \t\t/* make sure it's valid */\n-\tif (!refs_ref_exists(refs, b_remote_head.buf)) {\n+\tif (!is_bare && !refs_ref_exists(refs, b_remote_head.buf)) {\n \t\tresult = 1;\n \t\tgoto cleanup;\n \t}\n \tif (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n-\t\t\t\t\t\"fetch\", NULL, 1))\n+\t\t\t\t\t\"fetch\", NULL, !is_bare))\n \t\tresult = 1;\n \n cleanup:\ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex 01d2601174..b4f1ae5fb1 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -569,6 +569,16 @@ test_expect_success 'add --mirror && prune' '\n \t)\n '\n \n+test_expect_success 'add --mirror setting HEAD' '\n+\tmkdir headmirror &&\n+\t(\n+\t\tcd headmirror &&\n+\t\tgit init --bare -b notmain &&\n+\t\tgit remote add --mirror -f origin ../one &&\n+\t\ttest \"$(git symbolic-ref HEAD)\" = \"refs/heads/main\"\n+\t)\n+'\n+\n test_expect_success 'add --mirror=fetch' '\n \tmkdir mirror-fetch &&\n \tgit init -b main mirror-fetch/parent &&\n-- \n2.47.0.298.g52a96ec17b\n\n"},{"id":"507864","messageId":"20241121225757.3877852-10-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241121225757.3877852-1-bence@ferdinandy.com","subject":"[PATCH v14 09/10] fetch: set remote/HEAD if it does not exist","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-21T22:55:09Z","receivedAt":"2024-11-21T23:11:43Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"When cloning a repository remote/HEAD is created, but when the user\ncreates a repository with git init, and later adds a remote, remote/HEAD\nis only created if the user explicitly runs a variant of \"remote\nset-head\". Attempt to set remote/HEAD during fetch, if the user does not\nhave it already set. Silently ignore any errors.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v3: - does not rely on remote set-head anymore so it only authenticates\n        once\n        - uses the new REF_CREATE_ONLY to atomically check if the ref exists\n          and only write it if it doesn't\n        - in all other cases the maximum it does is print a warning\n    \n    v4: - instead of the discarded REF_CREATE_ONLY, it uses the existing,\n          but updated transaction api to request a silent create only\n        - it now uses the atomic before_target to determine reporting\n        - refactored for legibility\n    \n    v5: - instead of printing a not too useful message, it now fails\n          silently, this in line with the objective to only set up\n          remote/HEAD automatically if the right thing is trivial, for\n          everything else there is remote set-head\n        - fixed all failing tests\n        - added two new tests, one for checking if remote/HEAD is set to the\n          correct one, and one to test that we do not override remote/HEAD\n          if it has changed on the server from what we have locally\n    \n    v6: - fixed style issues and unintended extra empty line\n        - updated function call with bool to int from previous patch's\n          change\n        - removed calls to error(...) inherited from builtin/remote.c so we\n          actually fail silently\n        - set the test for remote set-head --auto to the correct value here,\n          which was previously erronously set in the remote set-head patch\n    \n    v7: - no change\n    \n    v8: - changed logmsg in call to refs_update_symref from \"remote\n          set-head\" to \"fetch\"\n    \n    v9: - follow through with refs_update_symref_extended\n        - fix test errors uncovered by the new patch\n    \n    v10: no change\n    \n    v11: fixed some memory leaks\n    \n    v12: no change\n    \n    v13: - fix printed information if the local HEAD is detached\n         - remove accidental formatting noise in a test\n    \n    v14: remove report_set_head_auto: as discussed, any noise should only be\n    created with an opt out, and the configuration for this will be added in\n    a later patch\n\n builtin/fetch.c                  | 68 ++++++++++++++++++++++++++++++++\n t/t4207-log-decoration-colors.sh |  3 +-\n t/t5505-remote.sh                | 21 +++++++---\n t/t5510-fetch.sh                 | 24 +++++++++++\n t/t5512-ls-remote.sh             |  2 +\n t/t5514-fetch-multiple.sh        | 17 +++++++-\n t/t5516-fetch-push.sh            |  3 +-\n t/t5527-fetch-odd-refs.sh        |  3 +-\n t/t7900-maintenance.sh           |  3 +-\n t/t9210-scalar.sh                |  5 ++-\n t/t9211-scalar-clone.sh          |  6 +--\n t/t9902-completion.sh            | 65 ++++++++++++++++++++++++++++++\n 12 files changed, 203 insertions(+), 17 deletions(-)\n\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 18eff4e5fa..b569c87f97 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1578,6 +1578,66 @@ static int backfill_tags(struct display_state *display_state,\n \treturn retcode;\n }\n \n+static const char *strip_refshead(const char *name){\n+\tskip_prefix(name, \"refs/heads/\", &name);\n+\treturn name;\n+}\n+\n+static int set_head(const struct ref *remote_refs)\n+{\n+\tint result = 0;\n+\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;\n+\tconst char *remote = gtransport->remote->name;\n+\tchar *head_name = NULL;\n+\tstruct ref *ref, *matches;\n+\tstruct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;\n+\tstruct refspec_item refspec = {\n+\t\t.force = 0,\n+\t\t.pattern = 1,\n+\t\t.src = (char *) \"refs/heads/*\",\n+\t\t.dst = (char *) \"refs/heads/*\",\n+\t};\n+\tstruct string_list heads = STRING_LIST_INIT_DUP;\n+\tstruct ref_store *refs = get_main_ref_store(the_repository);\n+\n+\tget_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);\n+\tmatches = guess_remote_head(find_ref_by_name(remote_refs, \"HEAD\"),\n+\t\t\t\t    fetch_map, 1);\n+\tfor (ref = matches; ref; ref = ref->next) {\n+\t\tstring_list_append(&heads, strip_refshead(ref->name));\n+\t}\n+\n+\n+\tif (!heads.nr)\n+\t\tresult = 1;\n+\telse if (heads.nr > 1)\n+\t\tresult = 1;\n+\telse\n+\t\thead_name = xstrdup(heads.items[0].string);\n+\n+\tif (!head_name)\n+\t\tgoto cleanup;\n+\tstrbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", remote);\n+\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", remote, head_name);\n+\t\t/* make sure it's valid */\n+\tif (!refs_ref_exists(refs, b_remote_head.buf)) {\n+\t\tresult = 1;\n+\t\tgoto cleanup;\n+\t}\n+\tif (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n+\t\t\t\t\t\"fetch\", NULL, 1))\n+\t\tresult = 1;\n+\n+cleanup:\n+\tfree(head_name);\n+\tfree_refs(fetch_map);\n+\tfree_refs(matches);\n+\tstring_list_clear(&heads, 0);\n+\tstrbuf_release(&b_head);\n+\tstrbuf_release(&b_remote_head);\n+\treturn result;\n+}\n+\n static int do_fetch(struct transport *transport,\n \t\t    struct refspec *rs,\n \t\t    const struct fetch_config *config)\n@@ -1647,6 +1707,8 @@ static int do_fetch(struct transport *transport,\n \t\t\t\t    \"refs/tags/\");\n \t}\n \n+\tstrvec_push(&transport_ls_refs_options.ref_prefixes, \"HEAD\");\n+\n \tif (must_list_refs) {\n \t\ttrace2_region_enter(\"fetch\", \"remote_refs\", the_repository);\n \t\tremote_refs = transport_get_remote_refs(transport,\n@@ -1791,6 +1853,12 @@ static int do_fetch(struct transport *transport,\n \t\t\t\t  \"you need to specify exactly one branch with the --set-upstream option\"));\n \t\t}\n \t}\n+\tif (set_head(remote_refs))\n+\t\t;\n+\t\t/*\n+\t\t * Way too many cases where this can go wrong\n+\t\t * so let's just fail silently for now.\n+\t\t */\n \n cleanup:\n \tif (retcode) {\ndiff --git a/t/t4207-log-decoration-colors.sh b/t/t4207-log-decoration-colors.sh\nindex 73ea9e5155..d55d22cb2f 100755\n--- a/t/t4207-log-decoration-colors.sh\n+++ b/t/t4207-log-decoration-colors.sh\n@@ -59,7 +59,8 @@ ${c_reset}${c_tag}tag: ${c_reset}${c_tag}v1.0${c_reset}${c_commit}, \\\n ${c_reset}${c_tag}tag: ${c_reset}${c_tag}B${c_reset}${c_commit})${c_reset} B\n ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\n ${c_tag}tag: ${c_reset}${c_tag}A1${c_reset}${c_commit}, \\\n-${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit})${c_reset} A1\n+${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit}, \\\n+${c_reset}${c_remoteBranch}other/HEAD${c_reset}${c_commit})${c_reset} A1\n \t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\n ${c_stash}refs/stash${c_reset}${c_commit})${c_reset} On main: Changes to A.t\n \t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex e6acff4afe..01d2601174 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -74,7 +74,7 @@ test_expect_success 'add another remote' '\n \t\tcd test &&\n \t\tgit remote add -f second ../two &&\n \t\ttokens_match \"origin second\" \"$(git remote)\" &&\n-\t\tcheck_tracking_branch second main side another &&\n+\t\tcheck_tracking_branch second main side another HEAD &&\n \t\tgit for-each-ref \"--format=%(refname)\" refs/remotes |\n \t\tsed -e \"/^refs\\/remotes\\/origin\\//d\" \\\n \t\t    -e \"/^refs\\/remotes\\/second\\//d\" >actual &&\n@@ -500,7 +500,7 @@ test_expect_success 'set-head --auto has no problem w/multiple HEADs' '\n \t\tcd test &&\n \t\tgit fetch two \"refs/heads/*:refs/remotes/two/*\" &&\n \t\tgit remote set-head --auto two >output 2>&1 &&\n-\t\techo \"${SQ}two/HEAD${SQ} is now created and points to ${SQ}main${SQ}\" >expect &&\n+\t\techo \"${SQ}two/HEAD${SQ} is unchanged and points to ${SQ}main${SQ}\" >expect &&\n \t\ttest_cmp expect output\n \t)\n '\n@@ -788,8 +788,10 @@ test_expect_success 'reject --no-no-tags' '\n '\n \n cat >one/expect <<\\EOF\n+  apis/HEAD -> apis/main\n   apis/main\n   apis/side\n+  drosophila/HEAD -> drosophila/main\n   drosophila/another\n   drosophila/main\n   drosophila/side\n@@ -807,11 +809,14 @@ test_expect_success 'update' '\n '\n \n cat >one/expect <<\\EOF\n+  drosophila/HEAD -> drosophila/main\n   drosophila/another\n   drosophila/main\n   drosophila/side\n+  manduca/HEAD -> manduca/main\n   manduca/main\n   manduca/side\n+  megaloprepus/HEAD -> megaloprepus/main\n   megaloprepus/main\n   megaloprepus/side\n EOF\n@@ -819,7 +824,7 @@ EOF\n test_expect_success 'update with arguments' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\n@@ -851,10 +856,13 @@ test_expect_success 'update --prune' '\n '\n \n cat >one/expect <<-\\EOF\n+  apis/HEAD -> apis/main\n   apis/main\n   apis/side\n+  manduca/HEAD -> manduca/main\n   manduca/main\n   manduca/side\n+  megaloprepus/HEAD -> megaloprepus/main\n   megaloprepus/main\n   megaloprepus/side\n EOF\n@@ -862,7 +870,7 @@ EOF\n test_expect_success 'update default' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\n@@ -874,6 +882,7 @@ test_expect_success 'update default' '\n '\n \n cat >one/expect <<\\EOF\n+  drosophila/HEAD -> drosophila/main\n   drosophila/another\n   drosophila/main\n   drosophila/side\n@@ -882,7 +891,7 @@ EOF\n test_expect_success 'update default (overridden, with funny whitespace)' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\n@@ -896,7 +905,7 @@ test_expect_success 'update default (overridden, with funny whitespace)' '\n test_expect_success 'update (with remotes.default defined)' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex 0890b9f61c..87698341f5 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -75,6 +75,30 @@ test_expect_success \"fetch test for-merge\" '\n \tcut -f -2 .git/FETCH_HEAD >actual &&\n \ttest_cmp expected actual'\n \n+test_expect_success \"fetch test remote HEAD\" '\n+\tcd \"$D\" &&\n+\tcd two &&\n+\tgit fetch &&\n+\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\tgit rev-parse --verify refs/remotes/origin/main &&\n+\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\tbranch=$(git rev-parse refs/remotes/origin/main) &&\n+\ttest \"z$head\" = \"z$branch\"'\n+\n+test_expect_success \"fetch test remote HEAD change\" '\n+\tcd \"$D\" &&\n+\tcd two &&\n+\tgit switch -c other &&\n+\tgit push -u origin other &&\n+\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\tgit rev-parse --verify refs/remotes/origin/main &&\n+\tgit rev-parse --verify refs/remotes/origin/other &&\n+\tgit remote set-head origin other &&\n+\tgit fetch &&\n+\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\tbranch=$(git rev-parse refs/remotes/origin/other) &&\n+\ttest \"z$head\" = \"z$branch\"'\n+\n test_expect_success 'fetch --prune on its own works as expected' '\n \tcd \"$D\" &&\n \tgit clone . prune &&\ndiff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh\nindex 64b3491e4e..1b3865e154 100755\n--- a/t/t5512-ls-remote.sh\n+++ b/t/t5512-ls-remote.sh\n@@ -293,6 +293,8 @@ test_expect_success 'ls-remote with filtered symref (refname)' '\n \tcat >expect <<-EOF &&\n \tref: refs/heads/main\tHEAD\n \t$rev\tHEAD\n+\tref: refs/remotes/origin/main\trefs/remotes/origin/HEAD\n+\t$rev\trefs/remotes/origin/HEAD\n \tEOF\n \tgit ls-remote --symref . HEAD >actual &&\n \ttest_cmp expect actual\ndiff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh\nindex 579872c258..e3482b27b2 100755\n--- a/t/t5514-fetch-multiple.sh\n+++ b/t/t5514-fetch-multiple.sh\n@@ -45,14 +45,17 @@ test_expect_success setup '\n '\n \n cat > test/expect << EOF\n+  one/HEAD -> one/main\n   one/main\n   one/side\n   origin/HEAD -> origin/main\n   origin/main\n   origin/side\n+  three/HEAD -> three/main\n   three/another\n   three/main\n   three/side\n+  two/HEAD -> two/main\n   two/another\n   two/main\n   two/side\n@@ -97,6 +100,7 @@ cat > expect << EOF\n   origin/HEAD -> origin/main\n   origin/main\n   origin/side\n+  three/HEAD -> three/main\n   three/another\n   three/main\n   three/side\n@@ -112,8 +116,10 @@ test_expect_success 'git fetch --multiple (but only one remote)' '\n '\n \n cat > expect << EOF\n+  one/HEAD -> one/main\n   one/main\n   one/side\n+  two/HEAD -> two/main\n   two/another\n   two/main\n   two/side\n@@ -141,7 +147,7 @@ test_expect_success 'git fetch --multiple (bad remote names)' '\n \n test_expect_success 'git fetch --all (skipFetchAll)' '\n \t(cd test4 &&\n-\t for b in $(git branch -r)\n+\t for b in $(git branch -r | grep -v HEAD)\n \t do\n \t\tgit branch -r -d $b || exit 1\n \t done &&\n@@ -153,11 +159,14 @@ test_expect_success 'git fetch --all (skipFetchAll)' '\n '\n \n cat > expect << EOF\n+  one/HEAD -> one/main\n   one/main\n   one/side\n+  three/HEAD -> three/main\n   three/another\n   three/main\n   three/side\n+  two/HEAD -> two/main\n   two/another\n   two/main\n   two/side\n@@ -165,7 +174,7 @@ EOF\n \n test_expect_success 'git fetch --multiple (ignoring skipFetchAll)' '\n \t(cd test4 &&\n-\t for b in $(git branch -r)\n+\t for b in $(git branch -r | grep -v HEAD)\n \t do\n \t\tgit branch -r -d $b || exit 1\n \t done &&\n@@ -221,14 +230,17 @@ test_expect_success 'git fetch --multiple --jobs=0 picks a default' '\n \n create_fetch_all_expect () {\n \tcat >expect <<-\\EOF\n+\t  one/HEAD -> one/main\n \t  one/main\n \t  one/side\n \t  origin/HEAD -> origin/main\n \t  origin/main\n \t  origin/side\n+\t  three/HEAD -> three/main\n \t  three/another\n \t  three/main\n \t  three/side\n+\t  two/HEAD -> two/main\n \t  two/another\n \t  two/main\n \t  two/side\n@@ -265,6 +277,7 @@ test_expect_success 'git fetch (fetch all remotes with fetch.all = true)' '\n \n create_fetch_one_expect () {\n \tcat >expect <<-\\EOF\n+\t  one/HEAD -> one/main\n \t  one/main\n \t  one/side\n \t  origin/HEAD -> origin/main\ndiff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\nindex 331778bd42..5a051aa0c7 100755\n--- a/t/t5516-fetch-push.sh\n+++ b/t/t5516-fetch-push.sh\n@@ -1395,7 +1395,8 @@ test_expect_success 'fetch follows tags by default' '\n \t\tgit tag -m \"annotated\" tag &&\n \t\tgit for-each-ref >tmp1 &&\n \t\tsed -n \"p; s|refs/heads/main$|refs/remotes/origin/main|p\" tmp1 |\n-\t\tsort -k 3 >../expect\n+\t\tsed -n \"p; s|refs/heads/main$|refs/remotes/origin/HEAD|p\"  |\n+\t\tsort -k 4 >../expect\n \t) &&\n \ttest_when_finished \"rm -rf dst\" &&\n \tgit init dst &&\ndiff --git a/t/t5527-fetch-odd-refs.sh b/t/t5527-fetch-odd-refs.sh\nindex 98ece27c6a..d3996af6ee 100755\n--- a/t/t5527-fetch-odd-refs.sh\n+++ b/t/t5527-fetch-odd-refs.sh\n@@ -52,7 +52,8 @@ test_expect_success LONG_REF 'fetch handles extremely long refname' '\n \tlong\n \tmain\n \tEOF\n-\tgit for-each-ref --format=\"%(subject)\" refs/remotes/long >actual &&\n+\tgit for-each-ref --format=\"%(subject)\" refs/remotes/long \\\n+\t\t--exclude=refs/remotes/long/HEAD >actual &&\n \ttest_cmp expect actual\n '\n \ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex c224c8450c..edb85b7145 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -329,7 +329,8 @@ test_expect_success 'incremental-repack task' '\n \n \t# Delete refs that have not been repacked in these packs.\n \tgit for-each-ref --format=\"delete %(refname)\" \\\n-\t\trefs/prefetch refs/tags refs/remotes >refs &&\n+\t\trefs/prefetch refs/tags refs/remotes \\\n+\t\t--exclude=refs/remotes/*/HEAD >refs &&\n \tgit update-ref --stdin <refs &&\n \n \t# Replace the object directory with this pack layout.\ndiff --git a/t/t9210-scalar.sh b/t/t9210-scalar.sh\nindex a30b2c9f70..2237844550 100755\n--- a/t/t9210-scalar.sh\n+++ b/t/t9210-scalar.sh\n@@ -151,7 +151,8 @@ test_expect_success 'scalar clone' '\n \t\t\t\"$(pwd)\" &&\n \n \t\tgit for-each-ref --format=\"%(refname)\" refs/remotes/origin/ >actual &&\n-\t\techo \"refs/remotes/origin/parallel\" >expect &&\n+\t\techo \"refs/remotes/origin/HEAD\" >>expect &&\n+\t\techo \"refs/remotes/origin/parallel\" >>expect &&\n \t\ttest_cmp expect actual &&\n \n \t\ttest_path_is_missing 1/2 &&\n@@ -220,7 +221,7 @@ test_expect_success 'scalar reconfigure --all with includeIf.onbranch' '\n \tdone\n '\n \n- test_expect_success 'scalar reconfigure --all with detached HEADs' '\n+test_expect_success 'scalar reconfigure --all with detached HEADs' '\n \trepos=\"two three four\" &&\n \tfor num in $repos\n \tdo\ndiff --git a/t/t9211-scalar-clone.sh b/t/t9211-scalar-clone.sh\nindex c16ea67c1d..d9cb6b9a3e 100755\n--- a/t/t9211-scalar-clone.sh\n+++ b/t/t9211-scalar-clone.sh\n@@ -32,7 +32,7 @@ test_expect_success 'set up repository to clone' '\n \t)\n '\n \n-cleanup_clone () {\n+cleanup_clone() {\n \trm -rf \"$1\"\n }\n \n@@ -128,7 +128,7 @@ test_expect_success '--single-branch clones HEAD only' '\n \t(\n \t\tcd $enlistment/src &&\n \t\tgit for-each-ref refs/remotes/origin >out &&\n-\t\ttest_line_count = 1 out &&\n+\t\ttest_line_count = 2 out &&\n \t\tgrep \"refs/remotes/origin/base\" out\n \t) &&\n \n@@ -142,7 +142,7 @@ test_expect_success '--no-single-branch clones all branches' '\n \t(\n \t\tcd $enlistment/src &&\n \t\tgit for-each-ref refs/remotes/origin >out &&\n-\t\ttest_line_count = 2 out &&\n+\t\ttest_line_count = 3 out &&\n \t\tgrep \"refs/remotes/origin/base\" out &&\n \t\tgrep \"refs/remotes/origin/parallel\" out\n \t) &&\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex cc6aa9f0cd..b663c4609e 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -658,6 +658,7 @@ test_expect_success '__git_refs - simple' '\n \tHEAD\n \tmain\n \tmatching-branch\n+\tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n \tmatching-tag\n@@ -673,6 +674,7 @@ test_expect_success '__git_refs - full refs' '\n \tcat >expected <<-EOF &&\n \trefs/heads/main\n \trefs/heads/matching-branch\n+\trefs/remotes/other/HEAD\n \trefs/remotes/other/branch-in-other\n \trefs/remotes/other/main-in-other\n \trefs/tags/matching-tag\n@@ -729,6 +731,7 @@ test_expect_success '__git_refs - remote on local file system - full refs' '\n test_expect_success '__git_refs - configured remote' '\n \tcat >expected <<-EOF &&\n \tHEAD\n+\tHEAD\n \tbranch-in-other\n \tmain-in-other\n \tEOF\n@@ -756,6 +759,7 @@ test_expect_success '__git_refs - configured remote - full refs' '\n test_expect_success '__git_refs - configured remote - repo given on the command line' '\n \tcat >expected <<-EOF &&\n \tHEAD\n+\tHEAD\n \tbranch-in-other\n \tmain-in-other\n \tEOF\n@@ -787,6 +791,7 @@ test_expect_success '__git_refs - configured remote - full refs - repo given on\n test_expect_success '__git_refs - configured remote - remote name matches a directory' '\n \tcat >expected <<-EOF &&\n \tHEAD\n+\tHEAD\n \tbranch-in-other\n \tmain-in-other\n \tEOF\n@@ -875,12 +880,14 @@ test_expect_success '__git_refs - unique remote branches for git checkout DWIMer\n \tHEAD\n \tmain\n \tmatching-branch\n+\tother/HEAD\n \tother/ambiguous\n \tother/branch-in-other\n \tother/main-in-other\n \tremote/ambiguous\n \tremote/branch-in-remote\n \tmatching-tag\n+\tHEAD\n \tbranch-in-other\n \tbranch-in-remote\n \tmain-in-other\n@@ -904,6 +911,7 @@ test_expect_success '__git_refs - after --opt=' '\n \tHEAD\n \tmain\n \tmatching-branch\n+\tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n \tmatching-tag\n@@ -919,6 +927,7 @@ test_expect_success '__git_refs - after --opt= - full refs' '\n \tcat >expected <<-EOF &&\n \trefs/heads/main\n \trefs/heads/matching-branch\n+\trefs/remotes/other/HEAD\n \trefs/remotes/other/branch-in-other\n \trefs/remotes/other/main-in-other\n \trefs/tags/matching-tag\n@@ -935,6 +944,7 @@ test_expect_success '__git refs - excluding refs' '\n \t^HEAD\n \t^main\n \t^matching-branch\n+\t^other/HEAD\n \t^other/branch-in-other\n \t^other/main-in-other\n \t^matching-tag\n@@ -950,6 +960,7 @@ test_expect_success '__git refs - excluding full refs' '\n \tcat >expected <<-EOF &&\n \t^refs/heads/main\n \t^refs/heads/matching-branch\n+\t^refs/remotes/other/HEAD\n \t^refs/remotes/other/branch-in-other\n \t^refs/remotes/other/main-in-other\n \t^refs/tags/matching-tag\n@@ -975,6 +986,7 @@ test_expect_success '__git_refs - do not filter refs unless told so' '\n \tmain\n \tmatching-branch\n \tmatching/branch\n+\tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n \tother/matching/branch-in-other\n@@ -1095,6 +1107,7 @@ test_expect_success '__git_complete_refs - simple' '\n \tHEAD Z\n \tmain Z\n \tmatching-branch Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tmatching-tag Z\n@@ -1123,6 +1136,7 @@ test_expect_success '__git_complete_refs - matching' '\n test_expect_success '__git_complete_refs - remote' '\n \tsed -e \"s/Z$//\" >expected <<-EOF &&\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain-in-other Z\n \tEOF\n@@ -1139,9 +1153,11 @@ test_expect_success '__git_complete_refs - track' '\n \tHEAD Z\n \tmain Z\n \tmatching-branch Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tmatching-tag Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain-in-other Z\n \tEOF\n@@ -1184,6 +1200,7 @@ test_expect_success '__git_complete_refs - suffix' '\n \tHEAD.\n \tmain.\n \tmatching-branch.\n+\tother/HEAD.\n \tother/branch-in-other.\n \tother/main-in-other.\n \tmatching-tag.\n@@ -1199,6 +1216,7 @@ test_expect_success '__git_complete_refs - suffix' '\n test_expect_success '__git_complete_fetch_refspecs - simple' '\n \tsed -e \"s/Z$//\" >expected <<-EOF &&\n \tHEAD:HEAD Z\n+\tHEAD:HEAD Z\n \tbranch-in-other:branch-in-other Z\n \tmain-in-other:main-in-other Z\n \tEOF\n@@ -1225,6 +1243,7 @@ test_expect_success '__git_complete_fetch_refspecs - matching' '\n test_expect_success '__git_complete_fetch_refspecs - prefix' '\n \tsed -e \"s/Z$//\" >expected <<-EOF &&\n \t+HEAD:HEAD Z\n+\t+HEAD:HEAD Z\n \t+branch-in-other:branch-in-other Z\n \t+main-in-other:main-in-other Z\n \tEOF\n@@ -1289,6 +1308,7 @@ test_expect_success '__git_complete_worktree_paths with -C' '\n \n test_expect_success 'git switch - with no options, complete local branches and unique remote branch names for DWIM logic' '\n \ttest_completion \"git switch \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1435,11 +1455,13 @@ test_expect_success 'git-bisect - existing view subcommand is recognized and ena\n test_expect_success 'git checkout - completes refs and unique remote branches for DWIM' '\n \ttest_completion \"git checkout \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1461,6 +1483,7 @@ test_expect_success 'git switch - with GIT_COMPLETION_CHECKOUT_NO_GUESS=1, compl\n \n test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete local branches and unique remote names for DWIM logic' '\n \tGIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git switch --guess \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1470,6 +1493,7 @@ test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_G\n \n test_expect_success 'git switch - a later --guess overrides previous --no-guess, complete local and remote unique branches for DWIM' '\n \ttest_completion \"git switch --no-guess --guess \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1490,6 +1514,7 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1498,11 +1523,13 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete\n test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1, complete refs and unique remote branches for DWIM' '\n \tGIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git checkout --guess \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1514,6 +1541,7 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1522,11 +1550,13 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '\n test_expect_success 'git checkout - a later --guess overrides previous --no-guess, complete refs and unique remote branches for DWIM' '\n \ttest_completion \"git checkout --no-guess --guess \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1538,6 +1568,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous --gues\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1550,6 +1581,7 @@ test_expect_success 'git checkout - with checkout.guess = false, only completes\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1559,11 +1591,13 @@ test_expect_success 'git checkout - with checkout.guess = true, completes refs a\n \ttest_config checkout.guess true &&\n \ttest_completion \"git checkout \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1573,11 +1607,13 @@ test_expect_success 'git checkout - a later --guess overrides previous checkout.\n \ttest_config checkout.guess false &&\n \ttest_completion \"git checkout --guess \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1590,6 +1626,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous checko\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1601,6 +1638,7 @@ test_expect_success 'git switch - with --detach, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1612,6 +1650,7 @@ test_expect_success 'git checkout - with --detach, complete only references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1783,6 +1822,7 @@ test_expect_success 'git switch - with -d, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1794,6 +1834,7 @@ test_expect_success 'git checkout - with -d, complete only references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1801,10 +1842,12 @@ test_expect_success 'git checkout - with -d, complete only references' '\n \n test_expect_success 'git switch - with --track, complete only remote branches' '\n \ttest_completion \"git switch --track \" <<-\\EOF &&\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n \ttest_completion \"git switch -t \" <<-\\EOF\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1812,10 +1855,12 @@ test_expect_success 'git switch - with --track, complete only remote branches' '\n \n test_expect_success 'git checkout - with --track, complete only remote branches' '\n \ttest_completion \"git checkout --track \" <<-\\EOF &&\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n \ttest_completion \"git checkout -t \" <<-\\EOF\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1834,6 +1879,7 @@ test_expect_success 'git checkout - with --no-track, complete only local referen\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1845,6 +1891,7 @@ test_expect_success 'git switch - with -c, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1856,6 +1903,7 @@ test_expect_success 'git switch - with -C, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1867,6 +1915,7 @@ test_expect_success 'git switch - with -c and --track, complete all references'\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1878,6 +1927,7 @@ test_expect_success 'git switch - with -C and --track, complete all references'\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1889,6 +1939,7 @@ test_expect_success 'git switch - with -c and --no-track, complete all reference\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1900,6 +1951,7 @@ test_expect_success 'git switch - with -C and --no-track, complete all reference\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1911,6 +1963,7 @@ test_expect_success 'git checkout - with -b, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1922,6 +1975,7 @@ test_expect_success 'git checkout - with -B, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1933,6 +1987,7 @@ test_expect_success 'git checkout - with -b and --track, complete all references\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1944,6 +1999,7 @@ test_expect_success 'git checkout - with -B and --track, complete all references\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1955,6 +2011,7 @@ test_expect_success 'git checkout - with -b and --no-track, complete all referen\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1966,6 +2023,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1973,6 +2031,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen\n \n test_expect_success 'git switch - for -c, complete local branches and unique remote branches' '\n \ttest_completion \"git switch -c \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1982,6 +2041,7 @@ test_expect_success 'git switch - for -c, complete local branches and unique rem\n \n test_expect_success 'git switch - for -C, complete local branches and unique remote branches' '\n \ttest_completion \"git switch -C \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2019,6 +2079,7 @@ test_expect_success 'git switch - for -C with --no-track, complete local branche\n \n test_expect_success 'git checkout - for -b, complete local branches and unique remote branches' '\n \ttest_completion \"git checkout -b \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2028,6 +2089,7 @@ test_expect_success 'git checkout - for -b, complete local branches and unique r\n \n test_expect_success 'git checkout - for -B, complete local branches and unique remote branches' '\n \ttest_completion \"git checkout -B \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2065,6 +2127,7 @@ test_expect_success 'git checkout - for -B with --no-track, complete local branc\n \n test_expect_success 'git switch - with --orphan completes local branch names and unique remote branch names' '\n \ttest_completion \"git switch --orphan \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2080,6 +2143,7 @@ test_expect_success 'git switch - --orphan with branch already provided complete\n \n test_expect_success 'git checkout - with --orphan completes local branch names and unique remote branch names' '\n \ttest_completion \"git checkout --orphan \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2093,6 +2157,7 @@ test_expect_success 'git checkout - --orphan with branch already provided comple\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n-- \n2.47.0.298.g52a96ec17b\n\n"},{"id":"507885","messageId":"xmqqbjy7amwz.fsf@gitster.g","threadId":"62096","inReplyTo":"20241121225757.3877852-3-bence@ferdinandy.com","subject":"Re: [PATCH v14 02/10] t/t5505-remote: test failure of set-head","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-11-22T04:54:20Z","receivedAt":"2024-11-22T04:54:22Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bence Ferdinandy <bence@ferdinandy.com> writes:\n\n> -\t\t\tresult |= error(_(\"Could not setup %s\"), buf.buf);\n> +\t\t\tresult |= error(_(\"Could not set up %s\"), buf.buf);\n\nGood eyes.\n\n> diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\n> index 9b50276646..4e127bf5b8 100755\n> --- a/t/t5505-remote.sh\n> +++ b/t/t5505-remote.sh\n> @@ -432,6 +432,18 @@ test_expect_success 'set-head --auto' '\n>  \t)\n>  '\n>  \n> +test_expect_success REFFILES 'set-head --auto failure' '\n\nWriting\n\n\ttest_when_finished \"rm -f test/.git/refs/remotes/origin/HEAD.lock\" &&\n\nhere allows us not to worry about commands in the sequence before\nthe \"rm\" below failing unexpectedly.\n\n> +\t(\n> +\t\tcd test &&\n> +\t\ttouch .git/refs/remotes/origin/HEAD.lock &&\n\n> +\t\tgit remote set-head --auto origin 2>errormsg ||\n> +\t\ttail -n1 errormsg >output &&\n\nAre we saying that it is OK for set-head not to fail here?  If not,\nthen this should be\n\n\t\ttest_must_fail git remote set-head ... 2>err &&\n\t\ttail -n 1 err >actual &&\n\ninstead.\n\n> +\t\trm .git/refs/remotes/origin/HEAD.lock &&\n> +\t\techo \"error: Could not set up refs/remotes/origin/HEAD\" >expect &&\n> +\t\ttest_cmp expect output\n> +\t)\n> +'\n> +\n>  test_expect_success 'set-head --auto has no problem w/multiple HEADs' '\n>  \t(\n>  \t\tcd test &&\n"},{"id":"507906","messageId":"CAOLa=ZS5yNpZEUqBAUpP-pSbJXk4+=XM6S6e9RY_eSVJEBhqkA@mail.gmail.com","threadId":"62096","inReplyTo":"20241121225757.3877852-4-bence@ferdinandy.com","subject":"Re: [PATCH v14 03/10] refs: standardize output of refs_read_symbolic_ref","fromName":"karthik nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-11-22T10:37:31Z","receivedAt":"2024-11-22T10:37:34Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Bence Ferdinandy <bence@ferdinandy.com> writes:\n\n[snip]\n\n> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\n> index 38eb14d591..1809e3426a 100644\n> --- a/refs/reftable-backend.c\n> +++ b/refs/reftable-backend.c\n> @@ -830,10 +830,12 @@ static int reftable_be_read_symbolic_ref(struct ref_store *ref_store,\n>  \t\treturn ret;\n>\n>  \tret = reftable_stack_read_ref(stack, refname, &ref);\n> -\tif (ret == 0 && ref.value_type == REFTABLE_REF_SYMREF)\n> +        if (ret)\n> +                ret = -1;\n> +        else if (ref.value_type == REFTABLE_REF_SYMREF)\n>  \t\tstrbuf_addstr(referent, ref.value.symref);\n> -\telse\n> -\t\tret = -1;\n> +        else\n> +                ret = NOT_A_SYMREF;\n>\n\nI was building my series on top of this, and noticed whitespace issues\nhere. A simple way to check your series is to run:\n\n  $ git log --check --pretty=format:\"---% h% s\"\n\n>  \treftable_ref_record_release(&ref);\n>  \treturn ret;\n> --\n> 2.47.0.298.g52a96ec17b\n"},{"id":"507908","messageId":"D5SNGOK1IKRS.1TY1DL9PJ7MPF@ferdinandy.com","threadId":"62096","inReplyTo":"CAOLa=ZS5yNpZEUqBAUpP-pSbJXk4+=XM6S6e9RY_eSVJEBhqkA@mail.gmail.com","subject":"Re: [PATCH v14 03/10] refs: standardize output of refs_read_symbolic_ref","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-22T10:53:33Z","receivedAt":"2024-11-22T10:54:04Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Fri Nov 22, 2024 at 11:37, karthik nayak <karthik.188@gmail.com> wrote:\n> Bence Ferdinandy <bence@ferdinandy.com> writes:\n>\n> [snip]\n>\n>> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\n>> index 38eb14d591..1809e3426a 100644\n>> --- a/refs/reftable-backend.c\n>> +++ b/refs/reftable-backend.c\n>> @@ -830,10 +830,12 @@ static int reftable_be_read_symbolic_ref(struct ref_store *ref_store,\n>>  \t\treturn ret;\n>>\n>>  \tret = reftable_stack_read_ref(stack, refname, &ref);\n>> -\tif (ret == 0 && ref.value_type == REFTABLE_REF_SYMREF)\n>> +        if (ret)\n>> +                ret = -1;\n>> +        else if (ref.value_type == REFTABLE_REF_SYMREF)\n>>  \t\tstrbuf_addstr(referent, ref.value.symref);\n>> -\telse\n>> -\t\tret = -1;\n>> +        else\n>> +                ret = NOT_A_SYMREF;\n>>\n>\n> I was building my series on top of this, and noticed whitespace issues\n> here. A simple way to check your series is to run:\n>\n>   $ git log --check --pretty=format:\"---% h% s\"\n\nI ran this on v15 and it didn't produce any output. I read what --check is in\nthe manpages, although the format is a bit cryptic for me. What does that do\nexactly? \n\nAnyhow if there was no output for v15 I should be fine, right?\n\n>\n>>  \treftable_ref_record_release(&ref);\n>>  \treturn ret;\n>> --\n>> 2.47.0.298.g52a96ec17b\n\n\n\n\n-- \nbence.ferdinandy.com\n\n"},{"id":"507909","messageId":"D5SNI5WC9IB5.10RIQJ256K4DS@ferdinandy.com","threadId":"62096","inReplyTo":"D5SNGOK1IKRS.1TY1DL9PJ7MPF@ferdinandy.com","subject":"Re: [PATCH v14 03/10] refs: standardize output of refs_read_symbolic_ref","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-22T10:55:29Z","receivedAt":"2024-11-22T10:55:56Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Fri Nov 22, 2024 at 11:53, Bence Ferdinandy <bence@ferdinandy.com> wrote:\n>\n> On Fri Nov 22, 2024 at 11:37, karthik nayak <karthik.188@gmail.com> wrote:\n>> Bence Ferdinandy <bence@ferdinandy.com> writes:\n>>\n>> [snip]\n>>\n>>> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\n>>> index 38eb14d591..1809e3426a 100644\n>>> --- a/refs/reftable-backend.c\n>>> +++ b/refs/reftable-backend.c\n>>> @@ -830,10 +830,12 @@ static int reftable_be_read_symbolic_ref(struct ref_store *ref_store,\n>>>  \t\treturn ret;\n>>>\n>>>  \tret = reftable_stack_read_ref(stack, refname, &ref);\n>>> -\tif (ret == 0 && ref.value_type == REFTABLE_REF_SYMREF)\n>>> +        if (ret)\n>>> +                ret = -1;\n>>> +        else if (ref.value_type == REFTABLE_REF_SYMREF)\n>>>  \t\tstrbuf_addstr(referent, ref.value.symref);\n>>> -\telse\n>>> -\t\tret = -1;\n>>> +        else\n>>> +                ret = NOT_A_SYMREF;\n>>>\n>>\n>> I was building my series on top of this, and noticed whitespace issues\n>> here. A simple way to check your series is to run:\n>>\n>>   $ git log --check --pretty=format:\"---% h% s\"\n>\n> I ran this on v15 and it didn't produce any output. I read what --check is in\n> the manpages, although the format is a bit cryptic for me. What does that do\n> exactly? \n>\n> Anyhow if there was no output for v15 I should be fine, right?\n\nBut then again: this patch is exactly like v14 so I'm not so sure about that ...\n\n\n\n-- \nbence.ferdinandy.com\n\n"},{"id":"507910","messageId":"D5SO6T9H97TG.32WME5GYFLYXM@ferdinandy.com","threadId":"62096","inReplyTo":"CAOLa=ZS5yNpZEUqBAUpP-pSbJXk4+=XM6S6e9RY_eSVJEBhqkA@mail.gmail.com","subject":"Re: [PATCH v14 03/10] refs: standardize output of refs_read_symbolic_ref","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-22T11:27:41Z","receivedAt":"2024-11-22T11:28:04Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Fri Nov 22, 2024 at 11:37, karthik nayak <karthik.188@gmail.com> wrote:\n> Bence Ferdinandy <bence@ferdinandy.com> writes:\n>\n> [snip]\n>\n>> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\n>> index 38eb14d591..1809e3426a 100644\n>> --- a/refs/reftable-backend.c\n>> +++ b/refs/reftable-backend.c\n>> @@ -830,10 +830,12 @@ static int reftable_be_read_symbolic_ref(struct ref_store *ref_store,\n>>  \t\treturn ret;\n>>\n>>  \tret = reftable_stack_read_ref(stack, refname, &ref);\n>> -\tif (ret == 0 && ref.value_type == REFTABLE_REF_SYMREF)\n>> +        if (ret)\n>> +                ret = -1;\n>> +        else if (ref.value_type == REFTABLE_REF_SYMREF)\n>>  \t\tstrbuf_addstr(referent, ref.value.symref);\n>> -\telse\n>> -\t\tret = -1;\n>> +        else\n>> +                ret = NOT_A_SYMREF;\n>>\n>\n> I was building my series on top of this, and noticed whitespace issues\n> here. A simple way to check your series is to run:\n\nFound it, thanks for catching, I had tabs and whitespaces mixed. \n\ngit log --check didn't find a problem with that though. \n\n>\n>>  \treftable_ref_record_release(&ref);\n>>  \treturn ret;\n>> --\n>> 2.47.0.298.g52a96ec17b\n\n\n\n\n-- \nbence.ferdinandy.com\n\n"},{"id":"507911","messageId":"CAOLa=ZS28xvpEBNO9AMamF00Yf8eHFGKyU5uHjBD7vOVF3_oEA@mail.gmail.com","threadId":"62096","inReplyTo":"D5SNGOK1IKRS.1TY1DL9PJ7MPF@ferdinandy.com","subject":"Re: [PATCH v14 03/10] refs: standardize output of refs_read_symbolic_ref","fromName":"karthik nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-11-22T11:30:45Z","receivedAt":"2024-11-22T11:30:47Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"\"Bence Ferdinandy\" <bence@ferdinandy.com> writes:\n\n> On Fri Nov 22, 2024 at 11:37, karthik nayak <karthik.188@gmail.com> wrote:\n>> Bence Ferdinandy <bence@ferdinandy.com> writes:\n>>\n>> [snip]\n>>\n>>> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\n>>> index 38eb14d591..1809e3426a 100644\n>>> --- a/refs/reftable-backend.c\n>>> +++ b/refs/reftable-backend.c\n>>> @@ -830,10 +830,12 @@ static int reftable_be_read_symbolic_ref(struct ref_store *ref_store,\n>>>  \t\treturn ret;\n>>>\n>>>  \tret = reftable_stack_read_ref(stack, refname, &ref);\n>>> -\tif (ret == 0 && ref.value_type == REFTABLE_REF_SYMREF)\n>>> +        if (ret)\n>>> +                ret = -1;\n>>> +        else if (ref.value_type == REFTABLE_REF_SYMREF)\n>>>  \t\tstrbuf_addstr(referent, ref.value.symref);\n>>> -\telse\n>>> -\t\tret = -1;\n>>> +        else\n>>> +                ret = NOT_A_SYMREF;\n>>>\n>>\n>> I was building my series on top of this, and noticed whitespace issues\n>> here. A simple way to check your series is to run:\n>>\n>>   $ git log --check --pretty=format:\"---% h% s\"\n>\n> I ran this on v15 and it didn't produce any output.\n\nDid you already post v15? I only see v14\n\n> I read what --check is in\n> the manpages, although the format is a bit cryptic for me. What does that do\n> exactly?\n>\n\nIt ensures that commits don't have conflict markers and that there are\nno trailing whitespaces and spaces followed by tabs by default.\n\nAlso this is included in the CI checks (see ci/check-whitespace.sh), so\nif you use either GitLab or GitHub you should see these shown as errors\non the CI. You'll have to raise a MR/PR to trigger the CI I believe.\n\nOn a sidenote, do you think we should modify the manpage? I found it\ncomprehensible, but would be nice to clarify anything cryptic.\n\n> Anyhow if there was no output for v15 I should be fine, right?\n>\n\nAt the least you should see `git log`'s output, but if there are issues\nthey should be shown inline. So when you say 'no output' do you mean you\nsee absolutely no output?\n\n>>\n>>>  \treftable_ref_record_release(&ref);\n>>>  \treturn ret;\n>>> --\n>>> 2.47.0.298.g52a96ec17b\n>\n>\n>\n>\n> --\n> bence.ferdinandy.com\n"},{"id":"507916","messageId":"D5SPDJZAM5K1.24R4JYB0WNTSF@ferdinandy.com","threadId":"62096","inReplyTo":"CAOLa=ZS28xvpEBNO9AMamF00Yf8eHFGKyU5uHjBD7vOVF3_oEA@mail.gmail.com","subject":"Re: [PATCH v14 03/10] refs: standardize output of refs_read_symbolic_ref","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-22T12:23:30Z","receivedAt":"2024-11-22T12:24:00Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Fri Nov 22, 2024 at 12:30, karthik nayak <karthik.188@gmail.com> wrote:\n> \"Bence Ferdinandy\" <bence@ferdinandy.com> writes:\n>\n>> On Fri Nov 22, 2024 at 11:37, karthik nayak <karthik.188@gmail.com> wrote:\n>>> Bence Ferdinandy <bence@ferdinandy.com> writes:\n>>>\n>>> [snip]\n>>>\n>>>> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\n>>>> index 38eb14d591..1809e3426a 100644\n>>>> --- a/refs/reftable-backend.c\n>>>> +++ b/refs/reftable-backend.c\n>>>> @@ -830,10 +830,12 @@ static int reftable_be_read_symbolic_ref(struct ref_store *ref_store,\n>>>>  \t\treturn ret;\n>>>>\n>>>>  \tret = reftable_stack_read_ref(stack, refname, &ref);\n>>>> -\tif (ret == 0 && ref.value_type == REFTABLE_REF_SYMREF)\n>>>> +        if (ret)\n>>>> +                ret = -1;\n>>>> +        else if (ref.value_type == REFTABLE_REF_SYMREF)\n>>>>  \t\tstrbuf_addstr(referent, ref.value.symref);\n>>>> -\telse\n>>>> -\t\tret = -1;\n>>>> +        else\n>>>> +                ret = NOT_A_SYMREF;\n>>>>\n>>>\n>>> I was building my series on top of this, and noticed whitespace issues\n>>> here. A simple way to check your series is to run:\n>>>\n>>>   $ git log --check --pretty=format:\"---% h% s\"\n>>\n>> I ran this on v15 and it didn't produce any output.\n>\n> Did you already post v15? I only see v14\n\nNot yet, but I'll be sending it in a pinch.\n\n>\n>> I read what --check is in\n>> the manpages, although the format is a bit cryptic for me. What does that do\n>> exactly?\n>>\n>\n> It ensures that commits don't have conflict markers and that there are\n> no trailing whitespaces and spaces followed by tabs by default.\n>\n> Also this is included in the CI checks (see ci/check-whitespace.sh), so\n> if you use either GitLab or GitHub you should see these shown as errors\n> on the CI. You'll have to raise a MR/PR to trigger the CI I believe.\n\nI've been running the CI by pushing to a fork since Taylor first caught an\nerror I didn't see locally and it never flagged. Now that you mention it, I'll\nalso add log --check to my CI-s.\n\n>\n> On a sidenote, do you think we should modify the manpage? I found it\n> comprehensible, but would be nice to clarify anything cryptic.\n\nNo, --check was quite clear, the `--pretty=format:\"---% h% s\"` part was what\nwas cryptic.\n\n>\n>> Anyhow if there was no output for v15 I should be fine, right?\n>>\n>\n> At the least you should see `git log`'s output, but if there are issues\n> they should be shown inline. So when you say 'no output' do you mean you\n> see absolutely no output?\n\nAbsolutely no output:\n\thttps://asciinema.org/a/lsqp4e1bNst6cFWw9M2jX1IqC\n\nBut I figured out why: the whitespace and the tabs were not mixed on the line,\njust across lines. As I read it, that is not an error to have tabs on one line\nand spaces on the next.\n\nAnyhow that should be now cleared up, thanks. Gotta say, I was expecting to\nlearn about internals doing this, but I also ended up picking up a couple of\nusage things as well, like --range-diff for format patch and such.\n\nThanks,\nBence\n"},{"id":"507917","messageId":"20241122123138.66960-2-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241122123138.66960-1-bence@ferdinandy.com","subject":"[PATCH v15 01/10] t/t5505-remote: set default branch to main","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-22T12:28:42Z","receivedAt":"2024-11-22T12:32:39Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Consider the bare repository called \"mirror\" in the test. Running `git\nremote add --mirror -f origin ../one` will not change HEAD, consequently\nif init.defaultBranch is not the same as what HEAD in the remote\n(\"one\"), HEAD in \"mirror\" will be pointing to a non-existent reference.\nHence if \"mirror\" is used as a remote by yet another repository,\nls-remote will not show HEAD. On the other hand, if init.defaultBranch\nhappens to match HEAD in \"one\", then ls-remote will show HEAD.\n\nSince the \"ci/run-build-and-tests.sh\" script globally exports\nGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main for some (but not all) jobs,\nthere may be a drift in some tests between how the test repositories are\nset up in the CI and during local testing, if the test itself uses\n\"master\" as default instead of \"main\". In particular, this happens in\nt5505-remote.sh. This issue does not manifest currently, as the test\ndoes not do any remote HEAD manipulation where this would come up, but\nshould such things be added, a locally passing test would break the CI\nand vice-versa.\n\nSet GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main in t5505-remote to be\nconsistent with the CI.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v9: - new patch\n        - a bandaid for the CI issue noticed by Taylor (cf:\n          https://lore.kernel.org/git/Zw8IKyPkG0Hr6%2F5t@nand.local/), but\n          see\n          https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/ for the root cause in detail\n    \n    v10: no change\n    \n    v11: no change\n    \n    v12: added forgotten sign-off\n    \n    v13: commit message udpated to be more precise\n    \n    v14: made explicit in the commit message that the default branch is only\n         set for some CI jobs\n    \n    v15: no change\n\n t/t5505-remote.sh | 3 +++\n 1 file changed, 3 insertions(+)\n\ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex 532035933f..9b50276646 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -2,6 +2,9 @@\n \n test_description='git remote porcelain-ish'\n \n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n-- \n2.47.0.298.g52a96ec17b\n\n"},{"id":"507918","messageId":"20241122123138.66960-1-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241121225757.3877852-1-bence@ferdinandy.com","subject":"[PATCH v15 00/10] set-head/fetch remote/HEAD updates","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-22T12:28:41Z","receivedAt":"2024-11-22T12:32:39Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"2/10: use test_must_fail and test_when_finished\n3/10: whitespace fix\n\n\nBence Ferdinandy (10):\n  t/t5505-remote: set default branch to main\n  t/t5505-remote: test failure of set-head\n  refs: standardize output of refs_read_symbolic_ref\n  refs: atomically record overwritten ref in update_symref\n  remote set-head: refactor for readability\n  remote set-head: better output for --auto\n  refs: add TRANSACTION_CREATE_EXISTS error\n  refs: add create_only option to refs_update_symref_extended\n  fetch: set remote/HEAD if it does not exist\n  fetch set_head: handle mirrored bare repositories\n\n builtin/fetch.c                  |  74 +++++++++++++++++++++\n builtin/remote.c                 |  72 +++++++++++++++------\n refs.c                           |  47 ++++++++++++--\n refs.h                           |  19 +++++-\n refs/files-backend.c             |  31 +++++----\n refs/refs-internal.h             |   5 ++\n refs/reftable-backend.c          |  12 ++--\n t/t4207-log-decoration-colors.sh |   3 +-\n t/t5505-remote.sh                | 107 +++++++++++++++++++++++++++++--\n t/t5510-fetch.sh                 |  24 +++++++\n t/t5512-ls-remote.sh             |   2 +\n t/t5514-fetch-multiple.sh        |  17 ++++-\n t/t5516-fetch-push.sh            |   3 +-\n t/t5527-fetch-odd-refs.sh        |   3 +-\n t/t7900-maintenance.sh           |   3 +-\n t/t9210-scalar.sh                |   5 +-\n t/t9211-scalar-clone.sh          |   6 +-\n t/t9902-completion.sh            |  65 +++++++++++++++++++\n 18 files changed, 439 insertions(+), 59 deletions(-)\n\nRange-diff against v14:\n 1:  49cfd222d5 =  1:  2e1d001d1a t/t5505-remote: set default branch to main\n 2:  54bf9c7fff !  2:  d24e62035a t/t5505-remote: test failure of set-head\n    @@ Commit message\n      ## Notes ##\n         v14: new patch\n     \n    +    v15: - use test_must_fail and test_when_finished\n    +\n      ## builtin/remote.c ##\n     @@ builtin/remote.c: static int set_head(int argc, const char **argv, const char *prefix)\n      \t\tif (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))\n    @@ t/t5505-remote.sh: test_expect_success 'set-head --auto' '\n      '\n      \n     +test_expect_success REFFILES 'set-head --auto failure' '\n    ++\ttest_when_finished \"rm -f test/.git/refs/remotes/origin/HEAD.lock\" &&\n     +\t(\n     +\t\tcd test &&\n     +\t\ttouch .git/refs/remotes/origin/HEAD.lock &&\n    -+\t\tgit remote set-head --auto origin 2>errormsg ||\n    -+\t\ttail -n1 errormsg >output &&\n    -+\t\trm .git/refs/remotes/origin/HEAD.lock &&\n    ++\t\ttest_must_fail git remote set-head --auto origin 2>err &&\n    ++\t\ttail -n1 err >output &&\n     +\t\techo \"error: Could not set up refs/remotes/origin/HEAD\" >expect &&\n     +\t\ttest_cmp expect output\n     +\t)\n 3:  c95362236e !  3:  1218d521e5 refs: standardize output of refs_read_symbolic_ref\n    @@ Notes\n              - rewrote comment in refs-internal.h to point to the one in refs.h\n              - created NOT_A_SYMREF=-2 constant\n     \n    +    v15: fix whitespace issues\n    +\n      ## refs.h ##\n     @@ refs.h: int refs_read_ref_full(struct ref_store *refs, const char *refname,\n      \n    @@ refs/reftable-backend.c: static int reftable_be_read_symbolic_ref(struct ref_sto\n      \n      \tret = reftable_stack_read_ref(stack, refname, &ref);\n     -\tif (ret == 0 && ref.value_type == REFTABLE_REF_SYMREF)\n    -+        if (ret)\n    -+                ret = -1;\n    -+        else if (ref.value_type == REFTABLE_REF_SYMREF)\n    ++\tif (ret)\n    ++\t\tret = -1;\n    ++\telse if (ref.value_type == REFTABLE_REF_SYMREF)\n      \t\tstrbuf_addstr(referent, ref.value.symref);\n    --\telse\n    + \telse\n     -\t\tret = -1;\n    -+        else\n    -+                ret = NOT_A_SYMREF;\n    ++\t\tret = NOT_A_SYMREF;\n      \n      \treftable_ref_record_release(&ref);\n      \treturn ret;\n 4:  21533e0e6b !  4:  4d5f3f4f01 refs: atomically record overwritten ref in update_symref\n    @@ Notes\n              - the returned error code by the above was also incorrect and now\n                is -2 (and uses NOT_A_SYMREF now)\n     \n    +    v15: no change\n    +\n      ## refs.c ##\n     @@ refs.c: int peel_iterated_oid(struct repository *r, const struct object_id *base, struct\n      \n 5:  2455768c24 !  5:  6f2cb51b0b remote set-head: refactor for readability\n    @@ Notes\n     \n         v14: no change\n     \n    +    v15: no change\n    +\n      ## builtin/remote.c ##\n     @@ builtin/remote.c: static int show(int argc, const char **argv, const char *prefix)\n      static int set_head(int argc, const char **argv, const char *prefix)\n 6:  396537a598 !  6:  ca8687bae1 remote set-head: better output for --auto\n    @@ Notes\n         v14: - fixed badly named variable\n              - fixed not reporting errors correctly\n     \n    +    v15: no change\n    +\n      ## builtin/remote.c ##\n     @@ builtin/remote.c: static int show(int argc, const char **argv, const char *prefix)\n      \treturn result;\n 7:  05e97155d1 !  7:  45bcb33b52 refs: add TRANSACTION_CREATE_EXISTS error\n    @@ Notes\n     \n         v14: no change\n     \n    +    v15: no change\n    +\n      ## refs.h ##\n     @@ refs.h: int ref_transaction_verify(struct ref_transaction *transaction,\n      \n 8:  3ea8b1b8b6 !  8:  a8a522a089 refs: add create_only option to refs_update_symref_extended\n    @@ Notes\n     \n         v14: no change\n     \n    +    v15: no change\n    +\n      ## builtin/remote.c ##\n     @@ builtin/remote.c: static int set_head(int argc, const char **argv, const char *prefix)\n      \t\tgoto cleanup;\n 9:  287c25b801 !  9:  ff294461d8 fetch: set remote/HEAD if it does not exist\n    @@ Notes\n         created with an opt out, and the configuration for this will be added in\n         a later patch\n     \n    +    v15: no change\n    +\n      ## builtin/fetch.c ##\n     @@ builtin/fetch.c: static int backfill_tags(struct display_state *display_state,\n      \treturn retcode;\n10:  05d8526399 ! 10:  0e96260ab6 fetch set_head: handle mirrored bare repositories\n    @@ Notes\n     \n         v14: no change\n     \n    +    v15: no change\n    +\n      ## builtin/fetch.c ##\n     @@ builtin/fetch.c: static const char *strip_refshead(const char *name){\n      \n-- \n2.47.0.298.g52a96ec17b\n\n"},{"id":"507919","messageId":"20241122123138.66960-4-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241122123138.66960-1-bence@ferdinandy.com","subject":"[PATCH v15 03/10] refs: standardize output of refs_read_symbolic_ref","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-22T12:28:44Z","receivedAt":"2024-11-22T12:32:46Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"When the symbolic reference we want to read with refs_read_symbolic_ref\nis actually not a symbolic reference, the files and the reftable\nbackends return different values (1 and -1 respectively). Standardize\nthe returned values so that 0 is success, -1 is a generic error and -2\nis that the reference was actually non-symbolic.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v13: new patch\n    \n    v14: - simplified the logic in reftables backend (thanks Junio)\n         - update the comment in refs.h (thanks Patrick)\n         - rewrote comment in refs-internal.h to point to the one in refs.h\n         - created NOT_A_SYMREF=-2 constant\n    \n    v15: fix whitespace issues\n\n refs.h                  | 11 +++++++++++\n refs/files-backend.c    |  7 +++----\n refs/refs-internal.h    |  5 +++++\n refs/reftable-backend.c |  6 ++++--\n 4 files changed, 23 insertions(+), 6 deletions(-)\n\ndiff --git a/refs.h b/refs.h\nindex 108dfc93b3..22c2b45b8b 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -83,6 +83,17 @@ int refs_read_ref_full(struct ref_store *refs, const char *refname,\n \n int refs_read_ref(struct ref_store *refs, const char *refname, struct object_id *oid);\n \n+#define NOT_A_SYMREF -2\n+\n+/*\n+ * Read the symbolic ref named \"refname\" and write its immediate referent into\n+ * the provided buffer. Referent is left empty if \"refname\" is not a symbolic\n+ * ref. It does not resolve the symbolic reference recursively in case the\n+ * target is also a symbolic ref.\n+ *\n+ * Returns 0 on success, -2 if the \"refname\" is not a symbolic ref,\n+ * -1 otherwise.\n+ */\n int refs_read_symbolic_ref(struct ref_store *ref_store, const char *refname,\n \t\t\t   struct strbuf *referent);\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 0824c0b8a9..4cc43c32f2 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -596,10 +596,9 @@ static int files_read_symbolic_ref(struct ref_store *ref_store, const char *refn\n \tunsigned int type;\n \n \tret = read_ref_internal(ref_store, refname, &oid, referent, &type, &failure_errno, 1);\n-\tif (ret)\n-\t\treturn ret;\n-\n-\treturn !(type & REF_ISSYMREF);\n+\tif (!ret && !(type & REF_ISSYMREF))\n+\t\treturn NOT_A_SYMREF;\n+\treturn ret;\n }\n \n int parse_loose_ref_contents(const struct git_hash_algo *algop,\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 2313c830d8..1399fee61c 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -673,6 +673,11 @@ struct ref_storage_be {\n \n \tref_iterator_begin_fn *iterator_begin;\n \tread_raw_ref_fn *read_raw_ref;\n+\n+\t/*\n+\t * Please refer to `refs_read_symbolic_ref()` for the expected\n+\t * behaviour.\n+\t */\n \tread_symbolic_ref_fn *read_symbolic_ref;\n \n \treflog_iterator_begin_fn *reflog_iterator_begin;\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 38eb14d591..e8afcda1f9 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -830,10 +830,12 @@ static int reftable_be_read_symbolic_ref(struct ref_store *ref_store,\n \t\treturn ret;\n \n \tret = reftable_stack_read_ref(stack, refname, &ref);\n-\tif (ret == 0 && ref.value_type == REFTABLE_REF_SYMREF)\n+\tif (ret)\n+\t\tret = -1;\n+\telse if (ref.value_type == REFTABLE_REF_SYMREF)\n \t\tstrbuf_addstr(referent, ref.value.symref);\n \telse\n-\t\tret = -1;\n+\t\tret = NOT_A_SYMREF;\n \n \treftable_ref_record_release(&ref);\n \treturn ret;\n-- \n2.47.0.298.g52a96ec17b\n\n"},{"id":"507920","messageId":"20241122123138.66960-3-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241122123138.66960-1-bence@ferdinandy.com","subject":"[PATCH v15 02/10] t/t5505-remote: test failure of set-head","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-22T12:28:43Z","receivedAt":"2024-11-22T12:32:47Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"The test coverage was missing a test for the failure branch of remote\nset-head auto's output. Add the missing text and while we are at it,\ncorrect a small grammatical mistake in the error's output (\"setup\" is\nthe noun, \"set up\" is the verb).\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v14: new patch\n    \n    v15: - use test_must_fail and test_when_finished\n\n builtin/remote.c  |  2 +-\n t/t5505-remote.sh | 12 ++++++++++++\n 2 files changed, 13 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 9093600965..8bca3fb04e 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1445,7 +1445,7 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\tif (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))\n \t\t\tresult |= error(_(\"Not a valid ref: %s\"), buf2.buf);\n \t\telse if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote set-head\"))\n-\t\t\tresult |= error(_(\"Could not setup %s\"), buf.buf);\n+\t\t\tresult |= error(_(\"Could not set up %s\"), buf.buf);\n \t\telse if (opt_a)\n \t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\n \t\tfree(head_name);\ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex 9b50276646..61e3ecc1af 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -432,6 +432,18 @@ test_expect_success 'set-head --auto' '\n \t)\n '\n \n+test_expect_success REFFILES 'set-head --auto failure' '\n+\ttest_when_finished \"rm -f test/.git/refs/remotes/origin/HEAD.lock\" &&\n+\t(\n+\t\tcd test &&\n+\t\ttouch .git/refs/remotes/origin/HEAD.lock &&\n+\t\ttest_must_fail git remote set-head --auto origin 2>err &&\n+\t\ttail -n1 err >output &&\n+\t\techo \"error: Could not set up refs/remotes/origin/HEAD\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n test_expect_success 'set-head --auto has no problem w/multiple HEADs' '\n \t(\n \t\tcd test &&\n-- \n2.47.0.298.g52a96ec17b\n\n"},{"id":"507921","messageId":"20241122123138.66960-5-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241122123138.66960-1-bence@ferdinandy.com","subject":"[PATCH v15 04/10] refs: atomically record overwritten ref in update_symref","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-22T12:28:45Z","receivedAt":"2024-11-22T12:32:51Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"When updating a symref with update_symref it's currently not possible to\nknow for sure what was the previous value that was overwritten. Extend\nrefs_update_symref under a new function name, to record the value after\nthe ref has been locked if the caller of refs_update_symref_extended\nrequests it via a new variable in the function call. Make the return\nvalue of the function notify the caller, if the previous value was\nactually not a symbolic reference. Keep the original refs_update_symref\nfunction with the same signature, but now as a wrapper around\nrefs_update_symref_extended.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v4: new patch\n    \n    v5: - added before_target to reftables backend\n        - added an extra safety check for transaction's existence in refs.c\n    \n    v6: - no change\n    \n    v7: - remove the whole before_target concept from the backends and\n          handle checking it in refs.c instead (thanks Karthik)\n        - rename the before_target to referent which is how the same concept\n          is called in the backends\n        - change commit prefix to be more in line with project standards\n    \n    v8: no change\n    \n    v9: - instead of adding parameters to refs_update_symref, rename what\n          was in v8 as refs_update_symref_extended and make refs_update_symref\n          a wrapper for that. This significantly reduces the number of files\n          that need to be touched, and avoids adding a lot of dummy NULL-s\n          in unrelated places.\n    \n    v10: no change\n    \n    v11: no change\n    \n    v12: no change\n    \n    v13: if referent is a non-symbolic ref, record the hash in referent and\n        signal this with a return value of -1\n    \n    v14: - mistakes were made: trying to read the referent should NOT lead to\n         an actual error, even if it doesn't work out in the end, that part of\n         the code should only set the return to -2 or not touch it\n         - the returned error code by the above was also incorrect and now\n           is -2 (and uses NOT_A_SYMREF now)\n    \n    v15: no change\n\n refs.c | 22 ++++++++++++++++++++--\n refs.h |  4 ++++\n 2 files changed, 24 insertions(+), 2 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 5f729ed412..d80efd58f0 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2115,6 +2115,13 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct\n \n int refs_update_symref(struct ref_store *refs, const char *ref,\n \t\t       const char *target, const char *logmsg)\n+{\n+\treturn refs_update_symref_extended(refs, ref, target, logmsg, NULL);\n+}\n+\n+int refs_update_symref_extended(struct ref_store *refs, const char *ref,\n+\t\t       const char *target, const char *logmsg,\n+\t\t       struct strbuf *referent)\n {\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n@@ -2125,10 +2132,22 @@ int refs_update_symref(struct ref_store *refs, const char *ref,\n \t    ref_transaction_update(transaction, ref, NULL, NULL,\n \t\t\t\t   target, NULL, REF_NO_DEREF,\n \t\t\t\t   logmsg, &err) ||\n-\t    ref_transaction_commit(transaction, &err)) {\n+\t    ref_transaction_prepare(transaction, &err)) {\n \t\tret = error(\"%s\", err.buf);\n+\t\tgoto cleanup;\n \t}\n+\tif (referent && refs_read_symbolic_ref(refs, ref, referent) == NOT_A_SYMREF) {\n+\t\tstruct object_id oid;\n+\t\tif (!refs_read_ref(refs, ref, &oid)) {\n+\t\t\tstrbuf_addstr(referent, oid_to_hex(&oid));\n+\t\t\tret = NOT_A_SYMREF;\n+\t\t}\n+\t}\n+\n+\tif (ref_transaction_commit(transaction, &err))\n+\t\tret = error(\"%s\", err.buf);\n \n+cleanup:\n \tstrbuf_release(&err);\n \tif (transaction)\n \t\tref_transaction_free(transaction);\n@@ -2948,4 +2967,3 @@ int ref_update_expects_existing_old_ref(struct ref_update *update)\n \treturn (update->flags & REF_HAVE_OLD) &&\n \t\t(!is_null_oid(&update->old_oid) || update->old_target);\n }\n-\ndiff --git a/refs.h b/refs.h\nindex 22c2b45b8b..5c46ac9f34 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -584,6 +584,10 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,\n int refs_update_symref(struct ref_store *refs, const char *refname,\n \t\t       const char *target, const char *logmsg);\n \n+int refs_update_symref_extended(struct ref_store *refs, const char *refname,\n+\t\t       const char *target, const char *logmsg,\n+\t\t       struct strbuf *referent);\n+\n enum action_on_err {\n \tUPDATE_REFS_MSG_ON_ERR,\n \tUPDATE_REFS_DIE_ON_ERR,\n-- \n2.47.0.298.g52a96ec17b\n\n"},{"id":"507922","messageId":"20241122123138.66960-6-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241122123138.66960-1-bence@ferdinandy.com","subject":"[PATCH v15 05/10] remote set-head: refactor for readability","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-22T12:28:46Z","receivedAt":"2024-11-22T12:32:53Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Make two different readability refactors:\n\nRename strbufs \"buf\" and \"buf2\" to something more explanatory.\n\nInstead of calling get_main_ref_store(the_repository) multiple times,\ncall it once and store the result in a new refs variable. Although this\nchange probably offers some performance benefits, the main purpose is to\nshorten the line lengths of function calls using this variable.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v5: new patch (split from the next patch as a preparatory step)\n    \n    v6: no change\n    \n    v7: - change commit prefix to be more in line with project standards\n    \n    v8: no change\n    \n    v9: - further improve readability by renaming buf, and buf2 consistently\n          with how patch 6 was already done\n    \n    v10: no change\n    \n    v11: no change\n    \n    v12: no change\n    \n    v13: more precise wording for commit message\n    \n    v14: no change\n    \n    v15: no change\n\n builtin/remote.c | 23 ++++++++++++-----------\n 1 file changed, 12 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 8bca3fb04e..47ca650de8 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1402,8 +1402,9 @@ static int show(int argc, const char **argv, const char *prefix)\n static int set_head(int argc, const char **argv, const char *prefix)\n {\n \tint i, opt_a = 0, opt_d = 0, result = 0;\n-\tstruct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;\n+\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;\n \tchar *head_name = NULL;\n+\tstruct ref_store *refs = get_main_ref_store(the_repository);\n \n \tstruct option options[] = {\n \t\tOPT_BOOL('a', \"auto\", &opt_a,\n@@ -1415,7 +1416,7 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \targc = parse_options(argc, argv, prefix, options,\n \t\t\t     builtin_remote_sethead_usage, 0);\n \tif (argc)\n-\t\tstrbuf_addf(&buf, \"refs/remotes/%s/HEAD\", argv[0]);\n+\t\tstrbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", argv[0]);\n \n \tif (!opt_a && !opt_d && argc == 2) {\n \t\thead_name = xstrdup(argv[1]);\n@@ -1434,25 +1435,25 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\t\thead_name = xstrdup(states.heads.items[0].string);\n \t\tfree_remote_ref_states(&states);\n \t} else if (opt_d && !opt_a && argc == 1) {\n-\t\tif (refs_delete_ref(get_main_ref_store(the_repository), NULL, buf.buf, NULL, REF_NO_DEREF))\n-\t\t\tresult |= error(_(\"Could not delete %s\"), buf.buf);\n+\t\tif (refs_delete_ref(refs, NULL, b_head.buf, NULL, REF_NO_DEREF))\n+\t\t\tresult |= error(_(\"Could not delete %s\"), b_head.buf);\n \t} else\n \t\tusage_with_options(builtin_remote_sethead_usage, options);\n \n \tif (head_name) {\n-\t\tstrbuf_addf(&buf2, \"refs/remotes/%s/%s\", argv[0], head_name);\n+\t\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", argv[0], head_name);\n \t\t/* make sure it's valid */\n-\t\tif (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))\n-\t\t\tresult |= error(_(\"Not a valid ref: %s\"), buf2.buf);\n-\t\telse if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, \"remote set-head\"))\n-\t\t\tresult |= error(_(\"Could not set up %s\"), buf.buf);\n+\t\tif (!refs_ref_exists(refs, b_remote_head.buf))\n+\t\t\tresult |= error(_(\"Not a valid ref: %s\"), b_remote_head.buf);\n+\t\telse if (refs_update_symref(refs, b_head.buf, b_remote_head.buf, \"remote set-head\"))\n+\t\t\tresult |= error(_(\"Could not set up %s\"), b_head.buf);\n \t\telse if (opt_a)\n \t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\n \t\tfree(head_name);\n \t}\n \n-\tstrbuf_release(&buf);\n-\tstrbuf_release(&buf2);\n+\tstrbuf_release(&b_head);\n+\tstrbuf_release(&b_remote_head);\n \treturn result;\n }\n \n-- \n2.47.0.298.g52a96ec17b\n\n"},{"id":"507923","messageId":"20241122123138.66960-8-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241122123138.66960-1-bence@ferdinandy.com","subject":"[PATCH v15 07/10] refs: add TRANSACTION_CREATE_EXISTS error","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-22T12:28:48Z","receivedAt":"2024-11-22T12:32:58Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Currently there is only one special error for transaction, for when\nthere is a naming conflict, all other errors are dumped under a generic\nerror. Add a new special error case for when the caller requests the\nreference to be updated only when it does not yet exist and the\nreference actually does exist.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v4: new patch\n    v5: no change\n    v6: no change\n    \n    v7: - change commit prefix to be more in line with project standards\n        - changed error checking to Karthik's suggestion\n    \n    v8: no change\n    \n    v9: - no change\n    \n    v10: no change\n    \n    v11: no change\n    \n    v12: no change\n    \n    v13: no change\n    \n    v14: no change\n    \n    v15: no change\n\n refs.h                  |  4 +++-\n refs/files-backend.c    | 24 ++++++++++++++++--------\n refs/reftable-backend.c |  6 ++++--\n 3 files changed, 23 insertions(+), 11 deletions(-)\n\ndiff --git a/refs.h b/refs.h\nindex 5c46ac9f34..b243739e4b 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -773,8 +773,10 @@ int ref_transaction_verify(struct ref_transaction *transaction,\n \n /* Naming conflict (for example, the ref names A and A/B conflict). */\n #define TRANSACTION_NAME_CONFLICT -1\n+/* When only creation was requested, but the ref already exists. */\n+#define TRANSACTION_CREATE_EXISTS -2\n /* All other errors. */\n-#define TRANSACTION_GENERIC_ERROR -2\n+#define TRANSACTION_GENERIC_ERROR -3\n \n /*\n  * Perform the preparatory stages of committing `transaction`. Acquire\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 4cc43c32f2..23ae74089d 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2501,14 +2501,18 @@ static int split_symref_update(struct ref_update *update,\n static int check_old_oid(struct ref_update *update, struct object_id *oid,\n \t\t\t struct strbuf *err)\n {\n+\tint ret = TRANSACTION_GENERIC_ERROR;\n+\n \tif (!(update->flags & REF_HAVE_OLD) ||\n \t\t   oideq(oid, &update->old_oid))\n \t\treturn 0;\n \n-\tif (is_null_oid(&update->old_oid))\n+\tif (is_null_oid(&update->old_oid)) {\n \t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n \t\t\t    \"reference already exists\",\n \t\t\t    ref_update_original_update_refname(update));\n+\t\tret = TRANSACTION_CREATE_EXISTS;\n+\t}\n \telse if (is_null_oid(oid))\n \t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n \t\t\t    \"reference is missing but expected %s\",\n@@ -2521,7 +2525,7 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid,\n \t\t\t    oid_to_hex(oid),\n \t\t\t    oid_to_hex(&update->old_oid));\n \n-\treturn -1;\n+\treturn ret;\n }\n \n /*\n@@ -2601,9 +2605,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\t\tgoto out;\n \t\t\t\t}\n-\t\t\t} else if  (check_old_oid(update, &lock->old_oid, err)) {\n-\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n-\t\t\t\tgoto out;\n+\t\t\t} else {\n+\t\t\t\tret = check_old_oid(update, &lock->old_oid, err);\n+\t\t\t\tif  (ret) {\n+\t\t\t\t\tgoto out;\n+\t\t\t\t}\n \t\t\t}\n \t\t} else {\n \t\t\t/*\n@@ -2634,9 +2640,11 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\t\t    update->old_target);\n \t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\tgoto out;\n-\t\t} else if  (check_old_oid(update, &lock->old_oid, err)) {\n-\t\t\tret = TRANSACTION_GENERIC_ERROR;\n-\t\t\tgoto out;\n+\t\t} else {\n+\t\t\tret = check_old_oid(update, &lock->old_oid, err);\n+\t\t\tif  (ret) {\n+\t\t\t\tgoto out;\n+\t\t\t}\n \t\t}\n \n \t\t/*\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex e8afcda1f9..965c17d795 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1208,10 +1208,13 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\tgoto done;\n \t\t\t}\n \t\t} else if ((u->flags & REF_HAVE_OLD) && !oideq(&current_oid, &u->old_oid)) {\n-\t\t\tif (is_null_oid(&u->old_oid))\n+\t\t\tret = TRANSACTION_NAME_CONFLICT;\n+\t\t\tif (is_null_oid(&u->old_oid)) {\n \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n \t\t\t\t\t\t   \"reference already exists\"),\n \t\t\t\t\t    ref_update_original_update_refname(u));\n+\t\t\t\tret = TRANSACTION_CREATE_EXISTS;\n+\t\t\t}\n \t\t\telse if (is_null_oid(&current_oid))\n \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n \t\t\t\t\t\t   \"reference is missing but expected %s\"),\n@@ -1223,7 +1226,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\t\t    ref_update_original_update_refname(u),\n \t\t\t\t\t    oid_to_hex(&current_oid),\n \t\t\t\t\t    oid_to_hex(&u->old_oid));\n-\t\t\tret = -1;\n \t\t\tgoto done;\n \t\t}\n \n-- \n2.47.0.298.g52a96ec17b\n\n"},{"id":"507924","messageId":"20241122123138.66960-7-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241122123138.66960-1-bence@ferdinandy.com","subject":"[PATCH v15 06/10] remote set-head: better output for --auto","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-22T12:28:47Z","receivedAt":"2024-11-22T12:33:00Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Currently, set-head --auto will print a message saying \"remote/HEAD set\nto branch\", which implies something was changed.\n\nChange the output of --auto, so the output actually reflects what was\ndone: a) set a previously unset HEAD, b) change HEAD because remote\nchanged or c) no updates. As edge cases, if HEAD is changed from\na previous symbolic reference that was not a remote branch, explicitly\ncall attention to this fact, and also notify the user if the previous\nreference was not a symbolic reference.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v1-v2:\n    \n        was RFC in https://lore.kernel.org/git/20240910203835.2288291-1-bence@ferdinandy.com/\n    \n    v3:\n    \n        This patch was originally sent along when I thought set-head was\n        going to be invoked by fetch, but the discussion on the RFC\n        concluded that it should be not. This opened the possibility to make\n        it more explicit.\n    \n        Note: although I feel both things the patch does are really just\n        cosmetic, an argument could be made for breaking it into two, one\n        for the no-op part and one for the --auto print update.\n    \n        Was sent in:\n        https://lore.kernel.org/git/20240915221055.904107-1-bence@ferdinandy.com/\n    \n    v4:\n        - changes are now handled atomically via the ref update transaction\n        - outputs have changed along the lines of Junio's suggestion\n        - minor refactor to set_head for improved legibility\n    \n    v5: - the minor refactor has been split out into its own patch\n    \n    v6: - fixed uninitialized prev_head\n        - fixed case of unusual previous target\n        - fixed a test that would have been actually broken at this patch\n          (the output was only correct with the later update to fetch)\n        - added 4 tests for the 4 output cases\n    \n    v7: - change commit prefix to be more in line with project standards\n        - fixed tests to also work with the reftable backend\n        - renamed report function, fixed style issue with checking buf len\n        - fixed not releasing an strbuf\n    \n    v8: no change\n    \n    v9: - mark output strings in report_set_head_auto as translatable\n        - rename buf_prev to b_local_head for consistency\n        - use ${SQ} in tests instead of '\\''\n    \n    v10: no change\n    \n    v11: no change\n    \n    v12: no change\n    \n    v13: added handling the edge case of previous remote/HEAD being\n    a detached HEAD\n    \n    v14: - fixed badly named variable\n         - fixed not reporting errors correctly\n    \n    v15: no change\n\n builtin/remote.c  | 59 +++++++++++++++++++++++++++++++++++---------\n t/t5505-remote.sh | 63 ++++++++++++++++++++++++++++++++++++++++++++++-\n 2 files changed, 109 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 47ca650de8..e1f15e68f4 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1399,10 +1399,38 @@ static int show(int argc, const char **argv, const char *prefix)\n \treturn result;\n }\n \n+static void report_set_head_auto(const char *remote, const char *head_name,\n+\t\t\tstruct strbuf *b_local_head, int was_detached) {\n+\tstruct strbuf buf_prefix = STRBUF_INIT;\n+\tconst char *prev_head = NULL;\n+\n+\tstrbuf_addf(&buf_prefix, \"refs/remotes/%s/\", remote);\n+\tskip_prefix(b_local_head->buf, buf_prefix.buf, &prev_head);\n+\n+\tif (prev_head && !strcmp(prev_head, head_name))\n+\t\tprintf(_(\"'%s/HEAD' is unchanged and points to '%s'\\n\"),\n+\t\t\tremote, head_name);\n+\telse if (prev_head)\n+\t\tprintf(_(\"'%s/HEAD' has changed from '%s' and now points to '%s'\\n\"),\n+\t\t\tremote, prev_head, head_name);\n+\telse if (!b_local_head->len)\n+\t\tprintf(_(\"'%s/HEAD' is now created and points to '%s'\\n\"),\n+\t\t\tremote, head_name);\n+\telse if (was_detached && b_local_head->len)\n+\t\tprintf(_(\"'%s/HEAD' was detached at '%s' and now points to '%s'\\n\"),\n+\t\t\tremote, b_local_head->buf, head_name);\n+\telse\n+\t\tprintf(_(\"'%s/HEAD' used to point to '%s' \"\n+\t\t\t\"(which is not a remote branch), but now points to '%s'\\n\"),\n+\t\t\tremote, b_local_head->buf, head_name);\n+\tstrbuf_release(&buf_prefix);\n+}\n+\n static int set_head(int argc, const char **argv, const char *prefix)\n {\n-\tint i, opt_a = 0, opt_d = 0, result = 0;\n-\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;\n+\tint i, opt_a = 0, opt_d = 0, result = 0, was_detached;\n+\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,\n+\t\tb_local_head = STRBUF_INIT;\n \tchar *head_name = NULL;\n \tstruct ref_store *refs = get_main_ref_store(the_repository);\n \n@@ -1440,20 +1468,27 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t} else\n \t\tusage_with_options(builtin_remote_sethead_usage, options);\n \n-\tif (head_name) {\n-\t\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", argv[0], head_name);\n-\t\t/* make sure it's valid */\n-\t\tif (!refs_ref_exists(refs, b_remote_head.buf))\n-\t\t\tresult |= error(_(\"Not a valid ref: %s\"), b_remote_head.buf);\n-\t\telse if (refs_update_symref(refs, b_head.buf, b_remote_head.buf, \"remote set-head\"))\n-\t\t\tresult |= error(_(\"Could not set up %s\"), b_head.buf);\n-\t\telse if (opt_a)\n-\t\t\tprintf(\"%s/HEAD set to %s\\n\", argv[0], head_name);\n-\t\tfree(head_name);\n+\tif (!head_name)\n+\t\tgoto cleanup;\n+\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", argv[0], head_name);\n+\tif (!refs_ref_exists(refs, b_remote_head.buf)) {\n+\t\tresult |= error(_(\"Not a valid ref: %s\"), b_remote_head.buf);\n+\t\tgoto cleanup;\n+\t}\n+\twas_detached = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n+\t\t\t\"remote set-head\", &b_local_head);\n+\tif (was_detached == -1) {\n+\t\tresult |= error(_(\"Could not set up %s\"), b_head.buf);\n+\t\tgoto cleanup;\n \t}\n+\tif (opt_a)\n+\t\treport_set_head_auto(argv[0], head_name, &b_local_head, was_detached);\n \n+cleanup:\n+\tfree(head_name);\n \tstrbuf_release(&b_head);\n \tstrbuf_release(&b_remote_head);\n+\tstrbuf_release(&b_local_head);\n \treturn result;\n }\n \ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex 61e3ecc1af..d15b579c95 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -444,12 +444,63 @@ test_expect_success REFFILES 'set-head --auto failure' '\n \t)\n '\n \n+test_expect_success 'set-head --auto detects creation' '\n+\t(\n+\t\tcd test &&\n+\t\tgit update-ref --no-deref -d refs/remotes/origin/HEAD &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"${SQ}origin/HEAD${SQ} is now created and points to ${SQ}main${SQ}\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n+test_expect_success 'set-head --auto to update a non symbolic ref' '\n+\t(\n+\t\tcd test &&\n+\t\tgit update-ref --no-deref -d refs/remotes/origin/HEAD &&\n+\t\tgit update-ref refs/remotes/origin/HEAD HEAD &&\n+\t\tHEAD=$(git log --pretty=\"%H\") &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"${SQ}origin/HEAD${SQ} was detached at ${SQ}${HEAD}${SQ} and now points to ${SQ}main${SQ}\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n+test_expect_success 'set-head --auto detects no change' '\n+\t(\n+\t\tcd test &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"${SQ}origin/HEAD${SQ} is unchanged and points to ${SQ}main${SQ}\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n+test_expect_success 'set-head --auto detects change' '\n+\t(\n+\t\tcd test &&\n+\t\tgit symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/ahead &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"${SQ}origin/HEAD${SQ} has changed from ${SQ}ahead${SQ} and now points to ${SQ}main${SQ}\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n+test_expect_success 'set-head --auto detects strange ref' '\n+\t(\n+\t\tcd test &&\n+\t\tgit symbolic-ref refs/remotes/origin/HEAD refs/heads/main &&\n+\t\tgit remote set-head --auto origin >output &&\n+\t\techo \"${SQ}origin/HEAD${SQ} used to point to ${SQ}refs/heads/main${SQ} (which is not a remote branch), but now points to ${SQ}main${SQ}\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n test_expect_success 'set-head --auto has no problem w/multiple HEADs' '\n \t(\n \t\tcd test &&\n \t\tgit fetch two \"refs/heads/*:refs/remotes/two/*\" &&\n \t\tgit remote set-head --auto two >output 2>&1 &&\n-\t\techo \"two/HEAD set to main\" >expect &&\n+\t\techo \"${SQ}two/HEAD${SQ} is now created and points to ${SQ}main${SQ}\" >expect &&\n \t\ttest_cmp expect output\n \t)\n '\n@@ -468,6 +519,16 @@ test_expect_success 'set-head explicit' '\n \t)\n '\n \n+test_expect_success 'set-head --auto reports change' '\n+\t(\n+\t\tcd test &&\n+\t\tgit remote set-head origin side2 &&\n+\t\tgit remote set-head --auto origin >output 2>&1 &&\n+\t\techo \"${SQ}origin/HEAD${SQ} has changed from ${SQ}side2${SQ} and now points to ${SQ}main${SQ}\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n cat >test/expect <<EOF\n Pruning origin\n URL: $(pwd)/one\n-- \n2.47.0.298.g52a96ec17b\n\n"},{"id":"507925","messageId":"20241122123138.66960-9-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241122123138.66960-1-bence@ferdinandy.com","subject":"[PATCH v15 08/10] refs: add create_only option to refs_update_symref_extended","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-22T12:28:49Z","receivedAt":"2024-11-22T12:33:05Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Allow the caller to specify that it only wants to update the symref if\nit does not already exist. Silently ignore the error from the\ntransaction API if the symref already exists.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v4: new patch\n    v5: no change\n    \n    v6: - switched from bool to int for create_only\n        - refactored logic in refs_update_symref with goto as layed out by\n          Junio\n    \n    v7: - change commit prefix to be more in line with project standards\n        - refactored code to accommodate changes in the first patch, but\n          otherwise no change\n    \n    v8: no change\n    \n    v9: - no change (except for following through the\n          refs_update_symref_extended refactoring)\n    \n    v10: no change\n    \n    v11: no change\n    \n    v12: no change\n    \n    v13: changes only due to changes in previous patch\n    \n    v14: no change\n    \n    v15: no change\n\n builtin/remote.c |  2 +-\n refs.c           | 33 ++++++++++++++++++++++++---------\n refs.h           |  2 +-\n 3 files changed, 26 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex e1f15e68f4..4a8b2ef678 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1476,7 +1476,7 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\tgoto cleanup;\n \t}\n \twas_detached = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n-\t\t\t\"remote set-head\", &b_local_head);\n+\t\t\t\"remote set-head\", &b_local_head, 0);\n \tif (was_detached == -1) {\n \t\tresult |= error(_(\"Could not set up %s\"), b_head.buf);\n \t\tgoto cleanup;\ndiff --git a/refs.c b/refs.c\nindex d80efd58f0..2efa6bcc5c 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2116,26 +2116,38 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct\n int refs_update_symref(struct ref_store *refs, const char *ref,\n \t\t       const char *target, const char *logmsg)\n {\n-\treturn refs_update_symref_extended(refs, ref, target, logmsg, NULL);\n+\treturn refs_update_symref_extended(refs, ref, target, logmsg, NULL, 0);\n }\n \n int refs_update_symref_extended(struct ref_store *refs, const char *ref,\n \t\t       const char *target, const char *logmsg,\n-\t\t       struct strbuf *referent)\n+\t\t       struct strbuf *referent, int create_only)\n {\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n-\tint ret = 0;\n+\tint ret = 0, prepret = 0;\n \n \ttransaction = ref_store_transaction_begin(refs, &err);\n-\tif (!transaction ||\n-\t    ref_transaction_update(transaction, ref, NULL, NULL,\n-\t\t\t\t   target, NULL, REF_NO_DEREF,\n-\t\t\t\t   logmsg, &err) ||\n-\t    ref_transaction_prepare(transaction, &err)) {\n+\tif (!transaction) {\n+\terror_return:\n \t\tret = error(\"%s\", err.buf);\n \t\tgoto cleanup;\n \t}\n+\tif (create_only) {\n+\t\tif (ref_transaction_create(transaction, ref, NULL, target,\n+\t\t\t\t\t   REF_NO_DEREF, logmsg, &err))\n+\t\t\tgoto error_return;\n+\t\tprepret = ref_transaction_prepare(transaction, &err);\n+\t\tif (prepret && prepret != TRANSACTION_CREATE_EXISTS)\n+\t\t\tgoto error_return;\n+\t} else {\n+\t\tif (ref_transaction_update(transaction, ref, NULL, NULL,\n+\t\t\t\t\t   target, NULL, REF_NO_DEREF,\n+\t\t\t\t\t   logmsg, &err) ||\n+\t\t\tref_transaction_prepare(transaction, &err))\n+\t\t\tgoto error_return;\n+\t}\n+\n \tif (referent && refs_read_symbolic_ref(refs, ref, referent) == NOT_A_SYMREF) {\n \t\tstruct object_id oid;\n \t\tif (!refs_read_ref(refs, ref, &oid)) {\n@@ -2144,8 +2156,11 @@ int refs_update_symref_extended(struct ref_store *refs, const char *ref,\n \t\t}\n \t}\n \n+\tif (prepret == TRANSACTION_CREATE_EXISTS)\n+\t\tgoto cleanup;\n+\n \tif (ref_transaction_commit(transaction, &err))\n-\t\tret = error(\"%s\", err.buf);\n+\t\tgoto error_return;\n \n cleanup:\n \tstrbuf_release(&err);\ndiff --git a/refs.h b/refs.h\nindex b243739e4b..be38377b1f 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -586,7 +586,7 @@ int refs_update_symref(struct ref_store *refs, const char *refname,\n \n int refs_update_symref_extended(struct ref_store *refs, const char *refname,\n \t\t       const char *target, const char *logmsg,\n-\t\t       struct strbuf *referent);\n+\t\t       struct strbuf *referent, int create_only);\n \n enum action_on_err {\n \tUPDATE_REFS_MSG_ON_ERR,\n-- \n2.47.0.298.g52a96ec17b\n\n"},{"id":"507926","messageId":"20241122123138.66960-11-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241122123138.66960-1-bence@ferdinandy.com","subject":"[PATCH v15 10/10] fetch set_head: handle mirrored bare repositories","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-22T12:28:51Z","receivedAt":"2024-11-22T12:33:16Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"When adding a remote to bare repository with \"git remote add --mirror\",\nrunning fetch will fail to update HEAD to the remote's HEAD, since it\ndoes not know how to handle bare repositories. On the other hand HEAD\nalready has content, since \"git init --bare\" has already set HEAD to\nwhatever is the default branch set for the user. Unless this - by chance\n- is the same as the remote's HEAD, HEAD will be pointing to a bad\nsymref. Teach set_head to handle bare repositories, by overwriting HEAD\nso it mirrors the remote's HEAD.\n\nNote, that in this case overriding the local HEAD reference is\nnecessary, since HEAD will exist before fetch can be run, but this\nshould not be an issue, since the whole purpose of --mirror is to be an\nexact mirror of the remote, so following any changes to HEAD makes\nsense.\n\nAlso note, that although \"git remote set-head\" also fails when trying to\nupdate the remote's locally tracked HEAD in a mirrored bare repository,\nthe usage of the command does not make much sense after this patch:\nfetch will update the remote HEAD correctly, and setting it manually to\nsomething else is antithetical to the concept of mirroring.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    state:\n        * last review: none\n    \n    v10: - new patch\n         - handles the issue discovered in\n           https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/T/\n    \n    v11: no change\n    \n    v12: no change\n    \n    v13: properly print the previous head if it was detached\n    \n    v14: no change\n    \n    v15: no change\n\n builtin/fetch.c   | 16 +++++++++++-----\n t/t5505-remote.sh | 10 ++++++++++\n 2 files changed, 21 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex bbfaf50b63..2f416cf867 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1581,7 +1581,7 @@ static const char *strip_refshead(const char *name){\n \n static int set_head(const struct ref *remote_refs)\n {\n-\tint result = 0;\n+\tint result = 0, is_bare;\n \tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;\n \tconst char *remote = gtransport->remote->name;\n \tchar *head_name = NULL;\n@@ -1613,15 +1613,21 @@ static int set_head(const struct ref *remote_refs)\n \n \tif (!head_name)\n \t\tgoto cleanup;\n-\tstrbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", remote);\n-\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", remote, head_name);\n+\tis_bare = is_bare_repository();\n+\tif (is_bare) {\n+\t\tstrbuf_addstr(&b_head, \"HEAD\");\n+\t\tstrbuf_addf(&b_remote_head, \"refs/heads/%s\", head_name);\n+\t} else {\n+\t\tstrbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", remote);\n+\t\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", remote, head_name);\n+\t}\n \t\t/* make sure it's valid */\n-\tif (!refs_ref_exists(refs, b_remote_head.buf)) {\n+\tif (!is_bare && !refs_ref_exists(refs, b_remote_head.buf)) {\n \t\tresult = 1;\n \t\tgoto cleanup;\n \t}\n \tif (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n-\t\t\t\t\t\"fetch\", NULL, 1))\n+\t\t\t\t\t\"fetch\", NULL, !is_bare))\n \t\tresult = 1;\n \n cleanup:\ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex afa261409f..2600add82a 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -569,6 +569,16 @@ test_expect_success 'add --mirror && prune' '\n \t)\n '\n \n+test_expect_success 'add --mirror setting HEAD' '\n+\tmkdir headmirror &&\n+\t(\n+\t\tcd headmirror &&\n+\t\tgit init --bare -b notmain &&\n+\t\tgit remote add --mirror -f origin ../one &&\n+\t\ttest \"$(git symbolic-ref HEAD)\" = \"refs/heads/main\"\n+\t)\n+'\n+\n test_expect_success 'add --mirror=fetch' '\n \tmkdir mirror-fetch &&\n \tgit init -b main mirror-fetch/parent &&\n-- \n2.47.0.298.g52a96ec17b\n\n"},{"id":"507927","messageId":"20241122123138.66960-10-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241122123138.66960-1-bence@ferdinandy.com","subject":"[PATCH v15 09/10] fetch: set remote/HEAD if it does not exist","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-22T12:28:50Z","receivedAt":"2024-11-22T12:33:22Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"When cloning a repository remote/HEAD is created, but when the user\ncreates a repository with git init, and later adds a remote, remote/HEAD\nis only created if the user explicitly runs a variant of \"remote\nset-head\". Attempt to set remote/HEAD during fetch, if the user does not\nhave it already set. Silently ignore any errors.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v3: - does not rely on remote set-head anymore so it only authenticates\n        once\n        - uses the new REF_CREATE_ONLY to atomically check if the ref exists\n          and only write it if it doesn't\n        - in all other cases the maximum it does is print a warning\n    \n    v4: - instead of the discarded REF_CREATE_ONLY, it uses the existing,\n          but updated transaction api to request a silent create only\n        - it now uses the atomic before_target to determine reporting\n        - refactored for legibility\n    \n    v5: - instead of printing a not too useful message, it now fails\n          silently, this in line with the objective to only set up\n          remote/HEAD automatically if the right thing is trivial, for\n          everything else there is remote set-head\n        - fixed all failing tests\n        - added two new tests, one for checking if remote/HEAD is set to the\n          correct one, and one to test that we do not override remote/HEAD\n          if it has changed on the server from what we have locally\n    \n    v6: - fixed style issues and unintended extra empty line\n        - updated function call with bool to int from previous patch's\n          change\n        - removed calls to error(...) inherited from builtin/remote.c so we\n          actually fail silently\n        - set the test for remote set-head --auto to the correct value here,\n          which was previously erronously set in the remote set-head patch\n    \n    v7: - no change\n    \n    v8: - changed logmsg in call to refs_update_symref from \"remote\n          set-head\" to \"fetch\"\n    \n    v9: - follow through with refs_update_symref_extended\n        - fix test errors uncovered by the new patch\n    \n    v10: no change\n    \n    v11: fixed some memory leaks\n    \n    v12: no change\n    \n    v13: - fix printed information if the local HEAD is detached\n         - remove accidental formatting noise in a test\n    \n    v14: remove report_set_head_auto: as discussed, any noise should only be\n    created with an opt out, and the configuration for this will be added in\n    a later patch\n    \n    v15: no change\n\n builtin/fetch.c                  | 68 ++++++++++++++++++++++++++++++++\n t/t4207-log-decoration-colors.sh |  3 +-\n t/t5505-remote.sh                | 21 +++++++---\n t/t5510-fetch.sh                 | 24 +++++++++++\n t/t5512-ls-remote.sh             |  2 +\n t/t5514-fetch-multiple.sh        | 17 +++++++-\n t/t5516-fetch-push.sh            |  3 +-\n t/t5527-fetch-odd-refs.sh        |  3 +-\n t/t7900-maintenance.sh           |  3 +-\n t/t9210-scalar.sh                |  5 ++-\n t/t9211-scalar-clone.sh          |  6 +--\n t/t9902-completion.sh            | 65 ++++++++++++++++++++++++++++++\n 12 files changed, 203 insertions(+), 17 deletions(-)\n\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 06b4611958..bbfaf50b63 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1574,6 +1574,66 @@ static int backfill_tags(struct display_state *display_state,\n \treturn retcode;\n }\n \n+static const char *strip_refshead(const char *name){\n+\tskip_prefix(name, \"refs/heads/\", &name);\n+\treturn name;\n+}\n+\n+static int set_head(const struct ref *remote_refs)\n+{\n+\tint result = 0;\n+\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;\n+\tconst char *remote = gtransport->remote->name;\n+\tchar *head_name = NULL;\n+\tstruct ref *ref, *matches;\n+\tstruct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;\n+\tstruct refspec_item refspec = {\n+\t\t.force = 0,\n+\t\t.pattern = 1,\n+\t\t.src = (char *) \"refs/heads/*\",\n+\t\t.dst = (char *) \"refs/heads/*\",\n+\t};\n+\tstruct string_list heads = STRING_LIST_INIT_DUP;\n+\tstruct ref_store *refs = get_main_ref_store(the_repository);\n+\n+\tget_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);\n+\tmatches = guess_remote_head(find_ref_by_name(remote_refs, \"HEAD\"),\n+\t\t\t\t    fetch_map, 1);\n+\tfor (ref = matches; ref; ref = ref->next) {\n+\t\tstring_list_append(&heads, strip_refshead(ref->name));\n+\t}\n+\n+\n+\tif (!heads.nr)\n+\t\tresult = 1;\n+\telse if (heads.nr > 1)\n+\t\tresult = 1;\n+\telse\n+\t\thead_name = xstrdup(heads.items[0].string);\n+\n+\tif (!head_name)\n+\t\tgoto cleanup;\n+\tstrbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", remote);\n+\tstrbuf_addf(&b_remote_head, \"refs/remotes/%s/%s\", remote, head_name);\n+\t\t/* make sure it's valid */\n+\tif (!refs_ref_exists(refs, b_remote_head.buf)) {\n+\t\tresult = 1;\n+\t\tgoto cleanup;\n+\t}\n+\tif (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n+\t\t\t\t\t\"fetch\", NULL, 1))\n+\t\tresult = 1;\n+\n+cleanup:\n+\tfree(head_name);\n+\tfree_refs(fetch_map);\n+\tfree_refs(matches);\n+\tstring_list_clear(&heads, 0);\n+\tstrbuf_release(&b_head);\n+\tstrbuf_release(&b_remote_head);\n+\treturn result;\n+}\n+\n static int do_fetch(struct transport *transport,\n \t\t    struct refspec *rs,\n \t\t    const struct fetch_config *config)\n@@ -1643,6 +1703,8 @@ static int do_fetch(struct transport *transport,\n \t\t\t\t    \"refs/tags/\");\n \t}\n \n+\tstrvec_push(&transport_ls_refs_options.ref_prefixes, \"HEAD\");\n+\n \tif (must_list_refs) {\n \t\ttrace2_region_enter(\"fetch\", \"remote_refs\", the_repository);\n \t\tremote_refs = transport_get_remote_refs(transport,\n@@ -1787,6 +1849,12 @@ static int do_fetch(struct transport *transport,\n \t\t\t\t  \"you need to specify exactly one branch with the --set-upstream option\"));\n \t\t}\n \t}\n+\tif (set_head(remote_refs))\n+\t\t;\n+\t\t/*\n+\t\t * Way too many cases where this can go wrong\n+\t\t * so let's just fail silently for now.\n+\t\t */\n \n cleanup:\n \tif (retcode) {\ndiff --git a/t/t4207-log-decoration-colors.sh b/t/t4207-log-decoration-colors.sh\nindex 73ea9e5155..d55d22cb2f 100755\n--- a/t/t4207-log-decoration-colors.sh\n+++ b/t/t4207-log-decoration-colors.sh\n@@ -59,7 +59,8 @@ ${c_reset}${c_tag}tag: ${c_reset}${c_tag}v1.0${c_reset}${c_commit}, \\\n ${c_reset}${c_tag}tag: ${c_reset}${c_tag}B${c_reset}${c_commit})${c_reset} B\n ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\n ${c_tag}tag: ${c_reset}${c_tag}A1${c_reset}${c_commit}, \\\n-${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit})${c_reset} A1\n+${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit}, \\\n+${c_reset}${c_remoteBranch}other/HEAD${c_reset}${c_commit})${c_reset} A1\n \t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\n ${c_stash}refs/stash${c_reset}${c_commit})${c_reset} On main: Changes to A.t\n \t${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}\\\ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex d15b579c95..afa261409f 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -74,7 +74,7 @@ test_expect_success 'add another remote' '\n \t\tcd test &&\n \t\tgit remote add -f second ../two &&\n \t\ttokens_match \"origin second\" \"$(git remote)\" &&\n-\t\tcheck_tracking_branch second main side another &&\n+\t\tcheck_tracking_branch second main side another HEAD &&\n \t\tgit for-each-ref \"--format=%(refname)\" refs/remotes |\n \t\tsed -e \"/^refs\\/remotes\\/origin\\//d\" \\\n \t\t    -e \"/^refs\\/remotes\\/second\\//d\" >actual &&\n@@ -500,7 +500,7 @@ test_expect_success 'set-head --auto has no problem w/multiple HEADs' '\n \t\tcd test &&\n \t\tgit fetch two \"refs/heads/*:refs/remotes/two/*\" &&\n \t\tgit remote set-head --auto two >output 2>&1 &&\n-\t\techo \"${SQ}two/HEAD${SQ} is now created and points to ${SQ}main${SQ}\" >expect &&\n+\t\techo \"${SQ}two/HEAD${SQ} is unchanged and points to ${SQ}main${SQ}\" >expect &&\n \t\ttest_cmp expect output\n \t)\n '\n@@ -788,8 +788,10 @@ test_expect_success 'reject --no-no-tags' '\n '\n \n cat >one/expect <<\\EOF\n+  apis/HEAD -> apis/main\n   apis/main\n   apis/side\n+  drosophila/HEAD -> drosophila/main\n   drosophila/another\n   drosophila/main\n   drosophila/side\n@@ -807,11 +809,14 @@ test_expect_success 'update' '\n '\n \n cat >one/expect <<\\EOF\n+  drosophila/HEAD -> drosophila/main\n   drosophila/another\n   drosophila/main\n   drosophila/side\n+  manduca/HEAD -> manduca/main\n   manduca/main\n   manduca/side\n+  megaloprepus/HEAD -> megaloprepus/main\n   megaloprepus/main\n   megaloprepus/side\n EOF\n@@ -819,7 +824,7 @@ EOF\n test_expect_success 'update with arguments' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\n@@ -851,10 +856,13 @@ test_expect_success 'update --prune' '\n '\n \n cat >one/expect <<-\\EOF\n+  apis/HEAD -> apis/main\n   apis/main\n   apis/side\n+  manduca/HEAD -> manduca/main\n   manduca/main\n   manduca/side\n+  megaloprepus/HEAD -> megaloprepus/main\n   megaloprepus/main\n   megaloprepus/side\n EOF\n@@ -862,7 +870,7 @@ EOF\n test_expect_success 'update default' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\n@@ -874,6 +882,7 @@ test_expect_success 'update default' '\n '\n \n cat >one/expect <<\\EOF\n+  drosophila/HEAD -> drosophila/main\n   drosophila/another\n   drosophila/main\n   drosophila/side\n@@ -882,7 +891,7 @@ EOF\n test_expect_success 'update default (overridden, with funny whitespace)' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\n@@ -896,7 +905,7 @@ test_expect_success 'update default (overridden, with funny whitespace)' '\n test_expect_success 'update (with remotes.default defined)' '\n \t(\n \t\tcd one &&\n-\t\tfor b in $(git branch -r)\n+\t\tfor b in $(git branch -r | grep -v HEAD)\n \t\tdo\n \t\tgit branch -r -d $b || exit 1\n \t\tdone &&\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex 0890b9f61c..87698341f5 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -75,6 +75,30 @@ test_expect_success \"fetch test for-merge\" '\n \tcut -f -2 .git/FETCH_HEAD >actual &&\n \ttest_cmp expected actual'\n \n+test_expect_success \"fetch test remote HEAD\" '\n+\tcd \"$D\" &&\n+\tcd two &&\n+\tgit fetch &&\n+\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\tgit rev-parse --verify refs/remotes/origin/main &&\n+\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\tbranch=$(git rev-parse refs/remotes/origin/main) &&\n+\ttest \"z$head\" = \"z$branch\"'\n+\n+test_expect_success \"fetch test remote HEAD change\" '\n+\tcd \"$D\" &&\n+\tcd two &&\n+\tgit switch -c other &&\n+\tgit push -u origin other &&\n+\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\tgit rev-parse --verify refs/remotes/origin/main &&\n+\tgit rev-parse --verify refs/remotes/origin/other &&\n+\tgit remote set-head origin other &&\n+\tgit fetch &&\n+\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\tbranch=$(git rev-parse refs/remotes/origin/other) &&\n+\ttest \"z$head\" = \"z$branch\"'\n+\n test_expect_success 'fetch --prune on its own works as expected' '\n \tcd \"$D\" &&\n \tgit clone . prune &&\ndiff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh\nindex 64b3491e4e..1b3865e154 100755\n--- a/t/t5512-ls-remote.sh\n+++ b/t/t5512-ls-remote.sh\n@@ -293,6 +293,8 @@ test_expect_success 'ls-remote with filtered symref (refname)' '\n \tcat >expect <<-EOF &&\n \tref: refs/heads/main\tHEAD\n \t$rev\tHEAD\n+\tref: refs/remotes/origin/main\trefs/remotes/origin/HEAD\n+\t$rev\trefs/remotes/origin/HEAD\n \tEOF\n \tgit ls-remote --symref . HEAD >actual &&\n \ttest_cmp expect actual\ndiff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh\nindex 579872c258..e3482b27b2 100755\n--- a/t/t5514-fetch-multiple.sh\n+++ b/t/t5514-fetch-multiple.sh\n@@ -45,14 +45,17 @@ test_expect_success setup '\n '\n \n cat > test/expect << EOF\n+  one/HEAD -> one/main\n   one/main\n   one/side\n   origin/HEAD -> origin/main\n   origin/main\n   origin/side\n+  three/HEAD -> three/main\n   three/another\n   three/main\n   three/side\n+  two/HEAD -> two/main\n   two/another\n   two/main\n   two/side\n@@ -97,6 +100,7 @@ cat > expect << EOF\n   origin/HEAD -> origin/main\n   origin/main\n   origin/side\n+  three/HEAD -> three/main\n   three/another\n   three/main\n   three/side\n@@ -112,8 +116,10 @@ test_expect_success 'git fetch --multiple (but only one remote)' '\n '\n \n cat > expect << EOF\n+  one/HEAD -> one/main\n   one/main\n   one/side\n+  two/HEAD -> two/main\n   two/another\n   two/main\n   two/side\n@@ -141,7 +147,7 @@ test_expect_success 'git fetch --multiple (bad remote names)' '\n \n test_expect_success 'git fetch --all (skipFetchAll)' '\n \t(cd test4 &&\n-\t for b in $(git branch -r)\n+\t for b in $(git branch -r | grep -v HEAD)\n \t do\n \t\tgit branch -r -d $b || exit 1\n \t done &&\n@@ -153,11 +159,14 @@ test_expect_success 'git fetch --all (skipFetchAll)' '\n '\n \n cat > expect << EOF\n+  one/HEAD -> one/main\n   one/main\n   one/side\n+  three/HEAD -> three/main\n   three/another\n   three/main\n   three/side\n+  two/HEAD -> two/main\n   two/another\n   two/main\n   two/side\n@@ -165,7 +174,7 @@ EOF\n \n test_expect_success 'git fetch --multiple (ignoring skipFetchAll)' '\n \t(cd test4 &&\n-\t for b in $(git branch -r)\n+\t for b in $(git branch -r | grep -v HEAD)\n \t do\n \t\tgit branch -r -d $b || exit 1\n \t done &&\n@@ -221,14 +230,17 @@ test_expect_success 'git fetch --multiple --jobs=0 picks a default' '\n \n create_fetch_all_expect () {\n \tcat >expect <<-\\EOF\n+\t  one/HEAD -> one/main\n \t  one/main\n \t  one/side\n \t  origin/HEAD -> origin/main\n \t  origin/main\n \t  origin/side\n+\t  three/HEAD -> three/main\n \t  three/another\n \t  three/main\n \t  three/side\n+\t  two/HEAD -> two/main\n \t  two/another\n \t  two/main\n \t  two/side\n@@ -265,6 +277,7 @@ test_expect_success 'git fetch (fetch all remotes with fetch.all = true)' '\n \n create_fetch_one_expect () {\n \tcat >expect <<-\\EOF\n+\t  one/HEAD -> one/main\n \t  one/main\n \t  one/side\n \t  origin/HEAD -> origin/main\ndiff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh\nindex 331778bd42..5a051aa0c7 100755\n--- a/t/t5516-fetch-push.sh\n+++ b/t/t5516-fetch-push.sh\n@@ -1395,7 +1395,8 @@ test_expect_success 'fetch follows tags by default' '\n \t\tgit tag -m \"annotated\" tag &&\n \t\tgit for-each-ref >tmp1 &&\n \t\tsed -n \"p; s|refs/heads/main$|refs/remotes/origin/main|p\" tmp1 |\n-\t\tsort -k 3 >../expect\n+\t\tsed -n \"p; s|refs/heads/main$|refs/remotes/origin/HEAD|p\"  |\n+\t\tsort -k 4 >../expect\n \t) &&\n \ttest_when_finished \"rm -rf dst\" &&\n \tgit init dst &&\ndiff --git a/t/t5527-fetch-odd-refs.sh b/t/t5527-fetch-odd-refs.sh\nindex 98ece27c6a..d3996af6ee 100755\n--- a/t/t5527-fetch-odd-refs.sh\n+++ b/t/t5527-fetch-odd-refs.sh\n@@ -52,7 +52,8 @@ test_expect_success LONG_REF 'fetch handles extremely long refname' '\n \tlong\n \tmain\n \tEOF\n-\tgit for-each-ref --format=\"%(subject)\" refs/remotes/long >actual &&\n+\tgit for-each-ref --format=\"%(subject)\" refs/remotes/long \\\n+\t\t--exclude=refs/remotes/long/HEAD >actual &&\n \ttest_cmp expect actual\n '\n \ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex c224c8450c..edb85b7145 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -329,7 +329,8 @@ test_expect_success 'incremental-repack task' '\n \n \t# Delete refs that have not been repacked in these packs.\n \tgit for-each-ref --format=\"delete %(refname)\" \\\n-\t\trefs/prefetch refs/tags refs/remotes >refs &&\n+\t\trefs/prefetch refs/tags refs/remotes \\\n+\t\t--exclude=refs/remotes/*/HEAD >refs &&\n \tgit update-ref --stdin <refs &&\n \n \t# Replace the object directory with this pack layout.\ndiff --git a/t/t9210-scalar.sh b/t/t9210-scalar.sh\nindex a30b2c9f70..2237844550 100755\n--- a/t/t9210-scalar.sh\n+++ b/t/t9210-scalar.sh\n@@ -151,7 +151,8 @@ test_expect_success 'scalar clone' '\n \t\t\t\"$(pwd)\" &&\n \n \t\tgit for-each-ref --format=\"%(refname)\" refs/remotes/origin/ >actual &&\n-\t\techo \"refs/remotes/origin/parallel\" >expect &&\n+\t\techo \"refs/remotes/origin/HEAD\" >>expect &&\n+\t\techo \"refs/remotes/origin/parallel\" >>expect &&\n \t\ttest_cmp expect actual &&\n \n \t\ttest_path_is_missing 1/2 &&\n@@ -220,7 +221,7 @@ test_expect_success 'scalar reconfigure --all with includeIf.onbranch' '\n \tdone\n '\n \n- test_expect_success 'scalar reconfigure --all with detached HEADs' '\n+test_expect_success 'scalar reconfigure --all with detached HEADs' '\n \trepos=\"two three four\" &&\n \tfor num in $repos\n \tdo\ndiff --git a/t/t9211-scalar-clone.sh b/t/t9211-scalar-clone.sh\nindex c16ea67c1d..d9cb6b9a3e 100755\n--- a/t/t9211-scalar-clone.sh\n+++ b/t/t9211-scalar-clone.sh\n@@ -32,7 +32,7 @@ test_expect_success 'set up repository to clone' '\n \t)\n '\n \n-cleanup_clone () {\n+cleanup_clone() {\n \trm -rf \"$1\"\n }\n \n@@ -128,7 +128,7 @@ test_expect_success '--single-branch clones HEAD only' '\n \t(\n \t\tcd $enlistment/src &&\n \t\tgit for-each-ref refs/remotes/origin >out &&\n-\t\ttest_line_count = 1 out &&\n+\t\ttest_line_count = 2 out &&\n \t\tgrep \"refs/remotes/origin/base\" out\n \t) &&\n \n@@ -142,7 +142,7 @@ test_expect_success '--no-single-branch clones all branches' '\n \t(\n \t\tcd $enlistment/src &&\n \t\tgit for-each-ref refs/remotes/origin >out &&\n-\t\ttest_line_count = 2 out &&\n+\t\ttest_line_count = 3 out &&\n \t\tgrep \"refs/remotes/origin/base\" out &&\n \t\tgrep \"refs/remotes/origin/parallel\" out\n \t) &&\ndiff --git a/t/t9902-completion.sh b/t/t9902-completion.sh\nindex cc6aa9f0cd..b663c4609e 100755\n--- a/t/t9902-completion.sh\n+++ b/t/t9902-completion.sh\n@@ -658,6 +658,7 @@ test_expect_success '__git_refs - simple' '\n \tHEAD\n \tmain\n \tmatching-branch\n+\tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n \tmatching-tag\n@@ -673,6 +674,7 @@ test_expect_success '__git_refs - full refs' '\n \tcat >expected <<-EOF &&\n \trefs/heads/main\n \trefs/heads/matching-branch\n+\trefs/remotes/other/HEAD\n \trefs/remotes/other/branch-in-other\n \trefs/remotes/other/main-in-other\n \trefs/tags/matching-tag\n@@ -729,6 +731,7 @@ test_expect_success '__git_refs - remote on local file system - full refs' '\n test_expect_success '__git_refs - configured remote' '\n \tcat >expected <<-EOF &&\n \tHEAD\n+\tHEAD\n \tbranch-in-other\n \tmain-in-other\n \tEOF\n@@ -756,6 +759,7 @@ test_expect_success '__git_refs - configured remote - full refs' '\n test_expect_success '__git_refs - configured remote - repo given on the command line' '\n \tcat >expected <<-EOF &&\n \tHEAD\n+\tHEAD\n \tbranch-in-other\n \tmain-in-other\n \tEOF\n@@ -787,6 +791,7 @@ test_expect_success '__git_refs - configured remote - full refs - repo given on\n test_expect_success '__git_refs - configured remote - remote name matches a directory' '\n \tcat >expected <<-EOF &&\n \tHEAD\n+\tHEAD\n \tbranch-in-other\n \tmain-in-other\n \tEOF\n@@ -875,12 +880,14 @@ test_expect_success '__git_refs - unique remote branches for git checkout DWIMer\n \tHEAD\n \tmain\n \tmatching-branch\n+\tother/HEAD\n \tother/ambiguous\n \tother/branch-in-other\n \tother/main-in-other\n \tremote/ambiguous\n \tremote/branch-in-remote\n \tmatching-tag\n+\tHEAD\n \tbranch-in-other\n \tbranch-in-remote\n \tmain-in-other\n@@ -904,6 +911,7 @@ test_expect_success '__git_refs - after --opt=' '\n \tHEAD\n \tmain\n \tmatching-branch\n+\tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n \tmatching-tag\n@@ -919,6 +927,7 @@ test_expect_success '__git_refs - after --opt= - full refs' '\n \tcat >expected <<-EOF &&\n \trefs/heads/main\n \trefs/heads/matching-branch\n+\trefs/remotes/other/HEAD\n \trefs/remotes/other/branch-in-other\n \trefs/remotes/other/main-in-other\n \trefs/tags/matching-tag\n@@ -935,6 +944,7 @@ test_expect_success '__git refs - excluding refs' '\n \t^HEAD\n \t^main\n \t^matching-branch\n+\t^other/HEAD\n \t^other/branch-in-other\n \t^other/main-in-other\n \t^matching-tag\n@@ -950,6 +960,7 @@ test_expect_success '__git refs - excluding full refs' '\n \tcat >expected <<-EOF &&\n \t^refs/heads/main\n \t^refs/heads/matching-branch\n+\t^refs/remotes/other/HEAD\n \t^refs/remotes/other/branch-in-other\n \t^refs/remotes/other/main-in-other\n \t^refs/tags/matching-tag\n@@ -975,6 +986,7 @@ test_expect_success '__git_refs - do not filter refs unless told so' '\n \tmain\n \tmatching-branch\n \tmatching/branch\n+\tother/HEAD\n \tother/branch-in-other\n \tother/main-in-other\n \tother/matching/branch-in-other\n@@ -1095,6 +1107,7 @@ test_expect_success '__git_complete_refs - simple' '\n \tHEAD Z\n \tmain Z\n \tmatching-branch Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tmatching-tag Z\n@@ -1123,6 +1136,7 @@ test_expect_success '__git_complete_refs - matching' '\n test_expect_success '__git_complete_refs - remote' '\n \tsed -e \"s/Z$//\" >expected <<-EOF &&\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain-in-other Z\n \tEOF\n@@ -1139,9 +1153,11 @@ test_expect_success '__git_complete_refs - track' '\n \tHEAD Z\n \tmain Z\n \tmatching-branch Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tmatching-tag Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain-in-other Z\n \tEOF\n@@ -1184,6 +1200,7 @@ test_expect_success '__git_complete_refs - suffix' '\n \tHEAD.\n \tmain.\n \tmatching-branch.\n+\tother/HEAD.\n \tother/branch-in-other.\n \tother/main-in-other.\n \tmatching-tag.\n@@ -1199,6 +1216,7 @@ test_expect_success '__git_complete_refs - suffix' '\n test_expect_success '__git_complete_fetch_refspecs - simple' '\n \tsed -e \"s/Z$//\" >expected <<-EOF &&\n \tHEAD:HEAD Z\n+\tHEAD:HEAD Z\n \tbranch-in-other:branch-in-other Z\n \tmain-in-other:main-in-other Z\n \tEOF\n@@ -1225,6 +1243,7 @@ test_expect_success '__git_complete_fetch_refspecs - matching' '\n test_expect_success '__git_complete_fetch_refspecs - prefix' '\n \tsed -e \"s/Z$//\" >expected <<-EOF &&\n \t+HEAD:HEAD Z\n+\t+HEAD:HEAD Z\n \t+branch-in-other:branch-in-other Z\n \t+main-in-other:main-in-other Z\n \tEOF\n@@ -1289,6 +1308,7 @@ test_expect_success '__git_complete_worktree_paths with -C' '\n \n test_expect_success 'git switch - with no options, complete local branches and unique remote branch names for DWIM logic' '\n \ttest_completion \"git switch \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1435,11 +1455,13 @@ test_expect_success 'git-bisect - existing view subcommand is recognized and ena\n test_expect_success 'git checkout - completes refs and unique remote branches for DWIM' '\n \ttest_completion \"git checkout \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1461,6 +1483,7 @@ test_expect_success 'git switch - with GIT_COMPLETION_CHECKOUT_NO_GUESS=1, compl\n \n test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete local branches and unique remote names for DWIM logic' '\n \tGIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git switch --guess \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1470,6 +1493,7 @@ test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_G\n \n test_expect_success 'git switch - a later --guess overrides previous --no-guess, complete local and remote unique branches for DWIM' '\n \ttest_completion \"git switch --no-guess --guess \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1490,6 +1514,7 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1498,11 +1523,13 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete\n test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1, complete refs and unique remote branches for DWIM' '\n \tGIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion \"git checkout --guess \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1514,6 +1541,7 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1522,11 +1550,13 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' '\n test_expect_success 'git checkout - a later --guess overrides previous --no-guess, complete refs and unique remote branches for DWIM' '\n \ttest_completion \"git checkout --no-guess --guess \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1538,6 +1568,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous --gues\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1550,6 +1581,7 @@ test_expect_success 'git checkout - with checkout.guess = false, only completes\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1559,11 +1591,13 @@ test_expect_success 'git checkout - with checkout.guess = true, completes refs a\n \ttest_config checkout.guess true &&\n \ttest_completion \"git checkout \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1573,11 +1607,13 @@ test_expect_success 'git checkout - a later --guess overrides previous checkout.\n \ttest_config checkout.guess false &&\n \ttest_completion \"git checkout --guess \" <<-\\EOF\n \tHEAD Z\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1590,6 +1626,7 @@ test_expect_success 'git checkout - a later --no-guess overrides previous checko\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1601,6 +1638,7 @@ test_expect_success 'git switch - with --detach, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1612,6 +1650,7 @@ test_expect_success 'git checkout - with --detach, complete only references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1783,6 +1822,7 @@ test_expect_success 'git switch - with -d, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1794,6 +1834,7 @@ test_expect_success 'git checkout - with -d, complete only references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1801,10 +1842,12 @@ test_expect_success 'git checkout - with -d, complete only references' '\n \n test_expect_success 'git switch - with --track, complete only remote branches' '\n \ttest_completion \"git switch --track \" <<-\\EOF &&\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n \ttest_completion \"git switch -t \" <<-\\EOF\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1812,10 +1855,12 @@ test_expect_success 'git switch - with --track, complete only remote branches' '\n \n test_expect_success 'git checkout - with --track, complete only remote branches' '\n \ttest_completion \"git checkout --track \" <<-\\EOF &&\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n \ttest_completion \"git checkout -t \" <<-\\EOF\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1834,6 +1879,7 @@ test_expect_success 'git checkout - with --no-track, complete only local referen\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1845,6 +1891,7 @@ test_expect_success 'git switch - with -c, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1856,6 +1903,7 @@ test_expect_success 'git switch - with -C, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1867,6 +1915,7 @@ test_expect_success 'git switch - with -c and --track, complete all references'\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1878,6 +1927,7 @@ test_expect_success 'git switch - with -C and --track, complete all references'\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1889,6 +1939,7 @@ test_expect_success 'git switch - with -c and --no-track, complete all reference\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1900,6 +1951,7 @@ test_expect_success 'git switch - with -C and --no-track, complete all reference\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1911,6 +1963,7 @@ test_expect_success 'git checkout - with -b, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1922,6 +1975,7 @@ test_expect_success 'git checkout - with -B, complete all references' '\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1933,6 +1987,7 @@ test_expect_success 'git checkout - with -b and --track, complete all references\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1944,6 +1999,7 @@ test_expect_success 'git checkout - with -B and --track, complete all references\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1955,6 +2011,7 @@ test_expect_success 'git checkout - with -b and --no-track, complete all referen\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1966,6 +2023,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n@@ -1973,6 +2031,7 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen\n \n test_expect_success 'git switch - for -c, complete local branches and unique remote branches' '\n \ttest_completion \"git switch -c \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -1982,6 +2041,7 @@ test_expect_success 'git switch - for -c, complete local branches and unique rem\n \n test_expect_success 'git switch - for -C, complete local branches and unique remote branches' '\n \ttest_completion \"git switch -C \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2019,6 +2079,7 @@ test_expect_success 'git switch - for -C with --no-track, complete local branche\n \n test_expect_success 'git checkout - for -b, complete local branches and unique remote branches' '\n \ttest_completion \"git checkout -b \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2028,6 +2089,7 @@ test_expect_success 'git checkout - for -b, complete local branches and unique r\n \n test_expect_success 'git checkout - for -B, complete local branches and unique remote branches' '\n \ttest_completion \"git checkout -B \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2065,6 +2127,7 @@ test_expect_success 'git checkout - for -B with --no-track, complete local branc\n \n test_expect_success 'git switch - with --orphan completes local branch names and unique remote branch names' '\n \ttest_completion \"git switch --orphan \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2080,6 +2143,7 @@ test_expect_success 'git switch - --orphan with branch already provided complete\n \n test_expect_success 'git checkout - with --orphan completes local branch names and unique remote branch names' '\n \ttest_completion \"git checkout --orphan \" <<-\\EOF\n+\tHEAD Z\n \tbranch-in-other Z\n \tmain Z\n \tmain-in-other Z\n@@ -2093,6 +2157,7 @@ test_expect_success 'git checkout - --orphan with branch already provided comple\n \tmain Z\n \tmatching-branch Z\n \tmatching-tag Z\n+\tother/HEAD Z\n \tother/branch-in-other Z\n \tother/main-in-other Z\n \tEOF\n-- \n2.47.0.298.g52a96ec17b\n\n"},{"id":"508005","messageId":"xmqqwmgs6mxk.fsf@gitster.g","threadId":"62096","inReplyTo":"D5SPDJZAM5K1.24R4JYB0WNTSF@ferdinandy.com","subject":"Re: [PATCH v14 03/10] refs: standardize output of refs_read_symbolic_ref","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-11-25T02:56:39Z","receivedAt":"2024-11-25T02:56:42Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Bence Ferdinandy\" <bence@ferdinandy.com> writes:\n\n>> At the least you should see `git log`'s output, but if there are issues\n>> they should be shown inline. So when you say 'no output' do you mean you\n>> see absolutely no output?\n>\n> Absolutely no output:\n> \thttps://asciinema.org/a/lsqp4e1bNst6cFWw9M2jX1IqC\n>\n> But I figured out why: the whitespace and the tabs were not mixed on the line,\n> just across lines. As I read it, that is not an error to have tabs on one line\n> and spaces on the next.\n\nOur .gitattribute starts like so:\n\n    * whitespace=!indent,trail,space\n    *.[ch] whitespace=indent,trail,space diff=cpp\n\nso, unless otherwise specified, we frown upon trailing whitespace\nand space before tab and indenting with non tab is permitted, but C\nsource and header files have further care about \"indent\" (short for\n\"indent-with-non-tab\".\n\nSo mixed or not, if you indented with spaces and not tabs, that\nwould be noticed.\n\n> Anyhow that should be now cleared up, thanks. Gotta say, I was expecting to\n> learn about internals doing this, but I also ended up picking up a couple of\n> usage things as well, like --range-diff for format patch and such.\n\nI usually have \"--whitespace=fix\" so if you did \"git log\" on the\ncommits I made out of your patches, it is not surprising if your\n\"log --check\" was silent.\n\nI re-applied your v14 with \"git am -s --whitespace=nowarn\" and here\nis what I saw.\n\ncommit 75a6a3e6597d5f3959eb269122e8c5f4e4baac0e\nAuthor: Bence Ferdinandy <bence@ferdinandy.com>\nDate:   Thu Nov 21 23:55:03 2024 +0100\n\n    refs: standardize output of refs_read_symbolic_ref\n    \n    When the symbolic reference we want to read with refs_read_symbolic_ref\n    is actually not a symbolic reference, the files and the reftable\n    backends return different values (1 and -1 respectively). Standardize\n    the returned values so that 0 is success, -1 is a generic error and -2\n    is that the reference was actually non-symbolic.\n    \n    Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n    Signed-off-by: Junio C Hamano <gitster@pobox.com>\n\nrefs/reftable-backend.c:833: indent with spaces.\n+        if (ret)\nrefs/reftable-backend.c:834: indent with spaces.\n+                ret = -1;\nrefs/reftable-backend.c:835: indent with spaces.\n+        else if (ref.value_type == REFTABLE_REF_SYMREF)\nrefs/reftable-backend.c:837: indent with spaces.\n+        else\nrefs/reftable-backend.c:838: indent with spaces.\n+                ret = NOT_A_SYMREF;\n"},{"id":"508185","messageId":"D5W75LQ6Z44H.3LKJ0OX40QKVQ@ferdinandy.com","threadId":"62096","inReplyTo":"xmqqwmgs6mxk.fsf@gitster.g","subject":"Re: [PATCH v14 03/10] refs: standardize output of refs_read_symbolic_ref","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-26T14:57:27Z","receivedAt":"2024-11-26T14:57:54Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"sorry about the other thread, I saw \"extend whitespace checks\" and I thought\nbased on what Kartik wrote that `log --check` should have caught it, which is\nwhy I thought it might be appropriate there.\n\nOn Mon Nov 25, 2024 at 03:56, Junio C Hamano <gitster@pobox.com> wrote:\n> \"Bence Ferdinandy\" <bence@ferdinandy.com> writes:\n>\n>>> At the least you should see `git log`'s output, but if there are issues\n>>> they should be shown inline. So when you say 'no output' do you mean you\n>>> see absolutely no output?\n>>\n>> Absolutely no output:\n>> \thttps://asciinema.org/a/lsqp4e1bNst6cFWw9M2jX1IqC\n>>\n>> But I figured out why: the whitespace and the tabs were not mixed on the line,\n>> just across lines. As I read it, that is not an error to have tabs on one line\n>> and spaces on the next.\n>\n> Our .gitattribute starts like so:\n>\n>     * whitespace=!indent,trail,space\n>     *.[ch] whitespace=indent,trail,space diff=cpp\n>\n> so, unless otherwise specified, we frown upon trailing whitespace\n> and space before tab and indenting with non tab is permitted, but C\n> source and header files have further care about \"indent\" (short for\n> \"indent-with-non-tab\".\n>\n> So mixed or not, if you indented with spaces and not tabs, that\n> would be noticed.\n\nSo `git log --check --pretty=format:\"---% h% s\"` was _not_ supposed to catch\nthis?\n\nI ran the CI with this patch again:\nhttps://github.com/ferdinandyb/git/actions/runs/12031250376\n\nand it's all green, so wherever this _should_ have been caught: it didn't work.\n\n\n\n>\n>> Anyhow that should be now cleared up, thanks. Gotta say, I was expecting to\n>> learn about internals doing this, but I also ended up picking up a couple of\n>> usage things as well, like --range-diff for format patch and such.\n>\n> I usually have \"--whitespace=fix\" so if you did \"git log\" on the\n> commits I made out of your patches, it is not surprising if your\n> \"log --check\" was silent.\n>\n> I re-applied your v14 with \"git am -s --whitespace=nowarn\" and here\n> is what I saw.\n>\n> commit 75a6a3e6597d5f3959eb269122e8c5f4e4baac0e\n> Author: Bence Ferdinandy <bence@ferdinandy.com>\n> Date:   Thu Nov 21 23:55:03 2024 +0100\n>\n>     refs: standardize output of refs_read_symbolic_ref\n>     \n>     When the symbolic reference we want to read with refs_read_symbolic_ref\n>     is actually not a symbolic reference, the files and the reftable\n>     backends return different values (1 and -1 respectively). Standardize\n>     the returned values so that 0 is success, -1 is a generic error and -2\n>     is that the reference was actually non-symbolic.\n>     \n>     Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n>     Signed-off-by: Junio C Hamano <gitster@pobox.com>\n>\n> refs/reftable-backend.c:833: indent with spaces.\n> +        if (ret)\n> refs/reftable-backend.c:834: indent with spaces.\n> +                ret = -1;\n> refs/reftable-backend.c:835: indent with spaces.\n> +        else if (ref.value_type == REFTABLE_REF_SYMREF)\n> refs/reftable-backend.c:837: indent with spaces.\n> +        else\n> refs/reftable-backend.c:838: indent with spaces.\n> +                ret = NOT_A_SYMREF;\n\nSo this did find it. Maybe something is misconfigured in the CI?\n\nBest,\nBence\n\n"},{"id":"508189","messageId":"CAOLa=ZQ-ZeM4Lk0=ojx3f9RebRkVCX7w4KUKHphZoxUa5Cgfsg@mail.gmail.com","threadId":"62096","inReplyTo":"D5W75LQ6Z44H.3LKJ0OX40QKVQ@ferdinandy.com","subject":"Re: [PATCH v14 03/10] refs: standardize output of refs_read_symbolic_ref","fromName":"karthik nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-11-26T16:53:01Z","receivedAt":"2024-11-26T16:53:03Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"\"Bence Ferdinandy\" <bence@ferdinandy.com> writes:\n\n> sorry about the other thread, I saw \"extend whitespace checks\" and I thought\n> based on what Kartik wrote that `log --check` should have caught it, which is\n> why I thought it might be appropriate there.\n>\n\nWith v14, running `git log --check --pretty=format:\"---% h% s\" master..`\ngives me:\n\n  --- f73b2c577b fetch set_head: handle mirrored bare repositories\n\n  --- c0c84fb7f9 fetch: set remote/HEAD if it does not exist\n\n  --- c366911074 refs: add create_only option to refs_update_symref_extended\n\n  --- c47704d4df refs: add TRANSACTION_CREATE_EXISTS error\n\n  --- 22e7a9bcae remote set-head: better output for --auto\n\n  --- 25d9944eb2 remote set-head: refactor for readability\n\n  --- 01fe46c842 refs: atomically record overwritten ref in update_symref\n\n  --- fed56bc6cc refs: standardize output of refs_read_symbolic_ref\n  refs/reftable-backend.c:833: indent with spaces.\n  +        if (ret)\n  refs/reftable-backend.c:834: indent with spaces.\n  +                ret = -1;\n  refs/reftable-backend.c:835: indent with spaces.\n  +        else if (ref.value_type == REFTABLE_REF_SYMREF)\n  refs/reftable-backend.c:837: indent with spaces.\n  +        else\n  refs/reftable-backend.c:838: indent with spaces.\n  +                ret = NOT_A_SYMREF;\n\n  --- d5534d6c67 t/t5505-remote: test failure of set-head\n\n  --- a77b3b7774 t/t5505-remote: set default branch to main\n\n> On Mon Nov 25, 2024 at 03:56, Junio C Hamano <gitster@pobox.com> wrote:\n>> \"Bence Ferdinandy\" <bence@ferdinandy.com> writes:\n>>\n>>>> At the least you should see `git log`'s output, but if there are issues\n>>>> they should be shown inline. So when you say 'no output' do you mean you\n>>>> see absolutely no output?\n>>>\n>>> Absolutely no output:\n>>> \thttps://asciinema.org/a/lsqp4e1bNst6cFWw9M2jX1IqC\n>>>\n>>> But I figured out why: the whitespace and the tabs were not mixed on the line,\n>>> just across lines. As I read it, that is not an error to have tabs on one line\n>>> and spaces on the next.\n>>\n>> Our .gitattribute starts like so:\n>>\n>>     * whitespace=!indent,trail,space\n>>     *.[ch] whitespace=indent,trail,space diff=cpp\n>>\n>> so, unless otherwise specified, we frown upon trailing whitespace\n>> and space before tab and indenting with non tab is permitted, but C\n>> source and header files have further care about \"indent\" (short for\n>> \"indent-with-non-tab\".\n>>\n>> So mixed or not, if you indented with spaces and not tabs, that\n>> would be noticed.\n>\n> So `git log --check --pretty=format:\"---% h% s\"` was _not_ supposed to catch\n> this?\n>\n> I ran the CI with this patch again:\n> https://github.com/ferdinandyb/git/actions/runs/12031250376\n>\n> and it's all green, so wherever this _should_ have been caught: it didn't work.\n\nI'm not an expert in GitHub actions, but if you look at\n`.github/workflows/check-whitespace.yml`, it says it is only triggered\nfor `pull_request`. Did you raise a pull request? From your link, I\ndon't see the whitespace job being triggered, so `it didn't work` would\nbe inaccurate, since it didn't even trigger the job ;)\n\n[snip]\n"},{"id":"508195","messageId":"xmqqh67twypb.fsf@gitster.g","threadId":"62096","inReplyTo":"CAOLa=ZQ-ZeM4Lk0=ojx3f9RebRkVCX7w4KUKHphZoxUa5Cgfsg@mail.gmail.com","subject":"Re: [PATCH v14 03/10] refs: standardize output of refs_read_symbolic_ref","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-11-26T20:02:24Z","receivedAt":"2024-11-26T20:02:28Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"karthik nayak <karthik.188@gmail.com> writes:\n\n> With v14, running `git log --check --pretty=format:\"---% h% s\" master..`\n> gives me:\n> ...\n>   --- fed56bc6cc refs: standardize output of refs_read_symbolic_ref\n>   refs/reftable-backend.c:833: indent with spaces.\n>   +        if (ret)\n>   refs/reftable-backend.c:834: indent with spaces.\n>   +                ret = -1;\n> ...\n\nThanks, the above matches what I saw in my message Bence responded\nto (to which you are responding).  I was unsure if/how the status of\neach \"diff --check\" is propagated up to the driving \"git log\", but\nthe job is reading from the output of the command and not using the\nexit status of \"git log --check\", so even if \"git log --check\" did\nnot signal a check failure with its status, it did not matter ;-) A\nquick local check says \"git log --check\" does exit with 2 if there\nis an offending commit in the range, so we should be OK.\n\nBy the way, I appreciate the cuteness of \"% s\" but I do not see the\npoint of \"% h\", as I do not think of a situation where '%h' can ever\nbe empty.  It seems that this was carried from the first day when\nGitHub CI learned the whitespace checks 32c83afc (ci: github action\n- add check for whitespace errors, 2020-09-22).\n"},{"id":"508196","messageId":"D5WEJJBMSO1K.2TPXDI1K08SHT@ferdinandy.com","threadId":"62096","inReplyTo":"CAOLa=ZQ-ZeM4Lk0=ojx3f9RebRkVCX7w4KUKHphZoxUa5Cgfsg@mail.gmail.com","subject":"Re: [PATCH v14 03/10] refs: standardize output of refs_read_symbolic_ref","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-26T20:44:47Z","receivedAt":"2024-11-26T20:45:13Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Tue Nov 26, 2024 at 17:53, karthik nayak <karthik.188@gmail.com> wrote:\n> \"Bence Ferdinandy\" <bence@ferdinandy.com> writes:\n>\n>> sorry about the other thread, I saw \"extend whitespace checks\" and I thought\n>> based on what Kartik wrote that `log --check` should have caught it, which is\n>> why I thought it might be appropriate there.\n>>\n>\n> With v14, running `git log --check --pretty=format:\"---% h% s\" master..`\n> gives me:\n>\n>   --- f73b2c577b fetch set_head: handle mirrored bare repositories\n>\n>   --- c0c84fb7f9 fetch: set remote/HEAD if it does not exist\n>\n>   --- c366911074 refs: add create_only option to refs_update_symref_extended\n>\n>   --- c47704d4df refs: add TRANSACTION_CREATE_EXISTS error\n>\n>   --- 22e7a9bcae remote set-head: better output for --auto\n>\n>   --- 25d9944eb2 remote set-head: refactor for readability\n>\n>   --- 01fe46c842 refs: atomically record overwritten ref in update_symref\n>\n>   --- fed56bc6cc refs: standardize output of refs_read_symbolic_ref\n>   refs/reftable-backend.c:833: indent with spaces.\n>   +        if (ret)\n>   refs/reftable-backend.c:834: indent with spaces.\n>   +                ret = -1;\n>   refs/reftable-backend.c:835: indent with spaces.\n>   +        else if (ref.value_type == REFTABLE_REF_SYMREF)\n>   refs/reftable-backend.c:837: indent with spaces.\n>   +        else\n>   refs/reftable-backend.c:838: indent with spaces.\n>   +                ret = NOT_A_SYMREF;\n>\n>   --- d5534d6c67 t/t5505-remote: test failure of set-head\n>\n>   --- a77b3b7774 t/t5505-remote: set default branch to main\n\nSo at least now I know why I'm confused :) I copied your exact command (well,\nwith upstream/master..), and I still have zero output. Since .gitattributes is\ncommitted to the repository, what else could be the difference?\n\n[snip]\n\n>>\n>> So `git log --check --pretty=format:\"---% h% s\"` was _not_ supposed to catch\n>> this?\n>>\n>> I ran the CI with this patch again:\n>> https://github.com/ferdinandyb/git/actions/runs/12031250376\n>>\n>> and it's all green, so wherever this _should_ have been caught: it didn't work.\n>\n> I'm not an expert in GitHub actions, but if you look at\n> `.github/workflows/check-whitespace.yml`, it says it is only triggered\n> for `pull_request`. Did you raise a pull request? From your link, I\n> don't see the whitespace job being triggered, so `it didn't work` would\n> be inaccurate, since it didn't even trigger the job ;)\n\nWell that make sense, the commit introducing it also talks about pull requests.\nOn the other hand: why does this run only on a pull request? The main.yml runs\non both pull requests and pushes and since the project doesn't really use pull\nrequests, it would make more sense to also run this on push? The\ncheck-style.yml is similar.\n"},{"id":"508197","messageId":"D5WESFCCZ463.3RFWANEKVGBOU@ferdinandy.com","threadId":"62096","inReplyTo":"xmqqh67twypb.fsf@gitster.g","subject":"Re: [PATCH v14 03/10] refs: standardize output of refs_read_symbolic_ref","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-26T20:56:23Z","receivedAt":"2024-11-26T20:57:03Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Tue Nov 26, 2024 at 21:02, Junio C Hamano <gitster@pobox.com> wrote:\n> karthik nayak <karthik.188@gmail.com> writes:\n>\n>> With v14, running `git log --check --pretty=format:\"---% h% s\" master..`\n>> gives me:\n>> ...\n>>   --- fed56bc6cc refs: standardize output of refs_read_symbolic_ref\n>>   refs/reftable-backend.c:833: indent with spaces.\n>>   +        if (ret)\n>>   refs/reftable-backend.c:834: indent with spaces.\n>>   +                ret = -1;\n>> ...\n>\n> Thanks, the above matches what I saw in my message Bence responded\n> to (to which you are responding).  I was unsure if/how the status of\n> each \"diff --check\" is propagated up to the driving \"git log\", but\n> the job is reading from the output of the command and not using the\n> exit status of \"git log --check\", so even if \"git log --check\" did\n> not signal a check failure with its status, it did not matter ;-) A\n> quick local check says \"git log --check\" does exit with 2 if there\n> is an offending commit in the range, so we should be OK.\n\nOk, so `git diff --check master` at least does produce the output for me, and\nnow that you mentioned the exit code, I checked that even though I get zero\noutput from git log --check, the exit code is indeed 2. So now I just don't get\nwhy I don't see any output ...\n\n"},{"id":"508198","messageId":"xmqqa5dlwu8a.fsf@gitster.g","threadId":"62096","inReplyTo":"D5WESFCCZ463.3RFWANEKVGBOU@ferdinandy.com","subject":"Re: [PATCH v14 03/10] refs: standardize output of refs_read_symbolic_ref","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-11-26T21:39:01Z","receivedAt":"2024-11-26T21:39:05Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Bence Ferdinandy\" <bence@ferdinandy.com> writes:\n\n> Ok, so `git diff --check master` at least does produce the output for me, and\n> now that you mentioned the exit code, I checked that even though I get zero\n> output from git log --check, the exit code is indeed 2. So now I just don't get\n> why I don't see any output ...\n\nI was about to say \"perhaps you are accidentally giving a wrong\nrevision range, like 'git checkout master && git log --check\nmaster..'?\" but then the command should exit with 0 for an empty\nrange, so that is not it.\n\nDo let us know when you find out why ;-)\n\nThanks.\n"},{"id":"508219","messageId":"20241127091718.345541-1-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241122123138.66960-1-bence@ferdinandy.com","subject":"[PATCH v1] fetch: add configuration for set_head behaviour","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-27T09:16:25Z","receivedAt":"2024-11-27T09:18:12Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"In the current implementatio, if refs/remotes/$remote/HEAD does not\nexist, running fetch will create it, but if it does exist it will not do\nanything, which is a somewhat safe and minimal approach. Unfortunately,\nfor users who wish to NOT have refs/remotes/$remote/HEAD set for any\nreason (e.g. so that `git rev-parse origin` doesn't accidentally point\nthem somewhere they do not want to), there is no way to remove this\nbehaviour. On the other side of the spectrum, users may want fetch to\nautomatically update HEAD or at least give them a warning if something\nchanged on the remote.\n\nIntroduce a new setting, remote.$remote.followRemoteHEAD with four\noptions:\n\n    - \"never\": do not ever do anything, not even create\n    - \"create\": the current behaviour, now the default behaviour\n    - \"warn\": print a message if remote and local HEAD is different\n    - \"always\": silently update HEAD on every change\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    This patch is built off v15 of the set-head/fetch updates series:\n    https://lore.kernel.org/git/D5WEJJBMSO1K.2TPXDI1K08SHT@ferdinandy.com/T/#m13a0c3e8919872188ef07fd9fd984c78e8aa35ba\n    \n    The patch is sent separately, because the rest of the series building up\n    to here seems ready, while this patch will likely trigger some\n    discussion.\n\n Documentation/config/remote.txt |  11 ++++\n builtin/fetch.c                 |  46 ++++++++++++--\n remote.c                        |   9 +++\n remote.h                        |   9 +++\n t/t5510-fetch.sh                | 102 ++++++++++++++++++++++++++++++++\n 5 files changed, 171 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/config/remote.txt b/Documentation/config/remote.txt\nindex 6d8b7d6c63..024f92befc 100644\n--- a/Documentation/config/remote.txt\n+++ b/Documentation/config/remote.txt\n@@ -101,6 +101,17 @@ remote.<name>.serverOption::\n \tThe default set of server options used when fetching from this remote.\n \tThese server options can be overridden by the `--server-option=` command\n \tline arguments.\n+\n+remote.<name>.followRemoteHEAD::\n+\tHow linkgit:git-fetch[1] should handle updates to `remotes/<name>/HEAD`.\n+\tThe default value is \"create\", which will create `remotes/<name>/HEAD`\n+\tif it exists on the remote, but not locally, but will not touch an\n+\talready existing local reference.  Setting to \"warn\" will print\n+\ta message if the remote has a different value, than the local one and\n+\tin case there is no local reference, it behaves like \"create\". Setting\n+\tto \"always\" will silently update it to the value on the remote.\n+\tFinally, setting it to \"never\" will never change or create the local\n+\treference.\n +\n This is a multi-valued variable, and an empty value can be used in a higher\n priority configuration file (e.g. `.git/config` in a repository) to clear\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 2f416cf867..b619bddd7a 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1579,10 +1579,35 @@ static const char *strip_refshead(const char *name){\n \treturn name;\n }\n \n-static int set_head(const struct ref *remote_refs)\n+static void report_set_head(const char *remote, const char *head_name,\n+\t\t\tstruct strbuf *buf_prev, int updateres) {\n+\tstruct strbuf buf_prefix = STRBUF_INIT;\n+\tconst char *prev_head = NULL;\n+\n+\tstrbuf_addf(&buf_prefix, \"refs/remotes/%s/\", remote);\n+\tskip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);\n+\n+\tif (prev_head && strcmp(prev_head, head_name)) {\n+\t\tprintf(\"'HEAD' at '%s' is '%s', but we have '%s' locally.\\n\",\n+\t\t\tremote, head_name, prev_head);\n+\t\tprintf(\"Run 'git remote set-head %s %s' to follow the change.\\n\",\n+\t\t\tremote, head_name);\n+\t}\n+\telse if (updateres && buf_prev->len) {\n+\t\tprintf(\"'HEAD' at '%s' is '%s', \"\n+\t\t\t\"but we have a detached HEAD pointing to '%s' locally.\\n\",\n+\t\t\tremote, head_name, buf_prev->buf);\n+\t\tprintf(\"Run 'git remote set-head %s %s' to follow the change.\\n\",\n+\t\t\tremote, head_name);\n+\t}\n+\tstrbuf_release(&buf_prefix);\n+}\n+\n+static int set_head(const struct ref *remote_refs, int follow_remote_head)\n {\n-\tint result = 0, is_bare;\n-\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;\n+\tint result = 0, create_only, is_bare, was_detached;\n+\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,\n+\t\t      b_local_head = STRBUF_INIT;\n \tconst char *remote = gtransport->remote->name;\n \tchar *head_name = NULL;\n \tstruct ref *ref, *matches;\n@@ -1603,6 +1628,8 @@ static int set_head(const struct ref *remote_refs)\n \t\tstring_list_append(&heads, strip_refshead(ref->name));\n \t}\n \n+\tif (follow_remote_head < 0)\n+\t\tgoto cleanup;\n \n \tif (!heads.nr)\n \t\tresult = 1;\n@@ -1614,6 +1641,7 @@ static int set_head(const struct ref *remote_refs)\n \tif (!head_name)\n \t\tgoto cleanup;\n \tis_bare = is_bare_repository();\n+\tcreate_only = follow_remote_head == 2 ? 0 : !is_bare;\n \tif (is_bare) {\n \t\tstrbuf_addstr(&b_head, \"HEAD\");\n \t\tstrbuf_addf(&b_remote_head, \"refs/heads/%s\", head_name);\n@@ -1626,9 +1654,14 @@ static int set_head(const struct ref *remote_refs)\n \t\tresult = 1;\n \t\tgoto cleanup;\n \t}\n-\tif (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n-\t\t\t\t\t\"fetch\", NULL, !is_bare))\n+\twas_detached = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n+\t\t\t\t\t\"fetch\", &b_local_head, create_only);\n+\tif (was_detached == -1) {\n \t\tresult = 1;\n+\t\tgoto cleanup;\n+\t}\n+\tif (follow_remote_head == 1 && verbosity >= 0)\n+\t\treport_set_head(remote, head_name, &b_local_head, was_detached);\n \n cleanup:\n \tfree(head_name);\n@@ -1636,6 +1669,7 @@ static int set_head(const struct ref *remote_refs)\n \tfree_refs(matches);\n \tstring_list_clear(&heads, 0);\n \tstrbuf_release(&b_head);\n+\tstrbuf_release(&b_local_head);\n \tstrbuf_release(&b_remote_head);\n \treturn result;\n }\n@@ -1855,7 +1889,7 @@ static int do_fetch(struct transport *transport,\n \t\t\t\t  \"you need to specify exactly one branch with the --set-upstream option\"));\n \t\t}\n \t}\n-\tif (set_head(remote_refs))\n+\tif (set_head(remote_refs, transport->remote->follow_remote_head))\n \t\t;\n \t\t/*\n \t\t * Way too many cases where this can go wrong\ndiff --git a/remote.c b/remote.c\nindex 10104d11e3..5a768ddac2 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -514,6 +514,15 @@ static int handle_config(const char *key, const char *value,\n \t} else if (!strcmp(subkey, \"serveroption\")) {\n \t\treturn parse_transport_option(key, value,\n \t\t\t\t\t      &remote->server_options);\n+\t} else if (!strcmp(subkey, \"followremotehead\")) {\n+\t\tif (!strcmp(value, \"never\"))\n+\t\t\tremote->follow_remote_head = -1;\n+\t\telse if (!strcmp(value, \"create\"))\n+\t\t\tremote->follow_remote_head = 0;\n+\t\telse if (!strcmp(value, \"warn\"))\n+\t\t\tremote->follow_remote_head = 1;\n+\t\telse if (!strcmp(value, \"always\"))\n+\t\t\tremote->follow_remote_head = 2;\n \t}\n \treturn 0;\n }\ndiff --git a/remote.h b/remote.h\nindex a7e5c4e07c..3ceadac820 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -107,6 +107,15 @@ struct remote {\n \tchar *http_proxy_authmethod;\n \n \tstruct string_list server_options;\n+\n+\t/*\n+\t * The setting for whether to update HEAD for the remote.\n+\t * -1 never update\n+\t * 0 create only (default)\n+\t * 1 warn on change\n+\t * 2 always update\n+\t */\n+\tint follow_remote_head;\n };\n \n /**\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex 87698341f5..2467027d34 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -99,6 +99,108 @@ test_expect_success \"fetch test remote HEAD change\" '\n \tbranch=$(git rev-parse refs/remotes/origin/other) &&\n \ttest \"z$head\" = \"z$branch\"'\n \n+test_expect_success \"fetch test followRemoteHEAD never\" '\n+\ttest_when_finished \"git config unset remote.origin.followRemoteHEAD\" &&\n+\t(\n+\t\tcd \"$D\" &&\n+\t\tcd two &&\n+\t\tgit update-ref --no-deref -d refs/remotes/origin/HEAD &&\n+\t\tgit config set remote.origin.followRemoteHEAD \"never\" &&\n+\t\tgit fetch &&\n+\t\ttest_must_fail git rev-parse --verify refs/remotes/origin/HEAD\n+\t)\n+'\n+\n+test_expect_success \"fetch test followRemoteHEAD warn no change\" '\n+\ttest_when_finished \"git config unset remote.origin.followRemoteHEAD\" &&\n+\t(\n+\t\tcd \"$D\" &&\n+\t\tcd two &&\n+\t\tgit rev-parse --verify refs/remotes/origin/other &&\n+\t\tgit remote set-head origin other &&\n+\t\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\t\tgit rev-parse --verify refs/remotes/origin/main &&\n+\t\tgit config set remote.origin.followRemoteHEAD \"warn\" &&\n+\t\tgit fetch >output &&\n+\t\techo \"${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ},\" \\\n+\t\t\t\"but we have ${SQ}other${SQ} locally.\" >expect &&\n+\t\techo \"Run ${SQ}git remote set-head origin main${SQ} to follow the change.\" >>expect &&\n+\t\ttest_cmp expect output &&\n+\t\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\t\tbranch=$(git rev-parse refs/remotes/origin/other) &&\n+\t\ttest \"z$head\" = \"z$branch\"\n+\t)\n+'\n+\n+test_expect_success \"fetch test followRemoteHEAD warn create\" '\n+\ttest_when_finished \"git config unset remote.origin.followRemoteHEAD\" &&\n+\t(\n+\t\tcd \"$D\" &&\n+\t\tcd two &&\n+\t\tgit update-ref --no-deref -d refs/remotes/origin/HEAD &&\n+\t\tgit config set remote.origin.followRemoteHEAD \"warn\" &&\n+\t\tgit rev-parse --verify refs/remotes/origin/main &&\n+\t\toutput=$(git fetch) &&\n+\t\ttest \"z\" = \"z$output\" &&\n+\t\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\t\tbranch=$(git rev-parse refs/remotes/origin/main) &&\n+\t\ttest \"z$head\" = \"z$branch\"\n+\t)\n+'\n+\n+test_expect_success \"fetch test followRemoteHEAD warn detached\" '\n+\ttest_when_finished \"git config unset remote.origin.followRemoteHEAD\" &&\n+\t(\n+\t\tcd \"$D\" &&\n+\t\tcd two &&\n+\t\tgit update-ref --no-deref -d refs/remotes/origin/HEAD &&\n+\t\tgit update-ref refs/remotes/origin/HEAD HEAD &&\n+\t\tHEAD=$(git log --pretty=\"%H\") &&\n+\t\tgit config set remote.origin.followRemoteHEAD \"warn\" &&\n+\t\tgit fetch >output &&\n+\t\techo \"${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ},\" \\\n+\t\t\t\"but we have a detached HEAD pointing to\" \\\n+\t\t\t\"${SQ}${HEAD}${SQ} locally.\" >expect &&\n+\t\techo \"Run ${SQ}git remote set-head origin main${SQ} to follow the change.\" >>expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n+test_expect_success \"fetch test followRemoteHEAD warn quiet\" '\n+\ttest_when_finished \"git config unset remote.origin.followRemoteHEAD\" &&\n+\t(\n+\t\tcd \"$D\" &&\n+\t\tcd two &&\n+\t\tgit rev-parse --verify refs/remotes/origin/other &&\n+\t\tgit remote set-head origin other &&\n+\t\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\t\tgit rev-parse --verify refs/remotes/origin/main &&\n+\t\tgit config set remote.origin.followRemoteHEAD \"warn\" &&\n+\t\toutput=$(git fetch --quiet) &&\n+\t\ttest \"z\" = \"z$output\" &&\n+\t\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\t\tbranch=$(git rev-parse refs/remotes/origin/other) &&\n+\t\ttest \"z$head\" = \"z$branch\"\n+\t)\n+'\n+\n+test_expect_success \"fetch test followRemoteHEAD always\" '\n+\ttest_when_finished \"git config unset remote.origin.followRemoteHEAD\" &&\n+\t(\n+\t\tcd \"$D\" &&\n+\t\tcd two &&\n+\t\tgit rev-parse --verify refs/remotes/origin/other &&\n+\t\tgit remote set-head origin other &&\n+\t\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\t\tgit rev-parse --verify refs/remotes/origin/main &&\n+\t\tgit config set remote.origin.followRemoteHEAD \"always\" &&\n+\t\tgit fetch &&\n+\t\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\t\tbranch=$(git rev-parse refs/remotes/origin/main) &&\n+\t\ttest \"z$head\" = \"z$branch\"\n+\t)\n+'\n+\n test_expect_success 'fetch --prune on its own works as expected' '\n \tcd \"$D\" &&\n \tgit clone . prune &&\n-- \n2.47.1.295.ge630f003e1.dirty\n\n"},{"id":"508226","messageId":"xmqqa5dku6w6.fsf@gitster.g","threadId":"62096","inReplyTo":"20241127091718.345541-1-bence@ferdinandy.com","subject":"Re: [PATCH v1] fetch: add configuration for set_head behaviour","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-11-27T13:46:01Z","receivedAt":"2024-11-27T13:46:04Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bence Ferdinandy <bence@ferdinandy.com> writes:\n\n> Introduce a new setting, remote.$remote.followRemoteHEAD with four\n> options:\n>\n>     - \"never\": do not ever do anything, not even create\n>     - \"create\": the current behaviour, now the default behaviour\n>     - \"warn\": print a message if remote and local HEAD is different\n>     - \"always\": silently update HEAD on every change\n\nThat seems to be plenty of choices to please many classes of users.\n\nExcept for the one that I would want to use myself, which is \"I\nunderstand their HEAD points at branch X right now; please warn when\nthey flip their HEAD to a different branch, but until then please do\nnothing\".  That's somewhere between \"never\" and \"warn\".\n\n> @@ -1603,6 +1628,8 @@ static int set_head(const struct ref *remote_refs)\n>  \t\tstring_list_append(&heads, strip_refshead(ref->name));\n>  \t}\n>  \n> +\tif (follow_remote_head < 0)\n> +\t\tgoto cleanup;\n\nThere is some \"magical\" value(s) that is/are negative; we will find\nout what they are later.\n\n> @@ -1614,6 +1641,7 @@ static int set_head(const struct ref *remote_refs)\n>  \tif (!head_name)\n>  \t\tgoto cleanup;\n>  \tis_bare = is_bare_repository();\n> +\tcreate_only = follow_remote_head == 2 ? 0 : !is_bare;\n\nThere is one more \"magical\" value that follow_remote_head can take.\n\n> @@ -1626,9 +1654,14 @@ static int set_head(const struct ref *remote_refs)\n>  \t\tresult = 1;\n>  \t\tgoto cleanup;\n>  \t}\n> -\tif (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n> -\t\t\t\t\t\"fetch\", NULL, !is_bare))\n> +\twas_detached = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n> +\t\t\t\t\t\"fetch\", &b_local_head, create_only);\n> +\tif (was_detached == -1) {\n>  \t\tresult = 1;\n> +\t\tgoto cleanup;\n> +\t}\n> +\tif (follow_remote_head == 1 && verbosity >= 0)\n\nAnd there is one more.\n\n> diff --git a/remote.c b/remote.c\n> index 10104d11e3..5a768ddac2 100644\n> --- a/remote.c\n> +++ b/remote.c\n> @@ -514,6 +514,15 @@ static int handle_config(const char *key, const char *value,\n>  \t} else if (!strcmp(subkey, \"serveroption\")) {\n>  \t\treturn parse_transport_option(key, value,\n>  \t\t\t\t\t      &remote->server_options);\n> +\t} else if (!strcmp(subkey, \"followremotehead\")) {\n> +\t\tif (!strcmp(value, \"never\"))\n> +\t\t\tremote->follow_remote_head = -1;\n> +\t\telse if (!strcmp(value, \"create\"))\n> +\t\t\tremote->follow_remote_head = 0;\n> +\t\telse if (!strcmp(value, \"warn\"))\n> +\t\t\tremote->follow_remote_head = 1;\n> +\t\telse if (!strcmp(value, \"always\"))\n> +\t\t\tremote->follow_remote_head = 2;\n\nUse something like\n\n\t/* The setting for whether to update HEAD for the remote. */\n\tenum follow_remote_head {\n\t\tFOLLOW_REMOTE_NEVER = -1,\n\t\tFOLLOW_REMOTE_CREATE = 0,\n\t\tFOLLOW_REMOTE_WARN = 1,\n\t\tFOLLOW_REMOTE_ALWAYS = 2,\n\t};\n\nor something?  I have no strong preference between \"enum\" and\n\"#define\" myself, but moderately strong preference for anything\nsymbolic over magic numbers.\n\n> diff --git a/remote.h b/remote.h\n> index a7e5c4e07c..3ceadac820 100644\n> --- a/remote.h\n> +++ b/remote.h\n> @@ -107,6 +107,15 @@ struct remote {\n>  \tchar *http_proxy_authmethod;\n>  \n>  \tstruct string_list server_options;\n> +\n> +\t/*\n> +\t * The setting for whether to update HEAD for the remote.\n> +\t * -1 never update\n> +\t * 0 create only (default)\n> +\t * 1 warn on change\n> +\t * 2 always update\n> +\t */\n> +\tint follow_remote_head;\n>  };\n\nOther than that, looking good from a cursory read.\n\nThanks.\n"},{"id":"508242","messageId":"D5X7DHD1BRYO.22CU1OL6KLN49@ferdinandy.com","threadId":"62096","inReplyTo":"xmqqa5dku6w6.fsf@gitster.g","subject":"Re: [PATCH v1] fetch: add configuration for set_head behaviour","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-27T19:20:25Z","receivedAt":"2024-11-27T19:20:49Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Wed Nov 27, 2024 at 14:46, Junio C Hamano <gitster@pobox.com> wrote:\n> Bence Ferdinandy <bence@ferdinandy.com> writes:\n>\n>> Introduce a new setting, remote.$remote.followRemoteHEAD with four\n>> options:\n>>\n>>     - \"never\": do not ever do anything, not even create\n>>     - \"create\": the current behaviour, now the default behaviour\n>>     - \"warn\": print a message if remote and local HEAD is different\n>>     - \"always\": silently update HEAD on every change\n>\n> That seems to be plenty of choices to please many classes of users.\n>\n> Except for the one that I would want to use myself, which is \"I\n> understand their HEAD points at branch X right now; please warn when\n> they flip their HEAD to a different branch, but until then please do\n> nothing\".  That's somewhere between \"never\" and \"warn\".\n\nJust to clarify, an example: the remote's HEAD is set to \"master\", and you have\n- git remote set-head origin next\n- git config set remote.origin.followRemoteHead \"manual\"\n- git config set remote.origin.followRemoteHeadManual \"master\"\n\nyou manually set these explicitly. As long as the remote's HEAD is still\n\"master\" you do not get a warning when running fetch, but if it changes to\nsomething else (even \"next\"), you _do_ get a warning, that is not silenced\nuntil you set followRemoteHeadManual to \"next\". \n\nWould you expect `git remote set-head origin next` to set followRemoteHead to\n\"manual\" and to set the correct value for the followRemoteHeadManual to\n\"master\" if it actually changed the current refs/remote/origin/HEAD from master\nto next? Or is having this completely manual fine?\n\nI toyed with the thought of rolling the two settings together (an unrecognized\nstring would mean the reference for which we must be silent), but then you\ncouldn't have a remote/HEAD called \"create\" for example, so I think we need to\nstore that separately. I'm also not quite happy with \"followRemoteHeadManual\"\n...\n\n\n>\n>> @@ -1603,6 +1628,8 @@ static int set_head(const struct ref *remote_refs)\n>>  \t\tstring_list_append(&heads, strip_refshead(ref->name));\n>>  \t}\n>>  \n>> +\tif (follow_remote_head < 0)\n>> +\t\tgoto cleanup;\n>\n> There is some \"magical\" value(s) that is/are negative; we will find\n> out what they are later.\n>\n>> @@ -1614,6 +1641,7 @@ static int set_head(const struct ref *remote_refs)\n>>  \tif (!head_name)\n>>  \t\tgoto cleanup;\n>>  \tis_bare = is_bare_repository();\n>> +\tcreate_only = follow_remote_head == 2 ? 0 : !is_bare;\n>\n> There is one more \"magical\" value that follow_remote_head can take.\n>\n>> @@ -1626,9 +1654,14 @@ static int set_head(const struct ref *remote_refs)\n>>  \t\tresult = 1;\n>>  \t\tgoto cleanup;\n>>  \t}\n>> -\tif (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n>> -\t\t\t\t\t\"fetch\", NULL, !is_bare))\n>> +\twas_detached = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n>> +\t\t\t\t\t\"fetch\", &b_local_head, create_only);\n>> +\tif (was_detached == -1) {\n>>  \t\tresult = 1;\n>> +\t\tgoto cleanup;\n>> +\t}\n>> +\tif (follow_remote_head == 1 && verbosity >= 0)\n>\n> And there is one more.\n>\n>> diff --git a/remote.c b/remote.c\n>> index 10104d11e3..5a768ddac2 100644\n>> --- a/remote.c\n>> +++ b/remote.c\n>> @@ -514,6 +514,15 @@ static int handle_config(const char *key, const char *value,\n>>  \t} else if (!strcmp(subkey, \"serveroption\")) {\n>>  \t\treturn parse_transport_option(key, value,\n>>  \t\t\t\t\t      &remote->server_options);\n>> +\t} else if (!strcmp(subkey, \"followremotehead\")) {\n>> +\t\tif (!strcmp(value, \"never\"))\n>> +\t\t\tremote->follow_remote_head = -1;\n>> +\t\telse if (!strcmp(value, \"create\"))\n>> +\t\t\tremote->follow_remote_head = 0;\n>> +\t\telse if (!strcmp(value, \"warn\"))\n>> +\t\t\tremote->follow_remote_head = 1;\n>> +\t\telse if (!strcmp(value, \"always\"))\n>> +\t\t\tremote->follow_remote_head = 2;\n>\n> Use something like\n>\n> \t/* The setting for whether to update HEAD for the remote. */\n> \tenum follow_remote_head {\n> \t\tFOLLOW_REMOTE_NEVER = -1,\n> \t\tFOLLOW_REMOTE_CREATE = 0,\n> \t\tFOLLOW_REMOTE_WARN = 1,\n> \t\tFOLLOW_REMOTE_ALWAYS = 2,\n> \t};\n>\n> or something?  I have no strong preference between \"enum\" and\n> \"#define\" myself, but moderately strong preference for anything\n> symbolic over magic numbers.\n\nAh, my mistake sorry. Magic number bad.\n\nBest,\nBence\n"},{"id":"508254","messageId":"xmqqplmgqkrr.fsf@gitster.g","threadId":"62096","inReplyTo":"D5X7DHD1BRYO.22CU1OL6KLN49@ferdinandy.com","subject":"Re: [PATCH v1] fetch: add configuration for set_head behaviour","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-11-28T00:12:08Z","receivedAt":"2024-11-28T00:12:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Bence Ferdinandy\" <bence@ferdinandy.com> writes:\n\n> Just to clarify, an example: the remote's HEAD is set to \"master\", and you have\n> - git remote set-head origin next\n> - git config set remote.origin.followRemoteHead \"manual\"\n> - git config set remote.origin.followRemoteHeadManual \"master\"\n>\n> you manually set these explicitly. As long as the remote's HEAD is still\n> \"master\" you do not get a warning when running fetch, but if it changes to\n> something else (even \"next\"), you _do_ get a warning, that is not silenced\n> until you set followRemoteHeadManual to \"next\".\n\nYup.\n\n> Would you expect `git remote set-head origin next` to set followRemoteHead to\n> \"manual\" and to set the correct value for the followRemoteHeadManual to\n> \"master\" if it actually changed the current refs/remote/origin/HEAD from master\n> to next?\n\nWhat I found missing is:\n\n    \"I know this is the value I expect them to have, because it was\n    the value I last observed there. Please let me know when they\n    changed their mind; I want to reconsider my position when it\n    happens, and your warning me would help me to do so.\"\n\nMy running \"remote set-head\" to manually change which of their\nbranches I am interested in does not tell Git anything about what\nbranch I expect them to be pointing at with HEAD.  It may be the\naction after such \"reconsideration\" of my position, but there is 0\nbit of information on what I expect their HEAD to be pointing at.\n\n> Or is having this completely manual fine?\n\nIf it can be automated, that would be nicer, but I do not think a\nmanual \"remote set-head origin next\" gives any information to help\nautomating it.\n\n> I toyed with the thought of rolling the two settings together (an unrecognized\n> string would mean the reference for which we must be silent), but then you\n> couldn't have a remote/HEAD called \"create\" for example, so I think we need to\n> store that separately. I'm also not quite happy with \"followRemoteHeadManual\"\n> ...\n\nYeah, I was thinking a value like \"warn-if-not-pointing-at-$branch\"\nwhere $branch part would be a token like 'bc/set-head-symref',\n'master', etc.\n\nI do not think any of the above should block this topic.  It can be\nadded later without disrupting all other modes implemented there.\n\nThanks.\n"},{"id":"508263","messageId":"D5XKQVYAZE2H.2SRB9JJ8GLWA3@ferdinandy.com","threadId":"62096","inReplyTo":"xmqqplmgqkrr.fsf@gitster.g","subject":"Re: [PATCH v1] fetch: add configuration for set_head behaviour","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-28T05:49:09Z","receivedAt":"2024-11-28T05:50:22Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Thu Nov 28, 2024 at 01:12, Junio C Hamano <gitster@pobox.com> wrote:\n> \"Bence Ferdinandy\" <bence@ferdinandy.com> writes:\n>\n>> Just to clarify, an example: the remote's HEAD is set to \"master\", and you have\n>> - git remote set-head origin next\n>> - git config set remote.origin.followRemoteHead \"manual\"\n>> - git config set remote.origin.followRemoteHeadManual \"master\"\n>>\n>> you manually set these explicitly. As long as the remote's HEAD is still\n>> \"master\" you do not get a warning when running fetch, but if it changes to\n>> something else (even \"next\"), you _do_ get a warning, that is not silenced\n>> until you set followRemoteHeadManual to \"next\".\n>\n> Yup.\n>\n>> Would you expect `git remote set-head origin next` to set followRemoteHead to\n>> \"manual\" and to set the correct value for the followRemoteHeadManual to\n>> \"master\" if it actually changed the current refs/remote/origin/HEAD from master\n>> to next?\n>\n> What I found missing is:\n>\n>     \"I know this is the value I expect them to have, because it was\n>     the value I last observed there. Please let me know when they\n>     changed their mind; I want to reconsider my position when it\n>     happens, and your warning me would help me to do so.\"\n>\n> My running \"remote set-head\" to manually change which of their\n> branches I am interested in does not tell Git anything about what\n> branch I expect them to be pointing at with HEAD.  It may be the\n> action after such \"reconsideration\" of my position, but there is 0\n> bit of information on what I expect their HEAD to be pointing at.\n\nHmm. After a bit more thought: running `remote set-head` doesn't make much\nsense if you have \"always\" and makes a bit of sense if you have \"warn\". So\nmaybe one thing set-head could do is _if_ you have always it could drop you to\n\"warn\" and \"warn\" could also include a line not just about following with\n\"remote set-head\" but something like \"You can either follow with set-head, or\nyou can turn this off but still get a warning if origin changes by setting\n...\".\n\n>\n>> Or is having this completely manual fine?\n>\n> If it can be automated, that would be nicer, but I do not think a\n> manual \"remote set-head origin next\" gives any information to help\n> automating it.\n>\n>> I toyed with the thought of rolling the two settings together (an unrecognized\n>> string would mean the reference for which we must be silent), but then you\n>> couldn't have a remote/HEAD called \"create\" for example, so I think we need to\n>> store that separately. I'm also not quite happy with \"followRemoteHeadManual\"\n>> ...\n>\n> Yeah, I was thinking a value like \"warn-if-not-pointing-at-$branch\"\n> where $branch part would be a token like 'bc/set-head-symref',\n> 'master', etc.\n\nGood idea. Probably shorter though like \"warn-if-not-$branch\".\n\n>\n> I do not think any of the above should block this topic.  It can be\n> added later without disrupting all other modes implemented there.\n\nAck, I'll send a respin with just enum fixed for now.\n\nThanks,\nBence\n"},{"id":"508264","messageId":"xmqqiks7oprx.fsf@gitster.g","threadId":"62096","inReplyTo":"D5XKQVYAZE2H.2SRB9JJ8GLWA3@ferdinandy.com","subject":"Re: [PATCH v1] fetch: add configuration for set_head behaviour","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-11-28T06:06:58Z","receivedAt":"2024-11-28T06:07:01Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Bence Ferdinandy\" <bence@ferdinandy.com> writes:\n\n> Hmm. After a bit more thought: running `remote set-head` doesn't make much\n> sense if you have \"always\" and makes a bit of sense if you have \"warn\".\n\nYeah, after \"warn\" tells the user what branch the remote points at,\nthe user may decide to match.  Or the user may decide to flip the\nremote-tracking HEAD to something unrelated.  In the former case,\nthe next and subsequent fetch will not warn, until the remote flips\nthe HEAD again.  In the latter case, the next and subsequent fetch\nwill warn, until they happen to change their HEAD again and their\nchoice happens to match ours.\n\n> So\n> maybe one thing set-head could do is _if_ you have always it could drop you to\n> \"warn\" and \"warn\" could also include a line not just about following with\n> \"remote set-head\" but something like \"You can either follow with set-head, or\n> you can turn this off but still get a warning if origin changes by setting\n> ...\".\n\nHmph, if you've been happy with \"always\", if we notice that the\nremote flipped its HEAD, we switch to \"warn\" and the next and\nsubsequent fetch will not warn, until they flip again, and then we\nwill keep warning.\n\nIf you do not want to follow (but still monitor origin), the above\nwould not be sufficient, unless \"by setting...\" part gives a new\nchoice via your .followRemoteHEADManual setting.\n\n>> Yeah, I was thinking a value like \"warn-if-not-pointing-at-$branch\"\n>> where $branch part would be a token like 'bc/set-head-symref',\n>> 'master', etc.\n>\n> Good idea. Probably shorter though like \"warn-if-not-$branch\".\n\nSorry, I wasn't offering an alternative I thought was better.  It\nwas \"we could do this, if we really want to avoid two variables\".\nBut thinking about it further, not having to worry about two\nvariables may be a huge benefit.  If you set .followRemoteHEAD to\n\"manual\" and .followRemoteHEADManual to some value, and then changed\n.followRemoteHEAD to something else, you may be very likely to\nforget unsetting .followRemoteHEADManual that is no longer in effect.\n\n>> I do not think any of the above should block this topic.  It can be\n>> added later without disrupting all other modes implemented there.\n>\n> Ack, I'll send a respin with just enum fixed for now.\n\nThanks.\n"},{"id":"508269","messageId":"20241128111946.366584-1-bence@ferdinandy.com","threadId":"62096","inReplyTo":"xmqqa5dku6w6.fsf@gitster.g","subject":"[PATCH v2] fetch: add configuration for set_head behaviour","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-28T11:19:13Z","receivedAt":"2024-11-28T11:20:18Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"In the current implementation, if refs/remotes/$remote/HEAD does not\nexist, running fetch will create it, but if it does exist it will not do\nanything, which is a somewhat safe and minimal approach. Unfortunately,\nfor users who wish to NOT have refs/remotes/$remote/HEAD set for any\nreason (e.g. so that `git rev-parse origin` doesn't accidentally point\nthem somewhere they do not want to), there is no way to remove this\nbehaviour. On the other side of the spectrum, users may want fetch to\nautomatically update HEAD or at least give them a warning if something\nchanged on the remote.\n\nIntroduce a new setting, remote.$remote.followRemoteHEAD with four\noptions:\n\n    - \"never\": do not ever do anything, not even create\n    - \"create\": the current behaviour, now the default behaviour\n    - \"warn\": print a message if remote and local HEAD is different\n    - \"always\": silently update HEAD on every change\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    This patch is built off v15 of the set-head/fetch updates series:\n    https://lore.kernel.org/git/D5WEJJBMSO1K.2TPXDI1K08SHT@ferdinandy.com/T/#m13a0c3e8919872188ef07fd9fd984c78e8aa35ba\n    \n    v2:\n        - magic numbers converted to enum\n        - since the enum is descriptive (well, along with the documentation)\n          I felt there is no need for the comment explaining the values\n\n Documentation/config/remote.txt |  11 ++++\n builtin/fetch.c                 |  46 ++++++++++++--\n remote.c                        |   9 +++\n remote.h                        |   9 +++\n t/t5510-fetch.sh                | 102 ++++++++++++++++++++++++++++++++\n 5 files changed, 171 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/config/remote.txt b/Documentation/config/remote.txt\nindex 6d8b7d6c63..024f92befc 100644\n--- a/Documentation/config/remote.txt\n+++ b/Documentation/config/remote.txt\n@@ -101,6 +101,17 @@ remote.<name>.serverOption::\n \tThe default set of server options used when fetching from this remote.\n \tThese server options can be overridden by the `--server-option=` command\n \tline arguments.\n+\n+remote.<name>.followRemoteHEAD::\n+\tHow linkgit:git-fetch[1] should handle updates to `remotes/<name>/HEAD`.\n+\tThe default value is \"create\", which will create `remotes/<name>/HEAD`\n+\tif it exists on the remote, but not locally, but will not touch an\n+\talready existing local reference.  Setting to \"warn\" will print\n+\ta message if the remote has a different value, than the local one and\n+\tin case there is no local reference, it behaves like \"create\". Setting\n+\tto \"always\" will silently update it to the value on the remote.\n+\tFinally, setting it to \"never\" will never change or create the local\n+\treference.\n +\n This is a multi-valued variable, and an empty value can be used in a higher\n priority configuration file (e.g. `.git/config` in a repository) to clear\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 2f416cf867..d74b20b49c 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1579,10 +1579,35 @@ static const char *strip_refshead(const char *name){\n \treturn name;\n }\n \n-static int set_head(const struct ref *remote_refs)\n+static void report_set_head(const char *remote, const char *head_name,\n+\t\t\tstruct strbuf *buf_prev, int updateres) {\n+\tstruct strbuf buf_prefix = STRBUF_INIT;\n+\tconst char *prev_head = NULL;\n+\n+\tstrbuf_addf(&buf_prefix, \"refs/remotes/%s/\", remote);\n+\tskip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);\n+\n+\tif (prev_head && strcmp(prev_head, head_name)) {\n+\t\tprintf(\"'HEAD' at '%s' is '%s', but we have '%s' locally.\\n\",\n+\t\t\tremote, head_name, prev_head);\n+\t\tprintf(\"Run 'git remote set-head %s %s' to follow the change.\\n\",\n+\t\t\tremote, head_name);\n+\t}\n+\telse if (updateres && buf_prev->len) {\n+\t\tprintf(\"'HEAD' at '%s' is '%s', \"\n+\t\t\t\"but we have a detached HEAD pointing to '%s' locally.\\n\",\n+\t\t\tremote, head_name, buf_prev->buf);\n+\t\tprintf(\"Run 'git remote set-head %s %s' to follow the change.\\n\",\n+\t\t\tremote, head_name);\n+\t}\n+\tstrbuf_release(&buf_prefix);\n+}\n+\n+static int set_head(const struct ref *remote_refs, int follow_remote_head)\n {\n-\tint result = 0, is_bare;\n-\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;\n+\tint result = 0, create_only, is_bare, was_detached;\n+\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,\n+\t\t      b_local_head = STRBUF_INIT;\n \tconst char *remote = gtransport->remote->name;\n \tchar *head_name = NULL;\n \tstruct ref *ref, *matches;\n@@ -1603,6 +1628,8 @@ static int set_head(const struct ref *remote_refs)\n \t\tstring_list_append(&heads, strip_refshead(ref->name));\n \t}\n \n+\tif (follow_remote_head == FOLLOW_REMOTE_NEVER)\n+\t\tgoto cleanup;\n \n \tif (!heads.nr)\n \t\tresult = 1;\n@@ -1614,6 +1641,7 @@ static int set_head(const struct ref *remote_refs)\n \tif (!head_name)\n \t\tgoto cleanup;\n \tis_bare = is_bare_repository();\n+\tcreate_only = follow_remote_head == 2 ? 0 : !is_bare;\n \tif (is_bare) {\n \t\tstrbuf_addstr(&b_head, \"HEAD\");\n \t\tstrbuf_addf(&b_remote_head, \"refs/heads/%s\", head_name);\n@@ -1626,9 +1654,14 @@ static int set_head(const struct ref *remote_refs)\n \t\tresult = 1;\n \t\tgoto cleanup;\n \t}\n-\tif (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n-\t\t\t\t\t\"fetch\", NULL, !is_bare))\n+\twas_detached = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n+\t\t\t\t\t\"fetch\", &b_local_head, create_only);\n+\tif (was_detached == -1) {\n \t\tresult = 1;\n+\t\tgoto cleanup;\n+\t}\n+\tif (follow_remote_head == FOLLOW_REMOTE_WARN && verbosity >= 0)\n+\t\treport_set_head(remote, head_name, &b_local_head, was_detached);\n \n cleanup:\n \tfree(head_name);\n@@ -1636,6 +1669,7 @@ static int set_head(const struct ref *remote_refs)\n \tfree_refs(matches);\n \tstring_list_clear(&heads, 0);\n \tstrbuf_release(&b_head);\n+\tstrbuf_release(&b_local_head);\n \tstrbuf_release(&b_remote_head);\n \treturn result;\n }\n@@ -1855,7 +1889,7 @@ static int do_fetch(struct transport *transport,\n \t\t\t\t  \"you need to specify exactly one branch with the --set-upstream option\"));\n \t\t}\n \t}\n-\tif (set_head(remote_refs))\n+\tif (set_head(remote_refs, transport->remote->follow_remote_head))\n \t\t;\n \t\t/*\n \t\t * Way too many cases where this can go wrong\ndiff --git a/remote.c b/remote.c\nindex 10104d11e3..0b18840d43 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -514,6 +514,15 @@ static int handle_config(const char *key, const char *value,\n \t} else if (!strcmp(subkey, \"serveroption\")) {\n \t\treturn parse_transport_option(key, value,\n \t\t\t\t\t      &remote->server_options);\n+\t} else if (!strcmp(subkey, \"followremotehead\")) {\n+\t\tif (!strcmp(value, \"never\"))\n+\t\t\tremote->follow_remote_head = FOLLOW_REMOTE_NEVER;\n+\t\telse if (!strcmp(value, \"create\"))\n+\t\t\tremote->follow_remote_head = FOLLOW_REMOTE_CREATE;\n+\t\telse if (!strcmp(value, \"warn\"))\n+\t\t\tremote->follow_remote_head = FOLLOW_REMOTE_WARN;\n+\t\telse if (!strcmp(value, \"always\"))\n+\t\t\tremote->follow_remote_head = FOLLOW_REMOTE_ALWAYS;\n \t}\n \treturn 0;\n }\ndiff --git a/remote.h b/remote.h\nindex a7e5c4e07c..184b35653d 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -59,6 +59,13 @@ struct remote_state {\n void remote_state_clear(struct remote_state *remote_state);\n struct remote_state *remote_state_new(void);\n \n+\tenum follow_remote_head_settings {\n+\t\tFOLLOW_REMOTE_NEVER = -1,\n+\t\tFOLLOW_REMOTE_CREATE = 0,\n+\t\tFOLLOW_REMOTE_WARN = 1,\n+\t\tFOLLOW_REMOTE_ALWAYS = 2,\n+\t};\n+\n struct remote {\n \tstruct hashmap_entry ent;\n \n@@ -107,6 +114,8 @@ struct remote {\n \tchar *http_proxy_authmethod;\n \n \tstruct string_list server_options;\n+\n+\tenum follow_remote_head_settings follow_remote_head;\n };\n \n /**\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex 87698341f5..2467027d34 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -99,6 +99,108 @@ test_expect_success \"fetch test remote HEAD change\" '\n \tbranch=$(git rev-parse refs/remotes/origin/other) &&\n \ttest \"z$head\" = \"z$branch\"'\n \n+test_expect_success \"fetch test followRemoteHEAD never\" '\n+\ttest_when_finished \"git config unset remote.origin.followRemoteHEAD\" &&\n+\t(\n+\t\tcd \"$D\" &&\n+\t\tcd two &&\n+\t\tgit update-ref --no-deref -d refs/remotes/origin/HEAD &&\n+\t\tgit config set remote.origin.followRemoteHEAD \"never\" &&\n+\t\tgit fetch &&\n+\t\ttest_must_fail git rev-parse --verify refs/remotes/origin/HEAD\n+\t)\n+'\n+\n+test_expect_success \"fetch test followRemoteHEAD warn no change\" '\n+\ttest_when_finished \"git config unset remote.origin.followRemoteHEAD\" &&\n+\t(\n+\t\tcd \"$D\" &&\n+\t\tcd two &&\n+\t\tgit rev-parse --verify refs/remotes/origin/other &&\n+\t\tgit remote set-head origin other &&\n+\t\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\t\tgit rev-parse --verify refs/remotes/origin/main &&\n+\t\tgit config set remote.origin.followRemoteHEAD \"warn\" &&\n+\t\tgit fetch >output &&\n+\t\techo \"${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ},\" \\\n+\t\t\t\"but we have ${SQ}other${SQ} locally.\" >expect &&\n+\t\techo \"Run ${SQ}git remote set-head origin main${SQ} to follow the change.\" >>expect &&\n+\t\ttest_cmp expect output &&\n+\t\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\t\tbranch=$(git rev-parse refs/remotes/origin/other) &&\n+\t\ttest \"z$head\" = \"z$branch\"\n+\t)\n+'\n+\n+test_expect_success \"fetch test followRemoteHEAD warn create\" '\n+\ttest_when_finished \"git config unset remote.origin.followRemoteHEAD\" &&\n+\t(\n+\t\tcd \"$D\" &&\n+\t\tcd two &&\n+\t\tgit update-ref --no-deref -d refs/remotes/origin/HEAD &&\n+\t\tgit config set remote.origin.followRemoteHEAD \"warn\" &&\n+\t\tgit rev-parse --verify refs/remotes/origin/main &&\n+\t\toutput=$(git fetch) &&\n+\t\ttest \"z\" = \"z$output\" &&\n+\t\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\t\tbranch=$(git rev-parse refs/remotes/origin/main) &&\n+\t\ttest \"z$head\" = \"z$branch\"\n+\t)\n+'\n+\n+test_expect_success \"fetch test followRemoteHEAD warn detached\" '\n+\ttest_when_finished \"git config unset remote.origin.followRemoteHEAD\" &&\n+\t(\n+\t\tcd \"$D\" &&\n+\t\tcd two &&\n+\t\tgit update-ref --no-deref -d refs/remotes/origin/HEAD &&\n+\t\tgit update-ref refs/remotes/origin/HEAD HEAD &&\n+\t\tHEAD=$(git log --pretty=\"%H\") &&\n+\t\tgit config set remote.origin.followRemoteHEAD \"warn\" &&\n+\t\tgit fetch >output &&\n+\t\techo \"${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ},\" \\\n+\t\t\t\"but we have a detached HEAD pointing to\" \\\n+\t\t\t\"${SQ}${HEAD}${SQ} locally.\" >expect &&\n+\t\techo \"Run ${SQ}git remote set-head origin main${SQ} to follow the change.\" >>expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n+test_expect_success \"fetch test followRemoteHEAD warn quiet\" '\n+\ttest_when_finished \"git config unset remote.origin.followRemoteHEAD\" &&\n+\t(\n+\t\tcd \"$D\" &&\n+\t\tcd two &&\n+\t\tgit rev-parse --verify refs/remotes/origin/other &&\n+\t\tgit remote set-head origin other &&\n+\t\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\t\tgit rev-parse --verify refs/remotes/origin/main &&\n+\t\tgit config set remote.origin.followRemoteHEAD \"warn\" &&\n+\t\toutput=$(git fetch --quiet) &&\n+\t\ttest \"z\" = \"z$output\" &&\n+\t\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\t\tbranch=$(git rev-parse refs/remotes/origin/other) &&\n+\t\ttest \"z$head\" = \"z$branch\"\n+\t)\n+'\n+\n+test_expect_success \"fetch test followRemoteHEAD always\" '\n+\ttest_when_finished \"git config unset remote.origin.followRemoteHEAD\" &&\n+\t(\n+\t\tcd \"$D\" &&\n+\t\tcd two &&\n+\t\tgit rev-parse --verify refs/remotes/origin/other &&\n+\t\tgit remote set-head origin other &&\n+\t\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\t\tgit rev-parse --verify refs/remotes/origin/main &&\n+\t\tgit config set remote.origin.followRemoteHEAD \"always\" &&\n+\t\tgit fetch &&\n+\t\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\t\tbranch=$(git rev-parse refs/remotes/origin/main) &&\n+\t\ttest \"z$head\" = \"z$branch\"\n+\t)\n+'\n+\n test_expect_success 'fetch --prune on its own works as expected' '\n \tcd \"$D\" &&\n \tgit clone . prune &&\n-- \n2.47.1.295.g12e80f91dc.dirty\n\n"},{"id":"508311","messageId":"20241128230806.2058962-1-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241128111946.366584-1-bence@ferdinandy.com","subject":"[PATCH v3] fetch: add configuration for set_head behaviour","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-28T23:06:46Z","receivedAt":"2024-11-28T23:09:35Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"In the current implementation, if refs/remotes/$remote/HEAD does not\nexist, running fetch will create it, but if it does exist it will not do\nanything, which is a somewhat safe and minimal approach. Unfortunately,\nfor users who wish to NOT have refs/remotes/$remote/HEAD set for any\nreason (e.g. so that `git rev-parse origin` doesn't accidentally point\nthem somewhere they do not want to), there is no way to remove this\nbehaviour. On the other side of the spectrum, users may want fetch to\nautomatically update HEAD or at least give them a warning if something\nchanged on the remote.\n\nIntroduce a new setting, remote.$remote.followRemoteHEAD with four\noptions:\n\n    - \"never\": do not ever do anything, not even create\n    - \"create\": the current behaviour, now the default behaviour\n    - \"warn\": print a message if remote and local HEAD is different\n    - \"always\": silently update HEAD on every change\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    This patch is built off v15 of the set-head/fetch updates series:\n    https://lore.kernel.org/git/D5WEJJBMSO1K.2TPXDI1K08SHT@ferdinandy.com/T/#m13a0c3e8919872188ef07fd9fd984c78e8aa35ba\n    \n    v2:\n        - magic numbers converted to enum\n        - since the enum is descriptive (well, along with the documentation)\n          I felt there is no need for the comment explaining the values\n    \n    v3: missed a magic number in v2\n\n Documentation/config/remote.txt |  11 ++++\n builtin/fetch.c                 |  46 ++++++++++++--\n remote.c                        |   9 +++\n remote.h                        |   9 +++\n t/t5510-fetch.sh                | 102 ++++++++++++++++++++++++++++++++\n 5 files changed, 171 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/config/remote.txt b/Documentation/config/remote.txt\nindex 6d8b7d6c63..024f92befc 100644\n--- a/Documentation/config/remote.txt\n+++ b/Documentation/config/remote.txt\n@@ -101,6 +101,17 @@ remote.<name>.serverOption::\n \tThe default set of server options used when fetching from this remote.\n \tThese server options can be overridden by the `--server-option=` command\n \tline arguments.\n+\n+remote.<name>.followRemoteHEAD::\n+\tHow linkgit:git-fetch[1] should handle updates to `remotes/<name>/HEAD`.\n+\tThe default value is \"create\", which will create `remotes/<name>/HEAD`\n+\tif it exists on the remote, but not locally, but will not touch an\n+\talready existing local reference.  Setting to \"warn\" will print\n+\ta message if the remote has a different value, than the local one and\n+\tin case there is no local reference, it behaves like \"create\". Setting\n+\tto \"always\" will silently update it to the value on the remote.\n+\tFinally, setting it to \"never\" will never change or create the local\n+\treference.\n +\n This is a multi-valued variable, and an empty value can be used in a higher\n priority configuration file (e.g. `.git/config` in a repository) to clear\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 2f416cf867..88c5c5d781 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1579,10 +1579,35 @@ static const char *strip_refshead(const char *name){\n \treturn name;\n }\n \n-static int set_head(const struct ref *remote_refs)\n+static void report_set_head(const char *remote, const char *head_name,\n+\t\t\tstruct strbuf *buf_prev, int updateres) {\n+\tstruct strbuf buf_prefix = STRBUF_INIT;\n+\tconst char *prev_head = NULL;\n+\n+\tstrbuf_addf(&buf_prefix, \"refs/remotes/%s/\", remote);\n+\tskip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);\n+\n+\tif (prev_head && strcmp(prev_head, head_name)) {\n+\t\tprintf(\"'HEAD' at '%s' is '%s', but we have '%s' locally.\\n\",\n+\t\t\tremote, head_name, prev_head);\n+\t\tprintf(\"Run 'git remote set-head %s %s' to follow the change.\\n\",\n+\t\t\tremote, head_name);\n+\t}\n+\telse if (updateres && buf_prev->len) {\n+\t\tprintf(\"'HEAD' at '%s' is '%s', \"\n+\t\t\t\"but we have a detached HEAD pointing to '%s' locally.\\n\",\n+\t\t\tremote, head_name, buf_prev->buf);\n+\t\tprintf(\"Run 'git remote set-head %s %s' to follow the change.\\n\",\n+\t\t\tremote, head_name);\n+\t}\n+\tstrbuf_release(&buf_prefix);\n+}\n+\n+static int set_head(const struct ref *remote_refs, int follow_remote_head)\n {\n-\tint result = 0, is_bare;\n-\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;\n+\tint result = 0, create_only, is_bare, was_detached;\n+\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,\n+\t\t      b_local_head = STRBUF_INIT;\n \tconst char *remote = gtransport->remote->name;\n \tchar *head_name = NULL;\n \tstruct ref *ref, *matches;\n@@ -1603,6 +1628,8 @@ static int set_head(const struct ref *remote_refs)\n \t\tstring_list_append(&heads, strip_refshead(ref->name));\n \t}\n \n+\tif (follow_remote_head == FOLLOW_REMOTE_NEVER)\n+\t\tgoto cleanup;\n \n \tif (!heads.nr)\n \t\tresult = 1;\n@@ -1614,6 +1641,7 @@ static int set_head(const struct ref *remote_refs)\n \tif (!head_name)\n \t\tgoto cleanup;\n \tis_bare = is_bare_repository();\n+\tcreate_only = follow_remote_head == FOLLOW_REMOTE_ALWAYS ? 0 : !is_bare;\n \tif (is_bare) {\n \t\tstrbuf_addstr(&b_head, \"HEAD\");\n \t\tstrbuf_addf(&b_remote_head, \"refs/heads/%s\", head_name);\n@@ -1626,9 +1654,14 @@ static int set_head(const struct ref *remote_refs)\n \t\tresult = 1;\n \t\tgoto cleanup;\n \t}\n-\tif (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n-\t\t\t\t\t\"fetch\", NULL, !is_bare))\n+\twas_detached = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n+\t\t\t\t\t\"fetch\", &b_local_head, create_only);\n+\tif (was_detached == -1) {\n \t\tresult = 1;\n+\t\tgoto cleanup;\n+\t}\n+\tif (follow_remote_head == FOLLOW_REMOTE_WARN && verbosity >= 0)\n+\t\treport_set_head(remote, head_name, &b_local_head, was_detached);\n \n cleanup:\n \tfree(head_name);\n@@ -1636,6 +1669,7 @@ static int set_head(const struct ref *remote_refs)\n \tfree_refs(matches);\n \tstring_list_clear(&heads, 0);\n \tstrbuf_release(&b_head);\n+\tstrbuf_release(&b_local_head);\n \tstrbuf_release(&b_remote_head);\n \treturn result;\n }\n@@ -1855,7 +1889,7 @@ static int do_fetch(struct transport *transport,\n \t\t\t\t  \"you need to specify exactly one branch with the --set-upstream option\"));\n \t\t}\n \t}\n-\tif (set_head(remote_refs))\n+\tif (set_head(remote_refs, transport->remote->follow_remote_head))\n \t\t;\n \t\t/*\n \t\t * Way too many cases where this can go wrong\ndiff --git a/remote.c b/remote.c\nindex 10104d11e3..0b18840d43 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -514,6 +514,15 @@ static int handle_config(const char *key, const char *value,\n \t} else if (!strcmp(subkey, \"serveroption\")) {\n \t\treturn parse_transport_option(key, value,\n \t\t\t\t\t      &remote->server_options);\n+\t} else if (!strcmp(subkey, \"followremotehead\")) {\n+\t\tif (!strcmp(value, \"never\"))\n+\t\t\tremote->follow_remote_head = FOLLOW_REMOTE_NEVER;\n+\t\telse if (!strcmp(value, \"create\"))\n+\t\t\tremote->follow_remote_head = FOLLOW_REMOTE_CREATE;\n+\t\telse if (!strcmp(value, \"warn\"))\n+\t\t\tremote->follow_remote_head = FOLLOW_REMOTE_WARN;\n+\t\telse if (!strcmp(value, \"always\"))\n+\t\t\tremote->follow_remote_head = FOLLOW_REMOTE_ALWAYS;\n \t}\n \treturn 0;\n }\ndiff --git a/remote.h b/remote.h\nindex a7e5c4e07c..184b35653d 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -59,6 +59,13 @@ struct remote_state {\n void remote_state_clear(struct remote_state *remote_state);\n struct remote_state *remote_state_new(void);\n \n+\tenum follow_remote_head_settings {\n+\t\tFOLLOW_REMOTE_NEVER = -1,\n+\t\tFOLLOW_REMOTE_CREATE = 0,\n+\t\tFOLLOW_REMOTE_WARN = 1,\n+\t\tFOLLOW_REMOTE_ALWAYS = 2,\n+\t};\n+\n struct remote {\n \tstruct hashmap_entry ent;\n \n@@ -107,6 +114,8 @@ struct remote {\n \tchar *http_proxy_authmethod;\n \n \tstruct string_list server_options;\n+\n+\tenum follow_remote_head_settings follow_remote_head;\n };\n \n /**\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex 87698341f5..2467027d34 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -99,6 +99,108 @@ test_expect_success \"fetch test remote HEAD change\" '\n \tbranch=$(git rev-parse refs/remotes/origin/other) &&\n \ttest \"z$head\" = \"z$branch\"'\n \n+test_expect_success \"fetch test followRemoteHEAD never\" '\n+\ttest_when_finished \"git config unset remote.origin.followRemoteHEAD\" &&\n+\t(\n+\t\tcd \"$D\" &&\n+\t\tcd two &&\n+\t\tgit update-ref --no-deref -d refs/remotes/origin/HEAD &&\n+\t\tgit config set remote.origin.followRemoteHEAD \"never\" &&\n+\t\tgit fetch &&\n+\t\ttest_must_fail git rev-parse --verify refs/remotes/origin/HEAD\n+\t)\n+'\n+\n+test_expect_success \"fetch test followRemoteHEAD warn no change\" '\n+\ttest_when_finished \"git config unset remote.origin.followRemoteHEAD\" &&\n+\t(\n+\t\tcd \"$D\" &&\n+\t\tcd two &&\n+\t\tgit rev-parse --verify refs/remotes/origin/other &&\n+\t\tgit remote set-head origin other &&\n+\t\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\t\tgit rev-parse --verify refs/remotes/origin/main &&\n+\t\tgit config set remote.origin.followRemoteHEAD \"warn\" &&\n+\t\tgit fetch >output &&\n+\t\techo \"${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ},\" \\\n+\t\t\t\"but we have ${SQ}other${SQ} locally.\" >expect &&\n+\t\techo \"Run ${SQ}git remote set-head origin main${SQ} to follow the change.\" >>expect &&\n+\t\ttest_cmp expect output &&\n+\t\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\t\tbranch=$(git rev-parse refs/remotes/origin/other) &&\n+\t\ttest \"z$head\" = \"z$branch\"\n+\t)\n+'\n+\n+test_expect_success \"fetch test followRemoteHEAD warn create\" '\n+\ttest_when_finished \"git config unset remote.origin.followRemoteHEAD\" &&\n+\t(\n+\t\tcd \"$D\" &&\n+\t\tcd two &&\n+\t\tgit update-ref --no-deref -d refs/remotes/origin/HEAD &&\n+\t\tgit config set remote.origin.followRemoteHEAD \"warn\" &&\n+\t\tgit rev-parse --verify refs/remotes/origin/main &&\n+\t\toutput=$(git fetch) &&\n+\t\ttest \"z\" = \"z$output\" &&\n+\t\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\t\tbranch=$(git rev-parse refs/remotes/origin/main) &&\n+\t\ttest \"z$head\" = \"z$branch\"\n+\t)\n+'\n+\n+test_expect_success \"fetch test followRemoteHEAD warn detached\" '\n+\ttest_when_finished \"git config unset remote.origin.followRemoteHEAD\" &&\n+\t(\n+\t\tcd \"$D\" &&\n+\t\tcd two &&\n+\t\tgit update-ref --no-deref -d refs/remotes/origin/HEAD &&\n+\t\tgit update-ref refs/remotes/origin/HEAD HEAD &&\n+\t\tHEAD=$(git log --pretty=\"%H\") &&\n+\t\tgit config set remote.origin.followRemoteHEAD \"warn\" &&\n+\t\tgit fetch >output &&\n+\t\techo \"${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ},\" \\\n+\t\t\t\"but we have a detached HEAD pointing to\" \\\n+\t\t\t\"${SQ}${HEAD}${SQ} locally.\" >expect &&\n+\t\techo \"Run ${SQ}git remote set-head origin main${SQ} to follow the change.\" >>expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n+test_expect_success \"fetch test followRemoteHEAD warn quiet\" '\n+\ttest_when_finished \"git config unset remote.origin.followRemoteHEAD\" &&\n+\t(\n+\t\tcd \"$D\" &&\n+\t\tcd two &&\n+\t\tgit rev-parse --verify refs/remotes/origin/other &&\n+\t\tgit remote set-head origin other &&\n+\t\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\t\tgit rev-parse --verify refs/remotes/origin/main &&\n+\t\tgit config set remote.origin.followRemoteHEAD \"warn\" &&\n+\t\toutput=$(git fetch --quiet) &&\n+\t\ttest \"z\" = \"z$output\" &&\n+\t\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\t\tbranch=$(git rev-parse refs/remotes/origin/other) &&\n+\t\ttest \"z$head\" = \"z$branch\"\n+\t)\n+'\n+\n+test_expect_success \"fetch test followRemoteHEAD always\" '\n+\ttest_when_finished \"git config unset remote.origin.followRemoteHEAD\" &&\n+\t(\n+\t\tcd \"$D\" &&\n+\t\tcd two &&\n+\t\tgit rev-parse --verify refs/remotes/origin/other &&\n+\t\tgit remote set-head origin other &&\n+\t\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\t\tgit rev-parse --verify refs/remotes/origin/main &&\n+\t\tgit config set remote.origin.followRemoteHEAD \"always\" &&\n+\t\tgit fetch &&\n+\t\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\t\tbranch=$(git rev-parse refs/remotes/origin/main) &&\n+\t\ttest \"z$head\" = \"z$branch\"\n+\t)\n+'\n+\n test_expect_success 'fetch --prune on its own works as expected' '\n \tcd \"$D\" &&\n \tgit clone . prune &&\n\nRange-diff against v2:\n1:  60986e890e ! 1:  4e9ac5524a fetch: add configuration for set_head behaviour\n    @@ Notes\n             - since the enum is descriptive (well, along with the documentation)\n               I felt there is no need for the comment explaining the values\n     \n    +    v3: missed a magic number in v2\n    +\n      ## Documentation/config/remote.txt ##\n     @@ Documentation/config/remote.txt: remote.<name>.serverOption::\n      \tThe default set of server options used when fetching from this remote.\n    @@ builtin/fetch.c: static int set_head(const struct ref *remote_refs)\n      \tif (!head_name)\n      \t\tgoto cleanup;\n      \tis_bare = is_bare_repository();\n    -+\tcreate_only = follow_remote_head == 2 ? 0 : !is_bare;\n    ++\tcreate_only = follow_remote_head == FOLLOW_REMOTE_ALWAYS ? 0 : !is_bare;\n      \tif (is_bare) {\n      \t\tstrbuf_addstr(&b_head, \"HEAD\");\n      \t\tstrbuf_addf(&b_remote_head, \"refs/heads/%s\", head_name);\n-- \n2.47.1.295.g66bad26ef4\n\n"},{"id":"508312","messageId":"20241128230944.2114228-1-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241128111946.366584-1-bence@ferdinandy.com","subject":"[PATCH v3] fetch: add configuration for set_head behaviour","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-11-28T23:09:38Z","receivedAt":"2024-11-28T23:11:08Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"In the current implementation, if refs/remotes/$remote/HEAD does not\nexist, running fetch will create it, but if it does exist it will not do\nanything, which is a somewhat safe and minimal approach. Unfortunately,\nfor users who wish to NOT have refs/remotes/$remote/HEAD set for any\nreason (e.g. so that `git rev-parse origin` doesn't accidentally point\nthem somewhere they do not want to), there is no way to remove this\nbehaviour. On the other side of the spectrum, users may want fetch to\nautomatically update HEAD or at least give them a warning if something\nchanged on the remote.\n\nIntroduce a new setting, remote.$remote.followRemoteHEAD with four\noptions:\n\n    - \"never\": do not ever do anything, not even create\n    - \"create\": the current behaviour, now the default behaviour\n    - \"warn\": print a message if remote and local HEAD is different\n    - \"always\": silently update HEAD on every change\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    This patch is built off v15 of the set-head/fetch updates series:\n    https://lore.kernel.org/git/D5WEJJBMSO1K.2TPXDI1K08SHT@ferdinandy.com/T/#m13a0c3e8919872188ef07fd9fd984c78e8aa35ba\n    \n    v2:\n        - magic numbers converted to enum\n        - since the enum is descriptive (well, along with the documentation)\n          I felt there is no need for the comment explaining the values\n    \n    v3: missed a magic number in v2\n\n Documentation/config/remote.txt |  11 ++++\n builtin/fetch.c                 |  46 ++++++++++++--\n remote.c                        |   9 +++\n remote.h                        |   9 +++\n t/t5510-fetch.sh                | 102 ++++++++++++++++++++++++++++++++\n 5 files changed, 171 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/config/remote.txt b/Documentation/config/remote.txt\nindex 6d8b7d6c63..024f92befc 100644\n--- a/Documentation/config/remote.txt\n+++ b/Documentation/config/remote.txt\n@@ -101,6 +101,17 @@ remote.<name>.serverOption::\n \tThe default set of server options used when fetching from this remote.\n \tThese server options can be overridden by the `--server-option=` command\n \tline arguments.\n+\n+remote.<name>.followRemoteHEAD::\n+\tHow linkgit:git-fetch[1] should handle updates to `remotes/<name>/HEAD`.\n+\tThe default value is \"create\", which will create `remotes/<name>/HEAD`\n+\tif it exists on the remote, but not locally, but will not touch an\n+\talready existing local reference.  Setting to \"warn\" will print\n+\ta message if the remote has a different value, than the local one and\n+\tin case there is no local reference, it behaves like \"create\". Setting\n+\tto \"always\" will silently update it to the value on the remote.\n+\tFinally, setting it to \"never\" will never change or create the local\n+\treference.\n +\n This is a multi-valued variable, and an empty value can be used in a higher\n priority configuration file (e.g. `.git/config` in a repository) to clear\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 2f416cf867..88c5c5d781 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1579,10 +1579,35 @@ static const char *strip_refshead(const char *name){\n \treturn name;\n }\n \n-static int set_head(const struct ref *remote_refs)\n+static void report_set_head(const char *remote, const char *head_name,\n+\t\t\tstruct strbuf *buf_prev, int updateres) {\n+\tstruct strbuf buf_prefix = STRBUF_INIT;\n+\tconst char *prev_head = NULL;\n+\n+\tstrbuf_addf(&buf_prefix, \"refs/remotes/%s/\", remote);\n+\tskip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);\n+\n+\tif (prev_head && strcmp(prev_head, head_name)) {\n+\t\tprintf(\"'HEAD' at '%s' is '%s', but we have '%s' locally.\\n\",\n+\t\t\tremote, head_name, prev_head);\n+\t\tprintf(\"Run 'git remote set-head %s %s' to follow the change.\\n\",\n+\t\t\tremote, head_name);\n+\t}\n+\telse if (updateres && buf_prev->len) {\n+\t\tprintf(\"'HEAD' at '%s' is '%s', \"\n+\t\t\t\"but we have a detached HEAD pointing to '%s' locally.\\n\",\n+\t\t\tremote, head_name, buf_prev->buf);\n+\t\tprintf(\"Run 'git remote set-head %s %s' to follow the change.\\n\",\n+\t\t\tremote, head_name);\n+\t}\n+\tstrbuf_release(&buf_prefix);\n+}\n+\n+static int set_head(const struct ref *remote_refs, int follow_remote_head)\n {\n-\tint result = 0, is_bare;\n-\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;\n+\tint result = 0, create_only, is_bare, was_detached;\n+\tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,\n+\t\t      b_local_head = STRBUF_INIT;\n \tconst char *remote = gtransport->remote->name;\n \tchar *head_name = NULL;\n \tstruct ref *ref, *matches;\n@@ -1603,6 +1628,8 @@ static int set_head(const struct ref *remote_refs)\n \t\tstring_list_append(&heads, strip_refshead(ref->name));\n \t}\n \n+\tif (follow_remote_head == FOLLOW_REMOTE_NEVER)\n+\t\tgoto cleanup;\n \n \tif (!heads.nr)\n \t\tresult = 1;\n@@ -1614,6 +1641,7 @@ static int set_head(const struct ref *remote_refs)\n \tif (!head_name)\n \t\tgoto cleanup;\n \tis_bare = is_bare_repository();\n+\tcreate_only = follow_remote_head == FOLLOW_REMOTE_ALWAYS ? 0 : !is_bare;\n \tif (is_bare) {\n \t\tstrbuf_addstr(&b_head, \"HEAD\");\n \t\tstrbuf_addf(&b_remote_head, \"refs/heads/%s\", head_name);\n@@ -1626,9 +1654,14 @@ static int set_head(const struct ref *remote_refs)\n \t\tresult = 1;\n \t\tgoto cleanup;\n \t}\n-\tif (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n-\t\t\t\t\t\"fetch\", NULL, !is_bare))\n+\twas_detached = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,\n+\t\t\t\t\t\"fetch\", &b_local_head, create_only);\n+\tif (was_detached == -1) {\n \t\tresult = 1;\n+\t\tgoto cleanup;\n+\t}\n+\tif (follow_remote_head == FOLLOW_REMOTE_WARN && verbosity >= 0)\n+\t\treport_set_head(remote, head_name, &b_local_head, was_detached);\n \n cleanup:\n \tfree(head_name);\n@@ -1636,6 +1669,7 @@ static int set_head(const struct ref *remote_refs)\n \tfree_refs(matches);\n \tstring_list_clear(&heads, 0);\n \tstrbuf_release(&b_head);\n+\tstrbuf_release(&b_local_head);\n \tstrbuf_release(&b_remote_head);\n \treturn result;\n }\n@@ -1855,7 +1889,7 @@ static int do_fetch(struct transport *transport,\n \t\t\t\t  \"you need to specify exactly one branch with the --set-upstream option\"));\n \t\t}\n \t}\n-\tif (set_head(remote_refs))\n+\tif (set_head(remote_refs, transport->remote->follow_remote_head))\n \t\t;\n \t\t/*\n \t\t * Way too many cases where this can go wrong\ndiff --git a/remote.c b/remote.c\nindex 10104d11e3..0b18840d43 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -514,6 +514,15 @@ static int handle_config(const char *key, const char *value,\n \t} else if (!strcmp(subkey, \"serveroption\")) {\n \t\treturn parse_transport_option(key, value,\n \t\t\t\t\t      &remote->server_options);\n+\t} else if (!strcmp(subkey, \"followremotehead\")) {\n+\t\tif (!strcmp(value, \"never\"))\n+\t\t\tremote->follow_remote_head = FOLLOW_REMOTE_NEVER;\n+\t\telse if (!strcmp(value, \"create\"))\n+\t\t\tremote->follow_remote_head = FOLLOW_REMOTE_CREATE;\n+\t\telse if (!strcmp(value, \"warn\"))\n+\t\t\tremote->follow_remote_head = FOLLOW_REMOTE_WARN;\n+\t\telse if (!strcmp(value, \"always\"))\n+\t\t\tremote->follow_remote_head = FOLLOW_REMOTE_ALWAYS;\n \t}\n \treturn 0;\n }\ndiff --git a/remote.h b/remote.h\nindex a7e5c4e07c..184b35653d 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -59,6 +59,13 @@ struct remote_state {\n void remote_state_clear(struct remote_state *remote_state);\n struct remote_state *remote_state_new(void);\n \n+\tenum follow_remote_head_settings {\n+\t\tFOLLOW_REMOTE_NEVER = -1,\n+\t\tFOLLOW_REMOTE_CREATE = 0,\n+\t\tFOLLOW_REMOTE_WARN = 1,\n+\t\tFOLLOW_REMOTE_ALWAYS = 2,\n+\t};\n+\n struct remote {\n \tstruct hashmap_entry ent;\n \n@@ -107,6 +114,8 @@ struct remote {\n \tchar *http_proxy_authmethod;\n \n \tstruct string_list server_options;\n+\n+\tenum follow_remote_head_settings follow_remote_head;\n };\n \n /**\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex 87698341f5..2467027d34 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -99,6 +99,108 @@ test_expect_success \"fetch test remote HEAD change\" '\n \tbranch=$(git rev-parse refs/remotes/origin/other) &&\n \ttest \"z$head\" = \"z$branch\"'\n \n+test_expect_success \"fetch test followRemoteHEAD never\" '\n+\ttest_when_finished \"git config unset remote.origin.followRemoteHEAD\" &&\n+\t(\n+\t\tcd \"$D\" &&\n+\t\tcd two &&\n+\t\tgit update-ref --no-deref -d refs/remotes/origin/HEAD &&\n+\t\tgit config set remote.origin.followRemoteHEAD \"never\" &&\n+\t\tgit fetch &&\n+\t\ttest_must_fail git rev-parse --verify refs/remotes/origin/HEAD\n+\t)\n+'\n+\n+test_expect_success \"fetch test followRemoteHEAD warn no change\" '\n+\ttest_when_finished \"git config unset remote.origin.followRemoteHEAD\" &&\n+\t(\n+\t\tcd \"$D\" &&\n+\t\tcd two &&\n+\t\tgit rev-parse --verify refs/remotes/origin/other &&\n+\t\tgit remote set-head origin other &&\n+\t\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\t\tgit rev-parse --verify refs/remotes/origin/main &&\n+\t\tgit config set remote.origin.followRemoteHEAD \"warn\" &&\n+\t\tgit fetch >output &&\n+\t\techo \"${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ},\" \\\n+\t\t\t\"but we have ${SQ}other${SQ} locally.\" >expect &&\n+\t\techo \"Run ${SQ}git remote set-head origin main${SQ} to follow the change.\" >>expect &&\n+\t\ttest_cmp expect output &&\n+\t\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\t\tbranch=$(git rev-parse refs/remotes/origin/other) &&\n+\t\ttest \"z$head\" = \"z$branch\"\n+\t)\n+'\n+\n+test_expect_success \"fetch test followRemoteHEAD warn create\" '\n+\ttest_when_finished \"git config unset remote.origin.followRemoteHEAD\" &&\n+\t(\n+\t\tcd \"$D\" &&\n+\t\tcd two &&\n+\t\tgit update-ref --no-deref -d refs/remotes/origin/HEAD &&\n+\t\tgit config set remote.origin.followRemoteHEAD \"warn\" &&\n+\t\tgit rev-parse --verify refs/remotes/origin/main &&\n+\t\toutput=$(git fetch) &&\n+\t\ttest \"z\" = \"z$output\" &&\n+\t\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\t\tbranch=$(git rev-parse refs/remotes/origin/main) &&\n+\t\ttest \"z$head\" = \"z$branch\"\n+\t)\n+'\n+\n+test_expect_success \"fetch test followRemoteHEAD warn detached\" '\n+\ttest_when_finished \"git config unset remote.origin.followRemoteHEAD\" &&\n+\t(\n+\t\tcd \"$D\" &&\n+\t\tcd two &&\n+\t\tgit update-ref --no-deref -d refs/remotes/origin/HEAD &&\n+\t\tgit update-ref refs/remotes/origin/HEAD HEAD &&\n+\t\tHEAD=$(git log --pretty=\"%H\") &&\n+\t\tgit config set remote.origin.followRemoteHEAD \"warn\" &&\n+\t\tgit fetch >output &&\n+\t\techo \"${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ},\" \\\n+\t\t\t\"but we have a detached HEAD pointing to\" \\\n+\t\t\t\"${SQ}${HEAD}${SQ} locally.\" >expect &&\n+\t\techo \"Run ${SQ}git remote set-head origin main${SQ} to follow the change.\" >>expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n+test_expect_success \"fetch test followRemoteHEAD warn quiet\" '\n+\ttest_when_finished \"git config unset remote.origin.followRemoteHEAD\" &&\n+\t(\n+\t\tcd \"$D\" &&\n+\t\tcd two &&\n+\t\tgit rev-parse --verify refs/remotes/origin/other &&\n+\t\tgit remote set-head origin other &&\n+\t\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\t\tgit rev-parse --verify refs/remotes/origin/main &&\n+\t\tgit config set remote.origin.followRemoteHEAD \"warn\" &&\n+\t\toutput=$(git fetch --quiet) &&\n+\t\ttest \"z\" = \"z$output\" &&\n+\t\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\t\tbranch=$(git rev-parse refs/remotes/origin/other) &&\n+\t\ttest \"z$head\" = \"z$branch\"\n+\t)\n+'\n+\n+test_expect_success \"fetch test followRemoteHEAD always\" '\n+\ttest_when_finished \"git config unset remote.origin.followRemoteHEAD\" &&\n+\t(\n+\t\tcd \"$D\" &&\n+\t\tcd two &&\n+\t\tgit rev-parse --verify refs/remotes/origin/other &&\n+\t\tgit remote set-head origin other &&\n+\t\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\t\tgit rev-parse --verify refs/remotes/origin/main &&\n+\t\tgit config set remote.origin.followRemoteHEAD \"always\" &&\n+\t\tgit fetch &&\n+\t\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\t\tbranch=$(git rev-parse refs/remotes/origin/main) &&\n+\t\ttest \"z$head\" = \"z$branch\"\n+\t)\n+'\n+\n test_expect_success 'fetch --prune on its own works as expected' '\n \tcd \"$D\" &&\n \tgit clone . prune &&\n\nRange-diff against v2:\n1:  60986e890e ! 1:  4e9ac5524a fetch: add configuration for set_head behaviour\n    @@ Notes\n             - since the enum is descriptive (well, along with the documentation)\n               I felt there is no need for the comment explaining the values\n     \n    +    v3: missed a magic number in v2\n    +\n      ## Documentation/config/remote.txt ##\n     @@ Documentation/config/remote.txt: remote.<name>.serverOption::\n      \tThe default set of server options used when fetching from this remote.\n    @@ builtin/fetch.c: static int set_head(const struct ref *remote_refs)\n      \tif (!head_name)\n      \t\tgoto cleanup;\n      \tis_bare = is_bare_repository();\n    -+\tcreate_only = follow_remote_head == 2 ? 0 : !is_bare;\n    ++\tcreate_only = follow_remote_head == FOLLOW_REMOTE_ALWAYS ? 0 : !is_bare;\n      \tif (is_bare) {\n      \t\tstrbuf_addstr(&b_head, \"HEAD\");\n      \t\tstrbuf_addf(&b_remote_head, \"refs/heads/%s\", head_name);\n-- \n2.47.1.295.g66bad26ef4\n\n"},{"id":"508404","messageId":"xmqqfrn6lx3n.fsf@gitster.g","threadId":"62096","inReplyTo":"20241128230806.2058962-1-bence@ferdinandy.com","subject":"Re: [PATCH v3] fetch: add configuration for set_head behaviour","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-12-02T00:58:20Z","receivedAt":"2024-12-02T00:58:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bence Ferdinandy <bence@ferdinandy.com> writes:\n\n>     v2:\n>         - magic numbers converted to enum\n>         - since the enum is descriptive (well, along with the documentation)\n>           I felt there is no need for the comment explaining the values\n>     \n>     v3: missed a magic number in v2\n\nBoth changes look quite good.  Will queue.\n\nLet me mark the topic for 'next'.\n\nThanks.\n"},{"id":"508571","messageId":"20241203215713.135068-2-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241203215713.135068-1-bence@ferdinandy.com","subject":"[RFC PATCH v1 1/2] fetch set_head: add warn-if-not-$branch option","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-12-03T21:56:47Z","receivedAt":"2024-12-03T21:58:22Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Currently if we want to have a remote/HEAD locally that is different\nfrom the one on the remote, but we still want to get a warning if remote\nchanges HEAD, our only option is to have an indiscriminate warning with\n\"follow_remote_head\" set to \"warn\". Add a new option\n\"warn-if-not-$branch\", where $branch is a branch name we do not wish to\nget a warning about. If the remote HEAD is $branch do not warn,\notherwise, behave as \"warn\".\n\nE.g. let's assume, that our remote origin has HEAD\nset to \"master\", but locally we have \"git remote set-head origin seen\".\nSetting 'remote.origin.followRemoteHEAD = \"warn\"' will always print\na warning, even though the remote has not changed HEAD from \"master\".\nSetting 'remote.origin.followRemoteHEAD = \"warn-if-not-master\" will\nsquelch the warning message, unless the remote changes HEAD from\n\"master\". Note, that should the remote change HEAD to \"seen\" (which we\nhave locally), there will still be no warning.\n\nImprove the advice message in report_set_head to also include silencing\nthe warning message with \"warn-if-not-$branch\".\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n builtin/fetch.c  | 26 ++++++++++++++++++-------\n remote.c         |  5 +++++\n remote.h         |  6 ++++--\n t/t5510-fetch.sh | 49 ++++++++++++++++++++++++++++++++++++++++++++++--\n 4 files changed, 75 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 88c5c5d781..fd7f3694cc 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1579,6 +1579,15 @@ static const char *strip_refshead(const char *name){\n \treturn name;\n }\n \n+static void set_head_advice_msg(const char *remote, const char *head_name) {\n+\n+\tprintf(\"Run 'git remote set-head %s %s' to follow the change, or\\n\"\n+\t\t\"'git config set remote.%s.warn-if-not-%s' to disable this warning\\n\"\n+\t\t\"until the remote changes HEAD again.\\n\",\n+\t\tremote, head_name, remote, head_name);\n+\n+}\n+\n static void report_set_head(const char *remote, const char *head_name,\n \t\t\tstruct strbuf *buf_prev, int updateres) {\n \tstruct strbuf buf_prefix = STRBUF_INIT;\n@@ -1590,20 +1599,19 @@ static void report_set_head(const char *remote, const char *head_name,\n \tif (prev_head && strcmp(prev_head, head_name)) {\n \t\tprintf(\"'HEAD' at '%s' is '%s', but we have '%s' locally.\\n\",\n \t\t\tremote, head_name, prev_head);\n-\t\tprintf(\"Run 'git remote set-head %s %s' to follow the change.\\n\",\n-\t\t\tremote, head_name);\n+\t\tset_head_advice_msg(remote, head_name);\n \t}\n \telse if (updateres && buf_prev->len) {\n \t\tprintf(\"'HEAD' at '%s' is '%s', \"\n \t\t\t\"but we have a detached HEAD pointing to '%s' locally.\\n\",\n \t\t\tremote, head_name, buf_prev->buf);\n-\t\tprintf(\"Run 'git remote set-head %s %s' to follow the change.\\n\",\n-\t\t\tremote, head_name);\n+\t\tset_head_advice_msg(remote, head_name);\n \t}\n \tstrbuf_release(&buf_prefix);\n }\n \n-static int set_head(const struct ref *remote_refs, int follow_remote_head)\n+static int set_head(const struct ref *remote_refs, int follow_remote_head,\n+\t\tconst char *no_warn_branch)\n {\n \tint result = 0, create_only, is_bare, was_detached;\n \tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,\n@@ -1660,7 +1668,10 @@ static int set_head(const struct ref *remote_refs, int follow_remote_head)\n \t\tresult = 1;\n \t\tgoto cleanup;\n \t}\n-\tif (follow_remote_head == FOLLOW_REMOTE_WARN && verbosity >= 0)\n+\tif ((follow_remote_head == FOLLOW_REMOTE_WARN ||\n+\t\t(follow_remote_head == FOLLOW_REMOTE_WARN_IF_NOT_BRANCH &&\n+\t\tstrcmp(no_warn_branch, head_name))\n+\t\t) && verbosity >= 0)\n \t\treport_set_head(remote, head_name, &b_local_head, was_detached);\n \n cleanup:\n@@ -1889,7 +1900,8 @@ static int do_fetch(struct transport *transport,\n \t\t\t\t  \"you need to specify exactly one branch with the --set-upstream option\"));\n \t\t}\n \t}\n-\tif (set_head(remote_refs, transport->remote->follow_remote_head))\n+\tif (set_head(remote_refs, transport->remote->follow_remote_head,\n+\t\ttransport->remote->no_warn_branch))\n \t\t;\n \t\t/*\n \t\t * Way too many cases where this can go wrong\ndiff --git a/remote.c b/remote.c\nindex 0b18840d43..f0e1b1b76a 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -515,6 +515,7 @@ static int handle_config(const char *key, const char *value,\n \t\treturn parse_transport_option(key, value,\n \t\t\t\t\t      &remote->server_options);\n \t} else if (!strcmp(subkey, \"followremotehead\")) {\n+\t\tconst char *no_warn_branch;\n \t\tif (!strcmp(value, \"never\"))\n \t\t\tremote->follow_remote_head = FOLLOW_REMOTE_NEVER;\n \t\telse if (!strcmp(value, \"create\"))\n@@ -523,6 +524,10 @@ static int handle_config(const char *key, const char *value,\n \t\t\tremote->follow_remote_head = FOLLOW_REMOTE_WARN;\n \t\telse if (!strcmp(value, \"always\"))\n \t\t\tremote->follow_remote_head = FOLLOW_REMOTE_ALWAYS;\n+\t\telse if (skip_prefix(value, \"warn-if-not-\", &no_warn_branch)) {\n+\t\t\tremote->follow_remote_head = FOLLOW_REMOTE_WARN_IF_NOT_BRANCH;\n+\t\t\tremote->no_warn_branch = no_warn_branch;\n+\t\t}\n \t}\n \treturn 0;\n }\ndiff --git a/remote.h b/remote.h\nindex 184b35653d..75be3977ba 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -62,8 +62,9 @@ struct remote_state *remote_state_new(void);\n \tenum follow_remote_head_settings {\n \t\tFOLLOW_REMOTE_NEVER = -1,\n \t\tFOLLOW_REMOTE_CREATE = 0,\n-\t\tFOLLOW_REMOTE_WARN = 1,\n-\t\tFOLLOW_REMOTE_ALWAYS = 2,\n+\t\tFOLLOW_REMOTE_WARN_IF_NOT_BRANCH = 1,\n+\t\tFOLLOW_REMOTE_WARN = 2,\n+\t\tFOLLOW_REMOTE_ALWAYS = 3,\n \t};\n \n struct remote {\n@@ -116,6 +117,7 @@ struct remote {\n \tstruct string_list server_options;\n \n \tenum follow_remote_head_settings follow_remote_head;\n+\tconst char *no_warn_branch;\n };\n \n /**\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex 2467027d34..be0c60be2c 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -124,7 +124,9 @@ test_expect_success \"fetch test followRemoteHEAD warn no change\" '\n \t\tgit fetch >output &&\n \t\techo \"${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ},\" \\\n \t\t\t\"but we have ${SQ}other${SQ} locally.\" >expect &&\n-\t\techo \"Run ${SQ}git remote set-head origin main${SQ} to follow the change.\" >>expect &&\n+\t\techo \"Run ${SQ}git remote set-head origin main${SQ} to follow the change, or\" >>expect &&\n+\t\techo \"${SQ}git config set remote.origin.warn-if-not-main${SQ} to disable this warning\" >>expect &&\n+\t\techo \"until the remote changes HEAD again.\" >>expect &&\n \t\ttest_cmp expect output &&\n \t\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n \t\tbranch=$(git rev-parse refs/remotes/origin/other) &&\n@@ -161,7 +163,9 @@ test_expect_success \"fetch test followRemoteHEAD warn detached\" '\n \t\techo \"${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ},\" \\\n \t\t\t\"but we have a detached HEAD pointing to\" \\\n \t\t\t\"${SQ}${HEAD}${SQ} locally.\" >expect &&\n-\t\techo \"Run ${SQ}git remote set-head origin main${SQ} to follow the change.\" >>expect &&\n+\t\techo \"Run ${SQ}git remote set-head origin main${SQ} to follow the change, or\" >>expect &&\n+\t\techo \"${SQ}git config set remote.origin.warn-if-not-main${SQ} to disable this warning\" >>expect &&\n+\t\techo \"until the remote changes HEAD again.\" >>expect &&\n \t\ttest_cmp expect output\n \t)\n '\n@@ -184,6 +188,47 @@ test_expect_success \"fetch test followRemoteHEAD warn quiet\" '\n \t)\n '\n \n+test_expect_success \"fetch test followRemoteHEAD warn-if-not-branch branch is same\" '\n+\ttest_when_finished \"git config unset remote.origin.followRemoteHEAD\" &&\n+\t(\n+\t\tcd \"$D\" &&\n+\t\tcd two &&\n+\t\tgit rev-parse --verify refs/remotes/origin/other &&\n+\t\tgit remote set-head origin other &&\n+\t\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\t\tgit rev-parse --verify refs/remotes/origin/main &&\n+\t\tgit config set remote.origin.followRemoteHEAD \"warn-if-not-main\" &&\n+\t\toutput=$(git fetch) &&\n+\t\ttest \"z\" = \"z$output\" &&\n+\t\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\t\tbranch=$(git rev-parse refs/remotes/origin/other) &&\n+\t\ttest \"z$head\" = \"z$branch\"\n+\t)\n+'\n+\n+test_expect_success \"fetch test followRemoteHEAD warn-if-not-branch branch is different\" '\n+\ttest_when_finished \"git config unset remote.origin.followRemoteHEAD\" &&\n+\t(\n+\t\tcd \"$D\" &&\n+\t\tcd two &&\n+\t\tgit rev-parse --verify refs/remotes/origin/other &&\n+\t\tgit remote set-head origin other &&\n+\t\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\t\tgit rev-parse --verify refs/remotes/origin/main &&\n+\t\tgit config set remote.origin.followRemoteHEAD \"warn-if-not-some/different-branch\" &&\n+\t\tgit fetch >output &&\n+\t\techo \"${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ},\" \\\n+\t\t\t\"but we have ${SQ}other${SQ} locally.\" >expect &&\n+\t\techo \"Run ${SQ}git remote set-head origin main${SQ} to follow the change, or\" >>expect &&\n+\t\techo \"${SQ}git config set remote.origin.warn-if-not-main${SQ} to disable this warning\" >>expect &&\n+\t\techo \"until the remote changes HEAD again.\" >>expect &&\n+\t\ttest_cmp expect output &&\n+\t\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\t\tbranch=$(git rev-parse refs/remotes/origin/other) &&\n+\t\ttest \"z$head\" = \"z$branch\"\n+\t)\n+'\n+\n test_expect_success \"fetch test followRemoteHEAD always\" '\n \ttest_when_finished \"git config unset remote.origin.followRemoteHEAD\" &&\n \t(\n-- \n2.47.1.297.g6455f89743\n\n"},{"id":"508572","messageId":"20241203215713.135068-1-bence@ferdinandy.com","threadId":"62096","inReplyTo":"xmqqiks7oprx.fsf@gitster.g","subject":"[RFC PATCH v1 0/2] set_head finishing touches","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-12-03T21:56:46Z","receivedAt":"2024-12-03T21:58:25Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"This series is intended to be the finishing touches on the fetch/remote\nset_head changes as in built off of bf/fetch-set-head-config\n<20241128230806.2058962-1-bence@ferdinandy.com>.\n\nPatch 1/2 contains the missing setting Junio was missing, while 2/2 is\nan attempt to do the right thing.\n\nI've marked them as RFC because I'm not sure about a couple of things:\n\n- 1/2 contains a slight refactor by extracting the hints into a separate\n  function to avoid writing it twice. It could be argued that this\n  refactor could be in a separate patch.\n\n- I'm not exactly sure what should be in this hint, I added the new\n  \"warn-if-not-$branch\", but arguments could be made for adding\n  \"always\", adding a generic \"or see the other possible settings in the\n  manual\" or any other combination\n\n- I've seen some examples of advice stuff in yellow that can be\n  disabled, so maybe a complete explanation of possibilites could be\n  done with that mechanism (I haven't yet looked into how that works)\n\n- I'm also not entirely sure about 2/2. I think it's a safe move, that\n  is very unlikely to be annoying, but it's hard to judge from my\n  limited perspective on all the different possible workflows.\n\nBence Ferdinandy (2):\n  fetch set_head: add warn-if-not-$branch option\n  remote set-head: set followRemoteHEAD to \"warn\" if \"always\"\n\n builtin/fetch.c   | 26 ++++++++++++++++++-------\n builtin/remote.c  | 12 +++++++++++-\n remote.c          |  5 +++++\n remote.h          |  6 ++++--\n t/t5505-remote.sh | 11 +++++++++++\n t/t5510-fetch.sh  | 49 +++++++++++++++++++++++++++++++++++++++++++++--\n 6 files changed, 97 insertions(+), 12 deletions(-)\n\n-- \n2.47.1.297.g6455f89743\n\n"},{"id":"508573","messageId":"20241203215713.135068-3-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241203215713.135068-1-bence@ferdinandy.com","subject":"[RFC PATCH v1 2/2] remote set-head: set followRemoteHEAD to \"warn\" if \"always\"","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-12-03T21:56:48Z","receivedAt":"2024-12-03T22:00:08Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"When running \"remote set-head\" manually it is unlikely, that the user\nwould actually like to have \"fetch\" always update the remote/HEAD. On\nthe contrary, it is more likely, that the user would expect remote/HEAD\nto stay the way they manually set it, and just forgot about having\n\"followRemoteHEAD\" set to \"always\".\n\nWhen \"followRemoteHEAD\" is set to \"always\" make running \"remote\nset-head\" change the config to \"warn\".\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n builtin/remote.c  | 12 +++++++++++-\n t/t5505-remote.sh | 11 +++++++++++\n 2 files changed, 22 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 4a8b2ef678..9a30c17724 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1433,6 +1433,7 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t\tb_local_head = STRBUF_INIT;\n \tchar *head_name = NULL;\n \tstruct ref_store *refs = get_main_ref_store(the_repository);\n+\tstruct remote *remote;\n \n \tstruct option options[] = {\n \t\tOPT_BOOL('a', \"auto\", &opt_a,\n@@ -1443,8 +1444,10 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t};\n \targc = parse_options(argc, argv, prefix, options,\n \t\t\t     builtin_remote_sethead_usage, 0);\n-\tif (argc)\n+\tif (argc) {\n \t\tstrbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", argv[0]);\n+\t\tremote = remote_get(argv[0]);\n+\t}\n \n \tif (!opt_a && !opt_d && argc == 2) {\n \t\thead_name = xstrdup(argv[1]);\n@@ -1483,6 +1486,13 @@ static int set_head(int argc, const char **argv, const char *prefix)\n \t}\n \tif (opt_a)\n \t\treport_set_head_auto(argv[0], head_name, &b_local_head, was_detached);\n+\tif (remote->follow_remote_head == FOLLOW_REMOTE_ALWAYS) {\n+\t\tstruct strbuf config_name = STRBUF_INIT;\n+\t\tstrbuf_addf(&config_name,\n+\t\t\t\"remote.%s.followremotehead\", remote->name);\n+\t\tgit_config_set(config_name.buf, \"warn\");\n+\t\tstrbuf_release(&config_name);\n+\t}\n \n cleanup:\n \tfree(head_name);\ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex 2600add82a..76ffdb2ec9 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -505,6 +505,17 @@ test_expect_success 'set-head --auto has no problem w/multiple HEADs' '\n \t)\n '\n \n+test_expect_success 'set-head changes followRemoteHEAD always to warn' '\n+\t(\n+\t\tcd test &&\n+\t\tgit config set remote.origin.followRemoteHEAD \"always\" &&\n+\t\tgit remote set-head --auto origin &&\n+\t\tgit config get remote.origin.followRemoteHEAD >output &&\n+\t\techo \"warn\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n cat >test/expect <<\\EOF\n refs/remotes/origin/side2\n EOF\n-- \n2.47.1.297.g6455f89743\n\n"},{"id":"508592","messageId":"xmqqh67k5gv8.fsf@gitster.g","threadId":"62096","inReplyTo":"20241203215713.135068-2-bence@ferdinandy.com","subject":"Re: [RFC PATCH v1 1/2] fetch set_head: add warn-if-not-$branch option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-12-04T02:20:11Z","receivedAt":"2024-12-04T02:20:14Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bence Ferdinandy <bence@ferdinandy.com> writes:\n\n> +static void set_head_advice_msg(const char *remote, const char *head_name) {\n> +\n> +\tprintf(\"Run 'git remote set-head %s %s' to follow the change, or\\n\"\n> +\t\t\"'git config set remote.%s.warn-if-not-%s' to disable this warning\\n\"\n> +\t\t\"until the remote changes HEAD again.\\n\",\n> +\t\tremote, head_name, remote, head_name);\n> +\n> +}\n\nStyle. \"{\" that encloses the function body sits on a line of its\nown.\n\nPerhaps use the advise_if_enabled(), so that those who already\nlearned how to deal with the situation can squelch the \"how to fix\"\nmessage.\n\n> -static int set_head(const struct ref *remote_refs, int follow_remote_head)\n> +static int set_head(const struct ref *remote_refs, int follow_remote_head,\n> +\t\tconst char *no_warn_branch)\n>  {\n>  \tint result = 0, create_only, is_bare, was_detached;\n>  \tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,\n> @@ -1660,7 +1668,10 @@ static int set_head(const struct ref *remote_refs, int follow_remote_head)\n>  \t\tresult = 1;\n>  \t\tgoto cleanup;\n>  \t}\n> -\tif (follow_remote_head == FOLLOW_REMOTE_WARN && verbosity >= 0)\n> +\tif ((follow_remote_head == FOLLOW_REMOTE_WARN ||\n> +\t\t(follow_remote_head == FOLLOW_REMOTE_WARN_IF_NOT_BRANCH &&\n> +\t\tstrcmp(no_warn_branch, head_name))\n> +\t\t) && verbosity >= 0)\n\nReorder conditions combined with &&- to have more expensive ones\nlater.\n\n\tif (verbosity >= 0 &&\n            (follow_remote_head == FOLLOW_REMOTE_WARN ||\n\t    (follow_remote_head == FOLLOW_REMOTE_WARN_IF_NOT_BRANCH &&\n\t     strcmp(no_warn_branch, head_name)))\n\nAs human readers, we may know that no_warn_branch is always valid\nwhen (follow_remote_head == FOLLOW_REMOTE_WARN_IF_NOT_BRANCH), but\nsemi clever compilers may not realize it and give a false warning\nabout using no_warn_branch potentially uninitialized.\n\nWe could do without adding FOLLOW_REMOTE_WARN_IF_NOT_BRANCH and reuse\nFOLLOW_REMOTE_WARN, like so:\n\n\tif (verbosity >= 0 &&\n            follow_remote_head == FOLLOW_REMOTE_WARN &&\n\t    (!no_warn_branch || strcmp(no_warn_branch, head_name)))\n\nThat is, \"if set to remote-warn, always warn, or no_warn_branch is\nnot NULL, only warn if the current head is different from it\".\n\n> diff --git a/remote.c b/remote.c\n> index 0b18840d43..f0e1b1b76a 100644\n> --- a/remote.c\n> +++ b/remote.c\n> @@ -515,6 +515,7 @@ static int handle_config(const char *key, const char *value,\n>  \t\treturn parse_transport_option(key, value,\n>  \t\t\t\t\t      &remote->server_options);\n>  \t} else if (!strcmp(subkey, \"followremotehead\")) {\n> +\t\tconst char *no_warn_branch;\n>  \t\tif (!strcmp(value, \"never\"))\n>  \t\t\tremote->follow_remote_head = FOLLOW_REMOTE_NEVER;\n>  \t\telse if (!strcmp(value, \"create\"))\n> @@ -523,6 +524,10 @@ static int handle_config(const char *key, const char *value,\n>  \t\t\tremote->follow_remote_head = FOLLOW_REMOTE_WARN;\n>  \t\telse if (!strcmp(value, \"always\"))\n>  \t\t\tremote->follow_remote_head = FOLLOW_REMOTE_ALWAYS;\n> +\t\telse if (skip_prefix(value, \"warn-if-not-\", &no_warn_branch)) {\n> +\t\t\tremote->follow_remote_head = FOLLOW_REMOTE_WARN_IF_NOT_BRANCH;\n> +\t\t\tremote->no_warn_branch = no_warn_branch;\n> +\t\t}\n\nIf we were to do without FOLLOW_REMOTE_WARN_IF_NOT_BRANCH, then the\nabove becomes\n\n\t\t\tremote->follow_remote_head = FOLLOW_REMOTE_WARN;\n\t\t\tremote->no_warn_branch = NULL;\n\t\t} else if (skip_prefix(value, \"warn-if-not-\", &no_warn_branch)) {\n\t\t\tremote->follow_remote_head = FOLLOW_REMOTE_WARN;\n\t\t\tremote->no_warn_branch = no_warn_branch;\n\t\t} else if (!strcmp(value, \"always\")) {\n\t\t\tremote->follow_remote_head = FOLLOW_REMOTE_ALWAYS;\n\t\t} else {\n\t\t\twarn(_(\"unrecognized followRemoteHEAD value '%s' ignored\"),\n\t\t\t     value);\n\t\t}\n\nWe'd want the new choice documented before we graduate this topic\nout of the RFC status.\n\nThanks.\n"},{"id":"508593","messageId":"xmqqbjxs5gs1.fsf@gitster.g","threadId":"62096","inReplyTo":"20241203215713.135068-3-bence@ferdinandy.com","subject":"Re: [RFC PATCH v1 2/2] remote set-head: set followRemoteHEAD to \"warn\" if \"always\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-12-04T02:22:06Z","receivedAt":"2024-12-04T02:22:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bence Ferdinandy <bence@ferdinandy.com> writes:\n\n> When running \"remote set-head\" manually it is unlikely, that the user\n> would actually like to have \"fetch\" always update the remote/HEAD. On\n> the contrary, it is more likely, that the user would expect remote/HEAD\n> to stay the way they manually set it, and just forgot about having\n> \"followRemoteHEAD\" set to \"always\".\n>\n> When \"followRemoteHEAD\" is set to \"always\" make running \"remote\n> set-head\" change the config to \"warn\".\n\nIf we were to take [1/2] of this series, then another plausible\noption is to set it to whatever their current value is, but that\nwould require an extra probing of the remote state, so \"warn\" is\nprobably a reasonable choice here.\n\nThanks.\n"},{"id":"508605","messageId":"D62RM601U1FN.12YBAISXW0ORI@ferdinandy.com","threadId":"62096","inReplyTo":"xmqqh67k5gv8.fsf@gitster.g","subject":"Re: [RFC PATCH v1 1/2] fetch set_head: add warn-if-not-$branch option","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-12-04T08:15:27Z","receivedAt":"2024-12-04T08:16:14Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Wed Dec 04, 2024 at 03:20, Junio C Hamano <gitster@pobox.com> wrote:\n> Bence Ferdinandy <bence@ferdinandy.com> writes:\n>\n>> +static void set_head_advice_msg(const char *remote, const char *head_name) {\n>> +\n>> +\tprintf(\"Run 'git remote set-head %s %s' to follow the change, or\\n\"\n>> +\t\t\"'git config set remote.%s.warn-if-not-%s' to disable this warning\\n\"\n>> +\t\t\"until the remote changes HEAD again.\\n\",\n>> +\t\tremote, head_name, remote, head_name);\n>> +\n>> +}\n>\n> Style. \"{\" that encloses the function body sits on a line of its\n> own.\n>\n> Perhaps use the advise_if_enabled(), so that those who already\n> learned how to deal with the situation can squelch the \"how to fix\"\n> message.\n>\n>> -static int set_head(const struct ref *remote_refs, int follow_remote_head)\n>> +static int set_head(const struct ref *remote_refs, int follow_remote_head,\n>> +\t\tconst char *no_warn_branch)\n>>  {\n>>  \tint result = 0, create_only, is_bare, was_detached;\n>>  \tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,\n>> @@ -1660,7 +1668,10 @@ static int set_head(const struct ref *remote_refs, int follow_remote_head)\n>>  \t\tresult = 1;\n>>  \t\tgoto cleanup;\n>>  \t}\n>> -\tif (follow_remote_head == FOLLOW_REMOTE_WARN && verbosity >= 0)\n>> +\tif ((follow_remote_head == FOLLOW_REMOTE_WARN ||\n>> +\t\t(follow_remote_head == FOLLOW_REMOTE_WARN_IF_NOT_BRANCH &&\n>> +\t\tstrcmp(no_warn_branch, head_name))\n>> +\t\t) && verbosity >= 0)\n>\n> Reorder conditions combined with &&- to have more expensive ones\n> later.\n>\n> \tif (verbosity >= 0 &&\n>             (follow_remote_head == FOLLOW_REMOTE_WARN ||\n> \t    (follow_remote_head == FOLLOW_REMOTE_WARN_IF_NOT_BRANCH &&\n> \t     strcmp(no_warn_branch, head_name)))\n>\n> As human readers, we may know that no_warn_branch is always valid\n> when (follow_remote_head == FOLLOW_REMOTE_WARN_IF_NOT_BRANCH), but\n> semi clever compilers may not realize it and give a false warning\n> about using no_warn_branch potentially uninitialized.\n>\n> We could do without adding FOLLOW_REMOTE_WARN_IF_NOT_BRANCH and reuse\n> FOLLOW_REMOTE_WARN, like so:\n>\n> \tif (verbosity >= 0 &&\n>             follow_remote_head == FOLLOW_REMOTE_WARN &&\n> \t    (!no_warn_branch || strcmp(no_warn_branch, head_name)))\n>\n> That is, \"if set to remote-warn, always warn, or no_warn_branch is\n> not NULL, only warn if the current head is different from it\".\n\nAh, nice, this has the added benefit of a bad configuration where\n\"warn-if-not-\" doesn't actually specify a branch to fall back to just \"warn\".\n\n>\n>> diff --git a/remote.c b/remote.c\n>> index 0b18840d43..f0e1b1b76a 100644\n>> --- a/remote.c\n>> +++ b/remote.c\n>> @@ -515,6 +515,7 @@ static int handle_config(const char *key, const char *value,\n>>  \t\treturn parse_transport_option(key, value,\n>>  \t\t\t\t\t      &remote->server_options);\n>>  \t} else if (!strcmp(subkey, \"followremotehead\")) {\n>> +\t\tconst char *no_warn_branch;\n>>  \t\tif (!strcmp(value, \"never\"))\n>>  \t\t\tremote->follow_remote_head = FOLLOW_REMOTE_NEVER;\n>>  \t\telse if (!strcmp(value, \"create\"))\n>> @@ -523,6 +524,10 @@ static int handle_config(const char *key, const char *value,\n>>  \t\t\tremote->follow_remote_head = FOLLOW_REMOTE_WARN;\n>>  \t\telse if (!strcmp(value, \"always\"))\n>>  \t\t\tremote->follow_remote_head = FOLLOW_REMOTE_ALWAYS;\n>> +\t\telse if (skip_prefix(value, \"warn-if-not-\", &no_warn_branch)) {\n>> +\t\t\tremote->follow_remote_head = FOLLOW_REMOTE_WARN_IF_NOT_BRANCH;\n>> +\t\t\tremote->no_warn_branch = no_warn_branch;\n>> +\t\t}\n>\n> If we were to do without FOLLOW_REMOTE_WARN_IF_NOT_BRANCH, then the\n> above becomes\n>\n> \t\t\tremote->follow_remote_head = FOLLOW_REMOTE_WARN;\n> \t\t\tremote->no_warn_branch = NULL;\n> \t\t} else if (skip_prefix(value, \"warn-if-not-\", &no_warn_branch)) {\n> \t\t\tremote->follow_remote_head = FOLLOW_REMOTE_WARN;\n> \t\t\tremote->no_warn_branch = no_warn_branch;\n> \t\t} else if (!strcmp(value, \"always\")) {\n> \t\t\tremote->follow_remote_head = FOLLOW_REMOTE_ALWAYS;\n> \t\t} else {\n> \t\t\twarn(_(\"unrecognized followRemoteHEAD value '%s' ignored\"),\n> \t\t\t     value);\n> \t\t}\n>\n> We'd want the new choice documented before we graduate this topic\n> out of the RFC status.\n>\n> Thanks.\n\n\n\n\n-- \nbence.ferdinandy.com\n\n"},{"id":"508610","messageId":"20241204104003.514905-2-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241204104003.514905-1-bence@ferdinandy.com","subject":"[PATCH v2 2/3] fetch set_head: add warn-if-not-$branch option","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-12-04T10:39:25Z","receivedAt":"2024-12-04T10:41:14Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Currently if we want to have a remote/HEAD locally that is different\nfrom the one on the remote, but we still want to get a warning if remote\nchanges HEAD, our only option is to have an indiscriminate warning with\n\"follow_remote_head\" set to \"warn\". Add a new option\n\"warn-if-not-$branch\", where $branch is a branch name we do not wish to\nget a warning about. If the remote HEAD is $branch do not warn,\notherwise, behave as \"warn\".\n\nE.g. let's assume, that our remote origin has HEAD\nset to \"master\", but locally we have \"git remote set-head origin seen\".\nSetting 'remote.origin.followRemoteHEAD = \"warn\"' will always print\na warning, even though the remote has not changed HEAD from \"master\".\nSetting 'remote.origin.followRemoteHEAD = \"warn-if-not-master\" will\nsquelch the warning message, unless the remote changes HEAD from\n\"master\". Note, that should the remote change HEAD to \"seen\" (which we\nhave locally), there will still be no warning.\n\nImprove the advice message in report_set_head to also include silencing\nthe warning message with \"warn-if-not-$branch\".\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v2: reuse FOLLOW_REMOTE_WARN\n        add documentation\n\n Documentation/config/remote.txt |  8 ++++---\n builtin/fetch.c                 | 16 +++++++++-----\n remote.c                        | 13 +++++++++--\n remote.h                        |  1 +\n t/t5510-fetch.sh                | 38 +++++++++++++++++++++++++++++++++\n 5 files changed, 66 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/config/remote.txt b/Documentation/config/remote.txt\nindex 024f92befc..4118c219c1 100644\n--- a/Documentation/config/remote.txt\n+++ b/Documentation/config/remote.txt\n@@ -106,10 +106,12 @@ remote.<name>.followRemoteHEAD::\n \tHow linkgit:git-fetch[1] should handle updates to `remotes/<name>/HEAD`.\n \tThe default value is \"create\", which will create `remotes/<name>/HEAD`\n \tif it exists on the remote, but not locally, but will not touch an\n-\talready existing local reference.  Setting to \"warn\" will print\n+\talready existing local reference. Setting to \"warn\" will print\n \ta message if the remote has a different value, than the local one and\n-\tin case there is no local reference, it behaves like \"create\". Setting\n-\tto \"always\" will silently update it to the value on the remote.\n+\tin case there is no local reference, it behaves like \"create\".\n+\tA variant on \"warn\" is \"warn-if-not-$branch\", which behaves like\n+\t\"warn\", but if `HEAD` on the remote is `$branch` it will be silent.\n+\tSetting to \"always\" will silently update it to the value on the remote.\n \tFinally, setting it to \"never\" will never change or create the local\n \treference.\n +\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 087beb4c92..b3f6793026 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1584,10 +1584,12 @@ static void set_head_advice_msg(const char *remote, const char *head_name)\n \tconst char message_advice_set_head[] =\n \tN_(\"Run 'git remote set-head %s %s' to follow the change, or set\\n\"\n \t   \"'remote.%s.followRemoteHEAD' configuration option to a different value\\n\"\n-\t   \"if you do not want to see this message.\");\n+\t   \"if you do not want to see this message. Specifically running\\n\"\n+\t   \"'git config set remote.%s.followRemoteHEAD %s' will disable the warning\\n\"\n+\t   \"until the remote changes HEAD to something else.\");\n \n \tadvise_if_enabled(ADVICE_FETCH_SET_HEAD_WARN, _(message_advice_set_head),\n-\t\t\tremote, head_name, remote);\n+\t\t\tremote, head_name, remote, remote, head_name);\n }\n \n static void report_set_head(const char *remote, const char *head_name,\n@@ -1612,7 +1614,8 @@ static void report_set_head(const char *remote, const char *head_name,\n \tstrbuf_release(&buf_prefix);\n }\n \n-static int set_head(const struct ref *remote_refs, int follow_remote_head)\n+static int set_head(const struct ref *remote_refs, int follow_remote_head,\n+\t\tconst char *no_warn_branch)\n {\n \tint result = 0, create_only, is_bare, was_detached;\n \tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,\n@@ -1669,7 +1672,9 @@ static int set_head(const struct ref *remote_refs, int follow_remote_head)\n \t\tresult = 1;\n \t\tgoto cleanup;\n \t}\n-\tif (follow_remote_head == FOLLOW_REMOTE_WARN && verbosity >= 0)\n+\tif (verbosity >= 0 &&\n+\t\tfollow_remote_head == FOLLOW_REMOTE_WARN &&\n+\t\t(!no_warn_branch || strcmp(no_warn_branch, head_name)))\n \t\treport_set_head(remote, head_name, &b_local_head, was_detached);\n \n cleanup:\n@@ -1898,7 +1903,8 @@ static int do_fetch(struct transport *transport,\n \t\t\t\t  \"you need to specify exactly one branch with the --set-upstream option\"));\n \t\t}\n \t}\n-\tif (set_head(remote_refs, transport->remote->follow_remote_head))\n+\tif (set_head(remote_refs, transport->remote->follow_remote_head,\n+\t\ttransport->remote->no_warn_branch))\n \t\t;\n \t\t/*\n \t\t * Way too many cases where this can go wrong\ndiff --git a/remote.c b/remote.c\nindex 0b18840d43..4ec5d3f47b 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -515,14 +515,23 @@ static int handle_config(const char *key, const char *value,\n \t\treturn parse_transport_option(key, value,\n \t\t\t\t\t      &remote->server_options);\n \t} else if (!strcmp(subkey, \"followremotehead\")) {\n+\t\tconst char *no_warn_branch;\n \t\tif (!strcmp(value, \"never\"))\n \t\t\tremote->follow_remote_head = FOLLOW_REMOTE_NEVER;\n \t\telse if (!strcmp(value, \"create\"))\n \t\t\tremote->follow_remote_head = FOLLOW_REMOTE_CREATE;\n-\t\telse if (!strcmp(value, \"warn\"))\n+\t\telse if (!strcmp(value, \"warn\")) {\n \t\t\tremote->follow_remote_head = FOLLOW_REMOTE_WARN;\n-\t\telse if (!strcmp(value, \"always\"))\n+\t\t\tremote->no_warn_branch = NULL;\n+\t\t} else if (skip_prefix(value, \"warn-if-not-\", &no_warn_branch)) {\n+\t\t\tremote->follow_remote_head = FOLLOW_REMOTE_WARN;\n+\t\t\tremote->no_warn_branch = no_warn_branch;\n+\t\t} else if (!strcmp(value, \"always\")) {\n \t\t\tremote->follow_remote_head = FOLLOW_REMOTE_ALWAYS;\n+\t\t} else {\n+\t\t\twarning(_(\"unrecognized followRemoteHEAD value '%s' ignored\"),\n+\t\t\t\tvalue);\n+\t\t}\n \t}\n \treturn 0;\n }\ndiff --git a/remote.h b/remote.h\nindex 184b35653d..bda10dd5c8 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -116,6 +116,7 @@ struct remote {\n \tstruct string_list server_options;\n \n \tenum follow_remote_head_settings follow_remote_head;\n+\tconst char *no_warn_branch;\n };\n \n /**\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex 5d7ee1b550..5c9915b836 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -181,6 +181,44 @@ test_expect_success \"fetch test followRemoteHEAD warn quiet\" '\n \t)\n '\n \n+test_expect_success \"fetch test followRemoteHEAD warn-if-not-branch branch is same\" '\n+\ttest_when_finished \"git config unset remote.origin.followRemoteHEAD\" &&\n+\t(\n+\t\tcd \"$D\" &&\n+\t\tcd two &&\n+\t\tgit rev-parse --verify refs/remotes/origin/other &&\n+\t\tgit remote set-head origin other &&\n+\t\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\t\tgit rev-parse --verify refs/remotes/origin/main &&\n+\t\tgit config set remote.origin.followRemoteHEAD \"warn-if-not-main\" &&\n+\t\toutput=$(git fetch) &&\n+\t\ttest \"z\" = \"z$output\" &&\n+\t\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\t\tbranch=$(git rev-parse refs/remotes/origin/other) &&\n+\t\ttest \"z$head\" = \"z$branch\"\n+\t)\n+'\n+\n+test_expect_success \"fetch test followRemoteHEAD warn-if-not-branch branch is different\" '\n+\ttest_when_finished \"git config unset remote.origin.followRemoteHEAD\" &&\n+\t(\n+\t\tcd \"$D\" &&\n+\t\tcd two &&\n+\t\tgit rev-parse --verify refs/remotes/origin/other &&\n+\t\tgit remote set-head origin other &&\n+\t\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\t\tgit rev-parse --verify refs/remotes/origin/main &&\n+\t\tgit config set remote.origin.followRemoteHEAD \"warn-if-not-some/different-branch\" &&\n+\t\tgit fetch >output &&\n+\t\techo \"${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ},\" \\\n+\t\t\t\"but we have ${SQ}other${SQ} locally.\" >expect &&\n+\t\ttest_cmp expect output &&\n+\t\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\t\tbranch=$(git rev-parse refs/remotes/origin/other) &&\n+\t\ttest \"z$head\" = \"z$branch\"\n+\t)\n+'\n+\n test_expect_success \"fetch test followRemoteHEAD always\" '\n \ttest_when_finished \"git config unset remote.origin.followRemoteHEAD\" &&\n \t(\n-- \n2.47.1.398.g18e7475ebe\n\n"},{"id":"508611","messageId":"20241204104003.514905-1-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241203215713.135068-1-bence@ferdinandy.com","subject":"[PATCH v2 1/3] fetch set_head: move warn advice into advise_if_enabled","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-12-04T10:39:24Z","receivedAt":"2024-12-04T10:41:20Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Advice about what to do when getting a warning is typed out explicitly\ntwice and is printed as regular output. The output is also tested for.\nExtract the advice message into a single place and use a wrapper\nfunction, so if later the advice is made more chatty the signature only\nneeds to be changed in once place. Remove the testing for the advice\noutput in the tests.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v2: new patch, with the refactor moved out from the following one\n\n advice.c         |  1 +\n advice.h         |  1 +\n builtin/fetch.c  | 17 +++++++++++++----\n t/t5510-fetch.sh |  2 --\n 4 files changed, 15 insertions(+), 6 deletions(-)\n\ndiff --git a/advice.c b/advice.c\nindex 6b879d805c..66461fdce9 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -53,6 +53,7 @@ static struct {\n \t[ADVICE_COMMIT_BEFORE_MERGE]\t\t\t= { \"commitBeforeMerge\" },\n \t[ADVICE_DETACHED_HEAD]\t\t\t\t= { \"detachedHead\" },\n \t[ADVICE_DIVERGING]\t\t\t\t= { \"diverging\" },\n+\t[ADVICE_FETCH_SET_HEAD_WARN]\t\t\t= { \"fetchRemoteHEADWarn\" },\n \t[ADVICE_FETCH_SHOW_FORCED_UPDATES]\t\t= { \"fetchShowForcedUpdates\" },\n \t[ADVICE_FORCE_DELETE_BRANCH]\t\t\t= { \"forceDeleteBranch\" },\n \t[ADVICE_GRAFT_FILE_DEPRECATED]\t\t\t= { \"graftFileDeprecated\" },\ndiff --git a/advice.h b/advice.h\nindex d7466bc0ef..cf2284ec43 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -20,6 +20,7 @@ enum advice_type {\n \tADVICE_COMMIT_BEFORE_MERGE,\n \tADVICE_DETACHED_HEAD,\n \tADVICE_DIVERGING,\n+\tADVICE_FETCH_SET_HEAD_WARN,\n \tADVICE_FETCH_SHOW_FORCED_UPDATES,\n \tADVICE_FORCE_DELETE_BRANCH,\n \tADVICE_GRAFT_FILE_DEPRECATED,\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 62769d1686..087beb4c92 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1579,6 +1579,17 @@ static const char *strip_refshead(const char *name){\n \treturn name;\n }\n \n+static void set_head_advice_msg(const char *remote, const char *head_name)\n+{\n+\tconst char message_advice_set_head[] =\n+\tN_(\"Run 'git remote set-head %s %s' to follow the change, or set\\n\"\n+\t   \"'remote.%s.followRemoteHEAD' configuration option to a different value\\n\"\n+\t   \"if you do not want to see this message.\");\n+\n+\tadvise_if_enabled(ADVICE_FETCH_SET_HEAD_WARN, _(message_advice_set_head),\n+\t\t\tremote, head_name, remote);\n+}\n+\n static void report_set_head(const char *remote, const char *head_name,\n \t\t\tstruct strbuf *buf_prev, int updateres) {\n \tstruct strbuf buf_prefix = STRBUF_INIT;\n@@ -1590,15 +1601,13 @@ static void report_set_head(const char *remote, const char *head_name,\n \tif (prev_head && strcmp(prev_head, head_name)) {\n \t\tprintf(\"'HEAD' at '%s' is '%s', but we have '%s' locally.\\n\",\n \t\t\tremote, head_name, prev_head);\n-\t\tprintf(\"Run 'git remote set-head %s %s' to follow the change.\\n\",\n-\t\t\tremote, head_name);\n+\t\tset_head_advice_msg(remote, head_name);\n \t}\n \telse if (updateres && buf_prev->len) {\n \t\tprintf(\"'HEAD' at '%s' is '%s', \"\n \t\t\t\"but we have a detached HEAD pointing to '%s' locally.\\n\",\n \t\t\tremote, head_name, buf_prev->buf);\n-\t\tprintf(\"Run 'git remote set-head %s %s' to follow the change.\\n\",\n-\t\t\tremote, head_name);\n+\t\tset_head_advice_msg(remote, head_name);\n \t}\n \tstrbuf_release(&buf_prefix);\n }\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex ccb74428bc..5d7ee1b550 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -123,7 +123,6 @@ test_expect_success \"fetch test followRemoteHEAD warn no change\" '\n \t\tgit fetch >output &&\n \t\techo \"${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ},\" \\\n \t\t\t\"but we have ${SQ}other${SQ} locally.\" >expect &&\n-\t\techo \"Run ${SQ}git remote set-head origin main${SQ} to follow the change.\" >>expect &&\n \t\ttest_cmp expect output &&\n \t\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n \t\tbranch=$(git rev-parse refs/remotes/origin/other) &&\n@@ -160,7 +159,6 @@ test_expect_success \"fetch test followRemoteHEAD warn detached\" '\n \t\techo \"${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ},\" \\\n \t\t\t\"but we have a detached HEAD pointing to\" \\\n \t\t\t\"${SQ}${HEAD}${SQ} locally.\" >expect &&\n-\t\techo \"Run ${SQ}git remote set-head origin main${SQ} to follow the change.\" >>expect &&\n \t\ttest_cmp expect output\n \t)\n '\n-- \n2.47.1.398.g18e7475ebe\n\n"},{"id":"508612","messageId":"20241204104003.514905-3-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241204104003.514905-1-bence@ferdinandy.com","subject":"[PATCH v2 3/3] remote set-head: set followRemoteHEAD to \"warn\" if \"always\"","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-12-04T10:39:26Z","receivedAt":"2024-12-04T10:41:49Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"When running \"remote set-head\" manually it is unlikely, that the user\nwould actually like to have \"fetch\" always update the remote/HEAD. On\nthe contrary, it is more likely, that the user would expect remote/HEAD\nto stay the way they manually set it, and just forgot about having\n\"followRemoteHEAD\" set to \"always\".\n\nWhen \"followRemoteHEAD\" is set to \"always\" make running \"remote\nset-head\" change the config to \"warn\".\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n builtin/remote.c  | 12 +++++++++++-\n t/t5505-remote.sh | 11 +++++++++++\n 2 files changed, 22 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex d5b81445f2..32d02ca4a0 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1438,6 +1438,7 @@ static int set_head(int argc, const char **argv, const char *prefix,\n \t\tb_local_head = STRBUF_INIT;\n \tchar *head_name = NULL;\n \tstruct ref_store *refs = get_main_ref_store(the_repository);\n+\tstruct remote *remote;\n \n \tstruct option options[] = {\n \t\tOPT_BOOL('a', \"auto\", &opt_a,\n@@ -1448,8 +1449,10 @@ static int set_head(int argc, const char **argv, const char *prefix,\n \t};\n \targc = parse_options(argc, argv, prefix, options,\n \t\t\t     builtin_remote_sethead_usage, 0);\n-\tif (argc)\n+\tif (argc) {\n \t\tstrbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", argv[0]);\n+\t\tremote = remote_get(argv[0]);\n+\t}\n \n \tif (!opt_a && !opt_d && argc == 2) {\n \t\thead_name = xstrdup(argv[1]);\n@@ -1488,6 +1491,13 @@ static int set_head(int argc, const char **argv, const char *prefix,\n \t}\n \tif (opt_a)\n \t\treport_set_head_auto(argv[0], head_name, &b_local_head, was_detached);\n+\tif (remote->follow_remote_head == FOLLOW_REMOTE_ALWAYS) {\n+\t\tstruct strbuf config_name = STRBUF_INIT;\n+\t\tstrbuf_addf(&config_name,\n+\t\t\t\"remote.%s.followremotehead\", remote->name);\n+\t\tgit_config_set(config_name.buf, \"warn\");\n+\t\tstrbuf_release(&config_name);\n+\t}\n \n cleanup:\n \tfree(head_name);\ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex 7411aa770d..daf70406be 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -504,6 +504,17 @@ test_expect_success 'set-head --auto has no problem w/multiple HEADs' '\n \t)\n '\n \n+test_expect_success 'set-head changes followRemoteHEAD always to warn' '\n+\t(\n+\t\tcd test &&\n+\t\tgit config set remote.origin.followRemoteHEAD \"always\" &&\n+\t\tgit remote set-head --auto origin &&\n+\t\tgit config get remote.origin.followRemoteHEAD >output &&\n+\t\techo \"warn\" >expect &&\n+\t\ttest_cmp expect output\n+\t)\n+'\n+\n cat >test/expect <<\\EOF\n refs/remotes/origin/side2\n EOF\n-- \n2.47.1.398.g18e7475ebe\n\n"},{"id":"508614","messageId":"cfd54be2-af8f-429f-a4c6-41f54c33c89c@app.fastmail.com","threadId":"62096","inReplyTo":"20241204104003.514905-3-bence@ferdinandy.com","subject":"Re: [PATCH v2 3/3] remote set-head: set followRemoteHEAD to \"warn\" if \"always\"","fromName":"Kristoffer Haugsbakk","fromEmail":"kristofferhaugsbakk@fastmail.com","sentAt":"2024-12-04T11:43:45Z","receivedAt":"2024-12-04T11:44:08Z","isPatch":true,"sender":{"key":"kristofferhaugsbakk@fastmail.com","avatar":null},"body":"Hi\n\nOn Wed, Dec 4, 2024, at 11:39, Bence Ferdinandy wrote:\n> diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\n> index 7411aa770d..daf70406be 100755\n> --- a/t/t5505-remote.sh\n> +++ b/t/t5505-remote.sh\n> @@ -504,6 +504,17 @@ test_expect_success 'set-head --auto has no\n> problem w/multiple HEADs' '\n>  \t)\n>  '\n>\n> +test_expect_success 'set-head changes followRemoteHEAD always to warn' '\n> +\t(\n> +\t\tcd test &&\n\nI think you need to `cd` in a subshell here.  See `t/README`, “Don't\nchdir around in tests.”.\n\n> +\t\tgit config set remote.origin.followRemoteHEAD \"always\" &&\n> +\t\tgit remote set-head --auto origin &&\n> +\t\tgit config get remote.origin.followRemoteHEAD >output &&\n\nNit: maybe `actual` instead of `output`?  Just for uniformity.\n"},{"id":"508618","messageId":"xmqqldwv2qom.fsf@gitster.g","threadId":"62096","inReplyTo":"20241204104003.514905-1-bence@ferdinandy.com","subject":"Re: [PATCH v2 1/3] fetch set_head: move warn advice into advise_if_enabled","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-12-04T19:28:41Z","receivedAt":"2024-12-04T19:28:44Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bence Ferdinandy <bence@ferdinandy.com> writes:\n\n> +static void set_head_advice_msg(const char *remote, const char *head_name)\n> +{\n> +\tconst char message_advice_set_head[] =\n> +\tN_(\"Run 'git remote set-head %s %s' to follow the change, or set\\n\"\n> +\t   \"'remote.%s.followRemoteHEAD' configuration option to a different value\\n\"\n> +\t   \"if you do not want to see this message.\");\n> +\n> +\tadvise_if_enabled(ADVICE_FETCH_SET_HEAD_WARN, _(message_advice_set_head),\n> +\t\t\tremote, head_name, remote);\n> +}\n> ...\n>  \telse if (updateres && buf_prev->len) {\n>  \t\tprintf(\"'HEAD' at '%s' is '%s', \"\n>  \t\t\t\"but we have a detached HEAD pointing to '%s' locally.\\n\",\n>  \t\t\tremote, head_name, buf_prev->buf);\n> -\t\tprintf(\"Run 'git remote set-head %s %s' to follow the change.\\n\",\n> -\t\t\tremote, head_name);\n> +\t\tset_head_advice_msg(remote, head_name);\n>  \t}\n>  \tstrbuf_release(&buf_prefix);\n>  }\n\nLooking good.\n\n> diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\n> index ccb74428bc..5d7ee1b550 100755\n> --- a/t/t5510-fetch.sh\n> +++ b/t/t5510-fetch.sh\n> @@ -123,7 +123,6 @@ test_expect_success \"fetch test followRemoteHEAD warn no change\" '\n>  \t\tgit fetch >output &&\n>  \t\techo \"${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ},\" \\\n>  \t\t\t\"but we have ${SQ}other${SQ} locally.\" >expect &&\n> -\t\techo \"Run ${SQ}git remote set-head origin main${SQ} to follow the change.\" >>expect &&\n>  \t\ttest_cmp expect output &&\n\nOK.  We just lose this part of the test, not because the advice\nmessages are squelched but because they go to the standard error\nstream.  An obvious alternative is to capture it in a separate\nfile and grep, i.e.\n\n\tgit fetch >output 2>error &&\n\techo ... >expect &&\n\ttest_grep \"Run ${SQ}git remote set-head\" error &&\n\ttest_cmp expect output\n\nIf we were testing that the advice mechanism is used, do that with\nand without the ADVICE_FETCH_SET_HEAD_WARN squelched, but probably\nit is a bit too much, in the sense that we are not in the business\nof testing that the advice mechanism works correctly in this series.\n\n"},{"id":"508621","messageId":"xmqqh67j2ndc.fsf@gitster.g","threadId":"62096","inReplyTo":"cfd54be2-af8f-429f-a4c6-41f54c33c89c@app.fastmail.com","subject":"Re: [PATCH v2 3/3] remote set-head: set followRemoteHEAD to \"warn\" if \"always\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-12-04T20:40:15Z","receivedAt":"2024-12-04T20:40:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Kristoffer Haugsbakk\" <kristofferhaugsbakk@fastmail.com> writes:\n\n> On Wed, Dec 4, 2024, at 11:39, Bence Ferdinandy wrote:\n>> diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\n>> index 7411aa770d..daf70406be 100755\n>> --- a/t/t5505-remote.sh\n>> +++ b/t/t5505-remote.sh\n>> @@ -504,6 +504,17 @@ test_expect_success 'set-head --auto has no\n>> problem w/multiple HEADs' '\n>>  \t)\n>>  '\n>>\n>> +test_expect_success 'set-head changes followRemoteHEAD always to warn' '\n>> +\t(\n>> +\t\tcd test &&\n>\n> I think you need to `cd` in a subshell here.  See `t/README`, “Don't\n> chdir around in tests.”.\n\nPuzzled.  Isn't this inside a (subshell) already?\n\n>> +\t\tgit config set remote.origin.followRemoteHEAD \"always\" &&\n>> +\t\tgit remote set-head --auto origin &&\n>> +\t\tgit config get remote.origin.followRemoteHEAD >output &&\n>\n> Nit: maybe `actual` instead of `output`?  Just for uniformity.\n\nt5505 is already heavily contaminated with the pattern to compare\n\"expect\" with \"output\", not with \"actual\", but that does not make\nit a good idea to make it even worse by adding more instances ;-)\n\n"},{"id":"508622","messageId":"173ea521-6a2e-45aa-ae5e-4da99060cc4f@app.fastmail.com","threadId":"62096","inReplyTo":"xmqqh67j2ndc.fsf@gitster.g","subject":"Re: [PATCH v2 3/3] remote set-head: set followRemoteHEAD to \"warn\" if \"always\"","fromName":"Kristoffer Haugsbakk","fromEmail":"kristofferhaugsbakk@fastmail.com","sentAt":"2024-12-04T20:44:26Z","receivedAt":"2024-12-04T20:45:35Z","isPatch":true,"sender":{"key":"kristofferhaugsbakk@fastmail.com","avatar":null},"body":"On Wed, Dec 4, 2024, at 21:40, Junio C Hamano wrote:\n> \"Kristoffer Haugsbakk\" <kristofferhaugsbakk@fastmail.com> writes:\n>\n>> On Wed, Dec 4, 2024, at 11:39, Bence Ferdinandy wrote:\n>>> diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\n>>> index 7411aa770d..daf70406be 100755\n>>> --- a/t/t5505-remote.sh\n>>> +++ b/t/t5505-remote.sh\n>>> @@ -504,6 +504,17 @@ test_expect_success 'set-head --auto has no\n>>> problem w/multiple HEADs' '\n>>>  \t)\n>>>  '\n>>>\n>>> +test_expect_success 'set-head changes followRemoteHEAD always to warn' '\n>>> +\t(\n>>> +\t\tcd test &&\n>>\n>> I think you need to `cd` in a subshell here.  See `t/README`, “Don't\n>> chdir around in tests.”.\n>\n> Puzzled.  Isn't this inside a (subshell) already?\n\nAha, then I didn’t read the context properly.\n"},{"id":"508641","messageId":"D63M7LIBTL4B.EXTQ3OSFM9XO@ferdinandy.com","threadId":"62096","inReplyTo":"173ea521-6a2e-45aa-ae5e-4da99060cc4f@app.fastmail.com","subject":"Re: [PATCH v2 3/3] remote set-head: set followRemoteHEAD to \"warn\" if \"always\"","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-12-05T08:14:00Z","receivedAt":"2024-12-05T08:14:38Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Wed Dec 04, 2024 at 21:44, Kristoffer Haugsbakk <kristofferhaugsbakk@fastmail.com> wrote:\n> On Wed, Dec 4, 2024, at 21:40, Junio C Hamano wrote:\n>> \"Kristoffer Haugsbakk\" <kristofferhaugsbakk@fastmail.com> writes:\n>>\n>>> On Wed, Dec 4, 2024, at 11:39, Bence Ferdinandy wrote:\n>>>> diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\n>>>> index 7411aa770d..daf70406be 100755\n>>>> --- a/t/t5505-remote.sh\n>>>> +++ b/t/t5505-remote.sh\n>>>> @@ -504,6 +504,17 @@ test_expect_success 'set-head --auto has no\n>>>> problem w/multiple HEADs' '\n>>>>  \t)\n>>>>  '\n>>>>\n>>>> +test_expect_success 'set-head changes followRemoteHEAD always to warn' '\n>>>> +\t(\n>>>> +\t\tcd test &&\n>>>\n>>> I think you need to `cd` in a subshell here.  See `t/README`, “Don't\n>>> chdir around in tests.”.\n>>\n>> Puzzled.  Isn't this inside a (subshell) already?\n>\n> Aha, then I didn’t read the context properly.\n\nThanks for the review! What I gathered is that v3 needs an s/output/actual and\nwe're good. I'll send that soonish.\n\nThanks,\nBence\n\n\n"},{"id":"508666","messageId":"20241205121737.1181695-2-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241205121737.1181695-1-bence@ferdinandy.com","subject":"[PATCH v3 2/3] fetch set_head: add warn-if-not-$branch option","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-12-05T12:16:21Z","receivedAt":"2024-12-05T12:18:50Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Currently if we want to have a remote/HEAD locally that is different\nfrom the one on the remote, but we still want to get a warning if remote\nchanges HEAD, our only option is to have an indiscriminate warning with\n\"follow_remote_head\" set to \"warn\". Add a new option\n\"warn-if-not-$branch\", where $branch is a branch name we do not wish to\nget a warning about. If the remote HEAD is $branch do not warn,\notherwise, behave as \"warn\".\n\nE.g. let's assume, that our remote origin has HEAD\nset to \"master\", but locally we have \"git remote set-head origin seen\".\nSetting 'remote.origin.followRemoteHEAD = \"warn\"' will always print\na warning, even though the remote has not changed HEAD from \"master\".\nSetting 'remote.origin.followRemoteHEAD = \"warn-if-not-master\" will\nsquelch the warning message, unless the remote changes HEAD from\n\"master\". Note, that should the remote change HEAD to \"seen\" (which we\nhave locally), there will still be no warning.\n\nImprove the advice message in report_set_head to also include silencing\nthe warning message with \"warn-if-not-$branch\".\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v2: reuse FOLLOW_REMOTE_WARN\n        add documentation\n    \n    v3: s/output/actual in tests\n\n Documentation/config/remote.txt |  8 ++++---\n builtin/fetch.c                 | 16 +++++++++-----\n remote.c                        | 13 +++++++++--\n remote.h                        |  1 +\n t/t5510-fetch.sh                | 38 +++++++++++++++++++++++++++++++++\n 5 files changed, 66 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/config/remote.txt b/Documentation/config/remote.txt\nindex 024f92befc..4118c219c1 100644\n--- a/Documentation/config/remote.txt\n+++ b/Documentation/config/remote.txt\n@@ -106,10 +106,12 @@ remote.<name>.followRemoteHEAD::\n \tHow linkgit:git-fetch[1] should handle updates to `remotes/<name>/HEAD`.\n \tThe default value is \"create\", which will create `remotes/<name>/HEAD`\n \tif it exists on the remote, but not locally, but will not touch an\n-\talready existing local reference.  Setting to \"warn\" will print\n+\talready existing local reference. Setting to \"warn\" will print\n \ta message if the remote has a different value, than the local one and\n-\tin case there is no local reference, it behaves like \"create\". Setting\n-\tto \"always\" will silently update it to the value on the remote.\n+\tin case there is no local reference, it behaves like \"create\".\n+\tA variant on \"warn\" is \"warn-if-not-$branch\", which behaves like\n+\t\"warn\", but if `HEAD` on the remote is `$branch` it will be silent.\n+\tSetting to \"always\" will silently update it to the value on the remote.\n \tFinally, setting it to \"never\" will never change or create the local\n \treference.\n +\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 087beb4c92..b3f6793026 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1584,10 +1584,12 @@ static void set_head_advice_msg(const char *remote, const char *head_name)\n \tconst char message_advice_set_head[] =\n \tN_(\"Run 'git remote set-head %s %s' to follow the change, or set\\n\"\n \t   \"'remote.%s.followRemoteHEAD' configuration option to a different value\\n\"\n-\t   \"if you do not want to see this message.\");\n+\t   \"if you do not want to see this message. Specifically running\\n\"\n+\t   \"'git config set remote.%s.followRemoteHEAD %s' will disable the warning\\n\"\n+\t   \"until the remote changes HEAD to something else.\");\n \n \tadvise_if_enabled(ADVICE_FETCH_SET_HEAD_WARN, _(message_advice_set_head),\n-\t\t\tremote, head_name, remote);\n+\t\t\tremote, head_name, remote, remote, head_name);\n }\n \n static void report_set_head(const char *remote, const char *head_name,\n@@ -1612,7 +1614,8 @@ static void report_set_head(const char *remote, const char *head_name,\n \tstrbuf_release(&buf_prefix);\n }\n \n-static int set_head(const struct ref *remote_refs, int follow_remote_head)\n+static int set_head(const struct ref *remote_refs, int follow_remote_head,\n+\t\tconst char *no_warn_branch)\n {\n \tint result = 0, create_only, is_bare, was_detached;\n \tstruct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,\n@@ -1669,7 +1672,9 @@ static int set_head(const struct ref *remote_refs, int follow_remote_head)\n \t\tresult = 1;\n \t\tgoto cleanup;\n \t}\n-\tif (follow_remote_head == FOLLOW_REMOTE_WARN && verbosity >= 0)\n+\tif (verbosity >= 0 &&\n+\t\tfollow_remote_head == FOLLOW_REMOTE_WARN &&\n+\t\t(!no_warn_branch || strcmp(no_warn_branch, head_name)))\n \t\treport_set_head(remote, head_name, &b_local_head, was_detached);\n \n cleanup:\n@@ -1898,7 +1903,8 @@ static int do_fetch(struct transport *transport,\n \t\t\t\t  \"you need to specify exactly one branch with the --set-upstream option\"));\n \t\t}\n \t}\n-\tif (set_head(remote_refs, transport->remote->follow_remote_head))\n+\tif (set_head(remote_refs, transport->remote->follow_remote_head,\n+\t\ttransport->remote->no_warn_branch))\n \t\t;\n \t\t/*\n \t\t * Way too many cases where this can go wrong\ndiff --git a/remote.c b/remote.c\nindex 0b18840d43..4ec5d3f47b 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -515,14 +515,23 @@ static int handle_config(const char *key, const char *value,\n \t\treturn parse_transport_option(key, value,\n \t\t\t\t\t      &remote->server_options);\n \t} else if (!strcmp(subkey, \"followremotehead\")) {\n+\t\tconst char *no_warn_branch;\n \t\tif (!strcmp(value, \"never\"))\n \t\t\tremote->follow_remote_head = FOLLOW_REMOTE_NEVER;\n \t\telse if (!strcmp(value, \"create\"))\n \t\t\tremote->follow_remote_head = FOLLOW_REMOTE_CREATE;\n-\t\telse if (!strcmp(value, \"warn\"))\n+\t\telse if (!strcmp(value, \"warn\")) {\n \t\t\tremote->follow_remote_head = FOLLOW_REMOTE_WARN;\n-\t\telse if (!strcmp(value, \"always\"))\n+\t\t\tremote->no_warn_branch = NULL;\n+\t\t} else if (skip_prefix(value, \"warn-if-not-\", &no_warn_branch)) {\n+\t\t\tremote->follow_remote_head = FOLLOW_REMOTE_WARN;\n+\t\t\tremote->no_warn_branch = no_warn_branch;\n+\t\t} else if (!strcmp(value, \"always\")) {\n \t\t\tremote->follow_remote_head = FOLLOW_REMOTE_ALWAYS;\n+\t\t} else {\n+\t\t\twarning(_(\"unrecognized followRemoteHEAD value '%s' ignored\"),\n+\t\t\t\tvalue);\n+\t\t}\n \t}\n \treturn 0;\n }\ndiff --git a/remote.h b/remote.h\nindex 184b35653d..bda10dd5c8 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -116,6 +116,7 @@ struct remote {\n \tstruct string_list server_options;\n \n \tenum follow_remote_head_settings follow_remote_head;\n+\tconst char *no_warn_branch;\n };\n \n /**\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex 5d7ee1b550..3885461ac1 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -181,6 +181,44 @@ test_expect_success \"fetch test followRemoteHEAD warn quiet\" '\n \t)\n '\n \n+test_expect_success \"fetch test followRemoteHEAD warn-if-not-branch branch is same\" '\n+\ttest_when_finished \"git config unset remote.origin.followRemoteHEAD\" &&\n+\t(\n+\t\tcd \"$D\" &&\n+\t\tcd two &&\n+\t\tgit rev-parse --verify refs/remotes/origin/other &&\n+\t\tgit remote set-head origin other &&\n+\t\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\t\tgit rev-parse --verify refs/remotes/origin/main &&\n+\t\tgit config set remote.origin.followRemoteHEAD \"warn-if-not-main\" &&\n+\t\tactual=$(git fetch) &&\n+\t\ttest \"z\" = \"z$actual\" &&\n+\t\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\t\tbranch=$(git rev-parse refs/remotes/origin/other) &&\n+\t\ttest \"z$head\" = \"z$branch\"\n+\t)\n+'\n+\n+test_expect_success \"fetch test followRemoteHEAD warn-if-not-branch branch is different\" '\n+\ttest_when_finished \"git config unset remote.origin.followRemoteHEAD\" &&\n+\t(\n+\t\tcd \"$D\" &&\n+\t\tcd two &&\n+\t\tgit rev-parse --verify refs/remotes/origin/other &&\n+\t\tgit remote set-head origin other &&\n+\t\tgit rev-parse --verify refs/remotes/origin/HEAD &&\n+\t\tgit rev-parse --verify refs/remotes/origin/main &&\n+\t\tgit config set remote.origin.followRemoteHEAD \"warn-if-not-some/different-branch\" &&\n+\t\tgit fetch >actual &&\n+\t\techo \"${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ},\" \\\n+\t\t\t\"but we have ${SQ}other${SQ} locally.\" >expect &&\n+\t\ttest_cmp expect actual &&\n+\t\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n+\t\tbranch=$(git rev-parse refs/remotes/origin/other) &&\n+\t\ttest \"z$head\" = \"z$branch\"\n+\t)\n+'\n+\n test_expect_success \"fetch test followRemoteHEAD always\" '\n \ttest_when_finished \"git config unset remote.origin.followRemoteHEAD\" &&\n \t(\n-- \n2.47.1.398.g18e7475ebe\n\n"},{"id":"508667","messageId":"20241205121737.1181695-1-bence@ferdinandy.com","threadId":"62096","inReplyTo":"173ea521-6a2e-45aa-ae5e-4da99060cc4f@app.fastmail.com","subject":"[PATCH v3 1/3] fetch set_head: move warn advice into advise_if_enabled","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-12-05T12:16:20Z","receivedAt":"2024-12-05T12:18:55Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Advice about what to do when getting a warning is typed out explicitly\ntwice and is printed as regular output. The output is also tested for.\nExtract the advice message into a single place and use a wrapper\nfunction, so if later the advice is made more chatty the signature only\nneeds to be changed in once place. Remove the testing for the advice\noutput in the tests.\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v2: new patch, with the refactor moved out from the following one\n\n advice.c         |  1 +\n advice.h         |  1 +\n builtin/fetch.c  | 17 +++++++++++++----\n t/t5510-fetch.sh |  2 --\n 4 files changed, 15 insertions(+), 6 deletions(-)\n\ndiff --git a/advice.c b/advice.c\nindex 6b879d805c..66461fdce9 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -53,6 +53,7 @@ static struct {\n \t[ADVICE_COMMIT_BEFORE_MERGE]\t\t\t= { \"commitBeforeMerge\" },\n \t[ADVICE_DETACHED_HEAD]\t\t\t\t= { \"detachedHead\" },\n \t[ADVICE_DIVERGING]\t\t\t\t= { \"diverging\" },\n+\t[ADVICE_FETCH_SET_HEAD_WARN]\t\t\t= { \"fetchRemoteHEADWarn\" },\n \t[ADVICE_FETCH_SHOW_FORCED_UPDATES]\t\t= { \"fetchShowForcedUpdates\" },\n \t[ADVICE_FORCE_DELETE_BRANCH]\t\t\t= { \"forceDeleteBranch\" },\n \t[ADVICE_GRAFT_FILE_DEPRECATED]\t\t\t= { \"graftFileDeprecated\" },\ndiff --git a/advice.h b/advice.h\nindex d7466bc0ef..cf2284ec43 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -20,6 +20,7 @@ enum advice_type {\n \tADVICE_COMMIT_BEFORE_MERGE,\n \tADVICE_DETACHED_HEAD,\n \tADVICE_DIVERGING,\n+\tADVICE_FETCH_SET_HEAD_WARN,\n \tADVICE_FETCH_SHOW_FORCED_UPDATES,\n \tADVICE_FORCE_DELETE_BRANCH,\n \tADVICE_GRAFT_FILE_DEPRECATED,\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 62769d1686..087beb4c92 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1579,6 +1579,17 @@ static const char *strip_refshead(const char *name){\n \treturn name;\n }\n \n+static void set_head_advice_msg(const char *remote, const char *head_name)\n+{\n+\tconst char message_advice_set_head[] =\n+\tN_(\"Run 'git remote set-head %s %s' to follow the change, or set\\n\"\n+\t   \"'remote.%s.followRemoteHEAD' configuration option to a different value\\n\"\n+\t   \"if you do not want to see this message.\");\n+\n+\tadvise_if_enabled(ADVICE_FETCH_SET_HEAD_WARN, _(message_advice_set_head),\n+\t\t\tremote, head_name, remote);\n+}\n+\n static void report_set_head(const char *remote, const char *head_name,\n \t\t\tstruct strbuf *buf_prev, int updateres) {\n \tstruct strbuf buf_prefix = STRBUF_INIT;\n@@ -1590,15 +1601,13 @@ static void report_set_head(const char *remote, const char *head_name,\n \tif (prev_head && strcmp(prev_head, head_name)) {\n \t\tprintf(\"'HEAD' at '%s' is '%s', but we have '%s' locally.\\n\",\n \t\t\tremote, head_name, prev_head);\n-\t\tprintf(\"Run 'git remote set-head %s %s' to follow the change.\\n\",\n-\t\t\tremote, head_name);\n+\t\tset_head_advice_msg(remote, head_name);\n \t}\n \telse if (updateres && buf_prev->len) {\n \t\tprintf(\"'HEAD' at '%s' is '%s', \"\n \t\t\t\"but we have a detached HEAD pointing to '%s' locally.\\n\",\n \t\t\tremote, head_name, buf_prev->buf);\n-\t\tprintf(\"Run 'git remote set-head %s %s' to follow the change.\\n\",\n-\t\t\tremote, head_name);\n+\t\tset_head_advice_msg(remote, head_name);\n \t}\n \tstrbuf_release(&buf_prefix);\n }\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex ccb74428bc..5d7ee1b550 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -123,7 +123,6 @@ test_expect_success \"fetch test followRemoteHEAD warn no change\" '\n \t\tgit fetch >output &&\n \t\techo \"${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ},\" \\\n \t\t\t\"but we have ${SQ}other${SQ} locally.\" >expect &&\n-\t\techo \"Run ${SQ}git remote set-head origin main${SQ} to follow the change.\" >>expect &&\n \t\ttest_cmp expect output &&\n \t\thead=$(git rev-parse refs/remotes/origin/HEAD) &&\n \t\tbranch=$(git rev-parse refs/remotes/origin/other) &&\n@@ -160,7 +159,6 @@ test_expect_success \"fetch test followRemoteHEAD warn detached\" '\n \t\techo \"${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ},\" \\\n \t\t\t\"but we have a detached HEAD pointing to\" \\\n \t\t\t\"${SQ}${HEAD}${SQ} locally.\" >expect &&\n-\t\techo \"Run ${SQ}git remote set-head origin main${SQ} to follow the change.\" >>expect &&\n \t\ttest_cmp expect output\n \t)\n '\n-- \n2.47.1.398.g18e7475ebe\n\n"},{"id":"508668","messageId":"20241205121737.1181695-3-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20241205121737.1181695-1-bence@ferdinandy.com","subject":"[PATCH v3 3/3] remote set-head: set followRemoteHEAD to \"warn\" if \"always\"","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-12-05T12:16:22Z","receivedAt":"2024-12-05T12:19:05Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"When running \"remote set-head\" manually it is unlikely, that the user\nwould actually like to have \"fetch\" always update the remote/HEAD. On\nthe contrary, it is more likely, that the user would expect remote/HEAD\nto stay the way they manually set it, and just forgot about having\n\"followRemoteHEAD\" set to \"always\".\n\nWhen \"followRemoteHEAD\" is set to \"always\" make running \"remote\nset-head\" change the config to \"warn\".\n\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n\nNotes:\n    v3: s/output/actual\n\n builtin/remote.c  | 12 +++++++++++-\n t/t5505-remote.sh | 11 +++++++++++\n 2 files changed, 22 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex d5b81445f2..32d02ca4a0 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -1438,6 +1438,7 @@ static int set_head(int argc, const char **argv, const char *prefix,\n \t\tb_local_head = STRBUF_INIT;\n \tchar *head_name = NULL;\n \tstruct ref_store *refs = get_main_ref_store(the_repository);\n+\tstruct remote *remote;\n \n \tstruct option options[] = {\n \t\tOPT_BOOL('a', \"auto\", &opt_a,\n@@ -1448,8 +1449,10 @@ static int set_head(int argc, const char **argv, const char *prefix,\n \t};\n \targc = parse_options(argc, argv, prefix, options,\n \t\t\t     builtin_remote_sethead_usage, 0);\n-\tif (argc)\n+\tif (argc) {\n \t\tstrbuf_addf(&b_head, \"refs/remotes/%s/HEAD\", argv[0]);\n+\t\tremote = remote_get(argv[0]);\n+\t}\n \n \tif (!opt_a && !opt_d && argc == 2) {\n \t\thead_name = xstrdup(argv[1]);\n@@ -1488,6 +1491,13 @@ static int set_head(int argc, const char **argv, const char *prefix,\n \t}\n \tif (opt_a)\n \t\treport_set_head_auto(argv[0], head_name, &b_local_head, was_detached);\n+\tif (remote->follow_remote_head == FOLLOW_REMOTE_ALWAYS) {\n+\t\tstruct strbuf config_name = STRBUF_INIT;\n+\t\tstrbuf_addf(&config_name,\n+\t\t\t\"remote.%s.followremotehead\", remote->name);\n+\t\tgit_config_set(config_name.buf, \"warn\");\n+\t\tstrbuf_release(&config_name);\n+\t}\n \n cleanup:\n \tfree(head_name);\ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex 7411aa770d..519f7973e3 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -504,6 +504,17 @@ test_expect_success 'set-head --auto has no problem w/multiple HEADs' '\n \t)\n '\n \n+test_expect_success 'set-head changes followRemoteHEAD always to warn' '\n+\t(\n+\t\tcd test &&\n+\t\tgit config set remote.origin.followRemoteHEAD \"always\" &&\n+\t\tgit remote set-head --auto origin &&\n+\t\tgit config get remote.origin.followRemoteHEAD >actual &&\n+\t\techo \"warn\" >expect &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n cat >test/expect <<\\EOF\n refs/remotes/origin/side2\n EOF\n-- \n2.47.1.398.g18e7475ebe\n\n"},{"id":"508676","messageId":"wmu5ld4xsfjz2tpp54jmxqroma423t5nn2jh3q5gq2wswr6fkm@vrlhigyuxfzu","threadId":"62096","inReplyTo":"20241122123138.66960-10-bence@ferdinandy.com","subject":"Re: [PATCH v15 09/10] fetch: set remote/HEAD if it does not exist","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2024-12-05T18:58:37Z","receivedAt":"2024-12-05T18:58:43Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2024.11.22 13:28, Bence Ferdinandy wrote:\n> When cloning a repository remote/HEAD is created, but when the user\n> creates a repository with git init, and later adds a remote, remote/HEAD\n> is only created if the user explicitly runs a variant of \"remote\n> set-head\". Attempt to set remote/HEAD during fetch, if the user does not\n> have it already set. Silently ignore any errors.\n> \n> Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n\nAt $DAYJOB, we noticed that this breaks `git fetch --tags`, although I\nhaven't had a chance to figure out what causes the error just yet.\n\nI was able to bisect down to this commit using Jonathan Tan's\nreproduction script:\n\nrm -rf test_tag_1 test_tag_2\nGIT=~/git/bin-wrappers/git\nmkdir test_tag_1 && cd test_tag_1\nREMOTE=$(pwd)\n$GIT init .\ntouch foo.txt\n$GIT add foo.txt\n$GIT commit foo.txt -m \"commit one\"\n$GIT tag foo\ncd ..\nmkdir test_tag_2 && cd test_tag_2\n$GIT init .\necho fetch --tags\n$GIT fetch --tags \"file://$REMOTE\"\necho regular fetch\n$GIT fetch \"file://$REMOTE\" 'refs/tags/*:refs/tags/*'\n$GIT --version\n\n\nPrior to this change, the first `$GIT fetch --tags \"file://$REMOTE\"`\nfetches the `foo` tag; with this change, it does not.\n"},{"id":"508680","messageId":"444kgiknevb3kwtypjjc2glryaav27t5fafgyzqq5257w7o4pf@4fngcyfmvfcp","threadId":"62096","inReplyTo":"wmu5ld4xsfjz2tpp54jmxqroma423t5nn2jh3q5gq2wswr6fkm@vrlhigyuxfzu","subject":"Re: [PATCH v15 09/10] fetch: set remote/HEAD if it does not exist","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2024-12-05T19:50:54Z","receivedAt":"2024-12-05T19:51:00Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2024.12.05 10:58, Josh Steadmon wrote:\n> On 2024.11.22 13:28, Bence Ferdinandy wrote:\n> > When cloning a repository remote/HEAD is created, but when the user\n> > creates a repository with git init, and later adds a remote, remote/HEAD\n> > is only created if the user explicitly runs a variant of \"remote\n> > set-head\". Attempt to set remote/HEAD during fetch, if the user does not\n> > have it already set. Silently ignore any errors.\n> > \n> > Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n> \n> At $DAYJOB, we noticed that this breaks `git fetch --tags`, although I\n> haven't had a chance to figure out what causes the error just yet.\n> \n> I was able to bisect down to this commit using Jonathan Tan's\n> reproduction script:\n> \n> rm -rf test_tag_1 test_tag_2\n> GIT=~/git/bin-wrappers/git\n> mkdir test_tag_1 && cd test_tag_1\n> REMOTE=$(pwd)\n> $GIT init .\n> touch foo.txt\n> $GIT add foo.txt\n> $GIT commit foo.txt -m \"commit one\"\n> $GIT tag foo\n> cd ..\n> mkdir test_tag_2 && cd test_tag_2\n> $GIT init .\n> echo fetch --tags\n> $GIT fetch --tags \"file://$REMOTE\"\n> echo regular fetch\n> $GIT fetch \"file://$REMOTE\" 'refs/tags/*:refs/tags/*'\n> $GIT --version\n> \n> \n> Prior to this change, the first `$GIT fetch --tags \"file://$REMOTE\"`\n> fetches the `foo` tag; with this change, it does not.\n\n\nFWIW, moving this:\n\n@@ -1643,6 +1703,8 @@ static int do_fetch(struct transport *transport,\n                                    \"refs/tags/\");\n        }\n\n+       strvec_push(&transport_ls_refs_options.ref_prefixes, \"HEAD\");\n+\n\nto just above the prior `if` block fixes our issue and doesn't break any\ntests. However, I'm not sure yet why the order of ref_prefixes should\nmatter here.\n"},{"id":"508683","messageId":"D641F472LOV5.223NEPZXKIL4Q@ferdinandy.com","threadId":"62096","inReplyTo":"444kgiknevb3kwtypjjc2glryaav27t5fafgyzqq5257w7o4pf@4fngcyfmvfcp","subject":"Re: [PATCH v15 09/10] fetch: set remote/HEAD if it does not exist","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-12-05T20:09:06Z","receivedAt":"2024-12-05T20:09:43Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Thu Dec 05, 2024 at 20:50, Josh Steadmon <steadmon@google.com> wrote:\n> On 2024.12.05 10:58, Josh Steadmon wrote:\n>> On 2024.11.22 13:28, Bence Ferdinandy wrote:\n>> > When cloning a repository remote/HEAD is created, but when the user\n>> > creates a repository with git init, and later adds a remote, remote/HEAD\n>> > is only created if the user explicitly runs a variant of \"remote\n>> > set-head\". Attempt to set remote/HEAD during fetch, if the user does not\n>> > have it already set. Silently ignore any errors.\n>> > \n>> > Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n>> \n>> At $DAYJOB, we noticed that this breaks `git fetch --tags`, although I\n>> haven't had a chance to figure out what causes the error just yet.\n>> \n>> I was able to bisect down to this commit using Jonathan Tan's\n>> reproduction script:\n>> \n>> rm -rf test_tag_1 test_tag_2\n>> GIT=~/git/bin-wrappers/git\n>> mkdir test_tag_1 && cd test_tag_1\n>> REMOTE=$(pwd)\n>> $GIT init .\n>> touch foo.txt\n>> $GIT add foo.txt\n>> $GIT commit foo.txt -m \"commit one\"\n>> $GIT tag foo\n>> cd ..\n>> mkdir test_tag_2 && cd test_tag_2\n>> $GIT init .\n>> echo fetch --tags\n>> $GIT fetch --tags \"file://$REMOTE\"\n>> echo regular fetch\n>> $GIT fetch \"file://$REMOTE\" 'refs/tags/*:refs/tags/*'\n>> $GIT --version\n>> \n>> \n>> Prior to this change, the first `$GIT fetch --tags \"file://$REMOTE\"`\n>> fetches the `foo` tag; with this change, it does not.\n>\n>\n> FWIW, moving this:\n>\n> @@ -1643,6 +1703,8 @@ static int do_fetch(struct transport *transport,\n>                                     \"refs/tags/\");\n>         }\n>\n> +       strvec_push(&transport_ls_refs_options.ref_prefixes, \"HEAD\");\n> +\n>\n> to just above the prior `if` block fixes our issue and doesn't break any\n> tests. However, I'm not sure yet why the order of ref_prefixes should\n> matter here.\n\nThanks for looking into this! I think the issue is with \n\n $GIT fetch --tags \"file://$REMOTE\"\n\ninstead of adding a proper remote. Tbh, I've never seen the above syntax before, so first I just ran your script, which reproduced the issue for me, but then I modified it to use a proper remote which works as expected:\n\nrm -rf test_tag_1 test_tag_2\nGIT=~/git/bin-wrappers/git\nmkdir test_tag_1 && cd test_tag_1\nREMOTE=$(pwd)\n$GIT init .\ntouch foo.txt\n$GIT add foo.txt\n$GIT commit foo.txt -m \"commit one\"\n$GIT tag foo\ncd ..\nmkdir test_tag_2 && cd test_tag_2\n$GIT init .\n$GIT remote add origin $REMOTE\necho fetch --tags\n$GIT fetch origin --tags \necho regular fetch\n$GIT fetch origin 'refs/tags/*:refs/tags/*'\n$GIT --version\n\nSo I'm assuming this is why also the tests never caught this, since probably\nall of them are using `git remote add`.\n\n"},{"id":"508684","messageId":"hpaekjhdpcovhdptdntdligp5jcdp7mygh5brnggu7itf5grzp@vl4l7uwnb3n7","threadId":"62096","inReplyTo":"D641F472LOV5.223NEPZXKIL4Q@ferdinandy.com","subject":"Re: [PATCH v15 09/10] fetch: set remote/HEAD if it does not exist","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2024-12-05T20:11:41Z","receivedAt":"2024-12-05T20:11:49Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2024.12.05 21:09, Bence Ferdinandy wrote:\n> \n> On Thu Dec 05, 2024 at 20:50, Josh Steadmon <steadmon@google.com> wrote:\n> > On 2024.12.05 10:58, Josh Steadmon wrote:\n> >> On 2024.11.22 13:28, Bence Ferdinandy wrote:\n> >> > When cloning a repository remote/HEAD is created, but when the user\n> >> > creates a repository with git init, and later adds a remote, remote/HEAD\n> >> > is only created if the user explicitly runs a variant of \"remote\n> >> > set-head\". Attempt to set remote/HEAD during fetch, if the user does not\n> >> > have it already set. Silently ignore any errors.\n> >> > \n> >> > Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n> >> \n> >> At $DAYJOB, we noticed that this breaks `git fetch --tags`, although I\n> >> haven't had a chance to figure out what causes the error just yet.\n> >> \n> >> I was able to bisect down to this commit using Jonathan Tan's\n> >> reproduction script:\n> >> \n> >> rm -rf test_tag_1 test_tag_2\n> >> GIT=~/git/bin-wrappers/git\n> >> mkdir test_tag_1 && cd test_tag_1\n> >> REMOTE=$(pwd)\n> >> $GIT init .\n> >> touch foo.txt\n> >> $GIT add foo.txt\n> >> $GIT commit foo.txt -m \"commit one\"\n> >> $GIT tag foo\n> >> cd ..\n> >> mkdir test_tag_2 && cd test_tag_2\n> >> $GIT init .\n> >> echo fetch --tags\n> >> $GIT fetch --tags \"file://$REMOTE\"\n> >> echo regular fetch\n> >> $GIT fetch \"file://$REMOTE\" 'refs/tags/*:refs/tags/*'\n> >> $GIT --version\n> >> \n> >> \n> >> Prior to this change, the first `$GIT fetch --tags \"file://$REMOTE\"`\n> >> fetches the `foo` tag; with this change, it does not.\n> >\n> >\n> > FWIW, moving this:\n> >\n> > @@ -1643,6 +1703,8 @@ static int do_fetch(struct transport *transport,\n> >                                     \"refs/tags/\");\n> >         }\n> >\n> > +       strvec_push(&transport_ls_refs_options.ref_prefixes, \"HEAD\");\n> > +\n> >\n> > to just above the prior `if` block fixes our issue and doesn't break any\n> > tests. However, I'm not sure yet why the order of ref_prefixes should\n> > matter here.\n> \n> Thanks for looking into this! I think the issue is with \n> \n>  $GIT fetch --tags \"file://$REMOTE\"\n> \n> instead of adding a proper remote. Tbh, I've never seen the above syntax before, so first I just ran your script, which reproduced the issue for me, but then I modified it to use a proper remote which works as expected:\n> \n> rm -rf test_tag_1 test_tag_2\n> GIT=~/git/bin-wrappers/git\n> mkdir test_tag_1 && cd test_tag_1\n> REMOTE=$(pwd)\n> $GIT init .\n> touch foo.txt\n> $GIT add foo.txt\n> $GIT commit foo.txt -m \"commit one\"\n> $GIT tag foo\n> cd ..\n> mkdir test_tag_2 && cd test_tag_2\n> $GIT init .\n> $GIT remote add origin $REMOTE\n> echo fetch --tags\n> $GIT fetch origin --tags \n> echo regular fetch\n> $GIT fetch origin 'refs/tags/*:refs/tags/*'\n> $GIT --version\n> \n> So I'm assuming this is why also the tests never caught this, since probably\n> all of them are using `git remote add`.\n> \n\nYeah, I think the issue is that we check the `--tags` flag first, but\nonly add the ref_prefixes entry if it's not empty already. Then after\nthat we unconditionally add HEAD. So that's why moving your strvec_push\nearlier fixes it. I'll send a fix + test patch in just a minute.\n"},{"id":"508689","messageId":"b41ae080654a3603af09801018df539f656cf9d8.1733430345.git.steadmon@google.com","threadId":"62096","inReplyTo":"hpaekjhdpcovhdptdntdligp5jcdp7mygh5brnggu7itf5grzp@vl4l7uwnb3n7","subject":"[PATCH] Fix `git fetch --tags` in repo with no configured remote","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2024-12-05T20:27:20Z","receivedAt":"2024-12-05T20:27:22Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"In 3f763ddf28 (fetch: set remote/HEAD if it does not exist, 2024-11-22),\ngit-fetch learned to opportunistically set $REMOTE/HEAD when fetching.\nHowever, this broke the logic for the `--tags` flag. Specifically, we\nnow unconditionally add HEAD to the ref_prefixes list, but we did this\n*after* deciding whether we also need to explicitly request tags.\n\nFix this by adding HEAD to the ref_prefixes list prior to handling the\n`--tags` flag, and removing the now obsolete check whether ref_prefixes\nis empty or not.\n\nSigned-off-by: Josh Steadmon <steadmon@google.com>\n---\n builtin/fetch.c  |  9 ++++-----\n t/t5510-fetch.sh | 17 +++++++++++++++++\n 2 files changed, 21 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex b2a36a5d95..e7b0c79678 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1699,15 +1699,14 @@ static int do_fetch(struct transport *transport,\n \t\t}\n \t}\n \n+\tstrvec_push(&transport_ls_refs_options.ref_prefixes, \"HEAD\");\n+\n \tif (tags == TAGS_SET || tags == TAGS_DEFAULT) {\n \t\tmust_list_refs = 1;\n-\t\tif (transport_ls_refs_options.ref_prefixes.nr)\n-\t\t\tstrvec_push(&transport_ls_refs_options.ref_prefixes,\n-\t\t\t\t    \"refs/tags/\");\n+\t\tstrvec_push(&transport_ls_refs_options.ref_prefixes,\n+\t\t\t    \"refs/tags/\");\n \t}\n \n-\tstrvec_push(&transport_ls_refs_options.ref_prefixes, \"HEAD\");\n-\n \tif (must_list_refs) {\n \t\ttrace2_region_enter(\"fetch\", \"remote_refs\", the_repository);\n \t\tremote_refs = transport_get_remote_refs(transport,\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex 87698341f5..d7602333ff 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -189,6 +189,23 @@ test_expect_success 'fetch --prune --tags with refspec prunes based on refspec'\n \tgit rev-parse sometag\n '\n \n+test_expect_success 'fetch --tags gets tags even without a configured remote' '\n+\tREMOTE=\"$(pwd)/test_tag_1\" &&\n+\tgit init test_tag_1 &&\n+\t(\n+\t\tcd test_tag_1 &&\n+\t\ttest_commit foo\n+\t) &&\n+\tgit init test_tag_2 &&\n+\t(\n+\t\tcd test_tag_2 &&\n+\t\tgit fetch --tags \"file://$REMOTE\" &&\n+\t\techo \"foo\" >expect &&\n+\t\tgit tag >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n test_expect_success REFFILES 'fetch --prune fails to delete branches' '\n \tcd \"$D\" &&\n \tgit clone . prune-fail &&\n\nbase-commit: 3f763ddf28d28fe63963991513c8db4045eabadc\n-- \n2.47.0.338.g60cca15819-goog\n\n"},{"id":"508691","messageId":"D642FXNUF2ZU.1EPADL3CIBAXE@ferdinandy.com","threadId":"62096","inReplyTo":"hpaekjhdpcovhdptdntdligp5jcdp7mygh5brnggu7itf5grzp@vl4l7uwnb3n7","subject":"Re: [PATCH v15 09/10] fetch: set remote/HEAD if it does not exist","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-12-05T20:57:11Z","receivedAt":"2024-12-05T20:57:44Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Thu Dec 05, 2024 at 21:11, Josh Steadmon <steadmon@google.com> wrote:\n[snip]\n>> \n>> Thanks for looking into this! I think the issue is with \n>> \n>>  $GIT fetch --tags \"file://$REMOTE\"\n>> \n>> instead of adding a proper remote. Tbh, I've never seen the above syntax before, so first I just ran your script, which reproduced the issue for me, but then I modified it to use a proper remote which works as expected:\n>> \n>> rm -rf test_tag_1 test_tag_2\n>> GIT=~/git/bin-wrappers/git\n>> mkdir test_tag_1 && cd test_tag_1\n>> REMOTE=$(pwd)\n>> $GIT init .\n>> touch foo.txt\n>> $GIT add foo.txt\n>> $GIT commit foo.txt -m \"commit one\"\n>> $GIT tag foo\n>> cd ..\n>> mkdir test_tag_2 && cd test_tag_2\n>> $GIT init .\n>> $GIT remote add origin $REMOTE\n>> echo fetch --tags\n>> $GIT fetch origin --tags \n>> echo regular fetch\n>> $GIT fetch origin 'refs/tags/*:refs/tags/*'\n>> $GIT --version\n>> \n>> So I'm assuming this is why also the tests never caught this, since probably\n>> all of them are using `git remote add`.\n>> \n>\n> Yeah, I think the issue is that we check the `--tags` flag first, but\n> only add the ref_prefixes entry if it's not empty already. Then after\n> that we unconditionally add HEAD. So that's why moving your strvec_push\n> earlier fixes it. I'll send a fix + test patch in just a minute.\n\nAwesome, thanks!\n"},{"id":"508699","messageId":"xmqqo71pzeyc.fsf@gitster.g","threadId":"62096","inReplyTo":"b41ae080654a3603af09801018df539f656cf9d8.1733430345.git.steadmon@google.com","subject":"Re: [PATCH] Fix `git fetch --tags` in repo with no configured remote","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-12-06T03:07:55Z","receivedAt":"2024-12-06T03:07:58Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n> In 3f763ddf28 (fetch: set remote/HEAD if it does not exist, 2024-11-22),\n> git-fetch learned to opportunistically set $REMOTE/HEAD when fetching.\n> However, this broke the logic for the `--tags` flag. Specifically, we\n> now unconditionally add HEAD to the ref_prefixes list, but we did this\n> *after* deciding whether we also need to explicitly request tags.\n>\n> Fix this by adding HEAD to the ref_prefixes list prior to handling the\n> `--tags` flag, and removing the now obsolete check whether ref_prefixes\n> is empty or not.\n>\n> Signed-off-by: Josh Steadmon <steadmon@google.com>\n> ---\n>  builtin/fetch.c  |  9 ++++-----\n>  t/t5510-fetch.sh | 17 +++++++++++++++++\n>  2 files changed, 21 insertions(+), 5 deletions(-)\n\nI see Bence is happy with the fix in a nearby message, so let me\nqueue (and perhaps later amend it with Acked-by from Bence if we see\none) the fix.\n\nThanks.\n"},{"id":"508702","messageId":"xmqqcyi5zdzb.fsf@gitster.g","threadId":"62096","inReplyTo":"b41ae080654a3603af09801018df539f656cf9d8.1733430345.git.steadmon@google.com","subject":"Re: [PATCH] Fix `git fetch --tags` in repo with no configured remote","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-12-06T03:28:56Z","receivedAt":"2024-12-06T03:28:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n> In 3f763ddf28 (fetch: set remote/HEAD if it does not exist, 2024-11-22),\n> git-fetch learned to opportunistically set $REMOTE/HEAD when fetching.\n> However, this broke the logic for the `--tags` flag. Specifically, we\n> now unconditionally add HEAD to the ref_prefixes list, but we did this\n> *after* deciding whether we also need to explicitly request tags.\n>\n> Fix this by adding HEAD to the ref_prefixes list prior to handling the\n> `--tags` flag, and removing the now obsolete check whether ref_prefixes\n> is empty or not.\n>\n> Signed-off-by: Josh Steadmon <steadmon@google.com>\n> ---\n>  builtin/fetch.c  |  9 ++++-----\n>  t/t5510-fetch.sh | 17 +++++++++++++++++\n>  2 files changed, 21 insertions(+), 5 deletions(-)\n>\n> diff --git a/builtin/fetch.c b/builtin/fetch.c\n> index b2a36a5d95..e7b0c79678 100644\n> --- a/builtin/fetch.c\n> +++ b/builtin/fetch.c\n> @@ -1699,15 +1699,14 @@ static int do_fetch(struct transport *transport,\n>  \t\t}\n>  \t}\n>  \n> +\tstrvec_push(&transport_ls_refs_options.ref_prefixes, \"HEAD\");\n> +\n>  \tif (tags == TAGS_SET || tags == TAGS_DEFAULT) {\n>  \t\tmust_list_refs = 1;\n> -\t\tif (transport_ls_refs_options.ref_prefixes.nr)\n> -\t\t\tstrvec_push(&transport_ls_refs_options.ref_prefixes,\n> -\t\t\t\t    \"refs/tags/\");\n> +\t\tstrvec_push(&transport_ls_refs_options.ref_prefixes,\n> +\t\t\t    \"refs/tags/\");\n>  \t}\n\nStepping back a bit, do we even need to learn where HEAD points at\nin the remote, when we are not doing the \"opportunistically set\n$REMOTE/HEAD\"?  Your example is \"in repo with no configured remote\",\nwhich by definition means that we do not use any refs/remotes/*/ ref\nhierarchy to keep track of the remote-tracking branches for the\nremote we are fetching from.  There is no place we record what we\nlearn by running ls-remote HEAD against them, so should we even push\n\"HEAD\" to the ls-remote prefixes in such a case?\n\nWhile this change may hide the breakage you saw in your set-up, we\nmay be now asking to ls-remote HEAD even in cases we do not need to.\n\n> Fix this by adding HEAD to the ref_prefixes list prior to handling the\n> `--tags` flag, and removing the now obsolete check whether ref_prefixes\n> is empty or not.\n\nAnd if we unconditionally add HEAD even when we do not need to,\nespecially with the loss of the ref-prefixes condition that was\nthere in order to implement \"learn refs/tags/* hierarchy only when\nwe are doing the default fetch\", wouldn't it mean we may learn\nrefs/tags/* even when we do not have to?\n\n> Signed-off-by: Josh Steadmon <steadmon@google.com>\n> ---\n>  builtin/fetch.c  |  9 ++++-----\n>  t/t5510-fetch.sh | 17 +++++++++++++++++\n>  2 files changed, 21 insertions(+), 5 deletions(-)\n>\n> diff --git a/builtin/fetch.c b/builtin/fetch.c\n> index b2a36a5d95..e7b0c79678 100644\n> --- a/builtin/fetch.c\n> +++ b/builtin/fetch.c\n> @@ -1699,15 +1699,14 @@ static int do_fetch(struct transport *transport,\n>  \t\t}\n>  \t}\n>  \n> +\tstrvec_push(&transport_ls_refs_options.ref_prefixes, \"HEAD\");\n> +\n>  \tif (tags == TAGS_SET || tags == TAGS_DEFAULT) {\n>  \t\tmust_list_refs = 1;\n> -\t\tif (transport_ls_refs_options.ref_prefixes.nr)\n> -\t\t\tstrvec_push(&transport_ls_refs_options.ref_prefixes,\n> -\t\t\t\t    \"refs/tags/\");\n> +\t\tstrvec_push(&transport_ls_refs_options.ref_prefixes,\n> +\t\t\t    \"refs/tags/\");\n>  \t}\n>  \n> -\tstrvec_push(&transport_ls_refs_options.ref_prefixes, \"HEAD\");\n> -\n>  \tif (must_list_refs) {\n>  \t\ttrace2_region_enter(\"fetch\", \"remote_refs\", the_repository);\n>  \t\tremote_refs = transport_get_remote_refs(transport,\n> diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\n> index 87698341f5..d7602333ff 100755\n> --- a/t/t5510-fetch.sh\n> +++ b/t/t5510-fetch.sh\n> @@ -189,6 +189,23 @@ test_expect_success 'fetch --prune --tags with refspec prunes based on refspec'\n>  \tgit rev-parse sometag\n>  '\n>  \n> +test_expect_success 'fetch --tags gets tags even without a configured remote' '\n> +\tREMOTE=\"$(pwd)/test_tag_1\" &&\n> +\tgit init test_tag_1 &&\n> +\t(\n> +\t\tcd test_tag_1 &&\n> +\t\ttest_commit foo\n> +\t) &&\n> +\tgit init test_tag_2 &&\n> +\t(\n> +\t\tcd test_tag_2 &&\n> +\t\tgit fetch --tags \"file://$REMOTE\" &&\n> +\t\techo \"foo\" >expect &&\n> +\t\tgit tag >actual &&\n> +\t\ttest_cmp expect actual\n> +\t)\n> +'\n> +\n>  test_expect_success REFFILES 'fetch --prune fails to delete branches' '\n>  \tcd \"$D\" &&\n>  \tgit clone . prune-fail &&\n>\n> base-commit: 3f763ddf28d28fe63963991513c8db4045eabadc\n"},{"id":"508704","messageId":"xmqq8qstzciw.fsf@gitster.g","threadId":"62096","inReplyTo":"xmqqcyi5zdzb.fsf@gitster.g","subject":"Re: [PATCH] Fix `git fetch --tags` in repo with no configured remote","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-12-06T04:00:23Z","receivedAt":"2024-12-06T04:00:26Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> And if we unconditionally add HEAD even when we do not need to,\n> especially with the loss of the ref-prefixes condition that was\n> there in order to implement \"learn refs/tags/* hierarchy only when\n> we are doing the default fetch\", wouldn't it mean we may learn\n> refs/tags/* even when we do not have to?\n\nMicro-correction.  \"We grab tags only when we are fetching something\nelse as well\" is what I should have said.\n\nThe general direction my comment leads to does not change, though.\nBecause we ask for \"HEAD\" even when we do not need to, the\nadditional change this patch names to unconditionally add \"refs/tags/\"\nwould make us ask to list all the tags even when we do not need to\nsee them.\n\nThanks.\n"},{"id":"508709","messageId":"xmqqfrn1xmj6.fsf@gitster.g","threadId":"62096","inReplyTo":"xmqqcyi5zdzb.fsf@gitster.g","subject":"Re* [PATCH] Fix `git fetch --tags` in repo with no configured remote","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-12-06T08:07:09Z","receivedAt":"2024-12-06T08:07:13Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Stepping back a bit, do we even need to learn where HEAD points at\n> in the remote, when we are not doing the \"opportunistically set\n> $REMOTE/HEAD\"?  Your example is \"in repo with no configured remote\",\n> which by definition means that we do not use any refs/remotes/*/ ref\n> hierarchy to keep track of the remote-tracking branches for the\n> remote we are fetching from.  There is no place we record what we\n> learn by running ls-remote HEAD against them, so should we even push\n> \"HEAD\" to the ls-remote prefixes in such a case?\n>\n> While this change may hide the breakage you saw in your set-up, we\n> may be now asking to ls-remote HEAD even in cases we do not need to.\n>\n>> Fix this by adding HEAD to the ref_prefixes list prior to handling the\n>> `--tags` flag, and removing the now obsolete check whether ref_prefixes\n>> is empty or not.\n>\n> And if we unconditionally add HEAD even when we do not need to,\n> especially with the loss of the ref-prefixes condition that was\n> there in order to implement \"learn refs/tags/* hierarchy only when\n> we are doing the default fetch\", wouldn't it mean we may learn\n> refs/tags/* even when we do not have to?\n\nIn other words, what I think the \"fix\" should look like is more like\nthe attached.  It seems to pass your test, as well as existing tests\nBence added and other tests about \"git fetch\".\n\nOne thing I am not happy about is the abstraction violation that is\nneeded to make the uses_remote_tracking() helper aware of the \"use\nthe rs, the refspec given from the command line, or if it is empty,\nuse the configured 'fetch' refspec from the remote\" rule, which is\nprimarily used by get_ref_map() that is much later called, but the\nlayering violation started when we started limiting the ls-remote\nrequest with narrowing common prefixes, and it would take a larger\nsurgery to fix, I would think.\n\n００００---- >8 ----\nSubject: [PATCH] fetch: do not ask for HEAD unnecessarily\n\nIn 3f763ddf28 (fetch: set remote/HEAD if it does not exist,\n2024-11-22), git-fetch learned to opportunistically set $REMOTE/HEAD\nwhen fetching by always asking for remote HEAD, in the hope that it\nwill help setting refs/remotes/<name>/HEAD if missing.\n\nBut it is not needed to always ask for remote HEAD.  When we are\nfetching from a remote, for which we have remote-tracking branches,\nwe do need to know about HEAD.  But if we are doing one-shot fetch,\ne.g.,\n\n  $ git fetch --tags https://github.com/git/git\n\nwe do not even know what sub-hierarchy of refs/remotes/<remote>/\nwe need to adjust the remote HEAD for.  There is no need to ask for\nHEAD in such a case.\n\nIncidentally, because the unconditional request to list \"HEAD\"\naffected the number of ref-prefixes requested in the ls-remote\nrequest, this affected how the requests for tags are added to the\nsame ls-remote request, breaking \"git fetch --tags $URL\" performed\nagainst a URL that is not configured as a remote.\n\nMake sure we ask to list \"HEAD\" from the remote only when we are\nfetching with configured remote for which we use remote-tracking\nbranches.\n\nReported-by: Josh Steadmon <steadmon@google.com>\n[jc: tests are also borrowed from Josh's patch]\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n\n * Even though I borrowed some part of the commit log message from\n   Josh's version, it not clear to me how \"*after* deciding\" led to\n   whatever the observed breakage (which was not described in the\n   log message), in the following part.\n\n      Specifically, we now unconditionally add HEAD to the\n      ref_prefixes list, but we did this *after* deciding whether we\n      also need to explicitly request tags.\n\n   Bence's change asks \"HEAD\" after \"if we are fetching something,\n   then also ask about refs/tags/\" logic thought we are not fetching\n   anything (i.e. ref_prefixes.nr == 0 at that point).  But before\n   Bence's series, the same refs/tags/ logic saw that (ref_prefix.nr\n   == 0), didn't it?  So that does not sound like a sufficient\n   explanation on how the series regressed.\n\n builtin/fetch.c  | 20 +++++++++++++++++++-\n t/t5510-fetch.sh | 17 +++++++++++++++++\n 2 files changed, 36 insertions(+), 1 deletion(-)\n\ndiff --git c/builtin/fetch.c w/builtin/fetch.c\nindex a64de4485f..3eb6f3acc9 100644\n--- c/builtin/fetch.c\n+++ w/builtin/fetch.c\n@@ -1643,6 +1643,21 @@ static int set_head(const struct ref *remote_refs)\n \treturn result;\n }\n \n+static int uses_remote_tracking(struct transport *transport, struct refspec *rs)\n+{\n+\tif (!remote_is_configured(transport->remote, 0))\n+\t\treturn 0;\n+\n+\tif (!rs->nr)\n+\t\trs = &transport->remote->fetch;\n+\n+\tfor (int i = 0; i < rs->nr; i++)\n+\t\tif (rs->items[i].dst)\n+\t\t\treturn 1;\n+\n+\treturn 0;\n+}\n+\n static int do_fetch(struct transport *transport,\n \t\t    struct refspec *rs,\n \t\t    const struct fetch_config *config)\n@@ -1712,7 +1727,10 @@ static int do_fetch(struct transport *transport,\n \t\t\t\t    \"refs/tags/\");\n \t}\n \n-\tstrvec_push(&transport_ls_refs_options.ref_prefixes, \"HEAD\");\n+\tif (uses_remote_tracking(transport, rs)) {\n+\t\tmust_list_refs = 1;\n+\t\tstrvec_push(&transport_ls_refs_options.ref_prefixes, \"HEAD\");\n+\t}\n \n \tif (must_list_refs) {\n \t\ttrace2_region_enter(\"fetch\", \"remote_refs\", the_repository);\ndiff --git c/t/t5510-fetch.sh w/t/t5510-fetch.sh\nindex 87698341f5..d7602333ff 100755\n--- c/t/t5510-fetch.sh\n+++ w/t/t5510-fetch.sh\n@@ -189,6 +189,23 @@ test_expect_success 'fetch --prune --tags with refspec prunes based on refspec'\n \tgit rev-parse sometag\n '\n \n+test_expect_success 'fetch --tags gets tags even without a configured remote' '\n+\tREMOTE=\"$(pwd)/test_tag_1\" &&\n+\tgit init test_tag_1 &&\n+\t(\n+\t\tcd test_tag_1 &&\n+\t\ttest_commit foo\n+\t) &&\n+\tgit init test_tag_2 &&\n+\t(\n+\t\tcd test_tag_2 &&\n+\t\tgit fetch --tags \"file://$REMOTE\" &&\n+\t\techo \"foo\" >expect &&\n+\t\tgit tag >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n test_expect_success REFFILES 'fetch --prune fails to delete branches' '\n \tcd \"$D\" &&\n \tgit clone . prune-fail &&\n"},{"id":"508710","messageId":"xmqqcyi5xmhr.fsf@gitster.g","threadId":"62096","inReplyTo":"xmqqcyi5zdzb.fsf@gitster.g","subject":"Re* [PATCH] Fix `git fetch --tags` in repo with no configured remote","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-12-06T08:08:00Z","receivedAt":"2024-12-06T08:08:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Stepping back a bit, do we even need to learn where HEAD points at\n> in the remote, when we are not doing the \"opportunistically set\n> $REMOTE/HEAD\"?  Your example is \"in repo with no configured remote\",\n> which by definition means that we do not use any refs/remotes/*/ ref\n> hierarchy to keep track of the remote-tracking branches for the\n> remote we are fetching from.  There is no place we record what we\n> learn by running ls-remote HEAD against them, so should we even push\n> \"HEAD\" to the ls-remote prefixes in such a case?\n>\n> While this change may hide the breakage you saw in your set-up, we\n> may be now asking to ls-remote HEAD even in cases we do not need to.\n>\n>> Fix this by adding HEAD to the ref_prefixes list prior to handling the\n>> `--tags` flag, and removing the now obsolete check whether ref_prefixes\n>> is empty or not.\n>\n> And if we unconditionally add HEAD even when we do not need to,\n> especially with the loss of the ref-prefixes condition that was\n> there in order to implement \"learn refs/tags/* hierarchy only when\n> we are doing the default fetch\", wouldn't it mean we may learn\n> refs/tags/* even when we do not have to?\n\nIn other words, what I think the \"fix\" should look like is more like\nthe attached.  It seems to pass your test, as well as existing tests\nBence added and other tests about \"git fetch\".\n\nOne thing I am not happy about is the abstraction violation that is\nneeded to make the uses_remote_tracking() helper aware of the \"use\nthe rs, the refspec given from the command line, or if it is empty,\nuse the configured 'fetch' refspec from the remote\" rule, which is\nprimarily used by get_ref_map() that is much later called, but the\nlayering violation started when we started limiting the ls-remote\nrequest with narrowing common prefixes, and it would take a larger\nsurgery to fix, I would think.\n\n---- >8 ----\nSubject: [PATCH] fetch: do not ask for HEAD unnecessarily\n\nIn 3f763ddf28 (fetch: set remote/HEAD if it does not exist,\n2024-11-22), git-fetch learned to opportunistically set $REMOTE/HEAD\nwhen fetching by always asking for remote HEAD, in the hope that it\nwill help setting refs/remotes/<name>/HEAD if missing.\n\nBut it is not needed to always ask for remote HEAD.  When we are\nfetching from a remote, for which we have remote-tracking branches,\nwe do need to know about HEAD.  But if we are doing one-shot fetch,\ne.g.,\n\n  $ git fetch --tags https://github.com/git/git\n\nwe do not even know what sub-hierarchy of refs/remotes/<remote>/\nwe need to adjust the remote HEAD for.  There is no need to ask for\nHEAD in such a case.\n\nIncidentally, because the unconditional request to list \"HEAD\"\naffected the number of ref-prefixes requested in the ls-remote\nrequest, this affected how the requests for tags are added to the\nsame ls-remote request, breaking \"git fetch --tags $URL\" performed\nagainst a URL that is not configured as a remote.\n\nReported-by: Josh Steadmon <steadmon@google.com>\n[jc: tests are also borrowed from Josh's patch]\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n\n * Even though I borrowed some part of the commit log message from\n   Josh's version, it not clear to me how \"*after* deciding\" led to\n   whatever the observed breakage (which was not described in the\n   log message), in the following part.\n\n      Specifically, we now unconditionally add HEAD to the\n      ref_prefixes list, but we did this *after* deciding whether we\n      also need to explicitly request tags.\n\n   Bence's change asks \"HEAD\" after \"if we are fetching something,\n   then also ask about refs/tags/\" logic thought we are not fetching\n   anything (i.e. ref_prefixes.nr == 0 at that point).  But before\n   Bence's series, the same refs/tags/ logic saw that (ref_prefix.nr\n   == 0), didn't it?  So that does not sound like a sufficient\n   explanation on how the series regressed.\n---\n builtin/fetch.c  | 20 +++++++++++++++++++-\n t/t5510-fetch.sh | 17 +++++++++++++++++\n 2 files changed, 36 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex a64de4485f..3eb6f3acc9 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1643,6 +1643,21 @@ static int set_head(const struct ref *remote_refs)\n \treturn result;\n }\n \n+static int uses_remote_tracking(struct transport *transport, struct refspec *rs)\n+{\n+\tif (!remote_is_configured(transport->remote, 0))\n+\t\treturn 0;\n+\n+\tif (!rs->nr)\n+\t\trs = &transport->remote->fetch;\n+\n+\tfor (int i = 0; i < rs->nr; i++)\n+\t\tif (rs->items[i].dst)\n+\t\t\treturn 1;\n+\n+\treturn 0;\n+}\n+\n static int do_fetch(struct transport *transport,\n \t\t    struct refspec *rs,\n \t\t    const struct fetch_config *config)\n@@ -1712,7 +1727,10 @@ static int do_fetch(struct transport *transport,\n \t\t\t\t    \"refs/tags/\");\n \t}\n \n-\tstrvec_push(&transport_ls_refs_options.ref_prefixes, \"HEAD\");\n+\tif (uses_remote_tracking(transport, rs)) {\n+\t\tmust_list_refs = 1;\n+\t\tstrvec_push(&transport_ls_refs_options.ref_prefixes, \"HEAD\");\n+\t}\n \n \tif (must_list_refs) {\n \t\ttrace2_region_enter(\"fetch\", \"remote_refs\", the_repository);\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex 87698341f5..d7602333ff 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -189,6 +189,23 @@ test_expect_success 'fetch --prune --tags with refspec prunes based on refspec'\n \tgit rev-parse sometag\n '\n \n+test_expect_success 'fetch --tags gets tags even without a configured remote' '\n+\tREMOTE=\"$(pwd)/test_tag_1\" &&\n+\tgit init test_tag_1 &&\n+\t(\n+\t\tcd test_tag_1 &&\n+\t\ttest_commit foo\n+\t) &&\n+\tgit init test_tag_2 &&\n+\t(\n+\t\tcd test_tag_2 &&\n+\t\tgit fetch --tags \"file://$REMOTE\" &&\n+\t\techo \"foo\" >expect &&\n+\t\tgit tag >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n test_expect_success REFFILES 'fetch --prune fails to delete branches' '\n \tcd \"$D\" &&\n \tgit clone . prune-fail &&\n-- \n2.47.1-576-g9f8c8eb655\n"},{"id":"508741","messageId":"D64KVBBE5WVH.19V9S7ZY89IZS@ferdinandy.com","threadId":"62096","inReplyTo":"xmqqcyi5xmhr.fsf@gitster.g","subject":"Re: Re* [PATCH] Fix `git fetch --tags` in repo with no configured remote","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-12-06T11:23:36Z","receivedAt":"2024-12-06T11:24:06Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Fri Dec 06, 2024 at 09:08, Junio C Hamano <gitster@pobox.com> wrote:\n> Junio C Hamano <gitster@pobox.com> writes:\n>\n>> Stepping back a bit, do we even need to learn where HEAD points at\n>> in the remote, when we are not doing the \"opportunistically set\n>> $REMOTE/HEAD\"?  Your example is \"in repo with no configured remote\",\n>> which by definition means that we do not use any refs/remotes/*/ ref\n>> hierarchy to keep track of the remote-tracking branches for the\n>> remote we are fetching from.  There is no place we record what we\n>> learn by running ls-remote HEAD against them, so should we even push\n>> \"HEAD\" to the ls-remote prefixes in such a case?\n\nYes, it probably doesn't make any sense to do that.\n\n>>\n>> While this change may hide the breakage you saw in your set-up, we\n>> may be now asking to ls-remote HEAD even in cases we do not need to.\n>>\n>>> Fix this by adding HEAD to the ref_prefixes list prior to handling the\n>>> `--tags` flag, and removing the now obsolete check whether ref_prefixes\n>>> is empty or not.\n>>\n>> And if we unconditionally add HEAD even when we do not need to,\n>> especially with the loss of the ref-prefixes condition that was\n>> there in order to implement \"learn refs/tags/* hierarchy only when\n>> we are doing the default fetch\", wouldn't it mean we may learn\n>> refs/tags/* even when we do not have to?\n>\n> In other words, what I think the \"fix\" should look like is more like\n> the attached.  It seems to pass your test, as well as existing tests\n> Bence added and other tests about \"git fetch\".\n>\n> One thing I am not happy about is the abstraction violation that is\n> needed to make the uses_remote_tracking() helper aware of the \"use\n> the rs, the refspec given from the command line, or if it is empty,\n> use the configured 'fetch' refspec from the remote\" rule, which is\n> primarily used by get_ref_map() that is much later called, but the\n> layering violation started when we started limiting the ls-remote\n> request with narrowing common prefixes, and it would take a larger\n> surgery to fix, I would think.\n>\n> ---- >8 ----\n> Subject: [PATCH] fetch: do not ask for HEAD unnecessarily\n>\n> In 3f763ddf28 (fetch: set remote/HEAD if it does not exist,\n> 2024-11-22), git-fetch learned to opportunistically set $REMOTE/HEAD\n> when fetching by always asking for remote HEAD, in the hope that it\n> will help setting refs/remotes/<name>/HEAD if missing.\n>\n> But it is not needed to always ask for remote HEAD.  When we are\n> fetching from a remote, for which we have remote-tracking branches,\n> we do need to know about HEAD.  But if we are doing one-shot fetch,\n> e.g.,\n>\n>   $ git fetch --tags https://github.com/git/git\n>\n> we do not even know what sub-hierarchy of refs/remotes/<remote>/\n> we need to adjust the remote HEAD for.  There is no need to ask for\n> HEAD in such a case.\n>\n> Incidentally, because the unconditional request to list \"HEAD\"\n> affected the number of ref-prefixes requested in the ls-remote\n> request, this affected how the requests for tags are added to the\n> same ls-remote request, breaking \"git fetch --tags $URL\" performed\n> against a URL that is not configured as a remote.\n>\n> Reported-by: Josh Steadmon <steadmon@google.com>\n> [jc: tests are also borrowed from Josh's patch]\n> Signed-off-by: Junio C Hamano <gitster@pobox.com>\n> ---\n>\n>  * Even though I borrowed some part of the commit log message from\n>    Josh's version, it not clear to me how \"*after* deciding\" led to\n>    whatever the observed breakage (which was not described in the\n>    log message), in the following part.\n>\n>       Specifically, we now unconditionally add HEAD to the\n>       ref_prefixes list, but we did this *after* deciding whether we\n>       also need to explicitly request tags.\n>\n>    Bence's change asks \"HEAD\" after \"if we are fetching something,\n>    then also ask about refs/tags/\" logic thought we are not fetching\n>    anything (i.e. ref_prefixes.nr == 0 at that point).  But before\n>    Bence's series, the same refs/tags/ logic saw that (ref_prefix.nr\n>    == 0), didn't it?  So that does not sound like a sufficient\n>    explanation on how the series regressed.\n\nI did a bit of poking around on what is happening. For one I can confirm, that\nboth before and after the set_head series\n`transport_ls_refs_options.ref_prefixes.nr` is 0. So the difference must be\nhappening somewhere after that, and is not a side effect of calling set_head\neither, but I didn't manage to pin it down further.\n\nI also checked what happens in set_head, just to be on the safe side: `heads`\nis empty so we reach the if where we check `heads.nr` which is 0. So at least\nno strange refs are created :)\n\n\n\n> ---\n>  builtin/fetch.c  | 20 +++++++++++++++++++-\n>  t/t5510-fetch.sh | 17 +++++++++++++++++\n>  2 files changed, 36 insertions(+), 1 deletion(-)\n>\n> diff --git a/builtin/fetch.c b/builtin/fetch.c\n> index a64de4485f..3eb6f3acc9 100644\n> --- a/builtin/fetch.c\n> +++ b/builtin/fetch.c\n> @@ -1643,6 +1643,21 @@ static int set_head(const struct ref *remote_refs)\n>  \treturn result;\n>  }\n>  \n> +static int uses_remote_tracking(struct transport *transport, struct refspec *rs)\n> +{\n> +\tif (!remote_is_configured(transport->remote, 0))\n> +\t\treturn 0;\n> +\n> +\tif (!rs->nr)\n> +\t\trs = &transport->remote->fetch;\n> +\n> +\tfor (int i = 0; i < rs->nr; i++)\n> +\t\tif (rs->items[i].dst)\n> +\t\t\treturn 1;\n> +\n> +\treturn 0;\n> +}\n> +\n>  static int do_fetch(struct transport *transport,\n>  \t\t    struct refspec *rs,\n>  \t\t    const struct fetch_config *config)\n> @@ -1712,7 +1727,10 @@ static int do_fetch(struct transport *transport,\n>  \t\t\t\t    \"refs/tags/\");\n>  \t}\n>  \n> -\tstrvec_push(&transport_ls_refs_options.ref_prefixes, \"HEAD\");\n> +\tif (uses_remote_tracking(transport, rs)) {\n> +\t\tmust_list_refs = 1;\n> +\t\tstrvec_push(&transport_ls_refs_options.ref_prefixes, \"HEAD\");\n> +\t}\n>  \n>  \tif (must_list_refs) {\n>  \t\ttrace2_region_enter(\"fetch\", \"remote_refs\", the_repository);\n> diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\n> index 87698341f5..d7602333ff 100755\n> --- a/t/t5510-fetch.sh\n> +++ b/t/t5510-fetch.sh\n> @@ -189,6 +189,23 @@ test_expect_success 'fetch --prune --tags with refspec prunes based on refspec'\n>  \tgit rev-parse sometag\n>  '\n>  \n> +test_expect_success 'fetch --tags gets tags even without a configured remote' '\n> +\tREMOTE=\"$(pwd)/test_tag_1\" &&\n> +\tgit init test_tag_1 &&\n> +\t(\n> +\t\tcd test_tag_1 &&\n> +\t\ttest_commit foo\n> +\t) &&\n> +\tgit init test_tag_2 &&\n> +\t(\n> +\t\tcd test_tag_2 &&\n> +\t\tgit fetch --tags \"file://$REMOTE\" &&\n> +\t\techo \"foo\" >expect &&\n> +\t\tgit tag >actual &&\n> +\t\ttest_cmp expect actual\n> +\t)\n> +'\n> +\n>  test_expect_success REFFILES 'fetch --prune fails to delete branches' '\n>  \tcd \"$D\" &&\n>  \tgit clone . prune-fail &&\n\n\n"},{"id":"508743","messageId":"xmqqo71pvyjk.fsf@gitster.g","threadId":"62096","inReplyTo":"xmqqcyi5xmhr.fsf@gitster.g","subject":"Re: Re* [PATCH] Fix `git fetch --tags` in repo with no configured remote","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-12-06T11:30:39Z","receivedAt":"2024-12-06T11:30:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> +static int uses_remote_tracking(struct transport *transport, struct refspec *rs)\n> +{\n> +\tif (!remote_is_configured(transport->remote, 0))\n> +\t\treturn 0;\n> +\n> +\tif (!rs->nr)\n> +\t\trs = &transport->remote->fetch;\n> +\n> +\tfor (int i = 0; i < rs->nr; i++)\n> +\t\tif (rs->items[i].dst)\n> +\t\t\treturn 1;\n> +\n> +\treturn 0;\n> +}\n\nFor the purpose of adjusting refs/remotes/<name>/HEAD, which is\nexpected to point at something that we copy from their refs/heads/,\nit may be tempting to tighten the above logic to say, instead of \"we\nhave a refspec item that stores to somewhere\", do something like\n\n\tfor (int i = 0; i < rs->nr; i++) {\n\t\tstruct refspec_item *item = &rs->items[i];\n\n\t\tif (item->dst &&\n\t\t    item->src && starts_with(item->src, \"refs/heads/\"))\n\t\t\treturn 1;\n\t}\n\nto make sure that we are tracking their branch refs, not some random\nhierarchy for which refs/remotes/<name>/HEAD does not matter.\n\nI will leave the simplest \"just check we have .dst\" version (in\nother words, without any additional check on the .src side) in my\ntree, but if anybody wants to pursue \"let's make sure that .src\ncopies from somewhere in refs/heads/\", please make sure that you\nconsider \"--mirror\", where an equivalent of \"refs/*:refs/*\" is used\nas the refspec, i.e. there is a case where .src does not begin with\n\"refs/heads/\" but does cover the hierarchy and wants to learn about\n\"HEAD\".\n\nThanks.\n\n\n\n"},{"id":"509925","messageId":"20250105114201.35079-1-tenglong.tl@alibaba-inc.com","threadId":"62096","inReplyTo":"20241205121737.1181695-2-bence@ferdinandy.com","subject":"[PATCH v3 2/3] fetch set_head: add warn-if-not-$branch option","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2025-01-05T11:42:01Z","receivedAt":"2025-01-05T11:42:09Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"\nBence Ferdinandy <bence@ferdinandy.com> wrote:\n\n> @@ -1584,10 +1584,12 @@ static void set_head_advice_msg(const char *remote, const char *head_name)\n>  \tconst char message_advice_set_head[] =\n>  \tN_(\"Run 'git remote set-head %s %s' to follow the change, or set\\n\"\n>  \t   \"'remote.%s.followRemoteHEAD' configuration option to a different value\\n\"\n> -\t   \"if you do not want to see this message.\");\n> +\t   \"if you do not want to see this message. Specifically running\\n\"\n> +\t   \"'git config set remote.%s.followRemoteHEAD %s' will disable the warning\\n\"\n> +\t   \"until the remote changes HEAD to something else.\");\n> \n>  \tadvise_if_enabled(ADVICE_FETCH_SET_HEAD_WARN, _(message_advice_set_head),\n> -\t\t\tremote, head_name, remote);\n> +\t\t\tremote, head_name, remote, remote, head_name);\n> \n\nSeems like the config is unsupported to be feed a variable named \"head_name\"?\n\nThanks.\n\n"},{"id":"509940","messageId":"D6U8JPC18M31.3N0PIVKB8XTLG@ferdinandy.com","threadId":"62096","inReplyTo":"20250105114201.35079-1-tenglong.tl@alibaba-inc.com","subject":"Re: [PATCH v3 2/3] fetch set_head: add warn-if-not-$branch option","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2025-01-05T15:13:32Z","receivedAt":"2025-01-05T15:18:57Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Sun Jan 05, 2025 at 12:42, Teng Long <dyroneteng@gmail.com> wrote:\n>\n> Bence Ferdinandy <bence@ferdinandy.com> wrote:\n>\n>> @@ -1584,10 +1584,12 @@ static void set_head_advice_msg(const char *remote, const char *head_name)\n>>  \tconst char message_advice_set_head[] =\n>>  \tN_(\"Run 'git remote set-head %s %s' to follow the change, or set\\n\"\n>>  \t   \"'remote.%s.followRemoteHEAD' configuration option to a different value\\n\"\n>> -\t   \"if you do not want to see this message.\");\n>> +\t   \"if you do not want to see this message. Specifically running\\n\"\n>> +\t   \"'git config set remote.%s.followRemoteHEAD %s' will disable the warning\\n\"\n>> +\t   \"until the remote changes HEAD to something else.\");\n>> \n>>  \tadvise_if_enabled(ADVICE_FETCH_SET_HEAD_WARN, _(message_advice_set_head),\n>> -\t\t\tremote, head_name, remote);\n>> +\t\t\tremote, head_name, remote, remote, head_name);\n>> \n>\n> Seems like the config is unsupported to be feed a variable named \"head_name\"?\n>\n> Thanks.\n\nAh, yes the warning seems to be off, it should read\n\ngit config set remote.%s.followRemoteHEAD warn-if-not-%s\n\nthanks for catching this mistake!\n\nBest,\nBence\n"},{"id":"509942","messageId":"20250105161017.210412-1-bence@ferdinandy.com","threadId":"62096","inReplyTo":"20250105114201.35079-1-tenglong.tl@alibaba-inc.com","subject":"[PATCH] fetch: fix erroneous set_head advice message","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2025-01-05T16:09:40Z","receivedAt":"2025-01-05T16:15:51Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"9e2b7005be (fetch set_head: add warn-if-not-$branch option, 2024-12-05)\ntried to expand the advice message for set_head with the new option, but\nunfortunately did not manage to add the right incantation. Fix the\nadvice message with the correct usage of warn-if-not-$branch.\n\nReported-by: Teng Long <dyroneteng@gmail.com>\nSigned-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n---\n builtin/fetch.c | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 2d37a378ba..fe2b26c74a 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1588,8 +1588,8 @@ static void set_head_advice_msg(const char *remote, const char *head_name)\n \tN_(\"Run 'git remote set-head %s %s' to follow the change, or set\\n\"\n \t   \"'remote.%s.followRemoteHEAD' configuration option to a different value\\n\"\n \t   \"if you do not want to see this message. Specifically running\\n\"\n-\t   \"'git config set remote.%s.followRemoteHEAD %s' will disable the warning\\n\"\n-\t   \"until the remote changes HEAD to something else.\");\n+\t   \"'git config set remote.%s.followRemoteHEAD warn-if-not-branch-%s'\\n\"\n+\t   \"will disable the warning until the remote changes HEAD to something else.\");\n \n \tadvise_if_enabled(ADVICE_FETCH_SET_HEAD_WARN, _(message_advice_set_head),\n \t\t\tremote, head_name, remote, remote, head_name);\n\nbase-commit: 1b4e9a5f8b5f048972c21fe8acafe0404096f694\n-- \n2.48.0.rc0.146.g306ab352f4\n\n"},{"id":"509990","messageId":"xmqqikqs2dzr.fsf@gitster.g","threadId":"62096","inReplyTo":"20250105161017.210412-1-bence@ferdinandy.com","subject":"Re: [PATCH] fetch: fix erroneous set_head advice message","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-01-06T14:50:16Z","receivedAt":"2025-01-06T14:50:20Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Bence Ferdinandy <bence@ferdinandy.com> writes:\n\n> 9e2b7005be (fetch set_head: add warn-if-not-$branch option, 2024-12-05)\n> tried to expand the advice message for set_head with the new option, but\n> unfortunately did not manage to add the right incantation. Fix the\n> advice message with the correct usage of warn-if-not-$branch.\n>\n> Reported-by: Teng Long <dyroneteng@gmail.com>\n> Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>\n> ---\n>  builtin/fetch.c | 4 ++--\n>  1 file changed, 2 insertions(+), 2 deletions(-)\n>\n> diff --git a/builtin/fetch.c b/builtin/fetch.c\n> index 2d37a378ba..fe2b26c74a 100644\n> --- a/builtin/fetch.c\n> +++ b/builtin/fetch.c\n> @@ -1588,8 +1588,8 @@ static void set_head_advice_msg(const char *remote, const char *head_name)\n>  \tN_(\"Run 'git remote set-head %s %s' to follow the change, or set\\n\"\n>  \t   \"'remote.%s.followRemoteHEAD' configuration option to a different value\\n\"\n>  \t   \"if you do not want to see this message. Specifically running\\n\"\n> -\t   \"'git config set remote.%s.followRemoteHEAD %s' will disable the warning\\n\"\n> -\t   \"until the remote changes HEAD to something else.\");\n> +\t   \"'git config set remote.%s.followRemoteHEAD warn-if-not-branch-%s'\\n\"\n> +\t   \"will disable the warning until the remote changes HEAD to something else.\");\n>  \n>  \tadvise_if_enabled(ADVICE_FETCH_SET_HEAD_WARN, _(message_advice_set_head),\n>  \t\t\tremote, head_name, remote, remote, head_name);\n\nThanks, both.  Will queue.\n"}]}