move worktree tests to t24*
authorThomas Gummerer <t.gummerer@gmail.com>
Thu, 20 Dec 2018 13:48:13 +0000 (13:48 +0000)
committerJunio C Hamano <gitster@pobox.com>
Wed, 2 Jan 2019 23:28:04 +0000 (15:28 -0800)
The 'git worktree' command used to be just another mode in 'git
checkout', namely 'git checkout --to'. When the tests for the latter
were retrofitted for the former, the test name was adjusted, but the
test number was kept, even though the test is testing a different
command now. t/README states: "Second digit tells the particular
command we are testing.", so 'git worktree' should have a separate
number just for itself.

Move the worktree tests to t24* to adhere to that guideline. We're
going to make use of the free'd up numbers in a subsequent commit.

Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
t/t2025-worktree-add.sh [deleted file]
t/t2026-worktree-prune.sh [deleted file]
t/t2027-worktree-list.sh [deleted file]
t/t2028-worktree-move.sh [deleted file]
t/t2029-worktree-config.sh [deleted file]
t/t2400-worktree-add.sh [new file with mode: 0755]
t/t2401-worktree-prune.sh [new file with mode: 0755]
t/t2402-worktree-list.sh [new file with mode: 0755]
t/t2403-worktree-move.sh [new file with mode: 0755]
t/t2404-worktree-config.sh [new file with mode: 0755]
diff --git a/t/t2025-worktree-add.sh b/t/t2025-worktree-add.sh
deleted file mode 100755 (executable)
index 286bba3..0000000
+++ /dev/null
@@ -1,573 +0,0 @@
-#!/bin/sh
-
-test_description='test git worktree add'
-
-. ./test-lib.sh
-
-. "$TEST_DIRECTORY"/lib-rebase.sh
-
-test_expect_success 'setup' '
-       test_commit init
-'
-
-test_expect_success '"add" an existing worktree' '
-       mkdir -p existing/subtree &&
-       test_must_fail git worktree add --detach existing master
-'
-
-test_expect_success '"add" an existing empty worktree' '
-       mkdir existing_empty &&
-       git worktree add --detach existing_empty master
-'
-
-test_expect_success '"add" using shorthand - fails when no previous branch' '
-       test_must_fail git worktree add existing_short -
-'
-
-test_expect_success '"add" using - shorthand' '
-       git checkout -b newbranch &&
-       echo hello >myworld &&
-       git add myworld &&
-       git commit -m myworld &&
-       git checkout master &&
-       git worktree add short-hand - &&
-       echo refs/heads/newbranch >expect &&
-       git -C short-hand rev-parse --symbolic-full-name HEAD >actual &&
-       test_cmp expect actual
-'
-
-test_expect_success '"add" refuses to checkout locked branch' '
-       test_must_fail git worktree add zere master &&
-       ! test -d zere &&
-       ! test -d .git/worktrees/zere
-'
-
-test_expect_success 'checking out paths not complaining about linked checkouts' '
-       (
-       cd existing_empty &&
-       echo dirty >>init.t &&
-       git checkout master -- init.t
-       )
-'
-
-test_expect_success '"add" worktree' '
-       git rev-parse HEAD >expect &&
-       git worktree add --detach here master &&
-       (
-               cd here &&
-               test_cmp ../init.t init.t &&
-               test_must_fail git symbolic-ref HEAD &&
-               git rev-parse HEAD >actual &&
-               test_cmp ../expect actual &&
-               git fsck
-       )
-'
-
-test_expect_success '"add" worktree with lock' '
-       git rev-parse HEAD >expect &&
-       git worktree add --detach --lock here-with-lock master &&
-       test -f .git/worktrees/here-with-lock/locked
-'
-
-test_expect_success '"add" worktree from a subdir' '
-       (
-               mkdir sub &&
-               cd sub &&
-               git worktree add --detach here master &&
-               cd here &&
-               test_cmp ../../init.t init.t
-       )
-'
-
-test_expect_success '"add" from a linked checkout' '
-       (
-               cd here &&
-               git worktree add --detach nested-here master &&
-               cd nested-here &&
-               git fsck
-       )
-'
-
-test_expect_success '"add" worktree creating new branch' '
-       git worktree add -b newmaster there master &&
-       (
-               cd there &&
-               test_cmp ../init.t init.t &&
-               git symbolic-ref HEAD >actual &&
-               echo refs/heads/newmaster >expect &&
-               test_cmp expect actual &&
-               git fsck
-       )
-'
-
-test_expect_success 'die the same branch is already checked out' '
-       (
-               cd here &&
-               test_must_fail git checkout newmaster
-       )
-'
-
-test_expect_success SYMLINKS 'die the same branch is already checked out (symlink)' '
-       head=$(git -C there rev-parse --git-path HEAD) &&
-       ref=$(git -C there symbolic-ref HEAD) &&
-       rm "$head" &&
-       ln -s "$ref" "$head" &&
-       test_must_fail git -C here checkout newmaster
-'
-
-test_expect_success 'not die the same branch is already checked out' '
-       (
-               cd here &&
-               git worktree add --force anothernewmaster newmaster
-       )
-'
-
-test_expect_success 'not die on re-checking out current branch' '
-       (
-               cd there &&
-               git checkout newmaster
-       )
-'
-
-test_expect_success '"add" from a bare repo' '
-       (
-               git clone --bare . bare &&
-               cd bare &&
-               git worktree add -b bare-master ../there2 master
-       )
-'
-
-test_expect_success 'checkout from a bare repo without "add"' '
-       (
-               cd bare &&
-               test_must_fail git checkout master
-       )
-'
-
-test_expect_success '"add" default branch of a bare repo' '
-       (
-               git clone --bare . bare2 &&
-               cd bare2 &&
-               git worktree add ../there3 master
-       )
-'
-
-test_expect_success 'checkout with grafts' '
-       test_when_finished rm .git/info/grafts &&
-       test_commit abc &&
-       SHA1=$(git rev-parse HEAD) &&
-       test_commit def &&
-       test_commit xyz &&
-       echo "$(git rev-parse HEAD) $SHA1" >.git/info/grafts &&
-       cat >expected <<-\EOF &&
-       xyz
-       abc
-       EOF
-       git log --format=%s -2 >actual &&
-       test_cmp expected actual &&
-       git worktree add --detach grafted master &&
-       git --git-dir=grafted/.git log --format=%s -2 >actual &&
-       test_cmp expected actual
-'
-
-test_expect_success '"add" from relative HEAD' '
-       test_commit a &&
-       test_commit b &&
-       test_commit c &&
-       git rev-parse HEAD~1 >expected &&
-       git worktree add relhead HEAD~1 &&
-       git -C relhead rev-parse HEAD >actual &&
-       test_cmp expected actual
-'
-
-test_expect_success '"add -b" with <branch> omitted' '
-       git worktree add -b burble flornk &&
-       test_cmp_rev HEAD burble
-'
-
-test_expect_success '"add --detach" with <branch> omitted' '
-       git worktree add --detach fishhook &&
-       git rev-parse HEAD >expected &&
-       git -C fishhook rev-parse HEAD >actual &&
-       test_cmp expected actual &&
-       test_must_fail git -C fishhook symbolic-ref HEAD
-'
-
-test_expect_success '"add" with <branch> omitted' '
-       git worktree add wiffle/bat &&
-       test_cmp_rev HEAD bat
-'
-
-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' '
-       git worktree add --detach mish/mash &&
-       test_must_fail git rev-parse mash -- &&
-       test_must_fail git -C mish/mash symbolic-ref HEAD
-'
-
-test_expect_success '"add" -b/-B mutually exclusive' '
-       test_must_fail git worktree add -b poodle -B poodle bamboo master
-'
-
-test_expect_success '"add" -b/--detach mutually exclusive' '
-       test_must_fail git worktree add -b poodle --detach bamboo master
-'
-
-test_expect_success '"add" -B/--detach mutually exclusive' '
-       test_must_fail git worktree add -B poodle --detach bamboo master
-'
-
-test_expect_success '"add -B" fails if the branch is checked out' '
-       git rev-parse newmaster >before &&
-       test_must_fail git worktree add -B newmaster bamboo master &&
-       git rev-parse newmaster >after &&
-       test_cmp before after
-'
-
-test_expect_success 'add -B' '
-       git worktree add -B poodle bamboo2 master^ &&
-       git -C bamboo2 symbolic-ref HEAD >actual &&
-       echo refs/heads/poodle >expected &&
-       test_cmp expected actual &&
-       test_cmp_rev master^ poodle
-'
-
-test_expect_success 'add --quiet' '
-       git worktree add --quiet another-worktree master 2>actual &&
-       test_must_be_empty actual
-'
-
-test_expect_success 'local clone from linked checkout' '
-       git clone --local here here-clone &&
-       ( cd here-clone && git fsck )
-'
-
-test_expect_success 'local clone --shared from linked checkout' '
-       git -C bare worktree add --detach ../baretree &&
-       git clone --local --shared baretree bare-clone &&
-       grep /bare/ bare-clone/.git/objects/info/alternates
-'
-
-test_expect_success '"add" worktree with --no-checkout' '
-       git worktree add --no-checkout -b swamp swamp &&
-       ! test -e swamp/init.t &&
-       git -C swamp reset --hard &&
-       test_cmp init.t swamp/init.t
-'
-
-test_expect_success '"add" worktree with --checkout' '
-       git worktree add --checkout -b swmap2 swamp2 &&
-       test_cmp init.t swamp2/init.t
-'
-
-test_expect_success 'put a worktree under rebase' '
-       git worktree add under-rebase &&
-       (
-               cd under-rebase &&
-               set_fake_editor &&
-               FAKE_LINES="edit 1" git rebase -i HEAD^ &&
-               git worktree list | grep "under-rebase.*detached HEAD"
-       )
-'
-
-test_expect_success 'add a worktree, checking out a rebased branch' '
-       test_must_fail git worktree add new-rebase under-rebase &&
-       ! test -d new-rebase
-'
-
-test_expect_success 'checking out a rebased branch from another worktree' '
-       git worktree add new-place &&
-       test_must_fail git -C new-place checkout under-rebase
-'
-
-test_expect_success 'not allow to delete a branch under rebase' '
-       (
-               cd under-rebase &&
-               test_must_fail git branch -D under-rebase
-       )
-'
-
-test_expect_success 'rename a branch under rebase not allowed' '
-       test_must_fail git branch -M under-rebase rebase-with-new-name
-'
-
-test_expect_success 'check out from current worktree branch ok' '
-       (
-               cd under-rebase &&
-               git checkout under-rebase &&
-               git checkout - &&
-               git rebase --abort
-       )
-'
-
-test_expect_success 'checkout a branch under bisect' '
-       git worktree add under-bisect &&
-       (
-               cd under-bisect &&
-               git bisect start &&
-               git bisect bad &&
-               git bisect good HEAD~2 &&
-               git worktree list | grep "under-bisect.*detached HEAD" &&
-               test_must_fail git worktree add new-bisect under-bisect &&
-               ! test -d new-bisect
-       )
-'
-
-test_expect_success 'rename a branch under bisect not allowed' '
-       test_must_fail git branch -M under-bisect bisect-with-new-name
-'
-# Is branch "refs/heads/$1" set to pull from "$2/$3"?
-test_branch_upstream () {
-       printf "%s\n" "$2" "refs/heads/$3" >expect.upstream &&
-       {
-               git config "branch.$1.remote" &&
-               git config "branch.$1.merge"
-       } >actual.upstream &&
-       test_cmp expect.upstream actual.upstream
-}
-
-test_expect_success '--track sets up tracking' '
-       test_when_finished rm -rf track &&
-       git worktree add --track -b track track master &&
-       test_branch_upstream track . master
-'
-
-# setup remote repository $1 and repository $2 with $1 set up as
-# remote.  The remote has two branches, master and foo.
-setup_remote_repo () {
-       git init $1 &&
-       (
-               cd $1 &&
-               test_commit $1_master &&
-               git checkout -b foo &&
-               test_commit upstream_foo
-       ) &&
-       git init $2 &&
-       (
-               cd $2 &&
-               test_commit $2_master &&
-               git remote add $1 ../$1 &&
-               git config remote.$1.fetch \
-                       "refs/heads/*:refs/remotes/$1/*" &&
-               git fetch --all
-       )
-}
-
-test_expect_success '--no-track avoids setting up tracking' '
-       test_when_finished rm -rf repo_upstream repo_local foo &&
-       setup_remote_repo repo_upstream repo_local &&
-       (
-               cd repo_local &&
-               git worktree add --no-track -b foo ../foo repo_upstream/foo
-       ) &&
-       (
-               cd foo &&
-               test_must_fail git config "branch.foo.remote" &&
-               test_must_fail git config "branch.foo.merge" &&
-               test_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo
-       )
-'
-
-test_expect_success '"add" <path> <non-existent-branch> fails' '
-       test_must_fail git worktree add foo non-existent
-'
-
-test_expect_success '"add" <path> <branch> dwims' '
-       test_when_finished rm -rf repo_upstream repo_dwim foo &&
-       setup_remote_repo repo_upstream repo_dwim &&
-       git init repo_dwim &&
-       (
-               cd repo_dwim &&
-               git worktree add ../foo foo
-       ) &&
-       (
-               cd foo &&
-               test_branch_upstream foo repo_upstream foo &&
-               test_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo
-       )
-'
-
-test_expect_success '"add" <path> <branch> dwims with checkout.defaultRemote' '
-       test_when_finished rm -rf repo_upstream repo_dwim foo &&
-       setup_remote_repo repo_upstream repo_dwim &&
-       git init repo_dwim &&
-       (
-               cd repo_dwim &&
-               git remote add repo_upstream2 ../repo_upstream &&
-               git fetch repo_upstream2 &&
-               test_must_fail git worktree add ../foo foo &&
-               git -c checkout.defaultRemote=repo_upstream worktree add ../foo foo &&
-               git status -uno --porcelain >status.actual &&
-               test_must_be_empty status.actual
-       ) &&
-       (
-               cd foo &&
-               test_branch_upstream foo repo_upstream foo &&
-               test_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo
-       )
-'
-
-test_expect_success 'git worktree add does not match remote' '
-       test_when_finished rm -rf repo_a repo_b foo &&
-       setup_remote_repo repo_a repo_b &&
-       (
-               cd repo_b &&
-               git worktree add ../foo
-       ) &&
-       (
-               cd foo &&
-               test_must_fail git config "branch.foo.remote" &&
-               test_must_fail git config "branch.foo.merge" &&
-               ! test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo
-       )
-'
-
-test_expect_success 'git worktree add --guess-remote sets up tracking' '
-       test_when_finished rm -rf repo_a repo_b foo &&
-       setup_remote_repo repo_a repo_b &&
-       (
-               cd repo_b &&
-               git worktree add --guess-remote ../foo
-       ) &&
-       (
-               cd foo &&
-               test_branch_upstream foo repo_a foo &&
-               test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo
-       )
-'
-
-test_expect_success 'git worktree add with worktree.guessRemote sets up tracking' '
-       test_when_finished rm -rf repo_a repo_b foo &&
-       setup_remote_repo repo_a repo_b &&
-       (
-               cd repo_b &&
-               git config worktree.guessRemote true &&
-               git worktree add ../foo
-       ) &&
-       (
-               cd foo &&
-               test_branch_upstream foo repo_a foo &&
-               test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo
-       )
-'
-
-test_expect_success 'git worktree --no-guess-remote option overrides config' '
-       test_when_finished rm -rf repo_a repo_b foo &&
-       setup_remote_repo repo_a repo_b &&
-       (
-               cd repo_b &&
-               git config worktree.guessRemote true &&
-               git worktree add --no-guess-remote ../foo
-       ) &&
-       (
-               cd foo &&
-               test_must_fail git config "branch.foo.remote" &&
-               test_must_fail git config "branch.foo.merge" &&
-               ! test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo
-       )
-'
-
-post_checkout_hook () {
-       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 &&
-       {
-               echo $ZERO_OID $(git rev-parse HEAD) 1 &&
-               echo $(pwd)/.git/worktrees/gumby &&
-               echo $(pwd)/gumby
-       } >hook.expect &&
-       git worktree add gumby &&
-       test_cmp hook.expect gumby/hook.actual
-'
-
-test_expect_success '"add" invokes post-checkout hook (detached)' '
-       post_checkout_hook &&
-       {
-               echo $ZERO_OID $(git rev-parse HEAD) 1 &&
-               echo $(pwd)/.git/worktrees/grumpy &&
-               echo $(pwd)/grumpy
-       } >hook.expect &&
-       git worktree add --detach grumpy &&
-       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 gloopy/hook.actual
-'
-
-test_expect_success '"add" in other worktree invokes post-checkout hook' '
-       post_checkout_hook &&
-       {
-               echo $ZERO_OID $(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 $ZERO_OID $(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_expect_success '"add" an existing but missing worktree' '
-       git worktree add --detach pneu &&
-       test_must_fail git worktree add --detach pneu &&
-       rm -fr pneu &&
-       test_must_fail git worktree add --detach pneu &&
-       git worktree add --force --detach pneu
-'
-
-test_expect_success '"add" an existing locked but missing worktree' '
-       git worktree add --detach gnoo &&
-       git worktree lock gnoo &&
-       test_when_finished "git worktree unlock gnoo || :" &&
-       rm -fr gnoo &&
-       test_must_fail git worktree add --detach gnoo &&
-       test_must_fail git worktree add --force --detach gnoo &&
-       git worktree add --force --force --detach gnoo
-'
-
-test_done
diff --git a/t/t2026-worktree-prune.sh b/t/t2026-worktree-prune.sh
deleted file mode 100755 (executable)
index b7d6d5d..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-#!/bin/sh
-
-test_description='prune $GIT_DIR/worktrees'
-
-. ./test-lib.sh
-
-test_expect_success initialize '
-       git commit --allow-empty -m init
-'
-
-test_expect_success 'worktree prune on normal repo' '
-       git worktree prune &&
-       test_must_fail git worktree prune abc
-'
-
-test_expect_success 'prune files inside $GIT_DIR/worktrees' '
-       mkdir .git/worktrees &&
-       : >.git/worktrees/abc &&
-       git worktree prune --verbose >actual &&
-       cat >expect <<EOF &&
-Removing worktrees/abc: not a valid directory
-EOF
-       test_i18ncmp expect actual &&
-       ! test -f .git/worktrees/abc &&
-       ! test -d .git/worktrees
-'
-
-test_expect_success 'prune directories without gitdir' '
-       mkdir -p .git/worktrees/def/abc &&
-       : >.git/worktrees/def/def &&
-       cat >expect <<EOF &&
-Removing worktrees/def: gitdir file does not exist
-EOF
-       git worktree prune --verbose >actual &&
-       test_i18ncmp expect actual &&
-       ! test -d .git/worktrees/def &&
-       ! test -d .git/worktrees
-'
-
-test_expect_success SANITY 'prune directories with unreadable gitdir' '
-       mkdir -p .git/worktrees/def/abc &&
-       : >.git/worktrees/def/def &&
-       : >.git/worktrees/def/gitdir &&
-       chmod u-r .git/worktrees/def/gitdir &&
-       git worktree prune --verbose >actual &&
-       test_i18ngrep "Removing worktrees/def: unable to read gitdir file" actual &&
-       ! test -d .git/worktrees/def &&
-       ! test -d .git/worktrees
-'
-
-test_expect_success 'prune directories with invalid gitdir' '
-       mkdir -p .git/worktrees/def/abc &&
-       : >.git/worktrees/def/def &&
-       : >.git/worktrees/def/gitdir &&
-       git worktree prune --verbose >actual &&
-       test_i18ngrep "Removing worktrees/def: invalid gitdir file" actual &&
-       ! test -d .git/worktrees/def &&
-       ! test -d .git/worktrees
-'
-
-test_expect_success 'prune directories with gitdir pointing to nowhere' '
-       mkdir -p .git/worktrees/def/abc &&
-       : >.git/worktrees/def/def &&
-       echo "$(pwd)"/nowhere >.git/worktrees/def/gitdir &&
-       git worktree prune --verbose >actual &&
-       test_i18ngrep "Removing worktrees/def: gitdir file points to non-existent location" actual &&
-       ! test -d .git/worktrees/def &&
-       ! test -d .git/worktrees
-'
-
-test_expect_success 'not prune locked checkout' '
-       test_when_finished rm -r .git/worktrees &&
-       mkdir -p .git/worktrees/ghi &&
-       : >.git/worktrees/ghi/locked &&
-       git worktree prune &&
-       test -d .git/worktrees/ghi
-'
-
-test_expect_success 'not prune recent checkouts' '
-       test_when_finished rm -r .git/worktrees &&
-       git worktree add jlm HEAD &&
-       test -d .git/worktrees/jlm &&
-       rm -rf jlm &&
-       git worktree prune --verbose --expire=2.days.ago &&
-       test -d .git/worktrees/jlm
-'
-
-test_expect_success 'not prune proper checkouts' '
-       test_when_finished rm -r .git/worktrees &&
-       git worktree add --detach "$PWD/nop" master &&
-       git worktree prune &&
-       test -d .git/worktrees/nop
-'
-
-test_done
diff --git a/t/t2027-worktree-list.sh b/t/t2027-worktree-list.sh
deleted file mode 100755 (executable)
index bb6fb9b..0000000
+++ /dev/null
@@ -1,154 +0,0 @@
-#!/bin/sh
-
-test_description='test git worktree list'
-
-. ./test-lib.sh
-
-test_expect_success 'setup' '
-       test_commit init
-'
-
-test_expect_success 'rev-parse --git-common-dir on main worktree' '
-       git rev-parse --git-common-dir >actual &&
-       echo .git >expected &&
-       test_cmp expected actual &&
-       mkdir sub &&
-       git -C sub rev-parse --git-common-dir >actual2 &&
-       echo ../.git >expected2 &&
-       test_cmp expected2 actual2
-'
-
-test_expect_success 'rev-parse --git-path objects linked worktree' '
-       echo "$(git rev-parse --show-toplevel)/.git/objects" >expect &&
-       test_when_finished "rm -rf linked-tree actual expect && git worktree prune" &&
-       git worktree add --detach linked-tree master &&
-       git -C linked-tree rev-parse --git-path objects >actual &&
-       test_cmp expect actual
-'
-
-test_expect_success '"list" all worktrees from main' '
-       echo "$(git rev-parse --show-toplevel) $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect &&
-       test_when_finished "rm -rf here out actual expect && git worktree prune" &&
-       git worktree add --detach here master &&
-       echo "$(git -C here rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >>expect &&
-       git worktree list >out &&
-       sed "s/  */ /g" <out >actual &&
-       test_cmp expect actual
-'
-
-test_expect_success '"list" all worktrees from linked' '
-       echo "$(git rev-parse --show-toplevel) $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect &&
-       test_when_finished "rm -rf here out actual expect && git worktree prune" &&
-       git worktree add --detach here master &&
-       echo "$(git -C here rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >>expect &&
-       git -C here worktree list >out &&
-       sed "s/  */ /g" <out >actual &&
-       test_cmp expect actual
-'
-
-test_expect_success '"list" all worktrees --porcelain' '
-       echo "worktree $(git rev-parse --show-toplevel)" >expect &&
-       echo "HEAD $(git rev-parse HEAD)" >>expect &&
-       echo "branch $(git symbolic-ref HEAD)" >>expect &&
-       echo >>expect &&
-       test_when_finished "rm -rf here actual expect && git worktree prune" &&
-       git worktree add --detach here master &&
-       echo "worktree $(git -C here rev-parse --show-toplevel)" >>expect &&
-       echo "HEAD $(git rev-parse HEAD)" >>expect &&
-       echo "detached" >>expect &&
-       echo >>expect &&
-       git worktree list --porcelain >actual &&
-       test_cmp expect actual
-'
-
-test_expect_success 'bare repo setup' '
-       git init --bare bare1 &&
-       echo "data" >file1 &&
-       git add file1 &&
-       git commit -m"File1: add data" &&
-       git push bare1 master &&
-       git reset --hard HEAD^
-'
-
-test_expect_success '"list" all worktrees from bare main' '
-       test_when_finished "rm -rf there out actual expect && git -C bare1 worktree prune" &&
-       git -C bare1 worktree add --detach ../there master &&
-       echo "$(pwd)/bare1 (bare)" >expect &&
-       echo "$(git -C there rev-parse --show-toplevel) $(git -C there rev-parse --short HEAD) (detached HEAD)" >>expect &&
-       git -C bare1 worktree list >out &&
-       sed "s/  */ /g" <out >actual &&
-       test_cmp expect actual
-'
-
-test_expect_success '"list" all worktrees --porcelain from bare main' '
-       test_when_finished "rm -rf there actual expect && git -C bare1 worktree prune" &&
-       git -C bare1 worktree add --detach ../there master &&
-       echo "worktree $(pwd)/bare1" >expect &&
-       echo "bare" >>expect &&
-       echo >>expect &&
-       echo "worktree $(git -C there rev-parse --show-toplevel)" >>expect &&
-       echo "HEAD $(git -C there rev-parse HEAD)" >>expect &&
-       echo "detached" >>expect &&
-       echo >>expect &&
-       git -C bare1 worktree list --porcelain >actual &&
-       test_cmp expect actual
-'
-
-test_expect_success '"list" all worktrees from linked with a bare main' '
-       test_when_finished "rm -rf there out actual expect && git -C bare1 worktree prune" &&
-       git -C bare1 worktree add --detach ../there master &&
-       echo "$(pwd)/bare1 (bare)" >expect &&
-       echo "$(git -C there rev-parse --show-toplevel) $(git -C there rev-parse --short HEAD) (detached HEAD)" >>expect &&
-       git -C there worktree list >out &&
-       sed "s/  */ /g" <out >actual &&
-       test_cmp expect actual
-'
-
-test_expect_success 'bare repo cleanup' '
-       rm -rf bare1
-'
-
-test_expect_success 'broken main worktree still at the top' '
-       git init broken-main &&
-       (
-               cd broken-main &&
-               test_commit new &&
-               git worktree add linked &&
-               cat >expected <<-EOF &&
-               worktree $(pwd)
-               HEAD $ZERO_OID
-
-               EOF
-               cd linked &&
-               echo "worktree $(pwd)" >expected &&
-               echo "ref: .broken" >../.git/HEAD &&
-               git worktree list --porcelain >out &&
-               head -n 3 out >actual &&
-               test_cmp ../expected actual &&
-               git worktree list >out &&
-               head -n 1 out >actual.2 &&
-               grep -F "(error)" actual.2
-       )
-'
-
-test_expect_success 'linked worktrees are sorted' '
-       mkdir sorted &&
-       git init sorted/main &&
-       (
-               cd sorted/main &&
-               test_tick &&
-               test_commit new &&
-               git worktree add ../first &&
-               git worktree add ../second &&
-               git worktree list --porcelain >out &&
-               grep ^worktree out >actual
-       ) &&
-       cat >expected <<-EOF &&
-       worktree $(pwd)/sorted/main
-       worktree $(pwd)/sorted/first
-       worktree $(pwd)/sorted/second
-       EOF
-       test_cmp expected sorted/main/actual
-'
-
-test_done
diff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh
deleted file mode 100755 (executable)
index 33c0337..0000000
+++ /dev/null
@@ -1,188 +0,0 @@
-#!/bin/sh
-
-test_description='test git worktree move, remove, lock and unlock'
-
-. ./test-lib.sh
-
-test_expect_success 'setup' '
-       test_commit init &&
-       git worktree add source &&
-       git worktree list --porcelain >out &&
-       grep "^worktree" out >actual &&
-       cat <<-EOF >expected &&
-       worktree $(pwd)
-       worktree $(pwd)/source
-       EOF
-       test_cmp expected actual
-'
-
-test_expect_success 'lock main worktree' '
-       test_must_fail git worktree lock .
-'
-
-test_expect_success 'lock linked worktree' '
-       git worktree lock --reason hahaha source &&
-       echo hahaha >expected &&
-       test_cmp expected .git/worktrees/source/locked
-'
-
-test_expect_success 'lock linked worktree from another worktree' '
-       rm .git/worktrees/source/locked &&
-       git worktree add elsewhere &&
-       git -C elsewhere worktree lock --reason hahaha ../source &&
-       echo hahaha >expected &&
-       test_cmp expected .git/worktrees/source/locked
-'
-
-test_expect_success 'lock worktree twice' '
-       test_must_fail git worktree lock source &&
-       echo hahaha >expected &&
-       test_cmp expected .git/worktrees/source/locked
-'
-
-test_expect_success 'lock worktree twice (from the locked worktree)' '
-       test_must_fail git -C source worktree lock . &&
-       echo hahaha >expected &&
-       test_cmp expected .git/worktrees/source/locked
-'
-
-test_expect_success 'unlock main worktree' '
-       test_must_fail git worktree unlock .
-'
-
-test_expect_success 'unlock linked worktree' '
-       git worktree unlock source &&
-       test_path_is_missing .git/worktrees/source/locked
-'
-
-test_expect_success 'unlock worktree twice' '
-       test_must_fail git worktree unlock source &&
-       test_path_is_missing .git/worktrees/source/locked
-'
-
-test_expect_success 'move non-worktree' '
-       mkdir abc &&
-       test_must_fail git worktree move abc def
-'
-
-test_expect_success 'move locked worktree' '
-       git worktree lock source &&
-       test_when_finished "git worktree unlock source" &&
-       test_must_fail git worktree move source destination
-'
-
-test_expect_success 'move worktree' '
-       git worktree move source destination &&
-       test_path_is_missing source &&
-       git worktree list --porcelain >out &&
-       grep "^worktree.*/destination$" out &&
-       ! grep "^worktree.*/source$" out &&
-       git -C destination log --format=%s >actual2 &&
-       echo init >expected2 &&
-       test_cmp expected2 actual2
-'
-
-test_expect_success 'move main worktree' '
-       test_must_fail git worktree move . def
-'
-
-test_expect_success 'move worktree to another dir' '
-       mkdir some-dir &&
-       git worktree move destination some-dir &&
-       test_when_finished "git worktree move some-dir/destination destination" &&
-       test_path_is_missing destination &&
-       git worktree list --porcelain >out &&
-       grep "^worktree.*/some-dir/destination$" out &&
-       git -C some-dir/destination log --format=%s >actual2 &&
-       echo init >expected2 &&
-       test_cmp expected2 actual2
-'
-
-test_expect_success 'move locked worktree (force)' '
-       test_when_finished "
-               git worktree unlock flump || :
-               git worktree remove flump || :
-               git worktree unlock ploof || :
-               git worktree remove ploof || :
-               " &&
-       git worktree add --detach flump &&
-       git worktree lock flump &&
-       test_must_fail git worktree move flump ploof" &&
-       test_must_fail git worktree move --force flump ploof" &&
-       git worktree move --force --force flump ploof
-'
-
-test_expect_success 'remove main worktree' '
-       test_must_fail git worktree remove .
-'
-
-test_expect_success 'remove locked worktree' '
-       git worktree lock destination &&
-       test_when_finished "git worktree unlock destination" &&
-       test_must_fail git worktree remove destination
-'
-
-test_expect_success 'remove worktree with dirty tracked file' '
-       echo dirty >>destination/init.t &&
-       test_when_finished "git -C destination checkout init.t" &&
-       test_must_fail git worktree remove destination
-'
-
-test_expect_success 'remove worktree with untracked file' '
-       : >destination/untracked &&
-       test_must_fail git worktree remove destination
-'
-
-test_expect_success 'force remove worktree with untracked file' '
-       git worktree remove --force destination &&
-       test_path_is_missing destination
-'
-
-test_expect_success 'remove missing worktree' '
-       git worktree add to-be-gone &&
-       test -d .git/worktrees/to-be-gone &&
-       mv to-be-gone gone &&
-       git worktree remove to-be-gone &&
-       test_path_is_missing .git/worktrees/to-be-gone
-'
-
-test_expect_success 'NOT remove missing-but-locked worktree' '
-       git worktree add gone-but-locked &&
-       git worktree lock gone-but-locked &&
-       test -d .git/worktrees/gone-but-locked &&
-       mv gone-but-locked really-gone-now &&
-       test_must_fail git worktree remove gone-but-locked &&
-       test_path_is_dir .git/worktrees/gone-but-locked
-'
-
-test_expect_success 'proper error when worktree not found' '
-       for i in noodle noodle/bork
-       do
-               test_must_fail git worktree lock $i 2>err &&
-               test_i18ngrep "not a working tree" err || return 1
-       done
-'
-
-test_expect_success 'remove locked worktree (force)' '
-       git worktree add --detach gumby &&
-       test_when_finished "git worktree remove gumby || :" &&
-       git worktree lock gumby &&
-       test_when_finished "git worktree unlock gumby || :" &&
-       test_must_fail git worktree remove gumby &&
-       test_must_fail git worktree remove --force gumby &&
-       git worktree remove --force --force gumby
-'
-
-test_expect_success 'remove cleans up .git/worktrees when empty' '
-       git init moog &&
-       (
-               cd moog &&
-               test_commit bim &&
-               git worktree add --detach goom &&
-               test_path_exists .git/worktrees &&
-               git worktree remove goom &&
-               test_path_is_missing .git/worktrees
-       )
-'
-
-test_done
diff --git a/t/t2029-worktree-config.sh b/t/t2029-worktree-config.sh
deleted file mode 100755 (executable)
index 286121d..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-#!/bin/sh
-
-test_description="config file in multi worktree"
-
-. ./test-lib.sh
-
-test_expect_success 'setup' '
-       test_commit start
-'
-
-test_expect_success 'config --worktree in single worktree' '
-       git config --worktree foo.bar true &&
-       test_cmp_config true foo.bar
-'
-
-test_expect_success 'add worktrees' '
-       git worktree add wt1 &&
-       git worktree add wt2
-'
-
-test_expect_success 'config --worktree without extension' '
-       test_must_fail git config --worktree foo.bar false
-'
-
-test_expect_success 'enable worktreeConfig extension' '
-       git config extensions.worktreeConfig true &&
-       test_cmp_config true extensions.worktreeConfig
-'
-
-test_expect_success 'config is shared as before' '
-       git config this.is shared &&
-       test_cmp_config shared this.is &&
-       test_cmp_config -C wt1 shared this.is &&
-       test_cmp_config -C wt2 shared this.is
-'
-
-test_expect_success 'config is shared (set from another worktree)' '
-       git -C wt1 config that.is also-shared &&
-       test_cmp_config also-shared that.is &&
-       test_cmp_config -C wt1 also-shared that.is &&
-       test_cmp_config -C wt2 also-shared that.is
-'
-
-test_expect_success 'config private to main worktree' '
-       git config --worktree this.is for-main &&
-       test_cmp_config for-main this.is &&
-       test_cmp_config -C wt1 shared this.is &&
-       test_cmp_config -C wt2 shared this.is
-'
-
-test_expect_success 'config private to linked worktree' '
-       git -C wt1 config --worktree this.is for-wt1 &&
-       test_cmp_config for-main this.is &&
-       test_cmp_config -C wt1 for-wt1 this.is &&
-       test_cmp_config -C wt2 shared this.is
-'
-
-test_expect_success 'core.bare no longer for main only' '
-       test_config core.bare true &&
-       test "$(git rev-parse --is-bare-repository)" = true &&
-       test "$(git -C wt1 rev-parse --is-bare-repository)" = true &&
-       test "$(git -C wt2 rev-parse --is-bare-repository)" = true
-'
-
-test_expect_success 'per-worktree core.bare is picked up' '
-       git -C wt1 config --worktree core.bare true &&
-       test "$(git rev-parse --is-bare-repository)" = false &&
-       test "$(git -C wt1 rev-parse --is-bare-repository)" = true &&
-       test "$(git -C wt2 rev-parse --is-bare-repository)" = false
-'
-
-test_expect_success 'config.worktree no longer read without extension' '
-       git config --unset extensions.worktreeConfig &&
-       test_cmp_config shared this.is &&
-       test_cmp_config -C wt1 shared this.is &&
-       test_cmp_config -C wt2 shared this.is
-'
-
-test_done
diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh
new file mode 100755 (executable)
index 0000000..286bba3
--- /dev/null
@@ -0,0 +1,573 @@
+#!/bin/sh
+
+test_description='test git worktree add'
+
+. ./test-lib.sh
+
+. "$TEST_DIRECTORY"/lib-rebase.sh
+
+test_expect_success 'setup' '
+       test_commit init
+'
+
+test_expect_success '"add" an existing worktree' '
+       mkdir -p existing/subtree &&
+       test_must_fail git worktree add --detach existing master
+'
+
+test_expect_success '"add" an existing empty worktree' '
+       mkdir existing_empty &&
+       git worktree add --detach existing_empty master
+'
+
+test_expect_success '"add" using shorthand - fails when no previous branch' '
+       test_must_fail git worktree add existing_short -
+'
+
+test_expect_success '"add" using - shorthand' '
+       git checkout -b newbranch &&
+       echo hello >myworld &&
+       git add myworld &&
+       git commit -m myworld &&
+       git checkout master &&
+       git worktree add short-hand - &&
+       echo refs/heads/newbranch >expect &&
+       git -C short-hand rev-parse --symbolic-full-name HEAD >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '"add" refuses to checkout locked branch' '
+       test_must_fail git worktree add zere master &&
+       ! test -d zere &&
+       ! test -d .git/worktrees/zere
+'
+
+test_expect_success 'checking out paths not complaining about linked checkouts' '
+       (
+       cd existing_empty &&
+       echo dirty >>init.t &&
+       git checkout master -- init.t
+       )
+'
+
+test_expect_success '"add" worktree' '
+       git rev-parse HEAD >expect &&
+       git worktree add --detach here master &&
+       (
+               cd here &&
+               test_cmp ../init.t init.t &&
+               test_must_fail git symbolic-ref HEAD &&
+               git rev-parse HEAD >actual &&
+               test_cmp ../expect actual &&
+               git fsck
+       )
+'
+
+test_expect_success '"add" worktree with lock' '
+       git rev-parse HEAD >expect &&
+       git worktree add --detach --lock here-with-lock master &&
+       test -f .git/worktrees/here-with-lock/locked
+'
+
+test_expect_success '"add" worktree from a subdir' '
+       (
+               mkdir sub &&
+               cd sub &&
+               git worktree add --detach here master &&
+               cd here &&
+               test_cmp ../../init.t init.t
+       )
+'
+
+test_expect_success '"add" from a linked checkout' '
+       (
+               cd here &&
+               git worktree add --detach nested-here master &&
+               cd nested-here &&
+               git fsck
+       )
+'
+
+test_expect_success '"add" worktree creating new branch' '
+       git worktree add -b newmaster there master &&
+       (
+               cd there &&
+               test_cmp ../init.t init.t &&
+               git symbolic-ref HEAD >actual &&
+               echo refs/heads/newmaster >expect &&
+               test_cmp expect actual &&
+               git fsck
+       )
+'
+
+test_expect_success 'die the same branch is already checked out' '
+       (
+               cd here &&
+               test_must_fail git checkout newmaster
+       )
+'
+
+test_expect_success SYMLINKS 'die the same branch is already checked out (symlink)' '
+       head=$(git -C there rev-parse --git-path HEAD) &&
+       ref=$(git -C there symbolic-ref HEAD) &&
+       rm "$head" &&
+       ln -s "$ref" "$head" &&
+       test_must_fail git -C here checkout newmaster
+'
+
+test_expect_success 'not die the same branch is already checked out' '
+       (
+               cd here &&
+               git worktree add --force anothernewmaster newmaster
+       )
+'
+
+test_expect_success 'not die on re-checking out current branch' '
+       (
+               cd there &&
+               git checkout newmaster
+       )
+'
+
+test_expect_success '"add" from a bare repo' '
+       (
+               git clone --bare . bare &&
+               cd bare &&
+               git worktree add -b bare-master ../there2 master
+       )
+'
+
+test_expect_success 'checkout from a bare repo without "add"' '
+       (
+               cd bare &&
+               test_must_fail git checkout master
+       )
+'
+
+test_expect_success '"add" default branch of a bare repo' '
+       (
+               git clone --bare . bare2 &&
+               cd bare2 &&
+               git worktree add ../there3 master
+       )
+'
+
+test_expect_success 'checkout with grafts' '
+       test_when_finished rm .git/info/grafts &&
+       test_commit abc &&
+       SHA1=$(git rev-parse HEAD) &&
+       test_commit def &&
+       test_commit xyz &&
+       echo "$(git rev-parse HEAD) $SHA1" >.git/info/grafts &&
+       cat >expected <<-\EOF &&
+       xyz
+       abc
+       EOF
+       git log --format=%s -2 >actual &&
+       test_cmp expected actual &&
+       git worktree add --detach grafted master &&
+       git --git-dir=grafted/.git log --format=%s -2 >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success '"add" from relative HEAD' '
+       test_commit a &&
+       test_commit b &&
+       test_commit c &&
+       git rev-parse HEAD~1 >expected &&
+       git worktree add relhead HEAD~1 &&
+       git -C relhead rev-parse HEAD >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success '"add -b" with <branch> omitted' '
+       git worktree add -b burble flornk &&
+       test_cmp_rev HEAD burble
+'
+
+test_expect_success '"add --detach" with <branch> omitted' '
+       git worktree add --detach fishhook &&
+       git rev-parse HEAD >expected &&
+       git -C fishhook rev-parse HEAD >actual &&
+       test_cmp expected actual &&
+       test_must_fail git -C fishhook symbolic-ref HEAD
+'
+
+test_expect_success '"add" with <branch> omitted' '
+       git worktree add wiffle/bat &&
+       test_cmp_rev HEAD bat
+'
+
+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' '
+       git worktree add --detach mish/mash &&
+       test_must_fail git rev-parse mash -- &&
+       test_must_fail git -C mish/mash symbolic-ref HEAD
+'
+
+test_expect_success '"add" -b/-B mutually exclusive' '
+       test_must_fail git worktree add -b poodle -B poodle bamboo master
+'
+
+test_expect_success '"add" -b/--detach mutually exclusive' '
+       test_must_fail git worktree add -b poodle --detach bamboo master
+'
+
+test_expect_success '"add" -B/--detach mutually exclusive' '
+       test_must_fail git worktree add -B poodle --detach bamboo master
+'
+
+test_expect_success '"add -B" fails if the branch is checked out' '
+       git rev-parse newmaster >before &&
+       test_must_fail git worktree add -B newmaster bamboo master &&
+       git rev-parse newmaster >after &&
+       test_cmp before after
+'
+
+test_expect_success 'add -B' '
+       git worktree add -B poodle bamboo2 master^ &&
+       git -C bamboo2 symbolic-ref HEAD >actual &&
+       echo refs/heads/poodle >expected &&
+       test_cmp expected actual &&
+       test_cmp_rev master^ poodle
+'
+
+test_expect_success 'add --quiet' '
+       git worktree add --quiet another-worktree master 2>actual &&
+       test_must_be_empty actual
+'
+
+test_expect_success 'local clone from linked checkout' '
+       git clone --local here here-clone &&
+       ( cd here-clone && git fsck )
+'
+
+test_expect_success 'local clone --shared from linked checkout' '
+       git -C bare worktree add --detach ../baretree &&
+       git clone --local --shared baretree bare-clone &&
+       grep /bare/ bare-clone/.git/objects/info/alternates
+'
+
+test_expect_success '"add" worktree with --no-checkout' '
+       git worktree add --no-checkout -b swamp swamp &&
+       ! test -e swamp/init.t &&
+       git -C swamp reset --hard &&
+       test_cmp init.t swamp/init.t
+'
+
+test_expect_success '"add" worktree with --checkout' '
+       git worktree add --checkout -b swmap2 swamp2 &&
+       test_cmp init.t swamp2/init.t
+'
+
+test_expect_success 'put a worktree under rebase' '
+       git worktree add under-rebase &&
+       (
+               cd under-rebase &&
+               set_fake_editor &&
+               FAKE_LINES="edit 1" git rebase -i HEAD^ &&
+               git worktree list | grep "under-rebase.*detached HEAD"
+       )
+'
+
+test_expect_success 'add a worktree, checking out a rebased branch' '
+       test_must_fail git worktree add new-rebase under-rebase &&
+       ! test -d new-rebase
+'
+
+test_expect_success 'checking out a rebased branch from another worktree' '
+       git worktree add new-place &&
+       test_must_fail git -C new-place checkout under-rebase
+'
+
+test_expect_success 'not allow to delete a branch under rebase' '
+       (
+               cd under-rebase &&
+               test_must_fail git branch -D under-rebase
+       )
+'
+
+test_expect_success 'rename a branch under rebase not allowed' '
+       test_must_fail git branch -M under-rebase rebase-with-new-name
+'
+
+test_expect_success 'check out from current worktree branch ok' '
+       (
+               cd under-rebase &&
+               git checkout under-rebase &&
+               git checkout - &&
+               git rebase --abort
+       )
+'
+
+test_expect_success 'checkout a branch under bisect' '
+       git worktree add under-bisect &&
+       (
+               cd under-bisect &&
+               git bisect start &&
+               git bisect bad &&
+               git bisect good HEAD~2 &&
+               git worktree list | grep "under-bisect.*detached HEAD" &&
+               test_must_fail git worktree add new-bisect under-bisect &&
+               ! test -d new-bisect
+       )
+'
+
+test_expect_success 'rename a branch under bisect not allowed' '
+       test_must_fail git branch -M under-bisect bisect-with-new-name
+'
+# Is branch "refs/heads/$1" set to pull from "$2/$3"?
+test_branch_upstream () {
+       printf "%s\n" "$2" "refs/heads/$3" >expect.upstream &&
+       {
+               git config "branch.$1.remote" &&
+               git config "branch.$1.merge"
+       } >actual.upstream &&
+       test_cmp expect.upstream actual.upstream
+}
+
+test_expect_success '--track sets up tracking' '
+       test_when_finished rm -rf track &&
+       git worktree add --track -b track track master &&
+       test_branch_upstream track . master
+'
+
+# setup remote repository $1 and repository $2 with $1 set up as
+# remote.  The remote has two branches, master and foo.
+setup_remote_repo () {
+       git init $1 &&
+       (
+               cd $1 &&
+               test_commit $1_master &&
+               git checkout -b foo &&
+               test_commit upstream_foo
+       ) &&
+       git init $2 &&
+       (
+               cd $2 &&
+               test_commit $2_master &&
+               git remote add $1 ../$1 &&
+               git config remote.$1.fetch \
+                       "refs/heads/*:refs/remotes/$1/*" &&
+               git fetch --all
+       )
+}
+
+test_expect_success '--no-track avoids setting up tracking' '
+       test_when_finished rm -rf repo_upstream repo_local foo &&
+       setup_remote_repo repo_upstream repo_local &&
+       (
+               cd repo_local &&
+               git worktree add --no-track -b foo ../foo repo_upstream/foo
+       ) &&
+       (
+               cd foo &&
+               test_must_fail git config "branch.foo.remote" &&
+               test_must_fail git config "branch.foo.merge" &&
+               test_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo
+       )
+'
+
+test_expect_success '"add" <path> <non-existent-branch> fails' '
+       test_must_fail git worktree add foo non-existent
+'
+
+test_expect_success '"add" <path> <branch> dwims' '
+       test_when_finished rm -rf repo_upstream repo_dwim foo &&
+       setup_remote_repo repo_upstream repo_dwim &&
+       git init repo_dwim &&
+       (
+               cd repo_dwim &&
+               git worktree add ../foo foo
+       ) &&
+       (
+               cd foo &&
+               test_branch_upstream foo repo_upstream foo &&
+               test_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo
+       )
+'
+
+test_expect_success '"add" <path> <branch> dwims with checkout.defaultRemote' '
+       test_when_finished rm -rf repo_upstream repo_dwim foo &&
+       setup_remote_repo repo_upstream repo_dwim &&
+       git init repo_dwim &&
+       (
+               cd repo_dwim &&
+               git remote add repo_upstream2 ../repo_upstream &&
+               git fetch repo_upstream2 &&
+               test_must_fail git worktree add ../foo foo &&
+               git -c checkout.defaultRemote=repo_upstream worktree add ../foo foo &&
+               git status -uno --porcelain >status.actual &&
+               test_must_be_empty status.actual
+       ) &&
+       (
+               cd foo &&
+               test_branch_upstream foo repo_upstream foo &&
+               test_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo
+       )
+'
+
+test_expect_success 'git worktree add does not match remote' '
+       test_when_finished rm -rf repo_a repo_b foo &&
+       setup_remote_repo repo_a repo_b &&
+       (
+               cd repo_b &&
+               git worktree add ../foo
+       ) &&
+       (
+               cd foo &&
+               test_must_fail git config "branch.foo.remote" &&
+               test_must_fail git config "branch.foo.merge" &&
+               ! test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo
+       )
+'
+
+test_expect_success 'git worktree add --guess-remote sets up tracking' '
+       test_when_finished rm -rf repo_a repo_b foo &&
+       setup_remote_repo repo_a repo_b &&
+       (
+               cd repo_b &&
+               git worktree add --guess-remote ../foo
+       ) &&
+       (
+               cd foo &&
+               test_branch_upstream foo repo_a foo &&
+               test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo
+       )
+'
+
+test_expect_success 'git worktree add with worktree.guessRemote sets up tracking' '
+       test_when_finished rm -rf repo_a repo_b foo &&
+       setup_remote_repo repo_a repo_b &&
+       (
+               cd repo_b &&
+               git config worktree.guessRemote true &&
+               git worktree add ../foo
+       ) &&
+       (
+               cd foo &&
+               test_branch_upstream foo repo_a foo &&
+               test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo
+       )
+'
+
+test_expect_success 'git worktree --no-guess-remote option overrides config' '
+       test_when_finished rm -rf repo_a repo_b foo &&
+       setup_remote_repo repo_a repo_b &&
+       (
+               cd repo_b &&
+               git config worktree.guessRemote true &&
+               git worktree add --no-guess-remote ../foo
+       ) &&
+       (
+               cd foo &&
+               test_must_fail git config "branch.foo.remote" &&
+               test_must_fail git config "branch.foo.merge" &&
+               ! test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo
+       )
+'
+
+post_checkout_hook () {
+       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 &&
+       {
+               echo $ZERO_OID $(git rev-parse HEAD) 1 &&
+               echo $(pwd)/.git/worktrees/gumby &&
+               echo $(pwd)/gumby
+       } >hook.expect &&
+       git worktree add gumby &&
+       test_cmp hook.expect gumby/hook.actual
+'
+
+test_expect_success '"add" invokes post-checkout hook (detached)' '
+       post_checkout_hook &&
+       {
+               echo $ZERO_OID $(git rev-parse HEAD) 1 &&
+               echo $(pwd)/.git/worktrees/grumpy &&
+               echo $(pwd)/grumpy
+       } >hook.expect &&
+       git worktree add --detach grumpy &&
+       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 gloopy/hook.actual
+'
+
+test_expect_success '"add" in other worktree invokes post-checkout hook' '
+       post_checkout_hook &&
+       {
+               echo $ZERO_OID $(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 $ZERO_OID $(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_expect_success '"add" an existing but missing worktree' '
+       git worktree add --detach pneu &&
+       test_must_fail git worktree add --detach pneu &&
+       rm -fr pneu &&
+       test_must_fail git worktree add --detach pneu &&
+       git worktree add --force --detach pneu
+'
+
+test_expect_success '"add" an existing locked but missing worktree' '
+       git worktree add --detach gnoo &&
+       git worktree lock gnoo &&
+       test_when_finished "git worktree unlock gnoo || :" &&
+       rm -fr gnoo &&
+       test_must_fail git worktree add --detach gnoo &&
+       test_must_fail git worktree add --force --detach gnoo &&
+       git worktree add --force --force --detach gnoo
+'
+
+test_done
diff --git a/t/t2401-worktree-prune.sh b/t/t2401-worktree-prune.sh
new file mode 100755 (executable)
index 0000000..b7d6d5d
--- /dev/null
@@ -0,0 +1,95 @@
+#!/bin/sh
+
+test_description='prune $GIT_DIR/worktrees'
+
+. ./test-lib.sh
+
+test_expect_success initialize '
+       git commit --allow-empty -m init
+'
+
+test_expect_success 'worktree prune on normal repo' '
+       git worktree prune &&
+       test_must_fail git worktree prune abc
+'
+
+test_expect_success 'prune files inside $GIT_DIR/worktrees' '
+       mkdir .git/worktrees &&
+       : >.git/worktrees/abc &&
+       git worktree prune --verbose >actual &&
+       cat >expect <<EOF &&
+Removing worktrees/abc: not a valid directory
+EOF
+       test_i18ncmp expect actual &&
+       ! test -f .git/worktrees/abc &&
+       ! test -d .git/worktrees
+'
+
+test_expect_success 'prune directories without gitdir' '
+       mkdir -p .git/worktrees/def/abc &&
+       : >.git/worktrees/def/def &&
+       cat >expect <<EOF &&
+Removing worktrees/def: gitdir file does not exist
+EOF
+       git worktree prune --verbose >actual &&
+       test_i18ncmp expect actual &&
+       ! test -d .git/worktrees/def &&
+       ! test -d .git/worktrees
+'
+
+test_expect_success SANITY 'prune directories with unreadable gitdir' '
+       mkdir -p .git/worktrees/def/abc &&
+       : >.git/worktrees/def/def &&
+       : >.git/worktrees/def/gitdir &&
+       chmod u-r .git/worktrees/def/gitdir &&
+       git worktree prune --verbose >actual &&
+       test_i18ngrep "Removing worktrees/def: unable to read gitdir file" actual &&
+       ! test -d .git/worktrees/def &&
+       ! test -d .git/worktrees
+'
+
+test_expect_success 'prune directories with invalid gitdir' '
+       mkdir -p .git/worktrees/def/abc &&
+       : >.git/worktrees/def/def &&
+       : >.git/worktrees/def/gitdir &&
+       git worktree prune --verbose >actual &&
+       test_i18ngrep "Removing worktrees/def: invalid gitdir file" actual &&
+       ! test -d .git/worktrees/def &&
+       ! test -d .git/worktrees
+'
+
+test_expect_success 'prune directories with gitdir pointing to nowhere' '
+       mkdir -p .git/worktrees/def/abc &&
+       : >.git/worktrees/def/def &&
+       echo "$(pwd)"/nowhere >.git/worktrees/def/gitdir &&
+       git worktree prune --verbose >actual &&
+       test_i18ngrep "Removing worktrees/def: gitdir file points to non-existent location" actual &&
+       ! test -d .git/worktrees/def &&
+       ! test -d .git/worktrees
+'
+
+test_expect_success 'not prune locked checkout' '
+       test_when_finished rm -r .git/worktrees &&
+       mkdir -p .git/worktrees/ghi &&
+       : >.git/worktrees/ghi/locked &&
+       git worktree prune &&
+       test -d .git/worktrees/ghi
+'
+
+test_expect_success 'not prune recent checkouts' '
+       test_when_finished rm -r .git/worktrees &&
+       git worktree add jlm HEAD &&
+       test -d .git/worktrees/jlm &&
+       rm -rf jlm &&
+       git worktree prune --verbose --expire=2.days.ago &&
+       test -d .git/worktrees/jlm
+'
+
+test_expect_success 'not prune proper checkouts' '
+       test_when_finished rm -r .git/worktrees &&
+       git worktree add --detach "$PWD/nop" master &&
+       git worktree prune &&
+       test -d .git/worktrees/nop
+'
+
+test_done
diff --git a/t/t2402-worktree-list.sh b/t/t2402-worktree-list.sh
new file mode 100755 (executable)
index 0000000..bb6fb9b
--- /dev/null
@@ -0,0 +1,154 @@
+#!/bin/sh
+
+test_description='test git worktree list'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       test_commit init
+'
+
+test_expect_success 'rev-parse --git-common-dir on main worktree' '
+       git rev-parse --git-common-dir >actual &&
+       echo .git >expected &&
+       test_cmp expected actual &&
+       mkdir sub &&
+       git -C sub rev-parse --git-common-dir >actual2 &&
+       echo ../.git >expected2 &&
+       test_cmp expected2 actual2
+'
+
+test_expect_success 'rev-parse --git-path objects linked worktree' '
+       echo "$(git rev-parse --show-toplevel)/.git/objects" >expect &&
+       test_when_finished "rm -rf linked-tree actual expect && git worktree prune" &&
+       git worktree add --detach linked-tree master &&
+       git -C linked-tree rev-parse --git-path objects >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '"list" all worktrees from main' '
+       echo "$(git rev-parse --show-toplevel) $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect &&
+       test_when_finished "rm -rf here out actual expect && git worktree prune" &&
+       git worktree add --detach here master &&
+       echo "$(git -C here rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >>expect &&
+       git worktree list >out &&
+       sed "s/  */ /g" <out >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '"list" all worktrees from linked' '
+       echo "$(git rev-parse --show-toplevel) $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect &&
+       test_when_finished "rm -rf here out actual expect && git worktree prune" &&
+       git worktree add --detach here master &&
+       echo "$(git -C here rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >>expect &&
+       git -C here worktree list >out &&
+       sed "s/  */ /g" <out >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '"list" all worktrees --porcelain' '
+       echo "worktree $(git rev-parse --show-toplevel)" >expect &&
+       echo "HEAD $(git rev-parse HEAD)" >>expect &&
+       echo "branch $(git symbolic-ref HEAD)" >>expect &&
+       echo >>expect &&
+       test_when_finished "rm -rf here actual expect && git worktree prune" &&
+       git worktree add --detach here master &&
+       echo "worktree $(git -C here rev-parse --show-toplevel)" >>expect &&
+       echo "HEAD $(git rev-parse HEAD)" >>expect &&
+       echo "detached" >>expect &&
+       echo >>expect &&
+       git worktree list --porcelain >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'bare repo setup' '
+       git init --bare bare1 &&
+       echo "data" >file1 &&
+       git add file1 &&
+       git commit -m"File1: add data" &&
+       git push bare1 master &&
+       git reset --hard HEAD^
+'
+
+test_expect_success '"list" all worktrees from bare main' '
+       test_when_finished "rm -rf there out actual expect && git -C bare1 worktree prune" &&
+       git -C bare1 worktree add --detach ../there master &&
+       echo "$(pwd)/bare1 (bare)" >expect &&
+       echo "$(git -C there rev-parse --show-toplevel) $(git -C there rev-parse --short HEAD) (detached HEAD)" >>expect &&
+       git -C bare1 worktree list >out &&
+       sed "s/  */ /g" <out >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '"list" all worktrees --porcelain from bare main' '
+       test_when_finished "rm -rf there actual expect && git -C bare1 worktree prune" &&
+       git -C bare1 worktree add --detach ../there master &&
+       echo "worktree $(pwd)/bare1" >expect &&
+       echo "bare" >>expect &&
+       echo >>expect &&
+       echo "worktree $(git -C there rev-parse --show-toplevel)" >>expect &&
+       echo "HEAD $(git -C there rev-parse HEAD)" >>expect &&
+       echo "detached" >>expect &&
+       echo >>expect &&
+       git -C bare1 worktree list --porcelain >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '"list" all worktrees from linked with a bare main' '
+       test_when_finished "rm -rf there out actual expect && git -C bare1 worktree prune" &&
+       git -C bare1 worktree add --detach ../there master &&
+       echo "$(pwd)/bare1 (bare)" >expect &&
+       echo "$(git -C there rev-parse --show-toplevel) $(git -C there rev-parse --short HEAD) (detached HEAD)" >>expect &&
+       git -C there worktree list >out &&
+       sed "s/  */ /g" <out >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'bare repo cleanup' '
+       rm -rf bare1
+'
+
+test_expect_success 'broken main worktree still at the top' '
+       git init broken-main &&
+       (
+               cd broken-main &&
+               test_commit new &&
+               git worktree add linked &&
+               cat >expected <<-EOF &&
+               worktree $(pwd)
+               HEAD $ZERO_OID
+
+               EOF
+               cd linked &&
+               echo "worktree $(pwd)" >expected &&
+               echo "ref: .broken" >../.git/HEAD &&
+               git worktree list --porcelain >out &&
+               head -n 3 out >actual &&
+               test_cmp ../expected actual &&
+               git worktree list >out &&
+               head -n 1 out >actual.2 &&
+               grep -F "(error)" actual.2
+       )
+'
+
+test_expect_success 'linked worktrees are sorted' '
+       mkdir sorted &&
+       git init sorted/main &&
+       (
+               cd sorted/main &&
+               test_tick &&
+               test_commit new &&
+               git worktree add ../first &&
+               git worktree add ../second &&
+               git worktree list --porcelain >out &&
+               grep ^worktree out >actual
+       ) &&
+       cat >expected <<-EOF &&
+       worktree $(pwd)/sorted/main
+       worktree $(pwd)/sorted/first
+       worktree $(pwd)/sorted/second
+       EOF
+       test_cmp expected sorted/main/actual
+'
+
+test_done
diff --git a/t/t2403-worktree-move.sh b/t/t2403-worktree-move.sh
new file mode 100755 (executable)
index 0000000..33c0337
--- /dev/null
@@ -0,0 +1,188 @@
+#!/bin/sh
+
+test_description='test git worktree move, remove, lock and unlock'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       test_commit init &&
+       git worktree add source &&
+       git worktree list --porcelain >out &&
+       grep "^worktree" out >actual &&
+       cat <<-EOF >expected &&
+       worktree $(pwd)
+       worktree $(pwd)/source
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'lock main worktree' '
+       test_must_fail git worktree lock .
+'
+
+test_expect_success 'lock linked worktree' '
+       git worktree lock --reason hahaha source &&
+       echo hahaha >expected &&
+       test_cmp expected .git/worktrees/source/locked
+'
+
+test_expect_success 'lock linked worktree from another worktree' '
+       rm .git/worktrees/source/locked &&
+       git worktree add elsewhere &&
+       git -C elsewhere worktree lock --reason hahaha ../source &&
+       echo hahaha >expected &&
+       test_cmp expected .git/worktrees/source/locked
+'
+
+test_expect_success 'lock worktree twice' '
+       test_must_fail git worktree lock source &&
+       echo hahaha >expected &&
+       test_cmp expected .git/worktrees/source/locked
+'
+
+test_expect_success 'lock worktree twice (from the locked worktree)' '
+       test_must_fail git -C source worktree lock . &&
+       echo hahaha >expected &&
+       test_cmp expected .git/worktrees/source/locked
+'
+
+test_expect_success 'unlock main worktree' '
+       test_must_fail git worktree unlock .
+'
+
+test_expect_success 'unlock linked worktree' '
+       git worktree unlock source &&
+       test_path_is_missing .git/worktrees/source/locked
+'
+
+test_expect_success 'unlock worktree twice' '
+       test_must_fail git worktree unlock source &&
+       test_path_is_missing .git/worktrees/source/locked
+'
+
+test_expect_success 'move non-worktree' '
+       mkdir abc &&
+       test_must_fail git worktree move abc def
+'
+
+test_expect_success 'move locked worktree' '
+       git worktree lock source &&
+       test_when_finished "git worktree unlock source" &&
+       test_must_fail git worktree move source destination
+'
+
+test_expect_success 'move worktree' '
+       git worktree move source destination &&
+       test_path_is_missing source &&
+       git worktree list --porcelain >out &&
+       grep "^worktree.*/destination$" out &&
+       ! grep "^worktree.*/source$" out &&
+       git -C destination log --format=%s >actual2 &&
+       echo init >expected2 &&
+       test_cmp expected2 actual2
+'
+
+test_expect_success 'move main worktree' '
+       test_must_fail git worktree move . def
+'
+
+test_expect_success 'move worktree to another dir' '
+       mkdir some-dir &&
+       git worktree move destination some-dir &&
+       test_when_finished "git worktree move some-dir/destination destination" &&
+       test_path_is_missing destination &&
+       git worktree list --porcelain >out &&
+       grep "^worktree.*/some-dir/destination$" out &&
+       git -C some-dir/destination log --format=%s >actual2 &&
+       echo init >expected2 &&
+       test_cmp expected2 actual2
+'
+
+test_expect_success 'move locked worktree (force)' '
+       test_when_finished "
+               git worktree unlock flump || :
+               git worktree remove flump || :
+               git worktree unlock ploof || :
+               git worktree remove ploof || :
+               " &&
+       git worktree add --detach flump &&
+       git worktree lock flump &&
+       test_must_fail git worktree move flump ploof" &&
+       test_must_fail git worktree move --force flump ploof" &&
+       git worktree move --force --force flump ploof
+'
+
+test_expect_success 'remove main worktree' '
+       test_must_fail git worktree remove .
+'
+
+test_expect_success 'remove locked worktree' '
+       git worktree lock destination &&
+       test_when_finished "git worktree unlock destination" &&
+       test_must_fail git worktree remove destination
+'
+
+test_expect_success 'remove worktree with dirty tracked file' '
+       echo dirty >>destination/init.t &&
+       test_when_finished "git -C destination checkout init.t" &&
+       test_must_fail git worktree remove destination
+'
+
+test_expect_success 'remove worktree with untracked file' '
+       : >destination/untracked &&
+       test_must_fail git worktree remove destination
+'
+
+test_expect_success 'force remove worktree with untracked file' '
+       git worktree remove --force destination &&
+       test_path_is_missing destination
+'
+
+test_expect_success 'remove missing worktree' '
+       git worktree add to-be-gone &&
+       test -d .git/worktrees/to-be-gone &&
+       mv to-be-gone gone &&
+       git worktree remove to-be-gone &&
+       test_path_is_missing .git/worktrees/to-be-gone
+'
+
+test_expect_success 'NOT remove missing-but-locked worktree' '
+       git worktree add gone-but-locked &&
+       git worktree lock gone-but-locked &&
+       test -d .git/worktrees/gone-but-locked &&
+       mv gone-but-locked really-gone-now &&
+       test_must_fail git worktree remove gone-but-locked &&
+       test_path_is_dir .git/worktrees/gone-but-locked
+'
+
+test_expect_success 'proper error when worktree not found' '
+       for i in noodle noodle/bork
+       do
+               test_must_fail git worktree lock $i 2>err &&
+               test_i18ngrep "not a working tree" err || return 1
+       done
+'
+
+test_expect_success 'remove locked worktree (force)' '
+       git worktree add --detach gumby &&
+       test_when_finished "git worktree remove gumby || :" &&
+       git worktree lock gumby &&
+       test_when_finished "git worktree unlock gumby || :" &&
+       test_must_fail git worktree remove gumby &&
+       test_must_fail git worktree remove --force gumby &&
+       git worktree remove --force --force gumby
+'
+
+test_expect_success 'remove cleans up .git/worktrees when empty' '
+       git init moog &&
+       (
+               cd moog &&
+               test_commit bim &&
+               git worktree add --detach goom &&
+               test_path_exists .git/worktrees &&
+               git worktree remove goom &&
+               test_path_is_missing .git/worktrees
+       )
+'
+
+test_done
diff --git a/t/t2404-worktree-config.sh b/t/t2404-worktree-config.sh
new file mode 100755 (executable)
index 0000000..286121d
--- /dev/null
@@ -0,0 +1,79 @@
+#!/bin/sh
+
+test_description="config file in multi worktree"
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       test_commit start
+'
+
+test_expect_success 'config --worktree in single worktree' '
+       git config --worktree foo.bar true &&
+       test_cmp_config true foo.bar
+'
+
+test_expect_success 'add worktrees' '
+       git worktree add wt1 &&
+       git worktree add wt2
+'
+
+test_expect_success 'config --worktree without extension' '
+       test_must_fail git config --worktree foo.bar false
+'
+
+test_expect_success 'enable worktreeConfig extension' '
+       git config extensions.worktreeConfig true &&
+       test_cmp_config true extensions.worktreeConfig
+'
+
+test_expect_success 'config is shared as before' '
+       git config this.is shared &&
+       test_cmp_config shared this.is &&
+       test_cmp_config -C wt1 shared this.is &&
+       test_cmp_config -C wt2 shared this.is
+'
+
+test_expect_success 'config is shared (set from another worktree)' '
+       git -C wt1 config that.is also-shared &&
+       test_cmp_config also-shared that.is &&
+       test_cmp_config -C wt1 also-shared that.is &&
+       test_cmp_config -C wt2 also-shared that.is
+'
+
+test_expect_success 'config private to main worktree' '
+       git config --worktree this.is for-main &&
+       test_cmp_config for-main this.is &&
+       test_cmp_config -C wt1 shared this.is &&
+       test_cmp_config -C wt2 shared this.is
+'
+
+test_expect_success 'config private to linked worktree' '
+       git -C wt1 config --worktree this.is for-wt1 &&
+       test_cmp_config for-main this.is &&
+       test_cmp_config -C wt1 for-wt1 this.is &&
+       test_cmp_config -C wt2 shared this.is
+'
+
+test_expect_success 'core.bare no longer for main only' '
+       test_config core.bare true &&
+       test "$(git rev-parse --is-bare-repository)" = true &&
+       test "$(git -C wt1 rev-parse --is-bare-repository)" = true &&
+       test "$(git -C wt2 rev-parse --is-bare-repository)" = true
+'
+
+test_expect_success 'per-worktree core.bare is picked up' '
+       git -C wt1 config --worktree core.bare true &&
+       test "$(git rev-parse --is-bare-repository)" = false &&
+       test "$(git -C wt1 rev-parse --is-bare-repository)" = true &&
+       test "$(git -C wt2 rev-parse --is-bare-repository)" = false
+'
+
+test_expect_success 'config.worktree no longer read without extension' '
+       git config --unset extensions.worktreeConfig &&
+       test_cmp_config shared this.is &&
+       test_cmp_config -C wt1 shared this.is &&
+       test_cmp_config -C wt2 shared this.is
+'
+
+test_done