fix(git): resolve sidebar and diff-split rendering bugs

This commit is contained in:
2026-04-28 05:35:45 +02:00
parent 461a4a8158
commit 80a486719e
2 changed files with 117 additions and 83 deletions
+72 -48
View File
@@ -59,36 +59,10 @@ local function attach_index_writer(buf, worktree, path)
})
end
---Run `git show <revspec>` asynchronously and call `callback(lines?)` on the
---main loop. `lines` is nil on failure (the error is logged).
---@param worktree string
---@param revspec string anything `git show` accepts (e.g. `HEAD:foo`, `:foo`, blob SHA)
---@param callback fun(lines: string[]?)
local function read_show_async(worktree, revspec, callback)
vim.system(
{ "git", "show", revspec },
{ cwd = worktree, text = true },
vim.schedule_wrap(function(result)
if result.code ~= 0 then
log.error(
"git show %s failed: %s",
revspec,
vim.trim(result.stderr or "")
)
callback(nil)
return
end
callback(util.split_lines(result.stdout or ""))
end)
)
end
---Internal builder: create a scratch buffer immediately and asynchronously
---fill it with the content of `git show <revspec>`. Returning the buffer
---synchronously lets callers wire up windows / `:diffthis` right away; the
---diff updates when the content arrives. The buffer starts non-modifiable
---and the index `BufWriteCmd` is only attached after a successful load, so
---a premature `:w` can't blow away the index entry with empty content.
---Internal builder: create a scratch buffer and fill it with the output of
---`git show <revspec>`. Synchronous so the buffer is ready by the time the
---caller wires up windows / `:diffthis`. An empty buffer briefly visible
---to the diff engine produces a spurious whole-file diff.
---@param worktree string
---@param revspec string
---@param is_index boolean
@@ -99,22 +73,35 @@ local function build_show_buf(worktree, revspec, is_index, index_path)
vim.bo[buf].buftype = "nofile"
vim.bo[buf].bufhidden = "wipe"
vim.bo[buf].swapfile = false
vim.bo[buf].modifiable = false
local result = vim.system(
{ "git", "show", revspec },
{ cwd = worktree, text = true }
)
:wait()
if result.code ~= 0 then
log.error(
"git show %s failed: %s",
revspec,
vim.trim(result.stderr or "")
)
else
vim.api.nvim_buf_set_lines(
buf,
0,
-1,
false,
util.split_lines(result.stdout or "")
)
end
if is_index then
vim.bo[buf].buftype = "acwrite"
attach_index_writer(buf, worktree, assert(index_path))
else
vim.bo[buf].modifiable = false
end
vim.bo[buf].modified = false
read_show_async(worktree, revspec, function(lines)
if not vim.api.nvim_buf_is_valid(buf) then
return
end
vim.bo[buf].modifiable = true
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines or {})
if is_index then
vim.bo[buf].buftype = "acwrite"
attach_index_writer(buf, worktree, assert(index_path))
else
vim.bo[buf].modifiable = false
end
vim.bo[buf].modified = false
end)
return buf
end
@@ -179,6 +166,28 @@ function M.set_buf_name_and_filetype(buf, name)
end
end
---Apply (or remove) the window-local options that `:diffthis` would
---normally set. Used by both `M.split` here and the sidebar so the two
---diff entry points behave consistently. Vim is supposed to save and
---restore these around the `'diff'` flag flip, but that round-trip is
---fragile when buffers are swapped under an already-diff window.
---@param win integer
---@param enabled boolean
function M.set_diff(win, enabled)
vim.wo[win].diff = enabled
if enabled then
vim.wo[win].foldmethod = "diff"
vim.wo[win].foldenable = true
vim.wo[win].foldlevel = 0
vim.wo[win].scrollbind = true
vim.wo[win].cursorbind = true
vim.wo[win].wrap = false
else
vim.wo[win].scrollbind = false
vim.wo[win].cursorbind = false
end
end
---@class ow.Git.SplitOpts
---@field ref string '' for index, 'HEAD' for HEAD
---@field vertical boolean
@@ -191,6 +200,10 @@ function M.split(opts)
log.warning("no file in current buffer")
return
end
if vim.bo[cur_buf].buftype ~= "" then
log.warning("cannot diff this buffer (not a worktree file)")
return
end
local _, worktree = repo.resolve(cur_path)
if not worktree then
log.warning("not in a git repository")
@@ -212,9 +225,20 @@ function M.split(opts)
split = opts.vertical and "left" or "above",
win = cur_win,
})
vim.wo[other_win].diff = true
vim.api.nvim_set_current_win(cur_win)
vim.wo[cur_win].diff = true
-- The synthetic index/HEAD buffer can't run BufRead, so its filetype
-- detection in `set_buf_name_and_filetype` only catches
-- filename-pattern matches. Mirror cur_buf's filetype, since this is
-- the same logical file at a different version. Done after the
-- window opens so any BufWinEnter / BufEnter autocmds that fire on
-- nvim_open_win can't undo it.
local cur_ft = vim.bo[cur_buf].filetype
if cur_ft ~= "" then
vim.bo[other].filetype = cur_ft
end
M.set_diff(cur_win, true)
M.set_diff(other_win, true)
end
return M