608
struct strbuf *header;
609
};
610
611
+/*
612
+ * State for the line-range callback wrappers that sit between
613
+ * xdi_diff_outf() and fn_out_consume(). xdiff produces a normal,
614
+ * unfiltered diff; the wrappers intercept each hunk header and line,
615
+ * track post-image position, and forward only lines that fall within
616
+ * the requested ranges. Contiguous in-range lines are collected into
617
+ * range hunks and flushed with a synthetic @@ header so that
618
+ * fn_out_consume() sees well-formed unified-diff fragments.
619
+ *
620
+ * Removal lines ('-') cannot be classified by post-image position, so
621
+ * they are buffered in pending_rm until the next '+' or ' ' line
622
+ * reveals whether they precede an in-range line (flush into range hunk) or
623
+ * an out-of-range line (discard).
624
+ */
625
+struct line_range_callback {
626
+ xdiff_emit_line_fn orig_line_fn;
627
+ void *orig_cb_data;
628
+ const struct range_set *ranges; /* 0-based [start, end) */
629
+ unsigned int cur_range; /* index into the range_set */
630
+
631
+ /* Post/pre-image line counters (1-based, set from hunk headers) */
632
+ long lno_post;
633
+ long lno_pre;
634
+
635
+ /*
636
+ * Function name from most recent xdiff hunk header;
637
+ * size matches struct func_line.buf in xdiff/xemit.c.
638
+ */
639
+ char func[80];
640
+ long funclen;
641
+
642
+ /* Range hunk being accumulated for the current range */
643
+ struct strbuf rhunk;
644
+ long rhunk_old_begin, rhunk_old_count;
645
+ long rhunk_new_begin, rhunk_new_count;
646
+ int rhunk_active;
647
+ int rhunk_has_changes; /* any '+' or '-' lines? */
648
+
649
+ /* Removal lines not yet known to be in-range */
650
+ struct strbuf pending_rm;
651
+ int pending_rm_count;
652
+ long pending_rm_pre_begin; /* pre-image line of first pending */
653
+
654
+ int ret; /* latched error from orig_line_fn */
655
+};
656
+
657
static int count_lines(const char *data, int size)
658
{
659
int count, ch, completely_empty = 1, nl_just_seen = 0;
2539
return 1;
2540
}
2541
2542
+static void discard_pending_rm(struct line_range_callback *s)
2543
+{
2544
+ strbuf_reset(&s->pending_rm);
2545
+ s->pending_rm_count = 0;
2546
+}
2547
+
2548
+static void flush_rhunk(struct line_range_callback *s)
2549
+{
2550
+ struct strbuf hdr = STRBUF_INIT;
2551
+ const char *p, *end;
2552
+
2553
+ if (!s->rhunk_active || s->ret)
2554
+ return;
2555
+
2556
+ /* Drain any pending removal lines into the range hunk */
2557
+ if (s->pending_rm_count) {
2558
+ strbuf_addbuf(&s->rhunk, &s->pending_rm);
2559
+ s->rhunk_old_count += s->pending_rm_count;
2560
+ s->rhunk_has_changes = 1;
2561
+ discard_pending_rm(s);
2562
+ }
2563
+
2564
+ /*
2565
+ * Suppress context-only hunks: they contain no actual changes
2566
+ * and would just be noise. This can happen when the inflated
2567
+ * ctxlen causes xdiff to emit context covering a range that
2568
+ * has no changes in this commit.
2569
+ */
2570
+ if (!s->rhunk_has_changes) {
2571
+ s->rhunk_active = 0;
2572
+ strbuf_reset(&s->rhunk);
2573
+ return;
2574
+ }
2575
+
2576
+ strbuf_addf(&hdr, "@@ -%ld,%ld +%ld,%ld @@",
2577
+ s->rhunk_old_begin, s->rhunk_old_count,
2578
+ s->rhunk_new_begin, s->rhunk_new_count);
2579
+ if (s->funclen > 0) {
2580
+ strbuf_addch(&hdr, ' ');
2581
+ strbuf_add(&hdr, s->func, s->funclen);
2582
+ }
2583
+ strbuf_addch(&hdr, '\n');
2584
+
2585
+ s->ret = s->orig_line_fn(s->orig_cb_data, hdr.buf, hdr.len);
2586
+ strbuf_release(&hdr);
2587
+
2588
+ /*
2589
+ * Replay buffered lines one at a time through fn_out_consume.
2590
+ * The cast discards const because xdiff_emit_line_fn takes
2591
+ * char *, though fn_out_consume does not modify the buffer.
2592
+ */
2593
+ p = s->rhunk.buf;
2594
+ end = p + s->rhunk.len;
2595
+ while (!s->ret && p < end) {
2596
+ const char *eol = memchr(p, '\n', end - p);
2597
+ unsigned long line_len = eol ? (unsigned long)(eol - p + 1)
2598
+ : (unsigned long)(end - p);
2599
+ s->ret = s->orig_line_fn(s->orig_cb_data, (char *)p, line_len);
2600
+ p += line_len;
2601
+ }
2602
+
2603
+ s->rhunk_active = 0;
2604
+ strbuf_reset(&s->rhunk);
2605
+}
2606
+
2607
+static void line_range_hunk_fn(void *data,
2608
+ long old_begin, long old_nr UNUSED,
2609
+ long new_begin, long new_nr UNUSED,
2610
+ const char *func, long funclen)
2611
+{
2612
+ struct line_range_callback *s = data;
2613
+
2614
+ /*
2615
+ * When count > 0, begin is 1-based. When count == 0, begin is
2616
+ * adjusted down by 1 by xdl_emit_hunk_hdr(), but no lines of
2617
+ * that type will arrive, so the value is unused.
2618
+ *
2619
+ * Any pending removal lines from the previous xdiff hunk are
2620
+ * intentionally left in pending_rm: the line callback will
2621
+ * flush or discard them when the next content line reveals
2622
+ * whether the removals precede in-range content.
2623
+ */
2624
+ s->lno_post = new_begin;
2625
+ s->lno_pre = old_begin;
2626
+
2627
+ if (funclen > 0) {
2628
+ if (funclen > (long)sizeof(s->func))
2629
+ funclen = sizeof(s->func);
2630
+ memcpy(s->func, func, funclen);
2631
+ }
2632
+ s->funclen = funclen;
2633
+}
2634
+
2635
+static int line_range_line_fn(void *priv, char *line, unsigned long len)
2636
+{
2637
+ struct line_range_callback *s = priv;
2638
+ const struct range *cur;
2639
+ long lno_0, cur_pre;
2640
+
2641
+ if (s->ret)
2642
+ return s->ret;
2643
+
2644
+ if (line[0] == '-') {
2645
+ if (!s->pending_rm_count)
2646
+ s->pending_rm_pre_begin = s->lno_pre;
2647
+ s->lno_pre++;
2648
+ strbuf_add(&s->pending_rm, line, len);
2649
+ s->pending_rm_count++;
2650
+ return s->ret;
2651
+ }
2652
+
2653
+ if (line[0] == '\\') {
2654
+ if (s->pending_rm_count)
2655
+ strbuf_add(&s->pending_rm, line, len);
2656
+ else if (s->rhunk_active)
2657
+ strbuf_add(&s->rhunk, line, len);
2658
+ /* otherwise outside tracked range; drop silently */
2659
+ return s->ret;
2660
+ }
2661
+
2662
+ if (line[0] != '+' && line[0] != ' ')
2663
+ BUG("unexpected diff line type '%c'", line[0]);
2664
+
2665
+ lno_0 = s->lno_post - 1;
2666
+ cur_pre = s->lno_pre; /* save before advancing for context lines */
2667
+ s->lno_post++;
2668
+ if (line[0] == ' ')
2669
+ s->lno_pre++;
2670
+
2671
+ /* Advance past ranges we've passed */
2672
+ while (s->cur_range < s->ranges->nr &&
2673
+ lno_0 >= s->ranges->ranges[s->cur_range].end) {
2674
+ if (s->rhunk_active)
2675
+ flush_rhunk(s);
2676
+ discard_pending_rm(s);
2677
+ s->cur_range++;
2678
+ }
2679
+
2680
+ /* Past all ranges */
2681
+ if (s->cur_range >= s->ranges->nr) {
2682
+ discard_pending_rm(s);
2683
+ return s->ret;
2684
+ }
2685
+
2686
+ cur = &s->ranges->ranges[s->cur_range];
2687
+
2688
+ /* Before current range */
2689
+ if (lno_0 < cur->start) {
2690
+ discard_pending_rm(s);
2691
+ return s->ret;
2692
+ }
2693
+
2694
+ /* In range so start a new range hunk if needed */
2695
+ if (!s->rhunk_active) {
2696
+ s->rhunk_active = 1;
2697
+ s->rhunk_has_changes = 0;
2698
+ s->rhunk_new_begin = lno_0 + 1;
2699
+ s->rhunk_old_begin = s->pending_rm_count
2700
+ ? s->pending_rm_pre_begin : cur_pre;
2701
+ s->rhunk_old_count = 0;
2702
+ s->rhunk_new_count = 0;
2703
+ strbuf_reset(&s->rhunk);
2704
+ }
2705
+
2706
+ /* Flush pending removals into range hunk */
2707
+ if (s->pending_rm_count) {
2708
+ strbuf_addbuf(&s->rhunk, &s->pending_rm);
2709
+ s->rhunk_old_count += s->pending_rm_count;
2710
+ s->rhunk_has_changes = 1;
2711
+ discard_pending_rm(s);
2712
+ }
2713
+
2714
+ strbuf_add(&s->rhunk, line, len);
2715
+ s->rhunk_new_count++;
2716
+ if (line[0] == '+')
2717
+ s->rhunk_has_changes = 1;
2718
+ else
2719
+ s->rhunk_old_count++;
2720
+
2721
+ return s->ret;
2722
+}
2723
+
2724
static void pprint_rename(struct strbuf *name, const char *a, const char *b)
2725
{
2726
const char *old_name = a;
3824
const char *xfrm_msg,
3825
int must_show_header,
3826
struct diff_options *o,
3599
- int complete_rewrite)
3827
+ int complete_rewrite,
3828
+ const struct range_set *line_ranges)
3829
{
3830
mmfile_t mf1, mf2;
3831
const char *lbl[2];
4066
*/
4067
xdi_diff_outf(&mf1, &mf2, NULL, quick_consume,
4068
&ecbdata, &xpp, &xecfg);
4069
+ } else if (line_ranges) {
4070
+ struct line_range_callback lr_state;
4071
+ unsigned int i;
4072
+ long max_span = 0;
4073
+
4074
+ memset(&lr_state, 0, sizeof(lr_state));
4075
+ lr_state.orig_line_fn = fn_out_consume;
4076
+ lr_state.orig_cb_data = &ecbdata;
4077
+ lr_state.ranges = line_ranges;
4078
+ strbuf_init(&lr_state.rhunk, 0);
4079
+ strbuf_init(&lr_state.pending_rm, 0);
4080
+
4081
+ /*
4082
+ * Inflate ctxlen so that all changes within
4083
+ * any single range are merged into one xdiff
4084
+ * hunk and the inter-change context is emitted.
4085
+ * The callback clips back to range boundaries.
4086
+ *
4087
+ * The optimal ctxlen depends on where changes
4088
+ * fall within the range, which is only known
4089
+ * after xdiff runs; the max range span is the
4090
+ * upper bound that guarantees correctness in a
4091
+ * single pass.
4092
+ */
4093
+ for (i = 0; i < line_ranges->nr; i++) {
4094
+ long span = line_ranges->ranges[i].end -
4095
+ line_ranges->ranges[i].start;
4096
+ if (span > max_span)
4097
+ max_span = span;
4098
+ }
4099
+ if (max_span > xecfg.ctxlen)
4100
+ xecfg.ctxlen = max_span;
4101
+
4102
+ if (xdi_diff_outf(&mf1, &mf2,
4103
+ line_range_hunk_fn,
4104
+ line_range_line_fn,
4105
+ &lr_state, &xpp, &xecfg))
4106
+ die("unable to generate diff for %s",
4107
+ one->path);
4108
+
4109
+ flush_rhunk(&lr_state);
4110
+ if (lr_state.ret)
4111
+ die("unable to generate diff for %s",
4112
+ one->path);
4113
+ strbuf_release(&lr_state.rhunk);
4114
+ strbuf_release(&lr_state.pending_rm);
4115
} else if (xdi_diff_outf(&mf1, &mf2, NULL, fn_out_consume,
4116
&ecbdata, &xpp, &xecfg))
4117
die("unable to generate diff for %s", one->path);
4953
4954
builtin_diff(name, other ? other : name,
4955
one, two, xfrm_msg, must_show_header,
4681
- o, complete_rewrite);
4956
+ o, complete_rewrite, p->line_ranges);
4957
if (p->status == DIFF_STATUS_COPIED ||
4958
p->status == DIFF_STATUS_RENAMED)
4959
o->found_changes = 1;