1#!/bin/sh
2
3test_description='git status --porcelain=v2
4
5This test exercises porcelain V2 output for git status.'
6
7
8GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=master
9export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
10
11. ./test-lib.sh
12
13
14test_expect_success setup '
15	git checkout -f --orphan initial-branch &&
16	test_tick &&
17	git config core.autocrlf false &&
18	echo x >file_x &&
19	echo y >file_y &&
20	echo z >file_z &&
21	mkdir dir1 &&
22	echo a >dir1/file_a &&
23	echo b >dir1/file_b
24'
25
26test_expect_success 'before initial commit, nothing added, only untracked' '
27	cat >expect <<-EOF &&
28	# branch.oid (initial)
29	# branch.head initial-branch
30	? actual
31	? dir1/
32	? expect
33	? file_x
34	? file_y
35	? file_z
36	EOF
37
38	git status --porcelain=v2 --branch --untracked-files=normal >actual &&
39	test_cmp expect actual
40'
41
42test_expect_success 'before initial commit, things added' '
43	git add file_x file_y file_z dir1 &&
44	OID_A=$(git hash-object -t blob -- dir1/file_a) &&
45	OID_B=$(git hash-object -t blob -- dir1/file_b) &&
46	OID_X=$(git hash-object -t blob -- file_x) &&
47	OID_Y=$(git hash-object -t blob -- file_y) &&
48	OID_Z=$(git hash-object -t blob -- file_z) &&
49
50	cat >expect <<-EOF &&
51	# branch.oid (initial)
52	# branch.head initial-branch
53	1 A. N... 000000 100644 100644 $ZERO_OID $OID_A dir1/file_a
54	1 A. N... 000000 100644 100644 $ZERO_OID $OID_B dir1/file_b
55	1 A. N... 000000 100644 100644 $ZERO_OID $OID_X file_x
56	1 A. N... 000000 100644 100644 $ZERO_OID $OID_Y file_y
57	1 A. N... 000000 100644 100644 $ZERO_OID $OID_Z file_z
58	? actual
59	? expect
60	EOF
61
62	git status --porcelain=v2 --branch --untracked-files=all >actual &&
63	test_cmp expect actual
64'
65
66test_expect_success 'before initial commit, things added (-z)' '
67	lf_to_nul >expect <<-EOF &&
68	# branch.oid (initial)
69	# branch.head initial-branch
70	1 A. N... 000000 100644 100644 $ZERO_OID $OID_A dir1/file_a
71	1 A. N... 000000 100644 100644 $ZERO_OID $OID_B dir1/file_b
72	1 A. N... 000000 100644 100644 $ZERO_OID $OID_X file_x
73	1 A. N... 000000 100644 100644 $ZERO_OID $OID_Y file_y
74	1 A. N... 000000 100644 100644 $ZERO_OID $OID_Z file_z
75	? actual
76	? expect
77	EOF
78
79	git status -z --porcelain=v2 --branch --untracked-files=all >actual &&
80	test_cmp expect actual
81'
82
83test_expect_success 'make first commit, comfirm HEAD oid and branch' '
84	git commit -m initial &&
85	H0=$(git rev-parse HEAD) &&
86	cat >expect <<-EOF &&
87	# branch.oid $H0
88	# branch.head initial-branch
89	? actual
90	? expect
91	EOF
92
93	git status --porcelain=v2 --branch --untracked-files=all >actual &&
94	test_cmp expect actual
95'
96
97test_expect_success 'after first commit, create unstaged changes' '
98	echo x >>file_x &&
99	OID_X1=$(git hash-object -t blob -- file_x) &&
100	rm file_z &&
101	H0=$(git rev-parse HEAD) &&
102
103	cat >expect <<-EOF &&
104	# branch.oid $H0
105	# branch.head initial-branch
106	1 .M N... 100644 100644 100644 $OID_X $OID_X file_x
107	1 .D N... 100644 100644 000000 $OID_Z $OID_Z file_z
108	? actual
109	? expect
110	EOF
111
112	git status --porcelain=v2 --branch --untracked-files=all >actual &&
113	test_cmp expect actual
114'
115
116test_expect_success 'after first commit but omit untracked files and branch' '
117	cat >expect <<-EOF &&
118	1 .M N... 100644 100644 100644 $OID_X $OID_X file_x
119	1 .D N... 100644 100644 000000 $OID_Z $OID_Z file_z
120	EOF
121
122	git status --porcelain=v2 --untracked-files=no >actual &&
123	test_cmp expect actual
124'
125
126test_expect_success 'after first commit, stage existing changes' '
127	git add file_x &&
128	git rm file_z &&
129	H0=$(git rev-parse HEAD) &&
130
131	cat >expect <<-EOF &&
132	# branch.oid $H0
133	# branch.head initial-branch
134	1 M. N... 100644 100644 100644 $OID_X $OID_X1 file_x
135	1 D. N... 100644 000000 000000 $OID_Z $ZERO_OID file_z
136	? actual
137	? expect
138	EOF
139
140	git status --porcelain=v2 --branch --untracked-files=all >actual &&
141	test_cmp expect actual
142'
143
144test_expect_success 'rename causes 2 path lines' '
145	git mv file_y renamed_y &&
146	H0=$(git rev-parse HEAD) &&
147
148	q_to_tab >expect <<-EOF &&
149	# branch.oid $H0
150	# branch.head initial-branch
151	1 M. N... 100644 100644 100644 $OID_X $OID_X1 file_x
152	1 D. N... 100644 000000 000000 $OID_Z $ZERO_OID file_z
153	2 R. N... 100644 100644 100644 $OID_Y $OID_Y R100 renamed_yQfile_y
154	? actual
155	? expect
156	EOF
157
158	git status --porcelain=v2 --branch --untracked-files=all >actual &&
159	test_cmp expect actual
160'
161
162test_expect_success 'rename causes 2 path lines (-z)' '
163	H0=$(git rev-parse HEAD) &&
164
165	## Lines use NUL path separator and line terminator, so double transform here.
166	q_to_nul <<-EOF | lf_to_nul >expect &&
167	# branch.oid $H0
168	# branch.head initial-branch
169	1 M. N... 100644 100644 100644 $OID_X $OID_X1 file_x
170	1 D. N... 100644 000000 000000 $OID_Z $ZERO_OID file_z
171	2 R. N... 100644 100644 100644 $OID_Y $OID_Y R100 renamed_yQfile_y
172	? actual
173	? expect
174	EOF
175
176	git status --porcelain=v2 --branch --untracked-files=all -z >actual &&
177	test_cmp expect actual
178'
179
180test_expect_success 'make second commit, confirm clean and new HEAD oid' '
181	git commit -m second &&
182	H1=$(git rev-parse HEAD) &&
183
184	cat >expect <<-EOF &&
185	# branch.oid $H1
186	# branch.head initial-branch
187	? actual
188	? expect
189	EOF
190
191	git status --porcelain=v2 --branch --untracked-files=all >actual &&
192	test_cmp expect actual
193'
194
195test_expect_success 'confirm ignored files are not printed' '
196	test_when_finished "rm -f x.ign .gitignore" &&
197	echo x.ign >.gitignore &&
198	echo "ignore me" >x.ign &&
199
200	cat >expect <<-EOF &&
201	? .gitignore
202	? actual
203	? expect
204	EOF
205
206	git status --porcelain=v2 --untracked-files=all >actual &&
207	test_cmp expect actual
208'
209
210test_expect_success 'ignored files are printed with --ignored' '
211	test_when_finished "rm -f x.ign .gitignore" &&
212	echo x.ign >.gitignore &&
213	echo "ignore me" >x.ign &&
214
215	cat >expect <<-EOF &&
216	? .gitignore
217	? actual
218	? expect
219	! x.ign
220	EOF
221
222	git status --porcelain=v2 --ignored --untracked-files=all >actual &&
223	test_cmp expect actual
224'
225
226test_expect_success 'create and commit permanent ignore file' '
227	cat >.gitignore <<-EOF &&
228	actual*
229	expect*
230	EOF
231
232	git add .gitignore &&
233	git commit -m ignore_trash &&
234	H1=$(git rev-parse HEAD) &&
235
236	cat >expect <<-EOF &&
237	# branch.oid $H1
238	# branch.head initial-branch
239	EOF
240
241	git status --porcelain=v2 --branch >actual &&
242	test_cmp expect actual
243'
244
245test_expect_success 'verify --intent-to-add output' '
246	test_when_finished "git rm -f intent1.add intent2.add" &&
247	touch intent1.add &&
248	echo test >intent2.add &&
249
250	git add --intent-to-add intent1.add intent2.add &&
251
252	cat >expect <<-EOF &&
253	1 .A N... 000000 000000 100644 $ZERO_OID $ZERO_OID intent1.add
254	1 .A N... 000000 000000 100644 $ZERO_OID $ZERO_OID intent2.add
255	EOF
256
257	git status --porcelain=v2 >actual &&
258	test_cmp expect actual
259'
260
261test_expect_success 'verify AA (add-add) conflict' '
262	test_when_finished "git reset --hard" &&
263
264	git branch AA_A initial-branch &&
265	git checkout AA_A &&
266	echo "Branch AA_A" >conflict.txt &&
267	OID_AA_A=$(git hash-object -t blob -- conflict.txt) &&
268	git add conflict.txt &&
269	git commit -m "branch aa_a" &&
270
271	git branch AA_B initial-branch &&
272	git checkout AA_B &&
273	echo "Branch AA_B" >conflict.txt &&
274	OID_AA_B=$(git hash-object -t blob -- conflict.txt) &&
275	git add conflict.txt &&
276	git commit -m "branch aa_b" &&
277
278	git branch AA_M AA_B &&
279	git checkout AA_M &&
280	test_must_fail git merge AA_A &&
281
282	HM=$(git rev-parse HEAD) &&
283
284	cat >expect <<-EOF &&
285	# branch.oid $HM
286	# branch.head AA_M
287	u AA N... 000000 100644 100644 100644 $ZERO_OID $OID_AA_B $OID_AA_A conflict.txt
288	EOF
289
290	git status --porcelain=v2 --branch --untracked-files=all >actual &&
291	test_cmp expect actual
292'
293
294test_expect_success 'verify UU (edit-edit) conflict' '
295	test_when_finished "git reset --hard" &&
296
297	git branch UU_ANC initial-branch &&
298	git checkout UU_ANC &&
299	echo "Ancestor" >conflict.txt &&
300	OID_UU_ANC=$(git hash-object -t blob -- conflict.txt) &&
301	git add conflict.txt &&
302	git commit -m "UU_ANC" &&
303
304	git branch UU_A UU_ANC &&
305	git checkout UU_A &&
306	echo "Branch UU_A" >conflict.txt &&
307	OID_UU_A=$(git hash-object -t blob -- conflict.txt) &&
308	git add conflict.txt &&
309	git commit -m "branch uu_a" &&
310
311	git branch UU_B UU_ANC &&
312	git checkout UU_B &&
313	echo "Branch UU_B" >conflict.txt &&
314	OID_UU_B=$(git hash-object -t blob -- conflict.txt) &&
315	git add conflict.txt &&
316	git commit -m "branch uu_b" &&
317
318	git branch UU_M UU_B &&
319	git checkout UU_M &&
320	test_must_fail git merge UU_A &&
321
322	HM=$(git rev-parse HEAD) &&
323
324	cat >expect <<-EOF &&
325	# branch.oid $HM
326	# branch.head UU_M
327	u UU N... 100644 100644 100644 100644 $OID_UU_ANC $OID_UU_B $OID_UU_A conflict.txt
328	EOF
329
330	git status --porcelain=v2 --branch --untracked-files=all >actual &&
331	test_cmp expect actual
332'
333
334test_expect_success 'verify upstream fields in branch header' '
335	git checkout initial-branch &&
336	test_when_finished "rm -rf sub_repo" &&
337	git clone . sub_repo &&
338	(
339		## Confirm local initial-branch tracks remote initial-branch.
340		cd sub_repo &&
341		HUF=$(git rev-parse HEAD) &&
342
343		cat >expect <<-EOF &&
344		# branch.oid $HUF
345		# branch.head initial-branch
346		# branch.upstream origin/initial-branch
347		# branch.ab +0 -0
348		EOF
349
350		git status --porcelain=v2 --branch --untracked-files=all >actual &&
351		test_cmp expect actual &&
352
353		## Test ahead/behind.
354		echo xyz >file_xyz &&
355		git add file_xyz &&
356		git commit -m xyz &&
357
358		HUF=$(git rev-parse HEAD) &&
359
360		cat >expect <<-EOF &&
361		# branch.oid $HUF
362		# branch.head initial-branch
363		# branch.upstream origin/initial-branch
364		# branch.ab +1 -0
365		EOF
366
367		git status --porcelain=v2 --branch --untracked-files=all >actual &&
368		test_cmp expect actual &&
369
370		## Repeat the above but without --branch.
371		git status --porcelain=v2 --untracked-files=all >actual &&
372		test_must_be_empty actual &&
373
374		## Test upstream-gone case. Fake this by pointing
375		## origin/initial-branch at a non-existing commit.
376		git update-ref -d refs/remotes/origin/initial-branch &&
377
378		HUF=$(git rev-parse HEAD) &&
379
380		cat >expect <<-EOF &&
381		# branch.oid $HUF
382		# branch.head initial-branch
383		# branch.upstream origin/initial-branch
384		EOF
385
386		git status --porcelain=v2 --branch --untracked-files=all >actual &&
387		test_cmp expect actual
388	)
389'
390
391test_expect_success 'verify --[no-]ahead-behind with V2 format' '
392	git checkout initial-branch &&
393	test_when_finished "rm -rf sub_repo" &&
394	git clone . sub_repo &&
395	(
396		## Confirm local initial-branch tracks remote initial-branch.
397		cd sub_repo &&
398		HUF=$(git rev-parse HEAD) &&
399
400		# Confirm --no-ahead-behind reports traditional branch.ab with 0/0 for equal branches.
401		cat >expect <<-EOF &&
402		# branch.oid $HUF
403		# branch.head initial-branch
404		# branch.upstream origin/initial-branch
405		# branch.ab +0 -0
406		EOF
407
408		git status --no-ahead-behind --porcelain=v2 --branch --untracked-files=all >actual &&
409		test_cmp expect actual &&
410
411		# Confirm --ahead-behind reports traditional branch.ab with 0/0.
412		cat >expect <<-EOF &&
413		# branch.oid $HUF
414		# branch.head initial-branch
415		# branch.upstream origin/initial-branch
416		# branch.ab +0 -0
417		EOF
418
419		git status --ahead-behind --porcelain=v2 --branch --untracked-files=all >actual &&
420		test_cmp expect actual &&
421
422		## Test non-equal ahead/behind.
423		echo xyz >file_xyz &&
424		git add file_xyz &&
425		git commit -m xyz &&
426
427		HUF=$(git rev-parse HEAD) &&
428
429		# Confirm --no-ahead-behind reports branch.ab with ?/? for non-equal branches.
430		cat >expect <<-EOF &&
431		# branch.oid $HUF
432		# branch.head initial-branch
433		# branch.upstream origin/initial-branch
434		# branch.ab +? -?
435		EOF
436
437		git status --no-ahead-behind --porcelain=v2 --branch --untracked-files=all >actual &&
438		test_cmp expect actual &&
439
440		# Confirm --ahead-behind reports traditional branch.ab with 1/0.
441		cat >expect <<-EOF &&
442		# branch.oid $HUF
443		# branch.head initial-branch
444		# branch.upstream origin/initial-branch
445		# branch.ab +1 -0
446		EOF
447
448		git status --ahead-behind --porcelain=v2 --branch --untracked-files=all >actual &&
449		test_cmp expect actual &&
450
451		# Confirm that "status.aheadbehind" DOES NOT work on V2 format.
452		git -c status.aheadbehind=false status --porcelain=v2 --branch --untracked-files=all >actual &&
453		test_cmp expect actual &&
454
455		# Confirm that "status.aheadbehind" DOES NOT work on V2 format.
456		git -c status.aheadbehind=true status --porcelain=v2 --branch --untracked-files=all >actual &&
457		test_cmp expect actual
458	)
459'
460
461test_expect_success 'create and add submodule, submodule appears clean (A. S...)' '
462	git checkout initial-branch &&
463	git clone . sub_repo &&
464	git clone . super_repo &&
465	(	cd super_repo &&
466		git submodule add ../sub_repo sub1 &&
467
468		## Confirm stage/add of clean submodule.
469		HMOD=$(git hash-object -t blob -- .gitmodules) &&
470		HSUP=$(git rev-parse HEAD) &&
471		HSUB=$HSUP &&
472
473		cat >expect <<-EOF &&
474		# branch.oid $HSUP
475		# branch.head initial-branch
476		# branch.upstream origin/initial-branch
477		# branch.ab +0 -0
478		1 A. N... 000000 100644 100644 $ZERO_OID $HMOD .gitmodules
479		1 A. S... 000000 160000 160000 $ZERO_OID $HSUB sub1
480		EOF
481
482		git status --porcelain=v2 --branch --untracked-files=all >actual &&
483		test_cmp expect actual
484	)
485'
486
487test_expect_success 'untracked changes in added submodule (AM S..U)' '
488	(	cd super_repo &&
489		## create untracked file in the submodule.
490		(	cd sub1 &&
491			echo "xxxx" >file_in_sub
492		) &&
493
494		HMOD=$(git hash-object -t blob -- .gitmodules) &&
495		HSUP=$(git rev-parse HEAD) &&
496		HSUB=$HSUP &&
497
498		cat >expect <<-EOF &&
499		# branch.oid $HSUP
500		# branch.head initial-branch
501		# branch.upstream origin/initial-branch
502		# branch.ab +0 -0
503		1 A. N... 000000 100644 100644 $ZERO_OID $HMOD .gitmodules
504		1 AM S..U 000000 160000 160000 $ZERO_OID $HSUB sub1
505		EOF
506
507		git status --porcelain=v2 --branch --untracked-files=all >actual &&
508		test_cmp expect actual
509	)
510'
511
512test_expect_success 'staged changes in added submodule (AM S.M.)' '
513	(	cd super_repo &&
514		## stage the changes in the submodule.
515		(	cd sub1 &&
516			git add file_in_sub
517		) &&
518
519		HMOD=$(git hash-object -t blob -- .gitmodules) &&
520		HSUP=$(git rev-parse HEAD) &&
521		HSUB=$HSUP &&
522
523		cat >expect <<-EOF &&
524		# branch.oid $HSUP
525		# branch.head initial-branch
526		# branch.upstream origin/initial-branch
527		# branch.ab +0 -0
528		1 A. N... 000000 100644 100644 $ZERO_OID $HMOD .gitmodules
529		1 AM S.M. 000000 160000 160000 $ZERO_OID $HSUB sub1
530		EOF
531
532		git status --porcelain=v2 --branch --untracked-files=all >actual &&
533		test_cmp expect actual
534	)
535'
536
537test_expect_success 'staged and unstaged changes in added (AM S.M.)' '
538	(	cd super_repo &&
539		(	cd sub1 &&
540			## make additional unstaged changes (on the same file) in the submodule.
541			## This does not cause us to get S.MU (because the submodule does not report
542			## a "?" line for the unstaged changes).
543			echo "more changes" >>file_in_sub
544		) &&
545
546		HMOD=$(git hash-object -t blob -- .gitmodules) &&
547		HSUP=$(git rev-parse HEAD) &&
548		HSUB=$HSUP &&
549
550		cat >expect <<-EOF &&
551		# branch.oid $HSUP
552		# branch.head initial-branch
553		# branch.upstream origin/initial-branch
554		# branch.ab +0 -0
555		1 A. N... 000000 100644 100644 $ZERO_OID $HMOD .gitmodules
556		1 AM S.M. 000000 160000 160000 $ZERO_OID $HSUB sub1
557		EOF
558
559		git status --porcelain=v2 --branch --untracked-files=all >actual &&
560		test_cmp expect actual
561	)
562'
563
564test_expect_success 'staged and untracked changes in added submodule (AM S.MU)' '
565	(	cd super_repo &&
566		(	cd sub1 &&
567			## stage new changes in tracked file.
568			git add file_in_sub &&
569			## create new untracked file.
570			echo "yyyy" >>another_file_in_sub
571		) &&
572
573		HMOD=$(git hash-object -t blob -- .gitmodules) &&
574		HSUP=$(git rev-parse HEAD) &&
575		HSUB=$HSUP &&
576
577		cat >expect <<-EOF &&
578		# branch.oid $HSUP
579		# branch.head initial-branch
580		# branch.upstream origin/initial-branch
581		# branch.ab +0 -0
582		1 A. N... 000000 100644 100644 $ZERO_OID $HMOD .gitmodules
583		1 AM S.MU 000000 160000 160000 $ZERO_OID $HSUB sub1
584		EOF
585
586		git status --porcelain=v2 --branch --untracked-files=all >actual &&
587		test_cmp expect actual
588	)
589'
590
591test_expect_success 'commit within the submodule appears as new commit in super (AM SC..)' '
592	(	cd super_repo &&
593		(	cd sub1 &&
594			## Make a new commit in the submodule.
595			git add file_in_sub &&
596			rm -f another_file_in_sub &&
597			git commit -m "new commit"
598		) &&
599
600		HMOD=$(git hash-object -t blob -- .gitmodules) &&
601		HSUP=$(git rev-parse HEAD) &&
602		HSUB=$HSUP &&
603
604		cat >expect <<-EOF &&
605		# branch.oid $HSUP
606		# branch.head initial-branch
607		# branch.upstream origin/initial-branch
608		# branch.ab +0 -0
609		1 A. N... 000000 100644 100644 $ZERO_OID $HMOD .gitmodules
610		1 AM SC.. 000000 160000 160000 $ZERO_OID $HSUB sub1
611		EOF
612
613		git status --porcelain=v2 --branch --untracked-files=all >actual &&
614		test_cmp expect actual
615	)
616'
617
618test_expect_success 'stage submodule in super and commit' '
619	(	cd super_repo &&
620		## Stage the new submodule commit in the super.
621		git add sub1 &&
622		## Commit the super so that the sub no longer appears as added.
623		git commit -m "super commit" &&
624
625		HSUP=$(git rev-parse HEAD) &&
626
627		cat >expect <<-EOF &&
628		# branch.oid $HSUP
629		# branch.head initial-branch
630		# branch.upstream origin/initial-branch
631		# branch.ab +1 -0
632		EOF
633
634		git status --porcelain=v2 --branch --untracked-files=all >actual &&
635		test_cmp expect actual
636	)
637'
638
639test_expect_success 'make unstaged changes in existing submodule (.M S.M.)' '
640	(	cd super_repo &&
641		(	cd sub1 &&
642			echo "zzzz" >>file_in_sub
643		) &&
644
645		HSUP=$(git rev-parse HEAD) &&
646		HSUB=$(cd sub1 && git rev-parse HEAD) &&
647
648		cat >expect <<-EOF &&
649		# branch.oid $HSUP
650		# branch.head initial-branch
651		# branch.upstream origin/initial-branch
652		# branch.ab +1 -0
653		1 .M S.M. 160000 160000 160000 $HSUB $HSUB sub1
654		EOF
655
656		git status --porcelain=v2 --branch --untracked-files=all >actual &&
657		test_cmp expect actual
658	)
659'
660
661test_done
662