diff mbox series

[v2,1/3] worktree: add worktree with unique suffix

Message ID 20241129-wt_unique_ids-v2-1-ff444e9e625a@pm.me (mailing list archive)
State New
Headers show
Series Ensure unique worktree ids across repositories | expand

Commit Message

Caleb White Nov. 29, 2024, 10:37 p.m. UTC
The `es/worktree-repair-copied` topic added support for repairing a
worktree from a copy scenario. However, the topic added the
ability for a repository to "take over" a worktree from another
repository if the worktree_id matched a worktree inside the current
repository which can happen if two repositories use the same worktree name.

This teaches Git to create worktrees with a unique suffix so the
worktree_id is unique across all repositories even if they have the
same name. For example creating a worktree `develop` would look like:

    foo/
    ├── .git/worktrees/develop-5445874156/
    └── develop/
    bar/
    ├── .git/worktrees/develop-1549518426/
    └── develop/

The actual worktree directory name is still `develop`, but the
worktree_id is unique and prevents the "take over" scenario. The suffix
is given by the `git_rand()` function. Worktree ids can already differ
from the actual directory name (appended with a number like `develop1`)
if the worktree name was already taken, so this should not be a
drastic change.

Signed-off-by: Caleb White <cdwhite3@pm.me>
---
 Documentation/git-worktree.txt     |  5 +-
 builtin/worktree.c                 |  6 +++
 t/t0035-safe-bare-repository.sh    |  4 +-
 t/t0600-reffiles-backend.sh        | 10 ++--
 t/t0601-reffiles-pack-refs.sh      |  4 +-
 t/t0610-reftable-basics.sh         | 54 +++++++++++-----------
 t/t1407-worktree-ref-store.sh      |  4 +-
 t/t1410-reflog.sh                  | 10 ++--
 t/t1415-worktree-refs.sh           | 26 +++++------
 t/t1450-fsck.sh                    | 14 +++---
 t/t1500-rev-parse.sh               |  6 +--
 t/t2400-worktree-add.sh            | 51 +++++++++++----------
 t/t2401-worktree-prune.sh          | 20 ++++----
 t/t2403-worktree-move.sh           | 32 ++++++-------
 t/t2405-worktree-submodule.sh      | 10 ++--
 t/t2406-worktree-repair.sh         | 93 ++++++++++++++++++++++++--------------
 t/t2407-worktree-heads.sh          | 27 +++++------
 t/t3200-branch.sh                  | 10 ++--
 t/t5304-prune.sh                   |  2 +-
 t/t7412-submodule-absorbgitdirs.sh |  4 +-
 20 files changed, 212 insertions(+), 180 deletions(-)
diff mbox series

Patch

diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt
index 8340b7f028e6c1c3bae3de0879e9754098466d14..e0604b043361828f94b58f676a5ed4f15b116348 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -352,12 +352,11 @@  DETAILS
 -------
 Each linked worktree has a private sub-directory in the repository's
 `$GIT_DIR/worktrees` directory.  The private sub-directory's name is usually
-the base name of the linked worktree's path, possibly appended with a
+the base name of the linked worktree's path, appended with a random
 number to make it unique.  For example, when `$GIT_DIR=/path/main/.git` the
 command `git worktree add /path/other/test-next next` creates the linked
 worktree in `/path/other/test-next` and also creates a
-`$GIT_DIR/worktrees/test-next` directory (or `$GIT_DIR/worktrees/test-next1`
-if `test-next` is already taken).
+`$GIT_DIR/worktrees/test-next-#######` directory.
 
 Within a linked worktree, `$GIT_DIR` is set to point to this private
 directory (e.g. `/path/main/.git/worktrees/test-next` in the example) and
diff --git a/builtin/worktree.c b/builtin/worktree.c
index fde9ff4dc9a734c655e95ccd62774282950cbba6..3ad355ca762729401fc0c8625f4fd05b154a84ec 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -421,6 +421,7 @@  static int add_worktree(const char *path, const char *refname,
 	struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
 	struct strbuf sb = STRBUF_INIT;
 	const char *name;
+	const char *suffix;
 	struct strvec child_env = STRVEC_INIT;
 	unsigned int counter = 0;
 	int len, ret;
@@ -455,6 +456,11 @@  static int add_worktree(const char *path, const char *refname,
 	strbuf_reset(&sb);
 	name = sb_name.buf;
 	git_path_buf(&sb_repo, "worktrees/%s", name);
+	suffix = getenv("GIT_TEST_WORKTREE_SUFFIX");
+	if (suffix)
+		strbuf_addf(&sb_repo, "-%s", suffix);
+	else
+		strbuf_addf(&sb_repo, "-%u", git_rand());
 	len = sb_repo.len;
 	if (safe_create_leading_directories_const(sb_repo.buf))
 		die_errno(_("could not create leading directories of '%s'"),
diff --git a/t/t0035-safe-bare-repository.sh b/t/t0035-safe-bare-repository.sh
index d3cb2a1cb9edb8f9ad7480be6ec3e02b464046bd..30cbf7fd32e2e4afd6d680e0328ee18b791778da 100755
--- a/t/t0035-safe-bare-repository.sh
+++ b/t/t0035-safe-bare-repository.sh
@@ -41,7 +41,7 @@  test_expect_success 'setup an embedded bare repo, secondary worktree and submodu
 		git -c protocol.file.allow=always \
 			submodule add --name subn -- ./bare-repo subd
 	) &&
-	test_path_is_dir outer-repo/.git/worktrees/outer-secondary &&
+	test_path_is_dir outer-repo/.git/worktrees/outer-secondary-* &&
 	test_path_is_dir outer-repo/.git/modules/subn
 '
 
@@ -97,7 +97,7 @@  test_expect_success 'no trace when "bare repository" is a subdir of .git' '
 '
 
 test_expect_success 'no trace in $GIT_DIR of secondary worktree' '
-	expect_accepted_implicit -C outer-repo/.git/worktrees/outer-secondary
+	expect_accepted_implicit -C outer-repo/.git/worktrees/outer-secondary-*
 '
 
 test_expect_success 'no trace in $GIT_DIR of a submodule' '
diff --git a/t/t0600-reffiles-backend.sh b/t/t0600-reffiles-backend.sh
index bef2b70871364931ab8b3ead950f59d11b6fe216..8da81b1ae34f5f095ba9406e8f031fd5e43ac789 100755
--- a/t/t0600-reffiles-backend.sh
+++ b/t/t0600-reffiles-backend.sh
@@ -256,12 +256,12 @@  test_expect_success 'delete fails cleanly if packed-refs.new write fails' '
 	test_cmp unchanged actual
 '
 
-RWT="test-tool ref-store worktree:wt"
+RWT="test-tool ref-store worktree:wt-123"
 RMAIN="test-tool ref-store worktree:main"
 
 test_expect_success 'setup worktree' '
 	test_commit first &&
-	git worktree add -b wt-main wt &&
+	GIT_TEST_WORKTREE_SUFFIX=123 git worktree add -b wt-main wt &&
 	(
 		cd wt &&
 		test_commit second
@@ -279,9 +279,9 @@  test_expect_success 'for_each_reflog()' '
 	mkdir -p     .git/logs/refs/bisect &&
 	echo $ZERO_OID >.git/logs/refs/bisect/random &&
 
-	echo $ZERO_OID >.git/worktrees/wt/logs/PSEUDO_WT_HEAD &&
-	mkdir -p     .git/worktrees/wt/logs/refs/bisect &&
-	echo $ZERO_OID >.git/worktrees/wt/logs/refs/bisect/wt-random &&
+	echo $ZERO_OID >.git/worktrees/wt-123/logs/PSEUDO_WT_HEAD &&
+	mkdir -p     .git/worktrees/wt-123/logs/refs/bisect &&
+	echo $ZERO_OID >.git/worktrees/wt-123/logs/refs/bisect/wt-random &&
 
 	$RWT for-each-reflog >actual &&
 	cat >expected <<-\EOF &&
diff --git a/t/t0601-reffiles-pack-refs.sh b/t/t0601-reffiles-pack-refs.sh
index d8cbd3f202b5f00c9a94c5a2ea2dced6606b6f4d..d6597938caca5e6c3a6e86b860f3c0d309aa2140 100755
--- a/t/t0601-reffiles-pack-refs.sh
+++ b/t/t0601-reffiles-pack-refs.sh
@@ -326,8 +326,8 @@  test_expect_success 'refs/worktree must not be packed' '
 	git pack-refs --all &&
 	test_path_is_missing .git/refs/tags/wt1 &&
 	test_path_is_file .git/refs/worktree/foo &&
-	test_path_is_file .git/worktrees/wt1/refs/worktree/foo &&
-	test_path_is_file .git/worktrees/wt2/refs/worktree/foo
+	test_path_is_file .git/worktrees/wt1-*/refs/worktree/foo &&
+	test_path_is_file .git/worktrees/wt2-*/refs/worktree/foo
 '
 
 # we do not want to count on running pack-refs to
diff --git a/t/t0610-reftable-basics.sh b/t/t0610-reftable-basics.sh
index eaf6fab6d29f01430abae3c7abf5a750d4271a36..f21fd9dbba2b77636045b297073e340209d61a2d 100755
--- a/t/t0610-reftable-basics.sh
+++ b/t/t0610-reftable-basics.sh
@@ -965,11 +965,11 @@  test_expect_success 'worktree: adding worktree creates separate stack' '
 	test_commit -C repo A &&
 
 	git -C repo worktree add ../worktree &&
-	test_path_is_file repo/.git/worktrees/worktree/refs/heads &&
+	test_path_is_file repo/.git/worktrees/worktree-*/refs/heads &&
 	echo "ref: refs/heads/.invalid" >expect &&
-	test_cmp expect repo/.git/worktrees/worktree/HEAD &&
-	test_path_is_dir repo/.git/worktrees/worktree/reftable &&
-	test_path_is_file repo/.git/worktrees/worktree/reftable/tables.list
+	test_cmp expect repo/.git/worktrees/worktree-*/HEAD &&
+	test_path_is_dir repo/.git/worktrees/worktree-*/reftable &&
+	test_path_is_file repo/.git/worktrees/worktree-*/reftable/tables.list
 '
 
 test_expect_success 'worktree: pack-refs in main repo packs main refs' '
@@ -982,10 +982,10 @@  test_expect_success 'worktree: pack-refs in main repo packs main refs' '
 	GIT_TEST_REFTABLE_AUTOCOMPACTION=false \
 	git -C worktree update-ref refs/worktree/per-worktree HEAD &&
 
-	test_line_count = 4 repo/.git/worktrees/worktree/reftable/tables.list &&
+	test_line_count = 4 repo/.git/worktrees/worktree-*/reftable/tables.list &&
 	test_line_count = 3 repo/.git/reftable/tables.list &&
 	git -C repo pack-refs &&
-	test_line_count = 4 repo/.git/worktrees/worktree/reftable/tables.list &&
+	test_line_count = 4 repo/.git/worktrees/worktree-*/reftable/tables.list &&
 	test_line_count = 1 repo/.git/reftable/tables.list
 '
 
@@ -999,10 +999,10 @@  test_expect_success 'worktree: pack-refs in worktree packs worktree refs' '
 	GIT_TEST_REFTABLE_AUTOCOMPACTION=false \
 	git -C worktree update-ref refs/worktree/per-worktree HEAD &&
 
-	test_line_count = 4 repo/.git/worktrees/worktree/reftable/tables.list &&
+	test_line_count = 4 repo/.git/worktrees/worktree-*/reftable/tables.list &&
 	test_line_count = 3 repo/.git/reftable/tables.list &&
 	git -C worktree pack-refs &&
-	test_line_count = 1 repo/.git/worktrees/worktree/reftable/tables.list &&
+	test_line_count = 1 repo/.git/worktrees/worktree-*/reftable/tables.list &&
 	test_line_count = 3 repo/.git/reftable/tables.list
 '
 
@@ -1014,12 +1014,12 @@  test_expect_success 'worktree: creating shared ref updates main stack' '
 	git -C repo worktree add ../worktree &&
 	git -C repo pack-refs &&
 	git -C worktree pack-refs &&
-	test_line_count = 1 repo/.git/worktrees/worktree/reftable/tables.list &&
+	test_line_count = 1 repo/.git/worktrees/worktree-*/reftable/tables.list &&
 	test_line_count = 1 repo/.git/reftable/tables.list &&
 
 	GIT_TEST_REFTABLE_AUTOCOMPACTION=false \
 	git -C worktree update-ref refs/heads/shared HEAD &&
-	test_line_count = 1 repo/.git/worktrees/worktree/reftable/tables.list &&
+	test_line_count = 1 repo/.git/worktrees/worktree-*/reftable/tables.list &&
 	test_line_count = 2 repo/.git/reftable/tables.list
 '
 
@@ -1031,11 +1031,11 @@  test_expect_success 'worktree: creating per-worktree ref updates worktree stack'
 	git -C repo worktree add ../worktree &&
 	git -C repo pack-refs &&
 	git -C worktree pack-refs &&
-	test_line_count = 1 repo/.git/worktrees/worktree/reftable/tables.list &&
+	test_line_count = 1 repo/.git/worktrees/worktree-*/reftable/tables.list &&
 	test_line_count = 1 repo/.git/reftable/tables.list &&
 
 	git -C worktree update-ref refs/bisect/per-worktree HEAD &&
-	test_line_count = 2 repo/.git/worktrees/worktree/reftable/tables.list &&
+	test_line_count = 2 repo/.git/worktrees/worktree-*/reftable/tables.list &&
 	test_line_count = 1 repo/.git/reftable/tables.list
 '
 
@@ -1044,14 +1044,14 @@  test_expect_success 'worktree: creating per-worktree ref from main repo' '
 	git init repo &&
 	test_commit -C repo A &&
 
-	git -C repo worktree add ../worktree &&
+	GIT_TEST_WORKTREE_SUFFIX=456 git -C repo worktree add ../worktree &&
 	git -C repo pack-refs &&
 	git -C worktree pack-refs &&
-	test_line_count = 1 repo/.git/worktrees/worktree/reftable/tables.list &&
+	test_line_count = 1 repo/.git/worktrees/worktree-456/reftable/tables.list &&
 	test_line_count = 1 repo/.git/reftable/tables.list &&
 
-	git -C repo update-ref worktrees/worktree/refs/bisect/per-worktree HEAD &&
-	test_line_count = 2 repo/.git/worktrees/worktree/reftable/tables.list &&
+	git -C repo update-ref worktrees/worktree-456/refs/bisect/per-worktree HEAD &&
+	test_line_count = 2 repo/.git/worktrees/worktree-456/reftable/tables.list &&
 	test_line_count = 1 repo/.git/reftable/tables.list
 '
 
@@ -1060,18 +1060,18 @@  test_expect_success 'worktree: creating per-worktree ref from second worktree' '
 	git init repo &&
 	test_commit -C repo A &&
 
-	git -C repo worktree add ../wt1 &&
-	git -C repo worktree add ../wt2 &&
+	GIT_TEST_WORKTREE_SUFFIX=123 git -C repo worktree add ../wt1 &&
+	GIT_TEST_WORKTREE_SUFFIX=456 git -C repo worktree add ../wt2 &&
 	git -C repo pack-refs &&
 	git -C wt1 pack-refs &&
 	git -C wt2 pack-refs &&
-	test_line_count = 1 repo/.git/worktrees/wt1/reftable/tables.list &&
-	test_line_count = 1 repo/.git/worktrees/wt2/reftable/tables.list &&
+	test_line_count = 1 repo/.git/worktrees/wt1-123/reftable/tables.list &&
+	test_line_count = 1 repo/.git/worktrees/wt2-456/reftable/tables.list &&
 	test_line_count = 1 repo/.git/reftable/tables.list &&
 
-	git -C wt1 update-ref worktrees/wt2/refs/bisect/per-worktree HEAD &&
-	test_line_count = 1 repo/.git/worktrees/wt1/reftable/tables.list &&
-	test_line_count = 2 repo/.git/worktrees/wt2/reftable/tables.list &&
+	git -C wt1 update-ref worktrees/wt2-456/refs/bisect/per-worktree HEAD &&
+	test_line_count = 1 repo/.git/worktrees/wt1-123/reftable/tables.list &&
+	test_line_count = 2 repo/.git/worktrees/wt2-456/reftable/tables.list &&
 	test_line_count = 1 repo/.git/reftable/tables.list
 '
 
@@ -1080,18 +1080,18 @@  test_expect_success 'worktree: can create shared and per-worktree ref in one tra
 	git init repo &&
 	test_commit -C repo A &&
 
-	git -C repo worktree add ../worktree &&
+	GIT_TEST_WORKTREE_SUFFIX=123 git -C repo worktree add ../worktree &&
 	git -C repo pack-refs &&
 	git -C worktree pack-refs &&
-	test_line_count = 1 repo/.git/worktrees/worktree/reftable/tables.list &&
+	test_line_count = 1 repo/.git/worktrees/worktree-123/reftable/tables.list &&
 	test_line_count = 1 repo/.git/reftable/tables.list &&
 
 	cat >stdin <<-EOF &&
-	create worktrees/worktree/refs/bisect/per-worktree HEAD
+	create worktrees/worktree-123/refs/bisect/per-worktree HEAD
 	create refs/branches/shared HEAD
 	EOF
 	git -C repo update-ref --stdin <stdin &&
-	test_line_count = 2 repo/.git/worktrees/worktree/reftable/tables.list &&
+	test_line_count = 2 repo/.git/worktrees/worktree-123/reftable/tables.list &&
 	test_line_count = 2 repo/.git/reftable/tables.list
 '
 
diff --git a/t/t1407-worktree-ref-store.sh b/t/t1407-worktree-ref-store.sh
index 48b1c92a41450b25645d6cd782aa86c8e630164b..4d081627a1e6b55936cd4051ed760fa92e7cdc02 100755
--- a/t/t1407-worktree-ref-store.sh
+++ b/t/t1407-worktree-ref-store.sh
@@ -8,12 +8,12 @@  export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
-RWT="test-tool ref-store worktree:wt"
+RWT="test-tool ref-store worktree:wt-456"
 RMAIN="test-tool ref-store worktree:main"
 
 test_expect_success 'setup' '
 	test_commit first &&
-	git worktree add -b wt-main wt &&
+	GIT_TEST_WORKTREE_SUFFIX=456 git worktree add -b wt-main wt &&
 	(
 		cd wt &&
 		test_commit second
diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh
index 246a3f46abafdf0e24528be59b33a4987ff791c1..c1b6b4d8fab8d261713a192478af12179f9bc917 100755
--- a/t/t1410-reflog.sh
+++ b/t/t1410-reflog.sh
@@ -402,12 +402,12 @@  test_expect_success 'expire with multiple worktrees' '
 		cd main-wt &&
 		test_tick &&
 		test_commit foo &&
-		git  worktree add link-wt &&
+		GIT_TEST_WORKTREE_SUFFIX=123 git  worktree add link-wt &&
 		test_tick &&
 		test_commit -C link-wt foobar &&
 		test_tick &&
 		git reflog expire --verbose --all --expire=$test_tick &&
-		test-tool ref-store worktree:link-wt for-each-reflog-ent HEAD >actual &&
+		test-tool ref-store worktree:link-wt-123 for-each-reflog-ent HEAD >actual &&
 		test_must_be_empty actual
 	)
 '
@@ -418,17 +418,17 @@  test_expect_success 'expire one of multiple worktrees' '
 		cd main-wt2 &&
 		test_tick &&
 		test_commit foo &&
-		git worktree add link-wt &&
+		GIT_TEST_WORKTREE_SUFFIX=456 git worktree add link-wt &&
 		test_tick &&
 		test_commit -C link-wt foobar &&
 		test_tick &&
-		test-tool ref-store worktree:link-wt for-each-reflog-ent HEAD \
+		test-tool ref-store worktree:link-wt-456 for-each-reflog-ent HEAD \
 			>expect-link-wt &&
 		git reflog expire --verbose --all --expire=$test_tick \
 			--single-worktree &&
 		test-tool ref-store worktree:main for-each-reflog-ent HEAD \
 			>actual-main &&
-		test-tool ref-store worktree:link-wt for-each-reflog-ent HEAD \
+		test-tool ref-store worktree:link-wt-456 for-each-reflog-ent HEAD \
 			>actual-link-wt &&
 		test_must_be_empty actual-main &&
 		test_cmp expect-link-wt actual-link-wt
diff --git a/t/t1415-worktree-refs.sh b/t/t1415-worktree-refs.sh
index eb4eec8becbfa64efcde4e866334363a866c01a2..c46bf29aa4d9ceac85147d685a1d23144ec23b2b 100755
--- a/t/t1415-worktree-refs.sh
+++ b/t/t1415-worktree-refs.sh
@@ -9,8 +9,8 @@  test_expect_success 'setup' '
 	test_commit initial &&
 	test_commit wt1 &&
 	test_commit wt2 &&
-	git worktree add wt1 wt1 &&
-	git worktree add wt2 wt2 &&
+	GIT_TEST_WORKTREE_SUFFIX=123 git worktree add wt1 wt1 &&
+	GIT_TEST_WORKTREE_SUFFIX=456 git worktree add wt2 wt2 &&
 	git checkout initial &&
 	git update-ref refs/worktree/foo HEAD &&
 	git -C wt1 update-ref refs/worktree/foo HEAD &&
@@ -37,16 +37,16 @@  test_expect_success 'ambiguous main-worktree/HEAD' '
 '
 
 test_expect_success 'resolve worktrees/xx/HEAD' '
-	test_cmp_rev worktrees/wt1/HEAD wt1 &&
-	( cd wt1 && test_cmp_rev worktrees/wt1/HEAD wt1 ) &&
-	( cd wt2 && test_cmp_rev worktrees/wt1/HEAD wt1 )
+	test_cmp_rev worktrees/wt1-123/HEAD wt1 &&
+	( cd wt1 && test_cmp_rev worktrees/wt1-123/HEAD wt1 ) &&
+	( cd wt2 && test_cmp_rev worktrees/wt1-123/HEAD wt1 )
 '
 
 test_expect_success 'ambiguous worktrees/xx/HEAD' '
-	git update-ref refs/heads/worktrees/wt1/HEAD $(git rev-parse HEAD) &&
-	test_when_finished git update-ref -d refs/heads/worktrees/wt1/HEAD &&
-	git rev-parse worktrees/wt1/HEAD 2>warn &&
-	grep "worktrees/wt1/HEAD.*ambiguous" warn
+	git update-ref refs/heads/worktrees/wt1-123/HEAD $(git rev-parse HEAD) &&
+	test_when_finished git update-ref -d refs/heads/worktrees/wt1-123/HEAD &&
+	git rev-parse worktrees/wt1-123/HEAD 2>warn &&
+	grep "worktrees/wt1-123/HEAD.*ambiguous" warn
 '
 
 test_expect_success 'reflog of main-worktree/HEAD' '
@@ -58,12 +58,12 @@  test_expect_success 'reflog of main-worktree/HEAD' '
 '
 
 test_expect_success 'reflog of worktrees/xx/HEAD' '
-	git -C wt2 reflog HEAD | sed "s/HEAD/worktrees\/wt2\/HEAD/" >expected &&
-	git reflog worktrees/wt2/HEAD >actual &&
+	git -C wt2 reflog HEAD | sed "s/HEAD/worktrees\/wt2-456\/HEAD/" >expected &&
+	git reflog worktrees/wt2-456/HEAD >actual &&
 	test_cmp expected actual &&
-	git -C wt1 reflog worktrees/wt2/HEAD >actual.wt1 &&
+	git -C wt1 reflog worktrees/wt2-456/HEAD >actual.wt1 &&
 	test_cmp expected actual.wt1 &&
-	git -C wt2 reflog worktrees/wt2/HEAD >actual.wt2 &&
+	git -C wt2 reflog worktrees/wt2-456/HEAD >actual.wt2 &&
 	test_cmp expected actual.wt2
 '
 
diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh
index 280cbf3e031e1ab67ed28aa2af4c3b105b7d254e..de25ab24fd8924e288969650a658908a5e9afd22 100755
--- a/t/t1450-fsck.sh
+++ b/t/t1450-fsck.sh
@@ -152,10 +152,10 @@  test_expect_success REFFILES 'HEAD link pointing at a funny object (from differe
 
 test_expect_success REFFILES 'other worktree HEAD link pointing at a funny object' '
 	test_when_finished "git worktree remove -f other" &&
-	git worktree add other &&
-	echo $ZERO_OID >.git/worktrees/other/HEAD &&
+	GIT_TEST_WORKTREE_SUFFIX=123 git worktree add other &&
+	echo $ZERO_OID >.git/worktrees/other-123/HEAD &&
 	test_must_fail git fsck 2>out &&
-	test_grep "worktrees/other/HEAD: detached HEAD points" out
+	test_grep "worktrees/other-123/HEAD: detached HEAD points" out
 '
 
 test_expect_success 'other worktree HEAD link pointing at missing object' '
@@ -164,7 +164,7 @@  test_expect_success 'other worktree HEAD link pointing at missing object' '
 	object_id=$(echo "Contents missing from repo" | git hash-object --stdin) &&
 	test-tool -C other ref-store main update-ref msg HEAD $object_id "" REF_NO_DEREF,REF_SKIP_OID_VERIFICATION &&
 	test_must_fail git fsck 2>out &&
-	test_grep "worktrees/other/HEAD: invalid sha1 pointer" out
+	test_grep "worktrees/other-.*/HEAD: invalid sha1 pointer" out
 '
 
 test_expect_success 'other worktree HEAD link pointing at a funny place' '
@@ -172,7 +172,7 @@  test_expect_success 'other worktree HEAD link pointing at a funny place' '
 	git worktree add other &&
 	git -C other symbolic-ref HEAD refs/funny/place &&
 	test_must_fail git fsck 2>out &&
-	test_grep "worktrees/other/HEAD points to something strange" out
+	test_grep "worktrees/other-.*/HEAD points to something strange" out
 '
 
 test_expect_success 'commit with multiple signatures is okay' '
@@ -1033,7 +1033,7 @@  test_expect_success 'fsck error on gitattributes with excessive size' '
 
 test_expect_success 'fsck detects problems in worktree index' '
 	test_when_finished "git worktree remove -f wt" &&
-	git worktree add wt &&
+	GIT_TEST_WORKTREE_SUFFIX=123 git worktree add wt &&
 
 	echo "this will be removed to break the worktree index" >wt/file &&
 	git -C wt add file &&
@@ -1042,7 +1042,7 @@  test_expect_success 'fsck detects problems in worktree index' '
 
 	test_must_fail git fsck --name-objects >actual 2>&1 &&
 	cat >expect <<-EOF &&
-	missing blob $blob (.git/worktrees/wt/index:file)
+	missing blob $blob (.git/worktrees/wt-123/index:file)
 	EOF
 	test_cmp expect actual
 '
diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh
index 30c31918fde6539d52800e18dfbb3423b5b73491..49e0ac68858d922336a498caaa743f2f4011d28a 100755
--- a/t/t1500-rev-parse.sh
+++ b/t/t1500-rev-parse.sh
@@ -80,7 +80,7 @@  test_expect_success 'setup' '
 	git checkout -b side &&
 	test_commit def &&
 	git checkout main &&
-	git worktree add worktree side
+	GIT_TEST_WORKTREE_SUFFIX=123 git worktree add worktree side
 '
 
 test_rev_parse toplevel false false true '' .git "$ROOT/.git"
@@ -113,7 +113,7 @@  test_expect_success 'rev-parse --path-format=absolute' '
 	test_one "." "$ROOT/.git" --path-format=absolute --git-common-dir &&
 	test_one "sub/dir" "$ROOT/.git" --path-format=absolute --git-dir &&
 	test_one "sub/dir" "$ROOT/.git" --path-format=absolute --git-common-dir &&
-	test_one "worktree" "$ROOT/.git/worktrees/worktree" --path-format=absolute --git-dir &&
+	test_one "worktree" "$ROOT/.git/worktrees/worktree-123" --path-format=absolute --git-dir &&
 	test_one "worktree" "$ROOT/.git" --path-format=absolute --git-common-dir &&
 	test_one "." "$ROOT" --path-format=absolute --show-toplevel &&
 	test_one "." "$ROOT/.git/objects" --path-format=absolute --git-path objects &&
@@ -125,7 +125,7 @@  test_expect_success 'rev-parse --path-format=relative' '
 	test_one "." ".git" --path-format=relative --git-common-dir &&
 	test_one "sub/dir" "../../.git" --path-format=relative --git-dir &&
 	test_one "sub/dir" "../../.git" --path-format=relative --git-common-dir &&
-	test_one "worktree" "../.git/worktrees/worktree" --path-format=relative --git-dir &&
+	test_one "worktree" "../.git/worktrees/worktree-123" --path-format=relative --git-dir &&
 	test_one "worktree" "../.git" --path-format=relative --git-common-dir &&
 	test_one "." "./" --path-format=relative --show-toplevel &&
 	test_one "." ".git/objects" --path-format=relative --git-path objects &&
diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh
index bc4f4e90d6ecfedbde9082bda6f9e4eec3e3575d..33262b49f18521c805f188a10f944dbfa9f285ba 100755
--- a/t/t2400-worktree-add.sh
+++ b/t/t2400-worktree-add.sh
@@ -71,21 +71,21 @@  test_expect_success '"add" worktree' '
 test_expect_success '"add" worktree with lock' '
 	git worktree add --detach --lock here-with-lock main &&
 	test_when_finished "git worktree unlock here-with-lock || :" &&
-	test -f .git/worktrees/here-with-lock/locked
+	test -f .git/worktrees/here-with-lock-*/locked
 '
 
 test_expect_success '"add" worktree with lock and reason' '
 	lock_reason="why not" &&
 	git worktree add --detach --lock --reason "$lock_reason" here-with-lock-reason main &&
 	test_when_finished "git worktree unlock here-with-lock-reason || :" &&
-	test -f .git/worktrees/here-with-lock-reason/locked &&
+	test -f .git/worktrees/here-with-lock-reason-*/locked &&
 	echo "$lock_reason" >expect &&
-	test_cmp expect .git/worktrees/here-with-lock-reason/locked
+	test_cmp expect .git/worktrees/here-with-lock-reason-*/locked
 '
 
 test_expect_success '"add" worktree with reason but no lock' '
 	test_must_fail git worktree add --detach --reason "why not" here-with-reason-only main &&
-	test_path_is_missing .git/worktrees/here-with-reason-only/locked
+	test_path_is_missing .git/worktrees/here-with-reason-only-*/locked
 '
 
 test_expect_success '"add" worktree from a subdir' '
@@ -413,16 +413,16 @@  test_expect_success '"add --orphan" with empty repository' '
 test_expect_success '"add" worktree with orphan branch and lock' '
 	git worktree add --lock --orphan -b orphanbr orphan-with-lock &&
 	test_when_finished "git worktree unlock orphan-with-lock || :" &&
-	test -f .git/worktrees/orphan-with-lock/locked
+	test -f .git/worktrees/orphan-with-lock-*/locked
 '
 
 test_expect_success '"add" worktree with orphan branch, lock, and reason' '
 	lock_reason="why not" &&
 	git worktree add --detach --lock --reason "$lock_reason" orphan-with-lock-reason main &&
 	test_when_finished "git worktree unlock orphan-with-lock-reason || :" &&
-	test -f .git/worktrees/orphan-with-lock-reason/locked &&
+	test -f .git/worktrees/orphan-with-lock-reason-*/locked &&
 	echo "$lock_reason" >expect &&
-	test_cmp expect .git/worktrees/orphan-with-lock-reason/locked
+	test_cmp expect .git/worktrees/orphan-with-lock-reason-*/locked
 '
 
 # Note: Quoted arguments containing spaces are not supported.
@@ -1088,10 +1088,10 @@  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)/.git/worktrees/gumby-123 &&
 		echo $(pwd)/gumby
 	} >hook.expect &&
-	git worktree add gumby &&
+	GIT_TEST_WORKTREE_SUFFIX="123" git worktree add gumby &&
 	test_cmp hook.expect gumby/hook.actual
 '
 
@@ -1099,10 +1099,10 @@  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)/.git/worktrees/grumpy-456 &&
 		echo $(pwd)/grumpy
 	} >hook.expect &&
-	git worktree add --detach grumpy &&
+	GIT_TEST_WORKTREE_SUFFIX="456" git worktree add --detach grumpy &&
 	test_cmp hook.expect grumpy/hook.actual
 '
 
@@ -1117,10 +1117,10 @@  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)/.git/worktrees/guppy-789 &&
 		echo $(pwd)/guppy
 	} >hook.expect &&
-	git -C gloopy worktree add --detach ../guppy &&
+	GIT_TEST_WORKTREE_SUFFIX="789" git -C gloopy worktree add --detach ../guppy &&
 	test_cmp hook.expect guppy/hook.actual
 '
 
@@ -1129,11 +1129,11 @@  test_expect_success '"add" in bare repo invokes post-checkout hook' '
 	git clone --bare . bare &&
 	{
 		echo $ZERO_OID $(git --git-dir=bare rev-parse HEAD) 1 &&
-		echo $(pwd)/bare/worktrees/goozy &&
+		echo $(pwd)/bare/worktrees/goozy-651 &&
 		echo $(pwd)/goozy
 	} >hook.expect &&
 	post_checkout_hook bare &&
-	git -C bare worktree add --detach ../goozy &&
+	GIT_TEST_WORKTREE_SUFFIX="651" git -C bare worktree add --detach ../goozy &&
 	test_cmp hook.expect goozy/hook.actual
 '
 
@@ -1165,8 +1165,9 @@  test_expect_success '"add" not tripped up by magic worktree matching"' '
 '
 
 test_expect_success FUNNYNAMES 'sanitize generated worktree name' '
-	git worktree add --detach ".  weird*..?.lock.lock" &&
-	test -d .git/worktrees/---weird-.-
+	GIT_TEST_WORKTREE_SUFFIX="1234" \
+		git worktree add --detach ".  weird*..?.lock.lock" &&
+	test -d .git/worktrees/---weird-.--1234
 '
 
 test_expect_success '"add" should not fail because of another bad worktree' '
@@ -1210,23 +1211,23 @@  test_expect_success '"add" with initialized submodule, with submodule.recurse se
 test_expect_success 'can create worktrees with relative paths' '
 	test_when_finished "git worktree remove relative" &&
 	test_config worktree.useRelativePaths false &&
-	git worktree add --relative-paths ./relative &&
-	echo "gitdir: ../.git/worktrees/relative" >expect &&
+	GIT_TEST_WORKTREE_SUFFIX=123 git worktree add --relative-paths ./relative &&
+	echo "gitdir: ../.git/worktrees/relative-123" >expect &&
 	test_cmp expect relative/.git &&
 	echo "../../../relative/.git" >expect &&
-	test_cmp expect .git/worktrees/relative/gitdir
+	test_cmp expect .git/worktrees/relative-123/gitdir
 '
 
 test_expect_success 'can create worktrees with absolute paths' '
 	test_config worktree.useRelativePaths true &&
-	git worktree add ./relative &&
-	echo "gitdir: ../.git/worktrees/relative" >expect &&
+	GIT_TEST_WORKTREE_SUFFIX=123 git worktree add ./relative &&
+	echo "gitdir: ../.git/worktrees/relative-123" >expect &&
 	test_cmp expect relative/.git &&
-	git worktree add --no-relative-paths ./absolute &&
-	echo "gitdir: $(pwd)/.git/worktrees/absolute" >expect &&
+	GIT_TEST_WORKTREE_SUFFIX=456 git worktree add --no-relative-paths ./absolute &&
+	echo "gitdir: $(pwd)/.git/worktrees/absolute-456" >expect &&
 	test_cmp expect absolute/.git &&
 	echo "$(pwd)/absolute/.git" >expect &&
-	test_cmp expect .git/worktrees/absolute/gitdir
+	test_cmp expect .git/worktrees/absolute-456/gitdir
 '
 
 test_expect_success 'move repo without breaking relative internal links' '
diff --git a/t/t2401-worktree-prune.sh b/t/t2401-worktree-prune.sh
index 5eb52b9abbf29514dc082c260ebb7a5e8e63aae0..856fcdd19d376d3448ee0be46592fe68d44617c4 100755
--- a/t/t2401-worktree-prune.sh
+++ b/t/t2401-worktree-prune.sh
@@ -83,29 +83,29 @@  test_expect_success 'not prune locked checkout' '
 test_expect_success 'not prune recent checkouts' '
 	test_when_finished rm -r .git/worktrees &&
 	git worktree add jlm HEAD &&
-	test -d .git/worktrees/jlm &&
+	test -d .git/worktrees/jlm-* &&
 	rm -rf jlm &&
 	git worktree prune --verbose --expire=2.days.ago &&
-	test -d .git/worktrees/jlm
+	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" main &&
 	git worktree prune &&
-	test -d .git/worktrees/nop
+	test -d .git/worktrees/nop-*
 '
 
 test_expect_success 'prune duplicate (linked/linked)' '
 	test_when_finished rm -fr .git/worktrees w1 w2 &&
-	git worktree add --detach w1 &&
-	git worktree add --detach w2 &&
-	sed "s/w2/w1/" .git/worktrees/w2/gitdir >.git/worktrees/w2/gitdir.new &&
-	mv .git/worktrees/w2/gitdir.new .git/worktrees/w2/gitdir &&
+	GIT_TEST_WORKTREE_SUFFIX=1 git worktree add --detach w1 &&
+	GIT_TEST_WORKTREE_SUFFIX=2 git worktree add --detach w2 &&
+	sed "s/w2/w1/" .git/worktrees/w2-2/gitdir >.git/worktrees/w2-2/gitdir.new &&
+	mv .git/worktrees/w2-2/gitdir.new .git/worktrees/w2-2/gitdir &&
 	git worktree prune --verbose 2>actual &&
 	test_grep "duplicate entry" actual &&
-	test -d .git/worktrees/w1 &&
-	! test -d .git/worktrees/w2
+	test -d .git/worktrees/w1-1 &&
+	! test -d .git/worktrees/w2-2
 '
 
 test_expect_success 'prune duplicate (main/linked)' '
@@ -117,7 +117,7 @@  test_expect_success 'prune duplicate (main/linked)' '
 	mv repo wt &&
 	git -C wt worktree prune --verbose 2>actual &&
 	test_grep "duplicate entry" actual &&
-	! test -d .git/worktrees/wt
+	! test -d .git/worktrees/wt-*
 '
 
 test_expect_success 'not prune proper worktrees inside linked worktree with relative paths' '
diff --git a/t/t2403-worktree-move.sh b/t/t2403-worktree-move.sh
index 422c1a05580057b18ab8bfdfe38da4d723749493..ba3f05c16a4969fb84d98052ae375ef162f3e73a 100755
--- a/t/t2403-worktree-move.sh
+++ b/t/t2403-worktree-move.sh
@@ -24,27 +24,27 @@  test_expect_success 'lock main worktree' '
 test_expect_success 'lock linked worktree' '
 	git worktree lock --reason hahaha source &&
 	echo hahaha >expected &&
-	test_cmp expected .git/worktrees/source/locked
+	test_cmp expected .git/worktrees/source-*/locked
 '
 
 test_expect_success 'lock linked worktree from another worktree' '
-	rm .git/worktrees/source/locked &&
+	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_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_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_cmp expected .git/worktrees/source-*/locked
 '
 
 test_expect_success 'unlock main worktree' '
@@ -183,19 +183,19 @@  test_expect_success 'force remove worktree with untracked file' '
 
 test_expect_success 'remove missing worktree' '
 	git worktree add to-be-gone &&
-	test -d .git/worktrees/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_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 &&
+	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_path_is_dir .git/worktrees/gone-but-locked-*
 '
 
 test_expect_success 'proper error when worktree not found' '
@@ -249,27 +249,27 @@  test_expect_success 'not remove a repo with initialized submodule' '
 
 test_expect_success 'move worktree with absolute path to relative path' '
 	test_config worktree.useRelativePaths false &&
-	git worktree add ./absolute &&
+	GIT_TEST_WORKTREE_SUFFIX=123 git worktree add ./absolute &&
 	git worktree move --relative-paths absolute relative &&
-	echo "gitdir: ../.git/worktrees/absolute" >expect &&
+	echo "gitdir: ../.git/worktrees/absolute-123" >expect &&
 	test_cmp expect relative/.git &&
 	echo "../../../relative/.git" >expect &&
-	test_cmp expect .git/worktrees/absolute/gitdir &&
+	test_cmp expect .git/worktrees/absolute-123/gitdir &&
 	test_config worktree.useRelativePaths true &&
 	git worktree move relative relative2 &&
-	echo "gitdir: ../.git/worktrees/absolute" >expect &&
+	echo "gitdir: ../.git/worktrees/absolute-123" >expect &&
 	test_cmp expect relative2/.git &&
 	echo "../../../relative2/.git" >expect &&
-	test_cmp expect .git/worktrees/absolute/gitdir
+	test_cmp expect .git/worktrees/absolute-123/gitdir
 '
 
 test_expect_success 'move worktree with relative path to absolute path' '
 	test_config worktree.useRelativePaths true &&
 	git worktree move --no-relative-paths relative2 absolute &&
-	echo "gitdir: $(pwd)/.git/worktrees/absolute" >expect &&
+	echo "gitdir: $(pwd)/.git/worktrees/absolute-123" >expect &&
 	test_cmp expect absolute/.git &&
 	echo "$(pwd)/absolute/.git" >expect &&
-	test_cmp expect .git/worktrees/absolute/gitdir
+	test_cmp expect .git/worktrees/absolute-123/gitdir
 '
 
 test_done
diff --git a/t/t2405-worktree-submodule.sh b/t/t2405-worktree-submodule.sh
index 1d7f60563387f9c2f53dfc3a79ac0289afe57611..5479b2a74aa0d9b1e8880ed7c038307ffa1d0c54 100755
--- a/t/t2405-worktree-submodule.sh
+++ b/t/t2405-worktree-submodule.sh
@@ -9,6 +9,7 @@  TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 base_path=$(pwd -P)
+suffix=4567
 
 test_expect_success 'setup: create origin repos'  '
 	git config --global protocol.file.allow always &&
@@ -61,9 +62,10 @@  test_expect_success 'submodule is checked out after manually adding submodule wo
 '
 
 test_expect_success 'checkout --recurse-submodules uses $GIT_DIR for submodules in a linked worktree' '
-	git -C main worktree add "$base_path/checkout-recurse" --detach  &&
+	GIT_TEST_WORKTREE_SUFFIX=$suffix \
+		git -C main worktree add "$base_path/checkout-recurse" --detach  &&
 	git -C checkout-recurse submodule update --init &&
-	echo "gitdir: ../../main/.git/worktrees/checkout-recurse/modules/sub" >expect-gitfile &&
+	echo "gitdir: ../../main/.git/worktrees/checkout-recurse-$suffix/modules/sub" >expect-gitfile &&
 	cat checkout-recurse/sub/.git >actual-gitfile &&
 	test_cmp expect-gitfile actual-gitfile &&
 	git -C main/sub rev-parse HEAD >expect-head-main &&
@@ -82,14 +84,14 @@  test_expect_success 'core.worktree is removed in $GIT_DIR/modules/<name>/config,
 	git -C checkout-recurse/sub config --get core.worktree >actual-linked &&
 	test_cmp expect-linked actual-linked &&
 	git -C checkout-recurse checkout --recurse-submodules first &&
-	test_expect_code 1 git -C main/.git/worktrees/checkout-recurse/modules/sub config --get core.worktree >linked-config &&
+	test_expect_code 1 git -C main/.git/worktrees/checkout-recurse-$suffix/modules/sub config --get core.worktree >linked-config &&
 	test_must_be_empty linked-config &&
 	git -C main/sub config --get core.worktree >actual-main &&
 	test_cmp expect-main actual-main
 '
 
 test_expect_success 'unsetting core.worktree does not prevent running commands directly against the submodule repository' '
-	git -C main/.git/worktrees/checkout-recurse/modules/sub log
+	git -C main/.git/worktrees/checkout-recurse-$suffix/modules/sub log
 '
 
 test_done
diff --git a/t/t2406-worktree-repair.sh b/t/t2406-worktree-repair.sh
index 49b70b999518d47e1edd72a61a847b427f4c67a1..49d020f5fe786014ddc428bcb74cb706f8cef3d1 100755
--- a/t/t2406-worktree-repair.sh
+++ b/t/t2406-worktree-repair.sh
@@ -106,8 +106,8 @@  test_expect_success 'repo not found; .git not file' '
 
 test_expect_success 'repo not found; .git not referencing repo' '
 	test_when_finished "rm -rf side not-a-repo && git worktree prune" &&
-	git worktree add --detach side &&
-	sed s,\.git/worktrees/side$,not-a-repo, side/.git >side/.newgit &&
+	GIT_TEST_WORKTREE_SUFFIX=1234 git worktree add --detach side &&
+	sed s,\.git/worktrees/side-1234$,not-a-repo, side/.git >side/.newgit &&
 	mv side/.newgit side/.git &&
 	mkdir not-a-repo &&
 	test_must_fail git worktree repair side 2>err &&
@@ -127,41 +127,41 @@  test_expect_success 'repo not found; .git file broken' '
 test_expect_success 'repair broken gitdir' '
 	test_when_finished "rm -rf orig moved && git worktree prune" &&
 	git worktree add --detach orig &&
-	sed s,orig/\.git$,moved/.git, .git/worktrees/orig/gitdir >expect &&
-	rm .git/worktrees/orig/gitdir &&
+	sed s,orig/\.git$,moved/.git, .git/worktrees/orig-*/gitdir >expect &&
+	rm .git/worktrees/orig-*/gitdir &&
 	mv orig moved &&
 	git worktree repair moved 2>err &&
-	test_cmp expect .git/worktrees/orig/gitdir &&
+	test_cmp expect .git/worktrees/orig-*/gitdir &&
 	test_grep "gitdir unreadable" err
 '
 
 test_expect_success 'repair incorrect gitdir' '
 	test_when_finished "rm -rf orig moved && git worktree prune" &&
 	git worktree add --detach orig &&
-	sed s,orig/\.git$,moved/.git, .git/worktrees/orig/gitdir >expect &&
+	sed s,orig/\.git$,moved/.git, .git/worktrees/orig-*/gitdir >expect &&
 	mv orig moved &&
 	git worktree repair moved 2>err &&
-	test_cmp expect .git/worktrees/orig/gitdir &&
+	test_cmp expect .git/worktrees/orig-*/gitdir &&
 	test_grep "gitdir incorrect" err
 '
 
 test_expect_success 'repair gitdir (implicit) from linked worktree' '
 	test_when_finished "rm -rf orig moved && git worktree prune" &&
 	git worktree add --detach orig &&
-	sed s,orig/\.git$,moved/.git, .git/worktrees/orig/gitdir >expect &&
+	sed s,orig/\.git$,moved/.git, .git/worktrees/orig-*/gitdir >expect &&
 	mv orig moved &&
 	git -C moved worktree repair 2>err &&
-	test_cmp expect .git/worktrees/orig/gitdir &&
+	test_cmp expect .git/worktrees/orig-*/gitdir &&
 	test_grep "gitdir incorrect" err
 '
 
 test_expect_success 'unable to repair gitdir (implicit) from main worktree' '
 	test_when_finished "rm -rf orig moved && git worktree prune" &&
 	git worktree add --detach orig &&
-	cat .git/worktrees/orig/gitdir >expect &&
+	cat .git/worktrees/orig-*/gitdir >expect &&
 	mv orig moved &&
 	git worktree repair 2>err &&
-	test_cmp expect .git/worktrees/orig/gitdir &&
+	test_cmp expect .git/worktrees/orig-*/gitdir &&
 	test_must_be_empty err
 '
 
@@ -170,15 +170,15 @@  test_expect_success 'repair multiple gitdir files' '
 		git worktree prune" &&
 	git worktree add --detach orig1 &&
 	git worktree add --detach orig2 &&
-	sed s,orig1/\.git$,moved1/.git, .git/worktrees/orig1/gitdir >expect1 &&
-	sed s,orig2/\.git$,moved2/.git, .git/worktrees/orig2/gitdir >expect2 &&
+	sed s,orig1/\.git$,moved1/.git, .git/worktrees/orig1-*/gitdir >expect1 &&
+	sed s,orig2/\.git$,moved2/.git, .git/worktrees/orig2-*/gitdir >expect2 &&
 	mv orig1 moved1 &&
 	mv orig2 moved2 &&
 	git worktree repair moved1 moved2 2>err &&
-	test_cmp expect1 .git/worktrees/orig1/gitdir &&
-	test_cmp expect2 .git/worktrees/orig2/gitdir &&
-	test_grep "gitdir incorrect:.*orig1/gitdir$" err &&
-	test_grep "gitdir incorrect:.*orig2/gitdir$" err
+	test_cmp expect1 .git/worktrees/orig1-*/gitdir &&
+	test_cmp expect2 .git/worktrees/orig2-*/gitdir &&
+	test_grep "gitdir incorrect:.*orig1-.*/gitdir$" err &&
+	test_grep "gitdir incorrect:.*orig2-.*/gitdir$" err
 '
 
 test_expect_success 'repair moved main and linked worktrees' '
@@ -186,14 +186,12 @@  test_expect_success 'repair moved main and linked worktrees' '
 	test_create_repo main &&
 	test_commit -C main init &&
 	git -C main worktree add --detach ../side &&
-	sed "s,side/\.git$,sidemoved/.git," \
-		main/.git/worktrees/side/gitdir >expect-gitdir &&
-	sed "s,main/.git/worktrees/side$,mainmoved/.git/worktrees/side," \
-		side/.git >expect-gitfile &&
+	sed "s,side,sidemoved," main/.git/worktrees/side-*/gitdir >expect-gitdir &&
+	sed "s,main,mainmoved," side/.git >expect-gitfile &&
 	mv main mainmoved &&
 	mv side sidemoved &&
 	git -C mainmoved worktree repair ../sidemoved &&
-	test_cmp expect-gitdir mainmoved/.git/worktrees/side/gitdir &&
+	test_cmp expect-gitdir mainmoved/.git/worktrees/side-*/gitdir &&
 	test_cmp expect-gitfile sidemoved/.git
 '
 
@@ -203,16 +201,15 @@  test_expect_success 'repair copied main and linked worktrees' '
 	git -C orig init main &&
 	test_commit -C orig/main nothing &&
 	git -C orig/main worktree add ../linked &&
-	cp orig/main/.git/worktrees/linked/gitdir orig/main.expect &&
+	cp orig/main/.git/worktrees/linked-*/gitdir orig/main.expect &&
 	cp orig/linked/.git orig/linked.expect &&
 	cp -R orig dup &&
 	sed "s,orig/linked/\.git$,dup/linked/.git," orig/main.expect >dup/main.expect &&
-	sed "s,orig/main/\.git/worktrees/linked$,dup/main/.git/worktrees/linked," \
-		orig/linked.expect >dup/linked.expect &&
+	sed "s,orig,dup," orig/linked.expect >dup/linked.expect &&
 	git -C dup/main worktree repair ../linked &&
-	test_cmp orig/main.expect orig/main/.git/worktrees/linked/gitdir &&
+	test_cmp orig/main.expect orig/main/.git/worktrees/linked-*/gitdir &&
 	test_cmp orig/linked.expect orig/linked/.git &&
-	test_cmp dup/main.expect dup/main/.git/worktrees/linked/gitdir &&
+	test_cmp dup/main.expect dup/main/.git/worktrees/linked-*/gitdir &&
 	test_cmp dup/linked.expect dup/linked/.git
 '
 
@@ -221,11 +218,11 @@  test_expect_success 'repair worktree with relative path with missing gitfile' '
 	test_create_repo main &&
 	git -C main config worktree.useRelativePaths true &&
 	test_commit -C main init &&
-	git -C main worktree add --detach ../wt &&
+	GIT_TEST_WORKTREE_SUFFIX=123 git -C main worktree add --detach ../wt &&
 	rm wt/.git &&
 	test_path_is_missing wt/.git &&
 	git -C main worktree repair &&
-	echo "gitdir: ../main/.git/worktrees/wt" >expect &&
+	echo "gitdir: ../main/.git/worktrees/wt-123" >expect &&
 	test_cmp expect wt/.git
 '
 
@@ -233,12 +230,12 @@  test_expect_success 'repair absolute worktree to use relative paths' '
 	test_when_finished "rm -rf main side sidemoved" &&
 	test_create_repo main &&
 	test_commit -C main init &&
-	git -C main worktree add --detach ../side &&
+	GIT_TEST_WORKTREE_SUFFIX=456 git -C main worktree add --detach ../side &&
 	echo "../../../../sidemoved/.git" >expect-gitdir &&
-	echo "gitdir: ../main/.git/worktrees/side" >expect-gitfile &&
+	echo "gitdir: ../main/.git/worktrees/side-456" >expect-gitfile &&
 	mv side sidemoved &&
 	git -C main worktree repair --relative-paths ../sidemoved &&
-	test_cmp expect-gitdir main/.git/worktrees/side/gitdir &&
+	test_cmp expect-gitdir main/.git/worktrees/side-456/gitdir &&
 	test_cmp expect-gitfile sidemoved/.git
 '
 
@@ -246,13 +243,39 @@  test_expect_success 'repair relative worktree to use absolute paths' '
 	test_when_finished "rm -rf main side sidemoved" &&
 	test_create_repo main &&
 	test_commit -C main init &&
-	git -C main worktree add --relative-paths --detach ../side &&
+	GIT_TEST_WORKTREE_SUFFIX=789 git -C main worktree add --relative-paths --detach ../side &&
 	echo "$(pwd)/sidemoved/.git" >expect-gitdir &&
-	echo "gitdir: $(pwd)/main/.git/worktrees/side" >expect-gitfile &&
+	echo "gitdir: $(pwd)/main/.git/worktrees/side-789" >expect-gitfile &&
 	mv side sidemoved &&
 	git -C main worktree repair ../sidemoved &&
-	test_cmp expect-gitdir main/.git/worktrees/side/gitdir &&
+	test_cmp expect-gitdir main/.git/worktrees/side-789/gitdir &&
 	test_cmp expect-gitfile sidemoved/.git
 '
 
+test_expect_success 'does not repair worktrees from another repo' '
+	test_when_finished "rm -rf repo1 repo2" &&
+	mkdir -p repo1 &&
+	git -C repo1 init main &&
+	test_commit -C repo1/main nothing &&
+	git -C repo1/main worktree add ../linked &&
+	cp repo1/main/.git/worktrees/linked-*/gitdir repo1/main.expect &&
+	cp repo1/linked/.git repo1/linked.expect &&
+	mkdir -p repo2 &&
+	git -C repo2 init main &&
+	test_commit -C repo2/main nothing &&
+	git -C repo2/main worktree add ../linked &&
+	cp repo2/main/.git/worktrees/linked-*/gitdir repo2/main.expect &&
+	cp repo2/linked/.git repo2/linked.expect &&
+	git -C repo1/main worktree repair ../../repo2/linked &&
+	test_cmp repo1/main.expect repo1/main/.git/worktrees/linked-*/gitdir &&
+	test_cmp repo1/linked.expect repo1/linked/.git &&
+	test_cmp repo2/main.expect repo2/main/.git/worktrees/linked-*/gitdir &&
+	test_cmp repo2/linked.expect repo2/linked/.git &&
+	git -C repo2/main worktree repair ../../repo1/linked &&
+	test_cmp repo1/main.expect repo1/main/.git/worktrees/linked-*/gitdir &&
+	test_cmp repo1/linked.expect repo1/linked/.git &&
+	test_cmp repo2/main.expect repo2/main/.git/worktrees/linked-*/gitdir &&
+	test_cmp repo2/linked.expect repo2/linked/.git
+'
+
 test_done
diff --git a/t/t2407-worktree-heads.sh b/t/t2407-worktree-heads.sh
index f6835c91dcc49cfeb23881fe0ef7a96629bfb2e6..1587dadfd1e1fa122edccc62ffd9aa4c20f0ec80 100755
--- a/t/t2407-worktree-heads.sh
+++ b/t/t2407-worktree-heads.sh
@@ -19,7 +19,8 @@  test_expect_success 'setup' '
 		test_commit $i &&
 		git branch wt-$i &&
 		git branch fake-$i &&
-		git worktree add wt-$i wt-$i || return 1
+		GIT_TEST_WORKTREE_SUFFIX=$i \
+			git worktree add wt-$i wt-$i || return 1
 	done &&
 
 	# Create a server that updates each branch by one commit
@@ -132,20 +133,20 @@  test_expect_success 'refuse to overwrite when in error states' '
 	test_when_finished rm -rf .git/worktrees/wt-*/BISECT_* &&
 
 	# Both branches are currently under rebase.
-	mkdir -p .git/worktrees/wt-3/rebase-merge &&
-	touch .git/worktrees/wt-3/rebase-merge/interactive &&
-	echo refs/heads/fake-1 >.git/worktrees/wt-3/rebase-merge/head-name &&
-	echo refs/heads/fake-2 >.git/worktrees/wt-3/rebase-merge/onto &&
-	mkdir -p .git/worktrees/wt-4/rebase-merge &&
-	touch .git/worktrees/wt-4/rebase-merge/interactive &&
-	echo refs/heads/fake-2 >.git/worktrees/wt-4/rebase-merge/head-name &&
-	echo refs/heads/fake-1 >.git/worktrees/wt-4/rebase-merge/onto &&
+	mkdir -p .git/worktrees/wt-3-3/rebase-merge &&
+	touch .git/worktrees/wt-3-3/rebase-merge/interactive &&
+	echo refs/heads/fake-1 >.git/worktrees/wt-3-3/rebase-merge/head-name &&
+	echo refs/heads/fake-2 >.git/worktrees/wt-3-3/rebase-merge/onto &&
+	mkdir -p .git/worktrees/wt-4-4/rebase-merge &&
+	touch .git/worktrees/wt-4-4/rebase-merge/interactive &&
+	echo refs/heads/fake-2 >.git/worktrees/wt-4-4/rebase-merge/head-name &&
+	echo refs/heads/fake-1 >.git/worktrees/wt-4-4/rebase-merge/onto &&
 
 	# Both branches are currently under bisect.
-	touch .git/worktrees/wt-4/BISECT_LOG &&
-	echo refs/heads/fake-2 >.git/worktrees/wt-4/BISECT_START &&
-	touch .git/worktrees/wt-1/BISECT_LOG &&
-	echo refs/heads/fake-1 >.git/worktrees/wt-1/BISECT_START &&
+	touch .git/worktrees/wt-4-4/BISECT_LOG &&
+	echo refs/heads/fake-2 >.git/worktrees/wt-4-4/BISECT_START &&
+	touch .git/worktrees/wt-1-1/BISECT_LOG &&
+	echo refs/heads/fake-1 >.git/worktrees/wt-1-1/BISECT_START &&
 
 	for i in 1 2
 	do
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index ccfa6a720d090c2f7f2a085f60065bdcfaf8d1d9..e44497ac94394119662115b1f6aa035c7f0565d2 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -246,14 +246,14 @@  test_expect_success 'git branch -M baz bam should succeed when baz is checked ou
 '
 
 test_expect_success REFFILES 'git branch -M fails if updating any linked working tree fails' '
-	git worktree add -b baz bazdir1 &&
-	git worktree add -f bazdir2 baz &&
-	touch .git/worktrees/bazdir1/HEAD.lock &&
+	GIT_TEST_WORKTREE_SUFFIX=123 git worktree add -b baz bazdir1 &&
+	GIT_TEST_WORKTREE_SUFFIX=456 git worktree add -f bazdir2 baz &&
+	touch .git/worktrees/bazdir1-123/HEAD.lock &&
 	test_must_fail git branch -M baz bam &&
 	test $(git -C bazdir2 rev-parse --abbrev-ref HEAD) = bam &&
 	git branch -M bam baz &&
-	rm .git/worktrees/bazdir1/HEAD.lock &&
-	touch .git/worktrees/bazdir2/HEAD.lock &&
+	rm .git/worktrees/bazdir1-123/HEAD.lock &&
+	touch .git/worktrees/bazdir2-456/HEAD.lock &&
 	test_must_fail git branch -M baz bam &&
 	test $(git -C bazdir1 rev-parse --abbrev-ref HEAD) = bam &&
 	rm -rf bazdir1 bazdir2 &&
diff --git a/t/t5304-prune.sh b/t/t5304-prune.sh
index e641df0116c24404e4892a0e30af4ef4bf8db493..0e98c6627a98ed197d7ab1ff41e8dd41eeaff3ac 100755
--- a/t/t5304-prune.sh
+++ b/t/t5304-prune.sh
@@ -293,7 +293,7 @@  test_expect_success 'prune: handle HEAD in multiple worktrees' '
 	echo "new blob for third-worktree" >third-worktree/blob &&
 	git -C third-worktree add blob &&
 	git -C third-worktree commit -m "third" &&
-	rm .git/worktrees/third-worktree/index &&
+	rm .git/worktrees/third-worktree-*/index &&
 	test_must_fail git -C third-worktree show :blob &&
 	git prune --expire=now &&
 	git -C third-worktree show HEAD:blob >actual &&
diff --git a/t/t7412-submodule-absorbgitdirs.sh b/t/t7412-submodule-absorbgitdirs.sh
index f77832185765585e2bda1677f8cbbe13841127f7..acf9544e35966f054cbc90c37e84377c9f461f2b 100755
--- a/t/t7412-submodule-absorbgitdirs.sh
+++ b/t/t7412-submodule-absorbgitdirs.sh
@@ -123,7 +123,7 @@  test_expect_success 'absorb the git dir outside of primary worktree' '
 	test_when_finished "rm -rf repo-bare.git" &&
 	git clone --bare . repo-bare.git &&
 	test_when_finished "rm -rf repo-wt" &&
-	git -C repo-bare.git worktree add ../repo-wt &&
+	GIT_TEST_WORKTREE_SUFFIX=123 git -C repo-bare.git worktree add ../repo-wt &&
 
 	test_when_finished "rm -f .gitconfig" &&
 	test_config_global protocol.file.allow always &&
@@ -134,7 +134,7 @@  test_expect_success 'absorb the git dir outside of primary worktree' '
 	cat >expect <<-EOF &&
 	Migrating git directory of '\''sub2'\'' from
 	'\''$cwd/repo-wt/sub2/.git'\'' to
-	'\''$cwd/repo-bare.git/worktrees/repo-wt/modules/sub2'\''
+	'\''$cwd/repo-bare.git/worktrees/repo-wt-123/modules/sub2'\''
 	EOF
 	git -C repo-wt submodule absorbgitdirs 2>actual &&
 	test_cmp expect actual