On Tue, Aug 28, 2018 at 05:20:22PM -0400, Eric Sunshine wrote:

> A given path should only ever be associated with a single registered
> worktree. This invariant is enforced by refusing to create a new
> worktree at a given path if that path already exists. For example:
> 
>     $ git worktree add -q --detach foo
>     $ git worktree add -q --detach foo
>     fatal: 'foo' already exists
> 
> However, the check can be fooled, and the invariant broken, if the
> path is missing. Continuing the example:
> [...]

Nicely explained.

> diff --git a/builtin/worktree.c b/builtin/worktree.c
> index 46c93771e8..1122f27b5f 100644
> --- a/builtin/worktree.c
> +++ b/builtin/worktree.c
> @@ -221,8 +221,33 @@ static const char *worktree_basename(const char *path, 
> int *olen)
>  
>  static void validate_worktree_add(const char *path, const struct add_opts 
> *opts)
>  {
> +     struct worktree **worktrees;
> +     struct worktree *wt;
> +     int locked;
> +
>       if (file_exists(path) && !is_empty_dir(path))
>               die(_("'%s' already exists"), path);
> +
> +     worktrees = get_worktrees(0);
> +     /*
> +      * find_worktree()'s suffix matching may undesirably find the main
> +      * rather than a linked worktree (for instance, when the basenames
> +      * of the main worktree and the one being created are the same).
> +      * We're only interested in linked worktrees, so skip the main
> +      * worktree with +1.
> +      */
> +     wt = find_worktree(worktrees + 1, NULL, path);

Very appropriate use of a comment. :)

I wondered whether find_worktree() would discover a collision like this:

  git worktree add --detach foo
  rm -rf foo
  git worktree add --detach bar/foo

but it is smart enough to actually compare the gitdirs and so we
(correctly) create "foo" and "foo1" in $GIT_DIR/worktrees.

> +     if (!wt)
> +             goto done;
> +
> +     locked = !!is_worktree_locked(wt);
> +     if (locked)
> +             die(_("'%s' is a missing but locked worktree;\nuse 'unlock' and 
> 'prune' or 'remove' to clear"), path);
> +     else
> +             die(_("'%s' is a missing but already registered worktree;\nuse 
> 'prune' or 'remove' to clear"), path);

Nice, I like giving separate messages for the locked and unlocked cases
(which I imagine will become even more obvious as you add --force
support).

The formatting of the message itself is a little funny:

  $ git worktree add --detach foo
  fatal: 'foo' is a missing but already registered worktree;
  use 'prune' or 'remove' to clear

I'd say that the second line would ideally be advise(), since we're
dying. You could do:

  error(%s is missing...)
  advise(use prune...)
  exit(128);

but that loses the "fatal" in the first message. I wonder if just
manually writing "hint:" would be so bad.

> +done:
> +     free_worktrees(worktrees);

You could easily avoid this goto with:

  if (wt) {
     /* check wt or die */
  }

  free_worktrees(worktrees);

but it may not be worth it if the logic gets more complicated in future
patches.

-Peff

Reply via email to