360ba417e828d92d7473f6dff8b49bb61a42440a
   1#include "cache.h"
   2#include "refs.h"
   3#include "strbuf.h"
   4#include "worktree.h"
   5
   6void 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]->id);
  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 */
  29static 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 */
  57static 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 */
  72static 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 head_ref = STRBUF_INIT;
  78        int is_bare = 0;
  79        int is_detached = 0;
  80
  81        strbuf_addstr(&worktree_path, absolute_path(get_git_common_dir()));
  82        is_bare = !strbuf_strip_suffix(&worktree_path, "/.git");
  83        if (is_bare)
  84                strbuf_strip_suffix(&worktree_path, "/.");
  85
  86        strbuf_addf(&path, "%s/HEAD", get_git_common_dir());
  87
  88        if (parse_ref(path.buf, &head_ref, &is_detached) < 0)
  89                goto done;
  90
  91        worktree = xmalloc(sizeof(struct worktree));
  92        worktree->path = strbuf_detach(&worktree_path, NULL);
  93        worktree->id = NULL;
  94        worktree->is_bare = is_bare;
  95        worktree->head_ref = NULL;
  96        worktree->is_detached = is_detached;
  97        add_head_info(&head_ref, worktree);
  98
  99done:
 100        strbuf_release(&path);
 101        strbuf_release(&worktree_path);
 102        strbuf_release(&head_ref);
 103        return worktree;
 104}
 105
 106static struct worktree *get_linked_worktree(const char *id)
 107{
 108        struct worktree *worktree = NULL;
 109        struct strbuf path = STRBUF_INIT;
 110        struct strbuf worktree_path = STRBUF_INIT;
 111        struct strbuf head_ref = STRBUF_INIT;
 112        int is_detached = 0;
 113
 114        if (!id)
 115                die("Missing linked worktree name");
 116
 117        strbuf_git_common_path(&path, "worktrees/%s/gitdir", id);
 118        if (strbuf_read_file(&worktree_path, path.buf, 0) <= 0)
 119                /* invalid gitdir file */
 120                goto done;
 121
 122        strbuf_rtrim(&worktree_path);
 123        if (!strbuf_strip_suffix(&worktree_path, "/.git")) {
 124                strbuf_reset(&worktree_path);
 125                strbuf_addstr(&worktree_path, absolute_path("."));
 126                strbuf_strip_suffix(&worktree_path, "/.");
 127        }
 128
 129        strbuf_reset(&path);
 130        strbuf_addf(&path, "%s/worktrees/%s/HEAD", get_git_common_dir(), id);
 131
 132        if (parse_ref(path.buf, &head_ref, &is_detached) < 0)
 133                goto done;
 134
 135        worktree = xmalloc(sizeof(struct worktree));
 136        worktree->path = strbuf_detach(&worktree_path, NULL);
 137        worktree->id = xstrdup(id);
 138        worktree->is_bare = 0;
 139        worktree->head_ref = NULL;
 140        worktree->is_detached = is_detached;
 141        add_head_info(&head_ref, worktree);
 142
 143done:
 144        strbuf_release(&path);
 145        strbuf_release(&worktree_path);
 146        strbuf_release(&head_ref);
 147        return worktree;
 148}
 149
 150struct worktree **get_worktrees(void)
 151{
 152        struct worktree **list = NULL;
 153        struct strbuf path = STRBUF_INIT;
 154        DIR *dir;
 155        struct dirent *d;
 156        int counter = 0, alloc = 2;
 157
 158        list = xmalloc(alloc * sizeof(struct worktree *));
 159
 160        if ((list[counter] = get_main_worktree()))
 161                counter++;
 162
 163        strbuf_addf(&path, "%s/worktrees", get_git_common_dir());
 164        dir = opendir(path.buf);
 165        strbuf_release(&path);
 166        if (dir) {
 167                while ((d = readdir(dir)) != NULL) {
 168                        struct worktree *linked = NULL;
 169                        if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
 170                                continue;
 171
 172                        if ((linked = get_linked_worktree(d->d_name))) {
 173                                ALLOC_GROW(list, counter + 1, alloc);
 174                                list[counter++] = linked;
 175                        }
 176                }
 177                closedir(dir);
 178        }
 179        ALLOC_GROW(list, counter + 1, alloc);
 180        list[counter] = NULL;
 181        return list;
 182}
 183
 184const char *get_worktree_git_dir(const struct worktree *wt)
 185{
 186        if (!wt)
 187                return get_git_dir();
 188        else if (!wt->id)
 189                return get_git_common_dir();
 190        else
 191                return git_common_path("worktrees/%s", wt->id);
 192}
 193
 194const struct worktree *find_shared_symref(const char *symref,
 195                                          const char *target)
 196{
 197        const struct worktree *existing = NULL;
 198        struct strbuf path = STRBUF_INIT;
 199        struct strbuf sb = STRBUF_INIT;
 200        static struct worktree **worktrees;
 201        int i = 0;
 202
 203        if (worktrees)
 204                free_worktrees(worktrees);
 205        worktrees = get_worktrees();
 206
 207        for (i = 0; worktrees[i]; i++) {
 208                strbuf_reset(&path);
 209                strbuf_reset(&sb);
 210                strbuf_addf(&path, "%s/%s",
 211                            get_worktree_git_dir(worktrees[i]),
 212                            symref);
 213
 214                if (parse_ref(path.buf, &sb, NULL)) {
 215                        continue;
 216                }
 217
 218                if (!strcmp(sb.buf, target)) {
 219                        existing = worktrees[i];
 220                        break;
 221                }
 222        }
 223
 224        strbuf_release(&path);
 225        strbuf_release(&sb);
 226
 227        return existing;
 228}