Git development
 help / color / mirror / Atom feed
* followRemoteHEAD management question
@ 2026-06-05 16:31 Matt Hunter
  2026-06-08 23:49 ` Jeff King
  2026-06-12  5:55 ` [PATCH 0/7] Introduce fetch.followRemoteHEAD config option Matt Hunter
  0 siblings, 2 replies; 16+ messages in thread
From: Matt Hunter @ 2026-06-05 16:31 UTC (permalink / raw)
  To: git; +Cc: Bence Ferdinandy

Hello git list,

In the past, I've preferred to run 'git remote set-head <name> -d' when
setting up a new repository, since I generally have an awareness of what
the remote default branch is, and I don't like seeing them in branch
listings or git-log annotations.  They are especially noisy to me if I
have multiple remotes.  It's possible this config is ill-advised - I
would love to be educated if so...

However, since b7f7d16562c3 (fetch: add configuration for set_head
behaviour), these changes are undone by every 'git fetch'.

The topic mentioned above (merged in a1f34d595503) adds a new
configuration key 'remote.<name>.followRemoteHEAD'.  I'm assuming that
the intended use for followRemoteHEAD is really only in local /
per-repository config, since trying to apply it to my personal
.gitconfig has some odd behavior.

The <name> in the key template does not accept a wildcard, so I must
list out each of the common remote names I use across different
repositories.  Since many of my repos don't actually have remotes
established for all of these names, they pick up a kind of half-baked
definition for each of them as git performs its config parsing.  For
instance, a name will appear under 'git remote -v', but it won't
have any actual properties configured.

I'd like to add a line to my config somewhere that can globally restore
the old behavior in this context, eg:

    git config --global remote.*.followRemoteHEAD never

instead of adding individual entries to each project's .git/config.

Is there another solution in place I've missed?  If not, would there be
any opposition to a new key like 'remote.followRemoteHEAD' which serves
to provide a default value for any remote that doesn't have its own
'remote.<name>.followRemoteHEAD' key?

I've started scouting out changes to make for such a patch.  It's not
ready yet, but I figured I would throw this question out in case an easy
answer can save the effort.

Thanks

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: followRemoteHEAD management question
  2026-06-05 16:31 followRemoteHEAD management question Matt Hunter
@ 2026-06-08 23:49 ` Jeff King
  2026-06-11  4:12   ` Matt Hunter
  2026-06-12  5:55 ` [PATCH 0/7] Introduce fetch.followRemoteHEAD config option Matt Hunter
  1 sibling, 1 reply; 16+ messages in thread
From: Jeff King @ 2026-06-08 23:49 UTC (permalink / raw)
  To: Matt Hunter; +Cc: git, Bence Ferdinandy

On Fri, Jun 05, 2026 at 12:31:30PM -0400, Matt Hunter wrote:

> In the past, I've preferred to run 'git remote set-head <name> -d' when
> setting up a new repository, since I generally have an awareness of what
> the remote default branch is, and I don't like seeing them in branch
> listings or git-log annotations.  They are especially noisy to me if I
> have multiple remotes.  It's possible this config is ill-advised - I
> would love to be educated if so...

No, it's perfectly reasonable. Being able to refer to "origin" to mean
"origin/HEAD" is sometimes handy, but if you don't use it, there's no
reason to set up the symref in the first place.

> However, since b7f7d16562c3 (fetch: add configuration for set_head
> behaviour), these changes are undone by every 'git fetch'.
> 
> The topic mentioned above (merged in a1f34d595503) adds a new
> configuration key 'remote.<name>.followRemoteHEAD'.  I'm assuming that
> the intended use for followRemoteHEAD is really only in local /
> per-repository config, since trying to apply it to my personal
> .gitconfig has some odd behavior.

I think this is a gap in the new feature's implementation. It added
per-remote config, but there is no global config to fall back to (e.g.,
the way that remote.*.prune falls back to fetch.prune). There should be
a fetch.followRemoteHEAD option (or perhaps remote.followRemoteHEAD).

> The <name> in the key template does not accept a wildcard, so I must
> list out each of the common remote names I use across different
> repositories.  Since many of my repos don't actually have remotes
> established for all of these names, they pick up a kind of half-baked
> definition for each of them as git performs its config parsing.  For
> instance, a name will appear under 'git remote -v', but it won't
> have any actual properties configured.

Yes, this is a common problem with the remote-config namespace. Defining
_any_ key makes the remote "exist", even without a defined url, but that
isn't usually the intent.  But we can't distinguish that from the case
where you really do want to define a remote without a url (in which case
the url is the name of the remote).

> Is there another solution in place I've missed?  If not, would there be
> any opposition to a new key like 'remote.followRemoteHEAD' which serves
> to provide a default value for any remote that doesn't have its own
> 'remote.<name>.followRemoteHEAD' key?
> 
> I've started scouting out changes to make for such a patch.  It's not
> ready yet, but I figured I would throw this question out in case an easy
> answer can save the effort.

I think you are on the right track. I can see arguments for or against
putting it in fetch.* or remote.*, so you'll have to pick one. ;)

-Peff

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: followRemoteHEAD management question
  2026-06-08 23:49 ` Jeff King
@ 2026-06-11  4:12   ` Matt Hunter
  2026-06-11  6:01     ` Jeff King
  0 siblings, 1 reply; 16+ messages in thread
From: Matt Hunter @ 2026-06-11  4:12 UTC (permalink / raw)
  To: Jeff King; +Cc: git, Bence Ferdinandy

On Mon Jun 8, 2026 at 7:49 PM EDT, Jeff King wrote:
>> 
>> The topic mentioned above (merged in a1f34d595503) adds a new
>> configuration key 'remote.<name>.followRemoteHEAD'.  I'm assuming that
>> the intended use for followRemoteHEAD is really only in local /
>> per-repository config, since trying to apply it to my personal
>> .gitconfig has some odd behavior.
>
> I think this is a gap in the new feature's implementation. It added
> per-remote config, but there is no global config to fall back to (e.g.,
> the way that remote.*.prune falls back to fetch.prune). There should be
> a fetch.followRemoteHEAD option (or perhaps remote.followRemoteHEAD).

Earlier on while working on this, I actually settled on
fetch.followRemoteHEAD instead, taking example from the prune setting.
Thanks for the confirmation.

>> The <name> in the key template does not accept a wildcard, so I must
>> list out each of the common remote names I use across different
>> repositories.  Since many of my repos don't actually have remotes
>> established for all of these names, they pick up a kind of half-baked
>> definition for each of them as git performs its config parsing.  For
>> instance, a name will appear under 'git remote -v', but it won't
>> have any actual properties configured.
>
> Yes, this is a common problem with the remote-config namespace. Defining
> _any_ key makes the remote "exist", even without a defined url, but that
> isn't usually the intent.  But we can't distinguish that from the case
> where you really do want to define a remote without a url (in which case
> the url is the name of the remote).

I had no idea a remote like that was supported.  Interesting.

>> Is there another solution in place I've missed?  If not, would there be
>> any opposition to a new key like 'remote.followRemoteHEAD' which serves
>> to provide a default value for any remote that doesn't have its own
>> 'remote.<name>.followRemoteHEAD' key?
>> 
>> I've started scouting out changes to make for such a patch.  It's not
>> ready yet, but I figured I would throw this question out in case an easy
>> answer can save the effort.
>
> I think you are on the right track. I can see arguments for or against
> putting it in fetch.* or remote.*, so you'll have to pick one. ;)

As stated, I think putting it in fetch.* is more consistent.  I'd be
curious to hear arguments the other way.

As for another design decision: I'm leaning toward omitting support for
the "warn-if-not-$branch" value in fetch.followRemoteHEAD.

My take on that option as-documented is that it serves more as an
acknowledgment from the user that "yes, I understand that origin has
pointed HEAD at foo, please only warn me if it changes" as opposed to the
user expressing that the branch "foo" is in some way special to them.

This interpretation feels very remote-dependent and doesn't make sense in
the context of a default catch-all value to me.

Thanks for the feedback!

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: followRemoteHEAD management question
  2026-06-11  4:12   ` Matt Hunter
@ 2026-06-11  6:01     ` Jeff King
  2026-06-11 20:36       ` Bence Ferdinandy
  0 siblings, 1 reply; 16+ messages in thread
From: Jeff King @ 2026-06-11  6:01 UTC (permalink / raw)
  To: Matt Hunter; +Cc: git, Bence Ferdinandy

On Thu, Jun 11, 2026 at 12:12:54AM -0400, Matt Hunter wrote:

> > Yes, this is a common problem with the remote-config namespace. Defining
> > _any_ key makes the remote "exist", even without a defined url, but that
> > isn't usually the intent.  But we can't distinguish that from the case
> > where you really do want to define a remote without a url (in which case
> > the url is the name of the remote).
> 
> I had no idea a remote like that was supported.  Interesting.

I suspect it is more of an emergent property than something that was
carefully designed, but after so many years I'd hesitate to change it
(at least without a big warning and deprecation period).

> > I think you are on the right track. I can see arguments for or against
> > putting it in fetch.* or remote.*, so you'll have to pick one. ;)
> 
> As stated, I think putting it in fetch.* is more consistent.  I'd be
> curious to hear arguments the other way.

My initial thought is that it might affect clone as well as fetch. But I
guess this feature does not kick in for clone, as it has its own logic
for handling the remote-tracking HEAD. Though arguably it should be
possible to configure it not to create one in the first place.

> As for another design decision: I'm leaning toward omitting support for
> the "warn-if-not-$branch" value in fetch.followRemoteHEAD.
> 
> My take on that option as-documented is that it serves more as an
> acknowledgment from the user that "yes, I understand that origin has
> pointed HEAD at foo, please only warn me if it changes" as opposed to the
> user expressing that the branch "foo" is in some way special to them.
> 
> This interpretation feels very remote-dependent and doesn't make sense in
> the context of a default catch-all value to me.

Agreed. I can't think of a reason you'd want it in the global option.
And if we're wrong, it is easy to add support later (versus adding it
now, finding out that it creates awkward corner cases, and then having
the backwards-incompatible change of ripping it out).

-Peff

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: followRemoteHEAD management question
  2026-06-11  6:01     ` Jeff King
@ 2026-06-11 20:36       ` Bence Ferdinandy
  2026-06-12  6:11         ` Matt Hunter
  0 siblings, 1 reply; 16+ messages in thread
From: Bence Ferdinandy @ 2026-06-11 20:36 UTC (permalink / raw)
  To: Jeff King, Matt Hunter; +Cc: git

On Thu Jun 11, 2026 at 08:01, Jeff King <peff@peff.net> wrote:
>
> My initial thought is that it might affect clone as well as fetch. But I
> guess this feature does not kick in for clone, as it has its own logic
> for handling the remote-tracking HEAD. Though arguably it should be
> possible to configure it not to create one in the first place.

If memory serves well clone has set the remote/HEAD well before this and
I think it indeed uses a different mechanism/logic.

>
>> As for another design decision: I'm leaning toward omitting support for
>> the "warn-if-not-$branch" value in fetch.followRemoteHEAD.
>> 
>> My take on that option as-documented is that it serves more as an
>> acknowledgment from the user that "yes, I understand that origin has
>> pointed HEAD at foo, please only warn me if it changes" as opposed to the
>> user expressing that the branch "foo" is in some way special to them.

Yes, that was the reasoning. So I also agree on not adding it to global. 

Bit late to the party, but happy to review/test patches if they come.

Best,
Bence

^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH 0/7] Introduce fetch.followRemoteHEAD config option
  2026-06-05 16:31 followRemoteHEAD management question Matt Hunter
  2026-06-08 23:49 ` Jeff King
@ 2026-06-12  5:55 ` Matt Hunter
  2026-06-12  5:55   ` [PATCH 1/7] fetch: fixup set_head advice for warn-if-not-branch Matt Hunter
                     ` (6 more replies)
  1 sibling, 7 replies; 16+ messages in thread
From: Matt Hunter @ 2026-06-12  5:55 UTC (permalink / raw)
  To: git; +Cc: Jeff King, Bence Ferdinandy

git-fetch presently offers some useful ways to control how remote HEAD
symbolic-refs are (or aren't) updated when fetching from remote
repositories.  Namely this is done via the
'remote.<name>.followRemoteHEAD' configuration option.

However, this option can be somewhat painful to use if you prefer a
default other than the "create" option, and often work with multiple
different remote repositories.

This series introduces the option 'fetch.followRemoteHEAD', which
provides a configurable default in place of per-remote settings.

'fetch.followRemoteHEAD' functions exactly the same as the original
option, except that it doesn't allow warning suppression via
'warn-if-not-$branch'.  Given that different remotes will vary their
HEAD and set of branches independently, setting a false-positive
globally in this way doesn't make logical sense.

While it is not mentioned by any of the patches in this series, note
also that the behavior introduced by 012bc566bad7 (remote set-head: set
followRemoteHEAD to "warn" if "always") is unaffected by this series,
and this feature continues to work for only the
'remote.<name>.followRemoteHEAD' option.

Matt Hunter (7):
  fetch: fixup set_head advice for warn-if-not-branch
  doc: explain fetchRemoteHEADWarn advice
  t5510: cleanup remote in followRemoteHEAD dangling ref test
  fetch: rename function report_set_head
  fetch: refactor do_fetch handling of followRemoteHEAD
  fetch: add configuration option fetch.followRemoteHEAD
  fetch: fixup a misaligned comment

 Documentation/config/advice.adoc |   4 ++
 Documentation/config/fetch.adoc  |  19 ++++++
 Documentation/config/remote.adoc |  21 +++---
 builtin/fetch.c                  |  52 +++++++++++----
 remote.h                         |  14 ++--
 t/t5510-fetch.sh                 | 106 +++++++++++++++++++++++++++++++
 6 files changed, 186 insertions(+), 30 deletions(-)


base-commit: 1ff279f3404a482a83fb04c7457e41ab26884aea
-- 
2.54.0


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH 1/7] fetch: fixup set_head advice for warn-if-not-branch
  2026-06-12  5:55 ` [PATCH 0/7] Introduce fetch.followRemoteHEAD config option Matt Hunter
@ 2026-06-12  5:55   ` Matt Hunter
  2026-06-12  5:55   ` [PATCH 2/7] doc: explain fetchRemoteHEADWarn advice Matt Hunter
                     ` (5 subsequent siblings)
  6 siblings, 0 replies; 16+ messages in thread
From: Matt Hunter @ 2026-06-12  5:55 UTC (permalink / raw)
  To: git

Specifying the word 'branch' in the command is not correct - a mismatch
with both the implementation in remote.c and the documentation.

Signed-off-by: Matt Hunter <m@lfurio.us>
---
 builtin/fetch.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/builtin/fetch.c b/builtin/fetch.c
index c1d7c672f4e0..82969e230f5a 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1700,7 +1700,7 @@ static void set_head_advice_msg(const char *remote, const char *head_name)
 	N_("Run 'git remote set-head %s %s' to follow the change, or set\n"
 	   "'remote.%s.followRemoteHEAD' configuration option to a different value\n"
 	   "if you do not want to see this message. Specifically running\n"
-	   "'git config set remote.%s.followRemoteHEAD warn-if-not-branch-%s'\n"
+	   "'git config set remote.%s.followRemoteHEAD warn-if-not-%s'\n"
 	   "will disable the warning until the remote changes HEAD to something else.");
 
 	advise_if_enabled(ADVICE_FETCH_SET_HEAD_WARN, _(message_advice_set_head),
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH 2/7] doc: explain fetchRemoteHEADWarn advice
  2026-06-12  5:55 ` [PATCH 0/7] Introduce fetch.followRemoteHEAD config option Matt Hunter
  2026-06-12  5:55   ` [PATCH 1/7] fetch: fixup set_head advice for warn-if-not-branch Matt Hunter
@ 2026-06-12  5:55   ` Matt Hunter
  2026-06-12  5:55   ` [PATCH 3/7] t5510: cleanup remote in followRemoteHEAD dangling ref test Matt Hunter
                     ` (4 subsequent siblings)
  6 siblings, 0 replies; 16+ messages in thread
From: Matt Hunter @ 2026-06-12  5:55 UTC (permalink / raw)
  To: git

When the user sets 'remote.<name>.followRemoteHEAD' to
'warn[-if-not-$branch]', git-fetch will report when a fetched HEAD
disagrees with the locally-configured remote's HEAD.  This additional
advice instructs the user how to deal with these warnings, but was
previously undocumented in git-config.

Signed-off-by: Matt Hunter <m@lfurio.us>
---
 Documentation/config/advice.adoc | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/Documentation/config/advice.adoc b/Documentation/config/advice.adoc
index 257db5891817..c3c190ba6a4f 100644
--- a/Documentation/config/advice.adoc
+++ b/Documentation/config/advice.adoc
@@ -48,6 +48,10 @@ all advice messages.
 		to create a local branch after the fact.
 	diverging::
 		Shown when a fast-forward is not possible.
+	fetchRemoteHEADWarn::
+		Shown when linkgit:git-fetch[1] reveals that a remote `HEAD`
+		differs from what is set locally and the user has opted into
+		receiving a warning in this situation.
 	fetchShowForcedUpdates::
 		Shown when linkgit:git-fetch[1] takes a long time
 		to calculate forced updates after ref updates, or to warn
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH 3/7] t5510: cleanup remote in followRemoteHEAD dangling ref test
  2026-06-12  5:55 ` [PATCH 0/7] Introduce fetch.followRemoteHEAD config option Matt Hunter
  2026-06-12  5:55   ` [PATCH 1/7] fetch: fixup set_head advice for warn-if-not-branch Matt Hunter
  2026-06-12  5:55   ` [PATCH 2/7] doc: explain fetchRemoteHEADWarn advice Matt Hunter
@ 2026-06-12  5:55   ` Matt Hunter
  2026-06-12  5:55   ` [PATCH 4/7] fetch: rename function report_set_head Matt Hunter
                     ` (3 subsequent siblings)
  6 siblings, 0 replies; 16+ messages in thread
From: Matt Hunter @ 2026-06-12  5:55 UTC (permalink / raw)
  To: git

A later patch will introduce a new test which closely mirrors this one.
Update this test to remove the 'custom-head' remote it creates.
Otherwise, the two tests will conflict with each other, as the second
one to execute will fail to create this remote (which already exists,
thanks to the first test).

Signed-off-by: Matt Hunter <m@lfurio.us>
---
 t/t5510-fetch.sh | 1 +
 1 file changed, 1 insertion(+)

diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index eca9a973b5cb..43190630e714 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -251,6 +251,7 @@ test_expect_success 'followRemoteHEAD does not kick in with refspecs' '
 '
 
 test_expect_success 'followRemoteHEAD create does not overwrite dangling symref' '
+	test_when_finished "git -C two remote remove custom-head" &&
 	git -C two remote add -m does-not-exist custom-head ../one &&
 	test_config -C two remote.custom-head.followRemoteHEAD create &&
 	git -C two fetch custom-head &&
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH 4/7] fetch: rename function report_set_head
  2026-06-12  5:55 ` [PATCH 0/7] Introduce fetch.followRemoteHEAD config option Matt Hunter
                     ` (2 preceding siblings ...)
  2026-06-12  5:55   ` [PATCH 3/7] t5510: cleanup remote in followRemoteHEAD dangling ref test Matt Hunter
@ 2026-06-12  5:55   ` Matt Hunter
  2026-06-12  5:55   ` [PATCH 5/7] fetch: refactor do_fetch handling of followRemoteHEAD Matt Hunter
                     ` (2 subsequent siblings)
  6 siblings, 0 replies; 16+ messages in thread
From: Matt Hunter @ 2026-06-12  5:55 UTC (permalink / raw)
  To: git

Update to the slightly more obvious name 'warn_set_head', which matches
the verbiage of the followRemoteHEAD options.

Signed-off-by: Matt Hunter <m@lfurio.us>
---
 builtin/fetch.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/builtin/fetch.c b/builtin/fetch.c
index 82969e230f5a..9a45e1e7a44d 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1707,7 +1707,7 @@ static void set_head_advice_msg(const char *remote, const char *head_name)
 			remote, head_name, remote, remote, head_name);
 }
 
-static void report_set_head(const char *remote, const char *head_name,
+static void warn_set_head(const char *remote, const char *head_name,
 			struct strbuf *buf_prev, int updateres) {
 	struct strbuf buf_prefix = STRBUF_INIT;
 	const char *prev_head = NULL;
@@ -1787,7 +1787,7 @@ static int set_head(const struct ref *remote_refs, struct remote *remote)
 	if (verbosity >= 0 &&
 		follow_remote_head == FOLLOW_REMOTE_WARN &&
 		(!no_warn_branch || strcmp(no_warn_branch, head_name)))
-		report_set_head(remote->name, head_name, &b_local_head, was_detached);
+		warn_set_head(remote->name, head_name, &b_local_head, was_detached);
 
 cleanup:
 	free(head_name);
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH 5/7] fetch: refactor do_fetch handling of followRemoteHEAD
  2026-06-12  5:55 ` [PATCH 0/7] Introduce fetch.followRemoteHEAD config option Matt Hunter
                     ` (3 preceding siblings ...)
  2026-06-12  5:55   ` [PATCH 4/7] fetch: rename function report_set_head Matt Hunter
@ 2026-06-12  5:55   ` Matt Hunter
  2026-06-12  5:55   ` [PATCH 6/7] fetch: add configuration option fetch.followRemoteHEAD Matt Hunter
  2026-06-12  5:55   ` [PATCH 7/7] fetch: fixup a misaligned comment Matt Hunter
  6 siblings, 0 replies; 16+ messages in thread
From: Matt Hunter @ 2026-06-12  5:55 UTC (permalink / raw)
  To: git

Update enum follow_remote_head_settings to include the value
FOLLOW_REMOTE_UNCONFIGURED as the new zero-initialized value for
followRemoteHEAD.  This will allow us to distinguish between the option
being unset vs. explicitly set to 'create', which is ultimately the
system default.  The unnecessary indentation is removed.

The do_fetch function is likewise updated to perform its own decision
making to determine the effective followRemoteHEAD mode, falling back to
the system default if necessary.  This will enable the next patch to
introduce a user-configurable fallback default option.

Function set_head now accepts this value as an argument rather than only
considering the value defined by the remote.

The use of the 'warn-if-not-$branch' value is awkward in the context of
a global default option, since the branches will differ between
individual remotes.  For this reason, it's left out of this scheme and
handling of the no_warn_branch variable is untouched.  Since a
remote-specific setting for followRemoteHEAD takes priority, we can
assume that if remote->no_warn_branch is set, then the remote is also
asserting FOLLOW_REMOTE_WARN as the effective operating mode, and it
will be honored by do_fetch.

Signed-off-by: Matt Hunter <m@lfurio.us>
---
 builtin/fetch.c | 14 ++++++++++----
 remote.h        | 14 ++++++++------
 2 files changed, 18 insertions(+), 10 deletions(-)

diff --git a/builtin/fetch.c b/builtin/fetch.c
index 9a45e1e7a44d..3cc7efdd83a0 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1729,12 +1729,12 @@ static void warn_set_head(const char *remote, const char *head_name,
 	strbuf_release(&buf_prefix);
 }
 
-static int set_head(const struct ref *remote_refs, struct remote *remote)
+static int set_head(const struct ref *remote_refs, struct remote *remote,
+			int follow_remote_head)
 {
 	int result = 0, create_only, baremirror, was_detached;
 	struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,
 		      b_local_head = STRBUF_INIT;
-	int follow_remote_head = remote->follow_remote_head;
 	const char *no_warn_branch = remote->no_warn_branch;
 	char *head_name = NULL;
 	struct ref *ref, *matches;
@@ -1901,6 +1901,7 @@ static int do_fetch(struct transport *transport,
 	struct ref_update_display_info_array display_array = { 0 };
 	struct strmap rejected_refs = STRMAP_INIT;
 	int summary_width = 0;
+	int follow_remote_head;
 
 	if (tags == TAGS_DEFAULT) {
 		if (transport->remote->fetch_tags == 2)
@@ -1916,6 +1917,11 @@ static int do_fetch(struct transport *transport,
 			goto cleanup;
 	}
 
+	if (transport->remote->follow_remote_head)
+		follow_remote_head = transport->remote->follow_remote_head;
+	else
+		follow_remote_head = BUILTIN_FOLLOW_REMOTE_HEAD_DFLT;
+
 	if (rs->nr) {
 		refspec_ref_prefixes(rs, &transport_ls_refs_options.ref_prefixes);
 	} else {
@@ -1924,7 +1930,7 @@ static int do_fetch(struct transport *transport,
 		if (transport->remote->fetch.nr) {
 			refspec_ref_prefixes(&transport->remote->fetch,
 					     &transport_ls_refs_options.ref_prefixes);
-			if (transport->remote->follow_remote_head != FOLLOW_REMOTE_NEVER)
+			if (follow_remote_head != FOLLOW_REMOTE_NEVER)
 				do_set_head = 1;
 		}
 		if (branch && branch_has_merge_config(branch) &&
@@ -2131,7 +2137,7 @@ static int do_fetch(struct transport *transport,
 		 * Way too many cases where this can go wrong so let's just
 		 * ignore errors and fail silently for now.
 		 */
-		set_head(remote_refs, transport->remote);
+		set_head(remote_refs, transport->remote, follow_remote_head);
 	}
 
 cleanup:
diff --git a/remote.h b/remote.h
index 54b17e4b028b..72a54d84ad51 100644
--- a/remote.h
+++ b/remote.h
@@ -62,12 +62,14 @@ struct remote_state {
 void remote_state_clear(struct remote_state *remote_state);
 struct remote_state *remote_state_new(void);
 
-	enum follow_remote_head_settings {
-		FOLLOW_REMOTE_NEVER = -1,
-		FOLLOW_REMOTE_CREATE = 0,
-		FOLLOW_REMOTE_WARN = 1,
-		FOLLOW_REMOTE_ALWAYS = 2,
-	};
+#define BUILTIN_FOLLOW_REMOTE_HEAD_DFLT FOLLOW_REMOTE_CREATE
+enum follow_remote_head_settings {
+	FOLLOW_REMOTE_UNCONFIGURED = 0,
+	FOLLOW_REMOTE_NEVER,
+	FOLLOW_REMOTE_CREATE,
+	FOLLOW_REMOTE_WARN,
+	FOLLOW_REMOTE_ALWAYS,
+};
 
 struct remote {
 	struct hashmap_entry ent;
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH 6/7] fetch: add configuration option fetch.followRemoteHEAD
  2026-06-12  5:55 ` [PATCH 0/7] Introduce fetch.followRemoteHEAD config option Matt Hunter
                     ` (4 preceding siblings ...)
  2026-06-12  5:55   ` [PATCH 5/7] fetch: refactor do_fetch handling of followRemoteHEAD Matt Hunter
@ 2026-06-12  5:55   ` Matt Hunter
  2026-06-12 14:00     ` Matt Hunter
  2026-06-12 14:17     ` Junio C Hamano
  2026-06-12  5:55   ` [PATCH 7/7] fetch: fixup a misaligned comment Matt Hunter
  6 siblings, 2 replies; 16+ messages in thread
From: Matt Hunter @ 2026-06-12  5:55 UTC (permalink / raw)
  To: git

'fetch.followRemoteHEAD' is added as a generic option used by all
remotes for which 'remote.<name>.followRemoteHEAD' is undefined.  If
both options are undefined, a builtin default of "create" is in effect,
matching the previous behavior.

As mentioned in the previous patch, 'fetch.followRemoteHEAD' supports
all of the values that its 'remote' counterpart does _except_
warn-if-not-$branch, due to its tighter coupling to individual remote
repositories.

Documentation and advice messages for both of the followRemoteHEAD
options are reworded to better capture the relationship between the two.

The added tests assert feature parity between the two followRemoteHEAD
options, as well as the fact that 'remote.<name>.followRemoteHEAD'
always supersedes this new configurable default.

Signed-off-by: Matt Hunter <m@lfurio.us>
---
 Documentation/config/fetch.adoc  |  19 ++++++
 Documentation/config/remote.adoc |  21 +++----
 builtin/fetch.c                  |  32 ++++++++--
 t/t5510-fetch.sh                 | 105 +++++++++++++++++++++++++++++++
 4 files changed, 160 insertions(+), 17 deletions(-)

diff --git a/Documentation/config/fetch.adoc b/Documentation/config/fetch.adoc
index 04ac90912d3a..f7de22a34a54 100644
--- a/Documentation/config/fetch.adoc
+++ b/Documentation/config/fetch.adoc
@@ -126,3 +126,22 @@ the new bundle URI.
 The creation token values are chosen by the provider serving the specific
 bundle URI. If you modify the URI at `fetch.bundleURI`, then be sure to
 remove the value for the `fetch.bundleCreationToken` value before fetching.
+
+`fetch.followRemoteHEAD`::
+	When fetching using a default refspec, this option determines how to handle
+	differences between a fetched remote's `HEAD` and the local
+	`remotes/<name>/HEAD` symbolic-ref.  Its value is one of
++
+--
+`create`;;
+	Create `remotes/<name>/HEAD` if a ref exists on the remote, but not locally.
+	An existing symbolic-ref will not be touched.  This is the default value.
+`warn`;;
+	Display a warning if the remote advertises a different `HEAD` than what is
+	set locally.  Behaves like "create" if the local symbolic-ref doesn't exist.
+`always`;;
+	Silently update `remotes/<name>/HEAD` whenever the remote advertises a new
+	value.
+`never`;;
+	Never create or modify the `remotes/<name>/HEAD` symbolic-ref.
+--
diff --git a/Documentation/config/remote.adoc b/Documentation/config/remote.adoc
index eb9c8a3c4884..761bf4ba7d14 100644
--- a/Documentation/config/remote.adoc
+++ b/Documentation/config/remote.adoc
@@ -157,15 +157,12 @@ Blank values signal to ignore all previous values, allowing a reset of
 the list from broader config scenarios.
 
 remote.<name>.followRemoteHEAD::
-	How linkgit:git-fetch[1] should handle updates to `remotes/<name>/HEAD`
-	when fetching using the configured refspecs of a remote.
-	The default value is "create", which will create `remotes/<name>/HEAD`
-	if it exists on the remote, but not locally; this will not touch an
-	already existing local reference. Setting it to "warn" will print
-	a message if the remote has a different value than the local one;
-	in case there is no local reference, it behaves like "create".
-	A variant on "warn" is "warn-if-not-$branch", which behaves like
-	"warn", but if `HEAD` on the remote is `$branch` it will be silent.
-	Setting it to "always" will silently update `remotes/<name>/HEAD` to
-	the value on the remote.  Finally, setting it to "never" will never
-	change or create the local reference.
+	When fetching this remote using its default refspec, this option determines
+	how to handle differences between the remote's `HEAD` and the local
+	`remotes/<name>/HEAD` symbolic-ref.  Overrides the setting for
+	`fetch.followRemoteHEAD`.  See `fetch.followRemoteHEAD` for a description of
+	accepted values.
++
+In addition to the values supported by `fetch.followRemoteHEAD`, this option may
+also take on the value "warn-if-not-`$branch`", which behaves like "warn", but
+ignores the warning if the remote's `HEAD` is `remotes/<name>/$branch`.
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 3cc7efdd83a0..a21bb82274d4 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -103,6 +103,7 @@ static struct string_list negotiation_include = STRING_LIST_INIT_NODUP;
 
 struct fetch_config {
 	enum display_format display_format;
+	enum follow_remote_head_settings follow_remote_head;
 	int all;
 	int prune;
 	int prune_tags;
@@ -173,6 +174,22 @@ static int git_fetch_config(const char *k, const char *v,
 			    "fetch.output", v);
 	}
 
+	if (!strcmp(k, "fetch.followremotehead")) {
+		if (!v)
+			return config_error_nonbool(k);
+		else if (!strcasecmp(v, "never"))
+			fetch_config->follow_remote_head = FOLLOW_REMOTE_NEVER;
+		else if (!strcasecmp(v, "create"))
+			fetch_config->follow_remote_head = FOLLOW_REMOTE_CREATE;
+		else if (!strcasecmp(v, "warn"))
+			fetch_config->follow_remote_head = FOLLOW_REMOTE_WARN;
+		else if (!strcasecmp(v, "always"))
+			fetch_config->follow_remote_head = FOLLOW_REMOTE_ALWAYS;
+		else
+			die(_("invalid value for '%s': '%s'"),
+				"fetch.followRemoteHEAD", v);
+	}
+
 	return git_default_config(k, v, ctx, cb);
 }
 
@@ -1697,11 +1714,13 @@ static const char *strip_refshead(const char *name){
 static void set_head_advice_msg(const char *remote, const char *head_name)
 {
 	const char message_advice_set_head[] =
-	N_("Run 'git remote set-head %s %s' to follow the change, or set\n"
-	   "'remote.%s.followRemoteHEAD' configuration option to a different value\n"
-	   "if you do not want to see this message. Specifically running\n"
-	   "'git config set remote.%s.followRemoteHEAD warn-if-not-%s'\n"
-	   "will disable the warning until the remote changes HEAD to something else.");
+	N_("Run 'git remote set-head %s %s' to follow the change, or modify\n"
+	   "either of the 'remote.%s.followRemoteHEAD' or 'fetch.followRemoteHEAD'\n"
+	   "configuration options to handle the situation differently.\n\n"
+
+	   "Using this specific option\n\n"
+	   "    git config set remote.%s.followRemoteHEAD warn-if-not-%s\n\n"
+	   "will suppress the warning until the remote changes HEAD to something else.");
 
 	advise_if_enabled(ADVICE_FETCH_SET_HEAD_WARN, _(message_advice_set_head),
 			remote, head_name, remote, remote, head_name);
@@ -1919,6 +1938,8 @@ static int do_fetch(struct transport *transport,
 
 	if (transport->remote->follow_remote_head)
 		follow_remote_head = transport->remote->follow_remote_head;
+	else if (config->follow_remote_head)
+		follow_remote_head = config->follow_remote_head;
 	else
 		follow_remote_head = BUILTIN_FOLLOW_REMOTE_HEAD_DFLT;
 
@@ -2477,6 +2498,7 @@ int cmd_fetch(int argc,
 {
 	struct fetch_config config = {
 		.display_format = DISPLAY_FORMAT_FULL,
+		.follow_remote_head = FOLLOW_REMOTE_UNCONFIGURED,
 		.prune = -1,
 		.prune_tags = -1,
 		.show_forced_updates = 1,
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index 43190630e714..6f0ae1bdd798 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -140,6 +140,16 @@ test_expect_success "fetch test remote HEAD change" '
 	)
 '
 
+test_expect_success "fetch test default followRemoteHEAD never" '
+	git -C two update-ref --no-deref -d refs/remotes/origin/HEAD &&
+	test_config -C two fetch.followRemoteHEAD "never" &&
+	GIT_TRACE_PACKET=$PWD/trace.out git -C two fetch &&
+	# Confirm that we do not even ask for HEAD when we are
+	# not going to act on it.
+	test_grep ! "ref-prefix HEAD" trace.out &&
+	test_must_fail git -C two rev-parse --verify refs/remotes/origin/HEAD
+'
+
 test_expect_success "fetch test followRemoteHEAD never" '
 	git -C two update-ref --no-deref -d refs/remotes/origin/HEAD &&
 	test_config -C two remote.origin.followRemoteHEAD "never" &&
@@ -150,6 +160,21 @@ test_expect_success "fetch test followRemoteHEAD never" '
 	test_must_fail git -C two rev-parse --verify refs/remotes/origin/HEAD
 '
 
+test_expect_success "fetch test default followRemoteHEAD warn no change" '
+	git -C two rev-parse --verify refs/remotes/origin/other &&
+	git -C two remote set-head origin other &&
+	git -C two rev-parse --verify refs/remotes/origin/HEAD &&
+	git -C two rev-parse --verify refs/remotes/origin/main &&
+	test_config -C two fetch.followRemoteHEAD "warn" &&
+	git -C two fetch >output &&
+	echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \
+		"but we have ${SQ}other${SQ} locally." >expect &&
+	test_cmp expect output &&
+	head=$(git -C two rev-parse refs/remotes/origin/HEAD) &&
+	branch=$(git -C two rev-parse refs/remotes/origin/other) &&
+	test "z$head" = "z$branch"
+'
+
 test_expect_success "fetch test followRemoteHEAD warn no change" '
 	git -C two rev-parse --verify refs/remotes/origin/other &&
 	git -C two remote set-head origin other &&
@@ -165,6 +190,17 @@ test_expect_success "fetch test followRemoteHEAD warn no change" '
 	test "z$head" = "z$branch"
 '
 
+test_expect_success "fetch test default followRemoteHEAD warn create" '
+	git -C two update-ref --no-deref -d refs/remotes/origin/HEAD &&
+	test_config -C two fetch.followRemoteHEAD "warn" &&
+	git -C two rev-parse --verify refs/remotes/origin/main &&
+	output=$(git -C two fetch) &&
+	test "z" = "z$output" &&
+	head=$(git -C two rev-parse refs/remotes/origin/HEAD) &&
+	branch=$(git -C two rev-parse refs/remotes/origin/main) &&
+	test "z$head" = "z$branch"
+'
+
 test_expect_success "fetch test followRemoteHEAD warn create" '
 	git -C two update-ref --no-deref -d refs/remotes/origin/HEAD &&
 	test_config -C two remote.origin.followRemoteHEAD "warn" &&
@@ -176,6 +212,18 @@ test_expect_success "fetch test followRemoteHEAD warn create" '
 	test "z$head" = "z$branch"
 '
 
+test_expect_success "fetch test default followRemoteHEAD warn detached" '
+	git -C two update-ref --no-deref -d refs/remotes/origin/HEAD &&
+	git -C two update-ref refs/remotes/origin/HEAD HEAD &&
+	HEAD=$(git -C two log --pretty="%H") &&
+	test_config -C two fetch.followRemoteHEAD "warn" &&
+	git -C two fetch >output &&
+	echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \
+		"but we have a detached HEAD pointing to" \
+		"${SQ}${HEAD}${SQ} locally." >expect &&
+	test_cmp expect output
+'
+
 test_expect_success "fetch test followRemoteHEAD warn detached" '
 	git -C two update-ref --no-deref -d refs/remotes/origin/HEAD &&
 	git -C two update-ref refs/remotes/origin/HEAD HEAD &&
@@ -188,6 +236,19 @@ test_expect_success "fetch test followRemoteHEAD warn detached" '
 	test_cmp expect output
 '
 
+test_expect_success "fetch test default followRemoteHEAD warn quiet" '
+	git -C two rev-parse --verify refs/remotes/origin/other &&
+	git -C two remote set-head origin other &&
+	git -C two rev-parse --verify refs/remotes/origin/HEAD &&
+	git -C two rev-parse --verify refs/remotes/origin/main &&
+	test_config -C two fetch.followRemoteHEAD "warn" &&
+	output=$(git -C two fetch --quiet) &&
+	test "z" = "z$output" &&
+	head=$(git -C two rev-parse refs/remotes/origin/HEAD) &&
+	branch=$(git -C two rev-parse refs/remotes/origin/other) &&
+	test "z$head" = "z$branch"
+'
+
 test_expect_success "fetch test followRemoteHEAD warn quiet" '
 	git -C two rev-parse --verify refs/remotes/origin/other &&
 	git -C two remote set-head origin other &&
@@ -229,6 +290,18 @@ test_expect_success "fetch test followRemoteHEAD warn-if-not-branch branch is di
 	test "z$head" = "z$branch"
 '
 
+test_expect_success "fetch test default followRemoteHEAD always" '
+	git -C two rev-parse --verify refs/remotes/origin/other &&
+	git -C two remote set-head origin other &&
+	git -C two rev-parse --verify refs/remotes/origin/HEAD &&
+	git -C two rev-parse --verify refs/remotes/origin/main &&
+	test_config -C two fetch.followRemoteHEAD "always" &&
+	git -C two fetch &&
+	head=$(git -C two rev-parse refs/remotes/origin/HEAD) &&
+	branch=$(git -C two rev-parse refs/remotes/origin/main) &&
+	test "z$head" = "z$branch"
+'
+
 test_expect_success "fetch test followRemoteHEAD always" '
 	git -C two rev-parse --verify refs/remotes/origin/other &&
 	git -C two remote set-head origin other &&
@@ -241,6 +314,28 @@ test_expect_success "fetch test followRemoteHEAD always" '
 	test "z$head" = "z$branch"
 '
 
+test_expect_success 'per-remote followRemoteHEAD takes priority over fetch default' '
+	git -C two rev-parse --verify refs/remotes/origin/other &&
+	git -C two remote set-head origin other &&
+	git -C two rev-parse --verify refs/remotes/origin/HEAD &&
+	git -C two rev-parse --verify refs/remotes/origin/main &&
+	test_config -C two fetch.followRemoteHEAD "never" &&
+	test_config -C two remote.origin.followRemoteHEAD "always" &&
+	git -C two fetch &&
+	head=$(git -C two rev-parse refs/remotes/origin/HEAD) &&
+	branch=$(git -C two rev-parse refs/remotes/origin/main) &&
+	test "z$head" = "z$branch"
+'
+
+test_expect_success 'default followRemoteHEAD does not kick in with refspecs' '
+	git -C two remote set-head origin other &&
+	test_config -C two fetch.followRemoteHEAD always &&
+	git -C two fetch origin refs/heads/main:refs/remotes/origin/main &&
+	echo refs/remotes/origin/other >expect &&
+	git -C two symbolic-ref refs/remotes/origin/HEAD >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success 'followRemoteHEAD does not kick in with refspecs' '
 	git -C two remote set-head origin other &&
 	test_config -C two remote.origin.followRemoteHEAD always &&
@@ -250,6 +345,16 @@ test_expect_success 'followRemoteHEAD does not kick in with refspecs' '
 	test_cmp expect actual
 '
 
+test_expect_success 'default followRemoteHEAD create does not overwrite dangling symref' '
+	test_when_finished "git -C two remote remove custom-head" &&
+	git -C two remote add -m does-not-exist custom-head ../one &&
+	test_config -C two fetch.followRemoteHEAD create &&
+	git -C two fetch custom-head &&
+	echo refs/remotes/custom-head/does-not-exist >expect &&
+	git -C two symbolic-ref refs/remotes/custom-head/HEAD >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success 'followRemoteHEAD create does not overwrite dangling symref' '
 	test_when_finished "git -C two remote remove custom-head" &&
 	git -C two remote add -m does-not-exist custom-head ../one &&
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH 7/7] fetch: fixup a misaligned comment
  2026-06-12  5:55 ` [PATCH 0/7] Introduce fetch.followRemoteHEAD config option Matt Hunter
                     ` (5 preceding siblings ...)
  2026-06-12  5:55   ` [PATCH 6/7] fetch: add configuration option fetch.followRemoteHEAD Matt Hunter
@ 2026-06-12  5:55   ` Matt Hunter
  6 siblings, 0 replies; 16+ messages in thread
From: Matt Hunter @ 2026-06-12  5:55 UTC (permalink / raw)
  To: git

Signed-off-by: Matt Hunter <m@lfurio.us>
---
 builtin/fetch.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/builtin/fetch.c b/builtin/fetch.c
index a21bb82274d4..911ac8a47221 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1792,7 +1792,7 @@ static int set_head(const struct ref *remote_refs, struct remote *remote,
 		strbuf_addf(&b_head, "refs/remotes/%s/HEAD", remote->name);
 		strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", remote->name, head_name);
 	}
-		/* make sure it's valid */
+	/* make sure it's valid */
 	if (!baremirror && !refs_ref_exists(refs, b_remote_head.buf)) {
 		result = 1;
 		goto cleanup;
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* Re: followRemoteHEAD management question
  2026-06-11 20:36       ` Bence Ferdinandy
@ 2026-06-12  6:11         ` Matt Hunter
  0 siblings, 0 replies; 16+ messages in thread
From: Matt Hunter @ 2026-06-12  6:11 UTC (permalink / raw)
  To: Bence Ferdinandy, Jeff King; +Cc: git

On Thu Jun 11, 2026 at 4:36 PM EDT, Bence Ferdinandy wrote:
> On Thu Jun 11, 2026 at 08:01, Jeff King <peff@peff.net> wrote:
>>
>> My initial thought is that it might affect clone as well as fetch. But I
>> guess this feature does not kick in for clone, as it has its own logic
>> for handling the remote-tracking HEAD. Though arguably it should be
>> possible to configure it not to create one in the first place.
>
> If memory serves well clone has set the remote/HEAD well before this and
> I think it indeed uses a different mechanism/logic.

I'm a little interested to try to look into the clone case as well, but
I think I'll save it for a later patch series and keep the scope of this
one as it is.

> Bit late to the party, but happy to review/test patches if they come.

Greatly appreciated!
>
> Best,
> Bence

The first version of my patches went out.  You two are Cc'd on the cover
letter, but that didn't propagate to the patches themselves, oops.

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 6/7] fetch: add configuration option fetch.followRemoteHEAD
  2026-06-12  5:55   ` [PATCH 6/7] fetch: add configuration option fetch.followRemoteHEAD Matt Hunter
@ 2026-06-12 14:00     ` Matt Hunter
  2026-06-12 14:17     ` Junio C Hamano
  1 sibling, 0 replies; 16+ messages in thread
From: Matt Hunter @ 2026-06-12 14:00 UTC (permalink / raw)
  To: git

On Fri Jun 12, 2026 at 1:55 AM EDT, Matt Hunter wrote:
> diff --git a/Documentation/config/remote.adoc b/Documentation/config/remote.adoc
> index eb9c8a3c4884..761bf4ba7d14 100644
> --- a/Documentation/config/remote.adoc
> +++ b/Documentation/config/remote.adoc
> @@ -157,15 +157,12 @@ Blank values signal to ignore all previous values, allowing a reset of
>  the list from broader config scenarios.
>  
>  remote.<name>.followRemoteHEAD::
> -	How linkgit:git-fetch[1] should handle updates to `remotes/<name>/HEAD`
> -	when fetching using the configured refspecs of a remote.
> -	The default value is "create", which will create `remotes/<name>/HEAD`
> -	if it exists on the remote, but not locally; this will not touch an
> -	already existing local reference. Setting it to "warn" will print
> -	a message if the remote has a different value than the local one;
> -	in case there is no local reference, it behaves like "create".
> -	A variant on "warn" is "warn-if-not-$branch", which behaves like
> -	"warn", but if `HEAD` on the remote is `$branch` it will be silent.
> -	Setting it to "always" will silently update `remotes/<name>/HEAD` to
> -	the value on the remote.  Finally, setting it to "never" will never
> -	change or create the local reference.
> +	When fetching this remote using its default refspec, this option determines
> +	how to handle differences between the remote's `HEAD` and the local
> +	`remotes/<name>/HEAD` symbolic-ref.  Overrides the setting for
> +	`fetch.followRemoteHEAD`.  See `fetch.followRemoteHEAD` for a description of
> +	accepted values.
> ++
> +In addition to the values supported by `fetch.followRemoteHEAD`, this option may
> +also take on the value "warn-if-not-`$branch`", which behaves like "warn", but
> +ignores the warning if the remote's `HEAD` is `remotes/<name>/$branch`.

In hindsight, I'm wondering if $branch ought to be stylized as <branch>
to match the rest of the docs.  Thoughts?

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH 6/7] fetch: add configuration option fetch.followRemoteHEAD
  2026-06-12  5:55   ` [PATCH 6/7] fetch: add configuration option fetch.followRemoteHEAD Matt Hunter
  2026-06-12 14:00     ` Matt Hunter
@ 2026-06-12 14:17     ` Junio C Hamano
  1 sibling, 0 replies; 16+ messages in thread
From: Junio C Hamano @ 2026-06-12 14:17 UTC (permalink / raw)
  To: Matt Hunter; +Cc: git

Matt Hunter <m@lfurio.us> writes:

I haven't been following the discussion, so I will not comment on
the idea, i.e., if it makes sense to add such a new option and
configuration, but if we were to add such a thing, I have some
comments on the mechanics.

By the way, do not call a "configuration variable" a "configuration option".
Let's keep the vocabulary forcused without using random synonyms.

> diff --git a/builtin/fetch.c b/builtin/fetch.c
> index 3cc7efdd83a0..a21bb82274d4 100644
> --- a/builtin/fetch.c
> +++ b/builtin/fetch.c
> @@ -103,6 +103,7 @@ static struct string_list negotiation_include = STRING_LIST_INIT_NODUP;
>  
>  struct fetch_config {
>  	enum display_format display_format;
> +	enum follow_remote_head_settings follow_remote_head;
>  	int all;
>  	int prune;
>  	int prune_tags;
> @@ -173,6 +174,22 @@ static int git_fetch_config(const char *k, const char *v,
>  			    "fetch.output", v);
>  	}
>  
> +	if (!strcmp(k, "fetch.followremotehead")) {
> +		if (!v)
> +			return config_error_nonbool(k);
> +		else if (!strcasecmp(v, "never"))
> +			fetch_config->follow_remote_head = FOLLOW_REMOTE_NEVER;
> +		else if (!strcasecmp(v, "create"))
> +			fetch_config->follow_remote_head = FOLLOW_REMOTE_CREATE;
> +		else if (!strcasecmp(v, "warn"))
> +			fetch_config->follow_remote_head = FOLLOW_REMOTE_WARN;
> +		else if (!strcasecmp(v, "always"))
> +			fetch_config->follow_remote_head = FOLLOW_REMOTE_ALWAYS;
> +		else
> +			die(_("invalid value for '%s': '%s'"),
> +				"fetch.followRemoteHEAD", v);
> +	}

I think these uses of strcasecmp() are unnecessary and actively
harms end-user experience.  This is especially true because the
value given to remote.<name>.followRemoteHEAD is case sensitive.

Instead of saying "if you want X to happen, set this variable to
'create'", you have to say "'create', or any other case variations
thereof like 'CrEAte'" somehow, for very dubious gain to the end
users.  If you use strcmp(), and document only all lowercase forms,
it would guarantee to avoid confusing a newbie who read the variable
to be set to 'never' on one blog and 'Never' on another and wonder
if 'NEVER' would work or not.

Admittedly values to some existing configuration variables may be
parsed case insensitively but we should aim to fix the mistake in
the longer term, and we should certainly not add more of them.

Is it sensible to die() here?  If you are fetching from somewhere
without keeping a set of remote-tracking branches for it (i.e., a
single shot "git fetch https://github.com/gitster/git master"), you
do not care what garbage value is in fetch.followRemoteHEAD.
Perhaps the version of Git that is slightly newer than the version
that ships with this patch defined new valid values that this patch
does not know about, and such a user who is doing a single-shot
fetch may have that setting to help them working with their usual
non-single shot repositories, but they use a newer version of Git
for such regular work, and they are using slightly old version of
Git to perform this single-shot fetch.  The point is that the
configured value will *NOT* be used for such a user, and dying only
because this piece of code does not understand the configuration that
will not be used is of dubious value.

^ permalink raw reply	[flat|nested] 16+ messages in thread

end of thread, other threads:[~2026-06-12 14:17 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-05 16:31 followRemoteHEAD management question Matt Hunter
2026-06-08 23:49 ` Jeff King
2026-06-11  4:12   ` Matt Hunter
2026-06-11  6:01     ` Jeff King
2026-06-11 20:36       ` Bence Ferdinandy
2026-06-12  6:11         ` Matt Hunter
2026-06-12  5:55 ` [PATCH 0/7] Introduce fetch.followRemoteHEAD config option Matt Hunter
2026-06-12  5:55   ` [PATCH 1/7] fetch: fixup set_head advice for warn-if-not-branch Matt Hunter
2026-06-12  5:55   ` [PATCH 2/7] doc: explain fetchRemoteHEADWarn advice Matt Hunter
2026-06-12  5:55   ` [PATCH 3/7] t5510: cleanup remote in followRemoteHEAD dangling ref test Matt Hunter
2026-06-12  5:55   ` [PATCH 4/7] fetch: rename function report_set_head Matt Hunter
2026-06-12  5:55   ` [PATCH 5/7] fetch: refactor do_fetch handling of followRemoteHEAD Matt Hunter
2026-06-12  5:55   ` [PATCH 6/7] fetch: add configuration option fetch.followRemoteHEAD Matt Hunter
2026-06-12 14:00     ` Matt Hunter
2026-06-12 14:17     ` Junio C Hamano
2026-06-12  5:55   ` [PATCH 7/7] fetch: fixup a misaligned comment Matt Hunter

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox