summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatrick Steinhardt <ps@pks.im>2020-06-17 14:23:15 +0200
committerPatrick Steinhardt <ps@pks.im>2020-07-12 18:12:16 +0200
commit7216b048f5bd3cdad6416295d128f0173cb74533 (patch)
tree95814aadbcc298bff3cd78e49c6e64fe30f46c70
parent2fcb4f2801ef9a1e74b4281a78deae5e25b0ad43 (diff)
downloadlibgit2-7216b048f5bd3cdad6416295d128f0173cb74533.tar.gz
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.
-rw-r--r--src/refs.c90
1 files changed, 25 insertions, 65 deletions
diff --git a/src/refs.c b/src/refs.c
index c5d69c18c..470ed5441 100644
--- a/src/refs.c
+++ b/src/refs.c
@@ -631,84 +631,33 @@ int git_reference_symbolic_set_target(
typedef struct {
const char *old_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;
- git_reference *head = NULL;
- char *gitdir = NULL;
+ refs_update_head_payload *payload = (refs_update_head_payload *)_payload;
+ git_reference *head = NULL, *updated = NULL;
int error;
- if ((error = git_reference__read_head(&head, repo, path)) < 0) {
- git_error_set(GIT_ERROR_REFERENCE, "could not read HEAD when renaming references");
+ if ((error = git_reference_lookup(&head, worktree, GIT_HEAD_FILE)) < 0)
goto out;
- }
-
- if ((gitdir = git_path_dirname(path)) == NULL) {
- error = -1;
- goto out;
- }
if (git_reference_type(head) != GIT_REFERENCE_SYMBOLIC ||
- git__strcmp(head->target.symbolic, data->old_name) != 0) {
- error = 0;
+ git__strcmp(git_reference_symbolic_target(head), payload->old_name) != 0)
goto out;
- }
- /* Update HEAD it was pointing to the reference being renamed */
- if ((error = git_repository_create_head(gitdir, data->new_name)) < 0) {
+ /* Update HEAD if it was pointing to the reference being renamed */
+ 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");
goto out;
}
out:
+ git_reference_free(updated);
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;
}
-
int git_reference_rename(
git_reference **out,
git_reference *ref,
@@ -716,17 +665,28 @@ int git_reference_rename(
int force,
const char *log_message)
{
- git_signature *who;
+ refs_update_head_payload payload;
+ git_signature *signature;
+ git_repository *repo;
int error;
assert(out && ref);
- if ((error = git_reference__log_signature(&who, ref->db->repo)) < 0)
- return error;
+ repo = git_reference_owner(ref);
- error = reference__rename(out, ref, new_name, force, who, log_message);
- git_signature_free(who);
+ if ((error = git_reference__log_signature(&signature, repo)) < 0 ||
+ (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;
}