Skip to content

Commit 7216b04

Browse files
committed
refs: update HEAD references via refdb
When renaming a reference, we need to iterate over every HEAD and potentially update it in case it is a symbolic reference pointing to the previous name of the renamed reference. Most importantly, this doesn't only include HEADs from the repo we're renaming the reference in, but we also need to iterate over HEADs from linked worktrees. In order to update the HEADs, we directly read them from the worktree's gitdir and thus assume that both repository and worktrees use the filesystem-based reference backend. But this breaks as soon as one got a repository with a different refdb and breaks our own abstractions. So let's instead update HEAD references via the refdb by first opening each worktree as a repository and then using the usual functions to read and update HEADs. This is a lot less efficient than the current code, but it's not like we can really help this: going via the refdb is mandatory.
1 parent 2fcb4f2 commit 7216b04

File tree

1 file changed

+25
-65
lines changed

1 file changed

+25
-65
lines changed

src/refs.c

Lines changed: 25 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -631,102 +631,62 @@ int git_reference_symbolic_set_target(
631631
typedef struct {
632632
const char *old_name;
633633
git_refname_t new_name;
634-
} rename_cb_data;
634+
} refs_update_head_payload;
635635

636-
static int update_wt_heads(git_repository *repo, const char *path, void *payload)
636+
static int refs_update_head(git_repository *worktree, void *_payload)
637637
{
638-
rename_cb_data *data = (rename_cb_data *) payload;
639-
git_reference *head = NULL;
640-
char *gitdir = NULL;
638+
refs_update_head_payload *payload = (refs_update_head_payload *)_payload;
639+
git_reference *head = NULL, *updated = NULL;
641640
int error;
642641

643-
if ((error = git_reference__read_head(&head, repo, path)) < 0) {
644-
git_error_set(GIT_ERROR_REFERENCE, "could not read HEAD when renaming references");
642+
if ((error = git_reference_lookup(&head, worktree, GIT_HEAD_FILE)) < 0)
645643
goto out;
646-
}
647-
648-
if ((gitdir = git_path_dirname(path)) == NULL) {
649-
error = -1;
650-
goto out;
651-
}
652644

653645
if (git_reference_type(head) != GIT_REFERENCE_SYMBOLIC ||
654-
git__strcmp(head->target.symbolic, data->old_name) != 0) {
655-
error = 0;
646+
git__strcmp(git_reference_symbolic_target(head), payload->old_name) != 0)
656647
goto out;
657-
}
658648

659-
/* Update HEAD it was pointing to the reference being renamed */
660-
if ((error = git_repository_create_head(gitdir, data->new_name)) < 0) {
649+
/* Update HEAD if it was pointing to the reference being renamed */
650+
if ((error = git_reference_symbolic_set_target(&updated, head, payload->new_name, NULL)) < 0) {
661651
git_error_set(GIT_ERROR_REFERENCE, "failed to update HEAD after renaming reference");
662652
goto out;
663653
}
664654

665655
out:
656+
git_reference_free(updated);
666657
git_reference_free(head);
667-
git__free(gitdir);
668-
669-
return error;
670-
}
671-
672-
static int reference__rename(git_reference **out, git_reference *ref, const char *new_name, int force,
673-
const git_signature *signature, const char *message)
674-
{
675-
git_repository *repo;
676-
git_refname_t normalized;
677-
bool should_head_be_updated = false;
678-
int error = 0;
679-
680-
assert(ref && new_name && signature);
681-
682-
repo = git_reference_owner(ref);
683-
684-
if ((error = reference_normalize_for_repo(
685-
normalized, repo, new_name, true)) < 0)
686-
return error;
687-
688-
/* Check if we have to update HEAD. */
689-
if ((error = git_branch_is_head(ref)) < 0)
690-
return error;
691-
692-
should_head_be_updated = (error > 0);
693-
694-
if ((error = git_refdb_rename(out, ref->db, ref->name, normalized, force, signature, message)) < 0)
695-
return error;
696-
697-
/* Update HEAD if it was pointing to the reference being renamed */
698-
if (should_head_be_updated) {
699-
error = git_repository_set_head(ref->db->repo, normalized);
700-
} else {
701-
rename_cb_data payload;
702-
payload.old_name = ref->name;
703-
memcpy(&payload.new_name, &normalized, sizeof(normalized));
704-
705-
error = git_repository_foreach_head(repo, update_wt_heads, 0, &payload);
706-
}
707-
708658
return error;
709659
}
710660

711-
712661
int git_reference_rename(
713662
git_reference **out,
714663
git_reference *ref,
715664
const char *new_name,
716665
int force,
717666
const char *log_message)
718667
{
719-
git_signature *who;
668+
refs_update_head_payload payload;
669+
git_signature *signature;
670+
git_repository *repo;
720671
int error;
721672

722673
assert(out && ref);
723674

724-
if ((error = git_reference__log_signature(&who, ref->db->repo)) < 0)
725-
return error;
675+
repo = git_reference_owner(ref);
726676

727-
error = reference__rename(out, ref, new_name, force, who, log_message);
728-
git_signature_free(who);
677+
if ((error = git_reference__log_signature(&signature, repo)) < 0 ||
678+
(error = reference_normalize_for_repo(payload.new_name, repo, new_name, true)) < 0 ||
679+
(error = git_refdb_rename(out, ref->db, ref->name, payload.new_name, force, signature, log_message)) < 0)
680+
goto out;
681+
682+
payload.old_name = ref->name;
729683

684+
/* We may have to update any HEAD that was pointing to the renamed reference. */
685+
if ((error = git_repository_foreach_worktree(repo, refs_update_head, &payload)) < 0)
686+
goto out;
687+
688+
out:
689+
git_signature_free(signature);
730690
return error;
731691
}
732692

0 commit comments

Comments
 (0)