In the current implementation, if refs/remotes/$remote/HEAD does not
exist, running fetch will create it, but if it does exist it will not do
anything, which is a somewhat safe and minimal approach. Unfortunately,
for users who wish to NOT have refs/remotes/$remote/HEAD set for any
reason (e.g. so that `git rev-parse origin` doesn't accidentally point
them somewhere they do not want to), there is no way to remove this
behaviour. On the other side of the spectrum, users may want fetch to
automatically update HEAD or at least give them a warning if something
changed on the remote.
Introduce a new setting, remote.$remote.followRemoteHEAD with four
options:
- "never": do not ever do anything, not even create
- "create": the current behaviour, now the default behaviour
- "warn": print a message if remote and local HEAD is different
- "always": silently update HEAD on every change
Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Bence Ferdinandy committedNov 29, 2024 at 00:06 UTCb7f7d16562c3c5af31d77254577e4afe9bf3e99a
5 files changed+171-6
Documentation/config/remote.txt
+11
index 6d8b7d6c63..024f92befc 100644--- a/Documentation/config/remote.txt+++ b/Documentation/config/remote.txt@@ -101,6 +101,17 @@ remote.<name>.serverOption:: The default set of server options used when fetching from this remote. These server options can be overridden by the `--server-option=` command line arguments.++remote.<name>.followRemoteHEAD::+ How linkgit:git-fetch[1] should handle updates to `remotes/<name>/HEAD`.+ The default value is "create", which will create `remotes/<name>/HEAD`+ if it exists on the remote, but not locally, but will not touch an+ already existing local reference. Setting to "warn" will print+ a message if the remote has a different value, than the local one and+ in case there is no local reference, it behaves like "create". Setting+ to "always" will silently update it to the value on the remote.+ Finally, setting it to "never" will never change or create the local+ reference. + This is a multi-valued variable, and an empty value can be used in a higher priority configuration file (e.g. `.git/config` in a repository) to clear
builtin/fetch.c
+40-6
index 2f416cf867..88c5c5d781 100644--- a/builtin/fetch.c+++ b/builtin/fetch.c@@ -1579,10 +1579,35 @@ static const char *strip_refshead(const char *name){ return name; }-static int set_head(const struct ref *remote_refs)+static void report_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;++ strbuf_addf(&buf_prefix, "refs/remotes/%s/", remote);+ skip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head);++ if (prev_head && strcmp(prev_head, head_name)) {+ printf("'HEAD' at '%s' is '%s', but we have '%s' locally.\n",+ remote, head_name, prev_head);+ printf("Run 'git remote set-head %s %s' to follow the change.\n",+ remote, head_name);+ }+ else if (updateres && buf_prev->len) {+ printf("'HEAD' at '%s' is '%s', "+ "but we have a detached HEAD pointing to '%s' locally.\n",+ remote, head_name, buf_prev->buf);+ printf("Run 'git remote set-head %s %s' to follow the change.\n",+ remote, head_name);+ }+ strbuf_release(&buf_prefix);+}++static int set_head(const struct ref *remote_refs, int follow_remote_head) {- int result = 0, is_bare;- struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;+ int result = 0, create_only, is_bare, was_detached;+ struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT,+ b_local_head = STRBUF_INIT; const char *remote = gtransport->remote->name; char *head_name = NULL; struct ref *ref, *matches;@@ -1603,6 +1628,8 @@ static int set_head(const struct ref *remote_refs) string_list_append(&heads, strip_refshead(ref->name)); }+ if (follow_remote_head == FOLLOW_REMOTE_NEVER)+ goto cleanup; if (!heads.nr) result = 1;@@ -1614,6 +1641,7 @@ static int set_head(const struct ref *remote_refs) if (!head_name) goto cleanup; is_bare = is_bare_repository();+ create_only = follow_remote_head == FOLLOW_REMOTE_ALWAYS ? 0 : !is_bare; if (is_bare) { strbuf_addstr(&b_head, "HEAD"); strbuf_addf(&b_remote_head, "refs/heads/%s", head_name);@@ -1626,9 +1654,14 @@ static int set_head(const struct ref *remote_refs) result = 1; goto cleanup; }- if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,- "fetch", NULL, !is_bare))+ was_detached = refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,+ "fetch", &b_local_head, create_only);+ if (was_detached == -1) { result = 1;+ goto cleanup;+ }+ if (follow_remote_head == FOLLOW_REMOTE_WARN && verbosity >= 0)+ report_set_head(remote, head_name, &b_local_head, was_detached); cleanup: free(head_name);@@ -1636,6 +1669,7 @@ cleanup: free_refs(matches); string_list_clear(&heads, 0); strbuf_release(&b_head);+ strbuf_release(&b_local_head); strbuf_release(&b_remote_head); return result; }@@ -1855,7 +1889,7 @@ static int do_fetch(struct transport *transport, "you need to specify exactly one branch with the --set-upstream option")); } }- if (set_head(remote_refs))+ if (set_head(remote_refs, transport->remote->follow_remote_head)) ; /* * Way too many cases where this can go wrong
remote.c
+9
index 10104d11e3..0b18840d43 100644--- a/remote.c+++ b/remote.c@@ -514,6 +514,15 @@ static int handle_config(const char *key, const char *value, } else if (!strcmp(subkey, "serveroption")) { return parse_transport_option(key, value, &remote->server_options);+ } else if (!strcmp(subkey, "followremotehead")) {+ if (!strcmp(value, "never"))+ remote->follow_remote_head = FOLLOW_REMOTE_NEVER;+ else if (!strcmp(value, "create"))+ remote->follow_remote_head = FOLLOW_REMOTE_CREATE;+ else if (!strcmp(value, "warn"))+ remote->follow_remote_head = FOLLOW_REMOTE_WARN;+ else if (!strcmp(value, "always"))+ remote->follow_remote_head = FOLLOW_REMOTE_ALWAYS; } return 0; }