@@ -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) &&
@@ -424,4 +510,199 @@ test_expect_failure '-L --find-object should filter commits by object' '
424510 test_must_be_empty actual
425511'
426512
513+ test_expect_success ' -L with --word-diff-regex' '
514+ git checkout parent-oids &&
515+ git log -L:func2:file.c --word-diff \
516+ --word-diff-regex="[a-zA-Z0-9_]+" --format= >actual &&
517+ # Word-diff markers must be present
518+ grep "{+" actual &&
519+ grep "+}" actual &&
520+ # No line-level +/- markers (word-diff replaces them);
521+ # exclude --- header lines from the check
522+ ! grep "^+[^+]" actual &&
523+ ! grep "^-[^-]" actual
524+ '
525+
526+ test_expect_success ' -L with --src-prefix and --dst-prefix' '
527+ git checkout parent-oids &&
528+ git log -L:func2:file.c --src-prefix=old/ --dst-prefix=new/ \
529+ --format= >actual &&
530+ grep "^diff --git old/file.c new/file.c" actual &&
531+ grep "^--- old/file.c" actual &&
532+ grep "^+++ new/file.c" actual &&
533+ ! grep "^--- a/" actual
534+ '
535+
536+ test_expect_success ' -L with --abbrev' '
537+ git checkout parent-oids &&
538+ git log -L:func2:file.c --abbrev=4 --format= -1 >actual &&
539+ # 4-char abbreviated hashes on index line
540+ grep "^index [0-9a-f]\{4\}\.\.[0-9a-f]\{4\}" actual
541+ '
542+
543+ test_expect_success ' -L with -b suppresses whitespace-only diff' '
544+ git checkout ws-change &&
545+ git log -L:func2:file.c --format= >without_b &&
546+ git log -L:func2:file.c --format= -b >with_b &&
547+ test $(grep -c "^diff --git" without_b) = 3 &&
548+ test $(grep -c "^diff --git" with_b) = 2
549+ '
550+
551+ test_expect_success ' -L with --output-indicator-*' '
552+ git checkout parent-oids &&
553+ git log -L:func2:file.c --output-indicator-new=">" \
554+ --output-indicator-old="<" --output-indicator-context="|" \
555+ --format= -1 >actual &&
556+ grep "^>" actual &&
557+ grep "^<" actual &&
558+ grep "^|" actual &&
559+ # No standard +/-/space content markers; exclude ---/+++ headers
560+ ! grep "^+[^+]" actual &&
561+ ! grep "^-[^-]" actual &&
562+ ! grep "^ " actual
563+ '
564+
565+ test_expect_success ' -L with -R reverses diff' '
566+ git checkout parent-oids &&
567+ git log -L:func2:file.c -R --format= -1 >actual &&
568+ grep "^diff --git b/file.c a/file.c" actual &&
569+ grep "^--- b/file.c" actual &&
570+ grep "^+++ a/file.c" actual &&
571+ # The modification added "F2 + 2", so reversed it is removed
572+ grep "^-.*F2 + 2" actual &&
573+ grep "^+.*return F2;" actual
574+ '
575+
576+ test_expect_success ' setup for color-moved test' '
577+ git checkout -b color-moved-test parent-oids &&
578+ cat >big.c <<-\EOF &&
579+ int bigfunc()
580+ {
581+ int a = 1;
582+ int b = 2;
583+ int c = 3;
584+ return a + b + c;
585+ }
586+ EOF
587+ git add big.c &&
588+ git commit -m "add bigfunc" &&
589+ sed "s/ / /" big.c >tmp && mv tmp big.c &&
590+ git commit -a -m "reindent bigfunc"
591+ '
592+
593+ test_expect_success ' -L with --color-moved' '
594+ git log -L:bigfunc:big.c --color-moved=zebra \
595+ --color-moved-ws=ignore-all-space \
596+ --color=always --format= -1 >actual.raw &&
597+ test_decode_color <actual.raw >actual &&
598+ # Old moved lines: bold magenta; new moved lines: bold cyan
599+ grep "BOLD;MAGENTA" actual &&
600+ grep "BOLD;CYAN" actual
601+ '
602+
603+ test_expect_success ' setup for no-newline-at-eof tests' '
604+ git checkout --orphan no-newline &&
605+ git reset --hard &&
606+ printf "int top()\n{\n return 1;\n}\n\nint bot()\n{\n return 2;\n}" >noeol.c &&
607+ git add noeol.c &&
608+ test_tick &&
609+ git commit -m "add noeol.c (no trailing newline)" &&
610+ sed "s/return 2/return 22/" noeol.c >tmp && mv tmp noeol.c &&
611+ git commit -a -m "modify bot()" &&
612+ printf "int top()\n{\n return 1;\n}\n\nint bot()\n{\n return 33;\n}\n" >noeol.c &&
613+ git commit -a -m "modify bot() and add trailing newline"
614+ '
615+
616+ # When the tracked function is at the end of a file with no trailing
617+ # newline, the "\ No newline at end of file" marker should appear.
618+ test_expect_success ' -L no-newline-at-eof appears in tracked range' '
619+ git log -L:bot:noeol.c --format= -1 HEAD~1 >actual &&
620+ grep "No newline at end of file" actual
621+ '
622+
623+ # When tracking a function that ends before the no-newline content,
624+ # the marker should not appear in the output.
625+ test_expect_success ' -L no-newline-at-eof suppressed outside range' '
626+ git log -L:top:noeol.c --format= >actual &&
627+ ! grep "No newline at end of file" actual
628+ '
629+
630+ # When a commit removes a no-newline last line and replaces it with
631+ # a newline-terminated line, the marker should still appear (on the
632+ # old side of the diff).
633+ test_expect_success ' -L no-newline-at-eof marker with deleted line' '
634+ git log -L:bot:noeol.c --format= -1 >actual &&
635+ grep "No newline at end of file" actual
636+ '
637+
638+ test_expect_success ' setup for range boundary deletion test' '
639+ git checkout --orphan range-boundary &&
640+ git reset --hard &&
641+ cat >boundary.c <<-\EOF &&
642+ void above()
643+ {
644+ return;
645+ }
646+
647+ void tracked()
648+ {
649+ int x = 1;
650+ int y = 2;
651+ }
652+
653+ void below()
654+ {
655+ return;
656+ }
657+ EOF
658+ git add boundary.c &&
659+ test_tick &&
660+ git commit -m "add boundary.c" &&
661+ cat >boundary.c <<-\EOF &&
662+ void above()
663+ {
664+ return;
665+ }
666+
667+ void tracked()
668+ {
669+ int x = 1;
670+ int y = 2;
671+ }
672+
673+ void below_renamed()
674+ {
675+ return 0;
676+ }
677+ EOF
678+ git commit -a -m "modify below() only"
679+ '
680+
681+ # When only a function below the tracked range is modified, the
682+ # tracked function should not produce a diff.
683+ test_expect_success ' -L suppresses deletions outside tracked range' '
684+ git log -L:tracked:boundary.c --format= >actual &&
685+ test $(grep -c "^diff --git" actual) = 1
686+ '
687+
688+ test_expect_success ' -L with -S filters to string-count changes' '
689+ git checkout parent-oids &&
690+ git log -L:func2:file.c -S "F2 + 2" --format= >actual &&
691+ # -S searches the whole file, not just the tracked range;
692+ # combined with the -L range walk, this selects commits that
693+ # both touch func2 and change the count of "F2 + 2" in the file.
694+ test $(grep -c "^diff --git" actual) = 1 &&
695+ grep "F2 + 2" actual
696+ '
697+
698+ test_expect_success ' -L with -G filters to diff-text matches' '
699+ git checkout parent-oids &&
700+ git log -L:func2:file.c -G "F2 [+] 2" --format= >actual &&
701+ # -G greps the whole-file diff text, not just the tracked range;
702+ # combined with -L, this selects commits that both touch func2
703+ # and have "F2 + 2" in their diff.
704+ test $(grep -c "^diff --git" actual) = 1 &&
705+ grep "F2 + 2" actual
706+ '
707+
427708test_done
0 commit comments