refactor(git): unify diff/object dispatch, codify naming, add :Gdiffsplit
This commit is contained in:
+79
-88
@@ -1,4 +1,5 @@
|
||||
local git = require("git")
|
||||
local commit = require("git.commit")
|
||||
local object = require("git.object")
|
||||
local repo = require("git.repo")
|
||||
local util = require("git.util")
|
||||
|
||||
@@ -8,11 +9,12 @@ local M = {}
|
||||
---@field ft string
|
||||
---@field needs_ref boolean?
|
||||
|
||||
---Subcommands whose output goes to a buffer. Subcommands with their
|
||||
---own dispatch (`commit`, `show`, `cat-file`) call `run_in_split`
|
||||
---directly with a one-off conf.
|
||||
---@type table<string, ow.Git.SplitHandler>
|
||||
local SPLIT_HANDLERS = {
|
||||
log = { ft = "git" },
|
||||
show = { ft = "git", needs_ref = true },
|
||||
["cat-file"] = { ft = "git", needs_ref = true },
|
||||
diff = { ft = "diff" },
|
||||
}
|
||||
|
||||
@@ -67,109 +69,70 @@ local function first_positional(args, start)
|
||||
end
|
||||
end
|
||||
|
||||
---Open `<ref>:<path>` in a split via the `git://` BufReadCmd loader.
|
||||
---Resolves to a sha first so the URI stays stable if the ref moves.
|
||||
---@param worktree string
|
||||
---@param user_ref string
|
||||
---@param path string
|
||||
local function show_file_in_split(worktree, user_ref, path)
|
||||
local label = repo.rev_parse(worktree, user_ref, true) or user_ref
|
||||
local uri = "git://" .. label .. ":" .. path
|
||||
local buf = vim.fn.bufadd(uri)
|
||||
vim.b[buf].git_worktree = worktree
|
||||
vim.cmd("split " .. vim.fn.fnameescape(uri))
|
||||
---Find or create the named scratch buffer and place it in a window.
|
||||
---@param name string
|
||||
---@return integer buf
|
||||
local function place_split(name)
|
||||
local buf = vim.fn.bufnr("\\V" .. name)
|
||||
if buf == -1 or not vim.api.nvim_buf_is_loaded(buf) then
|
||||
buf = util.new_scratch()
|
||||
pcall(vim.api.nvim_buf_set_name, buf, name)
|
||||
return buf
|
||||
end
|
||||
local win_id = vim.fn.bufwinid(buf)
|
||||
if win_id ~= -1 then
|
||||
vim.api.nvim_set_current_win(win_id)
|
||||
else
|
||||
vim.api.nvim_open_win(buf, true, {
|
||||
split = vim.o.splitbelow and "below" or "above",
|
||||
})
|
||||
end
|
||||
return buf
|
||||
end
|
||||
|
||||
---Run `git <args>` async. On success, drop the output into a named
|
||||
---scratch split (creating or reusing as needed). On failure, `util.exec`
|
||||
---notifies and the split is never opened, so a bad ref doesn't leave a
|
||||
---stray buffer behind.
|
||||
---@param worktree string
|
||||
---@param args string[]
|
||||
---@param conf ow.Git.SplitHandler
|
||||
local function run_in_split(worktree, args, conf)
|
||||
-- `<ref>:<path>` is a file lookup; the URI must carry the path so
|
||||
-- filetype detection has something to match against.
|
||||
if args[1] == "show" then
|
||||
local arg = first_positional(args, 2)
|
||||
if arg then
|
||||
local ref, path = arg:match("^(.-):(.+)$")
|
||||
if ref then
|
||||
---@cast path -nil
|
||||
show_file_in_split(worktree, ref, path)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- `cat-file -p <sha>` routes to the gitobject viewer so commits get
|
||||
-- the full message + diff view and other types render via cat-file.
|
||||
-- Other modes (-t, -s, -e) fall through to the generic dump.
|
||||
if args[1] == "cat-file" and vim.list_contains(args, "-p") then
|
||||
local ref = first_positional(args, 2)
|
||||
if ref then
|
||||
require("git.object").open_object(worktree, ref)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local name = "[git " .. table.concat(args, " ") .. "]"
|
||||
local buf = vim.fn.bufnr("\\V" .. name)
|
||||
if buf == -1 or not vim.api.nvim_buf_is_loaded(buf) then
|
||||
buf = git.new_scratch()
|
||||
pcall(vim.api.nvim_buf_set_name, buf, name)
|
||||
else
|
||||
local win_id = vim.fn.bufwinid(buf)
|
||||
if win_id ~= -1 then
|
||||
vim.api.nvim_set_current_win(win_id)
|
||||
else
|
||||
vim.api.nvim_open_win(buf, true, {
|
||||
split = vim.o.splitbelow and "below" or "above",
|
||||
})
|
||||
end
|
||||
vim.bo[buf].modifiable = true
|
||||
vim.api.nvim_buf_set_lines(buf, 0, -1, false, {})
|
||||
end
|
||||
|
||||
vim.b[buf].git_worktree = worktree
|
||||
vim.b[buf].git_ref = nil
|
||||
vim.b[buf].git_parent_ref = nil
|
||||
if conf.needs_ref then
|
||||
local user_ref = first_positional(args, 2) or "HEAD"
|
||||
local sha = repo.rev_parse(worktree, user_ref, true)
|
||||
if sha then
|
||||
vim.b[buf].git_ref = sha
|
||||
vim.b[buf].git_parent_ref =
|
||||
repo.rev_parse(worktree, user_ref .. "^", true)
|
||||
end
|
||||
end
|
||||
vim.bo[buf].filetype = conf.ft
|
||||
|
||||
local cmd = { "git" }
|
||||
vim.list_extend(cmd, args)
|
||||
vim.system(
|
||||
cmd,
|
||||
{ cwd = worktree, text = true },
|
||||
vim.schedule_wrap(function(obj)
|
||||
if not vim.api.nvim_buf_is_valid(buf) then
|
||||
util.exec(cmd, {
|
||||
cwd = worktree,
|
||||
on_done = function(stdout)
|
||||
if not stdout then
|
||||
return
|
||||
end
|
||||
local content = (obj.stdout or "") .. (obj.stderr or "")
|
||||
local name = "[git " .. table.concat(args, " ") .. "]"
|
||||
local buf = place_split(name)
|
||||
vim.b[buf].git_worktree = worktree
|
||||
vim.b[buf].git_ref = nil
|
||||
vim.b[buf].git_parent_ref = nil
|
||||
if conf.needs_ref then
|
||||
local user_ref = first_positional(args, 2) or "HEAD"
|
||||
local sha = repo.rev_parse(worktree, user_ref, true)
|
||||
if sha then
|
||||
vim.b[buf].git_ref = sha
|
||||
vim.b[buf].git_parent_ref =
|
||||
repo.rev_parse(worktree, user_ref .. "^", true)
|
||||
end
|
||||
end
|
||||
vim.bo[buf].filetype = conf.ft
|
||||
vim.bo[buf].modifiable = true
|
||||
vim.api.nvim_buf_set_lines(
|
||||
buf,
|
||||
0,
|
||||
-1,
|
||||
false,
|
||||
util.split_lines(content)
|
||||
util.split_lines(stdout)
|
||||
)
|
||||
vim.bo[buf].modifiable = false
|
||||
vim.bo[buf].modified = false
|
||||
if obj.code ~= 0 then
|
||||
util.error(
|
||||
"git %s failed: %s",
|
||||
args[1] or "",
|
||||
vim.trim(obj.stderr or "")
|
||||
)
|
||||
end
|
||||
end)
|
||||
)
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
---@param worktree string
|
||||
@@ -244,7 +207,35 @@ function M.run(args)
|
||||
|
||||
local sub = args[1]
|
||||
if sub == "commit" and not has_message(args) then
|
||||
require("git.commit").commit({ amend = has_flag(args, "--amend") })
|
||||
commit.commit({ amend = has_flag(args, "--amend") })
|
||||
return
|
||||
end
|
||||
|
||||
-- `:G show <ref>:<path>` opens the blob via the BufReadCmd loader
|
||||
-- so the URI carries the path and filetype detection has something
|
||||
-- to match against. Other show invocations dump output to a buffer.
|
||||
if sub == "show" then
|
||||
local arg = first_positional(args, 2)
|
||||
if arg and arg:find(":", 1, true) then
|
||||
object.open_object(worktree, arg)
|
||||
return
|
||||
end
|
||||
run_in_split(worktree, args, { ft = "git", needs_ref = true })
|
||||
return
|
||||
end
|
||||
|
||||
-- `:G cat-file -p <sha>` routes to the gitobject viewer so commits
|
||||
-- get the full message + diff view and other types render via
|
||||
-- cat-file. Other modes (-t, -s, -e) dump to a buffer.
|
||||
if sub == "cat-file" then
|
||||
if vim.list_contains(args, "-p") then
|
||||
local ref = first_positional(args, 2)
|
||||
if ref then
|
||||
object.open_object(worktree, ref)
|
||||
return
|
||||
end
|
||||
end
|
||||
run_in_split(worktree, args, { ft = "git", needs_ref = true })
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user