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