Raw
1 #!/bin/sh
2
3 test_description='test log -L'
4 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
5 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
6
7 . ./test-lib.sh
8
9 test_expect_success 'setup (import history)' '
10 git fast-import < "$TEST_DIRECTORY"/t4211/history.export &&
11 git reset --hard
12 '
13
14 test_expect_success 'basic command line parsing' '
15 # This may fail due to "no such path a.c in commit", or
16 # "-L is incompatible with pathspec", depending on the
17 # order the error is checked. Either is acceptable.
18 test_must_fail git log -L1,1:a.c -- a.c &&
19
20 # -L requires there is no pathspec
21 test_must_fail git log -L1,1:b.c -- b.c 2>error &&
22 test_grep "cannot be used with pathspec" error &&
23
24 # This would fail because --follow wants a single path, but
25 # we may fail due to incompatibility between -L/--follow in
26 # the future. Either is acceptable.
27 test_must_fail git log -L1,1:b.c --follow &&
28 test_must_fail git log --follow -L1,1:b.c &&
29
30 # This would fail because -L wants no pathspec, but
31 # we may fail due to incompatibility between -L/--follow in
32 # the future. Either is acceptable.
33 test_must_fail git log --follow -L1,1:b.c -- b.c
34 '
35
36 canned_test_1 () {
37 test_expect_$1 "$2" "
38 git log $2 >actual &&
39 test_cmp \"\$TEST_DIRECTORY\"/t4211/$(test_oid algo)/expect.$3 actual
40 "
41 }
42
43 canned_test () {
44 canned_test_1 success "$@"
45 }
46 canned_test_failure () {
47 canned_test_1 failure "$@"
48 }
49
50 test_bad_opts () {
51 test_expect_success "invalid args: $1" "
52 test_must_fail git log $1 2>errors &&
53 test_grep '$2' errors
54 "
55 }
56
57 canned_test "-L 4,12:a.c simple" simple-f
58 canned_test "-L 4,+9:a.c simple" simple-f
59 canned_test "-L '/long f/,/^}/:a.c' simple" simple-f
60 canned_test "-L :f:a.c simple" simple-f-to-main
61
62 canned_test "-L '/main/,/^}/:a.c' simple" simple-main
63 canned_test "-L :main:a.c simple" simple-main-to-end
64
65 canned_test "-L 1,+4:a.c simple" beginning-of-file
66
67 canned_test "-L 20:a.c simple" end-of-file
68
69 canned_test "-L '/long f/',/^}/:a.c -L /main/,/^}/:a.c simple" two-ranges
70 canned_test "-L 24,+1:a.c simple" vanishes-early
71
72 canned_test "-M -L '/long f/,/^}/:b.c' move-support" move-support-f
73 canned_test "-M -L ':f:b.c' parallel-change" parallel-change-f-to-main
74
75 canned_test "-L 4,12:a.c -L :main:a.c simple" multiple
76 canned_test "-L 4,18:a.c -L ^:main:a.c simple" multiple-overlapping
77 canned_test "-L :main:a.c -L 4,18:a.c simple" multiple-overlapping
78 canned_test "-L 4:a.c -L 8,12:a.c simple" multiple-superset
79 canned_test "-L 8,12:a.c -L 4:a.c simple" multiple-superset
80
81 canned_test "-L 10,16:b.c -L 18,26:b.c main" no-assertion-error
82
83 test_bad_opts "-L" "switch.*requires a value"
84 test_bad_opts "-L b.c" "argument not .start,end:file"
85 test_bad_opts "-L 1:" "argument not .start,end:file"
86 test_bad_opts "-L 1:nonexistent" "There is no path"
87 test_bad_opts "-L 1:simple" "There is no path"
88 test_bad_opts "-L '/foo:b.c'" "argument not .start,end:file"
89 test_bad_opts "-L 1000:b.c" "has only.*lines"
90 test_bad_opts "-L :b.c" "argument not .start,end:file"
91 test_bad_opts "-L :foo:b.c" "no match"
92
93 test_expect_success '-L X (X == nlines)' '
94 n=$(wc -l <b.c) &&
95 git log -L $n:b.c
96 '
97
98 test_expect_success '-L X (X == nlines + 1)' '
99 n=$(expr $(wc -l <b.c) + 1) &&
100 test_must_fail git log -L $n:b.c
101 '
102
103 test_expect_success '-L X (X == nlines + 2)' '
104 n=$(expr $(wc -l <b.c) + 2) &&
105 test_must_fail git log -L $n:b.c
106 '
107
108 test_expect_success '-L ,Y (Y == nlines)' '
109 n=$(printf "%d" $(wc -l <b.c)) &&
110 git log -L ,$n:b.c
111 '
112
113 test_expect_success '-L ,Y (Y == nlines + 1)' '
114 n=$(expr $(wc -l <b.c) + 1) &&
115 git log -L ,$n:b.c
116 '
117
118 test_expect_success '-L ,Y (Y == nlines + 2)' '
119 n=$(expr $(wc -l <b.c) + 2) &&
120 git log -L ,$n:b.c
121 '
122
123 test_expect_success '-L with --first-parent and a merge' '
124 git checkout parallel-change &&
125 git log --first-parent -L 1,1:b.c
126 '
127
128 test_expect_success '-L with --output' '
129 git checkout parallel-change &&
130 git log --output=log -L :main:b.c >output &&
131 test_must_be_empty output &&
132 test_line_count = 75 log
133 '
134
135 test_expect_success 'range_set_union' '
136 test_seq 500 > c.c &&
137 git add c.c &&
138 git commit -m "many lines" &&
139 test_seq 1000 > c.c &&
140 git add c.c &&
141 git commit -m "modify many lines" &&
142 git log $(for x in $(test_seq 200); do echo -L $((2*x)),+1:c.c || return 1; done)
143 '
144
145 test_expect_success '-s shows only line-log commits' '
146 git log --format="commit %s" -L1,24:b.c >expect.raw &&
147 grep ^commit expect.raw >expect &&
148 git log --format="commit %s" -L1,24:b.c -s >actual &&
149 test_cmp expect actual
150 '
151
152 test_expect_success '-p shows the default patch output' '
153 git log -L1,24:b.c >expect &&
154 git log -L1,24:b.c -p >actual &&
155 test_cmp expect actual
156 '
157
158 test_expect_success '--raw shows mode, oid, status and path' '
159 git log -L1,24:b.c --raw --format= >actual &&
160 test_grep "^:100644 100644 [0-9a-f]\{7\} [0-9a-f]\{7\} M b.c$" actual &&
161 test_grep ! "^diff --git" actual &&
162 test_grep ! "^@@" actual
163 '
164
165 test_expect_success '--name-only shows path' '
166 git log -L1,24:b.c --name-only --format= >actual &&
167 test_grep "^b.c$" actual &&
168 test_grep ! "^diff --git" actual &&
169 test_grep ! "^@@" actual
170 '
171
172 test_expect_success '--name-status shows status and path' '
173 git log -L1,24:b.c --name-status --format= >actual &&
174 test_grep "^M b.c$" actual &&
175 test_grep ! "^diff --git" actual &&
176 test_grep ! "^@@" actual
177 '
178
179 test_expect_success '--stat is not yet supported with -L' '
180 test_must_fail git log -L1,24:b.c --stat 2>err &&
181 test_grep "does not yet support" err
182 '
183
184 test_expect_success '--numstat is not yet supported with -L' '
185 test_must_fail git log -L1,24:b.c --numstat 2>err &&
186 test_grep "does not yet support" err
187 '
188
189 test_expect_success '--shortstat is not yet supported with -L' '
190 test_must_fail git log -L1,24:b.c --shortstat 2>err &&
191 test_grep "does not yet support" err
192 '
193
194 test_expect_success '--dirstat is not yet supported with -L' '
195 test_must_fail git log -L1,24:b.c --dirstat 2>err &&
196 test_grep "does not yet support" err
197 '
198
199 test_expect_success 'setup for checking fancy rename following' '
200 git checkout --orphan moves-start &&
201 git reset --hard &&
202
203 printf "%s\n" 12 13 14 15 b c d e >file-1 &&
204 printf "%s\n" 22 23 24 25 B C D E >file-2 &&
205 git add file-1 file-2 &&
206 test_tick &&
207 git commit -m "Add file-1 and file-2" &&
208 oid_add_f1_f2=$(git rev-parse --short HEAD) &&
209
210 git checkout -b moves-main &&
211 printf "%s\n" 11 12 13 14 15 b c d e >file-1 &&
212 git commit -a -m "Modify file-1 on main" &&
213 oid_mod_f1_main=$(git rev-parse --short HEAD) &&
214
215 printf "%s\n" 21 22 23 24 25 B C D E >file-2 &&
216 git commit -a -m "Modify file-2 on main #1" &&
217 oid_mod_f2_main_1=$(git rev-parse --short HEAD) &&
218
219 git mv file-1 renamed-1 &&
220 git commit -m "Rename file-1 to renamed-1 on main" &&
221
222 printf "%s\n" 11 12 13 14 15 b c d e f >renamed-1 &&
223 git commit -a -m "Modify renamed-1 on main" &&
224 oid_mod_r1_main=$(git rev-parse --short HEAD) &&
225
226 printf "%s\n" 21 22 23 24 25 B C D E F >file-2 &&
227 git commit -a -m "Modify file-2 on main #2" &&
228 oid_mod_f2_main_2=$(git rev-parse --short HEAD) &&
229
230 git checkout -b moves-side moves-start &&
231 printf "%s\n" 12 13 14 15 16 b c d e >file-1 &&
232 git commit -a -m "Modify file-1 on side #1" &&
233 oid_mod_f1_side_1=$(git rev-parse --short HEAD) &&
234
235 printf "%s\n" 22 23 24 25 26 B C D E >file-2 &&
236 git commit -a -m "Modify file-2 on side" &&
237 oid_mod_f2_side=$(git rev-parse --short HEAD) &&
238
239 git mv file-2 renamed-2 &&
240 git commit -m "Rename file-2 to renamed-2 on side" &&
241
242 printf "%s\n" 12 13 14 15 16 a b c d e >file-1 &&
243 git commit -a -m "Modify file-1 on side #2" &&
244 oid_mod_f1_side_2=$(git rev-parse --short HEAD) &&
245
246 printf "%s\n" 22 23 24 25 26 A B C D E >renamed-2 &&
247 git commit -a -m "Modify renamed-2 on side" &&
248 oid_mod_r2_side=$(git rev-parse --short HEAD) &&
249
250 git checkout moves-main &&
251 git merge moves-side &&
252 oid_merge=$(git rev-parse --short HEAD)
253 '
254
255 test_expect_success 'fancy rename following #1' '
256 cat >expect <<-EOF &&
257 $oid_merge Merge branch '\''moves-side'\'' into moves-main
258 $oid_mod_f1_side_2 Modify file-1 on side #2
259 $oid_mod_f1_side_1 Modify file-1 on side #1
260 $oid_mod_r1_main Modify renamed-1 on main
261 $oid_mod_f1_main Modify file-1 on main
262 $oid_add_f1_f2 Add file-1 and file-2
263 EOF
264 git log -L1:renamed-1 --oneline --no-patch >actual &&
265 test_cmp expect actual
266 '
267
268 test_expect_success 'fancy rename following #2' '
269 cat >expect <<-EOF &&
270 $oid_merge Merge branch '\''moves-side'\'' into moves-main
271 $oid_mod_r2_side Modify renamed-2 on side
272 $oid_mod_f2_side Modify file-2 on side
273 $oid_mod_f2_main_2 Modify file-2 on main #2
274 $oid_mod_f2_main_1 Modify file-2 on main #1
275 $oid_add_f1_f2 Add file-1 and file-2
276 EOF
277 git log -L1:renamed-2 --oneline --no-patch >actual &&
278 test_cmp expect actual
279 '
280
281 # Create the following linear history, where each commit does what its
282 # subject line promises:
283 #
284 # * 66c6410 Modify func2() in file.c
285 # * 50834e5 Modify other-file
286 # * fe5851c Modify func1() in file.c
287 # * 8c7c7dd Add other-file
288 # * d5f4417 Add func1() and func2() in file.c
289 test_expect_success 'setup for checking line-log and parent oids' '
290 git checkout --orphan parent-oids &&
291 git reset --hard &&
292
293 cat >file.c <<-\EOF &&
294 int func1()
295 {
296 return F1;
297 }
298
299 int func2()
300 {
301 return F2;
302 }
303 EOF
304 git add file.c &&
305 test_tick &&
306 first_tick=$test_tick &&
307 git commit -m "Add func1() and func2() in file.c" &&
308
309 echo 1 >other-file &&
310 git add other-file &&
311 test_tick &&
312 git commit -m "Add other-file" &&
313
314 sed -e "s/F1/F1 + 1/" file.c >tmp &&
315 mv tmp file.c &&
316 git commit -a -m "Modify func1() in file.c" &&
317
318 echo 2 >other-file &&
319 git commit -a -m "Modify other-file" &&
320
321 sed -e "s/F2/F2 + 2/" file.c >tmp &&
322 mv tmp file.c &&
323 git commit -a -m "Modify func2() in file.c" &&
324
325 head_oid=$(git rev-parse --short HEAD) &&
326 prev_oid=$(git rev-parse --short HEAD^) &&
327 root_oid=$(git rev-parse --short HEAD~4)
328 '
329
330 # Parent oid should be from immediate parent.
331 test_expect_success 'parent oids without parent rewriting' '
332 cat >expect <<-EOF &&
333 $head_oid $prev_oid Modify func2() in file.c
334 $root_oid Add func1() and func2() in file.c
335 EOF
336 git log --format="%h %p %s" --no-patch -L:func2:file.c >actual &&
337 test_cmp expect actual
338 '
339
340 # Parent oid should be from the most recent ancestor touching func2(),
341 # i.e. in this case from the root commit.
342 test_expect_success 'parent oids with parent rewriting' '
343 cat >expect <<-EOF &&
344 $head_oid $root_oid Modify func2() in file.c
345 $root_oid Add func1() and func2() in file.c
346 EOF
347 git log --format="%h %p %s" --no-patch -L:func2:file.c --parents >actual &&
348 test_cmp expect actual
349 '
350
351 test_expect_success 'line-log with --before' '
352 echo $root_oid >expect &&
353 git log --format=%h --no-patch -L:func2:file.c --before=$first_tick >actual &&
354 test_cmp expect actual
355 '
356
357 test_expect_success 'setup tests for zero-width regular expressions' '
358 cat >expect <<-EOF
359 Modify func1() in file.c
360 Add func1() and func2() in file.c
361 EOF
362 '
363
364 test_expect_success 'zero-width regex $ matches any function name' '
365 git log --format="%s" --no-patch "-L:$:file.c" >actual &&
366 test_cmp expect actual
367 '
368
369 test_expect_success 'zero-width regex ^ matches any function name' '
370 git log --format="%s" --no-patch "-L:^:file.c" >actual &&
371 test_cmp expect actual
372 '
373
374 test_expect_success 'zero-width regex .* matches any function name' '
375 git log --format="%s" --no-patch "-L:.*:file.c" >actual &&
376 test_cmp expect actual
377 '
378
379 test_expect_success 'setup for diff pipeline tests' '
380 git checkout parent-oids &&
381
382 head_blob_old=$(git rev-parse --short HEAD^:file.c) &&
383 head_blob_new=$(git rev-parse --short HEAD:file.c) &&
384 root_blob=$(git rev-parse --short HEAD~4:file.c) &&
385 null_blob=$(test_oid zero | cut -c1-7) &&
386 head_blob_old_full=$(git rev-parse HEAD^:file.c) &&
387 head_blob_new_full=$(git rev-parse HEAD:file.c) &&
388 root_blob_full=$(git rev-parse HEAD~4:file.c) &&
389 null_blob_full=$(test_oid zero)
390 '
391
392 test_expect_success '-L diff output includes index and new file mode' '
393 git log -L:func2:file.c --format= >actual &&
394
395 # Output should contain index headers (not present in old code path)
396 test_grep "^index $head_blob_old\.\.$head_blob_new 100644" actual &&
397
398 # Root commit should show new file mode and null index
399 test_grep "^new file mode 100644" actual &&
400 test_grep "^index $null_blob\.\.$root_blob$" actual &&
401
402 # Hunk headers should include funcname context
403 test_grep "^@@ .* @@ int func1()" actual
404 '
405
406 test_expect_success '-L with --word-diff' '
407 cat >expect <<-\EOF &&
408 diff --git a/file.c b/file.c
409 --- a/file.c
410 +++ b/file.c
411 @@ -6,4 +6,4 @@ int func1()
412 int func2()
413 {
414 return [-F2;-]{+F2 + 2;+}
415 }
416 diff --git a/file.c b/file.c
417 new file mode 100644
418 --- /dev/null
419 +++ b/file.c
420 @@ -0,0 +6,4 @@
421 {+int func2()+}
422 {+{+}
423 {+ return F2;+}
424 {+}+}
425 EOF
426 git log -L:func2:file.c --word-diff --format= >actual &&
427 grep -v "^index " actual >actual.filtered &&
428 grep -v "^index " expect >expect.filtered &&
429 test_cmp expect.filtered actual.filtered
430 '
431
432 test_expect_success '-L with --no-prefix' '
433 git log -L:func2:file.c --no-prefix --format= >actual &&
434 test_grep "^diff --git file.c file.c" actual &&
435 test_grep "^--- file.c" actual &&
436 test_grep ! "^--- a/" actual
437 '
438
439 test_expect_success '-L with --full-index' '
440 git log -L:func2:file.c --full-index --format= >actual &&
441 test_grep "^index $head_blob_old_full\.\.$head_blob_new_full 100644" actual &&
442 test_grep "^index $null_blob_full\.\.$root_blob_full$" actual
443 '
444
445 test_expect_success 'setup -L with whitespace change' '
446 git checkout -b ws-change parent-oids &&
447 sed "s/ return F2 + 2;/ return F2 + 2;/" file.c >tmp &&
448 mv tmp file.c &&
449 git commit -a -m "Whitespace change in func2()"
450 '
451
452 test_expect_success '-L with --ignore-all-space suppresses whitespace-only diff' '
453 git log -L:func2:file.c --format= >without_w &&
454 git log -L:func2:file.c --format= -w >with_w &&
455
456 # Without -w: three commits produce diffs (whitespace, modify, root)
457 test $(grep -c "^diff --git" without_w) = 3 &&
458
459 # With -w: whitespace-only commit produces no hunk, so only two diffs
460 test $(grep -c "^diff --git" with_w) = 2
461 '
462
463 test_expect_success 'show line-log with graph' '
464 git checkout parent-oids &&
465 head_blob_old=$(git rev-parse --short HEAD^:file.c) &&
466 head_blob_new=$(git rev-parse --short HEAD:file.c) &&
467 root_blob=$(git rev-parse --short HEAD~4:file.c) &&
468 null_blob=$(test_oid zero | cut -c1-7) &&
469 qz_to_tab_space >expect <<-EOF &&
470 * $head_oid Modify func2() in file.c
471 | diff --git a/file.c b/file.c
472 | index $head_blob_old..$head_blob_new 100644
473 | --- a/file.c
474 | +++ b/file.c
475 | @@ -6,4 +6,4 @@ int func1()
476 | int func2()
477 | {
478 | - return F2;
479 | + return F2 + 2;
480 | }
481 * $root_oid Add func1() and func2() in file.c
482 diff --git a/file.c b/file.c
483 new file mode 100644
484 index $null_blob..$root_blob
485 --- /dev/null
486 +++ b/file.c
487 @@ -0,0 +6,4 @@
488 +int func2()
489 +{
490 + return F2;
491 +}
492 EOF
493 git log --graph --oneline -L:func2:file.c >actual &&
494 test_cmp expect actual
495 '
496
497 test_expect_success 'setup for -L with -G/-S/--find-object and a merge with rename' '
498 git checkout --orphan pickaxe-rename &&
499 git reset --hard &&
500
501 echo content >file &&
502 git add file &&
503 git commit -m "add file" &&
504
505 git checkout -b pickaxe-rename-side &&
506 git mv file renamed-file &&
507 git commit -m "rename file" &&
508
509 git checkout pickaxe-rename &&
510 git commit --allow-empty -m "diverge" &&
511 git merge --no-edit pickaxe-rename-side &&
512
513 git mv renamed-file file &&
514 git commit -m "rename back"
515 '
516
517 test_expect_success '-L -G does not crash with merge and rename' '
518 git log --format="%s" --no-patch -L 1,1:file -G "." >actual
519 '
520
521 test_expect_success '-L -S does not crash with merge and rename' '
522 git log --format="%s" --no-patch -L 1,1:file -S content >actual
523 '
524
525 test_expect_success '-L --find-object does not crash with merge and rename' '
526 git log --format="%s" --no-patch -L 1,1:file \
527 --find-object=$(git rev-parse HEAD:file) >actual
528 '
529
530 test_expect_success '-L -G should filter commits by pattern' '
531 git log --format="%s" --no-patch -L 1,1:file -G "nomatch" >actual &&
532 test_must_be_empty actual
533 '
534
535 test_expect_success '-L -S should filter commits by pattern' '
536 git log --format="%s" --no-patch -L 1,1:file -S "nomatch" >actual &&
537 test_must_be_empty actual
538 '
539
540 test_expect_success '-L --find-object should filter commits by object' '
541 git log --format="%s" --no-patch -L 1,1:file \
542 --find-object=$ZERO_OID >actual &&
543 test_must_be_empty actual
544 '
545
546 test_expect_success '-L with --word-diff-regex' '
547 git checkout parent-oids &&
548 git log -L:func2:file.c --word-diff \
549 --word-diff-regex="[a-zA-Z0-9_]+" --format= >actual &&
550 # Word-diff markers must be present
551 test_grep "{+" actual &&
552 test_grep "+}" actual &&
553 # No line-level +/- markers (word-diff replaces them);
554 # exclude --- header lines from the check
555 test_grep ! "^+[^+]" actual &&
556 test_grep ! "^-[^-]" actual
557 '
558
559 test_expect_success '-L with --src-prefix and --dst-prefix' '
560 git checkout parent-oids &&
561 git log -L:func2:file.c --src-prefix=old/ --dst-prefix=new/ \
562 --format= >actual &&
563 test_grep "^diff --git old/file.c new/file.c" actual &&
564 test_grep "^--- old/file.c" actual &&
565 test_grep "^+++ new/file.c" actual &&
566 test_grep ! "^--- a/" actual
567 '
568
569 test_expect_success '-L with --abbrev' '
570 git checkout parent-oids &&
571 git log -L:func2:file.c --abbrev=4 --format= -1 >actual &&
572 # 4-char abbreviated hashes on index line
573 test_grep "^index [0-9a-f]\{4\}\.\.[0-9a-f]\{4\}" actual
574 '
575
576 test_expect_success '-L with -b suppresses whitespace-only diff' '
577 git checkout ws-change &&
578 git log -L:func2:file.c --format= >without_b &&
579 git log -L:func2:file.c --format= -b >with_b &&
580 test $(grep -c "^diff --git" without_b) = 3 &&
581 test $(grep -c "^diff --git" with_b) = 2
582 '
583
584 test_expect_success '-L with --output-indicator-*' '
585 git checkout parent-oids &&
586 git log -L:func2:file.c --output-indicator-new=">" \
587 --output-indicator-old="<" --output-indicator-context="|" \
588 --format= -1 >actual &&
589 test_grep "^>" actual &&
590 test_grep "^<" actual &&
591 test_grep "^|" actual &&
592 # No standard +/-/space content markers; exclude ---/+++ headers
593 test_grep ! "^+[^+]" actual &&
594 test_grep ! "^-[^-]" actual &&
595 test_grep ! "^ " actual
596 '
597
598 test_expect_success '-L with -R reverses diff' '
599 git checkout parent-oids &&
600 git log -L:func2:file.c -R --format= -1 >actual &&
601 test_grep "^diff --git b/file.c a/file.c" actual &&
602 test_grep "^--- b/file.c" actual &&
603 test_grep "^+++ a/file.c" actual &&
604 # The modification added "F2 + 2", so reversed it is removed
605 test_grep "^-.*F2 + 2" actual &&
606 test_grep "^+.*return F2;" actual
607 '
608
609 test_expect_success 'setup for color-moved test' '
610 git checkout -b color-moved-test parent-oids &&
611 cat >big.c <<-\EOF &&
612 int bigfunc()
613 {
614 int a = 1;
615 int b = 2;
616 int c = 3;
617 return a + b + c;
618 }
619 EOF
620 git add big.c &&
621 git commit -m "add bigfunc" &&
622 sed "s/ / /" big.c >tmp && mv tmp big.c &&
623 git commit -a -m "reindent bigfunc"
624 '
625
626 test_expect_success '-L with --color-moved' '
627 git log -L:bigfunc:big.c --color-moved=zebra \
628 --color-moved-ws=ignore-all-space \
629 --color=always --format= -1 >actual.raw &&
630 test_decode_color <actual.raw >actual &&
631 # Old moved lines: bold magenta; new moved lines: bold cyan
632 test_grep "BOLD;MAGENTA" actual &&
633 test_grep "BOLD;CYAN" actual
634 '
635
636 test_expect_success 'setup for no-newline-at-eof tests' '
637 git checkout --orphan no-newline &&
638 git reset --hard &&
639 printf "int top()\n{\n return 1;\n}\n\nint bot()\n{\n return 2;\n}" >noeol.c &&
640 git add noeol.c &&
641 test_tick &&
642 git commit -m "add noeol.c (no trailing newline)" &&
643 sed "s/return 2/return 22/" noeol.c >tmp && mv tmp noeol.c &&
644 git commit -a -m "modify bot()" &&
645 printf "int top()\n{\n return 1;\n}\n\nint bot()\n{\n return 33;\n}\n" >noeol.c &&
646 git commit -a -m "modify bot() and add trailing newline"
647 '
648
649 # When the tracked function is at the end of a file with no trailing
650 # newline, the "\ No newline at end of file" marker should appear.
651 test_expect_success '-L no-newline-at-eof appears in tracked range' '
652 git log -L:bot:noeol.c --format= -1 HEAD~1 >actual &&
653 test_grep "No newline at end of file" actual
654 '
655
656 # When tracking a function that ends before the no-newline content,
657 # the marker should not appear in the output.
658 test_expect_success '-L no-newline-at-eof suppressed outside range' '
659 git log -L:top:noeol.c --format= >actual &&
660 test_grep ! "No newline at end of file" actual
661 '
662
663 # When a commit removes a no-newline last line and replaces it with
664 # a newline-terminated line, the marker should still appear (on the
665 # old side of the diff).
666 test_expect_success '-L no-newline-at-eof marker with deleted line' '
667 git log -L:bot:noeol.c --format= -1 >actual &&
668 test_grep "No newline at end of file" actual
669 '
670
671 test_expect_success 'setup for range boundary deletion test' '
672 git checkout --orphan range-boundary &&
673 git reset --hard &&
674 cat >boundary.c <<-\EOF &&
675 void above()
676 {
677 return;
678 }
679
680 void tracked()
681 {
682 int x = 1;
683 int y = 2;
684 }
685
686 void below()
687 {
688 return;
689 }
690 EOF
691 git add boundary.c &&
692 test_tick &&
693 git commit -m "add boundary.c" &&
694 cat >boundary.c <<-\EOF &&
695 void above()
696 {
697 return;
698 }
699
700 void tracked()
701 {
702 int x = 1;
703 int y = 2;
704 }
705
706 void below_renamed()
707 {
708 return 0;
709 }
710 EOF
711 git commit -a -m "modify below() only"
712 '
713
714 # When only a function below the tracked range is modified, the
715 # tracked function should not produce a diff.
716 test_expect_success '-L suppresses deletions outside tracked range' '
717 git log -L:tracked:boundary.c --format= >actual &&
718 test $(grep -c "^diff --git" actual) = 1
719 '
720
721 test_expect_success '-L with -S filters to string-count changes' '
722 git checkout parent-oids &&
723 git log -L:func2:file.c -S "F2 + 2" --format= >actual &&
724 # -S searches the whole file, not just the tracked range;
725 # combined with the -L range walk, this selects commits that
726 # both touch func2 and change the count of "F2 + 2" in the file.
727 test $(grep -c "^diff --git" actual) = 1 &&
728 test_grep "F2 + 2" actual
729 '
730
731 test_expect_success '-L with -G filters to diff-text matches' '
732 git checkout parent-oids &&
733 git log -L:func2:file.c -G "F2 [+] 2" --format= >actual &&
734 # -G greps the whole-file diff text, not just the tracked range;
735 # combined with -L, this selects commits that both touch func2
736 # and have "F2 + 2" in their diff.
737 test $(grep -c "^diff --git" actual) = 1 &&
738 test_grep "F2 + 2" actual
739 '
740
741 test_expect_success '-L with --diff-filter=M excludes root commit' '
742 git checkout parent-oids &&
743 git log -L:func2:file.c --diff-filter=M --format=%s --no-patch >actual &&
744 # Root commit is an Add (A), not a Modify (M), so it should
745 # be excluded; only the modification commit remains.
746 echo "Modify func2() in file.c" >expect &&
747 test_cmp expect actual
748 '
749
750 test_expect_success '-L with --diff-filter=A shows only root commit' '
751 git checkout parent-oids &&
752 git log -L:func2:file.c --diff-filter=A --format=%s --no-patch >actual &&
753 echo "Add func1() and func2() in file.c" >expect &&
754 test_cmp expect actual
755 '
756
757 test_expect_success '-L with -S suppresses non-matching commits' '
758 git checkout parent-oids &&
759 git log -L:func2:file.c -S "F2 + 2" --format=%s --no-patch >actual &&
760 # Only the commit that changes the count of "F2 + 2" should appear.
761 echo "Modify func2() in file.c" >expect &&
762 test_cmp expect actual
763 '
764
765 test_expect_success '--full-diff is not yet supported with -L' '
766 test_must_fail git log -L1,24:b.c --full-diff 2>err &&
767 test_grep "does not yet support" err
768 '
769
770 test_expect_success '-L --oneline has no extra blank line before diff' '
771 git checkout parent-oids &&
772 git log --oneline -L:func2:file.c -1 >actual &&
773 # Oneline header on line 1, diff starts immediately on line 2
774 sed -n 2p actual >line2 &&
775 test_grep "^diff --git" line2
776 '
777
778 test_expect_success '--summary shows new file on root commit' '
779 git checkout parent-oids &&
780 git log -L:func2:file.c --summary --format= >actual &&
781 test_grep "create mode 100644 file.c" actual
782 '
783
784 test_done