@@ -339,6 +339,92 @@ test_expect_success 'zero-width regex .* matches any function name' '
339339 test_cmp expect actual
340340'
341341
342+ test_expect_success ' setup for diff pipeline tests' '
343+ git checkout parent-oids &&
344+
345+ head_blob_old=$(git rev-parse --short HEAD^:file.c) &&
346+ head_blob_new=$(git rev-parse --short HEAD:file.c) &&
347+ root_blob=$(git rev-parse --short HEAD~4:file.c) &&
348+ null_blob=$(test_oid zero | cut -c1-7) &&
349+ head_blob_old_full=$(git rev-parse HEAD^:file.c) &&
350+ head_blob_new_full=$(git rev-parse HEAD:file.c) &&
351+ root_blob_full=$(git rev-parse HEAD~4:file.c) &&
352+ null_blob_full=$(test_oid zero)
353+ '
354+
355+ test_expect_success ' -L diff output includes index and new file mode' '
356+ git log -L:func2:file.c --format= >actual &&
357+
358+ # Output should contain index headers (not present in old code path)
359+ grep "^index $head_blob_old\.\.$head_blob_new 100644" actual &&
360+
361+ # Root commit should show new file mode and null index
362+ grep "^new file mode 100644" actual &&
363+ grep "^index $null_blob\.\.$root_blob$" actual &&
364+
365+ # Hunk headers should include funcname context
366+ grep "^@@ .* @@ int func1()" actual
367+ '
368+
369+ test_expect_success ' -L with --word-diff' '
370+ cat >expect <<-\EOF &&
371+
372+ diff --git a/file.c b/file.c
373+ --- a/file.c
374+ +++ b/file.c
375+ @@ -6,4 +6,4 @@ int func1()
376+ int func2()
377+ {
378+ return [-F2;-]{+F2 + 2;+}
379+ }
380+
381+ diff --git a/file.c b/file.c
382+ new file mode 100644
383+ --- /dev/null
384+ +++ b/file.c
385+ @@ -0,0 +6,4 @@
386+ {+int func2()+}
387+ {+{+}
388+ {+ return F2;+}
389+ {+}+}
390+ EOF
391+ git log -L:func2:file.c --word-diff --format= >actual &&
392+ grep -v "^index " actual >actual.filtered &&
393+ grep -v "^index " expect >expect.filtered &&
394+ test_cmp expect.filtered actual.filtered
395+ '
396+
397+ test_expect_success ' -L with --no-prefix' '
398+ git log -L:func2:file.c --no-prefix --format= >actual &&
399+ grep "^diff --git file.c file.c" actual &&
400+ grep "^--- file.c" actual &&
401+ ! grep "^--- a/" actual
402+ '
403+
404+ test_expect_success ' -L with --full-index' '
405+ git log -L:func2:file.c --full-index --format= >actual &&
406+ grep "^index $head_blob_old_full\.\.$head_blob_new_full 100644" actual &&
407+ grep "^index $null_blob_full\.\.$root_blob_full$" actual
408+ '
409+
410+ test_expect_success ' setup -L with whitespace change' '
411+ git checkout -b ws-change parent-oids &&
412+ sed "s/ return F2 + 2;/ return F2 + 2;/" file.c >tmp &&
413+ mv tmp file.c &&
414+ git commit -a -m "Whitespace change in func2()"
415+ '
416+
417+ test_expect_success ' -L with --ignore-all-space suppresses whitespace-only diff' '
418+ git log -L:func2:file.c --format= >without_w &&
419+ git log -L:func2:file.c --format= -w >with_w &&
420+
421+ # Without -w: three commits produce diffs (whitespace, modify, root)
422+ test $(grep -c "^diff --git" without_w) = 3 &&
423+
424+ # With -w: whitespace-only commit produces no hunk, so only two diffs
425+ test $(grep -c "^diff --git" with_w) = 2
426+ '
427+
342428test_expect_success ' show line-log with graph' '
343429 git checkout parent-oids &&
344430 head_blob_old=$(git rev-parse --short HEAD^:file.c) &&
@@ -375,4 +461,199 @@ test_expect_success 'show line-log with graph' '
375461 test_cmp expect actual
376462'
377463
464+ test_expect_success ' -L with --word-diff-regex' '
465+ git checkout parent-oids &&
466+ git log -L:func2:file.c --word-diff \
467+ --word-diff-regex="[a-zA-Z0-9_]+" --format= >actual &&
468+ # Word-diff markers must be present
469+ grep "{+" actual &&
470+ grep "+}" actual &&
471+ # No line-level +/- markers (word-diff replaces them);
472+ # exclude --- header lines from the check
473+ ! grep "^+[^+]" actual &&
474+ ! grep "^-[^-]" actual
475+ '
476+
477+ test_expect_success ' -L with --src-prefix and --dst-prefix' '
478+ git checkout parent-oids &&
479+ git log -L:func2:file.c --src-prefix=old/ --dst-prefix=new/ \
480+ --format= >actual &&
481+ grep "^diff --git old/file.c new/file.c" actual &&
482+ grep "^--- old/file.c" actual &&
483+ grep "^+++ new/file.c" actual &&
484+ ! grep "^--- a/" actual
485+ '
486+
487+ test_expect_success ' -L with --abbrev' '
488+ git checkout parent-oids &&
489+ git log -L:func2:file.c --abbrev=4 --format= -1 >actual &&
490+ # 4-char abbreviated hashes on index line
491+ grep "^index [0-9a-f]\{4\}\.\.[0-9a-f]\{4\}" actual
492+ '
493+
494+ test_expect_success ' -L with -b suppresses whitespace-only diff' '
495+ git checkout ws-change &&
496+ git log -L:func2:file.c --format= >without_b &&
497+ git log -L:func2:file.c --format= -b >with_b &&
498+ test $(grep -c "^diff --git" without_b) = 3 &&
499+ test $(grep -c "^diff --git" with_b) = 2
500+ '
501+
502+ test_expect_success ' -L with --output-indicator-*' '
503+ git checkout parent-oids &&
504+ git log -L:func2:file.c --output-indicator-new=">" \
505+ --output-indicator-old="<" --output-indicator-context="|" \
506+ --format= -1 >actual &&
507+ grep "^>" actual &&
508+ grep "^<" actual &&
509+ grep "^|" actual &&
510+ # No standard +/-/space content markers; exclude ---/+++ headers
511+ ! grep "^+[^+]" actual &&
512+ ! grep "^-[^-]" actual &&
513+ ! grep "^ " actual
514+ '
515+
516+ test_expect_success ' -L with -R reverses diff' '
517+ git checkout parent-oids &&
518+ git log -L:func2:file.c -R --format= -1 >actual &&
519+ grep "^diff --git b/file.c a/file.c" actual &&
520+ grep "^--- b/file.c" actual &&
521+ grep "^+++ a/file.c" actual &&
522+ # The modification added "F2 + 2", so reversed it is removed
523+ grep "^-.*F2 + 2" actual &&
524+ grep "^+.*return F2;" actual
525+ '
526+
527+ test_expect_success ' setup for color-moved test' '
528+ git checkout -b color-moved-test parent-oids &&
529+ cat >big.c <<-\EOF &&
530+ int bigfunc()
531+ {
532+ int a = 1;
533+ int b = 2;
534+ int c = 3;
535+ return a + b + c;
536+ }
537+ EOF
538+ git add big.c &&
539+ git commit -m "add bigfunc" &&
540+ sed "s/ / /" big.c >tmp && mv tmp big.c &&
541+ git commit -a -m "reindent bigfunc"
542+ '
543+
544+ test_expect_success ' -L with --color-moved' '
545+ git log -L:bigfunc:big.c --color-moved=zebra \
546+ --color-moved-ws=ignore-all-space \
547+ --color=always --format= -1 >actual.raw &&
548+ test_decode_color <actual.raw >actual &&
549+ # Old moved lines: bold magenta; new moved lines: bold cyan
550+ grep "BOLD;MAGENTA" actual &&
551+ grep "BOLD;CYAN" actual
552+ '
553+
554+ test_expect_success ' setup for no-newline-at-eof tests' '
555+ git checkout --orphan no-newline &&
556+ git reset --hard &&
557+ printf "int top()\n{\n return 1;\n}\n\nint bot()\n{\n return 2;\n}" >noeol.c &&
558+ git add noeol.c &&
559+ test_tick &&
560+ git commit -m "add noeol.c (no trailing newline)" &&
561+ sed "s/return 2/return 22/" noeol.c >tmp && mv tmp noeol.c &&
562+ git commit -a -m "modify bot()" &&
563+ printf "int top()\n{\n return 1;\n}\n\nint bot()\n{\n return 33;\n}\n" >noeol.c &&
564+ git commit -a -m "modify bot() and add trailing newline"
565+ '
566+
567+ # When the tracked function is at the end of a file with no trailing
568+ # newline, the "\ No newline at end of file" marker should appear.
569+ test_expect_success ' -L no-newline-at-eof appears in tracked range' '
570+ git log -L:bot:noeol.c --format= -1 HEAD~1 >actual &&
571+ grep "No newline at end of file" actual
572+ '
573+
574+ # When tracking a function that ends before the no-newline content,
575+ # the marker should not appear in the output.
576+ test_expect_success ' -L no-newline-at-eof suppressed outside range' '
577+ git log -L:top:noeol.c --format= >actual &&
578+ ! grep "No newline at end of file" actual
579+ '
580+
581+ # When a commit removes a no-newline last line and replaces it with
582+ # a newline-terminated line, the marker should still appear (on the
583+ # old side of the diff).
584+ test_expect_success ' -L no-newline-at-eof marker with deleted line' '
585+ git log -L:bot:noeol.c --format= -1 >actual &&
586+ grep "No newline at end of file" actual
587+ '
588+
589+ test_expect_success ' setup for range boundary deletion test' '
590+ git checkout --orphan range-boundary &&
591+ git reset --hard &&
592+ cat >boundary.c <<-\EOF &&
593+ void above()
594+ {
595+ return;
596+ }
597+
598+ void tracked()
599+ {
600+ int x = 1;
601+ int y = 2;
602+ }
603+
604+ void below()
605+ {
606+ return;
607+ }
608+ EOF
609+ git add boundary.c &&
610+ test_tick &&
611+ git commit -m "add boundary.c" &&
612+ cat >boundary.c <<-\EOF &&
613+ void above()
614+ {
615+ return;
616+ }
617+
618+ void tracked()
619+ {
620+ int x = 1;
621+ int y = 2;
622+ }
623+
624+ void below_renamed()
625+ {
626+ return 0;
627+ }
628+ EOF
629+ git commit -a -m "modify below() only"
630+ '
631+
632+ # When only a function below the tracked range is modified, the
633+ # tracked function should not produce a diff.
634+ test_expect_success ' -L suppresses deletions outside tracked range' '
635+ git log -L:tracked:boundary.c --format= >actual &&
636+ test $(grep -c "^diff --git" actual) = 1
637+ '
638+
639+ test_expect_success ' -L with -S filters to string-count changes' '
640+ git checkout parent-oids &&
641+ git log -L:func2:file.c -S "F2 + 2" --format= >actual &&
642+ # -S searches the whole file, not just the tracked range;
643+ # combined with the -L range walk, this selects commits that
644+ # both touch func2 and change the count of "F2 + 2" in the file.
645+ test $(grep -c "^diff --git" actual) = 1 &&
646+ grep "F2 + 2" actual
647+ '
648+
649+ test_expect_success ' -L with -G filters to diff-text matches' '
650+ git checkout parent-oids &&
651+ git log -L:func2:file.c -G "F2 [+] 2" --format= >actual &&
652+ # -G greps the whole-file diff text, not just the tracked range;
653+ # combined with -L, this selects commits that both touch func2
654+ # and have "F2 + 2" in their diff.
655+ test $(grep -c "^diff --git" actual) = 1 &&
656+ grep "F2 + 2" actual
657+ '
658+
378659test_done
0 commit comments