worktree: add a function to get worktree details
[git/git.git] / worktree.c
CommitLineData
ac6c561b
MR
1#include "cache.h"
2#include "refs.h"
3#include "strbuf.h"
4#include "worktree.h"
5
51934904
MR
6void free_worktrees(struct worktree **worktrees)
7{
8 int i = 0;
9
10 for (i = 0; worktrees[i]; i++) {
11 free(worktrees[i]->path);
12 free(worktrees[i]);
13 }
14 free (worktrees);
15}
16
1ceb7f90
MR
17/*
18 * read 'path_to_ref' into 'ref'. Also if is_detached is not NULL,
19 * set is_detached to 1 (0) if the ref is detatched (is not detached).
20 *
21 * $GIT_COMMON_DIR/$symref (e.g. HEAD) is practically outside $GIT_DIR so
22 * for linked worktrees, `resolve_ref_unsafe()` won't work (it uses
23 * git_path). Parse the ref ourselves.
24 *
25 * return -1 if the ref is not a proper ref, 0 otherwise (success)
26 */
27static int parse_ref(char *path_to_ref, struct strbuf *ref, int *is_detached)
28{
29 if (is_detached)
30 *is_detached = 0;
31 if (!strbuf_readlink(ref, path_to_ref, 0)) {
32 /* HEAD is symbolic link */
33 if (!starts_with(ref->buf, "refs/") ||
34 check_refname_format(ref->buf, 0))
35 return -1;
36 } else if (strbuf_read_file(ref, path_to_ref, 0) >= 0) {
37 /* textual symref or detached */
38 if (!starts_with(ref->buf, "ref:")) {
39 if (is_detached)
40 *is_detached = 1;
41 } else {
42 strbuf_remove(ref, 0, strlen("ref:"));
43 strbuf_trim(ref);
44 if (check_refname_format(ref->buf, 0))
45 return -1;
46 }
47 } else
48 return -1;
49 return 0;
50}
51
51934904
MR
52/**
53 * get the main worktree
54 */
55static struct worktree *get_main_worktree(void)
1ceb7f90 56{
51934904 57 struct worktree *worktree = NULL;
1ceb7f90 58 struct strbuf path = STRBUF_INIT;
51934904 59 struct strbuf worktree_path = STRBUF_INIT;
1ceb7f90 60 struct strbuf gitdir = STRBUF_INIT;
51934904 61 struct strbuf head_ref = STRBUF_INIT;
1ceb7f90 62
51934904
MR
63 strbuf_addf(&gitdir, "%s", absolute_path(get_git_common_dir()));
64 strbuf_addbuf(&worktree_path, &gitdir);
65 if (!strbuf_strip_suffix(&worktree_path, "/.git"))
66 strbuf_strip_suffix(&worktree_path, "/.");
67
68 strbuf_addf(&path, "%s/HEAD", get_git_common_dir());
69
70 if (parse_ref(path.buf, &head_ref, NULL) >= 0) {
71 worktree = xmalloc(sizeof(struct worktree));
72 worktree->path = strbuf_detach(&worktree_path, NULL);
73 worktree->git_dir = strbuf_detach(&gitdir, NULL);
74 }
1ceb7f90 75 strbuf_release(&path);
1ceb7f90 76 strbuf_release(&gitdir);
51934904
MR
77 strbuf_release(&worktree_path);
78 strbuf_release(&head_ref);
79 return worktree;
1ceb7f90
MR
80}
81
51934904 82static struct worktree *get_linked_worktree(const char *id)
ac6c561b 83{
51934904 84 struct worktree *worktree = NULL;
ac6c561b 85 struct strbuf path = STRBUF_INIT;
51934904 86 struct strbuf worktree_path = STRBUF_INIT;
ac6c561b 87 struct strbuf gitdir = STRBUF_INIT;
51934904 88 struct strbuf head_ref = STRBUF_INIT;
ac6c561b 89
1ceb7f90
MR
90 if (!id)
91 die("Missing linked worktree name");
ac6c561b 92
51934904
MR
93 strbuf_addf(&gitdir, "%s/worktrees/%s",
94 absolute_path(get_git_common_dir()), id);
95 strbuf_addf(&path, "%s/gitdir", gitdir.buf);
96 if (strbuf_read_file(&worktree_path, path.buf, 0) <= 0)
97 /* invalid gitdir file */
ac6c561b 98 goto done;
51934904
MR
99
100 strbuf_rtrim(&worktree_path);
101 if (!strbuf_strip_suffix(&worktree_path, "/.git")) {
102 strbuf_reset(&worktree_path);
103 strbuf_addstr(&worktree_path, absolute_path("."));
104 strbuf_strip_suffix(&worktree_path, "/.");
105 }
106
1ceb7f90 107 strbuf_reset(&path);
51934904
MR
108 strbuf_addf(&path, "%s/worktrees/%s/HEAD", get_git_common_dir(), id);
109
110 if (parse_ref(path.buf, &head_ref, NULL) >= 0) {
111 worktree = xmalloc(sizeof(struct worktree));
112 worktree->path = strbuf_detach(&worktree_path, NULL);
113 worktree->git_dir = strbuf_detach(&gitdir, NULL);
114 }
ac6c561b 115
ac6c561b
MR
116done:
117 strbuf_release(&path);
ac6c561b 118 strbuf_release(&gitdir);
51934904
MR
119 strbuf_release(&worktree_path);
120 strbuf_release(&head_ref);
121 return worktree;
ac6c561b
MR
122}
123
51934904 124struct worktree **get_worktrees(void)
ac6c561b 125{
51934904 126 struct worktree **list = NULL;
ac6c561b
MR
127 struct strbuf path = STRBUF_INIT;
128 DIR *dir;
129 struct dirent *d;
51934904
MR
130 int counter = 0, alloc = 2;
131
132 list = xmalloc(alloc * sizeof(struct worktree *));
ac6c561b 133
51934904
MR
134 if ((list[counter] = get_main_worktree()))
135 counter++;
ac6c561b
MR
136
137 strbuf_addf(&path, "%s/worktrees", get_git_common_dir());
138 dir = opendir(path.buf);
139 strbuf_release(&path);
51934904
MR
140 if (dir) {
141 while ((d = readdir(dir)) != NULL) {
142 struct worktree *linked = NULL;
143 if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
144 continue;
ac6c561b 145
51934904
MR
146 if ((linked = get_linked_worktree(d->d_name))) {
147 ALLOC_GROW(list, counter + 1, alloc);
148 list[counter++] = linked;
149 }
150 }
151 closedir(dir);
152 }
153 ALLOC_GROW(list, counter + 1, alloc);
154 list[counter] = NULL;
155 return list;
156}
157
158char *find_shared_symref(const char *symref, const char *target)
159{
160 char *existing = NULL;
161 struct strbuf path = STRBUF_INIT;
162 struct strbuf sb = STRBUF_INIT;
163 struct worktree **worktrees = get_worktrees();
164 int i = 0;
165
166 for (i = 0; worktrees[i]; i++) {
167 strbuf_reset(&path);
168 strbuf_reset(&sb);
169 strbuf_addf(&path, "%s/%s", worktrees[i]->git_dir, symref);
170
171 if (parse_ref(path.buf, &sb, NULL)) {
ac6c561b 172 continue;
51934904
MR
173 }
174
175 if (!strcmp(sb.buf, target)) {
176 existing = xstrdup(worktrees[i]->path);
177 break;
178 }
ac6c561b 179 }
51934904
MR
180
181 strbuf_release(&path);
182 strbuf_release(&sb);
183 free_worktrees(worktrees);
ac6c561b
MR
184
185 return existing;
186}