## Problem Pressing `du` on a `UU` (unmerged) file in the fugitive status buffer had no effect. There was no way to see a proper ours-vs-theirs diff with syntax highlighting and intra-line changes, or to resolve conflicts from within a unified diff view. Additionally, pressing `du` on a section header containing only unmerged files showed "no changes in section" because `git diff` produces combined (`diff --cc`) output for unmerged files, which was stripped entirely. ## Solution Fetch `:2:` (ours) and `:3:` (theirs) from the git index and generate a standard unified diff. The existing highlight pipeline (treesitter + intra-line) applies automatically. Resolution keymaps (`doo`/`dot`/`dob`/`don`) on hunks in the diff view write changes back to the working file's conflict markers. Navigation (`]x`/`[x`) jumps between unresolved conflict hunks. For section diffs, combined diff entries are now replaced with generated ours-vs-theirs unified diffs instead of being stripped. Works for merge, cherry-pick, and rebase conflicts — git populates `:2:`/`:3:` the same way for all three. Closes #61
170 lines
4.9 KiB
Lua
170 lines
4.9 KiB
Lua
require('spec.helpers')
|
|
|
|
local commands = require('diffs.commands')
|
|
|
|
describe('commands', function()
|
|
describe('setup', function()
|
|
it('registers Gdiff, Gvdiff, and Ghdiff commands', function()
|
|
commands.setup()
|
|
local cmds = vim.api.nvim_get_commands({})
|
|
assert.is_not_nil(cmds.Gdiff)
|
|
assert.is_not_nil(cmds.Gvdiff)
|
|
assert.is_not_nil(cmds.Ghdiff)
|
|
end)
|
|
end)
|
|
|
|
describe('unified diff generation', function()
|
|
local old_lines = { 'local M = {}', 'return M' }
|
|
local new_lines = { 'local M = {}', 'local x = 1', 'return M' }
|
|
local diff_fn = vim.text and vim.text.diff or vim.diff
|
|
|
|
it('generates valid unified diff', function()
|
|
local old_content = table.concat(old_lines, '\n')
|
|
local new_content = table.concat(new_lines, '\n')
|
|
local diff_output = diff_fn(old_content, new_content, {
|
|
result_type = 'unified',
|
|
ctxlen = 3,
|
|
})
|
|
assert.is_not_nil(diff_output)
|
|
assert.is_true(diff_output:find('@@ ') ~= nil)
|
|
assert.is_true(diff_output:find('+local x = 1') ~= nil)
|
|
end)
|
|
|
|
it('returns empty for identical content', function()
|
|
local content = table.concat(old_lines, '\n')
|
|
local diff_output = diff_fn(content, content, {
|
|
result_type = 'unified',
|
|
ctxlen = 3,
|
|
})
|
|
assert.are.equal('', diff_output)
|
|
end)
|
|
end)
|
|
|
|
describe('filter_combined_diffs', function()
|
|
it('strips diff --cc entries entirely', function()
|
|
local lines = {
|
|
'diff --cc main.lua',
|
|
'index d13ab94,b113aee..0000000',
|
|
'--- a/main.lua',
|
|
'+++ b/main.lua',
|
|
'@@@ -1,7 -1,7 +1,11 @@@',
|
|
' local M = {}',
|
|
'++<<<<<<< HEAD',
|
|
' + return 1',
|
|
'++=======',
|
|
'+ return 2',
|
|
'++>>>>>>> theirs',
|
|
' end',
|
|
}
|
|
local result = commands.filter_combined_diffs(lines)
|
|
assert.are.equal(0, #result)
|
|
end)
|
|
|
|
it('preserves diff --git entries', function()
|
|
local lines = {
|
|
'diff --git a/file.lua b/file.lua',
|
|
'--- a/file.lua',
|
|
'+++ b/file.lua',
|
|
'@@ -1,3 +1,3 @@',
|
|
' local M = {}',
|
|
'-local x = 1',
|
|
'+local x = 2',
|
|
' return M',
|
|
}
|
|
local result = commands.filter_combined_diffs(lines)
|
|
assert.are.equal(8, #result)
|
|
assert.are.same(lines, result)
|
|
end)
|
|
|
|
it('strips combined but keeps unified in mixed output', function()
|
|
local lines = {
|
|
'diff --cc conflict.lua',
|
|
'index aaa,bbb..000',
|
|
'@@@ -1,1 -1,1 +1,5 @@@',
|
|
'++<<<<<<< HEAD',
|
|
'diff --git a/clean.lua b/clean.lua',
|
|
'--- a/clean.lua',
|
|
'+++ b/clean.lua',
|
|
'@@ -1,1 +1,1 @@',
|
|
'-old',
|
|
'+new',
|
|
}
|
|
local result = commands.filter_combined_diffs(lines)
|
|
assert.are.equal(6, #result)
|
|
assert.are.equal('diff --git a/clean.lua b/clean.lua', result[1])
|
|
assert.are.equal('+new', result[6])
|
|
end)
|
|
|
|
it('returns empty for empty input', function()
|
|
local result = commands.filter_combined_diffs({})
|
|
assert.are.equal(0, #result)
|
|
end)
|
|
|
|
it('returns empty when all entries are combined', function()
|
|
local lines = {
|
|
'diff --cc a.lua',
|
|
'some content',
|
|
'diff --cc b.lua',
|
|
'more content',
|
|
}
|
|
local result = commands.filter_combined_diffs(lines)
|
|
assert.are.equal(0, #result)
|
|
end)
|
|
end)
|
|
|
|
describe('find_hunk_line', function()
|
|
it('finds matching @@ header and returns target line', function()
|
|
local diff_lines = {
|
|
'diff --git a/file.lua b/file.lua',
|
|
'--- a/file.lua',
|
|
'+++ b/file.lua',
|
|
'@@ -1,3 +1,4 @@',
|
|
' local M = {}',
|
|
'+local new = true',
|
|
' return M',
|
|
}
|
|
local hunk_position = {
|
|
hunk_header = '@@ -1,3 +1,4 @@',
|
|
offset = 2,
|
|
}
|
|
local target_line = commands.find_hunk_line(diff_lines, hunk_position)
|
|
assert.equals(6, target_line)
|
|
end)
|
|
|
|
it('returns nil when hunk header not found', function()
|
|
local diff_lines = {
|
|
'diff --git a/file.lua b/file.lua',
|
|
'@@ -1,3 +1,4 @@',
|
|
' local M = {}',
|
|
}
|
|
local hunk_position = {
|
|
hunk_header = '@@ -99,3 +99,4 @@',
|
|
offset = 1,
|
|
}
|
|
local target_line = commands.find_hunk_line(diff_lines, hunk_position)
|
|
assert.is_nil(target_line)
|
|
end)
|
|
|
|
it('handles multiple hunks and finds correct one', function()
|
|
local diff_lines = {
|
|
'diff --git a/file.lua b/file.lua',
|
|
'--- a/file.lua',
|
|
'+++ b/file.lua',
|
|
'@@ -1,3 +1,4 @@',
|
|
' local M = {}',
|
|
'+local x = 1',
|
|
' ',
|
|
'@@ -10,3 +11,4 @@',
|
|
' function M.foo()',
|
|
'+ print("hello")',
|
|
' end',
|
|
}
|
|
local hunk_position = {
|
|
hunk_header = '@@ -10,3 +11,4 @@',
|
|
offset = 2,
|
|
}
|
|
local target_line = commands.find_hunk_line(diff_lines, hunk_position)
|
|
assert.equals(10, target_line)
|
|
end)
|
|
end)
|
|
end)
|