From: "Michael Montalbo via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: Michael Montalbo <mmontalbo@gmail.com>,
Michael Montalbo <mmontalbo@gmail.com>
Subject: [PATCH v3 6/7] diff: support --check with -L line ranges
Date: Thu, 03 Sep 2026 05:05:18 +0000 [thread overview]
Message-ID: <f1f5af8f77694ec4f775bcf33f17ed2cd00670e3.1788411919.git.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.2152.v3.git.1788411919.gitgitgadget@gmail.com>
From: Michael Montalbo <mmontalbo@gmail.com>
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 <mmontalbo@gmail.com>
---
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
next prev parent reply other threads:[~2026-09-03 5:05 UTC|newest]
Thread overview: 27+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-18 18:16 [PATCH 0/7] line-log: range-scope stat, check, and -G under -L Michael Montalbo via GitGitGadget
2026-06-18 18:16 ` [PATCH 1/7] diff: rename and group the line-range filter for clarity Michael Montalbo via GitGitGadget
2026-06-18 18:16 ` [PATCH 2/7] diff: simplify the line-range filter by classifying removals immediately Michael Montalbo via GitGitGadget
2026-06-18 18:16 ` [PATCH 3/7] diff: emit -L hunk headers via xdiff's formatter Michael Montalbo via GitGitGadget
2026-06-18 18:16 ` [PATCH 4/7] diff: extract a line-range diff helper for reuse Michael Montalbo via GitGitGadget
2026-06-18 18:16 ` [PATCH 5/7] line-log: support diff stat formats with -L Michael Montalbo via GitGitGadget
2026-06-18 22:00 ` Junio C Hamano
2026-06-23 2:25 ` Michael Montalbo
2026-06-18 18:16 ` [PATCH 6/7] diff: support --check with -L line ranges Michael Montalbo via GitGitGadget
2026-06-18 18:16 ` [PATCH 7/7] diffcore-pickaxe: scope -G to the -L tracked range Michael Montalbo via GitGitGadget
2026-06-27 17:28 ` [PATCH v2 0/7] line-log: scope stat, check, and -G to -L line ranges Michael Montalbo via GitGitGadget
2026-06-27 17:28 ` [PATCH v2 1/7] diff: rename and group the line-range filter for clarity Michael Montalbo via GitGitGadget
2026-06-27 17:28 ` [PATCH v2 2/7] diff: simplify the line-range filter by classifying removals immediately Michael Montalbo via GitGitGadget
2026-06-27 17:28 ` [PATCH v2 3/7] diff: emit -L hunk headers via xdiff's formatter Michael Montalbo via GitGitGadget
2026-06-27 17:28 ` [PATCH v2 4/7] diff: extract a line-range diff helper for reuse Michael Montalbo via GitGitGadget
2026-06-27 17:28 ` [PATCH v2 5/7] line-log: support diff stat formats with -L Michael Montalbo via GitGitGadget
2026-06-27 17:29 ` [PATCH v2 6/7] diff: support --check with -L line ranges Michael Montalbo via GitGitGadget
2026-06-27 17:29 ` [PATCH v2 7/7] diffcore-pickaxe: scope -G to the -L tracked range Michael Montalbo via GitGitGadget
2026-07-15 21:05 ` [PATCH 0/7] line-log: range-scope stat, check, and -G under -L Michael Montalbo
2026-09-03 5:05 ` [PATCH v3 0/7] line-log: scope stat, check, and -G to -L line ranges Michael Montalbo via GitGitGadget
2026-09-03 5:05 ` [PATCH v3 1/7] diff: rename line-range filter struct and clarify fields Michael Montalbo via GitGitGadget
2026-09-03 5:05 ` [PATCH v3 2/7] diff: simplify the line-range filter by classifying removals immediately Michael Montalbo via GitGitGadget
2026-09-03 5:05 ` [PATCH v3 3/7] diff: emit -L hunk headers via xdiff's formatter Michael Montalbo via GitGitGadget
2026-09-03 5:05 ` [PATCH v3 4/7] diff: extract a line-range diff helper for reuse Michael Montalbo via GitGitGadget
2026-09-03 5:05 ` [PATCH v3 5/7] diff: support stat formats with -L Michael Montalbo via GitGitGadget
2026-09-03 5:05 ` Michael Montalbo via GitGitGadget [this message]
2026-09-03 5:05 ` [PATCH v3 7/7] diffcore-pickaxe: limit -G to the -L tracked range Michael Montalbo via GitGitGadget
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=f1f5af8f77694ec4f775bcf33f17ed2cd00670e3.1788411919.git.gitgitgadget@gmail.com \
--to=gitgitgadget@gmail.com \
--cc=git@vger.kernel.org \
--cc=mmontalbo@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox