{"thread":{"id":"59559","subject":"[PATCH v2 0/2] Add fetch.updateHead option","startedAt":"2023-04-07T02:37:42Z","lastAt":"2023-04-07T02:37:50Z","messageCount":3,"participants":["Felipe Contreras"],"isPatch":true,"patchVersion":2,"patchTotal":2},"messages":[{"id":"474965","messageId":"20230407023736.49190-1-felipe.contreras@gmail.com","threadId":"59559","inReplyTo":null,"subject":"[PATCH v2 0/2] Add fetch.updateHead option","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2023-04-07T02:37:34Z","receivedAt":"2023-04-07T02:37:42Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"It's surprising that `git clone` and `git init && git remote add -f` don't\ncreate the same remote state.\n\nFix this by introducing a new configuration: `fetch.updateHead` which updates\nthe remote `HEAD` when it's not present with \"missing\", or always with\n\"always\".\n\nBy default it's \"never\", which retains the current behavior.\n\nThis has already been discussed before [1].\n\nChanges since v1:\n\n1. Make `fetch_update_head` a named enum as suggested by Ævar\n2. Remove `need_update_head`: use switch case instead, per Ævar\n3. Make `update_head` receive `fetch_missing` boolean, instead of enum,\n   per Ævar\n4. Make `update_head` receive an unconsted `struct remote`: worse, but\n   simplifies the review process\n\n[1] https://lore.kernel.org/git/20201118091219.3341585-1-felipe.contreras@gmail.com/\n\nFelipe Contreras (2):\n  Add fetch.updateHead option\n  fetch: add support for HEAD update on mirrors\n\n Documentation/config/fetch.txt  |  4 ++\n Documentation/config/remote.txt |  3 ++\n builtin/fetch.c                 | 76 ++++++++++++++++++++++++++++++++-\n remote.c                        | 20 +++++++++\n remote.h                        | 12 ++++++\n t/t5510-fetch.sh                | 49 +++++++++++++++++++++\n 6 files changed, 163 insertions(+), 1 deletion(-)\n\nRange-diff against v1:\n1:  1cb238c83d ! 1:  0b80baba39 Add fetch.updateHead option\n    @@ Commit message\n     \n         For the next major version of Git, we might want to change this default.\n     \n    +    Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n         Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n     \n      ## Documentation/config/fetch.txt ##\n    @@ builtin/fetch.c: static int fetch_prune_tags_config = -1; /* unspecified */\n      static int prune_tags = -1; /* unspecified */\n      #define PRUNE_TAGS_BY_DEFAULT 0 /* do we prune tags by default? */\n      \n    -+static int fetch_update_head = FETCH_UPDATE_HEAD_DEFAULT;\n    ++static enum fetch_update_head fetch_update_head = FETCH_UPDATE_HEAD_DEFAULT;\n     +\n      static int all, append, dry_run, force, keep, multiple, update_head_ok;\n      static int write_fetch_head = 1;\n    @@ builtin/fetch.c: static int backfill_tags(struct transport *transport,\n      \treturn retcode;\n      }\n      \n    -+static void update_head(int config, const struct ref *head, const struct remote *remote)\n    ++static void update_head(int fetch_missing, const struct ref *head,\n    ++\t\t\tstruct remote *remote)\n     +{\n     +\tchar *ref, *target;\n     +\tconst char *r;\n    @@ builtin/fetch.c: static int backfill_tags(struct transport *transport,\n     +\tif (!head || !head->symref || !remote)\n     +\t\treturn;\n     +\n    -+\tref = apply_refspecs((struct refspec *)&remote->fetch, \"refs/heads/HEAD\");\n    -+\ttarget = apply_refspecs((struct refspec *)&remote->fetch, head->symref);\n    ++\tref = apply_refspecs(&remote->fetch, \"refs/heads/HEAD\");\n    ++\ttarget = apply_refspecs(&remote->fetch, head->symref);\n     +\n     +\tif (!ref || !target) {\n     +\t\twarning(_(\"could not update remote head\"));\n    @@ builtin/fetch.c: static int backfill_tags(struct transport *transport,\n     +\tr = resolve_ref_unsafe(ref, 0, NULL, &flags);\n     +\n     +\tif (r) {\n    -+\t\tif (config == FETCH_UPDATE_HEAD_MISSING) {\n    -+\t\t\tif (flags & REF_ISSYMREF)\n    -+\t\t\t\t/* already present */\n    -+\t\t\t\treturn;\n    -+\t\t} else if (config == FETCH_UPDATE_HEAD_ALWAYS) {\n    ++\t\tif (!fetch_missing) {\n     +\t\t\tif (!strcmp(r, target))\n     +\t\t\t\t/* already up-to-date */\n     +\t\t\t\treturn;\n    -+\t\t} else\n    -+\t\t\t/* should never happen */\n    ++\t\t} else if (flags & REF_ISSYMREF)\n    ++\t\t\t/* already present */\n     +\t\t\treturn;\n     +\t}\n     +\n    @@ builtin/fetch.c: static int do_fetch(struct transport *transport,\n      \tint must_list_refs = 1;\n      \tstruct fetch_head fetch_head = { 0 };\n      \tstruct strbuf err = STRBUF_INIT;\n    -+\tint need_update_head = 0, update_head_config = 0;\n    ++\tenum fetch_update_head update_head_config = FETCH_UPDATE_HEAD_DEFAULT;\n      \n      \tif (tags == TAGS_DEFAULT) {\n      \t\tif (transport->remote->fetch_tags == 2)\n    @@ builtin/fetch.c: static int do_fetch(struct transport *transport,\n     +\t\t\telse\n     +\t\t\t\tupdate_head_config = fetch_update_head;\n     +\n    -+\t\t\tneed_update_head = update_head_config && update_head_config != FETCH_UPDATE_HEAD_NEVER;\n    -+\n    -+\t\t\tif (need_update_head)\n    ++\t\t\tswitch (update_head_config) {\n    ++\t\t\tcase FETCH_UPDATE_HEAD_MISSING:\n    ++\t\t\tcase FETCH_UPDATE_HEAD_ALWAYS:\n     +\t\t\t\tstrvec_push(&transport_ls_refs_options.ref_prefixes, \"HEAD\");\n    ++\t\t\tdefault:\n    ++\t\t\t\tbreak;\n    ++\t\t\t}\n      \t\t\trefspec_ref_prefixes(&transport->remote->fetch,\n      \t\t\t\t\t     &transport_ls_refs_options.ref_prefixes);\n     +\t\t}\n    @@ builtin/fetch.c: static int do_fetch(struct transport *transport,\n      \n      \tcommit_fetch_head(&fetch_head);\n      \n    -+\tif (need_update_head)\n    -+\t\tupdate_head(update_head_config, find_ref_by_name(remote_refs, \"HEAD\"), transport->remote);\n    ++\tswitch (update_head_config) {\n    ++\tcase FETCH_UPDATE_HEAD_MISSING:\n    ++\tcase FETCH_UPDATE_HEAD_ALWAYS:\n    ++\t\tupdate_head(update_head_config == FETCH_UPDATE_HEAD_MISSING,\n    ++\t\t\t    find_ref_by_name(remote_refs, \"HEAD\"),\n    ++\t\t\t    transport->remote);\n    ++\tdefault:\n    ++\t\tbreak;\n    ++\t}\n     +\n      \tif (set_upstream) {\n      \t\tstruct branch *branch = branch_get(\"HEAD\");\n    @@ remote.c: static void read_branches_file(struct remote_state *remote_state,\n      \tremote->fetch_tags = 1; /* always auto-follow */\n      }\n      \n    -+int parse_update_head(int *r, const char *var, const char *value)\n    ++int parse_update_head(enum fetch_update_head *r, const char *var,\n    ++\t\t      const char *value)\n     +{\n    -+\tif (!r)\n    -+\t\treturn -1;\n    -+\telse if (!value)\n    ++\tif (!value)\n     +\t\treturn config_error_nonbool(var);\n     +\telse if (!strcmp(value, \"never\"))\n     +\t\t*r = FETCH_UPDATE_HEAD_NEVER;\n    @@ remote.h: enum {\n      \tREMOTE_BRANCHES\n      };\n      \n    -+enum {\n    ++enum fetch_update_head {\n     +\tFETCH_UPDATE_HEAD_DEFAULT = 0,\n     +\tFETCH_UPDATE_HEAD_NEVER,\n     +\tFETCH_UPDATE_HEAD_MISSING,\n    @@ remote.h: struct remote {\n      \tint prune;\n      \tint prune_tags;\n      \n    -+\tint update_head;\n    ++\tenum fetch_update_head update_head;\n     +\n      \t/**\n      \t * The configured helper programs to run on the remote side, for\n    @@ remote.h: void apply_push_cas(struct push_cas_option *, struct remote *, struct\n      char *relative_url(const char *remote_url, const char *url,\n      \t\t   const char *up_path);\n      \n    -+int parse_update_head(int *r, const char *var, const char *value);\n    ++int parse_update_head(enum fetch_update_head *r, const char *var,\n    ++\t\t      const char *value);\n     +\n      #endif\n     \n2:  fe6d62510b ! 2:  5c0f48b9cc fetch: add support for HEAD update on mirrors\n    @@ Commit message\n         Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n     \n      ## builtin/fetch.c ##\n    -@@ builtin/fetch.c: static void update_head(int config, const struct ref *head, const struct remote\n    +@@ builtin/fetch.c: static void update_head(int fetch_missing, const struct ref *head,\n      \tif (!head || !head->symref || !remote)\n      \t\treturn;\n      \n    --\tref = apply_refspecs((struct refspec *)&remote->fetch, \"refs/heads/HEAD\");\n    --\ttarget = apply_refspecs((struct refspec *)&remote->fetch, head->symref);\n    +-\tref = apply_refspecs(&remote->fetch, \"refs/heads/HEAD\");\n    +-\ttarget = apply_refspecs(&remote->fetch, head->symref);\n     +\tif (!remote->mirror) {\n    -+\t\tref = apply_refspecs((struct refspec *)&remote->fetch, \"refs/heads/HEAD\");\n    -+\t\ttarget = apply_refspecs((struct refspec *)&remote->fetch, head->symref);\n    ++\t\tref = apply_refspecs(&remote->fetch, \"refs/heads/HEAD\");\n    ++\t\ttarget = apply_refspecs(&remote->fetch, head->symref);\n      \n     -\tif (!ref || !target) {\n     -\t\twarning(_(\"could not update remote head\"));\n-- \n2.40.0+fc1\n\n"},{"id":"474966","messageId":"20230407023736.49190-3-felipe.contreras@gmail.com","threadId":"59559","inReplyTo":"20230407023736.49190-1-felipe.contreras@gmail.com","subject":"[PATCH v2 2/2] fetch: add support for HEAD update on mirrors","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2023-04-07T02:37:36Z","receivedAt":"2023-04-07T02:37:45Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n builtin/fetch.c  | 15 ++++++++++-----\n t/t5510-fetch.sh | 18 ++++++++++++++++++\n 2 files changed, 28 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 3f7b33ba78..80fcaf79c3 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1594,12 +1594,17 @@ static void update_head(int fetch_missing, const struct ref *head,\n \tif (!head || !head->symref || !remote)\n \t\treturn;\n \n-\tref = apply_refspecs(&remote->fetch, \"refs/heads/HEAD\");\n-\ttarget = apply_refspecs(&remote->fetch, head->symref);\n+\tif (!remote->mirror) {\n+\t\tref = apply_refspecs(&remote->fetch, \"refs/heads/HEAD\");\n+\t\ttarget = apply_refspecs(&remote->fetch, head->symref);\n \n-\tif (!ref || !target) {\n-\t\twarning(_(\"could not update remote head\"));\n-\t\treturn;\n+\t\tif (!ref || !target) {\n+\t\t\twarning(_(\"could not update remote head\"));\n+\t\t\treturn;\n+\t\t}\n+\t} else {\n+\t\tref = \"HEAD\";\n+\t\ttarget = head->symref;\n \t}\n \n \tr = resolve_ref_unsafe(ref, 0, NULL, &flags);\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex dbeb2928ae..d3f3b24378 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -845,6 +845,24 @@ test_expect_success 'updatehead' '\n \t)\n '\n \n+test_expect_success 'updatehead mirror' '\n+\ttest_when_finished \"rm -rf updatehead\" &&\n+\n+\tgit clone --mirror . updatehead &&\n+\t(\n+\t\tcd updatehead &&\n+\n+\t\tgit config fetch.updateHead missing &&\n+\t\tgit symbolic-ref HEAD refs/heads/side &&\n+\t\tgit fetch &&\n+\t\ttest_cmp_symbolic_ref HEAD refs/heads/side &&\n+\n+\t\tgit config fetch.updateHead always &&\n+\t\tgit fetch &&\n+\t\ttest_cmp_symbolic_ref HEAD refs/heads/main\n+\t)\n+'\n+\n # configured prune tests\n \n set_config_tristate () {\n-- \n2.40.0+fc1\n\n"},{"id":"474967","messageId":"20230407023736.49190-2-felipe.contreras@gmail.com","threadId":"59559","inReplyTo":"20230407023736.49190-1-felipe.contreras@gmail.com","subject":"[PATCH v2 1/2] Add fetch.updateHead option","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2023-04-07T02:37:35Z","receivedAt":"2023-04-07T02:37:50Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Users might change the behavior when running \"git fetch\" so that the\nremote's HEAD symbolic ref is updated at certain point.\n\nFor example after running \"git remote add\" the remote HEAD is not\nset like it is with \"git clone\".\n\nSetting \"fetch.updatehead = missing\" would probably be a sensible\ndefault that everyone would want, but for now the default behavior is to\nnever update HEAD, so there shouldn't be any functional changes.\n\nFor the next major version of Git, we might want to change this default.\n\nHelped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n Documentation/config/fetch.txt  |  4 ++\n Documentation/config/remote.txt |  3 ++\n builtin/fetch.c                 | 71 ++++++++++++++++++++++++++++++++-\n remote.c                        | 20 ++++++++++\n remote.h                        | 12 ++++++\n t/t5510-fetch.sh                | 31 ++++++++++++++\n 6 files changed, 140 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/config/fetch.txt b/Documentation/config/fetch.txt\nindex 568f0f75b3..dc147ffb35 100644\n--- a/Documentation/config/fetch.txt\n+++ b/Documentation/config/fetch.txt\n@@ -120,3 +120,7 @@ fetch.bundleCreationToken::\n The creation token values are chosen by the provider serving the specific\n bundle URI. If you modify the URI at `fetch.bundleURI`, then be sure to\n remove the value for the `fetch.bundleCreationToken` value before fetching.\n+\n+fetch.updateHead::\n+\tDefines when to update the remote HEAD symbolic ref. Values are 'never',\n+\t'missing' (update only when HEAD is missing), and 'always'.\ndiff --git a/Documentation/config/remote.txt b/Documentation/config/remote.txt\nindex 0678b4bcfe..9d739d2ed4 100644\n--- a/Documentation/config/remote.txt\n+++ b/Documentation/config/remote.txt\n@@ -86,3 +86,6 @@ remote.<name>.partialclonefilter::\n \tChanging or clearing this value will only affect fetches for new commits.\n \tTo fetch associated objects for commits already present in the local object\n \tdatabase, use the `--refetch` option of linkgit:git-fetch[1].\n+\n+remote.<name>.updateHead::\n+\tDefines when to update the remote HEAD symbolic ref. See `fetch.updateHead`.\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 7221e57f35..3f7b33ba78 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -59,6 +59,8 @@ static int fetch_prune_tags_config = -1; /* unspecified */\n static int prune_tags = -1; /* unspecified */\n #define PRUNE_TAGS_BY_DEFAULT 0 /* do we prune tags by default? */\n \n+static enum fetch_update_head fetch_update_head = FETCH_UPDATE_HEAD_DEFAULT;\n+\n static int all, append, dry_run, force, keep, multiple, update_head_ok;\n static int write_fetch_head = 1;\n static int verbosity, deepen_relative, set_upstream, refetch;\n@@ -129,6 +131,9 @@ static int git_fetch_config(const char *k, const char *v, void *cb)\n \t\treturn 0;\n \t}\n \n+\tif (!strcmp(k, \"fetch.updatehead\"))\n+\t\treturn parse_update_head(&fetch_update_head, k, v);\n+\n \treturn git_default_config(k, v, cb);\n }\n \n@@ -1579,6 +1584,44 @@ static int backfill_tags(struct transport *transport,\n \treturn retcode;\n }\n \n+static void update_head(int fetch_missing, const struct ref *head,\n+\t\t\tstruct remote *remote)\n+{\n+\tchar *ref, *target;\n+\tconst char *r;\n+\tint flags;\n+\n+\tif (!head || !head->symref || !remote)\n+\t\treturn;\n+\n+\tref = apply_refspecs(&remote->fetch, \"refs/heads/HEAD\");\n+\ttarget = apply_refspecs(&remote->fetch, head->symref);\n+\n+\tif (!ref || !target) {\n+\t\twarning(_(\"could not update remote head\"));\n+\t\treturn;\n+\t}\n+\n+\tr = resolve_ref_unsafe(ref, 0, NULL, &flags);\n+\n+\tif (r) {\n+\t\tif (!fetch_missing) {\n+\t\t\tif (!strcmp(r, target))\n+\t\t\t\t/* already up-to-date */\n+\t\t\t\treturn;\n+\t\t} else if (flags & REF_ISSYMREF)\n+\t\t\t/* already present */\n+\t\t\treturn;\n+\t}\n+\n+\tif (!create_symref(ref, target, \"remote update head\")) {\n+\t\tif (verbosity >= 0)\n+\t\t\tprintf(_(\"Updated remote '%s' HEAD\\n\"), remote->name);\n+\t} else {\n+\t\twarning(_(\"could not update remote head\"));\n+\t}\n+}\n+\n static int do_fetch(struct transport *transport,\n \t\t    struct refspec *rs)\n {\n@@ -1592,6 +1635,7 @@ static int do_fetch(struct transport *transport,\n \tint must_list_refs = 1;\n \tstruct fetch_head fetch_head = { 0 };\n \tstruct strbuf err = STRBUF_INIT;\n+\tenum fetch_update_head update_head_config = FETCH_UPDATE_HEAD_DEFAULT;\n \n \tif (tags == TAGS_DEFAULT) {\n \t\tif (transport->remote->fetch_tags == 2)\n@@ -1626,9 +1670,24 @@ static int do_fetch(struct transport *transport,\n \t} else {\n \t\tstruct branch *branch = branch_get(NULL);\n \n-\t\tif (transport->remote->fetch.nr)\n+\t\tif (transport->remote->fetch.nr) {\n+\n+\t\t\tif (transport->remote->update_head)\n+\t\t\t\tupdate_head_config = transport->remote->update_head;\n+\t\t\telse\n+\t\t\t\tupdate_head_config = fetch_update_head;\n+\n+\t\t\tswitch (update_head_config) {\n+\t\t\tcase FETCH_UPDATE_HEAD_MISSING:\n+\t\t\tcase FETCH_UPDATE_HEAD_ALWAYS:\n+\t\t\t\tstrvec_push(&transport_ls_refs_options.ref_prefixes, \"HEAD\");\n+\t\t\tdefault:\n+\t\t\t\tbreak;\n+\t\t\t}\n \t\t\trefspec_ref_prefixes(&transport->remote->fetch,\n \t\t\t\t\t     &transport_ls_refs_options.ref_prefixes);\n+\t\t}\n+\n \t\tif (branch_has_merge_config(branch) &&\n \t\t    !strcmp(branch->remote_name, transport->remote->name)) {\n \t\t\tint i;\n@@ -1737,6 +1796,16 @@ static int do_fetch(struct transport *transport,\n \n \tcommit_fetch_head(&fetch_head);\n \n+\tswitch (update_head_config) {\n+\tcase FETCH_UPDATE_HEAD_MISSING:\n+\tcase FETCH_UPDATE_HEAD_ALWAYS:\n+\t\tupdate_head(update_head_config == FETCH_UPDATE_HEAD_MISSING,\n+\t\t\t    find_ref_by_name(remote_refs, \"HEAD\"),\n+\t\t\t    transport->remote);\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n \tif (set_upstream) {\n \t\tstruct branch *branch = branch_get(\"HEAD\");\n \t\tstruct ref *rm;\ndiff --git a/remote.c b/remote.c\nindex 641b083d90..c833f1b143 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -344,6 +344,24 @@ static void read_branches_file(struct remote_state *remote_state,\n \tremote->fetch_tags = 1; /* always auto-follow */\n }\n \n+int parse_update_head(enum fetch_update_head *r, const char *var,\n+\t\t      const char *value)\n+{\n+\tif (!value)\n+\t\treturn config_error_nonbool(var);\n+\telse if (!strcmp(value, \"never\"))\n+\t\t*r = FETCH_UPDATE_HEAD_NEVER;\n+\telse if (!strcmp(value, \"missing\"))\n+\t\t*r = FETCH_UPDATE_HEAD_MISSING;\n+\telse if (!strcmp(value, \"always\"))\n+\t\t*r = FETCH_UPDATE_HEAD_ALWAYS;\n+\telse {\n+\t\terror(_(\"malformed value for %s: %s\"), var, value);\n+\t\treturn error(_(\"must be one of never, missing, or always\"));\n+\t}\n+\treturn 0;\n+}\n+\n static int handle_config(const char *key, const char *value, void *cb)\n {\n \tconst char *name;\n@@ -473,6 +491,8 @@ static int handle_config(const char *key, const char *value, void *cb)\n \t\t\t\t\t key, value);\n \t} else if (!strcmp(subkey, \"vcs\")) {\n \t\treturn git_config_string(&remote->foreign_vcs, key, value);\n+\t} else if (!strcmp(subkey, \"updatehead\")) {\n+\t\treturn parse_update_head(&remote->update_head, key, value);\n \t}\n \treturn 0;\n }\ndiff --git a/remote.h b/remote.h\nindex 73638cefeb..0810f55294 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -22,6 +22,13 @@ enum {\n \tREMOTE_BRANCHES\n };\n \n+enum fetch_update_head {\n+\tFETCH_UPDATE_HEAD_DEFAULT = 0,\n+\tFETCH_UPDATE_HEAD_NEVER,\n+\tFETCH_UPDATE_HEAD_MISSING,\n+\tFETCH_UPDATE_HEAD_ALWAYS,\n+};\n+\n struct rewrite {\n \tconst char *base;\n \tsize_t baselen;\n@@ -97,6 +104,8 @@ struct remote {\n \tint prune;\n \tint prune_tags;\n \n+\tenum fetch_update_head update_head;\n+\n \t/**\n \t * The configured helper programs to run on the remote side, for\n \t * Git-native protocols.\n@@ -449,4 +458,7 @@ void apply_push_cas(struct push_cas_option *, struct remote *, struct ref *);\n char *relative_url(const char *remote_url, const char *url,\n \t\t   const char *up_path);\n \n+int parse_update_head(enum fetch_update_head *r, const char *var,\n+\t\t      const char *value);\n+\n #endif\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex dc44da9c79..dbeb2928ae 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -814,6 +814,37 @@ test_expect_success 'fetch from multiple configured URLs in single remote' '\n \tgit fetch multipleurls\n '\n \n+test_cmp_symbolic_ref () {\n+\tgit symbolic-ref \"$1\" >actual &&\n+\techo \"$2\" >expected &&\n+\ttest_cmp expected actual\n+}\n+\n+test_expect_success 'updatehead' '\n+\ttest_when_finished \"rm -rf updatehead\" &&\n+\n+\tgit init updatehead &&\n+\t(\n+\t\tcd updatehead &&\n+\n+\t\tgit config fetch.updateHead never &&\n+\t\tgit remote add origin .. &&\n+\t\tgit fetch &&\n+\t\ttest_must_fail git rev-parse --verify refs/remotes/origin/HEAD &&\n+\n+\t\tgit config fetch.updateHead missing &&\n+\t\tgit fetch &&\n+\t\ttest_cmp_symbolic_ref refs/remotes/origin/HEAD refs/remotes/origin/main &&\n+\t\tgit symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/side &&\n+\t\tgit fetch &&\n+\t\ttest_cmp_symbolic_ref refs/remotes/origin/HEAD refs/remotes/origin/side &&\n+\n+\t\tgit config fetch.updateHead always &&\n+\t\tgit fetch &&\n+\t\ttest_cmp_symbolic_ref refs/remotes/origin/HEAD refs/remotes/origin/main\n+\t)\n+'\n+\n # configured prune tests\n \n set_config_tristate () {\n-- \n2.40.0+fc1\n\n"}]}