aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorzeertzjq <zeertzjq@outlook.com>2025-03-03 10:53:03 +0800
committerGitHub <noreply@github.com>2025-03-03 10:53:03 +0800
commit948179cb19c75a9e79cdf2c86c441304c5285e81 (patch)
tree606d747392d93674adb32a929aecb12c0c1c0f16
parent560b8a8ce0f89e72b73c2a625f2ff6ad923c8183 (diff)
downloadrneovim-948179cb19c75a9e79cdf2c86c441304c5285e81.tar.gz
rneovim-948179cb19c75a9e79cdf2c86c441304c5285e81.tar.bz2
rneovim-948179cb19c75a9e79cdf2c86c441304c5285e81.zip
vim-patch:9.1.1165: diff: regression with multi-file diff blocks (#32702)
Problem: Vim's diff block merging algorithm when doing a multi-file diff is buggy when two different diff hunks overlap a single existing diff block (after v9.1.0743) Solution: fix a couple bugs in this logic: 1. Fix regression from v9.1.0743 where it's not correctly expanding the 2nd overlap correctly, where it always expands without taking into account that this was always taken care of when the first overlap happened. Instead, we should only grow the 2nd overlap if it overhangs outside the existing diff block, and if we encounter a new overlapping diff block (due to overlap chaining). 2. When we expand a diff block to match the hunk size on the orig side (when handling the first overlap), we expand the same amount of lines in the new side. This is not sound if there exists a second overlap hunk that we haven't processed yet, and that hunk has different number of lines in orig/new. Fix this by doing the corresponding counter adjustment when handling 2nd/3rd/etc overlap by calculating the difference in lines between orig and new side. (Yee Cheng Chin) closes: vim/vim#16768 https://github.com/vim/vim/commit/bc08ceb75572dcac57ef5019f3d0df6e8290c0f9 Co-authored-by: Yee Cheng Chin <ychin.git@gmail.com>
-rw-r--r--src/nvim/diff.c29
-rw-r--r--test/functional/ui/diff_spec.lua82
-rw-r--r--test/old/testdir/test_diffmode.vim24
3 files changed, 132 insertions, 3 deletions
diff --git a/src/nvim/diff.c b/src/nvim/diff.c
index 4a4d2baf94..c9ca58c816 100644
--- a/src/nvim/diff.c
+++ b/src/nvim/diff.c
@@ -1622,7 +1622,23 @@ static void process_hunk(diff_T **dpp, diff_T **dprevp, int idx_orig, int idx_ne
dp->df_count[idx_new] = (linenr_T)hunk->count_new - off;
} else {
// second overlap of new block with existing block
- dp->df_count[idx_new] += (linenr_T)hunk->count_new;
+
+ // if this hunk has different orig/new counts, adjust
+ // the diff block size first. When we handled the first hunk we
+ // would have expanded it to fit, without knowing that this
+ // hunk exists
+ int orig_size_in_dp = MIN(hunk->count_orig,
+ dp->df_lnum[idx_orig] +
+ dp->df_count[idx_orig] - hunk->lnum_orig);
+ int size_diff = hunk->count_new - orig_size_in_dp;
+ dp->df_count[idx_new] += size_diff;
+
+ // grow existing block to include the overlap completely
+ off = hunk->lnum_new + hunk->count_new
+ - (dp->df_lnum[idx_new] + dp->df_count[idx_new]);
+ if (off > 0) {
+ dp->df_count[idx_new] += off;
+ }
if ((dp->df_lnum[idx_new] + dp->df_count[idx_new] - 1)
> curtab->tp_diffbuf[idx_new]->b_ml.ml_line_count) {
dp->df_count[idx_new] = curtab->tp_diffbuf[idx_new]->b_ml.ml_line_count
@@ -1636,8 +1652,15 @@ static void process_hunk(diff_T **dpp, diff_T **dprevp, int idx_orig, int idx_ne
- (dpl->df_lnum[idx_orig] + dpl->df_count[idx_orig]);
if (off < 0) {
- // new change ends in existing block, adjust the end
- dp->df_count[idx_new] += -off;
+ // new change ends in existing block, adjust the end. We only
+ // need to do this once per block or we will over-adjust.
+ if (*notsetp || dp != dpl) {
+ // adjusting by 'off' here is only correct if
+ // there is not another hunk in this block. we
+ // adjust for this when we encounter a second
+ // overlap later.
+ dp->df_count[idx_new] += -off;
+ }
if ((dp->df_lnum[idx_new] + dp->df_count[idx_new] - 1)
> curtab->tp_diffbuf[idx_new]->b_ml.ml_line_count) {
dp->df_count[idx_new] = curtab->tp_diffbuf[idx_new]->b_ml.ml_line_count
diff --git a/test/functional/ui/diff_spec.lua b/test/functional/ui/diff_spec.lua
index dae373297a..e0d88771d3 100644
--- a/test/functional/ui/diff_spec.lua
+++ b/test/functional/ui/diff_spec.lua
@@ -2064,6 +2064,88 @@ it('diff mode overlapped diff blocks will be merged', function()
{2:Xdifile1 Xdifile2 }{3:Xdifile3 }|
|
]])
+
+ -- File 3 overlaps twice, 2nd overlap completely within the existing block.
+ WriteDiffFiles3('foo\na\nb\nc\nbar', 'foo\nw\nx\ny\nz\nbar', 'foo\n1\na\nb\n2\nbar')
+ screen:expect([[
+ {7: }foo │{7: }foo │{7: }^foo |
+ {7: }{27:a}{4: }│{7: }{27:w}{4: }│{7: }{27:1}{4: }|
+ {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{27:a}{4: }|
+ {7: }{27:c}{4: }│{7: }{27:y}{4: }│{7: }{27:b}{4: }|
+ {7: }{23:---------}│{7: }{27:z}{4: }│{7: }{27:2}{4: }|
+ {7: }bar │{7: }bar │{7: }bar |
+ {1:~ }│{1:~ }│{1:~ }|*12
+ {2:Xdifile1 Xdifile2 }{3:Xdifile3 }|
+ |
+ ]])
+
+ -- File 3 overlaps twice, 2nd overlap extends beyond existing block on new
+ -- side. Make sure we don't over-extend the range and hit 'bar'.
+ WriteDiffFiles3('foo\na\nb\nc\nd\nbar', 'foo\nw\nx\ny\nz\nu\nbar', 'foo\n1\na\nb\n2\nd\nbar')
+ screen:expect([[
+ {7: }foo │{7: }foo │{7: }^foo |
+ {7: }{27:a}{4: }│{7: }{27:w}{4: }│{7: }{27:1}{4: }|
+ {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{27:a}{4: }|
+ {7: }{27:c}{4: }│{7: }{27:y}{4: }│{7: }{27:b}{4: }|
+ {7: }{27:d}{4: }│{7: }{27:z}{4: }│{7: }{27:2}{4: }|
+ {7: }{23:---------}│{7: }{27:u}{4: }│{7: }{27:d}{4: }|
+ {7: }bar │{7: }bar │{7: }bar |
+ {1:~ }│{1:~ }│{1:~ }|*11
+ {2:Xdifile1 Xdifile2 }{3:Xdifile3 }|
+ |
+ ]])
+
+ -- Chained overlaps. File 3's 2nd overlap spans two diff blocks and is longer
+ -- than the 2nd one.
+ WriteDiffFiles3(
+ 'foo\na\nb\nc\nd\ne\nf\nbar',
+ 'foo\nw\nx\ny\nz\ne\nu\nbar',
+ 'foo\n1\nb\n2\n3\nd\n4\nf\nbar'
+ )
+ screen:expect([[
+ {7: }foo │{7: }foo │{7: }^foo |
+ {7: }{27:a}{4: }│{7: }{27:w}{4: }│{7: }{27:1}{4: }|
+ {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{27:b}{4: }|
+ {7: }{27:c}{4: }│{7: }{27:y}{4: }│{7: }{27:2}{4: }|
+ {7: }{27:d}{4: }│{7: }{27:z}{4: }│{7: }{27:3}{4: }|
+ {7: }{27:e}{4: }│{7: }{27:e}{4: }│{7: }{27:d}{4: }|
+ {7: }{27:f}{4: }│{7: }{27:u}{4: }│{7: }{27:4}{4: }|
+ {7: }{23:---------}│{7: }{23:---------}│{7: }{22:f }|
+ {7: }bar │{7: }bar │{7: }bar |
+ {1:~ }│{1:~ }│{1:~ }|*9
+ {2:Xdifile1 Xdifile2 }{3:Xdifile3 }|
+ |
+ ]])
+
+ -- File 3 has 2 overlaps. An add and a delete. First overlap's expansion hits
+ -- the 2nd one. Make sure we adjust the diff block to have fewer lines.
+ WriteDiffFiles3('foo\na\nb\nbar', 'foo\nx\ny\nbar', 'foo\n1\na\nbar')
+ screen:expect([[
+ {7: }foo │{7: }foo │{7: }^foo |
+ {7: }{27:a}{4: }│{7: }{27:x}{4: }│{7: }{27:1}{4: }|
+ {7: }{27:b}{4: }│{7: }{27:y}{4: }│{7: }{27:a}{4: }|
+ {7: }bar │{7: }bar │{7: }bar |
+ {1:~ }│{1:~ }│{1:~ }|*14
+ {2:Xdifile1 Xdifile2 }{3:Xdifile3 }|
+ |
+ ]])
+
+ -- File 3 has 2 overlaps. An add and another add. First overlap's expansion hits
+ -- the 2nd one. Make sure we adjust the diff block to have more lines.
+ WriteDiffFiles3('foo\na\nb\nc\nd\nbar', 'foo\nw\nx\ny\nz\nu\nbar', 'foo\n1\na\nb\n3\n4\nd\nbar')
+ screen:expect([[
+ {7: }foo │{7: }foo │{7: }^foo |
+ {7: }{27:a}{4: }│{7: }{27:w}{4: }│{7: }{27:1}{4: }|
+ {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{27:a}{4: }|
+ {7: }{27:c}{4: }│{7: }{27:y}{4: }│{7: }{27:b}{4: }|
+ {7: }{27:d}{4: }│{7: }{27:z}{4: }│{7: }{27:3}{4: }|
+ {7: }{23:---------}│{7: }{27:u}{4: }│{7: }{27:4}{4: }|
+ {7: }{23:---------}│{7: }{23:---------}│{7: }{22:d }|
+ {7: }bar │{7: }bar │{7: }bar |
+ {1:~ }│{1:~ }│{1:~ }|*10
+ {2:Xdifile1 Xdifile2 }{3:Xdifile3 }|
+ |
+ ]])
end)
-- oldtest: Test_diff_topline_noscroll()
diff --git a/test/old/testdir/test_diffmode.vim b/test/old/testdir/test_diffmode.vim
index ab64658bd0..0d20085360 100644
--- a/test/old/testdir/test_diffmode.vim
+++ b/test/old/testdir/test_diffmode.vim
@@ -2045,6 +2045,30 @@ func Test_diff_overlapped_diff_blocks_will_be_merged()
call WriteDiffFiles3(buf, ["a", "b", "c"], ["d", "e"], ["b"])
call VerifyBoth(buf, "Test_diff_overlapped_3.39", "")
+ " File 3 overlaps twice, 2nd overlap completely within the existing block.
+ call WriteDiffFiles3(buf, ["foo", "a", "b", "c", "bar"], ["foo", "w", "x", "y", "z", "bar"], ["foo", "1", "a", "b", "2", "bar"])
+ call VerifyBoth(buf, "Test_diff_overlapped_3.40", "")
+
+ " File 3 overlaps twice, 2nd overlap extends beyond existing block on new
+ " side. Make sure we don't over-extend the range and hit 'bar'.
+ call WriteDiffFiles3(buf, ["foo", "a", "b", "c", "d", "bar"], ["foo", "w", "x", "y", "z", "u", "bar"], ["foo", "1", "a", "b", "2", "d", "bar"])
+ call VerifyBoth(buf, "Test_diff_overlapped_3.41", "")
+
+ " Chained overlaps. File 3's 2nd overlap spans two diff blocks and is longer
+ " than the 2nd one.
+ call WriteDiffFiles3(buf, ["foo", "a", "b", "c", "d", "e", "f", "bar"], ["foo", "w", "x", "y", "z", "e", "u", "bar"], ["foo", "1", "b", "2", "3", "d", "4", "f", "bar"])
+ call VerifyBoth(buf, "Test_diff_overlapped_3.42", "")
+
+ " File 3 has 2 overlaps. An add and a delete. First overlap's expansion hits
+ " the 2nd one. Make sure we adjust the diff block to have fewer lines.
+ call WriteDiffFiles3(buf, ["foo", "a", "b", "bar"], ["foo", "x", "y", "bar"], ["foo", "1", "a", "bar"])
+ call VerifyBoth(buf, "Test_diff_overlapped_3.43", "")
+
+ " File 3 has 2 overlaps. An add and another add. First overlap's expansion hits
+ " the 2nd one. Make sure we adjust the diff block to have more lines.
+ call WriteDiffFiles3(buf, ["foo", "a", "b", "c", "d", "bar"], ["foo", "w", "x", "y", "z", "u", "bar"], ["foo", "1", "a", "b", "3", "4", "d", "bar"])
+ call VerifyBoth(buf, "Test_diff_overlapped_3.44", "")
+
call StopVimInTerminal(buf)
endfunc