diff: extract a line-range diff helper for reuse

builtin_diff() open-codes the line-range filter setup and teardown around its xdi_diff_outf() call: zero the struct, point it at the output callback, inflate ctxlen to the largest range span so each range yields a single xdiff hunk, run the diff, flush the trailing range hunk, and release the buffer. The upcoming -L stat and check formats need the same sequence. Extract line_range_filter_init() for the setup and a line_range_filter_diff() helper that prepares the xdiff config the filter needs, runs an initialized filter through xdi_diff_outf(), flushes the final range hunk, and releases it, returning the latched error. The helper inflates ctxlen to the largest range span so each range yields a single xdiff hunk, and clears XDL_EMIT_NO_HUNK_HDR so the hunk headers the filter seeds its position from are always emitted. Folding both into the helper keeps these invariants, which the filter's position tracking relies on, in a single place for every consumer. builtin_diff() now does init + line_range_filter_diff(); the next two patches reuse them in builtin_diffstat() and builtin_checkdiff() instead of repeating the boilerplate. No behavior change: builtin_diff() leaves XDL_EMIT_NO_HUNK_HDR unset, so clearing it is a no-op until the suppressing consumers arrive. Signed-off-by: Michael Montalbo <mmontalbo@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Michael Montalbo committed Jun 27, 2026 at 17:28 UTC 56cf30f68c6ab06e34c295c62b081da8904b1c41
1 file changed +61 -39
diff.c
+61 -39
@@ -2580,6 +2580,18 @@ static int quick_consume(void *priv, char *line UNUSED, unsigned long len UNUSED
2580 return 1;
2581 }
2582
2583 +static void line_range_filter_init(struct line_range_filter *filter,
2584 + const struct range_set *ranges,
2585 + xdiff_emit_line_fn line_fn,
2586 + void *cb_data)
2587 +{
2588 + memset(filter, 0, sizeof(*filter));
2589 + filter->orig_line_fn = line_fn;
2590 + filter->orig_cb_data = cb_data;
2591 + filter->ranges = ranges;
2592 + strbuf_init(&filter->hunk.lines, 0);
2593 +}
2594 +
2595 /*
2596 * Begin a range hunk at the first in-range line. Its position fixes the
2597 * hunk's begins, taken from the two image cursors before they advance:
@@ -2744,6 +2756,50 @@ static int line_range_line_fn(void *priv, char *line, unsigned long len)
2756 return filter->ret;
2757 }
2758
2759 +/*
2760 + * Run an xdiff pass through an initialized line-range filter, flush the
2761 + * final range hunk, and release the filter. Inflates ctxlen to the largest
2762 + * range span first, so that every change within a single range lands in one
2763 + * xdiff hunk and the inter-change context is emitted; the filter then clips
2764 + * back to range boundaries. The optimal ctxlen depends on where changes fall
2765 + * within the range, which is only known after xdiff runs, so the max span is
2766 + * the upper bound that guarantees correctness in a single pass. Every
2767 + * consumer (patch, diffstat, check) relies on one xdiff hunk per range, so
2768 + * this lives here rather than at each call site. Also clears
2769 + * XDL_EMIT_NO_HUNK_HDR: the filter seeds its per-image position from the hunk
2770 + * headers, so a consumer that otherwise suppresses them (diffstat) still gets
2771 + * them here. Returns non-zero if xdiff or any forwarded callback failed.
2772 + */
2773 +static int line_range_filter_diff(struct line_range_filter *filter,
2774 + mmfile_t *mf1, mmfile_t *mf2,
2775 + xpparam_t *xpp, xdemitconf_t *xecfg)
2776 +{
2777 + const struct range_set *ranges = filter->ranges;
2778 + long max_span = 0;
2779 + unsigned int i;
2780 + int ret;
2781 +
2782 + for (i = 0; i < ranges->nr; i++) {
2783 + long span = ranges->ranges[i].end - ranges->ranges[i].start;
2784 + if (span > max_span)
2785 + max_span = span;
2786 + }
2787 + if (max_span > xecfg->ctxlen)
2788 + xecfg->ctxlen = max_span;
2789 +
2790 + /* the filter seeds its per-image position from hunk headers */
2791 + xecfg->flags &= ~XDL_EMIT_NO_HUNK_HDR;
2792 +
2793 + ret = xdi_diff_outf(mf1, mf2, line_range_hunk_fn,
2794 + line_range_line_fn, filter, xpp, xecfg);
2795 + if (!ret) {
2796 + flush_range_hunk(filter);
2797 + ret = filter->ret;
2798 + }
2799 + strbuf_release(&filter->hunk.lines);
2800 + return ret;
2801 +}
2802 +
2803 static void pprint_rename(struct strbuf *name, const char *a, const char *b)
2804 {
2805 const char *old_name = a;
@@ -4108,49 +4164,15 @@ static void builtin_diff(const char *name_a,
4164 xdi_diff_outf(&mf1, &mf2, NULL, quick_consume,
4165 &ecbdata, &xpp, &xecfg);
4166 } else if (line_ranges) {
4111 - struct line_range_filter lr_state;
4112 - unsigned int i;
4113 - long max_span = 0;
4167 + struct line_range_filter lr_filter;
4168
4115 - memset(&lr_state, 0, sizeof(lr_state));
4116 - lr_state.orig_line_fn = fn_out_consume;
4117 - lr_state.orig_cb_data = &ecbdata;
4118 - lr_state.ranges = line_ranges;
4119 - strbuf_init(&lr_state.hunk.lines, 0);
4120 -
4121 - /*
4122 - * Inflate ctxlen so that all changes within
4123 - * any single range are merged into one xdiff
4124 - * hunk and the inter-change context is emitted.
4125 - * The callback clips back to range boundaries.
4126 - *
4127 - * The optimal ctxlen depends on where changes
4128 - * fall within the range, which is only known
4129 - * after xdiff runs; the max range span is the
4130 - * upper bound that guarantees correctness in a
4131 - * single pass.
4132 - */
4133 - for (i = 0; i < line_ranges->nr; i++) {
4134 - long span = line_ranges->ranges[i].end -
4135 - line_ranges->ranges[i].start;
4136 - if (span > max_span)
4137 - max_span = span;
4138 - }
4139 - if (max_span > xecfg.ctxlen)
4140 - xecfg.ctxlen = max_span;
4141 -
4142 - if (xdi_diff_outf(&mf1, &mf2,
4143 - line_range_hunk_fn,
4144 - line_range_line_fn,
4145 - &lr_state, &xpp, &xecfg))
4146 - die("unable to generate diff for %s",
4147 - one->path);
4169 + line_range_filter_init(&lr_filter, line_ranges,
4170 + fn_out_consume, &ecbdata);
4171
4149 - flush_range_hunk(&lr_state);
4150 - if (lr_state.ret)
4172 + if (line_range_filter_diff(&lr_filter, &mf1, &mf2,
4173 + &xpp, &xecfg))
4174 die("unable to generate diff for %s",
4175 one->path);
4153 - strbuf_release(&lr_state.hunk.lines);
4176 } else if (xdi_diff_outf(&mf1, &mf2, NULL, fn_out_consume,
4177 &ecbdata, &xpp, &xecfg))
4178 die("unable to generate diff for %s", one->path);