From 3f763ddf28d28fe63963991513c8db4045eabadc Mon Sep 17 00:00:00 2001 From: Bence Ferdinandy Date: Fri, 22 Nov 2024 13:28:50 +0100 Subject: fetch: set remote/HEAD if it does not exist When cloning a repository remote/HEAD is created, but when the user creates a repository with git init, and later adds a remote, remote/HEAD is only created if the user explicitly runs a variant of "remote set-head". Attempt to set remote/HEAD during fetch, if the user does not have it already set. Silently ignore any errors. Signed-off-by: Bence Ferdinandy Signed-off-by: Junio C Hamano --- builtin/fetch.c | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) (limited to 'builtin/fetch.c') diff --git a/builtin/fetch.c b/builtin/fetch.c index c900f57721..b2a36a5d95 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -1577,6 +1577,66 @@ static int backfill_tags(struct display_state *display_state, return retcode; } +static const char *strip_refshead(const char *name){ + skip_prefix(name, "refs/heads/", &name); + return name; +} + +static int set_head(const struct ref *remote_refs) +{ + int result = 0; + struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT; + const char *remote = gtransport->remote->name; + char *head_name = NULL; + struct ref *ref, *matches; + struct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map; + struct refspec_item refspec = { + .force = 0, + .pattern = 1, + .src = (char *) "refs/heads/*", + .dst = (char *) "refs/heads/*", + }; + struct string_list heads = STRING_LIST_INIT_DUP; + struct ref_store *refs = get_main_ref_store(the_repository); + + get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0); + matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"), + fetch_map, 1); + for (ref = matches; ref; ref = ref->next) { + string_list_append(&heads, strip_refshead(ref->name)); + } + + + if (!heads.nr) + result = 1; + else if (heads.nr > 1) + result = 1; + else + head_name = xstrdup(heads.items[0].string); + + if (!head_name) + goto cleanup; + strbuf_addf(&b_head, "refs/remotes/%s/HEAD", remote); + strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", remote, head_name); + /* make sure it's valid */ + if (!refs_ref_exists(refs, b_remote_head.buf)) { + result = 1; + goto cleanup; + } + if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf, + "fetch", NULL, 1)) + result = 1; + +cleanup: + free(head_name); + free_refs(fetch_map); + free_refs(matches); + string_list_clear(&heads, 0); + strbuf_release(&b_head); + strbuf_release(&b_remote_head); + return result; +} + static int do_fetch(struct transport *transport, struct refspec *rs, const struct fetch_config *config) @@ -1646,6 +1706,8 @@ static int do_fetch(struct transport *transport, "refs/tags/"); } + strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD"); + if (must_list_refs) { trace2_region_enter("fetch", "remote_refs", the_repository); remote_refs = transport_get_remote_refs(transport, @@ -1790,6 +1852,12 @@ static int do_fetch(struct transport *transport, "you need to specify exactly one branch with the --set-upstream option")); } } + if (set_head(remote_refs)) + ; + /* + * Way too many cases where this can go wrong + * so let's just fail silently for now. + */ cleanup: if (retcode) { -- cgit v1.2.3 From b1b713f722894d7f66e9ec64bc934ca32004d3d1 Mon Sep 17 00:00:00 2001 From: Bence Ferdinandy Date: Fri, 22 Nov 2024 13:28:51 +0100 Subject: fetch set_head: handle mirrored bare repositories When adding a remote to bare repository with "git remote add --mirror", running fetch will fail to update HEAD to the remote's HEAD, since it does not know how to handle bare repositories. On the other hand HEAD already has content, since "git init --bare" has already set HEAD to whatever is the default branch set for the user. Unless this - by chance - is the same as the remote's HEAD, HEAD will be pointing to a bad symref. Teach set_head to handle bare repositories, by overwriting HEAD so it mirrors the remote's HEAD. Note, that in this case overriding the local HEAD reference is necessary, since HEAD will exist before fetch can be run, but this should not be an issue, since the whole purpose of --mirror is to be an exact mirror of the remote, so following any changes to HEAD makes sense. Also note, that although "git remote set-head" also fails when trying to update the remote's locally tracked HEAD in a mirrored bare repository, the usage of the command does not make much sense after this patch: fetch will update the remote HEAD correctly, and setting it manually to something else is antithetical to the concept of mirroring. Signed-off-by: Bence Ferdinandy Signed-off-by: Junio C Hamano --- builtin/fetch.c | 16 +++++++++++----- t/t5505-remote.sh | 10 ++++++++++ 2 files changed, 21 insertions(+), 5 deletions(-) (limited to 'builtin/fetch.c') diff --git a/builtin/fetch.c b/builtin/fetch.c index b2a36a5d95..a64de4485f 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -1584,7 +1584,7 @@ static const char *strip_refshead(const char *name){ static int set_head(const struct ref *remote_refs) { - int result = 0; + int result = 0, is_bare; struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT; const char *remote = gtransport->remote->name; char *head_name = NULL; @@ -1616,15 +1616,21 @@ static int set_head(const struct ref *remote_refs) if (!head_name) goto cleanup; - strbuf_addf(&b_head, "refs/remotes/%s/HEAD", remote); - strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", remote, head_name); + is_bare = is_bare_repository(); + if (is_bare) { + strbuf_addstr(&b_head, "HEAD"); + strbuf_addf(&b_remote_head, "refs/heads/%s", head_name); + } else { + strbuf_addf(&b_head, "refs/remotes/%s/HEAD", remote); + strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", remote, head_name); + } /* make sure it's valid */ - if (!refs_ref_exists(refs, b_remote_head.buf)) { + if (!is_bare && !refs_ref_exists(refs, b_remote_head.buf)) { result = 1; goto cleanup; } if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf, - "fetch", NULL, 1)) + "fetch", NULL, !is_bare)) result = 1; cleanup: diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index afa261409f..2600add82a 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -569,6 +569,16 @@ test_expect_success 'add --mirror && prune' ' ) ' +test_expect_success 'add --mirror setting HEAD' ' + mkdir headmirror && + ( + cd headmirror && + git init --bare -b notmain && + git remote add --mirror -f origin ../one && + test "$(git symbolic-ref HEAD)" = "refs/heads/main" + ) +' + test_expect_success 'add --mirror=fetch' ' mkdir mirror-fetch && git init -b main mirror-fetch/parent && -- cgit v1.2.3