packaging: Add contrib installation
[platform/upstream/git.git] / worktree.c
index eba4fd3..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)
 {
@@ -49,10 +49,8 @@ static struct worktree *get_main_worktree(void)
        struct worktree *worktree = NULL;
        struct strbuf worktree_path = STRBUF_INIT;
 
-       strbuf_add_absolute_path(&worktree_path, get_git_common_dir());
-       strbuf_strip_suffix(&worktree_path, "/.");
-       if (!strbuf_strip_suffix(&worktree_path, "/.git"))
-               strbuf_strip_suffix(&worktree_path, "/.");
+       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);
@@ -66,8 +64,6 @@ static struct worktree *get_main_worktree(void)
        worktree->is_bare = (is_bare_repository_cfg == 1) ||
                is_bare_repository();
        add_head_info(worktree);
-
-       strbuf_release(&worktree_path);
        return worktree;
 }
 
@@ -84,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);
@@ -123,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;
@@ -161,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;
 }
@@ -226,17 +200,20 @@ struct worktree *find_worktree(struct worktree **list,
 
 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++) {
-               const char *wt_path = real_path_if_valid((*list)->path);
+               if (!strbuf_realpath(&wt_path, (*list)->path, 0))
+                       continue;
 
-               if (wt_path && !fspathcmp(path, wt_path))
+               if (!fspathcmp(path, wt_path.buf))
                        break;
        }
        free(path);
+       strbuf_release(&wt_path);
        return *list;
 }
 
@@ -285,6 +262,7 @@ 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;
 
@@ -336,7 +314,8 @@ int validate_worktree(const struct worktree *wt, struct strbuf *errmsg,
                goto done;
        }
 
-       ret = fspathcmp(path, real_path(git_common_path("worktrees/%s", wt->id)));
+       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'"),
@@ -344,6 +323,7 @@ int validate_worktree(const struct worktree *wt, struct strbuf *errmsg,
 done:
        free(path);
        strbuf_release(&wt_path);
+       strbuf_release(&realpath);
        return ret;
 }
 
@@ -372,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;
 }
 
@@ -386,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;
 }
 
 /*
@@ -412,7 +391,7 @@ 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];
@@ -450,7 +429,7 @@ 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;
@@ -464,18 +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"));
@@ -559,21 +536,13 @@ void strbuf_worktree_ref(const struct worktree *wt,
        strbuf_addstr(sb, refname);
 }
 
-const char *worktree_ref(const struct worktree *wt, const char *refname)
-{
-       static struct strbuf sb = STRBUF_INIT;
-
-       strbuf_reset(&sb);
-       strbuf_worktree_ref(wt, &sb, refname);
-       return sb.buf;
-}
-
 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(0);
+       worktrees = get_worktrees();
        for (p = worktrees; *p; p++) {
                struct worktree *wt = *p;
                struct object_id oid;
@@ -582,14 +551,152 @@ int other_head_refs(each_ref_fn fn, void *cb_data)
                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),
-                                       worktree_ref(wt, "HEAD"),
+                                       refname.buf,
                                        RESOLVE_REF_READING,
                                        &oid, &flag))
-                       ret = fn(worktree_ref(wt, "HEAD"), &oid, flag, cb_data);
+                       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);
+}