worktree: add a function to get worktree details
[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]);
13         }
14         free (worktrees);
15 }
16
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  */
27 static 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
52 /**
53  * get the main worktree
54  */
55 static struct worktree *get_main_worktree(void)
56 {
57         struct worktree *worktree = NULL;
58         struct strbuf path = STRBUF_INIT;
59         struct strbuf worktree_path = STRBUF_INIT;
60         struct strbuf gitdir = STRBUF_INIT;
61         struct strbuf head_ref = STRBUF_INIT;
62
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         }
75         strbuf_release(&path);
76         strbuf_release(&gitdir);
77         strbuf_release(&worktree_path);
78         strbuf_release(&head_ref);
79         return worktree;
80 }
81
82 static struct worktree *get_linked_worktree(const char *id)
83 {
84         struct worktree *worktree = NULL;
85         struct strbuf path = STRBUF_INIT;
86         struct strbuf worktree_path = STRBUF_INIT;
87         struct strbuf gitdir = STRBUF_INIT;
88         struct strbuf head_ref = STRBUF_INIT;
89
90         if (!id)
91                 die("Missing linked worktree name");
92
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 */
98                 goto done;
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
107         strbuf_reset(&path);
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         }
115
116 done:
117         strbuf_release(&path);
118         strbuf_release(&gitdir);
119         strbuf_release(&worktree_path);
120         strbuf_release(&head_ref);
121         return worktree;
122 }
123
124 struct worktree **get_worktrees(void)
125 {
126         struct worktree **list = NULL;
127         struct strbuf path = STRBUF_INIT;
128         DIR *dir;
129         struct dirent *d;
130         int counter = 0, alloc = 2;
131
132         list = xmalloc(alloc * sizeof(struct worktree *));
133
134         if ((list[counter] = get_main_worktree()))
135                 counter++;
136
137         strbuf_addf(&path, "%s/worktrees", get_git_common_dir());
138         dir = opendir(path.buf);
139         strbuf_release(&path);
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;
145
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
158 char *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)) {
172                         continue;
173                 }
174
175                 if (!strcmp(sb.buf, target)) {
176                         existing = xstrdup(worktrees[i]->path);
177                         break;
178                 }
179         }
180
181         strbuf_release(&path);
182         strbuf_release(&sb);
183         free_worktrees(worktrees);
184
185         return existing;
186 }