Skip to content

Commit 12108c4

Browse files
committed
diff: simplify line-range filter by classifying removals immediately
The line-range filter buffered '-' (removal) lines in a pending_rm strbuf, deferring their classification until a '+' or ' ' line arrived to reveal the post-image position. This buffering is unnecessary. Removal lines are pre-image content that occupies no post-image space, so they do not advance lno_post. Within a hunk, xdiff emits removals before additions for each change, so a '-' line always arrives while lno_post is at the same position that the following '+' or ' ' line will occupy. Each line can therefore be classified against the tracked ranges as it arrives, without waiting for a non-removal line to confirm the position. The buffering also had a bug: flush_rhunk() unconditionally drained the pending buffer when the range hunk was active, even if lno_post had advanced past the tracked range. This caused deletions immediately after the tracked function to be incorrectly included in patch output. Remove the pending_rm buffer and classify '-' lines using the same in_range check applied to '+' and ' ' lines. This simplifies the filter, removes three struct fields and a helper function, and makes the flush_rhunk() bug impossible by construction. Signed-off-by: Michael Montalbo <mmontalbo@gmail.com>
1 parent 9f223ef commit 12108c4

2 files changed

Lines changed: 157 additions & 77 deletions

File tree

diff.c

Lines changed: 33 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -616,11 +616,6 @@ struct emit_callback {
616616
* the requested ranges. Contiguous in-range lines are collected into
617617
* range hunks and flushed with a synthetic @@ header so that
618618
* 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).
624619
*/
625620
struct line_range_callback {
626621
xdiff_emit_line_fn orig_line_fn;
@@ -646,11 +641,6 @@ struct line_range_callback {
646641
int rhunk_active;
647642
int rhunk_has_changes; /* any '+' or '-' lines? */
648643

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-
654644
int ret; /* latched error from orig_line_fn */
655645
};
656646

@@ -2539,12 +2529,6 @@ static int quick_consume(void *priv, char *line UNUSED, unsigned long len UNUSED
25392529
return 1;
25402530
}
25412531

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-
25482532
static void flush_rhunk(struct line_range_callback *s)
25492533
{
25502534
struct strbuf hdr = STRBUF_INIT;
@@ -2553,14 +2537,6 @@ static void flush_rhunk(struct line_range_callback *s)
25532537
if (!s->rhunk_active || s->ret)
25542538
return;
25552539

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-
25642540
/*
25652541
* Suppress context-only hunks: they contain no actual changes
25662542
* and would just be noise. This can happen when the inflated
@@ -2615,11 +2591,6 @@ static void line_range_hunk_fn(void *data,
26152591
* When count > 0, begin is 1-based. When count == 0, begin is
26162592
* adjusted down by 1 by xdl_emit_hunk_hdr(), but no lines of
26172593
* 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.
26232594
*/
26242595
s->lno_post = new_begin;
26252596
s->lno_pre = old_begin;
@@ -2635,88 +2606,75 @@ static void line_range_hunk_fn(void *data,
26352606
static int line_range_line_fn(void *priv, char *line, unsigned long len)
26362607
{
26372608
struct line_range_callback *s = priv;
2638-
const struct range *cur;
2639-
long lno_0, cur_pre;
2609+
long lno_0;
2610+
int in_range;
26402611

26412612
if (s->ret)
26422613
return s->ret;
26432614

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-
26532615
if (line[0] == '\\') {
2654-
if (s->pending_rm_count)
2655-
strbuf_add(&s->pending_rm, line, len);
2656-
else if (s->rhunk_active)
2616+
if (s->rhunk_active)
26572617
strbuf_add(&s->rhunk, line, len);
2658-
/* otherwise outside tracked range; drop silently */
26592618
return s->ret;
26602619
}
26612620

2662-
if (line[0] != '+' && line[0] != ' ')
2621+
if (line[0] != '+' && line[0] != ' ' && line[0] != '-')
26632622
BUG("unexpected diff line type '%c'", line[0]);
26642623

2624+
/*
2625+
* Compute post-image position. '+' and ' ' lines advance
2626+
* lno_post; '-' lines do not (they occupy no post-image space).
2627+
*/
26652628
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++;
2629+
if (line[0] != '-')
2630+
s->lno_post++;
26702631

2671-
/* Advance past ranges we've passed */
2632+
/*
2633+
* Advance past any ranges we've moved beyond. Emit the
2634+
* accumulated range hunk for the range we're leaving.
2635+
*/
26722636
while (s->cur_range < s->ranges->nr &&
26732637
lno_0 >= s->ranges->ranges[s->cur_range].end) {
26742638
if (s->rhunk_active)
26752639
flush_rhunk(s);
2676-
discard_pending_rm(s);
26772640
s->cur_range++;
26782641
}
26792642

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];
2643+
in_range = s->cur_range < s->ranges->nr &&
2644+
lno_0 >= s->ranges->ranges[s->cur_range].start &&
2645+
lno_0 < s->ranges->ranges[s->cur_range].end;
26872646

2688-
/* Before current range */
2689-
if (lno_0 < cur->start) {
2690-
discard_pending_rm(s);
2647+
if (!in_range) {
2648+
if (line[0] != '+')
2649+
s->lno_pre++;
26912650
return s->ret;
26922651
}
26932652

2694-
/* In range so start a new range hunk if needed */
2653+
/* Start a new range hunk if this is the first in-range line */
26952654
if (!s->rhunk_active) {
26962655
s->rhunk_active = 1;
26972656
s->rhunk_has_changes = 0;
26982657
s->rhunk_new_begin = lno_0 + 1;
2699-
s->rhunk_old_begin = s->pending_rm_count
2700-
? s->pending_rm_pre_begin : cur_pre;
2658+
s->rhunk_old_begin = s->lno_pre;
27012659
s->rhunk_old_count = 0;
27022660
s->rhunk_new_count = 0;
27032661
strbuf_reset(&s->rhunk);
27042662
}
27052663

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-
2664+
/* Append line to the range hunk */
27142665
strbuf_add(&s->rhunk, line, len);
2715-
s->rhunk_new_count++;
2716-
if (line[0] == '+')
2666+
if (line[0] == '-') {
2667+
s->rhunk_old_count++;
27172668
s->rhunk_has_changes = 1;
2718-
else
2669+
s->lno_pre++;
2670+
} else if (line[0] == '+') {
2671+
s->rhunk_new_count++;
2672+
s->rhunk_has_changes = 1;
2673+
} else {
27192674
s->rhunk_old_count++;
2675+
s->rhunk_new_count++;
2676+
s->lno_pre++;
2677+
}
27202678

27212679
return s->ret;
27222680
}
@@ -4072,7 +4030,6 @@ static void builtin_diff(const char *name_a,
40724030
lr_state.orig_cb_data = &ecbdata;
40734031
lr_state.ranges = line_ranges;
40744032
strbuf_init(&lr_state.rhunk, 0);
4075-
strbuf_init(&lr_state.pending_rm, 0);
40764033

40774034
/*
40784035
* Inflate ctxlen so that all changes within
@@ -4107,7 +4064,6 @@ static void builtin_diff(const char *name_a,
41074064
die("unable to generate diff for %s",
41084065
one->path);
41094066
strbuf_release(&lr_state.rhunk);
4110-
strbuf_release(&lr_state.pending_rm);
41114067
} else if (xdi_diff_outf(&mf1, &mf2, NULL, fn_out_consume,
41124068
&ecbdata, &xpp, &xecfg))
41134069
die("unable to generate diff for %s", one->path);

t/t4211-line-log.sh

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -711,4 +711,128 @@ test_expect_success '-L with -G filters to diff-text matches' '
711711
grep "F2 + 2" actual
712712
'
713713

714+
test_expect_success 'setup for trailing deletion test' '
715+
git checkout --orphan trailing-del &&
716+
git reset --hard &&
717+
cat >file.c <<-\EOF &&
718+
void tracked()
719+
{
720+
return 1;
721+
}
722+
// trailing comment
723+
EOF
724+
git add file.c &&
725+
test_tick &&
726+
git commit -m "add file with trailing comment" &&
727+
# Modify tracked() AND delete the trailing comment in
728+
# one commit, so the commit touches the tracked range
729+
# and is not filtered out by the revision walker.
730+
cat >file.c <<-\EOF &&
731+
void tracked()
732+
{
733+
return 2;
734+
}
735+
EOF
736+
git commit -a -m "modify tracked and delete trailing comment"
737+
'
738+
739+
test_expect_success '-L does not include deletions past end of tracked range' '
740+
git log -L:tracked:file.c --format= -1 -p >actual &&
741+
# The trailing comment deletion is outside the tracked
742+
# range and should not appear in the patch output.
743+
grep "return 2" actual &&
744+
! grep "trailing comment" actual
745+
'
746+
747+
test_expect_success '-L includes leading deletions resolved by in-range line' '
748+
git checkout --orphan leading-del &&
749+
git reset --hard &&
750+
cat >file.c <<-\EOF &&
751+
// leading comment
752+
void tracked()
753+
{
754+
return 1;
755+
}
756+
EOF
757+
git add file.c &&
758+
test_tick &&
759+
git commit -m "add file with leading comment" &&
760+
cat >file.c <<-\EOF &&
761+
void tracked()
762+
{
763+
return 2;
764+
}
765+
EOF
766+
git commit -a -m "modify tracked and delete leading comment" &&
767+
git log -L:tracked:file.c --format= -1 -p >actual &&
768+
# The leading comment deletion is resolved by the next
769+
# non-removal line (void tracked), which is in range.
770+
# Pending removals are attributed to the range they precede.
771+
grep "return 2" actual &&
772+
grep "leading comment" actual
773+
'
774+
775+
test_expect_success 'setup for line-range filter edge cases' '
776+
git checkout --orphan filter-edge &&
777+
git reset --hard &&
778+
cat >file.c <<-\EOF &&
779+
void before()
780+
{
781+
return 0;
782+
}
783+
784+
void tracked()
785+
{
786+
int a = 1;
787+
int b = 2;
788+
int c = 3;
789+
return a + b + c;
790+
}
791+
792+
void after()
793+
{
794+
return 9;
795+
}
796+
EOF
797+
git add file.c &&
798+
test_tick &&
799+
git commit -m "initial"
800+
'
801+
802+
test_expect_success '-L change at exact first line of range' '
803+
git checkout filter-edge &&
804+
# Change the function signature (first line of range)
805+
sed "s/void tracked/int tracked/" file.c >tmp &&
806+
mv tmp file.c &&
807+
git commit -a -m "change first line" &&
808+
git log -L:tracked:file.c -p --format=%s -1 >actual &&
809+
grep "change first line" actual &&
810+
grep "+int tracked" actual &&
811+
grep "\\-void tracked" actual
812+
'
813+
814+
test_expect_success '-L change at exact last line of range' '
815+
git checkout filter-edge &&
816+
git reset --hard HEAD~1 &&
817+
# Change the closing brace line (last line of range)
818+
sed "s/^}$/} \/\/ end tracked/" file.c >tmp &&
819+
mv tmp file.c &&
820+
git commit -a -m "change last line" &&
821+
git log -L:tracked:file.c -p --format=%s -1 >actual &&
822+
grep "change last line" actual &&
823+
grep "end tracked" actual
824+
'
825+
826+
test_expect_success '-L pure deletion in range (no additions)' '
827+
git checkout filter-edge &&
828+
git reset --hard HEAD~1 &&
829+
# Delete a line inside tracked() without adding anything
830+
sed "/int c/d" file.c >tmp &&
831+
mv tmp file.c &&
832+
git commit -a -m "pure deletion" &&
833+
git log -L:tracked:file.c -p --format=%s -1 >actual &&
834+
grep "pure deletion" actual &&
835+
grep "\\-.*int c" actual
836+
'
837+
714838
test_done

0 commit comments

Comments
 (0)