From: "Michael Montalbo via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: "D. Ben Knoble" <ben.knoble@gmail.com>,
Michael Montalbo <mmontalbo@gmail.com>,
Michael Montalbo <mmontalbo@gmail.com>
Subject: [PATCH 3/7] diff: emit -L hunk headers via xdiff's formatter
Date: Thu, 18 Jun 2026 18:16:28 +0000 [thread overview]
Message-ID: <d211c82e40446d1d0b33f117d817a03ad716eea0.1781806593.git.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.2152.git.1781806593.gitgitgadget@gmail.com>
From: Michael Montalbo <mmontalbo@gmail.com>
The line-range filter builds its own "@@ -<old> +<new> @@" header for
each range hunk. For a side with no lines (count 0, such as the old
side of a pure insertion), the begin should be the number of the line
before the change, per the convention git diff and xdl_emit_hunk_hdr()
follow. The hand-rolled code's begin was one too high; in t4211 this
produced
@@ -25,0 +18,9 @@
an old begin of 25 in a 24-line file, where git diff would give 24.
Stop hand-rolling the header. flush_range_hunk() now formats it through
xdiff's own emitter: a new xdiff_emit_hunk_header() helper wraps
xdl_emit_hunk_hdr(), the function that produces every other diff's hunk
headers. The count-0 begin is then correct by construction, and as a
side effect -L headers match git diff exactly, including its omission of
a count of 1 ("@@ -22 +22 @@" rather than "@@ -22,1 +22,1 @@").
xdiff's hunk callback already hands line_range_hunk_fn() a count-0 begin
decremented, so undo that when seeding the cursors and let the formatter
re-apply the convention once, at emit time.
The off-by-one predates this series, and the two regenerated fixtures
reach it from different origins: no-assertion-error has carried it since
its test was added in ab60c693a2 (line-log: fix assertion error,
2025-08-18), while vanishes-early acquired it when 86e986f166 (line-log:
route -L output through the standard diff pipeline) reshaped its tracked
line into a pure insertion. vanishes-early also drops its count-1
counts.
Signed-off-by: Michael Montalbo <mmontalbo@gmail.com>
---
diff.c | 27 +++++++++++-------------
t/t4211/sha1/expect.no-assertion-error | 2 +-
t/t4211/sha1/expect.vanishes-early | 6 +++---
t/t4211/sha256/expect.no-assertion-error | 2 +-
t/t4211/sha256/expect.vanishes-early | 6 +++---
xdiff-interface.c | 19 +++++++++++++++++
xdiff-interface.h | 15 +++++++++++++
7 files changed, 54 insertions(+), 23 deletions(-)
diff --git a/diff.c b/diff.c
index ee765d7ac2..9751bb6798 100644
--- a/diff.c
+++ b/diff.c
@@ -2636,14 +2636,9 @@ static void flush_range_hunk(struct line_range_filter *filter)
return;
}
- strbuf_addf(&hdr, "@@ -%ld,%ld +%ld,%ld @@",
- filter->hunk.old_begin, old_count,
- filter->hunk.new_begin, new_count);
- if (filter->funclen > 0) {
- strbuf_addch(&hdr, ' ');
- strbuf_add(&hdr, filter->func, filter->funclen);
- }
- strbuf_addch(&hdr, '\n');
+ xdiff_emit_hunk_header(&hdr, filter->hunk.old_begin, old_count,
+ filter->hunk.new_begin, new_count,
+ filter->func, filter->funclen);
filter->ret = filter->orig_line_fn(filter->orig_cb_data, hdr.buf, hdr.len);
strbuf_release(&hdr);
@@ -2668,19 +2663,21 @@ static void flush_range_hunk(struct line_range_filter *filter)
}
static void line_range_hunk_fn(void *data,
- long old_begin, long old_nr UNUSED,
- long new_begin, long new_nr UNUSED,
+ long old_begin, long old_nr,
+ long new_begin, long new_nr,
const char *func, long funclen)
{
struct line_range_filter *filter = data;
/*
- * When count > 0, begin is 1-based. When count == 0, begin is
- * adjusted down by 1 by xdl_emit_hunk_hdr(), but no lines of
- * that type will arrive, so the value is unused.
+ * Seed the per-image line cursors from the hunk header's begins. For
+ * a side with no lines (count 0), xdiff's callback has already moved
+ * its begin to the line before the change, so add one back to recover
+ * the true 1-based start. xdiff_emit_hunk_header() reapplies that -1
+ * when the clipped hunk is emitted.
*/
- filter->lno_in_postimage = new_begin;
- filter->lno_in_preimage = old_begin;
+ filter->lno_in_postimage = new_nr ? new_begin : new_begin + 1;
+ filter->lno_in_preimage = old_nr ? old_begin : old_begin + 1;
if (funclen > 0) {
if (funclen > (long)sizeof(filter->func))
diff --git a/t/t4211/sha1/expect.no-assertion-error b/t/t4211/sha1/expect.no-assertion-error
index 54c568f273..95faf51a7b 100644
--- a/t/t4211/sha1/expect.no-assertion-error
+++ b/t/t4211/sha1/expect.no-assertion-error
@@ -8,7 +8,7 @@ diff --git a/b.c b/b.c
index bf79c2f..27c829c 100644
--- a/b.c
+++ b/b.c
-@@ -25,0 +18,9 @@
+@@ -24,0 +18,9 @@
+long f(long x)
+{
+ int s = 0;
diff --git a/t/t4211/sha1/expect.vanishes-early b/t/t4211/sha1/expect.vanishes-early
index a413ad3659..e4b1a201d5 100644
--- a/t/t4211/sha1/expect.vanishes-early
+++ b/t/t4211/sha1/expect.vanishes-early
@@ -8,7 +8,7 @@ diff --git a/a.c b/a.c
index 0b9cae5..5de3ea4 100644
--- a/a.c
+++ b/a.c
-@@ -23,0 +24,1 @@ int main ()
+@@ -22,0 +24 @@ int main ()
+/* incomplete lines are bad! */
commit 100b61a6f2f720f812620a9d10afb3a960ccb73c
@@ -21,7 +21,7 @@ diff --git a/a.c b/a.c
index 5e709a1..0b9cae5 100644
--- a/a.c
+++ b/a.c
-@@ -22,1 +22,1 @@ int main ()
+@@ -22 +22 @@ int main ()
-}
+}
\ No newline at end of file
@@ -37,5 +37,5 @@ new file mode 100644
index 0000000..444e415
--- /dev/null
+++ b/a.c
-@@ -0,0 +20,1 @@
+@@ -0,0 +20 @@
+}
diff --git a/t/t4211/sha256/expect.no-assertion-error b/t/t4211/sha256/expect.no-assertion-error
index c25f2ce19c..815d27f7f1 100644
--- a/t/t4211/sha256/expect.no-assertion-error
+++ b/t/t4211/sha256/expect.no-assertion-error
@@ -8,7 +8,7 @@ diff --git a/b.c b/b.c
index 69cb69c..a0d566e 100644
--- a/b.c
+++ b/b.c
-@@ -25,0 +18,9 @@
+@@ -24,0 +18,9 @@
+long f(long x)
+{
+ int s = 0;
diff --git a/t/t4211/sha256/expect.vanishes-early b/t/t4211/sha256/expect.vanishes-early
index bc33b963dc..263fc9eaac 100644
--- a/t/t4211/sha256/expect.vanishes-early
+++ b/t/t4211/sha256/expect.vanishes-early
@@ -8,7 +8,7 @@ diff --git a/a.c b/a.c
index e4fa1d8..62c1fc2 100644
--- a/a.c
+++ b/a.c
-@@ -23,0 +24,1 @@ int main ()
+@@ -22,0 +24 @@ int main ()
+/* incomplete lines are bad! */
commit 29f32ac3141c48b22803e5c4127b719917b67d0f8ca8c5248bebfa2a19f7da10
@@ -21,7 +21,7 @@ diff --git a/a.c b/a.c
index d325124..e4fa1d8 100644
--- a/a.c
+++ b/a.c
-@@ -22,1 +22,1 @@ int main ()
+@@ -22 +22 @@ int main ()
-}
+}
\ No newline at end of file
@@ -37,5 +37,5 @@ new file mode 100644
index 0000000..9f550c3
--- /dev/null
+++ b/a.c
-@@ -0,0 +20,1 @@
+@@ -0,0 +20 @@
+}
diff --git a/xdiff-interface.c b/xdiff-interface.c
index 5ee2b96d0a..32e04630ee 100644
--- a/xdiff-interface.c
+++ b/xdiff-interface.c
@@ -91,6 +91,25 @@ static int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf)
return 0;
}
+static int strbuf_out_line(void *priv, mmbuffer_t *mb, int nbuf)
+{
+ struct strbuf *out = priv;
+ int i;
+ for (i = 0; i < nbuf; i++)
+ strbuf_add(out, mb[i].ptr, mb[i].size);
+ return 0;
+}
+
+void xdiff_emit_hunk_header(struct strbuf *out,
+ long old_begin, long old_count,
+ long new_begin, long new_count,
+ const char *func, long funclen)
+{
+ xdemitcb_t ecb = { .priv = out, .out_line = strbuf_out_line };
+ xdl_emit_hunk_hdr(old_begin, old_count, new_begin, new_count,
+ func, funclen, &ecb);
+}
+
/*
* Trim down common substring at the end of the buffers,
* but end on a complete line.
diff --git a/xdiff-interface.h b/xdiff-interface.h
index ce54e1c0e0..51c88296ed 100644
--- a/xdiff-interface.h
+++ b/xdiff-interface.h
@@ -76,4 +76,19 @@ int xdiff_compare_lines(const char *l1, long s1,
*/
unsigned long xdiff_hash_string(const char *s, size_t len, long flags);
+struct strbuf;
+
+/*
+ * Append a unified-diff hunk header to `out`, e.g.
+ * "@@ -<old> +<new> @@ func\n". The header comes from wrapping xdiff's
+ * own hunk-header emitter, so it matches what a normal diff would
+ * produce for these begins and counts. For a side with no lines
+ * (count 0) the begin is the line before the change, and a count of 1
+ * is omitted.
+ */
+void xdiff_emit_hunk_header(struct strbuf *out,
+ long old_begin, long old_count,
+ long new_begin, long new_count,
+ const char *func, long funclen);
+
#endif
--
gitgitgadget
next prev parent reply other threads:[~2026-06-18 18:16 UTC|newest]
Thread overview: 9+ 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 ` Michael Montalbo via GitGitGadget [this message]
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-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
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=d211c82e40446d1d0b33f117d817a03ad716eea0.1781806593.git.gitgitgadget@gmail.com \
--to=gitgitgadget@gmail.com \
--cc=ben.knoble@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