test_expect_success '-L with -G filters to diff-text matches' '
git checkout parent-oids &&
git log -L:func2:file.c -G "F2 [+] 2" --format= >actual &&
- # -G greps the whole-file diff text, not just the tracked range;
- # combined with -L, this selects commits that both touch func2
- # and have "F2 + 2" in their diff.
+ # -G greps the diff text, and under -L only the lines in the
+ # tracked range (unlike -S above, which searches the whole file);
+ # this selects commits whose change to func2 contains "F2 + 2".
test $(grep -c "^diff --git" actual) = 1 &&
- grep "F2 + 2" actual
+ test_grep "F2 + 2" actual
'
+ test_expect_success 'setup for trailing deletion test' '
+ git checkout --orphan trailing-del &&
+ git reset --hard &&
+ cat >file.c <<-\EOF &&
+ void tracked()
+ {
+ return 1;
+ }
+ // trailing comment
+ EOF
+ git add file.c &&
+ test_tick &&
+ git commit -m "add file with trailing comment" &&
+ # Modify tracked() AND delete the trailing comment in
+ # one commit, so the commit touches the tracked range
+ # and is not filtered out by the revision walker.
+ cat >file.c <<-\EOF &&
+ void tracked()
+ {
+ return 2;
+ }
+ EOF
+ git commit -a -m "modify tracked and delete trailing comment"
+ '
+
+ test_expect_success '-L does not include deletions past end of tracked range' '
+ git log -L:tracked:file.c --format= -1 -p >actual &&
+ # The trailing comment deletion is outside the tracked
+ # range and should not appear in the patch output.
+ test_grep "return 2" actual &&
+ test_grep ! "trailing comment" actual
+ '
+
+ test_expect_success '-L includes leading deletions resolved by in-range line' '
+ git checkout --orphan leading-del &&
+ git reset --hard &&
+ cat >file.c <<-\EOF &&
+ // leading comment
+ void tracked()
+ {
+ return 1;
+ }
+ EOF
+ git add file.c &&
+ test_tick &&
+ git commit -m "add file with leading comment" &&
+ cat >file.c <<-\EOF &&
+ void tracked()
+ {
+ return 2;
+ }
+ EOF
+ git commit -a -m "modify tracked and delete leading comment" &&
+ git log -L:tracked:file.c --format= -1 -p >actual &&
+ # The leading comment deletion is resolved by the next
+ # non-removal line (void tracked), which is in range: a
+ # removal is classified by the position of the following
+ # line, so it joins the range that line falls in.
+ test_grep "return 2" actual &&
+ test_grep "leading comment" actual
+ '
+
+ test_expect_success 'setup for line-range filter edge cases' '
+ git checkout --orphan filter-edge &&
+ git reset --hard &&
+ cat >file.c <<-\EOF &&
+ void before()
+ {
+ return 0;
+ }
+
+ void tracked()
+ {
+ int a = 1;
+ int b = 2;
+ int c = 3;
+ return a + b + c;
+ }
+
+ void after()
+ {
+ return 9;
+ }
+ EOF
+ git add file.c &&
+ test_tick &&
+ git commit -m "initial"
+ '
+
+ test_expect_success '-L change at exact first line of range' '
+ git checkout filter-edge &&
+ # Change the function signature (first line of range)
+ sed "s/void tracked/int tracked/" file.c >tmp &&
+ mv tmp file.c &&
+ git commit -a -m "change first line" &&
+ git log -L:tracked:file.c -p --format=%s -1 >actual &&
+ test_grep "change first line" actual &&
+ test_grep "+int tracked" actual &&
+ test_grep "\\-void tracked" actual
+ '
+
+ test_expect_success '-L change at exact last line of range' '
+ git checkout filter-edge &&
+ git reset --hard HEAD~1 &&
+ # Change the closing brace line (last line of range)
+ sed "s/^}$/} \/\/ end tracked/" file.c >tmp &&
+ mv tmp file.c &&
+ git commit -a -m "change last line" &&
+ git log -L:tracked:file.c -p --format=%s -1 >actual &&
+ test_grep "change last line" actual &&
+ test_grep "end tracked" actual
+ '
+
+ test_expect_success '-L pure deletion in range (no additions)' '
+ git checkout filter-edge &&
+ git reset --hard HEAD~1 &&
+ # Delete a line inside tracked() without adding anything
+ sed "/int c/d" file.c >tmp &&
+ mv tmp file.c &&
+ git commit -a -m "pure deletion" &&
+ git log -L:tracked:file.c -p --format=%s -1 >actual &&
+ test_grep "pure deletion" actual &&
+ test_grep "\\-.*int c" actual
+ '
+
test_expect_success '-L with --diff-filter=M excludes root commit' '
git checkout parent-oids &&
git log -L:func2:file.c --diff-filter=M --format=%s --no-patch >actual &&
test_grep "create mode 100644 file.c" actual
'
+ test_expect_success 'setup for --check test' '
+ git checkout --orphan check-test &&
+ git reset --hard &&
+ cat >check.c <<-\EOF &&
+ void tracked()
+ {
+ return;
+ }
+
+ void other()
+ {
+ return;
+ }
+ EOF
+ git add check.c &&
+ test_tick &&
+ git commit -m "add check.c" &&
+ # Introduce trailing whitespace errors in both functions
+ sed "s/return;/return; /" check.c >check.c.tmp &&
+ mv check.c.tmp check.c &&
+ git commit -a -m "introduce trailing whitespace"
+ '
+
+ test_expect_success '--check scoped to tracked range with correct file line' '
+ # tracked() trailing whitespace is at check.c:3; report it with the
+ # real file line number, not a count from the start of the range
+ # hunk. other() at check.c:8 is outside the range and is excluded.
+ test_must_fail git log -L:tracked:check.c --check --format= >actual &&
+ test_grep "check.c:3: trailing whitespace" actual &&
+ test_grep ! "check.c:8:" actual
+ '
+
+ test_expect_success '--check reports each of several tracked ranges' '
+ # Track both functions as separate ranges. Each range is flushed
+ # as its own hunk, so the second error must report its real file
+ # line (check.c:8), not continue the numbering from the first
+ # range (check.c:3).
+ test_must_fail git log -L:tracked:check.c -L:other:check.c \
+ --check --format= >actual &&
+ test_grep "check.c:3: trailing whitespace" actual &&
+ test_grep "check.c:8: trailing whitespace" actual
+ '
+
+ test_expect_success '--check line numbers stay correct across a gap in one range' '
+ git checkout --orphan check-gap &&
+ git reset --hard &&
+ cat >gap.c <<-\EOF &&
+ void tracked()
+ {
+ int a = 1;
+ int b = 2;
+ int c = 3;
+ int d = 4;
+ int e = 5;
+ int g = 7;
+ return;
+ }
+ EOF
+ git add gap.c &&
+ test_tick &&
+ git commit -m "add gap.c" &&
+ # Two trailing-whitespace errors within one tracked range,
+ # separated by clean lines. ctxlen is inflated to the range span,
+ # so they land in a single xdiff hunk with the gap as context;
+ # both must report their real file line number, with the context
+ # lines between them counted.
+ sed -e "s/int a = 1;/int a = 1; /" -e "s/int g = 7;/int g = 7; /" gap.c >tmp &&
+ mv tmp gap.c &&
+ git commit -a -m "ws errors with a gap" &&
+ test_must_fail git log -L:tracked:gap.c --check --format= >actual &&
+ test_grep "gap.c:3: trailing whitespace" actual &&
+ test_grep "gap.c:8: trailing whitespace" actual
+ '
+
+ test_expect_success '--check does not report blank-at-eof outside the range' '
+ git checkout --orphan check-eof &&
+ git reset --hard &&
+ printf "void tracked()\n{\n return;\n}\n\nint tail = 1;\n" >eof.c &&
+ git add eof.c &&
+ test_tick &&
+ git commit -m "add eof.c" &&
+ # One commit introduces a trailing-whitespace error inside tracked()
+ # (line 3) and a blank line at end of file (line 7, outside the
+ # range). The blank-at-eof check scans the whole file, so it must be
+ # scoped: report the in-range error, not the out-of-range EOF blank.
+ printf "void tracked()\n{\n return; \n}\n\nint tail = 1;\n\n" >eof.c &&
+ git commit -a -m "ws in range, blank at eof out of range" &&
+ test_must_fail git log -L:tracked:eof.c --check --format= >actual &&
+ test_grep "eof.c:3: trailing whitespace" actual &&
+ test_grep ! "blank line at EOF" actual
+ '
+
+ test_expect_success '-L -G is scoped to the tracked range' '
+ git checkout --orphan grep-scope &&
+ git reset --hard &&
+ cat >gp.c <<-\EOF &&
+ int func1()
+ {
+ return ALPHA;
+ }
+
+ int func2()
+ {
+ return BETA;
+ }
+ EOF
+ git add gp.c &&
+ test_tick &&
+ git commit -m "add gp.c" &&
+ sed -e "s/ALPHA/ALPHA2/" -e "s/BETA/BETA2/" gp.c >tmp &&
+ mv tmp gp.c &&
+ git commit -a -m "touch both functions" &&
+ # The commit changes ALPHA (func1) and BETA (func2). Tracking func2,
+ # -G BETA matches its in-range change; -G ALPHA must not, since ALPHA
+ # changes only outside the tracked range.
+ git log -L:func2:gp.c -G BETA --format=%s >actual &&
+ test_grep "touch both functions" actual &&
+ git log -L:func2:gp.c -G ALPHA --format=%s >actual &&
+ test_grep ! "touch both functions" actual
+ '
+
+ test_expect_success '-L -G searches the whole file under textconv' '
+ git checkout --orphan grep-textconv &&
+ git reset --hard &&
+ cat >tc.c <<-\EOF &&
+ int func1()
+ {
+ return F1;
+ }
+
+ int func2()
+ {
+ return F2;
+ }
+ EOF
+ git add tc.c &&
+ test_tick &&
+ git commit -m "add tc.c" &&
+ # One commit changes func1 and func2; MAGIC lands only in the
+ # func2 change, outside func1.
+ sed -e "s/F1/F1 + 1/" -e "s/return F2/return MAGIC/" tc.c >tmp &&
+ mv tmp tc.c &&
+ git commit -a -m "change both funcs" &&
+ echo "tc.c diff=tc" >.gitattributes &&
+
+ # Without a textconv driver, -G is scoped to func1, so MAGIC (only
+ # in the func2 change) does not select the commit.
+ git log -L:func1:tc.c -G MAGIC --format=%s --no-patch >actual &&
+ test_must_be_empty actual &&
+
+ # A textconv driver makes the range (original-file line numbers)
+ # meaningless against the driver output, so -G falls back to the
+ # whole file and MAGIC now selects the commit.
+ git config diff.tc.textconv cat &&
+ git log -L:func1:tc.c -G MAGIC --format=%s --no-patch >actual &&
+ test_grep "change both funcs" actual
+ '
+
+test_expect_success 'get_commit_action() does not mutate a not-yet-walked commit' '
+ git init peek &&
+ (
+ cd peek &&
+ test_write_lines 1 2 3 4 5 >f.c &&
+ git add f.c && test_tick && git commit -m base &&
+ test_write_lines 1 two 3 4 5 >f.c &&
+ test_tick && git commit -am change &&
+
+ # Peek HEAD^, which the walk has not reached (the out-of-order
+ # call a lookahead makes), and confirm get_commit_action() leaves
+ # it untouched. A side effect is invisible in the commit list
+ # (range merges are idempotent), so the helper reports whether the
+ # call mutated the peeked commit at all.
+ echo "mutated 0" >expect &&
+ test-tool revision-walking line-log-peek HEAD^ 1,3:f.c HEAD >actual &&
+ test_cmp expect actual
+ )
+'
+
test_done