Merge branch 'tg/worktree-add-existing-branch'
authorJunio C Hamano <gitster@pobox.com>
Wed, 23 May 2018 05:38:18 +0000 (14:38 +0900)
committerJunio C Hamano <gitster@pobox.com>
Wed, 23 May 2018 05:38:18 +0000 (14:38 +0900)
"git worktree add" learned to check out an existing branch.

* tg/worktree-add-existing-branch:
worktree: teach "add" to check out existing branches
worktree: factor out dwim_branch function
worktree: improve message when creating a new worktree
worktree: remove extra members from struct add_opts

1  2 
Documentation/git-worktree.txt
builtin/worktree.c
t/t2025-worktree-add.sh
index 9920d9c06ed213ad12c89050742b2da949303445,5d54f36a7134f479ac403bcc9ffa1e109c7b64e2..afc6576a14d56ea49e37d1251a5665bf77457f89
@@@ -12,9 -12,7 +12,9 @@@ SYNOPSI
  'git worktree add' [-f] [--detach] [--checkout] [--lock] [-b <new-branch>] <path> [<commit-ish>]
  'git worktree list' [--porcelain]
  'git worktree lock' [--reason <string>] <worktree>
 +'git worktree move' <worktree> <new-path>
  'git worktree prune' [-n] [-v] [--expire <expire>]
 +'git worktree remove' [-f] <worktree>
  'git worktree unlock' <worktree>
  
  DESCRIPTION
@@@ -27,16 -25,19 +27,16 @@@ out more than one branch at a time.  Wi
  tree is associated with the repository.  This new working tree is called a
  "linked working tree" as opposed to the "main working tree" prepared by "git
  init" or "git clone".  A repository has one main working tree (if it's not a
 -bare repository) and zero or more linked working trees.
 +bare repository) and zero or more linked working trees. When you are done
 +with a linked working tree, remove it with `git worktree remove`.
  
 -When you are done with a linked working tree you can simply delete it.
 -The working tree's administrative files in the repository (see
 -"DETAILS" below) will eventually be removed automatically (see
 +If a working tree is deleted without using `git worktree remove`, then
 +its associated administrative files, which reside in the repository
 +(see "DETAILS" below), will eventually be removed automatically (see
  `gc.worktreePruneExpire` in linkgit:git-config[1]), or you can run
  `git worktree prune` in the main or any linked working tree to
  clean up any stale administrative files.
  
 -If you move a linked working tree, you need to manually update the
 -administrative files so that they do not get pruned automatically. See
 -section "DETAILS" for more information.
 -
  If a linked working tree is stored on a portable device or network share
  which is not always mounted, you can prevent its administrative files from
  being pruned by issuing the `git worktree lock` command, optionally
@@@ -51,18 -52,22 +51,23 @@@ is linked to the current repository, sh
  directory specific files such as HEAD, index, etc. `-` may also be
  specified as `<commit-ish>`; it is synonymous with `@{-1}`.
  +
 -If <commit-ish> is a branch name (call it `<branch>` and is not found,
 +If <commit-ish> is a branch name (call it `<branch>`) and is not found,
  and neither `-b` nor `-B` nor `--detach` are used, but there does
  exist a tracking branch in exactly one remote (call it `<remote>`)
 -with a matching name, treat as equivalent to
 +with a matching name, treat as equivalent to:
 ++
  ------------
  $ git worktree add --track -b <branch> <path> <remote>/<branch>
  ------------
  +
  If `<commit-ish>` is omitted and neither `-b` nor `-B` nor `--detach` used,
- then, as a convenience, a new branch based at HEAD is created automatically,
- as if `-b $(basename <path>)` was specified.
+ then, as a convenience, the new worktree is associated with a branch
+ (call it `<branch>`) named after `$(basename <path>)`.  If `<branch>`
+ doesn't exist, a new branch based on HEAD is automatically created as
+ if `-b <branch>` was given.  If `<branch>` does exist, it will be
+ checked out in the new worktree, if it's not checked out anywhere
+ else, otherwise the command will refuse to create the worktree (unless
+ `--force` is used).
  
  list::
  
@@@ -79,22 -84,10 +84,22 @@@ files from being pruned automatically. 
  being moved or deleted. Optionally, specify a reason for the lock
  with `--reason`.
  
 +move::
 +
 +Move a working tree to a new location. Note that the main working tree
 +or linked working trees containing submodules cannot be moved.
 +
  prune::
  
  Prune working tree information in $GIT_DIR/worktrees.
  
 +remove::
 +
 +Remove a working tree. Only clean working trees (no untracked files
 +and no modification in tracked files) can be removed. Unclean working
 +trees or ones with submodules can be removed with `--force`. The main
 +working tree cannot be removed.
 +
  unlock::
  
  Unlock a working tree, allowing it to be pruned, moved or deleted.
@@@ -104,10 -97,9 +109,10 @@@ OPTION
  
  -f::
  --force::
 -      By default, `add` refuses to create a new working tree when `<commit-ish>` is a branch name and
 -      is already checked out by another working tree. This option overrides
 -      that safeguard.
 +      By default, `add` refuses to create a new working tree when
 +      `<commit-ish>` is a branch name and is already checked out by
 +      another working tree and `remove` refuses to remove an unclean
 +      working tree. This option overrides these safeguards.
  
  -b <new-branch>::
  -B <new-branch>::
@@@ -209,7 -201,7 +214,7 @@@ thumb is do not make any assumption abo
  $GIT_DIR or $GIT_COMMON_DIR when you need to directly access something
  inside $GIT_DIR. Use `git rev-parse --git-path` to get the final path.
  
 -If you move a linked working tree, you need to update the 'gitdir' file
 +If you manually move a linked working tree, you need to update the 'gitdir' file
  in the entry's directory. For example, if a linked working tree is moved
  to `/newpath/test-next` and its `.git` file points to
  `/path/main/.git/worktrees/test-next`, then update
@@@ -233,7 -225,7 +238,7 @@@ The worktree list command has two outpu
  details on a single line with columns.  For example:
  
  ------------
 -S git worktree list
 +$ git worktree list
  /path/to/bare-source            (bare)
  /path/to/linked-worktree        abcd1234 [master]
  /path/to/other-linked-worktree  1234abc  (detached HEAD)
@@@ -248,7 -240,7 +253,7 @@@ if the value is true.  An empty line in
  example:
  
  ------------
 -S git worktree list --porcelain
 +$ git worktree list --porcelain
  worktree /path/to/bare-source
  bare
  
@@@ -279,7 -271,8 +284,7 @@@ $ pushd ../tem
  # ... hack hack hack ...
  $ git commit -a -m 'emergency fix for boss'
  $ popd
 -$ rm -rf ../temp
 -$ git worktree prune
 +$ git worktree remove ../temp
  ------------
  
  BUGS
@@@ -288,6 -281,13 +293,6 @@@ Multiple checkout in general is still e
  for submodules is incomplete. It is NOT recommended to make multiple
  checkouts of a superproject.
  
 -git-worktree could provide more automation for tasks currently
 -performed manually, such as:
 -
 -- `remove` to remove a linked working tree and its administrative files (and
 -  warn if the working tree is dirty)
 -- `mv` to move or rename a working tree and update its administrative files
 -
  GIT
  ---
  Part of the linkgit:git[1] suite
diff --combined builtin/worktree.c
index 30647b30c5337e23c70720558a5ab96e9a8d68fd,d3aeb4877dbd1f0398d7a3ace61785d9e890fd09..5c7d2bb1807f942139b3ec41b426320e4b0cfc2a
  #include "worktree.h"
  
  static const char * const worktree_usage[] = {
 -      N_("git worktree add [<options>] <path> [<branch>]"),
 +      N_("git worktree add [<options>] <path> [<commit-ish>]"),
        N_("git worktree list [<options>]"),
        N_("git worktree lock [<options>] <path>"),
 +      N_("git worktree move <worktree> <new-path>"),
        N_("git worktree prune [<options>]"),
 +      N_("git worktree remove [<options>] <worktree>"),
        N_("git worktree unlock <path>"),
        NULL
  };
@@@ -29,8 -27,6 +29,6 @@@ struct add_opts 
        int detach;
        int checkout;
        int keep_locked;
-       const char *new_branch;
-       int force_new_branch;
  };
  
  static int show_only;
@@@ -101,9 -97,16 +99,9 @@@ static int prune_worktree(const char *i
        }
        path[len] = '\0';
        if (!file_exists(path)) {
 -              struct stat st_link;
                free(path);
 -              /*
 -               * the repo is moved manually and has not been
 -               * accessed since?
 -               */
 -              if (!stat(git_path("worktrees/%s/link", id), &st_link) &&
 -                  st_link.st_nlink > 1)
 -                      return 0;
 -              if (st.st_mtime <= expire) {
 +              if (stat(git_path("worktrees/%s/index", id), &st) ||
 +                  st.st_mtime <= expire) {
                        strbuf_addf(reason, _("Removing worktrees/%s: gitdir file points to non-existent location"), id);
                        return 1;
                } else {
@@@ -298,8 -301,6 +296,6 @@@ static int add_worktree(const char *pat
        strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
        write_file(sb.buf, "../..");
  
-       fprintf_ln(stderr, _("Preparing %s (identifier %s)"), path, name);
        argv_array_pushf(&child_env, "%s=%s", GIT_DIR_ENVIRONMENT, sb_git.buf);
        argv_array_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path);
        cp.git_cmd = 1;
@@@ -340,23 -341,9 +336,23 @@@ done
         * Hook failure does not warrant worktree deletion, so run hook after
         * is_junk is cleared, but do return appropriate code when hook fails.
         */
 -      if (!ret && opts->checkout)
 -              ret = run_hook_le(NULL, "post-checkout", oid_to_hex(&null_oid),
 -                                oid_to_hex(&commit->object.oid), "1", NULL);
 +      if (!ret && opts->checkout) {
 +              const char *hook = find_hook("post-checkout");
 +              if (hook) {
 +                      const char *env[] = { "GIT_DIR", "GIT_WORK_TREE", NULL };
 +                      cp.git_cmd = 0;
 +                      cp.no_stdin = 1;
 +                      cp.stdout_to_stderr = 1;
 +                      cp.dir = path;
 +                      cp.env = env;
 +                      cp.argv = NULL;
 +                      argv_array_pushl(&cp.args, absolute_path(hook),
 +                                       oid_to_hex(&null_oid),
 +                                       oid_to_hex(&commit->object.oid),
 +                                       "1", NULL);
 +                      ret = run_command(&cp);
 +              }
 +      }
  
        argv_array_clear(&child_env);
        strbuf_release(&sb);
        return ret;
  }
  
 -                                find_unique_abbrev(commit->object.oid.hash,
 -                                                   DEFAULT_ABBREV));
+ static void print_preparing_worktree_line(int detach,
+                                         const char *branch,
+                                         const char *new_branch,
+                                         int force_new_branch)
+ {
+       if (force_new_branch) {
+               struct commit *commit = lookup_commit_reference_by_name(new_branch);
+               if (!commit)
+                       printf_ln(_("Preparing worktree (new branch '%s')"), new_branch);
+               else
+                       printf_ln(_("Preparing worktree (resetting branch '%s'; was at %s)"),
+                                 new_branch,
 -                                find_unique_abbrev(commit->object.oid.hash,
 -                                                   DEFAULT_ABBREV));
++                                find_unique_abbrev(&commit->object.oid, DEFAULT_ABBREV));
+       } else if (new_branch) {
+               printf_ln(_("Preparing worktree (new branch '%s')"), new_branch);
+       } else {
+               struct strbuf s = STRBUF_INIT;
+               if (!detach && !strbuf_check_branch_ref(&s, branch) &&
+                   ref_exists(s.buf))
+                       printf_ln(_("Preparing worktree (checking out '%s')"),
+                                 branch);
+               else {
+                       struct commit *commit = lookup_commit_reference_by_name(branch);
+                       if (!commit)
+                               die(_("invalid reference: %s"), branch);
+                       printf_ln(_("Preparing worktree (detached HEAD %s)"),
++                                find_unique_abbrev(&commit->object.oid, DEFAULT_ABBREV));
+               }
+               strbuf_release(&s);
+       }
+ }
+ static const char *dwim_branch(const char *path, const char **new_branch)
+ {
+       int n;
+       const char *s = worktree_basename(path, &n);
+       const char *branchname = xstrndup(s, n);
+       struct strbuf ref = STRBUF_INIT;
+       UNLEAK(branchname);
+       if (!strbuf_check_branch_ref(&ref, branchname) &&
+           ref_exists(ref.buf)) {
+               strbuf_release(&ref);
+               return branchname;
+       }
+       *new_branch = branchname;
+       if (guess_remote) {
+               struct object_id oid;
+               const char *remote =
+                       unique_tracking_name(*new_branch, &oid);
+               return remote;
+       }
+       return NULL;
+ }
  static int add(int ac, const char **av, const char *prefix)
  {
        struct add_opts opts;
        const char *new_branch_force = NULL;
        char *path;
        const char *branch;
+       const char *new_branch = NULL;
        const char *opt_track = NULL;
        struct option options[] = {
 -              OPT__FORCE(&opts.force, N_("checkout <branch> even if already checked out in other worktree")),
 +              OPT__FORCE(&opts.force,
 +                         N_("checkout <branch> even if already checked out in other worktree"),
 +                         PARSE_OPT_NOCOMPLETE),
-               OPT_STRING('b', NULL, &opts.new_branch, N_("branch"),
+               OPT_STRING('b', NULL, &new_branch, N_("branch"),
                           N_("create a new branch")),
                OPT_STRING('B', NULL, &new_branch_force, N_("branch"),
                           N_("create or reset a branch")),
        memset(&opts, 0, sizeof(opts));
        opts.checkout = 1;
        ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
-       if (!!opts.detach + !!opts.new_branch + !!new_branch_force > 1)
+       if (!!opts.detach + !!new_branch + !!new_branch_force > 1)
                die(_("-b, -B, and --detach are mutually exclusive"));
        if (ac < 1 || ac > 2)
                usage_with_options(worktree_usage, options);
        if (!strcmp(branch, "-"))
                branch = "@{-1}";
  
-       opts.force_new_branch = !!new_branch_force;
-       if (opts.force_new_branch) {
+       if (new_branch_force) {
                struct strbuf symref = STRBUF_INIT;
  
-               opts.new_branch = new_branch_force;
+               new_branch = new_branch_force;
  
                if (!opts.force &&
-                   !strbuf_check_branch_ref(&symref, opts.new_branch) &&
+                   !strbuf_check_branch_ref(&symref, new_branch) &&
                    ref_exists(symref.buf))
                        die_if_checked_out(symref.buf, 0);
                strbuf_release(&symref);
        }
  
-       if (ac < 2 && !opts.new_branch && !opts.detach) {
-               int n;
-               const char *s = worktree_basename(path, &n);
-               opts.new_branch = xstrndup(s, n);
-               if (guess_remote) {
-                       struct object_id oid;
-                       const char *remote =
-                               unique_tracking_name(opts.new_branch, &oid);
-                       if (remote)
-                               branch = remote;
-               }
+       if (ac < 2 && !new_branch && !opts.detach) {
+               const char *s = dwim_branch(path, &new_branch);
+               if (s)
+                       branch = s;
        }
  
-       if (ac == 2 && !opts.new_branch && !opts.detach) {
+       if (ac == 2 && !new_branch && !opts.detach) {
                struct object_id oid;
                struct commit *commit;
                const char *remote;
                if (!commit) {
                        remote = unique_tracking_name(branch, &oid);
                        if (remote) {
-                               opts.new_branch = branch;
+                               new_branch = branch;
                                branch = remote;
                        }
                }
        }
  
-       if (opts.new_branch) {
+       print_preparing_worktree_line(opts.detach, branch, new_branch, !!new_branch_force);
+       if (new_branch) {
                struct child_process cp = CHILD_PROCESS_INIT;
                cp.git_cmd = 1;
                argv_array_push(&cp.args, "branch");
-               if (opts.force_new_branch)
+               if (new_branch_force)
                        argv_array_push(&cp.args, "--force");
-               argv_array_push(&cp.args, opts.new_branch);
+               argv_array_push(&cp.args, new_branch);
                argv_array_push(&cp.args, branch);
                if (opt_track)
                        argv_array_push(&cp.args, opt_track);
                if (run_command(&cp))
                        return -1;
-               branch = opts.new_branch;
+               branch = new_branch;
        } else if (opt_track) {
                die(_("--[no-]track can only be used if a new branch is created"));
        }
@@@ -495,7 -533,7 +542,7 @@@ static void show_worktree(struct worktr
                strbuf_addstr(&sb, "(bare)");
        else {
                strbuf_addf(&sb, "%-*s ", abbrev_len,
 -                              find_unique_abbrev(wt->head_oid.hash, DEFAULT_ABBREV));
 +                              find_unique_abbrev(&wt->head_oid, DEFAULT_ABBREV));
                if (wt->is_detached)
                        strbuf_addstr(&sb, "(detached HEAD)");
                else if (wt->head_ref) {
@@@ -520,7 -558,7 +567,7 @@@ static void measure_widths(struct workt
  
                if (path_len > *maxlen)
                        *maxlen = path_len;
 -              sha1_len = strlen(find_unique_abbrev(wt[i]->head_oid.hash, *abbrev));
 +              sha1_len = strlen(find_unique_abbrev(&wt[i]->head_oid, *abbrev));
                if (sha1_len > *abbrev)
                        *abbrev = sha1_len;
        }
@@@ -616,221 -654,6 +663,221 @@@ static int unlock_worktree(int ac, cons
        return ret;
  }
  
 +static void validate_no_submodules(const struct worktree *wt)
 +{
 +      struct index_state istate = { NULL };
 +      int i, found_submodules = 0;
 +
 +      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];
 +
 +                      if (S_ISGITLINK(ce->ce_mode)) {
 +                              found_submodules = 1;
 +                              break;
 +                      }
 +              }
 +      }
 +      discard_index(&istate);
 +
 +      if (found_submodules)
 +              die(_("working trees containing submodules cannot be moved or removed"));
 +}
 +
 +static int move_worktree(int ac, const char **av, const char *prefix)
 +{
 +      struct option options[] = {
 +              OPT_END()
 +      };
 +      struct worktree **worktrees, *wt;
 +      struct strbuf dst = STRBUF_INIT;
 +      struct strbuf errmsg = STRBUF_INIT;
 +      const char *reason;
 +      char *path;
 +
 +      ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
 +      if (ac != 2)
 +              usage_with_options(worktree_usage, options);
 +
 +      path = prefix_filename(prefix, av[1]);
 +      strbuf_addstr(&dst, path);
 +      free(path);
 +
 +      worktrees = get_worktrees(0);
 +      wt = find_worktree(worktrees, prefix, av[0]);
 +      if (!wt)
 +              die(_("'%s' is not a working tree"), av[0]);
 +      if (is_main_worktree(wt))
 +              die(_("'%s' is a main working tree"), av[0]);
 +      if (is_directory(dst.buf)) {
 +              const char *sep = find_last_dir_sep(wt->path);
 +
 +              if (!sep)
 +                      die(_("could not figure out destination name from '%s'"),
 +                          wt->path);
 +              strbuf_trim_trailing_dir_sep(&dst);
 +              strbuf_addstr(&dst, sep);
 +      }
 +      if (file_exists(dst.buf))
 +              die(_("target '%s' already exists"), dst.buf);
 +
 +      validate_no_submodules(wt);
 +
 +      reason = is_worktree_locked(wt);
 +      if (reason) {
 +              if (*reason)
 +                      die(_("cannot move a locked working tree, lock reason: %s"),
 +                          reason);
 +              die(_("cannot move a locked working tree"));
 +      }
 +      if (validate_worktree(wt, &errmsg, 0))
 +              die(_("validation failed, cannot move working tree: %s"),
 +                  errmsg.buf);
 +      strbuf_release(&errmsg);
 +
 +      if (rename(wt->path, dst.buf) == -1)
 +              die_errno(_("failed to move '%s' to '%s'"), wt->path, dst.buf);
 +
 +      update_worktree_location(wt, dst.buf);
 +
 +      strbuf_release(&dst);
 +      free_worktrees(worktrees);
 +      return 0;
 +}
 +
 +/*
 + * Note, "git status --porcelain" is used to determine if it's safe to
 + * delete a whole worktree. "git status" does not ignore user
 + * configuration, so if a normal "git status" shows "clean" for the
 + * user, then it's ok to remove it.
 + *
 + * This assumption may be a bad one. We may want to ignore
 + * (potentially bad) user settings and only delete a worktree when
 + * it's absolutely safe to do so from _our_ point of view because we
 + * know better.
 + */
 +static void check_clean_worktree(struct worktree *wt,
 +                               const char *original_path)
 +{
 +      struct argv_array child_env = ARGV_ARRAY_INIT;
 +      struct child_process cp;
 +      char buf[1];
 +      int ret;
 +
 +      /*
 +       * Until we sort this out, all submodules are "dirty" and
 +       * will abort this function.
 +       */
 +      validate_no_submodules(wt);
 +
 +      argv_array_pushf(&child_env, "%s=%s/.git",
 +                       GIT_DIR_ENVIRONMENT, wt->path);
 +      argv_array_pushf(&child_env, "%s=%s",
 +                       GIT_WORK_TREE_ENVIRONMENT, wt->path);
 +      memset(&cp, 0, sizeof(cp));
 +      argv_array_pushl(&cp.args, "status",
 +                       "--porcelain", "--ignore-submodules=none",
 +                       NULL);
 +      cp.env = child_env.argv;
 +      cp.git_cmd = 1;
 +      cp.dir = wt->path;
 +      cp.out = -1;
 +      ret = start_command(&cp);
 +      if (ret)
 +              die_errno(_("failed to run 'git status' on '%s'"),
 +                        original_path);
 +      ret = xread(cp.out, buf, sizeof(buf));
 +      if (ret)
 +              die(_("'%s' is dirty, use --force to delete it"),
 +                  original_path);
 +      close(cp.out);
 +      ret = finish_command(&cp);
 +      if (ret)
 +              die_errno(_("failed to run 'git status' on '%s', code %d"),
 +                        original_path, ret);
 +}
 +
 +static int delete_git_work_tree(struct worktree *wt)
 +{
 +      struct strbuf sb = STRBUF_INIT;
 +      int ret = 0;
 +
 +      strbuf_addstr(&sb, wt->path);
 +      if (remove_dir_recursively(&sb, 0)) {
 +              error_errno(_("failed to delete '%s'"), sb.buf);
 +              ret = -1;
 +      }
 +      strbuf_release(&sb);
 +      return ret;
 +}
 +
 +static int delete_git_dir(struct worktree *wt)
 +{
 +      struct strbuf sb = STRBUF_INIT;
 +      int ret = 0;
 +
 +      strbuf_addstr(&sb, git_common_path("worktrees/%s", wt->id));
 +      if (remove_dir_recursively(&sb, 0)) {
 +              error_errno(_("failed to delete '%s'"), sb.buf);
 +              ret = -1;
 +      }
 +      strbuf_release(&sb);
 +      return ret;
 +}
 +
 +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"),
 +                       PARSE_OPT_NOCOMPLETE),
 +              OPT_END()
 +      };
 +      struct worktree **worktrees, *wt;
 +      struct strbuf errmsg = STRBUF_INIT;
 +      const char *reason;
 +      int ret = 0;
 +
 +      ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
 +      if (ac != 1)
 +              usage_with_options(worktree_usage, options);
 +
 +      worktrees = get_worktrees(0);
 +      wt = find_worktree(worktrees, prefix, av[0]);
 +      if (!wt)
 +              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 (reason) {
 +              if (*reason)
 +                      die(_("cannot remove a locked working tree, lock reason: %s"),
 +                          reason);
 +              die(_("cannot remove a locked working tree"));
 +      }
 +      if (validate_worktree(wt, &errmsg, WT_VALIDATE_WORKTREE_MISSING_OK))
 +              die(_("validation failed, cannot remove working tree: %s"),
 +                  errmsg.buf);
 +      strbuf_release(&errmsg);
 +
 +      if (file_exists(wt->path)) {
 +              if (!force)
 +                      check_clean_worktree(wt, av[0]);
 +
 +              ret |= delete_git_work_tree(wt);
 +      }
 +      /*
 +       * continue on even if ret is non-zero, there's no going back
 +       * from here.
 +       */
 +      ret |= delete_git_dir(wt);
 +
 +      free_worktrees(worktrees);
 +      return ret;
 +}
 +
  int cmd_worktree(int ac, const char **av, const char *prefix)
  {
        struct option options[] = {
                return lock_worktree(ac - 1, av + 1, prefix);
        if (!strcmp(av[1], "unlock"))
                return unlock_worktree(ac - 1, av + 1, prefix);
 +      if (!strcmp(av[1], "move"))
 +              return move_worktree(ac - 1, av + 1, prefix);
 +      if (!strcmp(av[1], "remove"))
 +              return remove_worktree(ac - 1, av + 1, prefix);
        usage_with_options(worktree_usage, options);
  }
diff --combined t/t2025-worktree-add.sh
index d0d2e4f7ec3310ec51da7144fa87151129f393c0,ad38507d000a527011d42505b16792bb018aec41..224049892423075dd7d03bb15229f4a11302d66f
@@@ -198,13 -198,25 +198,25 @@@ test_expect_success '"add" with <branch
        test_cmp_rev HEAD bat
  '
  
- test_expect_success '"add" auto-vivify does not clobber existing branch' '
-       test_commit c1 &&
-       test_commit c2 &&
-       git branch precious HEAD~1 &&
-       test_must_fail git worktree add precious &&
-       test_cmp_rev HEAD~1 precious &&
-       test_path_is_missing precious
+ test_expect_success '"add" checks out existing branch of dwimd name' '
+       git branch dwim HEAD~1 &&
+       git worktree add dwim &&
+       test_cmp_rev HEAD~1 dwim &&
+       (
+               cd dwim &&
+               test_cmp_rev HEAD dwim
+       )
+ '
+ test_expect_success '"add <path>" dwim fails with checked out branch' '
+       git checkout -b test-branch &&
+       test_must_fail git worktree add test-branch &&
+       test_path_is_missing test-branch
+ '
+ test_expect_success '"add --force" with existing dwimd name doesnt die' '
+       git checkout test-branch &&
+       git worktree add --force test-branch
  '
  
  test_expect_success '"add" no auto-vivify with --detach and <branch> omitted' '
@@@ -451,68 -463,32 +463,68 @@@ test_expect_success 'git worktree --no-
  '
  
  post_checkout_hook () {
 -      test_when_finished "rm -f .git/hooks/post-checkout" &&
 -      mkdir -p .git/hooks &&
 -      write_script .git/hooks/post-checkout <<-\EOF
 -      echo $* >hook.actual
 +      gitdir=${1:-.git}
 +      test_when_finished "rm -f $gitdir/hooks/post-checkout" &&
 +      mkdir -p $gitdir/hooks &&
 +      write_script $gitdir/hooks/post-checkout <<-\EOF
 +      {
 +              echo $*
 +              git rev-parse --git-dir --show-toplevel
 +      } >hook.actual
        EOF
  }
  
  test_expect_success '"add" invokes post-checkout hook (branch)' '
        post_checkout_hook &&
 -      printf "%s %s 1\n" $_z40 $(git rev-parse HEAD) >hook.expect &&
 +      {
 +              echo $_z40 $(git rev-parse HEAD) 1 &&
 +              echo $(pwd)/.git/worktrees/gumby &&
 +              echo $(pwd)/gumby
 +      } >hook.expect &&
        git worktree add gumby &&
 -      test_cmp hook.expect hook.actual
 +      test_cmp hook.expect gumby/hook.actual
  '
  
  test_expect_success '"add" invokes post-checkout hook (detached)' '
        post_checkout_hook &&
 -      printf "%s %s 1\n" $_z40 $(git rev-parse HEAD) >hook.expect &&
 +      {
 +              echo $_z40 $(git rev-parse HEAD) 1 &&
 +              echo $(pwd)/.git/worktrees/grumpy &&
 +              echo $(pwd)/grumpy
 +      } >hook.expect &&
        git worktree add --detach grumpy &&
 -      test_cmp hook.expect hook.actual
 +      test_cmp hook.expect grumpy/hook.actual
  '
  
  test_expect_success '"add --no-checkout" suppresses post-checkout hook' '
        post_checkout_hook &&
        rm -f hook.actual &&
        git worktree add --no-checkout gloopy &&
 -      test_path_is_missing hook.actual
 +      test_path_is_missing gloopy/hook.actual
 +'
 +
 +test_expect_success '"add" in other worktree invokes post-checkout hook' '
 +      post_checkout_hook &&
 +      {
 +              echo $_z40 $(git rev-parse HEAD) 1 &&
 +              echo $(pwd)/.git/worktrees/guppy &&
 +              echo $(pwd)/guppy
 +      } >hook.expect &&
 +      git -C gloopy worktree add --detach ../guppy &&
 +      test_cmp hook.expect guppy/hook.actual
 +'
 +
 +test_expect_success '"add" in bare repo invokes post-checkout hook' '
 +      rm -rf bare &&
 +      git clone --bare . bare &&
 +      {
 +              echo $_z40 $(git --git-dir=bare rev-parse HEAD) 1 &&
 +              echo $(pwd)/bare/worktrees/goozy &&
 +              echo $(pwd)/goozy
 +      } >hook.expect &&
 +      post_checkout_hook bare &&
 +      git -C bare worktree add --detach ../goozy &&
 +      test_cmp hook.expect goozy/hook.actual
  '
  
  test_done