Commit 7216b048 by Patrick Steinhardt

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.
parent 2fcb4f28
...@@ -631,84 +631,33 @@ int git_reference_symbolic_set_target( ...@@ -631,84 +631,33 @@ int git_reference_symbolic_set_target(
typedef struct { typedef struct {
const char *old_name; const char *old_name;
git_refname_t new_name; git_refname_t new_name;
} rename_cb_data; } refs_update_head_payload;
static int update_wt_heads(git_repository *repo, const char *path, void *payload) static int refs_update_head(git_repository *worktree, void *_payload)
{ {
rename_cb_data *data = (rename_cb_data *) payload; refs_update_head_payload *payload = (refs_update_head_payload *)_payload;
git_reference *head = NULL; git_reference *head = NULL, *updated = NULL;
char *gitdir = NULL;
int error; int error;
if ((error = git_reference__read_head(&head, repo, path)) < 0) { if ((error = git_reference_lookup(&head, worktree, GIT_HEAD_FILE)) < 0)
git_error_set(GIT_ERROR_REFERENCE, "could not read HEAD when renaming references");
goto out; goto out;
}
if ((gitdir = git_path_dirname(path)) == NULL) {
error = -1;
goto out;
}
if (git_reference_type(head) != GIT_REFERENCE_SYMBOLIC || if (git_reference_type(head) != GIT_REFERENCE_SYMBOLIC ||
git__strcmp(head->target.symbolic, data->old_name) != 0) { git__strcmp(git_reference_symbolic_target(head), payload->old_name) != 0)
error = 0;
goto out; goto out;
}
/* Update HEAD it was pointing to the reference being renamed */ /* Update HEAD if it was pointing to the reference being renamed */
if ((error = git_repository_create_head(gitdir, data->new_name)) < 0) { if ((error = git_reference_symbolic_set_target(&updated, head, payload->new_name, NULL)) < 0) {
git_error_set(GIT_ERROR_REFERENCE, "failed to update HEAD after renaming reference"); git_error_set(GIT_ERROR_REFERENCE, "failed to update HEAD after renaming reference");
goto out; goto out;
} }
out: out:
git_reference_free(updated);
git_reference_free(head); git_reference_free(head);
git__free(gitdir);
return error;
}
static int reference__rename(git_reference **out, git_reference *ref, const char *new_name, int force,
const git_signature *signature, const char *message)
{
git_repository *repo;
git_refname_t normalized;
bool should_head_be_updated = false;
int error = 0;
assert(ref && new_name && signature);
repo = git_reference_owner(ref);
if ((error = reference_normalize_for_repo(
normalized, repo, new_name, true)) < 0)
return error;
/* Check if we have to update HEAD. */
if ((error = git_branch_is_head(ref)) < 0)
return error;
should_head_be_updated = (error > 0);
if ((error = git_refdb_rename(out, ref->db, ref->name, normalized, force, signature, message)) < 0)
return error;
/* Update HEAD if it was pointing to the reference being renamed */
if (should_head_be_updated) {
error = git_repository_set_head(ref->db->repo, normalized);
} else {
rename_cb_data payload;
payload.old_name = ref->name;
memcpy(&payload.new_name, &normalized, sizeof(normalized));
error = git_repository_foreach_head(repo, update_wt_heads, 0, &payload);
}
return error; return error;
} }
int git_reference_rename( int git_reference_rename(
git_reference **out, git_reference **out,
git_reference *ref, git_reference *ref,
...@@ -716,17 +665,28 @@ int git_reference_rename( ...@@ -716,17 +665,28 @@ int git_reference_rename(
int force, int force,
const char *log_message) const char *log_message)
{ {
git_signature *who; refs_update_head_payload payload;
git_signature *signature;
git_repository *repo;
int error; int error;
assert(out && ref); assert(out && ref);
if ((error = git_reference__log_signature(&who, ref->db->repo)) < 0) repo = git_reference_owner(ref);
return error;
error = reference__rename(out, ref, new_name, force, who, log_message); if ((error = git_reference__log_signature(&signature, repo)) < 0 ||
git_signature_free(who); (error = reference_normalize_for_repo(payload.new_name, repo, new_name, true)) < 0 ||
(error = git_refdb_rename(out, ref->db, ref->name, payload.new_name, force, signature, log_message)) < 0)
goto out;
payload.old_name = ref->name;
/* We may have to update any HEAD that was pointing to the renamed reference. */
if ((error = git_repository_foreach_worktree(repo, refs_update_head, &payload)) < 0)
goto out;
out:
git_signature_free(signature);
return error; return error;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment