1#!/bin/sh
2#
3# Copyright (c) 2009 Johan Herland
4#
5
6test_description='test git fast-import of notes objects'
7GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
8export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
9
10. ./test-lib.sh
11
12
13test_tick
14cat >input <<INPUT_END
15commit refs/heads/main
16committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
17data <<COMMIT
18first commit
19COMMIT
20
21M 644 inline foo
22data <<EOF
23file foo in first commit
24EOF
25
26M 755 inline bar
27data <<EOF
28file bar in first commit
29EOF
30
31M 644 inline baz/xyzzy
32data <<EOF
33file baz/xyzzy in first commit
34EOF
35
36commit refs/heads/main
37committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
38data <<COMMIT
39second commit
40COMMIT
41
42M 644 inline foo
43data <<EOF
44file foo in second commit
45EOF
46
47M 755 inline baz/xyzzy
48data <<EOF
49file baz/xyzzy in second commit
50EOF
51
52commit refs/heads/main
53committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
54data <<COMMIT
55third commit
56COMMIT
57
58M 644 inline foo
59data <<EOF
60file foo in third commit
61EOF
62
63commit refs/heads/main
64committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
65data <<COMMIT
66fourth commit
67COMMIT
68
69M 755 inline bar
70data <<EOF
71file bar in fourth commit
72EOF
73
74INPUT_END
75
76test_expect_success 'set up main branch' '
77
78	git fast-import <input &&
79	git whatchanged main
80'
81
82commit4=$(git rev-parse refs/heads/main)
83commit3=$(git rev-parse "$commit4^")
84commit2=$(git rev-parse "$commit4~2")
85commit1=$(git rev-parse "$commit4~3")
86
87test_tick
88cat >input <<INPUT_END
89commit refs/notes/test
90committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
91data <<COMMIT
92first notes commit
93COMMIT
94
95M 644 inline $commit1
96data <<EOF
97first note for first commit
98EOF
99
100M 755 inline $commit2
101data <<EOF
102first note for second commit
103EOF
104
105INPUT_END
106
107cat >expect <<EXPECT_END
108    fourth commit
109    third commit
110    second commit
111    first note for second commit
112    first commit
113    first note for first commit
114EXPECT_END
115
116test_expect_success 'add notes with simple M command' '
117
118	git fast-import <input &&
119	GIT_NOTES_REF=refs/notes/test git log | grep "^    " > actual &&
120	test_cmp expect actual
121
122'
123
124test_tick
125cat >input <<INPUT_END
126feature notes
127commit refs/notes/test
128committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
129data <<COMMIT
130second notes commit
131COMMIT
132
133from refs/notes/test^0
134N inline $commit3
135data <<EOF
136first note for third commit
137EOF
138
139N inline $commit4
140data <<EOF
141first note for fourth commit
142EOF
143
144INPUT_END
145
146cat >expect <<EXPECT_END
147    fourth commit
148    first note for fourth commit
149    third commit
150    first note for third commit
151    second commit
152    first note for second commit
153    first commit
154    first note for first commit
155EXPECT_END
156
157test_expect_success 'add notes with simple N command' '
158
159	git fast-import <input &&
160	GIT_NOTES_REF=refs/notes/test git log | grep "^    " > actual &&
161	test_cmp expect actual
162
163'
164
165test_tick
166cat >input <<INPUT_END
167commit refs/notes/test
168committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
169data <<COMMIT
170third notes commit
171COMMIT
172
173from refs/notes/test^0
174N inline $commit1
175data <<EOF
176second note for first commit
177EOF
178
179N inline $commit2
180data <<EOF
181second note for second commit
182EOF
183
184N inline $commit3
185data <<EOF
186second note for third commit
187EOF
188
189N inline $commit4
190data <<EOF
191second note for fourth commit
192EOF
193
194INPUT_END
195
196cat >expect <<EXPECT_END
197    fourth commit
198    second note for fourth commit
199    third commit
200    second note for third commit
201    second commit
202    second note for second commit
203    first commit
204    second note for first commit
205EXPECT_END
206
207test_expect_success 'update existing notes with N command' '
208
209	git fast-import <input &&
210	GIT_NOTES_REF=refs/notes/test git log | grep "^    " > actual &&
211	test_cmp expect actual
212
213'
214
215test_tick
216cat >input <<INPUT_END
217commit refs/notes/test
218committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
219data <<COMMIT
220fourth notes commit
221COMMIT
222
223from refs/notes/test^0
224M 644 inline $(echo "$commit3" | sed "s|^..|&/|")
225data <<EOF
226prefix of note for third commit
227EOF
228
229M 644 inline $(echo "$commit4" | sed "s|^..|&/|")
230data <<EOF
231prefix of note for fourth commit
232EOF
233
234M 644 inline $(echo "$commit4" | sed "s|^\(..\)\(..\)|\1/\2/|")
235data <<EOF
236pre-prefix of note for fourth commit
237EOF
238
239N inline $commit1
240data <<EOF
241third note for first commit
242EOF
243
244N inline $commit2
245data <<EOF
246third note for second commit
247EOF
248
249N inline $commit3
250data <<EOF
251third note for third commit
252EOF
253
254N inline $commit4
255data <<EOF
256third note for fourth commit
257EOF
258
259
260INPUT_END
261
262whitespace="    "
263
264cat >expect <<EXPECT_END
265    fourth commit
266    pre-prefix of note for fourth commit
267$whitespace
268    prefix of note for fourth commit
269$whitespace
270    third note for fourth commit
271    third commit
272    prefix of note for third commit
273$whitespace
274    third note for third commit
275    second commit
276    third note for second commit
277    first commit
278    third note for first commit
279EXPECT_END
280
281test_expect_success 'add concatenation notes with M command' '
282
283	git fast-import <input &&
284	GIT_NOTES_REF=refs/notes/test git log | grep "^    " > actual &&
285	test_cmp expect actual
286
287'
288
289test_tick
290cat >input <<INPUT_END
291commit refs/notes/test
292committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
293data <<COMMIT
294fifth notes commit
295COMMIT
296
297from refs/notes/test^0
298deleteall
299
300INPUT_END
301
302cat >expect <<EXPECT_END
303    fourth commit
304    third commit
305    second commit
306    first commit
307EXPECT_END
308
309test_expect_success 'verify that deleteall also removes notes' '
310
311	git fast-import <input &&
312	GIT_NOTES_REF=refs/notes/test git log | grep "^    " > actual &&
313	test_cmp expect actual
314
315'
316
317test_tick
318cat >input <<INPUT_END
319commit refs/notes/test
320committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
321data <<COMMIT
322sixth notes commit
323COMMIT
324
325from refs/notes/test^0
326M 644 inline $commit1
327data <<EOF
328third note for first commit
329EOF
330
331M 644 inline $commit3
332data <<EOF
333third note for third commit
334EOF
335
336N inline $commit1
337data <<EOF
338fourth note for first commit
339EOF
340
341N inline $commit3
342data <<EOF
343fourth note for third commit
344EOF
345
346INPUT_END
347
348cat >expect <<EXPECT_END
349    fourth commit
350    third commit
351    fourth note for third commit
352    second commit
353    first commit
354    fourth note for first commit
355EXPECT_END
356
357test_expect_success 'verify that later N commands override earlier M commands' '
358
359	git fast-import <input &&
360	GIT_NOTES_REF=refs/notes/test git log | grep "^    " > actual &&
361	test_cmp expect actual
362
363'
364
365# Write fast-import commands to create the given number of commits
366fast_import_commits () {
367	my_ref=$1
368	my_num_commits=$2
369	my_append_to_file=$3
370	my_i=0
371	while test $my_i -lt $my_num_commits
372	do
373		my_i=$(($my_i + 1))
374		test_tick
375		cat >>"$my_append_to_file" <<INPUT_END
376commit $my_ref
377mark :$my_i
378committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
379data <<COMMIT
380commit #$my_i
381COMMIT
382
383M 644 inline file
384data <<EOF
385file contents in commit #$my_i
386EOF
387
388INPUT_END
389	done
390}
391
392# Write fast-import commands to create the given number of notes annotating
393# the commits created by fast_import_commits()
394fast_import_notes () {
395	my_notes_ref=$1
396	my_num_commits=$2
397	my_append_to_file=$3
398	my_note_append=$4
399	test_tick
400	cat >>"$my_append_to_file" <<INPUT_END
401commit $my_notes_ref
402committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
403data <<COMMIT
404committing $my_num_commits notes
405COMMIT
406
407INPUT_END
408
409	my_i=0
410	while test $my_i -lt $my_num_commits
411	do
412		my_i=$(($my_i + 1))
413		cat >>"$my_append_to_file" <<INPUT_END
414N inline :$my_i
415data <<EOF
416note for commit #$my_i$my_note_append
417EOF
418
419INPUT_END
420	done
421}
422
423
424rm input expect
425num_commits=400
426# Create lots of commits
427fast_import_commits "refs/heads/many_commits" $num_commits input
428# Create one note per above commit
429fast_import_notes "refs/notes/many_notes" $num_commits input
430# Add a couple of non-notes as well
431test_tick
432cat >>input <<INPUT_END
433commit refs/notes/many_notes
434committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
435data <<COMMIT
436committing some non-notes to the notes tree
437COMMIT
438
439M 755 inline foobar/non-note.txt
440data <<EOF
441This is not a note, but rather a regular file residing in a notes tree
442EOF
443
444M 644 inline deadbeef
445data <<EOF
446Non-note file
447EOF
448
449M 644 inline de/adbeef
450data <<EOF
451Another non-note file
452EOF
453
454INPUT_END
455# Finally create the expected output from all these notes and commits
456i=$num_commits
457while test $i -gt 0
458do
459	cat >>expect <<EXPECT_END
460    commit #$i
461    note for commit #$i
462EXPECT_END
463	i=$(($i - 1))
464done
465
466test_expect_success 'add lots of commits and notes' '
467
468	git fast-import <input &&
469	GIT_NOTES_REF=refs/notes/many_notes git log refs/heads/many_commits |
470	    grep "^    " > actual &&
471	test_cmp expect actual
472
473'
474
475test_expect_success 'verify that lots of notes trigger a fanout scheme' '
476	hexsz=$(test_oid hexsz) &&
477
478	# None of the entries in the top-level notes tree should be a full SHA1
479	git ls-tree --name-only refs/notes/many_notes |
480	while read path
481	do
482		if test $(expr length "$path") -ge $hexsz
483		then
484			return 1
485		fi
486	done
487
488'
489
490# Create another notes tree from the one above
491SP=" "
492cat >>input <<INPUT_END
493commit refs/heads/other_commits
494committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
495data <<COMMIT
496commit #$(($num_commit + 1))
497COMMIT
498
499from refs/heads/many_commits
500M 644 inline file
501data <<EOF
502file contents in commit #$(($num_commit + 1))
503EOF
504
505commit refs/notes/other_notes
506committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
507data <<COMMIT
508committing one more note on a tree imported from a previous notes tree
509COMMIT
510
511M 040000 $(git log --no-walk --format=%T refs/notes/many_notes)$SP
512N inline :$(($num_commit + 1))
513data <<EOF
514note for commit #$(($num_commit + 1))
515EOF
516INPUT_END
517
518test_expect_success 'verify that importing a notes tree respects the fanout scheme' '
519	git fast-import <input &&
520
521	# None of the entries in the top-level notes tree should be a full SHA1
522	git ls-tree --name-only refs/notes/other_notes |
523	while read path
524	do
525		if test $(expr length "$path") -ge $hexsz
526		then
527			return 1
528		fi
529	done
530'
531
532cat >>expect_non-note1 << EOF
533This is not a note, but rather a regular file residing in a notes tree
534EOF
535
536cat >>expect_non-note2 << EOF
537Non-note file
538EOF
539
540cat >>expect_non-note3 << EOF
541Another non-note file
542EOF
543
544test_expect_success 'verify that non-notes are untouched by a fanout change' '
545
546	git cat-file -p refs/notes/many_notes:foobar/non-note.txt > actual &&
547	test_cmp expect_non-note1 actual &&
548	git cat-file -p refs/notes/many_notes:deadbeef > actual &&
549	test_cmp expect_non-note2 actual &&
550	git cat-file -p refs/notes/many_notes:de/adbeef > actual &&
551	test_cmp expect_non-note3 actual
552
553'
554
555# Change the notes for the three top commits
556test_tick
557cat >input <<INPUT_END
558commit refs/notes/many_notes
559committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
560data <<COMMIT
561changing notes for the top three commits
562COMMIT
563from refs/notes/many_notes^0
564INPUT_END
565
566rm expect
567i=$num_commits
568j=0
569while test $j -lt 3
570do
571	cat >>input <<INPUT_END
572N inline refs/heads/many_commits~$j
573data <<EOF
574changed note for commit #$i
575EOF
576INPUT_END
577	cat >>expect <<EXPECT_END
578    commit #$i
579    changed note for commit #$i
580EXPECT_END
581	i=$(($i - 1))
582	j=$(($j + 1))
583done
584
585test_expect_success 'change a few existing notes' '
586
587	git fast-import <input &&
588	GIT_NOTES_REF=refs/notes/many_notes git log -n3 refs/heads/many_commits |
589	    grep "^    " > actual &&
590	test_cmp expect actual
591
592'
593
594test_expect_success 'verify that changing notes respect existing fanout' '
595
596	# None of the entries in the top-level notes tree should be a full SHA1
597	git ls-tree --name-only refs/notes/many_notes |
598	while read path
599	do
600		if test $(expr length "$path") -ge $hexsz
601		then
602			return 1
603		fi
604	done
605
606'
607
608remaining_notes=10
609test_tick
610cat >input <<INPUT_END
611commit refs/notes/many_notes
612committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
613data <<COMMIT
614removing all notes but $remaining_notes
615COMMIT
616from refs/notes/many_notes^0
617INPUT_END
618
619i=$(($num_commits - $remaining_notes))
620for sha1 in $(git rev-list -n $i refs/heads/many_commits)
621do
622	cat >>input <<INPUT_END
623N $ZERO_OID $sha1
624INPUT_END
625done
626
627i=$num_commits
628rm expect
629while test $i -gt 0
630do
631	cat >>expect <<EXPECT_END
632    commit #$i
633EXPECT_END
634	if test $i -le $remaining_notes
635	then
636		cat >>expect <<EXPECT_END
637    note for commit #$i
638EXPECT_END
639	fi
640	i=$(($i - 1))
641done
642
643test_expect_success 'remove lots of notes' '
644
645	git fast-import <input &&
646	GIT_NOTES_REF=refs/notes/many_notes git log refs/heads/many_commits |
647	    grep "^    " > actual &&
648	test_cmp expect actual
649
650'
651
652test_expect_success 'verify that removing notes trigger fanout consolidation' '
653	# All entries in the top-level notes tree should be a full SHA1
654	git ls-tree --name-only -r refs/notes/many_notes |
655	while read path
656	do
657		# Explicitly ignore the non-note paths
658		test "$path" = "foobar/non-note.txt" && continue
659		test "$path" = "deadbeef" && continue
660		test "$path" = "de/adbeef" && continue
661
662		if test $(expr length "$path") -ne $hexsz
663		then
664			return 1
665		fi
666	done
667
668'
669
670test_expect_success 'verify that non-notes are untouched by a fanout change' '
671
672	git cat-file -p refs/notes/many_notes:foobar/non-note.txt > actual &&
673	test_cmp expect_non-note1 actual &&
674	git cat-file -p refs/notes/many_notes:deadbeef > actual &&
675	test_cmp expect_non-note2 actual &&
676	git cat-file -p refs/notes/many_notes:de/adbeef > actual &&
677	test_cmp expect_non-note3 actual
678
679'
680
681
682rm input expect
683num_notes_refs=10
684num_commits=16
685some_commits=8
686# Create commits
687fast_import_commits "refs/heads/more_commits" $num_commits input
688# Create one note per above commit per notes ref
689i=0
690while test $i -lt $num_notes_refs
691do
692	i=$(($i + 1))
693	fast_import_notes "refs/notes/more_notes_$i" $num_commits input
694done
695# Trigger branch reloading in git-fast-import by repeating the note creation
696i=0
697while test $i -lt $num_notes_refs
698do
699	i=$(($i + 1))
700	fast_import_notes "refs/notes/more_notes_$i" $some_commits input " (2)"
701done
702# Finally create the expected output from the notes in refs/notes/more_notes_1
703i=$num_commits
704while test $i -gt 0
705do
706	note_data="note for commit #$i"
707	if test $i -le $some_commits
708	then
709		note_data="$note_data (2)"
710	fi
711	cat >>expect <<EXPECT_END
712    commit #$i
713    $note_data
714EXPECT_END
715	i=$(($i - 1))
716done
717
718test_expect_success "add notes to $num_commits commits in each of $num_notes_refs refs" '
719
720	git fast-import --active-branches=5 <input &&
721	GIT_NOTES_REF=refs/notes/more_notes_1 git log refs/heads/more_commits |
722	    grep "^    " > actual &&
723	test_cmp expect actual
724
725'
726
727test_done
728