git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH 1/2] worktree: add worktree with unique suffix

From
Caleb White <cdwhite3@pm.me>
Date
Nov 29, 2024, 02:44 UTC
Message-ID
<20241128-wt_unique_ids-v1-1-30345d010e43@pm.me>
In-Reply-To
<20241128-wt_unique_ids-v1-0-30345d010e43@pm.me>

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 --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
-- 
2.47.0
Previous: Caleb WhiteNext: Caleb White
Message 2 of 11 in “Ensure unique worktree ids across repositories”
  1. 0/2 Ensure unique worktree ids across repositoriesCaleb White, Nov 29, 2024
  2. 1/2 worktree: add worktree with unique suffixCaleb White, Nov 29, 2024
  3. 2/2 worktree: rename worktree id during worktree moveCaleb White, Nov 29, 2024
  4. Caleb WhiteNov 29, 2024
  5. Junio C HamanoNov 29, 2024
  6. Caleb WhiteNov 29, 2024
  7. Junio C HamanoDec 1, 2024
  8. shejialuoNov 29, 2024
  9. Caleb WhiteNov 29, 2024
  10. shejialuoNov 29, 2024
  11. Caleb WhiteNov 29, 2024

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.