fix(difftool): fully resolve symlinks when comparing paths #36147

Fixes issue on mac where it was constantly reloading buffers as paths
were not being normalized and resolved correctly (in relation to buffer
name).

Quickfix entry:
/var/folders/pt/2s7dzyw12v36tsslrghfgpkr0000gn/T/git-difftool.m95lj8/right/app.vue

Buffer name:
/private/var/folders/pt/2s7dzyw12v36tsslrghfgpkr0000gn/T/git-difftool.m95lj8/right/app.vue

/var was synlinked to /private/var and this was not being properly
handled.

Also added lazy redraw to avoid too many redraws when this happens in
future and added test for symlink handling.

Signed-off-by: Tomas Slusny <slusnucky@gmail.com>
This commit is contained in:
Tomas Slusny
2025-10-12 20:25:14 +02:00
committed by GitHub
parent 638d44ded8
commit c6113da5a9
3 changed files with 52 additions and 10 deletions

View File

@@ -5,12 +5,20 @@ local M = {}
--- @param file string --- @param file string
--- @return number buffer number of the edited buffer --- @return number buffer number of the edited buffer
M.edit_in = function(winnr, file) M.edit_in = function(winnr, file)
local function resolved_path(path)
if not path or path == '' then
return ''
end
return vim.fn.resolve(vim.fs.abspath(path))
end
return vim.api.nvim_win_call(winnr, function() return vim.api.nvim_win_call(winnr, function()
local current = vim.fs.abspath(vim.api.nvim_buf_get_name(vim.api.nvim_win_get_buf(winnr))) local current_buf = vim.api.nvim_win_get_buf(winnr)
local current = resolved_path(vim.api.nvim_buf_get_name(current_buf))
-- Check if the current buffer is already the target file -- Check if the current buffer is already the target file
if current == (file and vim.fs.abspath(file) or '') then if current == resolved_path(file) then
return vim.api.nvim_get_current_buf() return current_buf
end end
-- Read the file into the buffer -- Read the file into the buffer

View File

@@ -140,14 +140,16 @@ local function diff_dirs_diffr(left_dir, right_dir, opt)
elseif right_exists then elseif right_exists then
status = 'A' status = 'A'
end end
local left = vim.fn.resolve(vim.fs.abspath(modified_left))
local right = vim.fn.resolve(vim.fs.abspath(modified_right))
table.insert(qf_entries, { table.insert(qf_entries, {
filename = modified_right, filename = right,
text = status, text = status,
user_data = { user_data = {
diff = true, diff = true,
rel = vim.fs.relpath(left_dir, modified_left), rel = vim.fs.relpath(left_dir, modified_left),
left = vim.fs.abspath(modified_left), left = left,
right = vim.fs.abspath(modified_right), right = right,
}, },
}) })
end end
@@ -426,7 +428,7 @@ function M.open(left, right, opt)
layout.group = vim.api.nvim_create_augroup('nvim.difftool.events', { clear = true }) layout.group = vim.api.nvim_create_augroup('nvim.difftool.events', { clear = true })
local hl_id = vim.api.nvim_create_namespace('nvim.difftool.hl') local hl_id = vim.api.nvim_create_namespace('nvim.difftool.hl')
local function get_diff_entry() local function get_diff_entry(bufnr)
--- @type {idx: number, items: table[], size: number} --- @type {idx: number, items: table[], size: number}
local qf_info = vim.fn.getqflist({ idx = 0, items = 1, size = 1 }) local qf_info = vim.fn.getqflist({ idx = 0, items = 1, size = 1 })
if qf_info.size == 0 then if qf_info.size == 0 then
@@ -434,7 +436,12 @@ function M.open(left, right, opt)
end end
local entry = qf_info.items[qf_info.idx] local entry = qf_info.items[qf_info.idx]
if not entry or not entry.user_data or not entry.user_data.diff then if
not entry
or not entry.user_data
or not entry.user_data.diff
or (bufnr and entry.bufnr ~= bufnr)
then
return nil return nil
end end
@@ -466,14 +473,16 @@ function M.open(left, right, opt)
vim.api.nvim_create_autocmd('BufWinEnter', { vim.api.nvim_create_autocmd('BufWinEnter', {
group = layout.group, group = layout.group,
pattern = '*', pattern = '*',
callback = function() callback = function(args)
local entry = get_diff_entry() local entry = get_diff_entry(args.buf)
if not entry then if not entry then
return return
end end
vim.w.lazyredraw = true
vim.schedule(function() vim.schedule(function()
diff_files(entry.user_data.left, entry.user_data.right, true) diff_files(entry.user_data.left, entry.user_data.right, true)
vim.w.lazyredraw = false
end) end)
end, end,
}) })

View File

@@ -61,6 +61,31 @@ describe('nvim.difftool', function()
) )
end) end)
it('handles symlinks', function()
-- Create a symlink in right dir pointing to file2.txt in left dir
local symlink_path = vim.fs.joinpath(testdir_right, 'file2.txt')
local target_path = vim.fs.joinpath('..', testdir_left, 'file2.txt')
assert(vim.uv.fs_symlink(target_path, symlink_path) == true)
finally(function()
os.remove(symlink_path)
end)
assert(fn.getftype(symlink_path) == 'link')
-- Run difftool
command(('DiffTool %s %s'):format(testdir_left, testdir_right))
local qflist = fn.getqflist()
local entries = {}
for _, item in ipairs(qflist) do
table.insert(entries, { text = item.text, rel = item.user_data and item.user_data.rel })
end
-- file2.txt should not be reported as added or deleted anymore
eq({
{ text = 'M', rel = 'file1.txt' },
{ text = 'A', rel = 'file3.txt' },
}, entries)
end)
it('has autocmds when diff window is opened', function() it('has autocmds when diff window is opened', function()
command(('DiffTool %s %s'):format(testdir_left, testdir_right)) command(('DiffTool %s %s'):format(testdir_left, testdir_right))
local autocmds = fn.nvim_get_autocmds({ group = 'nvim.difftool.events' }) local autocmds = fn.nvim_get_autocmds({ group = 'nvim.difftool.events' })