Git 2.23
[gitweb.git] / builtin / worktree.c
index 3eb2f89b0f181909a16189de725d50c3bc351e4c..a5bb02b2076a27a78947fda25c1e16dafd637622 100644 (file)
@@ -9,6 +9,7 @@
 #include "refs.h"
 #include "run-command.h"
 #include "sigchain.h"
+#include "submodule.h"
 #include "refs.h"
 #include "utf8.h"
 #include "worktree.h"
@@ -62,6 +63,11 @@ static int delete_git_dir(const char *id)
        return ret;
 }
 
+static void delete_worktrees_dir_if_empty(void)
+{
+       rmdir(git_path("worktrees")); /* ignore failed removal */
+}
+
 static int prune_worktree(const char *id, struct strbuf *reason)
 {
        struct stat st;
@@ -149,7 +155,7 @@ static void prune_worktrees(void)
        }
        closedir(dir);
        if (!show_only)
-               rmdir(git_path("worktrees"));
+               delete_worktrees_dir_if_empty();
        strbuf_release(&reason);
 }
 
@@ -240,7 +246,7 @@ static void validate_worktree_add(const char *path, const struct add_opts *opts)
        if (!wt)
                goto done;
 
-       locked = !!is_worktree_locked(wt);
+       locked = !!worktree_lock_reason(wt);
        if ((!locked && opts->force) || (locked && opts->force > 1)) {
                if (delete_git_dir(wt->id))
                    die(_("unable to re-add worktree '%s'"), path);
@@ -262,13 +268,14 @@ static int add_worktree(const char *path, const char *refname,
        struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
        struct strbuf sb = STRBUF_INIT;
        const char *name;
-       struct stat st;
        struct child_process cp = CHILD_PROCESS_INIT;
        struct argv_array child_env = ARGV_ARRAY_INIT;
-       int counter = 0, len, ret;
+       unsigned int counter = 0;
+       int len, ret;
        struct strbuf symref = STRBUF_INIT;
        struct commit *commit = NULL;
        int is_branch = 0;
+       struct strbuf sb_name = STRBUF_INIT;
 
        validate_worktree_add(path, opts);
 
@@ -284,13 +291,23 @@ static int add_worktree(const char *path, const char *refname,
                die(_("invalid reference: %s"), refname);
 
        name = worktree_basename(path, &len);
-       git_path_buf(&sb_repo, "worktrees/%.*s", (int)(path + len - name), name);
+       strbuf_add(&sb, name, path + len - name);
+       sanitize_refname_component(sb.buf, &sb_name);
+       if (!sb_name.len)
+               BUG("How come '%s' becomes empty after sanitization?", sb.buf);
+       strbuf_reset(&sb);
+       name = sb_name.buf;
+       git_path_buf(&sb_repo, "worktrees/%s", name);
        len = sb_repo.len;
        if (safe_create_leading_directories_const(sb_repo.buf))
                die_errno(_("could not create leading directories of '%s'"),
                          sb_repo.buf);
-       while (!stat(sb_repo.buf, &st)) {
+
+       while (mkdir(sb_repo.buf, 0777)) {
                counter++;
+               if ((errno != EEXIST) || !counter /* overflow */)
+                       die_errno(_("could not create directory of '%s'"),
+                                 sb_repo.buf);
                strbuf_setlen(&sb_repo, len);
                strbuf_addf(&sb_repo, "%d", counter);
        }
@@ -300,8 +317,6 @@ static int add_worktree(const char *path, const char *refname,
        atexit(remove_junk);
        sigchain_push_common(remove_junk_on_signal);
 
-       if (mkdir(sb_repo.buf, 0777))
-               die_errno(_("could not create directory of '%s'"), sb_repo.buf);
        junk_git_dir = xstrdup(sb_repo.buf);
        is_junk = 1;
 
@@ -396,6 +411,7 @@ static int add_worktree(const char *path, const char *refname,
                        cp.dir = path;
                        cp.env = env;
                        cp.argv = NULL;
+                       cp.trace2_hook_name = "post-checkout";
                        argv_array_pushl(&cp.args, absolute_path(hook),
                                         oid_to_hex(&null_oid),
                                         oid_to_hex(&commit->object.oid),
@@ -409,6 +425,7 @@ static int add_worktree(const char *path, const char *refname,
        strbuf_release(&symref);
        strbuf_release(&sb_repo);
        strbuf_release(&sb_git);
+       strbuf_release(&sb_name);
        return ret;
 }
 
@@ -677,7 +694,7 @@ static int lock_worktree(int ac, const char **av, const char *prefix)
        if (is_main_worktree(wt))
                die(_("The main working tree cannot be locked or unlocked"));
 
-       old_reason = is_worktree_locked(wt);
+       old_reason = worktree_lock_reason(wt);
        if (old_reason) {
                if (*old_reason)
                        die(_("'%s' is already locked, reason: %s"),
@@ -709,7 +726,7 @@ static int unlock_worktree(int ac, const char **av, const char *prefix)
                die(_("'%s' is not a working tree"), av[0]);
        if (is_main_worktree(wt))
                die(_("The main working tree cannot be locked or unlocked"));
-       if (!is_worktree_locked(wt))
+       if (!worktree_lock_reason(wt))
                die(_("'%s' is not locked"), av[0]);
        ret = unlink_or_warn(git_common_path("worktrees/%s/locked", wt->id));
        free_worktrees(worktrees);
@@ -719,20 +736,36 @@ static int unlock_worktree(int ac, const char **av, const char *prefix)
 static void validate_no_submodules(const struct worktree *wt)
 {
        struct index_state istate = { NULL };
+       struct strbuf path = STRBUF_INIT;
        int i, found_submodules = 0;
 
-       if (read_index_from(&istate, worktree_git_path(wt, "index"),
-                           get_worktree_git_dir(wt)) > 0) {
+       if (is_directory(worktree_git_path(wt, "modules"))) {
+               /*
+                * There could be false positives, e.g. the "modules"
+                * directory exists but is empty. But it's a rare case and
+                * this simpler check is probably good enough for now.
+                */
+               found_submodules = 1;
+       } else if (read_index_from(&istate, worktree_git_path(wt, "index"),
+                                  get_worktree_git_dir(wt)) > 0) {
                for (i = 0; i < istate.cache_nr; i++) {
                        struct cache_entry *ce = istate.cache[i];
+                       int err;
 
-                       if (S_ISGITLINK(ce->ce_mode)) {
-                               found_submodules = 1;
-                               break;
-                       }
+                       if (!S_ISGITLINK(ce->ce_mode))
+                               continue;
+
+                       strbuf_reset(&path);
+                       strbuf_addf(&path, "%s/%s", wt->path, ce->name);
+                       if (!is_submodule_populated_gently(path.buf, &err))
+                               continue;
+
+                       found_submodules = 1;
+                       break;
                }
        }
        discard_index(&istate);
+       strbuf_release(&path);
 
        if (found_submodules)
                die(_("working trees containing submodules cannot be moved or removed"));
@@ -740,13 +773,17 @@ static void validate_no_submodules(const struct worktree *wt)
 
 static int move_worktree(int ac, const char **av, const char *prefix)
 {
+       int force = 0;
        struct option options[] = {
+               OPT__FORCE(&force,
+                        N_("force move even if worktree is dirty or locked"),
+                        PARSE_OPT_NOCOMPLETE),
                OPT_END()
        };
        struct worktree **worktrees, *wt;
        struct strbuf dst = STRBUF_INIT;
        struct strbuf errmsg = STRBUF_INIT;
-       const char *reason;
+       const char *reason = NULL;
        char *path;
 
        ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
@@ -777,12 +814,13 @@ static int move_worktree(int ac, const char **av, const char *prefix)
 
        validate_no_submodules(wt);
 
-       reason = is_worktree_locked(wt);
+       if (force < 2)
+               reason = worktree_lock_reason(wt);
        if (reason) {
                if (*reason)
-                       die(_("cannot move a locked working tree, lock reason: %s"),
+                       die(_("cannot move a locked working tree, lock reason: %s\nuse 'move -f -f' to override or unlock first"),
                            reason);
-               die(_("cannot move a locked working tree"));
+               die(_("cannot move a locked working tree;\nuse 'move -f -f' to override or unlock first"));
        }
        if (validate_worktree(wt, &errmsg, 0))
                die(_("validation failed, cannot move working tree: %s"),
@@ -870,13 +908,13 @@ static int remove_worktree(int ac, const char **av, const char *prefix)
        int force = 0;
        struct option options[] = {
                OPT__FORCE(&force,
-                        N_("force removing even if the worktree is dirty"),
+                        N_("force removal even if worktree is dirty or locked"),
                         PARSE_OPT_NOCOMPLETE),
                OPT_END()
        };
        struct worktree **worktrees, *wt;
        struct strbuf errmsg = STRBUF_INIT;
-       const char *reason;
+       const char *reason = NULL;
        int ret = 0;
 
        ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
@@ -889,12 +927,13 @@ static int remove_worktree(int ac, const char **av, const char *prefix)
                die(_("'%s' is not a working tree"), av[0]);
        if (is_main_worktree(wt))
                die(_("'%s' is a main working tree"), av[0]);
-       reason = is_worktree_locked(wt);
+       if (force < 2)
+               reason = worktree_lock_reason(wt);
        if (reason) {
                if (*reason)
-                       die(_("cannot remove a locked working tree, lock reason: %s"),
+                       die(_("cannot remove a locked working tree, lock reason: %s\nuse 'remove -f -f' to override or unlock first"),
                            reason);
-               die(_("cannot remove a locked working tree"));
+               die(_("cannot remove a locked working tree;\nuse 'remove -f -f' to override or unlock first"));
        }
        if (validate_worktree(wt, &errmsg, WT_VALIDATE_WORKTREE_MISSING_OK))
                die(_("validation failed, cannot remove working tree: %s"),
@@ -912,6 +951,7 @@ static int remove_worktree(int ac, const char **av, const char *prefix)
         * from here.
         */
        ret |= delete_git_dir(wt->id);
+       delete_worktrees_dir_if_empty();
 
        free_worktrees(worktrees);
        return ret;