worktree: refactor find_linked_symref function
[git] / worktree.c
1 #include "cache.h"
2 #include "refs.h"
3 #include "strbuf.h"
4 #include "worktree.h"
5
6 /*
7  * read 'path_to_ref' into 'ref'.  Also if is_detached is not NULL,
8  * set is_detached to 1 (0) if the ref is detatched (is not detached).
9  *
10  * $GIT_COMMON_DIR/$symref (e.g. HEAD) is practically outside $GIT_DIR so
11  * for linked worktrees, `resolve_ref_unsafe()` won't work (it uses
12  * git_path). Parse the ref ourselves.
13  *
14  * return -1 if the ref is not a proper ref, 0 otherwise (success)
15  */
16 static int parse_ref(char *path_to_ref, struct strbuf *ref, int *is_detached)
17 {
18         if (is_detached)
19                 *is_detached = 0;
20         if (!strbuf_readlink(ref, path_to_ref, 0)) {
21                 /* HEAD is symbolic link */
22                 if (!starts_with(ref->buf, "refs/") ||
23                                 check_refname_format(ref->buf, 0))
24                         return -1;
25         } else if (strbuf_read_file(ref, path_to_ref, 0) >= 0) {
26                 /* textual symref or detached */
27                 if (!starts_with(ref->buf, "ref:")) {
28                         if (is_detached)
29                                 *is_detached = 1;
30                 } else {
31                         strbuf_remove(ref, 0, strlen("ref:"));
32                         strbuf_trim(ref);
33                         if (check_refname_format(ref->buf, 0))
34                                 return -1;
35                 }
36         } else
37                 return -1;
38         return 0;
39 }
40
41 static char *find_main_symref(const char *symref, const char *branch)
42 {
43         struct strbuf sb = STRBUF_INIT;
44         struct strbuf path = STRBUF_INIT;
45         struct strbuf gitdir = STRBUF_INIT;
46         char *existing = NULL;
47
48         strbuf_addf(&path, "%s/%s", get_git_common_dir(), symref);
49         if (parse_ref(path.buf, &sb, NULL) < 0)
50                 goto done;
51         if (strcmp(sb.buf, branch))
52                 goto done;
53         strbuf_addstr(&gitdir, get_git_common_dir());
54         strbuf_strip_suffix(&gitdir, ".git");
55         existing = strbuf_detach(&gitdir, NULL);
56 done:
57         strbuf_release(&path);
58         strbuf_release(&sb);
59         strbuf_release(&gitdir);
60
61         return existing;
62 }
63
64 static char *find_linked_symref(const char *symref, const char *branch,
65                                 const char *id)
66 {
67         struct strbuf sb = STRBUF_INIT;
68         struct strbuf path = STRBUF_INIT;
69         struct strbuf gitdir = STRBUF_INIT;
70         char *existing = NULL;
71
72         if (!id)
73                 die("Missing linked worktree name");
74
75         strbuf_addf(&path, "%s/worktrees/%s/%s", get_git_common_dir(), id, symref);
76
77         if (parse_ref(path.buf, &sb, NULL) < 0)
78                 goto done;
79         if (strcmp(sb.buf, branch))
80                 goto done;
81         strbuf_reset(&path);
82         strbuf_addf(&path, "%s/worktrees/%s/gitdir", get_git_common_dir(), id);
83         if (strbuf_read_file(&gitdir, path.buf, 0) <= 0)
84                 goto done;
85         strbuf_rtrim(&gitdir);
86         strbuf_strip_suffix(&gitdir, ".git");
87
88         existing = strbuf_detach(&gitdir, NULL);
89 done:
90         strbuf_release(&path);
91         strbuf_release(&sb);
92         strbuf_release(&gitdir);
93
94         return existing;
95 }
96
97 char *find_shared_symref(const char *symref, const char *target)
98 {
99         struct strbuf path = STRBUF_INIT;
100         DIR *dir;
101         struct dirent *d;
102         char *existing;
103
104         if ((existing = find_main_symref(symref, target)))
105                 return existing;
106
107         strbuf_addf(&path, "%s/worktrees", get_git_common_dir());
108         dir = opendir(path.buf);
109         strbuf_release(&path);
110         if (!dir)
111                 return NULL;
112
113         while ((d = readdir(dir)) != NULL) {
114                 if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
115                         continue;
116                 existing = find_linked_symref(symref, target, d->d_name);
117                 if (existing)
118                         goto done;
119         }
120 done:
121         closedir(dir);
122
123         return existing;
124 }