{"thread":{"id":"62383","subject":"[PATCH v10 0/8] set-head/fetch remote/HEAD updates","startedAt":"2024-10-21T13:44:43Z","lastAt":"2024-10-22T19:09:57Z","messageCount":15,"participants":["Bence Ferdinandy","Taylor Blau"],"isPatch":true,"patchVersion":10,"patchTotal":8},"messages":[{"id":"505688","messageId":"20241021134354.705636-1-bence@ferdinandy.com","threadId":"62383","inReplyTo":"1088915169.629942.1729445083543@FVRT-HAMMYAS-P.vizmuvek.hu","subject":"[PATCH v10 0/8] set-head/fetch remote/HEAD updates","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-21T13:36:57Z","receivedAt":"2024-10-21T13:44:43Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"Sorry for sending a v10 so soon after v9 without really waiting for feedback,\nbut as I started digging into\nhttps://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/ I\nrealized that the root cause is in fetch and not remote add, so the solution\nbelongs to this series. I'll leave my overall comments for v9 in here for\nclarity.\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\nHopefully with 1/7 the series is ready to move back to seen :)\n\nBest,\nBence\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                  |  90 ++++++++++++\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, 486 insertions(+), 154 deletions(-)\n\n-- \n2.47.0.94.g8861098b6d\n\n"},{"id":"505689","messageId":"20241021134354.705636-2-bence@ferdinandy.com","threadId":"62383","inReplyTo":"20241021134354.705636-1-bence@ferdinandy.com","subject":"[PATCH v10 1/8] t/t5505-remote: set default branch to main","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-21T13:36:58Z","receivedAt":"2024-10-21T13:44:44Z","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 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.g8861098b6d\n\n"},{"id":"505690","messageId":"20241021134354.705636-3-bence@ferdinandy.com","threadId":"62383","inReplyTo":"20241021134354.705636-1-bence@ferdinandy.com","subject":"[PATCH v10 2/8] refs: atomically record overwritten ref in update_symref","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-21T13:36:59Z","receivedAt":"2024-10-21T13:44:46Z","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 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.g8861098b6d\n\n"},{"id":"505691","messageId":"20241021134354.705636-4-bence@ferdinandy.com","threadId":"62383","inReplyTo":"20241021134354.705636-1-bence@ferdinandy.com","subject":"[PATCH v10 3/8] remote set-head: refactor for readability","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-21T13:37:00Z","receivedAt":"2024-10-21T13:44:49Z","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 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.g8861098b6d\n\n"},{"id":"505692","messageId":"20241021134354.705636-5-bence@ferdinandy.com","threadId":"62383","inReplyTo":"20241021134354.705636-1-bence@ferdinandy.com","subject":"[PATCH v10 4/8] remote set-head: better output for --auto","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-21T13:37:01Z","receivedAt":"2024-10-21T13:44:50Z","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 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.g8861098b6d\n\n"},{"id":"505693","messageId":"20241021134354.705636-6-bence@ferdinandy.com","threadId":"62383","inReplyTo":"20241021134354.705636-1-bence@ferdinandy.com","subject":"[PATCH v10 5/8] refs: add TRANSACTION_CREATE_EXISTS error","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-21T13:37:02Z","receivedAt":"2024-10-21T13:44: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 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.g8861098b6d\n\n"},{"id":"505694","messageId":"20241021134354.705636-7-bence@ferdinandy.com","threadId":"62383","inReplyTo":"20241021134354.705636-1-bence@ferdinandy.com","subject":"[PATCH v10 6/8] refs: add create_only option to refs_update_symref_extended","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-21T13:37:03Z","receivedAt":"2024-10-21T13:45: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 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.g8861098b6d\n\n"},{"id":"505695","messageId":"20241021134354.705636-9-bence@ferdinandy.com","threadId":"62383","inReplyTo":"20241021134354.705636-1-bence@ferdinandy.com","subject":"[PATCH v10 8/8] fetch set_head: handle mirrored bare repositories","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-21T13:37:05Z","receivedAt":"2024-10-21T13:45: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 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 31edc67447..249556c9c6 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1632,14 +1632,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.g8861098b6d\n\n"},{"id":"505696","messageId":"20241021134354.705636-8-bence@ferdinandy.com","threadId":"62383","inReplyTo":"20241021134354.705636-1-bence@ferdinandy.com","subject":"[PATCH v10 7/8] fetch: set remote/HEAD if it does not exist","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-21T13:37:04Z","receivedAt":"2024-10-21T13:45:43Z","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 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.94.g8861098b6d\n\n\n"},{"id":"505748","messageId":"Zxa83K3M7vUY7k4m@nand.local","threadId":"62383","inReplyTo":"20241021134354.705636-1-bence@ferdinandy.com","subject":"Re: [PATCH v10 0/8] set-head/fetch remote/HEAD updates","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2024-10-21T20:43:08Z","receivedAt":"2024-10-21T20:43:11Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Mon, Oct 21, 2024 at 03:36:57PM +0200, Bence Ferdinandy wrote:\n> Sorry for sending a v10 so soon after v9 without really waiting for feedback,\n> but as I started digging into\n> https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/ I\n> realized that the root cause is in fetch and not remote add, so the solution\n> belongs to this series. I'll leave my overall comments for v9 in here for\n> clarity.\n>\n> v10: added a new patch (8/8) on top of v9 to handle HEAD in mirrored bare\n> repositories, rest of the patches are unchanged\n\nThanks. For other readers, this is a continuation of the v9 from this\nthread:\n\n    https://lore.kernel.org/git/20241019225330.4001477-1-bence@ferdinandy.com/\n\nThanks,\nTaylor\n"},{"id":"505765","messageId":"4836c7c9-58d7-4e2a-ab78-be4e7296dc1f@ferdinandy.com","threadId":"62383","inReplyTo":"Zxa83K3M7vUY7k4m@nand.local","subject":"Re: [PATCH v10 0/8] set-head/fetch remote/HEAD updates","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-21T21:06:27Z","receivedAt":"2024-10-21T21:06:54Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"2024. okt. 21. 22:43:38 Taylor Blau <me@ttaylorr.com>:\n\n> On Mon, Oct 21, 2024 at 03:36:57PM +0200, Bence Ferdinandy wrote:\n>> Sorry for sending a v10 so soon after v9 without really waiting for \n>> feedback,\n>> but as I started digging into\n>> https://lore.kernel.org/git/D4ZAELFWJMKN.S88LJ6YK31LZ@ferdinandy.com/ \n>> I\n>> realized that the root cause is in fetch and not remote add, so the \n>> solution\n>> belongs to this series. I'll leave my overall comments for v9 in here \n>> for\n>> clarity.\n>>\n>> v10: added a new patch (8/8) on top of v9 to handle HEAD in mirrored \n>> bare\n>> repositories, rest of the patches are unchanged\n>\n> Thanks. For other readers, this is a continuation of the v9 from this\n> thread:\n>\n>     \n> https://lore.kernel.org/git/20241019225330.4001477-1-bence@ferdinandy.com/\n\nAnd people may also want to drop the accidental noreply address in Cc \nwhen replying :) Sorry for the mixup!\n"},{"id":"505850","messageId":"ZxfjWnZF4EbtVH12@nand.local","threadId":"62383","inReplyTo":"20241021134354.705636-1-bence@ferdinandy.com","subject":"Re: [PATCH v10 0/8] set-head/fetch remote/HEAD updates","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2024-10-22T17:39:38Z","receivedAt":"2024-10-22T17:39:40Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Mon, Oct 21, 2024 at 03:36:57PM +0200, Bence Ferdinandy wrote:\n> Bence 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\nAfter applying this topic to 'seen', I noticed some new CI breakage that\nappears to be caused by this topic:\n\n    https://github.com/git/git/actions/runs/11449483611/job/31855171514#step:4:2506\n\nAfter dropping the topic locally and building with 'make SANITIZE=leak'\nand then running t0410-partial-clone.sh, I was able to resolve the\nfailures.\n\nWould you mind building with 'SANITIZE=leak' and running that script to\nsee if you can address the issue? Thanks.\n\nThanks,\nTaylor\n"},{"id":"505851","messageId":"D52JTXM32QOS.16IVFGPEJ4AKN@ferdinandy.com","threadId":"62383","inReplyTo":"ZxfjWnZF4EbtVH12@nand.local","subject":"Re: [PATCH v10 0/8] set-head/fetch remote/HEAD updates","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-22T18:33:28Z","receivedAt":"2024-10-22T18:34:04Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Tue Oct 22, 2024 at 19:39, Taylor Blau <me@ttaylorr.com> wrote:\n> On Mon, Oct 21, 2024 at 03:36:57PM +0200, Bence Ferdinandy wrote:\n>> Bence 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> After applying this topic to 'seen', I noticed some new CI breakage that\n> appears to be caused by this topic:\n>\n>     https://github.com/git/git/actions/runs/11449483611/job/31855171514#step:4:2506\n>\n> After dropping the topic locally and building with 'make SANITIZE=leak'\n> and then running t0410-partial-clone.sh, I was able to resolve the\n> failures.\n>\n> Would you mind building with 'SANITIZE=leak' and running that script to\n> see if you can address the issue? Thanks.\n\nThanks, I can reproduce the issue. I'm hoping there are no more tricks to\ntesting things :D\n\nLet's see how fast I can handle this ...\n\nBest,\nBence\n\n-- \nbence.ferdinandy.com\n\n"},{"id":"505852","messageId":"Zxf3JBz0u+ZF90tb@nand.local","threadId":"62383","inReplyTo":"D52JTXM32QOS.16IVFGPEJ4AKN@ferdinandy.com","subject":"Re: [PATCH v10 0/8] set-head/fetch remote/HEAD updates","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2024-10-22T19:04:04Z","receivedAt":"2024-10-22T19:04:11Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Tue, Oct 22, 2024 at 08:33:28PM +0200, Bence Ferdinandy wrote:\n>\n> On Tue Oct 22, 2024 at 19:39, Taylor Blau <me@ttaylorr.com> wrote:\n> > On Mon, Oct 21, 2024 at 03:36:57PM +0200, Bence Ferdinandy wrote:\n> >> Bence 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> > After applying this topic to 'seen', I noticed some new CI breakage that\n> > appears to be caused by this topic:\n> >\n> >     https://github.com/git/git/actions/runs/11449483611/job/31855171514#step:4:2506\n> >\n> > After dropping the topic locally and building with 'make SANITIZE=leak'\n> > and then running t0410-partial-clone.sh, I was able to resolve the\n> > failures.\n> >\n> > Would you mind building with 'SANITIZE=leak' and running that script to\n> > see if you can address the issue? Thanks.\n>\n> Thanks, I can reproduce the issue. I'm hoping there are no more tricks to\n> testing things :D\n\nGreat, I'm glad that it was easily reproducible on your end. This one\nshould have minimal differences from the setup in CI, as I think\nbuilding with 'SANITIZE=leak' will suffice.\n\n> Let's see how fast I can handle this ...\n\nThanks for looking.\n\nThanks,\nTaylor\n"},{"id":"505853","messageId":"D52KL6REL1RF.29CBKCMCJOD29@ferdinandy.com","threadId":"62383","inReplyTo":"Zxf3JBz0u+ZF90tb@nand.local","subject":"Re: [PATCH v10 0/8] set-head/fetch remote/HEAD updates","fromName":"Bence Ferdinandy","fromEmail":"bence@ferdinandy.com","sentAt":"2024-10-22T19:09:04Z","receivedAt":"2024-10-22T19:09:57Z","isPatch":true,"sender":{"key":"bence@ferdinandy.com","avatar":"https://avatars.githubusercontent.com/u/6343487?v=4"},"body":"\nOn Tue Oct 22, 2024 at 21:04, Taylor Blau <me@ttaylorr.com> wrote:\n> On Tue, Oct 22, 2024 at 08:33:28PM +0200, Bence Ferdinandy wrote:\n>>\n>> On Tue Oct 22, 2024 at 19:39, Taylor Blau <me@ttaylorr.com> wrote:\n>> > On Mon, Oct 21, 2024 at 03:36:57PM +0200, Bence Ferdinandy wrote:\n>> >> Bence 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>> > After applying this topic to 'seen', I noticed some new CI breakage that\n>> > appears to be caused by this topic:\n>> >\n>> >     https://github.com/git/git/actions/runs/11449483611/job/31855171514#step:4:2506\n>> >\n>> > After dropping the topic locally and building with 'make SANITIZE=leak'\n>> > and then running t0410-partial-clone.sh, I was able to resolve the\n>> > failures.\n>> >\n>> > Would you mind building with 'SANITIZE=leak' and running that script to\n>> > see if you can address the issue? Thanks.\n>>\n>> Thanks, I can reproduce the issue. I'm hoping there are no more tricks to\n>> testing things :D\n>\n> Great, I'm glad that it was easily reproducible on your end. This one\n> should have minimal differences from the setup in CI, as I think\n> building with 'SANITIZE=leak' will suffice.\n>\n>> Let's see how fast I can handle this ...\n>\n> Thanks for looking.\n\nI'll send v11 to the correct thread, hopefully with no random companies in Cc :)\n"}]}