packaging: Add contrib installation
[platform/upstream/git.git] / worktree.c
index e28ffbe..f84ceae 100644 (file)
@@ -21,7 +21,7 @@ void free_worktrees(struct worktree **worktrees)
 }
 
 /**
- * Update head_sha1, head_ref and is_detached of the given worktree
+ * Update head_oid, head_ref and is_detached of the given worktree
  */
 static void add_head_info(struct worktree *wt)
 {
@@ -30,8 +30,8 @@ static void add_head_info(struct worktree *wt)
 
        target = refs_resolve_ref_unsafe(get_worktree_ref_store(wt),
                                         "HEAD",
-                                        RESOLVE_REF_READING,
-                                        wt->head_sha1, &flags);
+                                        0,
+                                        &wt->head_oid, &flags);
        if (!target)
                return;
 
@@ -47,24 +47,23 @@ static void add_head_info(struct worktree *wt)
 static struct worktree *get_main_worktree(void)
 {
        struct worktree *worktree = NULL;
-       struct strbuf path = STRBUF_INIT;
        struct strbuf worktree_path = STRBUF_INIT;
-       int is_bare = 0;
-
-       strbuf_add_absolute_path(&worktree_path, get_git_common_dir());
-       is_bare = !strbuf_strip_suffix(&worktree_path, "/.git");
-       if (is_bare)
-               strbuf_strip_suffix(&worktree_path, "/.");
 
-       strbuf_addf(&path, "%s/HEAD", get_git_common_dir());
+       strbuf_add_real_path(&worktree_path, get_git_common_dir());
+       strbuf_strip_suffix(&worktree_path, "/.git");
 
        worktree = xcalloc(1, sizeof(*worktree));
        worktree->path = strbuf_detach(&worktree_path, NULL);
-       worktree->is_bare = is_bare;
+       /*
+        * NEEDSWORK: If this function is called from a secondary worktree and
+        * config.worktree is present, is_bare_repository_cfg will reflect the
+        * contents of config.worktree, not the contents of the main worktree.
+        * This means that worktree->is_bare may be set to 0 even if the main
+        * worktree is configured to be bare.
+        */
+       worktree->is_bare = (is_bare_repository_cfg == 1) ||
+               is_bare_repository();
        add_head_info(worktree);
-
-       strbuf_release(&path);
-       strbuf_release(&worktree_path);
        return worktree;
 }
 
@@ -81,16 +80,8 @@ static struct worktree *get_linked_worktree(const char *id)
        if (strbuf_read_file(&worktree_path, path.buf, 0) <= 0)
                /* invalid gitdir file */
                goto done;
-
        strbuf_rtrim(&worktree_path);
-       if (!strbuf_strip_suffix(&worktree_path, "/.git")) {
-               strbuf_reset(&worktree_path);
-               strbuf_add_absolute_path(&worktree_path, ".");
-               strbuf_strip_suffix(&worktree_path, "/.");
-       }
-
-       strbuf_reset(&path);
-       strbuf_addf(&path, "%s/worktrees/%s/HEAD", get_git_common_dir(), id);
+       strbuf_strip_suffix(&worktree_path, "/.git");
 
        worktree = xcalloc(1, sizeof(*worktree));
        worktree->path = strbuf_detach(&worktree_path, NULL);
@@ -120,14 +111,7 @@ static void mark_current_worktree(struct worktree **worktrees)
        free(git_dir);
 }
 
-static int compare_worktree(const void *a_, const void *b_)
-{
-       const struct worktree *const *a = a_;
-       const struct worktree *const *b = b_;
-       return fspathcmp((*a)->path, (*b)->path);
-}
-
-struct worktree **get_worktrees(unsigned flags)
+struct worktree **get_worktrees(void)
 {
        struct worktree **list = NULL;
        struct strbuf path = STRBUF_INIT;
@@ -158,13 +142,6 @@ struct worktree **get_worktrees(unsigned flags)
        ALLOC_GROW(list, counter + 1, alloc);
        list[counter] = NULL;
 
-       if (flags & GWT_SORT_LINKED)
-               /*
-                * don't sort the first item (main worktree), which will
-                * always be the first
-                */
-               QSORT(list + 1, counter - 1, compare_worktree);
-
        mark_current_worktree(list);
        return list;
 }
@@ -209,7 +186,6 @@ struct worktree *find_worktree(struct worktree **list,
                               const char *arg)
 {
        struct worktree *wt;
-       char *path;
        char *to_free = NULL;
 
        if ((wt = find_worktree_by_suffix(list, arg)))
@@ -217,12 +193,27 @@ struct worktree *find_worktree(struct worktree **list,
 
        if (prefix)
                arg = to_free = prefix_filename(prefix, arg);
-       path = real_pathdup(arg, 1);
-       for (; *list; list++)
-               if (!fspathcmp(path, real_path((*list)->path)))
+       wt = find_worktree_by_path(list, arg);
+       free(to_free);
+       return wt;
+}
+
+struct worktree *find_worktree_by_path(struct worktree **list, const char *p)
+{
+       struct strbuf wt_path = STRBUF_INIT;
+       char *path = real_pathdup(p, 0);
+
+       if (!path)
+               return NULL;
+       for (; *list; list++) {
+               if (!strbuf_realpath(&wt_path, (*list)->path, 0))
+                       continue;
+
+               if (!fspathcmp(path, wt_path.buf))
                        break;
+       }
        free(path);
-       free(to_free);
+       strbuf_release(&wt_path);
        return *list;
 }
 
@@ -231,7 +222,7 @@ int is_main_worktree(const struct worktree *wt)
        return !wt->id;
 }
 
-const char *is_worktree_locked(struct worktree *wt)
+const char *worktree_lock_reason(struct worktree *wt)
 {
        assert(!is_main_worktree(wt));
 
@@ -254,6 +245,105 @@ const char *is_worktree_locked(struct worktree *wt)
        return wt->lock_reason;
 }
 
+/* convenient wrapper to deal with NULL strbuf */
+static void strbuf_addf_gently(struct strbuf *buf, const char *fmt, ...)
+{
+       va_list params;
+
+       if (!buf)
+               return;
+
+       va_start(params, fmt);
+       strbuf_vaddf(buf, fmt, params);
+       va_end(params);
+}
+
+int validate_worktree(const struct worktree *wt, struct strbuf *errmsg,
+                     unsigned flags)
+{
+       struct strbuf wt_path = STRBUF_INIT;
+       struct strbuf realpath = STRBUF_INIT;
+       char *path = NULL;
+       int err, ret = -1;
+
+       strbuf_addf(&wt_path, "%s/.git", wt->path);
+
+       if (is_main_worktree(wt)) {
+               if (is_directory(wt_path.buf)) {
+                       ret = 0;
+                       goto done;
+               }
+               /*
+                * Main worktree using .git file to point to the
+                * repository would make it impossible to know where
+                * the actual worktree is if this function is executed
+                * from another worktree. No .git file support for now.
+                */
+               strbuf_addf_gently(errmsg,
+                                  _("'%s' at main working tree is not the repository directory"),
+                                  wt_path.buf);
+               goto done;
+       }
+
+       /*
+        * Make sure "gitdir" file points to a real .git file and that
+        * file points back here.
+        */
+       if (!is_absolute_path(wt->path)) {
+               strbuf_addf_gently(errmsg,
+                                  _("'%s' file does not contain absolute path to the working tree location"),
+                                  git_common_path("worktrees/%s/gitdir", wt->id));
+               goto done;
+       }
+
+       if (flags & WT_VALIDATE_WORKTREE_MISSING_OK &&
+           !file_exists(wt->path)) {
+               ret = 0;
+               goto done;
+       }
+
+       if (!file_exists(wt_path.buf)) {
+               strbuf_addf_gently(errmsg, _("'%s' does not exist"), wt_path.buf);
+               goto done;
+       }
+
+       path = xstrdup_or_null(read_gitfile_gently(wt_path.buf, &err));
+       if (!path) {
+               strbuf_addf_gently(errmsg, _("'%s' is not a .git file, error code %d"),
+                                  wt_path.buf, err);
+               goto done;
+       }
+
+       strbuf_realpath(&realpath, git_common_path("worktrees/%s", wt->id), 1);
+       ret = fspathcmp(path, realpath.buf);
+
+       if (ret)
+               strbuf_addf_gently(errmsg, _("'%s' does not point back to '%s'"),
+                                  wt->path, git_common_path("worktrees/%s", wt->id));
+done:
+       free(path);
+       strbuf_release(&wt_path);
+       strbuf_release(&realpath);
+       return ret;
+}
+
+void update_worktree_location(struct worktree *wt, const char *path_)
+{
+       struct strbuf path = STRBUF_INIT;
+
+       if (is_main_worktree(wt))
+               BUG("can't relocate main worktree");
+
+       strbuf_realpath(&path, path_, 1);
+       if (fspathcmp(wt->path, path.buf)) {
+               write_file(git_common_path("worktrees/%s/gitdir", wt->id),
+                          "%s/.git", path.buf);
+               free(wt->path);
+               wt->path = strbuf_detach(&path, NULL);
+       }
+       strbuf_release(&path);
+}
+
 int is_worktree_being_rebased(const struct worktree *wt,
                              const char *target)
 {
@@ -262,13 +352,12 @@ int is_worktree_being_rebased(const struct worktree *wt,
 
        memset(&state, 0, sizeof(state));
        found_rebase = wt_status_check_rebase(wt, &state) &&
-               ((state.rebase_in_progress ||
-                 state.rebase_interactive_in_progress) &&
-                state.branch &&
-                starts_with(target, "refs/heads/") &&
-                !strcmp(state.branch, target + strlen("refs/heads/")));
-       free(state.branch);
-       free(state.onto);
+                      (state.rebase_in_progress ||
+                       state.rebase_interactive_in_progress) &&
+                      state.branch &&
+                      skip_prefix(target, "refs/heads/", &target) &&
+                      !strcmp(state.branch, target);
+       wt_status_state_free_buffers(&state);
        return found_rebase;
 }
 
@@ -276,15 +365,15 @@ int is_worktree_being_bisected(const struct worktree *wt,
                               const char *target)
 {
        struct wt_status_state state;
-       int found_rebase;
+       int found_bisect;
 
        memset(&state, 0, sizeof(state));
-       found_rebase = wt_status_check_bisect(wt, &state) &&
-               state.branch &&
-               starts_with(target, "refs/heads/") &&
-               !strcmp(state.branch, target + strlen("refs/heads/"));
-       free(state.branch);
-       return found_rebase;
+       found_bisect = wt_status_check_bisect(wt, &state) &&
+                      state.branch &&
+                      skip_prefix(target, "refs/heads/", &target) &&
+                      !strcmp(state.branch, target);
+       wt_status_state_free_buffers(&state);
+       return found_bisect;
 }
 
 /*
@@ -302,12 +391,11 @@ const struct worktree *find_shared_symref(const char *symref,
 
        if (worktrees)
                free_worktrees(worktrees);
-       worktrees = get_worktrees(0);
+       worktrees = get_worktrees();
 
        for (i = 0; worktrees[i]; i++) {
                struct worktree *wt = worktrees[i];
                const char *symref_target;
-               unsigned char sha1[20];
                struct ref_store *refs;
                int flags;
 
@@ -327,8 +415,9 @@ const struct worktree *find_shared_symref(const char *symref,
 
                refs = get_worktree_ref_store(wt);
                symref_target = refs_resolve_ref_unsafe(refs, symref, 0,
-                                                       sha1, &flags);
-               if ((flags & REF_ISSYMREF) && !strcmp(symref_target, target)) {
+                                                       NULL, &flags);
+               if ((flags & REF_ISSYMREF) &&
+                   symref_target && !strcmp(symref_target, target)) {
                        existing = wt;
                        break;
                }
@@ -340,11 +429,11 @@ const struct worktree *find_shared_symref(const char *symref,
 int submodule_uses_worktrees(const char *path)
 {
        char *submodule_gitdir;
-       struct strbuf sb = STRBUF_INIT;
+       struct strbuf sb = STRBUF_INIT, err = STRBUF_INIT;
        DIR *dir;
        struct dirent *d;
        int ret = 0;
-       struct repository_format format;
+       struct repository_format format = REPOSITORY_FORMAT_INIT;
 
        submodule_gitdir = git_pathdup_submodule(path, "%s", "");
        if (!submodule_gitdir)
@@ -354,16 +443,16 @@ int submodule_uses_worktrees(const char *path)
        get_common_dir_noenv(&sb, submodule_gitdir);
        free(submodule_gitdir);
 
-       /*
-        * The check below is only known to be good for repository format
-        * version 0 at the time of writing this code.
-        */
        strbuf_addstr(&sb, "/config");
        read_repository_format(&format, sb.buf);
-       if (format.version != 0) {
+       if (verify_repository_format(&format, &err)) {
+               strbuf_release(&err);
                strbuf_release(&sb);
+               clear_repository_format(&format);
                return 1;
        }
+       clear_repository_format(&format);
+       strbuf_release(&err);
 
        /* Replace config by worktrees. */
        strbuf_setlen(&sb, sb.len - strlen("config"));
@@ -386,3 +475,228 @@ int submodule_uses_worktrees(const char *path)
        closedir(dir);
        return ret;
 }
+
+int parse_worktree_ref(const char *worktree_ref, const char **name,
+                      int *name_length, const char **ref)
+{
+       if (skip_prefix(worktree_ref, "main-worktree/", &worktree_ref)) {
+               if (!*worktree_ref)
+                       return -1;
+               if (name)
+                       *name = NULL;
+               if (name_length)
+                       *name_length = 0;
+               if (ref)
+                       *ref = worktree_ref;
+               return 0;
+       }
+       if (skip_prefix(worktree_ref, "worktrees/", &worktree_ref)) {
+               const char *slash = strchr(worktree_ref, '/');
+
+               if (!slash || slash == worktree_ref || !slash[1])
+                       return -1;
+               if (name)
+                       *name = worktree_ref;
+               if (name_length)
+                       *name_length = slash - worktree_ref;
+               if (ref)
+                       *ref = slash + 1;
+               return 0;
+       }
+       return -1;
+}
+
+void strbuf_worktree_ref(const struct worktree *wt,
+                        struct strbuf *sb,
+                        const char *refname)
+{
+       switch (ref_type(refname)) {
+       case REF_TYPE_PSEUDOREF:
+       case REF_TYPE_PER_WORKTREE:
+               if (wt && !wt->is_current) {
+                       if (is_main_worktree(wt))
+                               strbuf_addstr(sb, "main-worktree/");
+                       else
+                               strbuf_addf(sb, "worktrees/%s/", wt->id);
+               }
+               break;
+
+       case REF_TYPE_MAIN_PSEUDOREF:
+       case REF_TYPE_OTHER_PSEUDOREF:
+               break;
+
+       case REF_TYPE_NORMAL:
+               /*
+                * For shared refs, don't prefix worktrees/ or
+                * main-worktree/. It's not necessary and
+                * files-backend.c can't handle it anyway.
+                */
+               break;
+       }
+       strbuf_addstr(sb, refname);
+}
+
+int other_head_refs(each_ref_fn fn, void *cb_data)
+{
+       struct worktree **worktrees, **p;
+       struct strbuf refname = STRBUF_INIT;
+       int ret = 0;
+
+       worktrees = get_worktrees();
+       for (p = worktrees; *p; p++) {
+               struct worktree *wt = *p;
+               struct object_id oid;
+               int flag;
+
+               if (wt->is_current)
+                       continue;
+
+               strbuf_reset(&refname);
+               strbuf_worktree_ref(wt, &refname, "HEAD");
+               if (!refs_read_ref_full(get_main_ref_store(the_repository),
+                                       refname.buf,
+                                       RESOLVE_REF_READING,
+                                       &oid, &flag))
+                       ret = fn(refname.buf, &oid, flag, cb_data);
+               if (ret)
+                       break;
+       }
+       free_worktrees(worktrees);
+       strbuf_release(&refname);
+       return ret;
+}
+
+/*
+ * Repair worktree's /path/to/worktree/.git file if missing, corrupt, or not
+ * pointing at <repo>/worktrees/<id>.
+ */
+static void repair_gitfile(struct worktree *wt,
+                          worktree_repair_fn fn, void *cb_data)
+{
+       struct strbuf dotgit = STRBUF_INIT;
+       struct strbuf repo = STRBUF_INIT;
+       char *backlink;
+       const char *repair = NULL;
+       int err;
+
+       /* missing worktree can't be repaired */
+       if (!file_exists(wt->path))
+               return;
+
+       if (!is_directory(wt->path)) {
+               fn(1, wt->path, _("not a directory"), cb_data);
+               return;
+       }
+
+       strbuf_realpath(&repo, git_common_path("worktrees/%s", wt->id), 1);
+       strbuf_addf(&dotgit, "%s/.git", wt->path);
+       backlink = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err));
+
+       if (err == READ_GITFILE_ERR_NOT_A_FILE)
+               fn(1, wt->path, _(".git is not a file"), cb_data);
+       else if (err)
+               repair = _(".git file broken");
+       else if (fspathcmp(backlink, repo.buf))
+               repair = _(".git file incorrect");
+
+       if (repair) {
+               fn(0, wt->path, repair, cb_data);
+               write_file(dotgit.buf, "gitdir: %s", repo.buf);
+       }
+
+       free(backlink);
+       strbuf_release(&repo);
+       strbuf_release(&dotgit);
+}
+
+static void repair_noop(int iserr, const char *path, const char *msg,
+                       void *cb_data)
+{
+       /* nothing */
+}
+
+void repair_worktrees(worktree_repair_fn fn, void *cb_data)
+{
+       struct worktree **worktrees = get_worktrees();
+       struct worktree **wt = worktrees + 1; /* +1 skips main worktree */
+
+       if (!fn)
+               fn = repair_noop;
+       for (; *wt; wt++)
+               repair_gitfile(*wt, fn, cb_data);
+       free_worktrees(worktrees);
+}
+
+static int is_main_worktree_path(const char *path)
+{
+       struct strbuf target = STRBUF_INIT;
+       struct strbuf maindir = STRBUF_INIT;
+       int cmp;
+
+       strbuf_add_real_path(&target, path);
+       strbuf_strip_suffix(&target, "/.git");
+       strbuf_add_real_path(&maindir, get_git_common_dir());
+       strbuf_strip_suffix(&maindir, "/.git");
+       cmp = fspathcmp(maindir.buf, target.buf);
+
+       strbuf_release(&maindir);
+       strbuf_release(&target);
+       return !cmp;
+}
+
+/*
+ * Repair <repo>/worktrees/<id>/gitdir if missing, corrupt, or not pointing at
+ * the worktree's path.
+ */
+void repair_worktree_at_path(const char *path,
+                            worktree_repair_fn fn, void *cb_data)
+{
+       struct strbuf dotgit = STRBUF_INIT;
+       struct strbuf realdotgit = STRBUF_INIT;
+       struct strbuf gitdir = STRBUF_INIT;
+       struct strbuf olddotgit = STRBUF_INIT;
+       char *backlink = NULL;
+       const char *repair = NULL;
+       int err;
+
+       if (!fn)
+               fn = repair_noop;
+
+       if (is_main_worktree_path(path))
+               goto done;
+
+       strbuf_addf(&dotgit, "%s/.git", path);
+       if (!strbuf_realpath(&realdotgit, dotgit.buf, 0)) {
+               fn(1, path, _("not a valid path"), cb_data);
+               goto done;
+       }
+
+       backlink = xstrdup_or_null(read_gitfile_gently(realdotgit.buf, &err));
+       if (err == READ_GITFILE_ERR_NOT_A_FILE) {
+               fn(1, realdotgit.buf, _("unable to locate repository; .git is not a file"), cb_data);
+               goto done;
+       } else if (err) {
+               fn(1, realdotgit.buf, _("unable to locate repository; .git file broken"), cb_data);
+               goto done;
+       }
+
+       strbuf_addf(&gitdir, "%s/gitdir", backlink);
+       if (strbuf_read_file(&olddotgit, gitdir.buf, 0) < 0)
+               repair = _("gitdir unreadable");
+       else {
+               strbuf_rtrim(&olddotgit);
+               if (fspathcmp(olddotgit.buf, realdotgit.buf))
+                       repair = _("gitdir incorrect");
+       }
+
+       if (repair) {
+               fn(0, gitdir.buf, repair, cb_data);
+               write_file(gitdir.buf, "%s", realdotgit.buf);
+       }
+done:
+       free(backlink);
+       strbuf_release(&olddotgit);
+       strbuf_release(&gitdir);
+       strbuf_release(&realdotgit);
+       strbuf_release(&dotgit);
+}