Skip to content

Commit 7df580f

Browse files
authored
Merge pull request libgit2#4191 from pks-t/pks/wt-ref-renames
Branch renames with worktrees
2 parents 6cf25a3 + 2a485da commit 7df580f

File tree

7 files changed

+257
-124
lines changed

7 files changed

+257
-124
lines changed

src/branch.c

Lines changed: 16 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -127,62 +127,29 @@ int git_branch_create_from_annotated(
127127
repository, branch_name, commit->commit, commit->description, force);
128128
}
129129

130-
int git_branch_is_checked_out(
131-
const git_reference *branch)
130+
static int branch_equals(git_repository *repo, const char *path, void *payload)
132131
{
133-
git_buf path = GIT_BUF_INIT, buf = GIT_BUF_INIT;
134-
git_strarray worktrees;
135-
git_reference *ref = NULL;
136-
git_repository *repo;
137-
const char *worktree;
138-
int found = false;
139-
size_t i;
140-
141-
assert(branch && git_reference_is_branch(branch));
142-
143-
repo = git_reference_owner(branch);
144-
145-
if (git_worktree_list(&worktrees, repo) < 0)
146-
return -1;
147-
148-
for (i = 0; i < worktrees.count; i++) {
149-
worktree = worktrees.strings[i];
150-
151-
if (git_repository_head_for_worktree(&ref, repo, worktree) < 0)
152-
continue;
153-
154-
if (git__strcmp(ref->name, branch->name) == 0) {
155-
found = true;
156-
git_reference_free(ref);
157-
break;
158-
}
159-
160-
git_reference_free(ref);
161-
}
162-
git_strarray_free(&worktrees);
163-
164-
if (found)
165-
return found;
132+
git_reference *branch = (git_reference *) payload;
133+
git_reference *head;
134+
int equal;
166135

167-
/* Check HEAD of parent */
168-
if (git_buf_joinpath(&path, repo->commondir, GIT_HEAD_FILE) < 0)
169-
goto out;
170-
if (git_futils_readbuffer(&buf, path.ptr) < 0)
171-
goto out;
172-
if (git__prefixcmp(buf.ptr, "ref: ") == 0)
173-
git_buf_consume(&buf, buf.ptr + strlen("ref: "));
174-
git_buf_rtrim(&buf);
136+
if (git_reference__read_head(&head, repo, path) < 0 ||
137+
git_reference_type(head) != GIT_REF_SYMBOLIC)
138+
return 0;
175139

176-
found = git__strcmp(buf.ptr, branch->name) == 0;
140+
equal = !git__strcmp(head->target.symbolic, branch->name);
141+
git_reference_free(head);
142+
return equal;
143+
}
177144

178-
out:
179-
git_buf_free(&buf);
180-
git_buf_free(&path);
145+
int git_branch_is_checked_out(const git_reference *branch)
146+
{
147+
assert(branch && git_reference_is_branch(branch));
181148

182-
return found;
149+
return git_repository_foreach_head(git_reference_owner(branch),
150+
branch_equals, (void *) branch) == 1;
183151
}
184152

185-
186153
int git_branch_delete(git_reference *branch)
187154
{
188155
int is_head;

src/refs.c

Lines changed: 79 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,40 @@ int git_reference_lookup_resolved(
249249
return 0;
250250
}
251251

252+
int git_reference__read_head(
253+
git_reference **out,
254+
git_repository *repo,
255+
const char *path)
256+
{
257+
git_buf reference = GIT_BUF_INIT;
258+
char *name = NULL;
259+
int error;
260+
261+
if ((error = git_futils_readbuffer(&reference, path)) < 0)
262+
goto out;
263+
git_buf_rtrim(&reference);
264+
265+
if (git__strncmp(reference.ptr, GIT_SYMREF, strlen(GIT_SYMREF)) == 0) {
266+
git_buf_consume(&reference, reference.ptr + strlen(GIT_SYMREF));
267+
268+
name = git_path_basename(path);
269+
270+
if ((*out = git_reference__alloc_symbolic(name, reference.ptr)) == NULL) {
271+
error = -1;
272+
goto out;
273+
}
274+
} else {
275+
if ((error = git_reference_lookup(out, repo, reference.ptr)) < 0)
276+
goto out;
277+
}
278+
279+
out:
280+
free(name);
281+
git_buf_clear(&reference);
282+
283+
return error;
284+
}
285+
252286
int git_reference_dwim(git_reference **out, git_repository *repo, const char *refname)
253287
{
254288
int error = 0, i;
@@ -580,20 +614,53 @@ int git_reference_symbolic_set_target(
580614
out, ref->db->repo, ref->name, target, 1, ref->target.symbolic, log_message);
581615
}
582616

617+
typedef struct {
618+
const char *old_name;
619+
git_refname_t new_name;
620+
} rename_cb_data;
621+
622+
static int update_wt_heads(git_repository *repo, const char *path, void *payload)
623+
{
624+
rename_cb_data *data = (rename_cb_data *) payload;
625+
git_reference *head;
626+
char *gitdir = NULL;
627+
int error = 0;
628+
629+
if (git_reference__read_head(&head, repo, path) < 0 ||
630+
git_reference_type(head) != GIT_REF_SYMBOLIC ||
631+
git__strcmp(head->target.symbolic, data->old_name) != 0 ||
632+
(gitdir = git_path_dirname(path)) == NULL)
633+
goto out;
634+
635+
/* Update HEAD it was pointing to the reference being renamed */
636+
if ((error = git_repository_create_head(gitdir, data->new_name)) < 0) {
637+
giterr_set(GITERR_REFERENCE, "failed to update HEAD after renaming reference");
638+
goto out;
639+
}
640+
641+
out:
642+
git_reference_free(head);
643+
git__free(gitdir);
644+
645+
return error;
646+
}
647+
583648
static int reference__rename(git_reference **out, git_reference *ref, const char *new_name, int force,
584649
const git_signature *signature, const char *message)
585650
{
651+
git_repository *repo;
586652
git_refname_t normalized;
587653
bool should_head_be_updated = false;
588654
int error = 0;
589655

590656
assert(ref && new_name && signature);
591657

658+
repo = git_reference_owner(ref);
659+
592660
if ((error = reference_normalize_for_repo(
593-
normalized, git_reference_owner(ref), new_name, true)) < 0)
661+
normalized, repo, new_name, true)) < 0)
594662
return error;
595663

596-
597664
/* Check if we have to update HEAD. */
598665
if ((error = git_branch_is_head(ref)) < 0)
599666
return error;
@@ -603,14 +670,18 @@ static int reference__rename(git_reference **out, git_reference *ref, const char
603670
if ((error = git_refdb_rename(out, ref->db, ref->name, normalized, force, signature, message)) < 0)
604671
return error;
605672

606-
/* Update HEAD it was pointing to the reference being renamed */
607-
if (should_head_be_updated &&
608-
(error = git_repository_set_head(ref->db->repo, normalized)) < 0) {
609-
giterr_set(GITERR_REFERENCE, "failed to update HEAD after renaming reference");
610-
return error;
673+
/* Update HEAD if it was pointing to the reference being renamed */
674+
if (should_head_be_updated) {
675+
error = git_repository_set_head(ref->db->repo, normalized);
676+
} else {
677+
rename_cb_data payload;
678+
payload.old_name = ref->name;
679+
memcpy(&payload.new_name, &normalized, sizeof(normalized));
680+
681+
error = git_repository_foreach_head(repo, update_wt_heads, &payload);
611682
}
612683

613-
return 0;
684+
return error;
614685
}
615686

616687

src/refs.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,20 @@ int git_reference_lookup_resolved(
107107
const char *name,
108108
int max_deref);
109109

110+
/**
111+
* Read reference from a file.
112+
*
113+
* This function will read in the file at `path`. If it is a
114+
* symref, it will return a new unresolved symbolic reference
115+
* with the given name pointing to the reference pointed to by
116+
* the file. If it is not a symbolic reference, it will return
117+
* the resolved reference.
118+
*/
119+
int git_reference__read_head(
120+
git_reference **out,
121+
git_repository *repo,
122+
const char *path);
123+
110124
int git_reference__log_signature(git_signature **out, git_repository *repo);
111125

112126
/** Update a reference after a commit. */

src/repository.c

Lines changed: 57 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2063,47 +2063,27 @@ int git_repository_head_detached(git_repository *repo)
20632063
return exists;
20642064
}
20652065

2066-
static int read_worktree_head(git_buf *out, git_repository *repo, const char *name)
2066+
static int get_worktree_file_path(git_buf *out, git_repository *repo, const char *worktree, const char *file)
20672067
{
2068-
git_buf path = GIT_BUF_INIT;
2069-
int err;
2070-
2071-
assert(out && repo && name);
2072-
20732068
git_buf_clear(out);
2074-
2075-
if ((err = git_buf_printf(&path, "%s/worktrees/%s/HEAD", repo->commondir, name)) < 0)
2076-
goto out;
2077-
if (!git_path_exists(path.ptr))
2078-
{
2079-
err = -1;
2080-
goto out;
2081-
}
2082-
2083-
if ((err = git_futils_readbuffer(out, path.ptr)) < 0)
2084-
goto out;
2085-
git_buf_rtrim(out);
2086-
2087-
out:
2088-
git_buf_free(&path);
2089-
2090-
return err;
2069+
return git_buf_printf(out, "%s/worktrees/%s/%s", repo->commondir, worktree, file);
20912070
}
20922071

20932072
int git_repository_head_detached_for_worktree(git_repository *repo, const char *name)
20942073
{
2095-
git_buf buf = GIT_BUF_INIT;
2096-
int ret;
2074+
git_reference *ref = NULL;
2075+
int error;
20972076

20982077
assert(repo && name);
20992078

2100-
if (read_worktree_head(&buf, repo, name) < 0)
2101-
return -1;
2079+
if ((error = git_repository_head_for_worktree(&ref, repo, name)) < 0)
2080+
goto out;
21022081

2103-
ret = git__strncmp(buf.ptr, GIT_SYMREF, strlen(GIT_SYMREF)) != 0;
2104-
git_buf_free(&buf);
2082+
error = (git_reference_type(ref) != GIT_REF_SYMBOLIC);
2083+
out:
2084+
git_reference_free(ref);
21052085

2106-
return ret;
2086+
return error;
21072087
}
21082088

21092089
int git_repository_head(git_reference **head_out, git_repository *repo)
@@ -2127,44 +2107,66 @@ int git_repository_head(git_reference **head_out, git_repository *repo)
21272107

21282108
int git_repository_head_for_worktree(git_reference **out, git_repository *repo, const char *name)
21292109
{
2130-
git_buf buf = GIT_BUF_INIT;
2131-
git_reference *head;
2132-
int err;
2110+
git_buf path = GIT_BUF_INIT;
2111+
git_reference *head = NULL;
2112+
int error;
21332113

21342114
assert(out && repo && name);
21352115

21362116
*out = NULL;
21372117

2138-
if (git_repository_head_detached_for_worktree(repo, name))
2139-
return -1;
2140-
if ((err = read_worktree_head(&buf, repo, name)) < 0)
2118+
if ((error = get_worktree_file_path(&path, repo, name, GIT_HEAD_FILE)) < 0 ||
2119+
(error = git_reference__read_head(&head, repo, path.ptr)) < 0)
21412120
goto out;
21422121

2143-
/* We can only resolve symbolic references */
2144-
if (git__strncmp(buf.ptr, GIT_SYMREF, strlen(GIT_SYMREF)))
2145-
{
2146-
err = -1;
2147-
goto out;
2122+
if (git_reference_type(head) != GIT_REF_OID) {
2123+
git_reference *resolved;
2124+
2125+
error = git_reference_lookup_resolved(&resolved, repo, git_reference_symbolic_target(head), -1);
2126+
git_reference_free(head);
2127+
head = resolved;
21482128
}
2149-
git_buf_consume(&buf, buf.ptr + strlen(GIT_SYMREF));
21502129

2151-
if ((err = git_reference_lookup(&head, repo, buf.ptr)) < 0)
2130+
*out = head;
2131+
2132+
out:
2133+
if (error)
2134+
git_reference_free(head);
2135+
git_buf_clear(&path);
2136+
2137+
return error;
2138+
}
2139+
2140+
int git_repository_foreach_head(git_repository *repo, git_repository_foreach_head_cb cb, void *payload)
2141+
{
2142+
git_strarray worktrees = GIT_VECTOR_INIT;
2143+
git_buf path = GIT_BUF_INIT;
2144+
int error;
2145+
size_t i;
2146+
2147+
/* Execute callback for HEAD of commondir */
2148+
if ((error = git_buf_joinpath(&path, repo->commondir, GIT_HEAD_FILE)) < 0 ||
2149+
(error = cb(repo, path.ptr, payload) != 0))
21522150
goto out;
2153-
if (git_reference_type(head) == GIT_REF_OID)
2154-
{
2155-
*out = head;
2156-
err = 0;
2151+
2152+
if ((error = git_worktree_list(&worktrees, repo)) < 0) {
2153+
error = 0;
21572154
goto out;
21582155
}
21592156

2160-
err = git_reference_lookup_resolved(
2161-
out, repo, git_reference_symbolic_target(head), -1);
2162-
git_reference_free(head);
2157+
/* Execute callback for all worktree HEADs */
2158+
for (i = 0; i < worktrees.count; i++) {
2159+
if (get_worktree_file_path(&path, repo, worktrees.strings[i], GIT_HEAD_FILE) < 0)
2160+
continue;
21632161

2164-
out:
2165-
git_buf_free(&buf);
2162+
if ((error = cb(repo, path.ptr, payload)) != 0)
2163+
goto out;
2164+
}
21662165

2167-
return err;
2166+
out:
2167+
git_buf_free(&path);
2168+
git_strarray_free(&worktrees);
2169+
return error;
21682170
}
21692171

21702172
int git_repository_head_unborn(git_repository *repo)
@@ -2562,6 +2564,8 @@ int git_repository_set_head(
25622564

25632565
if (ref && current->type == GIT_REF_SYMBOLIC && git__strcmp(current->target.symbolic, ref->name) &&
25642566
git_reference_is_branch(ref) && git_branch_is_checked_out(ref)) {
2567+
giterr_set(GITERR_REPOSITORY, "cannot set HEAD to reference '%s' as it is the current HEAD "
2568+
"of a linked repository.", git_reference_name(ref));
25652569
error = -1;
25662570
goto cleanup;
25672571
}

0 commit comments

Comments
 (0)