wt-status.c: make wt_status_check_rebase() work on any worktree
[git] / worktree.c
1 #include "cache.h"
2 #include "refs.h"
3 #include "strbuf.h"
4 #include "worktree.h"
5 #include "dir.h"
6
7 void free_worktrees(struct worktree **worktrees)
8 {
9         int i = 0;
10
11         for (i = 0; worktrees[i]; i++) {
12                 free(worktrees[i]->path);
13                 free(worktrees[i]->id);
14                 free(worktrees[i]->head_ref);
15                 free(worktrees[i]);
16         }
17         free (worktrees);
18 }
19
20 /*
21  * read 'path_to_ref' into 'ref'.  Also if is_detached is not NULL,
22  * set is_detached to 1 (0) if the ref is detatched (is not detached).
23  *
24  * $GIT_COMMON_DIR/$symref (e.g. HEAD) is practically outside $GIT_DIR so
25  * for linked worktrees, `resolve_ref_unsafe()` won't work (it uses
26  * git_path). Parse the ref ourselves.
27  *
28  * return -1 if the ref is not a proper ref, 0 otherwise (success)
29  */
30 static int parse_ref(char *path_to_ref, struct strbuf *ref, int *is_detached)
31 {
32         if (is_detached)
33                 *is_detached = 0;
34         if (!strbuf_readlink(ref, path_to_ref, 0)) {
35                 /* HEAD is symbolic link */
36                 if (!starts_with(ref->buf, "refs/") ||
37                                 check_refname_format(ref->buf, 0))
38                         return -1;
39         } else if (strbuf_read_file(ref, path_to_ref, 0) >= 0) {
40                 /* textual symref or detached */
41                 if (!starts_with(ref->buf, "ref:")) {
42                         if (is_detached)
43                                 *is_detached = 1;
44                 } else {
45                         strbuf_remove(ref, 0, strlen("ref:"));
46                         strbuf_trim(ref);
47                         if (check_refname_format(ref->buf, 0))
48                                 return -1;
49                 }
50         } else
51                 return -1;
52         return 0;
53 }
54
55 /**
56  * Add the head_sha1 and head_ref (if not detached) to the given worktree
57  */
58 static void add_head_info(struct strbuf *head_ref, struct worktree *worktree)
59 {
60         if (head_ref->len) {
61                 if (worktree->is_detached) {
62                         get_sha1_hex(head_ref->buf, worktree->head_sha1);
63                 } else {
64                         resolve_ref_unsafe(head_ref->buf, 0, worktree->head_sha1, NULL);
65                         worktree->head_ref = strbuf_detach(head_ref, NULL);
66                 }
67         }
68 }
69
70 /**
71  * get the main worktree
72  */
73 static struct worktree *get_main_worktree(void)
74 {
75         struct worktree *worktree = NULL;
76         struct strbuf path = STRBUF_INIT;
77         struct strbuf worktree_path = STRBUF_INIT;
78         struct strbuf head_ref = STRBUF_INIT;
79         int is_bare = 0;
80         int is_detached = 0;
81
82         strbuf_addstr(&worktree_path, absolute_path(get_git_common_dir()));
83         is_bare = !strbuf_strip_suffix(&worktree_path, "/.git");
84         if (is_bare)
85                 strbuf_strip_suffix(&worktree_path, "/.");
86
87         strbuf_addf(&path, "%s/HEAD", get_git_common_dir());
88
89         if (parse_ref(path.buf, &head_ref, &is_detached) < 0)
90                 goto done;
91
92         worktree = xmalloc(sizeof(struct worktree));
93         worktree->path = strbuf_detach(&worktree_path, NULL);
94         worktree->id = NULL;
95         worktree->is_bare = is_bare;
96         worktree->head_ref = NULL;
97         worktree->is_detached = is_detached;
98         worktree->is_current = 0;
99         add_head_info(&head_ref, worktree);
100
101 done:
102         strbuf_release(&path);
103         strbuf_release(&worktree_path);
104         strbuf_release(&head_ref);
105         return worktree;
106 }
107
108 static struct worktree *get_linked_worktree(const char *id)
109 {
110         struct worktree *worktree = NULL;
111         struct strbuf path = STRBUF_INIT;
112         struct strbuf worktree_path = STRBUF_INIT;
113         struct strbuf head_ref = STRBUF_INIT;
114         int is_detached = 0;
115
116         if (!id)
117                 die("Missing linked worktree name");
118
119         strbuf_git_common_path(&path, "worktrees/%s/gitdir", id);
120         if (strbuf_read_file(&worktree_path, path.buf, 0) <= 0)
121                 /* invalid gitdir file */
122                 goto done;
123
124         strbuf_rtrim(&worktree_path);
125         if (!strbuf_strip_suffix(&worktree_path, "/.git")) {
126                 strbuf_reset(&worktree_path);
127                 strbuf_addstr(&worktree_path, absolute_path("."));
128                 strbuf_strip_suffix(&worktree_path, "/.");
129         }
130
131         strbuf_reset(&path);
132         strbuf_addf(&path, "%s/worktrees/%s/HEAD", get_git_common_dir(), id);
133
134         if (parse_ref(path.buf, &head_ref, &is_detached) < 0)
135                 goto done;
136
137         worktree = xmalloc(sizeof(struct worktree));
138         worktree->path = strbuf_detach(&worktree_path, NULL);
139         worktree->id = xstrdup(id);
140         worktree->is_bare = 0;
141         worktree->head_ref = NULL;
142         worktree->is_detached = is_detached;
143         worktree->is_current = 0;
144         add_head_info(&head_ref, worktree);
145
146 done:
147         strbuf_release(&path);
148         strbuf_release(&worktree_path);
149         strbuf_release(&head_ref);
150         return worktree;
151 }
152
153 static void mark_current_worktree(struct worktree **worktrees)
154 {
155         struct strbuf git_dir = STRBUF_INIT;
156         struct strbuf path = STRBUF_INIT;
157         int i;
158
159         strbuf_addstr(&git_dir, absolute_path(get_git_dir()));
160         for (i = 0; worktrees[i]; i++) {
161                 struct worktree *wt = worktrees[i];
162                 strbuf_addstr(&path, absolute_path(get_worktree_git_dir(wt)));
163                 wt->is_current = !fspathcmp(git_dir.buf, path.buf);
164                 strbuf_reset(&path);
165                 if (wt->is_current)
166                         break;
167         }
168         strbuf_release(&git_dir);
169         strbuf_release(&path);
170 }
171
172 struct worktree **get_worktrees(void)
173 {
174         struct worktree **list = NULL;
175         struct strbuf path = STRBUF_INIT;
176         DIR *dir;
177         struct dirent *d;
178         int counter = 0, alloc = 2;
179
180         list = xmalloc(alloc * sizeof(struct worktree *));
181
182         if ((list[counter] = get_main_worktree()))
183                 counter++;
184
185         strbuf_addf(&path, "%s/worktrees", get_git_common_dir());
186         dir = opendir(path.buf);
187         strbuf_release(&path);
188         if (dir) {
189                 while ((d = readdir(dir)) != NULL) {
190                         struct worktree *linked = NULL;
191                         if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
192                                 continue;
193
194                         if ((linked = get_linked_worktree(d->d_name))) {
195                                 ALLOC_GROW(list, counter + 1, alloc);
196                                 list[counter++] = linked;
197                         }
198                 }
199                 closedir(dir);
200         }
201         ALLOC_GROW(list, counter + 1, alloc);
202         list[counter] = NULL;
203
204         mark_current_worktree(list);
205         return list;
206 }
207
208 const char *get_worktree_git_dir(const struct worktree *wt)
209 {
210         if (!wt)
211                 return get_git_dir();
212         else if (!wt->id)
213                 return get_git_common_dir();
214         else
215                 return git_common_path("worktrees/%s", wt->id);
216 }
217
218 const struct worktree *find_shared_symref(const char *symref,
219                                           const char *target)
220 {
221         const struct worktree *existing = NULL;
222         struct strbuf path = STRBUF_INIT;
223         struct strbuf sb = STRBUF_INIT;
224         static struct worktree **worktrees;
225         int i = 0;
226
227         if (worktrees)
228                 free_worktrees(worktrees);
229         worktrees = get_worktrees();
230
231         for (i = 0; worktrees[i]; i++) {
232                 strbuf_reset(&path);
233                 strbuf_reset(&sb);
234                 strbuf_addf(&path, "%s/%s",
235                             get_worktree_git_dir(worktrees[i]),
236                             symref);
237
238                 if (parse_ref(path.buf, &sb, NULL)) {
239                         continue;
240                 }
241
242                 if (!strcmp(sb.buf, target)) {
243                         existing = worktrees[i];
244                         break;
245                 }
246         }
247
248         strbuf_release(&path);
249         strbuf_release(&sb);
250
251         return existing;
252 }