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