worktree: add top-level worktree.c
[git] / worktree.c
1 #include "cache.h"
2 #include "refs.h"
3 #include "strbuf.h"
4 #include "worktree.h"
5
6 static char *find_linked_symref(const char *symref, const char *branch,
7                                 const char *id)
8 {
9         struct strbuf sb = STRBUF_INIT;
10         struct strbuf path = STRBUF_INIT;
11         struct strbuf gitdir = STRBUF_INIT;
12         char *existing = NULL;
13
14         /*
15          * $GIT_COMMON_DIR/$symref (e.g. HEAD) is practically outside
16          * $GIT_DIR so resolve_ref_unsafe() won't work (it uses
17          * git_path). Parse the ref ourselves.
18          */
19         if (id)
20                 strbuf_addf(&path, "%s/worktrees/%s/%s", get_git_common_dir(), id, symref);
21         else
22                 strbuf_addf(&path, "%s/%s", get_git_common_dir(), symref);
23
24         if (!strbuf_readlink(&sb, path.buf, 0)) {
25                 if (!starts_with(sb.buf, "refs/") ||
26                     check_refname_format(sb.buf, 0))
27                         goto done;
28         } else if (strbuf_read_file(&sb, path.buf, 0) >= 0 &&
29             starts_with(sb.buf, "ref:")) {
30                 strbuf_remove(&sb, 0, strlen("ref:"));
31                 strbuf_trim(&sb);
32         } else
33                 goto done;
34         if (strcmp(sb.buf, branch))
35                 goto done;
36         if (id) {
37                 strbuf_reset(&path);
38                 strbuf_addf(&path, "%s/worktrees/%s/gitdir", get_git_common_dir(), id);
39                 if (strbuf_read_file(&gitdir, path.buf, 0) <= 0)
40                         goto done;
41                 strbuf_rtrim(&gitdir);
42         } else
43                 strbuf_addstr(&gitdir, get_git_common_dir());
44         strbuf_strip_suffix(&gitdir, ".git");
45
46         existing = strbuf_detach(&gitdir, NULL);
47 done:
48         strbuf_release(&path);
49         strbuf_release(&sb);
50         strbuf_release(&gitdir);
51
52         return existing;
53 }
54
55 char *find_shared_symref(const char *symref, const char *target)
56 {
57         struct strbuf path = STRBUF_INIT;
58         DIR *dir;
59         struct dirent *d;
60         char *existing;
61
62         if ((existing = find_linked_symref(symref, target, NULL)))
63                 return existing;
64
65         strbuf_addf(&path, "%s/worktrees", get_git_common_dir());
66         dir = opendir(path.buf);
67         strbuf_release(&path);
68         if (!dir)
69                 return NULL;
70
71         while ((d = readdir(dir)) != NULL) {
72                 if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
73                         continue;
74                 existing = find_linked_symref(symref, target, d->d_name);
75                 if (existing)
76                         goto done;
77         }
78 done:
79         closedir(dir);
80
81         return existing;
82 }