{"thread":{"id":"65552","subject":"[PATCH] remote: add --set-head option to 'git remote add'","startedAt":"2026-04-25T11:19:41Z","lastAt":"2026-04-26T08:21:10Z","messageCount":6,"participants":["Harald Nordgren via GitGitGadget","Ben Knoble","Harald Nordgren","Junio C Hamano","Jeff King"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"542296","messageId":"pull.2283.git.git.1777115978088.gitgitgadget@gmail.com","threadId":"65552","inReplyTo":null,"subject":"[PATCH] remote: add --set-head option to 'git remote add'","fromName":"Harald Nordgren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-25T11:19:38Z","receivedAt":"2026-04-25T11:19:41Z","isPatch":true,"body":"From: Harald Nordgren <haraldnordgren@gmail.com>\n\nMirror the behavior 'git clone' applies to its first remote: after\nfetching, set refs/remotes/<name>/HEAD to the remote's default branch.\n\nEquivalent to running:\n\n    git remote add -f <name> <url>\n    git remote set-head <name> -a\n\nThe new option implies --fetch.\n\nSigned-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n---\n    remote: add --set-head option to 'git remote add'\n    \n    When using GitHub's gh tool to fork a repo, it seems that set-head isn't\n    run on the upstream remote. So its default branch is not recorded\n    locally, meaning that 'git log fork' will not work.\n    \n    With git remote add --set-head upstream , the default branch is set in\n    the same step and things can work out of the box after a small change on\n    'gh' that I will do as a next step.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2283%2FHaraldNordgren%2Fset_head-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2283/HaraldNordgren/set_head-v1\nPull-Request: https://github.com/git/git/pull/2283\n\n Documentation/git-remote.adoc |  9 ++++++++-\n builtin/remote.c              | 26 ++++++++++++++++++++++++--\n t/t5505-remote.sh             |  8 ++++++++\n 3 files changed, 40 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-remote.adoc b/Documentation/git-remote.adoc\nindex eaae30aa88..0ef49c4164 100644\n--- a/Documentation/git-remote.adoc\n+++ b/Documentation/git-remote.adoc\n@@ -10,7 +10,7 @@ SYNOPSIS\n --------\n [synopsis]\n git remote [-v | --verbose]\n-git remote add [-t <branch>] [-m <master>] [-f] [--[no-]tags] [--mirror=(fetch|push)] <name> <URL>\n+git remote add [-t <branch>] [-m <master>] [-f] [--set-head] [--[no-]tags] [--mirror=(fetch|push)] <name> <URL>\n git remote rename [--[no-]progress] <old> <new>\n git remote remove <name>\n git remote set-head <name> (-a | --auto | -d | --delete | <branch>)\n@@ -73,6 +73,13 @@ multiple branches without grabbing all branches.\n With `-m <master>` option, a symbolic-ref `refs/remotes/<name>/HEAD` is set\n up to point at remote's _<master>_ branch. See also the set-head command.\n +\n+With `--set-head` option, a symbolic-ref `refs/remotes/<name>/HEAD` is set\n+up to point at the remote's default branch, mirroring the behavior of\n+`git clone`. This is equivalent to running `git remote set-head <name> -a`\n+after the remote is added, and implies `-f` so that the remote's refs are\n+available locally. It cannot be combined with `-m <master>` or with a push\n+mirror.\n++\n When a fetch mirror is created with `--mirror=fetch`, the refs will not\n be stored in the `refs/remotes/` namespace, but rather everything in\n `refs/` on the remote will be directly mirrored into `refs/` in the\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex de989ea3ba..8273b425a5 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -23,7 +23,7 @@\n \n static const char * const builtin_remote_usage[] = {\n \t\"git remote [-v | --verbose]\",\n-\tN_(\"git remote add [-t <branch>] [-m <master>] [-f] [--tags | --no-tags] [--mirror=<fetch|push>] <name> <url>\"),\n+\tN_(\"git remote add [-t <branch>] [-m <master>] [-f] [--set-head] [--tags | --no-tags] [--mirror=<fetch|push>] <name> <url>\"),\n \tN_(\"git remote rename [--[no-]progress] <old> <new>\"),\n \tN_(\"git remote remove <name>\"),\n \tN_(\"git remote set-head <name> (-a | --auto | -d | --delete | <branch>)\"),\n@@ -174,10 +174,21 @@ static int check_remote_collision(struct remote *remote, void *data)\n \treturn 0;\n }\n \n+static int set_head_auto_for_remote(const char *name)\n+{\n+\tstruct child_process cmd = CHILD_PROCESS_INIT;\n+\n+\tstrvec_pushl(&cmd.args, \"remote\", \"set-head\", \"--auto\", name, NULL);\n+\tcmd.git_cmd = 1;\n+\tif (run_command(&cmd))\n+\t\treturn error(_(\"Could not set up HEAD for %s\"), name);\n+\treturn 0;\n+}\n+\n static int add(int argc, const char **argv, const char *prefix,\n \t       struct repository *repo UNUSED)\n {\n-\tint fetch = 0, fetch_tags = TAGS_DEFAULT;\n+\tint fetch = 0, fetch_tags = TAGS_DEFAULT, set_head_auto = 0;\n \tunsigned mirror = MIRROR_NONE;\n \tstruct string_list track = STRING_LIST_INIT_NODUP;\n \tconst char *master = NULL;\n@@ -195,6 +206,8 @@ static int add(int argc, const char **argv, const char *prefix,\n \t\tOPT_STRING_LIST('t', \"track\", &track, N_(\"branch\"),\n \t\t\t\tN_(\"branch(es) to track\")),\n \t\tOPT_STRING('m', \"master\", &master, N_(\"branch\"), N_(\"master branch\")),\n+\t\tOPT_BOOL(0, \"set-head\", &set_head_auto,\n+\t\t\t N_(\"set refs/remotes/<name>/HEAD according to remote (implies --fetch)\")),\n \t\tOPT_CALLBACK_F(0, \"mirror\", &mirror, \"(push|fetch)\",\n \t\t\tN_(\"set up remote as a mirror to push to or fetch from\"),\n \t\t\tPARSE_OPT_OPTARG | PARSE_OPT_COMP_ARG, parse_mirror_opt),\n@@ -211,6 +224,12 @@ static int add(int argc, const char **argv, const char *prefix,\n \t\tdie(_(\"specifying a master branch makes no sense with --mirror\"));\n \tif (mirror && !(mirror & MIRROR_FETCH) && track.nr)\n \t\tdie(_(\"specifying branches to track makes sense only with fetch mirrors\"));\n+\tif (set_head_auto && master)\n+\t\tdie(_(\"--set-head and --master are mutually exclusive\"));\n+\tif (set_head_auto && mirror && !(mirror & MIRROR_FETCH))\n+\t\tdie(_(\"--set-head makes no sense with a push mirror\"));\n+\tif (set_head_auto)\n+\t\tfetch = 1;\n \n \tname = argv[0];\n \turl = argv[1];\n@@ -269,6 +288,9 @@ static int add(int argc, const char **argv, const char *prefix,\n \t\t\tresult = error(_(\"Could not setup master '%s'\"), master);\n \t}\n \n+\tif (set_head_auto && set_head_auto_for_remote(name))\n+\t\tresult = 1;\n+\n out:\n \tstrbuf_release(&buf);\n \tstrbuf_release(&buf2);\ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex e592c0bcde..043c86315f 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -81,6 +81,14 @@ test_expect_success 'add another remote' '\n \t)\n '\n \n+test_expect_success 'add remote with --set-head implies --fetch and sets HEAD' '\n+\ttest_when_finished \"git -C test remote remove third\" &&\n+\tgit -C test remote add --set-head third ../two &&\n+\techo refs/remotes/third/main >expect &&\n+\tgit -C test symbolic-ref refs/remotes/third/HEAD >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'setup bare clone for server' '\n \tgit clone --bare \"file://$(pwd)/one\" srv.bare &&\n \tgit -C srv.bare config --local uploadpack.allowfilter 1 &&\n\nbase-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0\n-- \ngitgitgadget\n"},{"id":"542297","messageId":"6F9060F0-20EB-4B60-8677-86DA2AB39B35@gmail.com","threadId":"65552","inReplyTo":"pull.2283.git.git.1777115978088.gitgitgadget@gmail.com","subject":"Re: [PATCH] remote: add --set-head option to 'git remote add'","fromName":"Ben Knoble","fromEmail":"ben.knoble@gmail.com","sentAt":"2026-04-25T17:20:42Z","receivedAt":"2026-04-25T17:20:55Z","isPatch":true,"body":"> Le 25 avr. 2026 à 07:19, Harald Nordgren via GitGitGadget <gitgitgadget@gmail.com> a écrit :\n> \n> ﻿From: Harald Nordgren <haraldnordgren@gmail.com>\n> \n> Mirror the behavior 'git clone' applies to its first remote: after\n> fetching, set refs/remotes/<name>/HEAD to the remote's default branch.\n> \n> Equivalent to running:\n> \n>    git remote add -f <name> <url>\n>    git remote set-head <name> -a\n> \n> The new option implies --fetch.\n> \n> Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>\n> ---\n>    remote: add --set-head option to 'git remote add'\n> \n>    When using GitHub's gh tool to fork a repo, it seems that set-head isn't\n>    run on the upstream remote. So its default branch is not recorded\n>    locally, meaning that 'git log fork' will not work.\n> \n>    With git remote add --set-head upstream , the default branch is set in\n>    the same step and things can work out of the box after a small change on\n>    'gh' that I will do as a next step.\n\nI’m not totally opposed to this convenience, but couldn’t we also just teach gh to run set-head as a second command?\n\n(Of course, it will need a version check; if memory serves not all Git versions used in practice have this command? But I am on mobile and have not validated the history of git-remote’s sub-commands.)"},{"id":"542304","messageId":"20260425180749.49933-1-haraldnordgren@gmail.com","threadId":"65552","inReplyTo":"6F9060F0-20EB-4B60-8677-86DA2AB39B35@gmail.com","subject":"gh","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-04-25T18:07:49Z","receivedAt":"2026-04-25T18:07:52Z","isPatch":false,"body":"> I’m not totally opposed to this convenience, but couldn’t we also just teach gh to run set-head as a second command?\n\nWe probably could, and maybe we should.\n\nOne argument for this new options is that I believe 'git clone' has this\nbehavior, so it's attractive if forking (adding a secondary remote) could\nwork in the same way as clone (adding the first remote).\n\n\nHarald\n"},{"id":"542307","messageId":"xmqqzf2q8zxc.fsf@gitster.g","threadId":"65552","inReplyTo":"pull.2283.git.git.1777115978088.gitgitgadget@gmail.com","subject":"Re: [PATCH] remote: add --set-head option to 'git remote add'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-25T21:58:55Z","receivedAt":"2026-04-25T21:58:57Z","isPatch":true,"body":"\"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Harald Nordgren <haraldnordgren@gmail.com>\n>\n> Mirror the behavior 'git clone' applies to its first remote: after\n> fetching, set refs/remotes/<name>/HEAD to the remote's default branch.\n>\n> Equivalent to running:\n>\n>     git remote add -f <name> <url>\n>     git remote set-head <name> -a\n>\n> The new option implies --fetch.\n\nShould this option (and the auto mode of \"git remote set-head\") even\nbe necessary as an extra thing that the end-user should need to be\naware of these days?\n\nIt feels to me that the \"fetch\" part of \"git remote add --fetch\"\ncommand should behave in line with what \"git fetch\" from the remote\ndoes with \"remote.<name>.followRemoteHEAD\" configuration.\n\nOf course, the current implementation may not do so, and that is why\nyou are sending this patch.  A patch would need to plumb through the\nmechanism, but I think this should be pretty much automatic without\ngiving more control than what the users already have.\n\nAnd because remote.<name>.followRemoteHEAD that is unconfigured is\nthe same as setting it to \"create\", it means \"git remote add -f\"\nwill behave just like \"git clone\" would to remember the upstream\nchoice of which of their branches is the primary one (which is what\nHEAD in the publishing repository means), unless the variable is\nexplicitly configured to \"never\".\n\nIOW, I think this should/can be done as a bugfix, i.e.,\n\n    Even though \"git clone -o <name> <URL>\" does, \"git remote add\n    --fetch <name> <URL>\" does not create refs/remotes/<name>/HEAD.\n\n    Fix it by making it honor the remote.<name>.followRemoteHEAD\n    configuration variable, which was invented exactly for this\n    purpose..\n\nor something.\n"},{"id":"542308","messageId":"20260425220629.GA28590@coredump.intra.peff.net","threadId":"65552","inReplyTo":"xmqqzf2q8zxc.fsf@gitster.g","subject":"Re: [PATCH] remote: add --set-head option to 'git remote add'","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2026-04-25T22:06:29Z","receivedAt":"2026-04-25T22:06:38Z","isPatch":true,"body":"On Sun, Apr 26, 2026 at 06:58:55AM +0900, Junio C Hamano wrote:\n\n> \"Harald Nordgren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n> > From: Harald Nordgren <haraldnordgren@gmail.com>\n> >\n> > Mirror the behavior 'git clone' applies to its first remote: after\n> > fetching, set refs/remotes/<name>/HEAD to the remote's default branch.\n> >\n> > Equivalent to running:\n> >\n> >     git remote add -f <name> <url>\n> >     git remote set-head <name> -a\n> >\n> > The new option implies --fetch.\n> \n> Should this option (and the auto mode of \"git remote set-head\") even\n> be necessary as an extra thing that the end-user should need to be\n> aware of these days?\n> \n> It feels to me that the \"fetch\" part of \"git remote add --fetch\"\n> command should behave in line with what \"git fetch\" from the remote\n> does with \"remote.<name>.followRemoteHEAD\" configuration.\n\nIt already does, doesn't it? Doing:\n\n  $ git init\n  $ git remote add --fetch origin /path/to/some/repo\n  $ git for-each-ref\n\nshows an origin/HEAD link.\n\nWhich I think is not too surprising, as it is just calling \"git fetch\"\nunder the hood.\n\n-Peff\n"},{"id":"542319","messageId":"20260426082106.11266-1-haraldnordgren@gmail.com","threadId":"65552","inReplyTo":"20260425220629.GA28590@coredump.intra.peff.net","subject":"Re: [PATCH] remote: add --set-head option to 'git remote add'","fromName":"Harald Nordgren","fromEmail":"haraldnordgren@gmail.com","sentAt":"2026-04-26T08:21:06Z","receivedAt":"2026-04-26T08:21:10Z","isPatch":true,"body":"> It already does, doesn't it? Doing:\n> \n>   $ git init\n>   $ git remote add --fetch origin /path/to/some/repo\n>   $ git for-each-ref\n> \n> shows an origin/HEAD link.\n> \n> Which I think is not too surprising, as it is just calling \"git fetch\"\n> under the hood.\n\nI think you are right! So maybe all of this new code is not needed at all.\n\n\nHarald\n"}]}