worktree.con commit path.c: refactor and add worktree_git_path() (2e641d5)
   1#include "cache.h"
   2#include "refs.h"
   3#include "strbuf.h"
   4#include "worktree.h"
   5#include "dir.h"
   6
   7void free_worktrees(struct worktree **worktrees)
   8{
   9        int i = 0;
  10
  11        for (i = 0; worktrees[i]; i++) {
  12                free(worktrees[i]->path);
  13                free(worktrees[i]->id);
  14                free(worktrees[i]->head_ref);
  15                free(worktrees[i]);
  16        }
  17        free (worktrees);
  18}
  19
  20/*
  21 * read 'path_to_ref' into 'ref'.  Also if is_detached is not NULL,
  22 * set is_detached to 1 (0) if the ref is detatched (is not detached).
  23 *
  24 * $GIT_COMMON_DIR/$symref (e.g. HEAD) is practically outside $GIT_DIR so
  25 * for linked worktrees, `resolve_ref_unsafe()` won't work (it uses
  26 * git_path). Parse the ref ourselves.
  27 *
  28 * return -1 if the ref is not a proper ref, 0 otherwise (success)
  29 */
  30static int parse_ref(char *path_to_ref, struct strbuf *ref, int *is_detached)
  31{
  32        if (is_detached)
  33                *is_detached = 0;
  34        if (!strbuf_readlink(ref, path_to_ref, 0)) {
  35                /* HEAD is symbolic link */
  36                if (!starts_with(ref->buf, "refs/") ||
  37                                check_refname_format(ref->buf, 0))
  38                        return -1;
  39        } else if (strbuf_read_file(ref, path_to_ref, 0) >= 0) {
  40                /* textual symref or detached */
  41                if (!starts_with(ref->buf, "ref:")) {
  42                        if (is_detached)
  43                                *is_detached = 1;
  44                } else {
  45                        strbuf_remove(ref, 0, strlen("ref:"));
  46                        strbuf_trim(ref);
  47                        if (check_refname_format(ref->buf, 0))
  48                                return -1;
  49                }
  50        } else
  51                return -1;
  52        return 0;
  53}
  54
  55/**
  56 * Add the head_sha1 and head_ref (if not detached) to the given worktree
  57 */
  58static void add_head_info(struct strbuf *head_ref, struct worktree *worktree)
  59{
  60        if (head_ref->len) {
  61                if (worktree->is_detached) {
  62                        get_sha1_hex(head_ref->buf, worktree->head_sha1);
  63                } else {
  64                        resolve_ref_unsafe(head_ref->buf, 0, worktree->head_sha1, NULL);
  65                        worktree->head_ref = strbuf_detach(head_ref, NULL);
  66                }
  67        }
  68}
  69
  70/**
  71 * get the main worktree
  72 */
  73static struct worktree *get_main_worktree(void)
  74{
  75        struct worktree *worktree = NULL;
  76        struct strbuf path = STRBUF_INIT;
  77        struct strbuf worktree_path = STRBUF_INIT;
  78        struct strbuf head_ref = STRBUF_INIT;
  79        int is_bare = 0;
  80        int is_detached = 0;
  81
  82        strbuf_addstr(&worktree_path, absolute_path(get_git_common_dir()));
  83        is_bare = !strbuf_strip_suffix(&worktree_path, "/.git");
  84        if (is_bare)
  85                strbuf_strip_suffix(&worktree_path, "/.");
  86
  87        strbuf_addf(&path, "%s/HEAD", get_git_common_dir());
  88
  89        if (parse_ref(path.buf, &head_ref, &is_detached) < 0)
  90                goto done;
  91
  92        worktree = xmalloc(sizeof(struct worktree));
  93        worktree->path = strbuf_detach(&worktree_path, NULL);
  94        worktree->id = NULL;
  95        worktree->is_bare = is_bare;
  96        worktree->head_ref = NULL;
  97        worktree->is_detached = is_detached;
  98        worktree->is_current = 0;
  99        add_head_info(&head_ref, worktree);
 100
 101done:
 102        strbuf_release(&path);
 103        strbuf_release(&worktree_path);
 104        strbuf_release(&head_ref);
 105        return worktree;
 106}
 107
 108static struct worktree *get_linked_worktree(const char *id)
 109{
 110        struct worktree *worktree = NULL;
 111        struct strbuf path = STRBUF_INIT;
 112        struct strbuf worktree_path = STRBUF_INIT;
 113        struct strbuf head_ref = STRBUF_INIT;
 114        int is_detached = 0;
 115
 116        if (!id)
 117                die("Missing linked worktree name");
 118
 119        strbuf_git_common_path(&path, "worktrees/%s/gitdir", id);
 120        if (strbuf_read_file(&worktree_path, path.buf, 0) <= 0)
 121                /* invalid gitdir file */
 122                goto done;
 123
 124        strbuf_rtrim(&worktree_path);
 125        if (!strbuf_strip_suffix(&worktree_path, "/.git")) {
 126                strbuf_reset(&worktree_path);
 127                strbuf_addstr(&worktree_path, absolute_path("."));
 128                strbuf_strip_suffix(&worktree_path, "/.");
 129        }
 130
 131        strbuf_reset(&path);
 132        strbuf_addf(&path, "%s/worktrees/%s/HEAD", get_git_common_dir(), id);
 133
 134        if (parse_ref(path.buf, &head_ref, &is_detached) < 0)
 135                goto done;
 136
 137        worktree = xmalloc(sizeof(struct worktree));
 138        worktree->path = strbuf_detach(&worktree_path, NULL);
 139        worktree->id = xstrdup(id);
 140        worktree->is_bare = 0;
 141        worktree->head_ref = NULL;
 142        worktree->is_detached = is_detached;
 143        worktree->is_current = 0;
 144        add_head_info(&head_ref, worktree);
 145
 146done:
 147        strbuf_release(&path);
 148        strbuf_release(&worktree_path);
 149        strbuf_release(&head_ref);
 150        return worktree;
 151}
 152
 153static void mark_current_worktree(struct worktree **worktrees)
 154{
 155        struct strbuf git_dir = STRBUF_INIT;
 156        struct strbuf path = STRBUF_INIT;
 157        int i;
 158
 159        strbuf_addstr(&git_dir, absolute_path(get_git_dir()));
 160        for (i = 0; worktrees[i]; i++) {
 161                struct worktree *wt = worktrees[i];
 162                strbuf_addstr(&path, absolute_path(get_worktree_git_dir(wt)));
 163                wt->is_current = !fspathcmp(git_dir.buf, path.buf);
 164                strbuf_reset(&path);
 165                if (wt->is_current)
 166                        break;
 167        }
 168        strbuf_release(&git_dir);
 169        strbuf_release(&path);
 170}
 171
 172struct worktree **get_worktrees(void)
 173{
 174        struct worktree **list = NULL;
 175        struct strbuf path = STRBUF_INIT;
 176        DIR *dir;
 177        struct dirent *d;
 178        int counter = 0, alloc = 2;
 179
 180        list = xmalloc(alloc * sizeof(struct worktree *));
 181
 182        if ((list[counter] = get_main_worktree()))
 183                counter++;
 184
 185        strbuf_addf(&path, "%s/worktrees", get_git_common_dir());
 186        dir = opendir(path.buf);
 187        strbuf_release(&path);
 188        if (dir) {
 189                while ((d = readdir(dir)) != NULL) {
 190                        struct worktree *linked = NULL;
 191                        if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
 192                                continue;
 193
 194                        if ((linked = get_linked_worktree(d->d_name))) {
 195                                ALLOC_GROW(list, counter + 1, alloc);
 196                                list[counter++] = linked;
 197                        }
 198                }
 199                closedir(dir);
 200        }
 201        ALLOC_GROW(list, counter + 1, alloc);
 202        list[counter] = NULL;
 203
 204        mark_current_worktree(list);
 205        return list;
 206}
 207
 208const char *get_worktree_git_dir(const struct worktree *wt)
 209{
 210        if (!wt)
 211                return get_git_dir();
 212        else if (!wt->id)
 213                return get_git_common_dir();
 214        else
 215                return git_common_path("worktrees/%s", wt->id);
 216}
 217
 218const struct worktree *find_shared_symref(const char *symref,
 219                                          const char *target)
 220{
 221        const struct worktree *existing = NULL;
 222        struct strbuf path = STRBUF_INIT;
 223        struct strbuf sb = STRBUF_INIT;
 224        static struct worktree **worktrees;
 225        int i = 0;
 226
 227        if (worktrees)
 228                free_worktrees(worktrees);
 229        worktrees = get_worktrees();
 230
 231        for (i = 0; worktrees[i]; i++) {
 232                strbuf_reset(&path);
 233                strbuf_reset(&sb);
 234                strbuf_addf(&path, "%s/%s",
 235                            get_worktree_git_dir(worktrees[i]),
 236                            symref);
 237
 238                if (parse_ref(path.buf, &sb, NULL)) {
 239                        continue;
 240                }
 241
 242                if (!strcmp(sb.buf, target)) {
 243                        existing = worktrees[i];
 244                        break;
 245                }
 246        }
 247
 248        strbuf_release(&path);
 249        strbuf_release(&sb);
 250
 251        return existing;
 252}