fetch set_head: add warn-if-not-$branch option

Currently if we want to have a remote/HEAD locally that is different from the one on the remote, but we still want to get a warning if remote changes HEAD, our only option is to have an indiscriminate warning with "follow_remote_head" set to "warn". Add a new option "warn-if-not-$branch", where $branch is a branch name we do not wish to get a warning about. If the remote HEAD is $branch do not warn, otherwise, behave as "warn". E.g. let's assume, that our remote origin has HEAD set to "master", but locally we have "git remote set-head origin seen". Setting 'remote.origin.followRemoteHEAD = "warn"' will always print a warning, even though the remote has not changed HEAD from "master". Setting 'remote.origin.followRemoteHEAD = "warn-if-not-master" will squelch the warning message, unless the remote changes HEAD from "master". Note, that should the remote change HEAD to "seen" (which we have locally), there will still be no warning. Improve the advice message in report_set_head to also include silencing the warning message with "warn-if-not-$branch". Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Bence Ferdinandy committed Dec 5, 2024 at 13:16 UTC 9e2b7005becaf730ff75f6efbef4542cc4454107
5 files changed +66 -10
Documentation/config/remote.txt
+5 -3
@@ -106,10 +106,12 @@ remote.<name>.followRemoteHEAD::
106 How linkgit:git-fetch[1] should handle updates to `remotes/<name>/HEAD`.
107 The default value is "create", which will create `remotes/<name>/HEAD`
108 if it exists on the remote, but not locally, but will not touch an
109 - already existing local reference. Setting to "warn" will print
109 + already existing local reference. Setting to "warn" will print
110 a message if the remote has a different value, than the local one and
111 - in case there is no local reference, it behaves like "create". Setting
112 - to "always" will silently update it to the value on the remote.
111 + in case there is no local reference, it behaves like "create".
112 + A variant on "warn" is "warn-if-not-$branch", which behaves like
113 + "warn", but if `HEAD` on the remote is `$branch` it will be silent.
114 + Setting to "always" will silently update it to the value on the remote.
115 Finally, setting it to "never" will never change or create the local
116 reference.
117 +
builtin/fetch.c
+11 -5
@@ -1584,10 +1584,12 @@ static void set_head_advice_msg(const char *remote, const char *head_name)
1584 const char message_advice_set_head[] =
1585 N_("Run 'git remote set-head %s %s' to follow the change, or set\n"
1586 "'remote.%s.followRemoteHEAD' configuration option to a different value\n"
1587 - "if you do not want to see this message.");
1587 + "if you do not want to see this message. Specifically running\n"
1588 + "'git config set remote.%s.followRemoteHEAD %s' will disable the warning\n"
1589 + "until the remote changes HEAD to something else.");
1590
1591 advise_if_enabled(ADVICE_FETCH_SET_HEAD_WARN, _(message_advice_set_head),
1590 - remote, head_name, remote);
1592 + remote, head_name, remote, remote, head_name);
1593 }
1594
1595 static void report_set_head(const char *remote, const char *head_name,
@@ -1612,7 +1614,8 @@ static void report_set_head(const char *remote, const char *head_name,
1614 strbuf_release(&buf_prefix);
1615 }
1616
1615 -static int set_head(const struct ref *remote_refs, int follow_remote_head)
1617 +static int set_head(const struct ref *remote_refs, int follow_remote_head,
1618 + const char *no_warn_branch)
1619 {
1620 int result = 0, create_only, is_bare, was_detached;
1621 struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,
@@ -1669,7 +1672,9 @@ static int set_head(const struct ref *remote_refs, int follow_remote_head)
1672 result = 1;
1673 goto cleanup;
1674 }
1672 - if (follow_remote_head == FOLLOW_REMOTE_WARN && verbosity >= 0)
1675 + if (verbosity >= 0 &&
1676 + follow_remote_head == FOLLOW_REMOTE_WARN &&
1677 + (!no_warn_branch || strcmp(no_warn_branch, head_name)))
1678 report_set_head(remote, head_name, &b_local_head, was_detached);
1679
1680 cleanup:
@@ -1898,7 +1903,8 @@ static int do_fetch(struct transport *transport,
1903 "you need to specify exactly one branch with the --set-upstream option"));
1904 }
1905 }
1901 - if (set_head(remote_refs, transport->remote->follow_remote_head))
1906 + if (set_head(remote_refs, transport->remote->follow_remote_head,
1907 + transport->remote->no_warn_branch))
1908 ;
1909 /*
1910 * Way too many cases where this can go wrong
remote.c
+11 -2
@@ -515,14 +515,23 @@ static int handle_config(const char *key, const char *value,
515 return parse_transport_option(key, value,
516 &remote->server_options);
517 } else if (!strcmp(subkey, "followremotehead")) {
518 + const char *no_warn_branch;
519 if (!strcmp(value, "never"))
520 remote->follow_remote_head = FOLLOW_REMOTE_NEVER;
521 else if (!strcmp(value, "create"))
522 remote->follow_remote_head = FOLLOW_REMOTE_CREATE;
522 - else if (!strcmp(value, "warn"))
523 + else if (!strcmp(value, "warn")) {
524 remote->follow_remote_head = FOLLOW_REMOTE_WARN;
524 - else if (!strcmp(value, "always"))
525 + remote->no_warn_branch = NULL;
526 + } else if (skip_prefix(value, "warn-if-not-", &no_warn_branch)) {
527 + remote->follow_remote_head = FOLLOW_REMOTE_WARN;
528 + remote->no_warn_branch = no_warn_branch;
529 + } else if (!strcmp(value, "always")) {
530 remote->follow_remote_head = FOLLOW_REMOTE_ALWAYS;
531 + } else {
532 + warning(_("unrecognized followRemoteHEAD value '%s' ignored"),
533 + value);
534 + }
535 }
536 return 0;
537 }
remote.h
+1
@@ -116,6 +116,7 @@ struct remote {
116 struct string_list server_options;
117
118 enum follow_remote_head_settings follow_remote_head;
119 + const char *no_warn_branch;
120 };
121
122 /**
t/t5510-fetch.sh
+38
@@ -182,6 +182,44 @@ test_expect_success "fetch test followRemoteHEAD warn quiet" '
182 )
183 '
184
185 +test_expect_success "fetch test followRemoteHEAD warn-if-not-branch branch is same" '
186 + test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
187 + (
188 + cd "$D" &&
189 + cd two &&
190 + git rev-parse --verify refs/remotes/origin/other &&
191 + git remote set-head origin other &&
192 + git rev-parse --verify refs/remotes/origin/HEAD &&
193 + git rev-parse --verify refs/remotes/origin/main &&
194 + git config set remote.origin.followRemoteHEAD "warn-if-not-main" &&
195 + actual=$(git fetch) &&
196 + test "z" = "z$actual" &&
197 + head=$(git rev-parse refs/remotes/origin/HEAD) &&
198 + branch=$(git rev-parse refs/remotes/origin/other) &&
199 + test "z$head" = "z$branch"
200 + )
201 +'
202 +
203 +test_expect_success "fetch test followRemoteHEAD warn-if-not-branch branch is different" '
204 + test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
205 + (
206 + cd "$D" &&
207 + cd two &&
208 + git rev-parse --verify refs/remotes/origin/other &&
209 + git remote set-head origin other &&
210 + git rev-parse --verify refs/remotes/origin/HEAD &&
211 + git rev-parse --verify refs/remotes/origin/main &&
212 + git config set remote.origin.followRemoteHEAD "warn-if-not-some/different-branch" &&
213 + git fetch >actual &&
214 + echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \
215 + "but we have ${SQ}other${SQ} locally." >expect &&
216 + test_cmp expect actual &&
217 + head=$(git rev-parse refs/remotes/origin/HEAD) &&
218 + branch=$(git rev-parse refs/remotes/origin/other) &&
219 + test "z$head" = "z$branch"
220 + )
221 +'
222 +
223 test_expect_success "fetch test followRemoteHEAD always" '
224 test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
225 (