worktree: disallow adding same path multiple times

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: $ rm -fr foo $ git worktree add -q --detach foo $ git worktree list ... eadebfe [master] .../foo eadebfe (detached HEAD) .../foo eadebfe (detached HEAD) This "corruption" leads to the unfortunate situation in which the worktree can not be removed: $ git worktree remove foo fatal: validation failed, cannot remove working tree: '.../foo' does not point back to '.git/worktrees/foo' Nor can the bogus entry be pruned: $ git worktree prune -v $ git worktree list ... eadebfe [master] .../foo eadebfe (detached HEAD) .../foo eadebfe (detached HEAD) without first deleting the worktree directory manually: $ rm -fr foo $ git worktree prune -v Removing .../foo: gitdir file points to non-existent location Removing .../foo1: gitdir file points to non-existent location $ git worktree list ... eadebfe [master] or by manually deleting the worktree entry in .git/worktrees. To address this problem, upgrade "git worktree add" validation to allow worktree creation only if the given path is not already associated with an existing worktree (even if the path itself is non-existent), thus preventing such bogus worktree entries from being created in the first place. Reported-by: Jeff King <peff@peff.net> Signed-off-by: Eric Sunshine <sunshine@sunshineco.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Eric Sunshine committed Aug 28, 2018 at 17:20 UTC cb56f55c16c128e18449c9417dc045f787c1b663
2 files changed +32
builtin/worktree.c
+25
@@ -221,8 +221,33 @@ static const char *worktree_basename(const char *path, int *olen)
221
222 static void validate_worktree_add(const char *path, const struct add_opts *opts)
223 {
224 + struct worktree **worktrees;
225 + struct worktree *wt;
226 + int locked;
227 +
228 if (file_exists(path) && !is_empty_dir(path))
229 die(_("'%s' already exists"), path);
230 +
231 + worktrees = get_worktrees(0);
232 + /*
233 + * find_worktree()'s suffix matching may undesirably find the main
234 + * rather than a linked worktree (for instance, when the basenames
235 + * of the main worktree and the one being created are the same).
236 + * We're only interested in linked worktrees, so skip the main
237 + * worktree with +1.
238 + */
239 + wt = find_worktree(worktrees + 1, NULL, path);
240 + if (!wt)
241 + goto done;
242 +
243 + locked = !!is_worktree_locked(wt);
244 + if (locked)
245 + die(_("'%s' is a missing but locked worktree;\nuse 'unlock' and 'prune' or 'remove' to clear"), path);
246 + else
247 + die(_("'%s' is a missing but already registered worktree;\nuse 'prune' or 'remove' to clear"), path);
248 +
249 +done:
250 + free_worktrees(worktrees);
251 }
252
253 static int add_worktree(const char *path, const char *refname,
t/t2025-worktree-add.sh
+7
@@ -552,4 +552,11 @@ test_expect_success '"add" in bare repo invokes post-checkout hook' '
552 test_cmp hook.expect goozy/hook.actual
553 '
554
555 +test_expect_success '"add" an existing but missing worktree' '
556 + git worktree add --detach pneu &&
557 + test_must_fail git worktree add --detach pneu &&
558 + rm -fr pneu &&
559 + test_must_fail git worktree add --detach pneu
560 +'
561 +
562 test_done