Skip to content

Commit cea155c

Browse files
committed
t2403, t2405: test per-worktree submodule gitdir model
Add tests verifying: * "worktree add --recurse-submodules" initialises submodules and places their per-worktree gitdir at $GIT_COMMON_DIR/modules/<name>/worktrees/<wt-id>/ with a commondir file pointing at the shared repo and no objects/ of its own. * "worktree remove" deletes the per-worktree submodule gitdir and leaves the shared submodule repo intact. * Removal is blocked when any submodule uses the legacy shared gitdir layout or when submodule changes are uncommitted. * scan_modules_for_wt_id() handles nested submodule repos and slash-named submodule paths correctly. * The auto-absorb path works when the main worktree has an in-tree submodule .git/ directory. Signed-off-by: Jimmy Aguilar Mena <kratsbinovish@gmail.com>
1 parent 61fcb23 commit cea155c

2 files changed

Lines changed: 159 additions & 11 deletions

File tree

t/t2403-worktree-move.sh

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,13 +236,62 @@ test_expect_success 'remove a repo with uninitialized submodule' '
236236
)
237237
'
238238

239-
test_expect_success 'not remove a repo with initialized submodule' '
239+
test_expect_success 'remove a clean worktree with per-worktree submodule gitdir' '
240240
test_config_global protocol.file.allow always &&
241241
(
242242
cd withsub &&
243243
git worktree add to-remove HEAD &&
244244
git -C to-remove submodule update &&
245-
test_must_fail git worktree remove to-remove
245+
git worktree remove to-remove
246+
)
247+
'
248+
249+
test_expect_success 'not remove a repo with dirty initialized submodule' '
250+
test_config_global protocol.file.allow always &&
251+
(
252+
cd withsub &&
253+
git worktree add to-remove-dirty HEAD &&
254+
git -C to-remove-dirty submodule update &&
255+
echo dirty >to-remove-dirty/sub/dirty-file &&
256+
test_must_fail git worktree remove to-remove-dirty &&
257+
git worktree remove --force to-remove-dirty
258+
)
259+
'
260+
261+
test_expect_success 'not remove a worktree with mixed per-worktree and legacy submodule gitdirs' '
262+
test_config_global protocol.file.allow always &&
263+
(
264+
cd withsub &&
265+
git worktree add mixed-layout HEAD &&
266+
git -C mixed-layout submodule update &&
267+
# Inject a fake shared submodule repo in modules/ that lacks a
268+
# worktrees/mixed-layout/ entry (legacy model). scan_modules_for_wt_id
269+
# finds sub/ (has the entry) and legacy-sub/ (lacks it), returning 0.
270+
mkdir -p .git/modules/legacy-sub &&
271+
printf "ref: refs/heads/main\n" >.git/modules/legacy-sub/HEAD &&
272+
test_must_fail git worktree remove mixed-layout &&
273+
# Clean up the injected repo and the worktree.
274+
rm -rf .git/modules/legacy-sub &&
275+
git worktree remove --force mixed-layout
276+
)
277+
'
278+
279+
test_expect_success 'not remove a worktree whose per-worktree submodule has a legacy nested submodule gitdir' '
280+
test_config_global protocol.file.allow always &&
281+
(
282+
cd withsub &&
283+
git worktree add nested-sm HEAD &&
284+
git -C nested-sm submodule update &&
285+
# Inject a fake nested shared repo inside sub/modules/ that lacks
286+
# a worktrees/nested-sm/ entry. scan_modules_for_wt_id recurses
287+
# into sub/modules/ and finds it, forcing rejection.
288+
mkdir -p .git/modules/sub/modules/nested &&
289+
printf "ref: refs/heads/main\n" \
290+
>.git/modules/sub/modules/nested/HEAD &&
291+
test_must_fail git worktree remove nested-sm &&
292+
# Clean up.
293+
rm -rf .git/modules/sub/modules &&
294+
git worktree remove --force nested-sm
246295
)
247296
'
248297

t/t2405-worktree-submodule.sh

Lines changed: 108 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,38 @@ test_expect_success 'add superproject worktree' '
3434
git -C main worktree add "$base_path/worktree" "$rev1_hash_main"
3535
'
3636

37-
test_expect_failure 'submodule is checked out just after worktree add' '
37+
test_expect_failure 'submodule is checked out just after worktree add (without flag)' '
3838
git -C worktree diff --submodule main"^!" >out &&
3939
grep "file1 updated" out
4040
'
4141

42+
test_expect_success 'worktree add --recurse-submodules initializes submodules' '
43+
git -C main worktree add --recurse-submodules \
44+
"$base_path/worktree-recurse" "$rev1_hash_main" &&
45+
git -C worktree-recurse diff --submodule main"^!" >out &&
46+
grep "file1 updated" out
47+
'
48+
49+
test_expect_success 'submodule in --recurse-submodules worktree uses per-worktree gitdir' '
50+
# The per-worktree submodule gitdir must live under the worktree entry,
51+
# not under $GIT_COMMON_DIR/modules/, so it is cleaned up with the
52+
# worktree and does not disturb the main worktree submodule.
53+
sub_gitdir="$base_path/main/.git/modules/sub/worktrees/worktree-recurse" &&
54+
test -d "$sub_gitdir" &&
55+
# .git pointer in the working tree must reference the per-worktree gitdir
56+
echo "gitdir: ../../main/.git/modules/sub/worktrees/worktree-recurse" \
57+
>expect-gitfile &&
58+
cat "$base_path/worktree-recurse/sub/.git" >actual-gitfile &&
59+
test_cmp expect-gitfile actual-gitfile &&
60+
# The per-worktree gitdir must have a commondir file pointing at the
61+
# shared submodule repo, not its own object store.
62+
echo "../.." >expect-commondir &&
63+
test_cmp expect-commondir "$sub_gitdir/commondir" &&
64+
test_path_is_missing "$sub_gitdir/objects" &&
65+
# The working tree is populated (test_commit creates <name>.t files)
66+
test -f "$base_path/worktree-recurse/sub/file1.t"
67+
'
68+
4269
test_expect_success 'add superproject worktree and initialize submodules' '
4370
git -C main worktree add "$base_path/worktree-submodule-update" "$rev1_hash_main" &&
4471
git -C worktree-submodule-update submodule update
@@ -62,7 +89,7 @@ test_expect_success 'submodule is checked out after manually adding submodule wo
6289
test_expect_success 'checkout --recurse-submodules uses $GIT_DIR for submodules in a linked worktree' '
6390
git -C main worktree add "$base_path/checkout-recurse" --detach &&
6491
git -C checkout-recurse submodule update --init &&
65-
echo "gitdir: ../../main/.git/worktrees/checkout-recurse/modules/sub" >expect-gitfile &&
92+
echo "gitdir: ../../main/.git/modules/sub/worktrees/checkout-recurse" >expect-gitfile &&
6693
cat checkout-recurse/sub/.git >actual-gitfile &&
6794
test_cmp expect-gitfile actual-gitfile &&
6895
git -C main/sub rev-parse HEAD >expect-head-main &&
@@ -73,22 +100,94 @@ test_expect_success 'checkout --recurse-submodules uses $GIT_DIR for submodules
73100
test_cmp expect-head-main actual-head-main
74101
'
75102

76-
test_expect_success 'core.worktree is removed in $GIT_DIR/modules/<name>/config, not in $GIT_COMMON_DIR/modules/<name>/config' '
103+
test_expect_success 'per-worktree submodule gitdir uses commondir; shared config is unchanged' '
104+
# The shared submodule repo core.worktree points at the main worktree.
77105
echo "../../../sub" >expect-main &&
78106
git -C main/sub config --get core.worktree >actual-main &&
79107
test_cmp expect-main actual-main &&
80-
echo "../../../../../../checkout-recurse/sub" >expect-linked &&
81-
git -C checkout-recurse/sub config --get core.worktree >actual-linked &&
82-
test_cmp expect-linked actual-linked &&
108+
109+
# The per-worktree submodule gitdir has a commondir file pointing at
110+
# the shared submodule repo, not its own objects or refs.
111+
linked_sm_gitdir="main/.git/modules/sub/worktrees/checkout-recurse" &&
112+
test -f "$linked_sm_gitdir/commondir" &&
113+
echo "../.." >expect-commondir &&
114+
test_cmp expect-commondir "$linked_sm_gitdir/commondir" &&
115+
116+
# Checking out a commit that removes the submodule leaves the shared
117+
# submodule repo intact.
83118
git -C checkout-recurse checkout --recurse-submodules first &&
84-
test_expect_code 1 git -C main/.git/worktrees/checkout-recurse/modules/sub config --get core.worktree >linked-config &&
85-
test_must_be_empty linked-config &&
86119
git -C main/sub config --get core.worktree >actual-main &&
87120
test_cmp expect-main actual-main
88121
'
89122

123+
test_expect_success 'worktree remove cleans up per-worktree submodule gitdir' '
124+
git -C main worktree add "$base_path/remove-recurse" "$rev1_hash_main" &&
125+
git -C remove-recurse submodule update --init &&
126+
test -d "main/.git/modules/sub/worktrees/remove-recurse" &&
127+
git -C main worktree remove remove-recurse &&
128+
test_path_is_missing "main/.git/worktrees/remove-recurse" &&
129+
test_path_is_missing "remove-recurse" &&
130+
# The per-worktree submodule gitdir must also be removed.
131+
test_path_is_missing "main/.git/modules/sub/worktrees/remove-recurse" &&
132+
# The shared submodule repo must not be affected.
133+
test -d "main/.git/modules/sub" &&
134+
git -C main/sub log --oneline -1
135+
'
136+
90137
test_expect_success 'unsetting core.worktree does not prevent running commands directly against the submodule repository' '
91-
git -C main/.git/worktrees/checkout-recurse/modules/sub log
138+
git -C main/.git/modules/sub/worktrees/checkout-recurse log
139+
'
140+
141+
test_expect_success 'auto-absorb: submodule with in-tree gitdir is absorbed on first linked-worktree submodule init' '
142+
# Clone the superproject without initializing the submodule, then
143+
# clone the submodule in-tree (legacy layout: .git/ is a directory,
144+
# not absorbed into $GIT_DIR/modules/).
145+
git clone "$base_path/origin/main" main-intree &&
146+
test_when_finished "rm -rf main-intree worktree-absorb" &&
147+
git -C main-intree submodule init &&
148+
git clone "$base_path/origin/sub" main-intree/sub &&
149+
git -C main-intree/sub checkout "$rev1_hash_sub" &&
150+
# The submodule gitdir is in-tree: a directory, not a pointer file.
151+
test -d "main-intree/sub/.git" &&
152+
test_path_is_missing "main-intree/.git/modules" &&
153+
154+
# Initialize the submodule in a linked worktree: absorb_in_main_worktree
155+
# should relocate the in-tree gitdir to modules/sub, then set up the
156+
# per-worktree gitdir with commondir indirection.
157+
git -C main-intree worktree add "$base_path/worktree-absorb" "$rev1_hash_main" &&
158+
git -C worktree-absorb submodule update --init &&
159+
160+
# Absorption happened: submodule gitdir now lives under modules/.
161+
test -d "main-intree/.git/modules/sub" &&
162+
# Per-worktree gitdir with commondir exists.
163+
test -f "main-intree/.git/modules/sub/worktrees/worktree-absorb/commondir" &&
164+
# Submodule working tree is populated.
165+
test -f "worktree-absorb/sub/file1.t"
166+
'
167+
168+
test_expect_success 'worktree remove handles submodule with slash in name (nested modules path)' '
169+
# Create a superproject with a submodule whose path contains a slash so
170+
# that its gitdir lives at modules/nested/sub/ rather than modules/sub/.
171+
git init nested-super &&
172+
test_when_finished "rm -rf nested-super nested-worktree" &&
173+
git init nested-super/nested/sub &&
174+
test_commit -C nested-super/nested/sub file1 &&
175+
(
176+
cd nested-super &&
177+
git -c protocol.file.allow=always submodule add ./nested/sub nested/sub &&
178+
git commit -m "add nested/sub"
179+
) &&
180+
181+
git -C nested-super worktree add "$base_path/nested-worktree" HEAD &&
182+
git -C nested-worktree submodule update --init &&
183+
184+
# The per-worktree gitdir should be at modules/nested/sub/ with commondir.
185+
test -f "nested-super/.git/modules/nested/sub/worktrees/nested-worktree/commondir" &&
186+
187+
# worktree remove must succeed: all submodule gitdirs are per-worktree.
188+
git -C nested-super worktree remove "$base_path/nested-worktree" &&
189+
test_path_is_missing "nested-super/.git/worktrees/nested-worktree" &&
190+
test_path_is_missing "nested-worktree"
92191
'
93192

94193
test_done

0 commit comments

Comments
 (0)