c2e6db0a2c5120509b958014ccf4f882df4ddf29
   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]);
  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 */
  27static 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 */
  55static 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
  82static 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
 116done:
 117        strbuf_release(&path);
 118        strbuf_release(&gitdir);
 119        strbuf_release(&worktree_path);
 120        strbuf_release(&head_ref);
 121        return worktree;
 122}
 123
 124struct 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
 158char *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}