git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v3 6/7] diff: support --check with -L line ranges

From
Michael Montalbo via GitGitGadget <gitgitgadget@gmail.com>
Date
Sep 3, 2026, 05:05 UTC
Message-ID
<f1f5af8f77694ec4f775bcf33f17ed2cd00670e3.1788411919.git.gitgitgadget@gmail.com>
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
Previous: Michael Montalbo via GitGitGadgetNext: Michael Montalbo via GitGitGadget
Message 26 of 27 in “line-log: range-scope stat, check, and -G under -L”
  1. 0/7 line-log: range-scope stat, check, and -G under -LMichael Montalbo via GitGitGadget, Jun 18, 2026
  2. 1/7 diff: rename and group the line-range filter for clarityMichael Montalbo via GitGitGadget, Jun 18, 2026
  3. 2/7 diff: simplify the line-range filter by classifying removals immediatelyMichael Montalbo via GitGitGadget, Jun 18, 2026
  4. 3/7 diff: emit -L hunk headers via xdiff's formatterMichael Montalbo via GitGitGadget, Jun 18, 2026
  5. 4/7 diff: extract a line-range diff helper for reuseMichael Montalbo via GitGitGadget, Jun 18, 2026
  6. 5/7 line-log: support diff stat formats with -LMichael Montalbo via GitGitGadget, Jun 18, 2026
  7. Junio C HamanoJun 18, 2026
  8. Michael MontalboJun 23, 2026
  9. 6/7 diff: support --check with -L line rangesMichael Montalbo via GitGitGadget, Jun 18, 2026
  10. 7/7 diffcore-pickaxe: scope -G to the -L tracked rangeMichael Montalbo via GitGitGadget, Jun 18, 2026
  11. 0/7 line-log: scope stat, check, and -G to -L line rangesMichael Montalbo via GitGitGadget, Jun 27, 2026
  12. 1/7 diff: rename and group the line-range filter for clarityMichael Montalbo via GitGitGadget, Jun 27, 2026
  13. 2/7 diff: simplify the line-range filter by classifying removals immediatelyMichael Montalbo via GitGitGadget, Jun 27, 2026
  14. 3/7 diff: emit -L hunk headers via xdiff's formatterMichael Montalbo via GitGitGadget, Jun 27, 2026
  15. 4/7 diff: extract a line-range diff helper for reuseMichael Montalbo via GitGitGadget, Jun 27, 2026
  16. 5/7 line-log: support diff stat formats with -LMichael Montalbo via GitGitGadget, Jun 27, 2026
  17. 6/7 diff: support --check with -L line rangesMichael Montalbo via GitGitGadget, Jun 27, 2026
  18. 7/7 diffcore-pickaxe: scope -G to the -L tracked rangeMichael Montalbo via GitGitGadget, Jun 27, 2026
  19. Michael MontalboJul 15, 2026
  20. 0/7 line-log: scope stat, check, and -G to -L line rangesMichael Montalbo via GitGitGadget, Sep 3, 2026
  21. 1/7 diff: rename line-range filter struct and clarify fieldsMichael Montalbo via GitGitGadget, Sep 3, 2026
  22. 2/7 diff: simplify the line-range filter by classifying removals immediatelyMichael Montalbo via GitGitGadget, Sep 3, 2026
  23. 3/7 diff: emit -L hunk headers via xdiff's formatterMichael Montalbo via GitGitGadget, Sep 3, 2026
  24. 4/7 diff: extract a line-range diff helper for reuseMichael Montalbo via GitGitGadget, Sep 3, 2026
  25. 5/7 diff: support stat formats with -LMichael Montalbo via GitGitGadget, Sep 3, 2026
  26. 6/7 diff: support --check with -L line rangesMichael Montalbo via GitGitGadget, Sep 3, 2026
  27. 7/7 diffcore-pickaxe: limit -G to the -L tracked rangeMichael Montalbo via GitGitGadget, Sep 3, 2026

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.