feat(git): replace vim-fugitive with custom git module
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
local diff = require("git.diff")
|
||||
local log = require("log")
|
||||
local repo = require("git.repo")
|
||||
|
||||
local M = {}
|
||||
|
||||
---@class ow.Git.DiffSection
|
||||
---@field pre_path string path on the parent side (`a/...`)
|
||||
---@field post_path string path on the current side (`b/...`)
|
||||
---@field pre_blob string?
|
||||
---@field post_blob string?
|
||||
|
||||
---@class ow.Git.ShowContext
|
||||
---@field worktree string
|
||||
---@field ref string resolved commit SHA of the gitobject buffer
|
||||
---@field parent_ref string? resolved parent commit SHA, nil for root commits
|
||||
|
||||
---@return ow.Git.ShowContext?
|
||||
local function context()
|
||||
local worktree = vim.b.git_worktree
|
||||
local ref = vim.b.git_ref
|
||||
if not worktree or not ref then
|
||||
return nil
|
||||
end
|
||||
return { worktree = worktree, ref = ref, parent_ref = vim.b.git_parent_ref }
|
||||
end
|
||||
|
||||
---Walk upward from the cursor to the enclosing `diff --git` line and parse
|
||||
---the section's pre/post paths plus the pre/post blob SHAs from the `index`
|
||||
---line.
|
||||
---@param cursor_lnum integer 1-indexed
|
||||
---@return ow.Git.DiffSection?
|
||||
local function diff_section(cursor_lnum)
|
||||
local lines = vim.api.nvim_buf_get_lines(0, 0, cursor_lnum, false)
|
||||
local diff_lnum, diff_line
|
||||
for i = #lines, 1, -1 do
|
||||
if lines[i]:match("^diff %-%-git ") then
|
||||
diff_lnum = i
|
||||
diff_line = lines[i]
|
||||
break
|
||||
end
|
||||
end
|
||||
if not diff_lnum or not diff_line then
|
||||
return nil
|
||||
end
|
||||
local pre_path, post_path = diff_line:match("^diff %-%-git a/(.-) b/(.+)$")
|
||||
if not pre_path or not post_path then
|
||||
return nil
|
||||
end
|
||||
|
||||
local header =
|
||||
vim.api.nvim_buf_get_lines(0, diff_lnum, diff_lnum + 20, false)
|
||||
local pre_blob, post_blob
|
||||
for _, l in ipairs(header) do
|
||||
if l:sub(1, 3) == "@@ " or l:match("^diff %-%-git ") then
|
||||
break
|
||||
end
|
||||
local pre, post = l:match("^index (%x+)%.%.(%x+)")
|
||||
if pre then
|
||||
pre_blob = pre
|
||||
post_blob = post
|
||||
break
|
||||
end
|
||||
end
|
||||
return {
|
||||
pre_path = pre_path,
|
||||
post_path = post_path,
|
||||
pre_blob = pre_blob,
|
||||
post_blob = post_blob,
|
||||
}
|
||||
end
|
||||
|
||||
---@param sha string?
|
||||
---@return boolean
|
||||
local function is_zero(sha)
|
||||
return sha == nil or sha:match("^0+$") ~= nil
|
||||
end
|
||||
|
||||
---@param ref string buffer-name ref segment
|
||||
---@param path string
|
||||
---@return integer
|
||||
local function empty_buf(ref, path)
|
||||
local buf = vim.api.nvim_create_buf(false, true)
|
||||
vim.bo[buf].buftype = "nofile"
|
||||
vim.bo[buf].bufhidden = "hide"
|
||||
vim.bo[buf].swapfile = false
|
||||
vim.bo[buf].modifiable = false
|
||||
pcall(vim.api.nvim_buf_set_name, buf, "git://" .. ref .. "/" .. path)
|
||||
return buf
|
||||
end
|
||||
|
||||
---Build a buffer holding the file's content at a given blob, named after the
|
||||
---commit ref it corresponds to (so the name lines up with `git log` output
|
||||
---instead of an opaque blob hash).
|
||||
---@param worktree string
|
||||
---@param blob string?
|
||||
---@param path string
|
||||
---@param ref string the commit ref the blob represents (e.g. `<sha>` or `<sha>^`)
|
||||
---@return integer
|
||||
local function blob_buf(worktree, blob, path, ref)
|
||||
if is_zero(blob) then
|
||||
return empty_buf(ref, path)
|
||||
end
|
||||
---@cast blob string
|
||||
local buf = diff.git_show_blob(worktree, blob)
|
||||
pcall(vim.api.nvim_buf_set_name, buf, "git://" .. ref .. "/" .. path)
|
||||
local ft = vim.filetype.match({ buf = buf })
|
||||
if ft then
|
||||
vim.bo[buf].filetype = ft
|
||||
end
|
||||
return buf
|
||||
end
|
||||
|
||||
---@param worktree string
|
||||
---@param blob string?
|
||||
---@param path string
|
||||
---@param ref string
|
||||
local function show_blob(worktree, blob, path, ref)
|
||||
local buf = blob_buf(worktree, blob, path, ref)
|
||||
vim.cmd("normal! m'")
|
||||
vim.cmd("buffer " .. buf)
|
||||
end
|
||||
|
||||
---@param ctx ow.Git.ShowContext
|
||||
---@param section ow.Git.DiffSection
|
||||
local function show_diff(ctx, section)
|
||||
if not section.pre_blob or not section.post_blob then
|
||||
log.warning("no index line; cannot determine blob SHAs")
|
||||
return
|
||||
end
|
||||
local parent = ctx.parent_ref or "0"
|
||||
local left =
|
||||
blob_buf(ctx.worktree, section.pre_blob, section.pre_path, parent)
|
||||
local right =
|
||||
blob_buf(ctx.worktree, section.post_blob, section.post_path, ctx.ref)
|
||||
vim.cmd("normal! m'")
|
||||
vim.cmd("buffer " .. left)
|
||||
vim.cmd("diffthis")
|
||||
vim.cmd("rightbelow vertical sbuffer " .. right)
|
||||
vim.cmd("diffthis")
|
||||
vim.cmd("wincmd p")
|
||||
end
|
||||
|
||||
---@param worktree string
|
||||
---@param ref string
|
||||
function M.open_commit(worktree, ref)
|
||||
local result = vim.system(
|
||||
{ "git", "show", ref },
|
||||
{ cwd = worktree, text = true }
|
||||
)
|
||||
:wait()
|
||||
if result.code ~= 0 then
|
||||
log.error("git show %s failed: %s", ref, result.stderr or "")
|
||||
return
|
||||
end
|
||||
local content = result.stdout or ""
|
||||
local lines = vim.split(content, "\n", { plain = true, trimempty = false })
|
||||
if #lines > 0 and lines[#lines] == "" then
|
||||
table.remove(lines)
|
||||
end
|
||||
local sha = repo.rev_parse(worktree, ref, true) or ref
|
||||
local parent = repo.rev_parse(worktree, ref .. "^", true)
|
||||
local buf = vim.api.nvim_create_buf(false, true)
|
||||
vim.bo[buf].buftype = "nofile"
|
||||
vim.bo[buf].bufhidden = "hide"
|
||||
vim.bo[buf].swapfile = false
|
||||
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
|
||||
vim.bo[buf].modifiable = false
|
||||
vim.bo[buf].modified = false
|
||||
pcall(vim.api.nvim_buf_set_name, buf, "git://" .. sha .. "/")
|
||||
vim.b[buf].git_worktree = worktree
|
||||
vim.b[buf].git_ref = sha
|
||||
vim.b[buf].git_parent_ref = parent
|
||||
vim.bo[buf].filetype = "git"
|
||||
vim.cmd("normal! m'")
|
||||
vim.cmd("buffer " .. buf)
|
||||
end
|
||||
|
||||
---@return boolean dispatched true if the cursor was on an actionable line
|
||||
function M.open_at_cursor()
|
||||
local ctx = context()
|
||||
if not ctx then
|
||||
return false
|
||||
end
|
||||
local cursor_lnum = vim.api.nvim_win_get_cursor(0)[1]
|
||||
local section = diff_section(cursor_lnum)
|
||||
if not section then
|
||||
return false
|
||||
end
|
||||
local parent = ctx.parent_ref or "0"
|
||||
|
||||
local line = vim.api.nvim_get_current_line()
|
||||
if line:match("^diff %-%-git ") then
|
||||
show_diff(ctx, section)
|
||||
return true
|
||||
end
|
||||
if line:match("^%-%-%- ") then
|
||||
show_blob(ctx.worktree, section.pre_blob, section.pre_path, parent)
|
||||
return true
|
||||
end
|
||||
if line:match("^%+%+%+ ") then
|
||||
show_blob(ctx.worktree, section.post_blob, section.post_path, ctx.ref)
|
||||
return true
|
||||
end
|
||||
local prefix = line:sub(1, 1)
|
||||
if prefix == "+" then
|
||||
show_blob(ctx.worktree, section.post_blob, section.post_path, ctx.ref)
|
||||
return true
|
||||
elseif prefix == "-" then
|
||||
show_blob(ctx.worktree, section.pre_blob, section.pre_path, parent)
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
return M
|
||||
Reference in New Issue
Block a user