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 <bence@ferdinandy.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Bence Ferdinandy committed Nov 22, 2024 at 13:28 UTC b1b713f722894d7f66e9ec64bc934ca32004d3d1
2 files changed +21 -5
builtin/fetch.c
+11 -5
@@ -1584,7 +1584,7 @@ static const char *strip_refshead(const char *name){
1584
1585 static int set_head(const struct ref *remote_refs)
1586 {
1587 - int result = 0;
1587 + int result = 0, is_bare;
1588 struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT;
1589 const char *remote = gtransport->remote->name;
1590 char *head_name = NULL;
@@ -1616,15 +1616,21 @@ static int set_head(const struct ref *remote_refs)
1616
1617 if (!head_name)
1618 goto cleanup;
1619 - strbuf_addf(&b_head, "refs/remotes/%s/HEAD", remote);
1620 - strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", remote, head_name);
1619 + is_bare = is_bare_repository();
1620 + if (is_bare) {
1621 + strbuf_addstr(&b_head, "HEAD");
1622 + strbuf_addf(&b_remote_head, "refs/heads/%s", head_name);
1623 + } else {
1624 + strbuf_addf(&b_head, "refs/remotes/%s/HEAD", remote);
1625 + strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", remote, head_name);
1626 + }
1627 /* make sure it's valid */
1622 - if (!refs_ref_exists(refs, b_remote_head.buf)) {
1628 + if (!is_bare && !refs_ref_exists(refs, b_remote_head.buf)) {
1629 result = 1;
1630 goto cleanup;
1631 }
1632 if (refs_update_symref_extended(refs, b_head.buf, b_remote_head.buf,
1627 - "fetch", NULL, 1))
1633 + "fetch", NULL, !is_bare))
1634 result = 1;
1635
1636 cleanup:
t/t5505-remote.sh
+10
@@ -569,6 +569,16 @@ test_expect_success 'add --mirror && prune' '
569 )
570 '
571
572 +test_expect_success 'add --mirror setting HEAD' '
573 + mkdir headmirror &&
574 + (
575 + cd headmirror &&
576 + git init --bare -b notmain &&
577 + git remote add --mirror -f origin ../one &&
578 + test "$(git symbolic-ref HEAD)" = "refs/heads/main"
579 + )
580 +'
581 +
582 test_expect_success 'add --mirror=fetch' '
583 mkdir mirror-fetch &&
584 git init -b main mirror-fetch/parent &&