* [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; 14+ 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] 14+ 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; 14+ 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] 14+ 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; 14+ 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] 14+ 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; 14+ 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] 14+ 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; 14+ 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] 14+ 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 5:55 ` [PATCH 7/7] fetch: fixup a misaligned comment Matt Hunter
6 siblings, 0 replies; 14+ 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] 14+ 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; 14+ 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] 14+ messages in thread