[PATCH v3 6/7] diff: support --check with -L line ranges
From: Michael Montalbo via GitGitGadget <hidden>
Date: 2026-09-03 05:05:30
Subsystem:
documentation, the rest · Maintainers:
Jonathan Corbet, Linus Torvalds
From: Michael Montalbo <redacted> Reuse the line_range_filter in builtin_checkdiff() so -L supports the --check option. Add orig_hunk_fn field similar to orig_line_fn that forwards xdiff_emit_hunk_fn calls when we flush filtered hunks. This is necessary because --check relies on receiving calls to its checkdiff_consume_hunk function for managing state. Document and ungate the newly enabled option, and add tests verifying the new behavior. Signed-off-by: Michael Montalbo <redacted> --- Documentation/line-range-options.adoc | 11 ++-- diff.c | 43 +++++++++++++- revision.c | 2 +- t/t4211-line-log.sh | 83 +++++++++++++++++++++++++++ 4 files changed, 130 insertions(+), 9 deletions(-)
diff --git a/Documentation/line-range-options.adoc b/Documentation/line-range-options.adoc
index b3e8b5c62c..4a7ab97d75 100644
--- a/Documentation/line-range-options.adoc
+++ b/Documentation/line-range-options.adoc@@ -10,11 +10,12 @@ You can specify this option more than once. Implies `--patch`. Patch output can be suppressed using `--no-patch`. The following non-patch diff formats are supported: `--raw`, - `--name-only`, `--name-status`, `--summary`, `--stat`, `--numstat`, - and `--shortstat`. The stat formats count only lines within the tracked - range. `--dirstat` is not supported with `-L`: it summarizes change as each - directory's share of the total churn, not as counts for the tracked lines. - Use `--numstat` for exact per-file counts within the range. + `--name-only`, `--name-status`, `--summary`, `--check`, `--stat`, + `--numstat`, and `--shortstat`. The stat formats count only lines + within the tracked range. `--dirstat` is not supported with `-L`: it + reports how change is distributed across directories over whole files, + which is not meaningful for line ranges within a file. Use `--numstat` + for exact per-file counts within the range. + Patch formatting options such as `--word-diff`, `--color-moved`, `--no-prefix`, and whitespace options (`-w`, `-b`) are supported,
diff --git a/diff.c b/diff.c
index 4a30d7b631..49b6732c81 100644
--- a/diff.c
+++ b/diff.c@@ -614,6 +614,7 @@ struct emit_callback { */ struct line_range_filter { xdiff_emit_line_fn orig_line_fn; + xdiff_emit_hunk_fn orig_hunk_fn; void *orig_cb_data; const struct range_set *range_sets_to_filter_by; unsigned int range_set_idx;
@@ -2577,6 +2578,13 @@ static void flush_range_hunk(struct line_range_filter *filter) filter->accumulating_hunk.func_name, filter->accumulating_hunk.func_name_len); + if (filter->orig_hunk_fn) + filter->orig_hunk_fn(filter->orig_cb_data, + filter->accumulating_hunk.old_begin, old_count, + filter->accumulating_hunk.new_begin, new_count, + filter->accumulating_hunk.func_name, + filter->accumulating_hunk.func_name_len); + filter->ret = filter->orig_line_fn(filter->orig_cb_data, hdr.buf, hdr.len); strbuf_release(&hdr);
@@ -4203,11 +4211,23 @@ static void builtin_diffstat(const char *name_a, const char *name_b, diff_free_filespec_data(two); } +static int idx_in_ranges(const struct range_set *ranges, long idx) +{ + unsigned int i; + + for (i = 0; i < ranges->nr; i++) + if (idx >= ranges->ranges[i].start && + idx < ranges->ranges[i].end) + return 1; + return 0; +} + static void builtin_checkdiff(const char *name_a, const char *name_b, const char *attr_path, struct diff_filespec *one, struct diff_filespec *two, - struct diff_options *o) + struct diff_options *o, + const struct range_set *line_ranges) { mmfile_t mf1, mf2; struct checkdiff_t data;
@@ -4247,7 +4267,19 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, memset(&xecfg, 0, sizeof(xecfg)); xecfg.ctxlen = 1; /* at least one context line */ xpp.flags = 0; - if (xdi_diff_outf(&mf1, &mf2, checkdiff_consume_hunk, + + if (line_ranges) { + struct line_range_filter lr_filter; + + line_range_filter_init(&lr_filter, line_ranges, + checkdiff_consume, &data); + lr_filter.orig_hunk_fn = checkdiff_consume_hunk; + + if (line_range_filter_diff(&lr_filter, &mf1, &mf2, + &xpp, &xecfg)) + die("unable to generate checkdiff for %s", + one->path); + } else if (xdi_diff_outf(&mf1, &mf2, checkdiff_consume_hunk, checkdiff_consume, &data, &xpp, &xecfg)) die("unable to generate checkdiff for %s", one->path);
@@ -4260,6 +4292,10 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, check_blank_at_eof(&mf1, &mf2, &ecbdata); blank_at_eof = ecbdata.blank_at_eof_in_postimage; + if (blank_at_eof && line_ranges && + !idx_in_ranges(line_ranges, blank_at_eof - 1)) + blank_at_eof = 0; + if (blank_at_eof) { static char *err; if (!err)
@@ -5055,7 +5091,8 @@ static void run_checkdiff(struct diff_filepair *p, struct diff_options *o) diff_fill_oid_info(p->one, o->repo->index); diff_fill_oid_info(p->two, o->repo->index); - builtin_checkdiff(name, other, attr_path, p->one, p->two, o); + builtin_checkdiff(name, other, attr_path, p->one, p->two, o, + p->line_ranges); } void repo_diff_setup(struct repository *r, struct diff_options *options)
diff --git a/revision.c b/revision.c
index 4639c0df8e..4cc0d032bc 100644
--- a/revision.c
+++ b/revision.c@@ -3231,7 +3231,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s DIFF_FORMAT_RAW | DIFF_FORMAT_NAME | DIFF_FORMAT_NAME_STATUS | DIFF_FORMAT_SUMMARY | DIFF_FORMAT_NUMSTAT | DIFF_FORMAT_DIFFSTAT | - DIFF_FORMAT_SHORTSTAT)))) + DIFF_FORMAT_SHORTSTAT | DIFF_FORMAT_CHECKDIFF)))) die(_("-L does not support the requested diff format")); if (revs->expand_tabs_in_log < 0)
diff --git a/t/t4211-line-log.sh b/t/t4211-line-log.sh
index 4e8f71c289..2a542aa643 100755
--- a/t/t4211-line-log.sh
+++ b/t/t4211-line-log.sh@@ -924,4 +924,87 @@ test_expect_success 'get_commit_action() does not mutate a not-yet-walked commit ) ' +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" && + 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 is limited to tracked ranges and reports real file line numbers' ' + test_must_fail git log -L:tracked:check.c --check --format= >raw && + grep -E ":[0-9]+:" raw >actual && + echo "check.c:3: trailing whitespace." >expect && + test_cmp expect actual && + + test_must_fail git log -L:tracked:check.c -L:other:check.c \ + --check --format= >raw && + grep -E ":[0-9]+:" raw >actual && + cat >expect <<-\EOF && + check.c:3: trailing whitespace. + check.c:8: trailing whitespace. + EOF + test_cmp expect actual +' + +test_expect_success '--check reports each error at its real line 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" && + 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= >raw && + grep -E ":[0-9]+:" raw >actual && + cat >expect <<-\EOF && + gap.c:3: trailing whitespace. + gap.c:8: trailing whitespace. + EOF + test_cmp expect 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" && + 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= >raw && + grep -E ":[0-9]+:" raw >actual && + echo "eof.c:3: trailing whitespace." >expect && + test_cmp expect actual +' + test_done
--
gitgitgadget