1#!/bin/sh
2
3test_description='checkout into detached HEAD state'
4GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
5export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
6
7. ./test-lib.sh
8
9check_detached () {
10	test_must_fail git symbolic-ref -q HEAD >/dev/null
11}
12
13check_not_detached () {
14	git symbolic-ref -q HEAD >/dev/null
15}
16
17PREV_HEAD_DESC='Previous HEAD position was'
18check_orphan_warning() {
19	test_i18ngrep "you are leaving $2 behind" "$1" &&
20	test_i18ngrep ! "$PREV_HEAD_DESC" "$1"
21}
22check_no_orphan_warning() {
23	test_i18ngrep ! "you are leaving .* commit.*behind" "$1" &&
24	test_i18ngrep "$PREV_HEAD_DESC" "$1"
25}
26
27reset () {
28	git checkout main &&
29	check_not_detached
30}
31
32test_expect_success 'setup' '
33	test_commit one &&
34	test_commit two &&
35	test_commit three && git tag -d three &&
36	test_commit four && git tag -d four &&
37	git branch branch &&
38	git tag tag
39'
40
41test_expect_success 'checkout branch does not detach' '
42	reset &&
43	git checkout branch &&
44	check_not_detached
45'
46
47test_expect_success 'checkout tag detaches' '
48	reset &&
49	git checkout tag &&
50	check_detached
51'
52
53test_expect_success 'checkout branch by full name detaches' '
54	reset &&
55	git checkout refs/heads/branch &&
56	check_detached
57'
58
59test_expect_success 'checkout non-ref detaches' '
60	reset &&
61	git checkout branch^ &&
62	check_detached
63'
64
65test_expect_success 'checkout ref^0 detaches' '
66	reset &&
67	git checkout branch^0 &&
68	check_detached
69'
70
71test_expect_success 'checkout --detach detaches' '
72	reset &&
73	git checkout --detach branch &&
74	check_detached
75'
76
77test_expect_success 'checkout --detach without branch name' '
78	reset &&
79	git checkout --detach &&
80	check_detached
81'
82
83test_expect_success 'checkout --detach errors out for non-commit' '
84	reset &&
85	test_must_fail git checkout --detach one^{tree} &&
86	check_not_detached
87'
88
89test_expect_success 'checkout --detach errors out for extra argument' '
90	reset &&
91	git checkout main &&
92	test_must_fail git checkout --detach tag one.t &&
93	check_not_detached
94'
95
96test_expect_success 'checkout --detached and -b are incompatible' '
97	reset &&
98	test_must_fail git checkout --detach -b newbranch tag &&
99	check_not_detached
100'
101
102test_expect_success 'checkout --detach moves HEAD' '
103	reset &&
104	git checkout one &&
105	git checkout --detach two &&
106	git diff --exit-code HEAD &&
107	git diff --exit-code two
108'
109
110test_expect_success 'checkout warns on orphan commits' '
111	reset &&
112	git checkout --detach two &&
113	echo content >orphan &&
114	git add orphan &&
115	git commit -a -m orphan1 &&
116	echo new content >orphan &&
117	git commit -a -m orphan2 &&
118	orphan2=$(git rev-parse HEAD) &&
119	git checkout main 2>stderr
120'
121
122test_expect_success 'checkout warns on orphan commits: output' '
123	check_orphan_warning stderr "2 commits"
124'
125
126test_expect_success 'checkout warns orphaning 1 of 2 commits' '
127	git checkout "$orphan2" &&
128	git checkout HEAD^ 2>stderr
129'
130
131test_expect_success 'checkout warns orphaning 1 of 2 commits: output' '
132	check_orphan_warning stderr "1 commit"
133'
134
135test_expect_success 'checkout does not warn leaving ref tip' '
136	reset &&
137	git checkout --detach two &&
138	git checkout main 2>stderr
139'
140
141test_expect_success 'checkout does not warn leaving ref tip' '
142	check_no_orphan_warning stderr
143'
144
145test_expect_success 'checkout does not warn leaving reachable commit' '
146	reset &&
147	git checkout --detach HEAD^ &&
148	git checkout main 2>stderr
149'
150
151test_expect_success 'checkout does not warn leaving reachable commit' '
152	check_no_orphan_warning stderr
153'
154
155cat >expect <<'EOF'
156Your branch is behind 'main' by 1 commit, and can be fast-forwarded.
157  (use "git pull" to update your local branch)
158EOF
159test_expect_success 'tracking count is accurate after orphan check' '
160	reset &&
161	git branch child main^ &&
162	git config branch.child.remote . &&
163	git config branch.child.merge refs/heads/main &&
164	git checkout child^ &&
165	git checkout child >stdout &&
166	test_cmp expect stdout
167'
168
169test_expect_success 'no advice given for explicit detached head state' '
170	# baseline
171	test_config advice.detachedHead true &&
172	git checkout child && git checkout HEAD^0 >expect.advice 2>&1 &&
173	test_config advice.detachedHead false &&
174	git checkout child && git checkout HEAD^0 >expect.no-advice 2>&1 &&
175	test_unconfig advice.detachedHead &&
176	# without configuration, the advice.* variables default to true
177	git checkout child && git checkout HEAD^0 >actual 2>&1 &&
178	test_cmp expect.advice actual &&
179
180	# with explicit --detach
181	# no configuration
182	test_unconfig advice.detachedHead &&
183	git checkout child && git checkout --detach HEAD^0 >actual 2>&1 &&
184	test_cmp expect.no-advice actual &&
185
186	# explicitly decline advice
187	test_config advice.detachedHead false &&
188	git checkout child && git checkout --detach HEAD^0 >actual 2>&1 &&
189	test_cmp expect.no-advice actual
190'
191
192# Detached HEAD tests for GIT_PRINT_SHA1_ELLIPSIS (new format)
193test_expect_success 'describe_detached_head prints no SHA-1 ellipsis when not asked to' "
194
195	commit=$(git rev-parse --short=12 main^) &&
196	commit2=$(git rev-parse --short=12 main~2) &&
197	commit3=$(git rev-parse --short=12 main~3) &&
198
199	# The first detach operation is more chatty than the following ones.
200	cat >1st_detach <<-EOF &&
201	Note: switching to 'HEAD^'.
202
203	You are in 'detached HEAD' state. You can look around, make experimental
204	changes and commit them, and you can discard any commits you make in this
205	state without impacting any branches by switching back to a branch.
206
207	If you want to create a new branch to retain commits you create, you may
208	do so (now or later) by using -c with the switch command. Example:
209
210	  git switch -c <new-branch-name>
211
212	Or undo this operation with:
213
214	  git switch -
215
216	Turn off this advice by setting config variable advice.detachedHead to false
217
218	HEAD is now at \$commit three
219	EOF
220
221	# The remaining ones just show info about previous and current HEADs.
222	cat >2nd_detach <<-EOF &&
223	Previous HEAD position was \$commit three
224	HEAD is now at \$commit2 two
225	EOF
226
227	cat >3rd_detach <<-EOF &&
228	Previous HEAD position was \$commit2 two
229	HEAD is now at \$commit3 one
230	EOF
231
232	reset &&
233	check_not_detached &&
234
235	# Various ways of *not* asking for ellipses
236
237	sane_unset GIT_PRINT_SHA1_ELLIPSIS &&
238	git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 &&
239	check_detached &&
240	test_cmp 1st_detach actual &&
241
242	GIT_PRINT_SHA1_ELLIPSIS="no" git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 &&
243	check_detached &&
244	test_cmp 2nd_detach actual &&
245
246	GIT_PRINT_SHA1_ELLIPSIS= git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 &&
247	check_detached &&
248	test_cmp 3rd_detach actual &&
249
250	sane_unset GIT_PRINT_SHA1_ELLIPSIS &&
251
252	# We only have four commits, but we can re-use them
253	reset &&
254	check_not_detached &&
255
256	# Make no mention of the env var at all
257	git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 &&
258	check_detached &&
259	test_cmp 1st_detach actual &&
260
261	GIT_PRINT_SHA1_ELLIPSIS='nope' &&
262	git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 &&
263	check_detached &&
264	test_cmp 2nd_detach actual &&
265
266	GIT_PRINT_SHA1_ELLIPSIS=nein &&
267	git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 &&
268	check_detached &&
269	test_cmp 3rd_detach actual &&
270
271	true
272"
273
274# Detached HEAD tests for GIT_PRINT_SHA1_ELLIPSIS (old format)
275test_expect_success 'describe_detached_head does print SHA-1 ellipsis when asked to' "
276
277	commit=$(git rev-parse --short=12 main^) &&
278	commit2=$(git rev-parse --short=12 main~2) &&
279	commit3=$(git rev-parse --short=12 main~3) &&
280
281	# The first detach operation is more chatty than the following ones.
282	cat >1st_detach <<-EOF &&
283	Note: switching to 'HEAD^'.
284
285	You are in 'detached HEAD' state. You can look around, make experimental
286	changes and commit them, and you can discard any commits you make in this
287	state without impacting any branches by switching back to a branch.
288
289	If you want to create a new branch to retain commits you create, you may
290	do so (now or later) by using -c with the switch command. Example:
291
292	  git switch -c <new-branch-name>
293
294	Or undo this operation with:
295
296	  git switch -
297
298	Turn off this advice by setting config variable advice.detachedHead to false
299
300	HEAD is now at \$commit... three
301	EOF
302
303	# The remaining ones just show info about previous and current HEADs.
304	cat >2nd_detach <<-EOF &&
305	Previous HEAD position was \$commit... three
306	HEAD is now at \$commit2... two
307	EOF
308
309	cat >3rd_detach <<-EOF &&
310	Previous HEAD position was \$commit2... two
311	HEAD is now at \$commit3... one
312	EOF
313
314	reset &&
315	check_not_detached &&
316
317	# Various ways of asking for ellipses...
318	# The user can just use any kind of quoting (including none).
319
320	GIT_PRINT_SHA1_ELLIPSIS=yes git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 &&
321	check_detached &&
322	test_cmp 1st_detach actual &&
323
324	GIT_PRINT_SHA1_ELLIPSIS=Yes git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 &&
325	check_detached &&
326	test_cmp 2nd_detach actual &&
327
328	GIT_PRINT_SHA1_ELLIPSIS=YES git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 &&
329	check_detached &&
330	test_cmp 3rd_detach actual &&
331
332	true
333"
334
335test_done
336