refactor(git): route every git invocation through util.git

This commit is contained in:
2026-05-08 03:44:23 +02:00
parent ebfcaef240
commit 01a543c12f
6 changed files with 115 additions and 132 deletions
+33 -38
View File
@@ -22,17 +22,12 @@ local function git_cmds()
if cached_cmds then if cached_cmds then
return cached_cmds return cached_cmds
end end
local result = vim.system( local out = util.git({ "--list-cmds=main,others,alias" })
{ "git", "--list-cmds=main,others,alias" }, if not out then
{ text = true }
)
:wait()
if result.code ~= 0 then
util.error("git --list-cmds failed: %s", vim.trim(result.stderr or ""))
return {} return {}
end end
cached_cmds = {} cached_cmds = {}
for line in (result.stdout or ""):gmatch("[^\r\n]+") do for line in out:gmatch("[^\r\n]+") do
if line ~= "" then if line ~= "" then
table.insert(cached_cmds, line) table.insert(cached_cmds, line)
end end
@@ -191,10 +186,16 @@ end
local function run_in_split(r, args, conf) local function run_in_split(r, args, conf)
util.git(args, { util.git(args, {
cwd = r.worktree, cwd = r.worktree,
on_done = function(stdout) on_exit = function(result)
if not stdout then if result.code ~= 0 then
util.error(
"git %s failed: %s",
args[1] or "?",
vim.trim(result.stderr or "")
)
return return
end end
local stdout = result.stdout or ""
local buf = place_split("[Git " .. table.concat(args, " ") .. "]") local buf = place_split("[Git " .. table.concat(args, " ") .. "]")
repo.bind(buf, r) repo.bind(buf, r)
object.attach_dispatch(buf) object.attach_dispatch(buf)
@@ -216,14 +217,11 @@ end
---@param r ow.Git.Repo ---@param r ow.Git.Repo
---@param args string[] ---@param args string[]
local function run_to_messages(r, args) local function run_to_messages(r, args)
local cmd = { "git" } util.git(args, {
vim.list_extend(cmd, args) cwd = r.worktree,
vim.system( on_exit = function(result)
cmd, local out = vim.trim(result.stdout or "")
{ cwd = r.worktree, text = true }, local err = vim.trim(result.stderr or "")
vim.schedule_wrap(function(obj)
local out = vim.trim(obj.stdout or "")
local err = vim.trim(obj.stderr or "")
local chunks = {} local chunks = {}
if out ~= "" then if out ~= "" then
table.insert(chunks, { out }) table.insert(chunks, { out })
@@ -234,17 +232,17 @@ local function run_to_messages(r, args)
end end
table.insert(chunks, { err, "ErrorMsg" }) table.insert(chunks, { err, "ErrorMsg" })
end end
if #chunks == 0 and obj.code ~= 0 then if #chunks == 0 and result.code ~= 0 then
table.insert( table.insert(
chunks, chunks,
{ "git exited " .. tostring(obj.code), "ErrorMsg" } { "git exited " .. tostring(result.code), "ErrorMsg" }
) )
end end
if #chunks > 0 then if #chunks > 0 then
vim.api.nvim_echo(chunks, true, {}) vim.api.nvim_echo(chunks, true, {})
end end
end) end,
) })
end end
---@param args string[] ---@param args string[]
@@ -337,11 +335,11 @@ end
---@return string[] ---@return string[]
local function list_files(r, dir) local function list_files(r, dir)
return r:get_cached("files:" .. dir, function(self) return r:get_cached("files:" .. dir, function(self)
local cmd = { "git", "ls-files" } local args = { "ls-files" }
if dir ~= "" then if dir ~= "" then
table.insert(cmd, dir) table.insert(args, dir)
end end
local out = util.exec(cmd, { cwd = self.worktree, silent = true }) local out = util.git(args, { cwd = self.worktree, silent = true })
return out and util.split_lines(out) or {} return out and util.split_lines(out) or {}
end) end)
end end
@@ -350,8 +348,8 @@ end
---@return string[] ---@return string[]
local function list_remotes(r) local function list_remotes(r)
return r:get_cached("remotes", function(self) return r:get_cached("remotes", function(self)
local out = util.exec( local out = util.git(
{ "git", "remote" }, { "remote" },
{ cwd = self.worktree, silent = true } { cwd = self.worktree, silent = true }
) )
return out and util.split_lines(out) or {} return out and util.split_lines(out) or {}
@@ -382,13 +380,10 @@ local function fetch_completions(sub)
if cached_completions[sub] then if cached_completions[sub] then
return cached_completions[sub] return cached_completions[sub]
end end
local out = util.exec( local out = util.git(
{ "git", sub, "--git-completion-helper-all" }, { sub, "--git-completion-helper-all" },
{ silent = true } { silent = true }
) or util.exec( ) or util.git({ sub, "--git-completion-helper" }, { silent = true })
{ "git", sub, "--git-completion-helper" },
{ silent = true }
)
local items = {} local items = {}
if out then if out then
for tok in out:gmatch("%S+") do for tok in out:gmatch("%S+") do
@@ -469,8 +464,8 @@ function M.complete_rev(arg_lead)
local stage, stage_path_lead = arg_lead:match("^:([0-3]):(.*)$") local stage, stage_path_lead = arg_lead:match("^:([0-3]):(.*)$")
if stage then if stage then
local out = util.exec( local out = util.git(
{ "git", "ls-files", "--stage" }, { "ls-files", "--stage" },
{ cwd = r.worktree, silent = true } { cwd = r.worktree, silent = true }
) )
if not out then if not out then
@@ -506,11 +501,11 @@ function M.complete_rev(arg_lead)
name_lead = name_lead or path_lead name_lead = name_lead or path_lead
if rev ~= "" then if rev ~= "" then
local cmd = { "git", "ls-tree", rev } local args = { "ls-tree", rev }
if dir ~= "" then if dir ~= "" then
table.insert(cmd, dir) table.insert(args, dir)
end end
local out = util.exec(cmd, { cwd = r.worktree, silent = true }) local out = util.git(args, { cwd = r.worktree, silent = true })
if not out then if not out then
return {} return {}
end end
+3 -4
View File
@@ -32,8 +32,7 @@ end
---@param max_count integer? ---@param max_count integer?
---@return string? ---@return string?
local function fetch(worktree, max_count) local function fetch(worktree, max_count)
local cmd = { local args = {
"git",
"log", "log",
"--graph", "--graph",
"--all", "--all",
@@ -42,9 +41,9 @@ local function fetch(worktree, max_count)
"--format=format:" .. LOG_FORMAT, "--format=format:" .. LOG_FORMAT,
} }
if max_count then if max_count then
table.insert(cmd, "--max-count=" .. max_count) table.insert(args, "--max-count=" .. max_count)
end end
return util.exec(cmd, { cwd = worktree }) return util.git(args, { cwd = worktree })
end end
---@type table<string, integer> -- worktree -> max_count ---@type table<string, integer> -- worktree -> max_count
+7 -8
View File
@@ -96,8 +96,8 @@ local function attach_index_writer(buf, r, path)
vim.api.nvim_buf_get_lines(buf, 0, -1, false), vim.api.nvim_buf_get_lines(buf, 0, -1, false),
"\n" "\n"
) .. "\n" ) .. "\n"
local hash_stdout = util.exec( local hash_stdout = util.git(
{ "git", "hash-object", "-w", "--stdin" }, { "hash-object", "-w", "--stdin" },
{ cwd = r.worktree, stdin = body } { cwd = r.worktree, stdin = body }
) )
if not hash_stdout then if not hash_stdout then
@@ -108,8 +108,8 @@ local function attach_index_writer(buf, r, path)
local mode = state and state.index_mode local mode = state and state.index_mode
if not mode then if not mode then
mode = "100644" mode = "100644"
local ls = util.exec( local ls = util.git(
{ "git", "ls-files", "-s", "--", path }, { "ls-files", "-s", "--", path },
{ cwd = r.worktree, silent = true } { cwd = r.worktree, silent = true }
) )
if ls then if ls then
@@ -123,8 +123,7 @@ local function attach_index_writer(buf, r, path)
end end
end end
if if
not util.exec({ not util.git({
"git",
"update-index", "update-index",
"--cacheinfo", "--cacheinfo",
mode, mode,
@@ -171,8 +170,8 @@ end
---@return boolean ok ---@return boolean ok
local function populate(buf, r, rev, state, rev_sha) local function populate(buf, r, rev, state, rev_sha)
local rev_str = rev:format() local rev_str = rev:format()
local stdout = util.exec( local stdout = util.git(
{ "git", "cat-file", "-p", rev_str }, { "cat-file", "-p", rev_str },
{ cwd = r.worktree } { cwd = r.worktree }
) )
if not stdout then if not stdout then
+21 -21
View File
@@ -38,8 +38,7 @@ local global = util.Emitter.new()
local Repo = {} local Repo = {}
Repo.__index = Repo Repo.__index = Repo
local STATUS_CMD = { local STATUS_ARGS = {
"git",
"--no-optional-locks", "--no-optional-locks",
"-c", "-c",
"core.quotePath=false", "core.quotePath=false",
@@ -52,20 +51,22 @@ local STATUS_CMD = {
---@private ---@private
function Repo:_fetch_status() function Repo:_fetch_status()
vim.system( util.git(STATUS_ARGS, {
STATUS_CMD, cwd = self.worktree,
{ cwd = self.worktree, text = true }, on_exit = function(result)
vim.schedule_wrap(function(obj)
self._cache = {} self._cache = {}
if obj.code ~= 0 then if result.code ~= 0 then
util.error("git status failed: %s", vim.trim(obj.stderr or "")) util.error(
"git status failed: %s",
vim.trim(result.stderr or "")
)
return return
end end
self.status = status.parse(obj.stdout or "") self.status = status.parse(result.stdout or "")
self._events:emit("refresh", self.status) self._events:emit("refresh", self.status)
global:emit("refresh", self, self.status) global:emit("refresh", self, self.status)
end) end,
) })
end end
function Repo:refresh() function Repo:refresh()
@@ -242,8 +243,7 @@ end
---@return string[] ---@return string[]
function Repo:list_refs() function Repo:list_refs()
return self:get_cached("refs", function(self) return self:get_cached("refs", function(self)
local out = util.exec({ local out = util.git({
"git",
"for-each-ref", "for-each-ref",
"--format=%(refname:short)", "--format=%(refname:short)",
"refs/heads", "refs/heads",
@@ -287,8 +287,8 @@ function Repo:list_stash_refs()
return {} return {}
end end
local refs = { "stash" } local refs = { "stash" }
local out = util.exec( local out = util.git(
{ "git", "stash", "list", "--pretty=format:%gd" }, { "stash", "list", "--pretty=format:%gd" },
{ cwd = self.worktree, silent = true } { cwd = self.worktree, silent = true }
) )
if out then if out then
@@ -304,12 +304,12 @@ end
---@param short boolean ---@param short boolean
---@return string? ---@return string?
function Repo:rev_parse(rev, short) function Repo:rev_parse(rev, short)
local cmd = { "git", "rev-parse", "--verify", "--quiet" } local args = { "rev-parse", "--verify", "--quiet" }
if short then if short then
table.insert(cmd, "--short") table.insert(args, "--short")
end end
table.insert(cmd, rev) table.insert(args, rev)
local stdout = util.exec(cmd, { cwd = self.worktree, silent = true }) local stdout = util.git(args, { cwd = self.worktree, silent = true })
local trimmed = stdout and vim.trim(stdout) or "" local trimmed = stdout and vim.trim(stdout) or ""
return trimmed ~= "" and trimmed or nil return trimmed ~= "" and trimmed or nil
end end
@@ -321,8 +321,8 @@ end
---@return ow.Git.Repo.ResolveStatus ---@return ow.Git.Repo.ResolveStatus
function Repo:resolve_sha(prefix) function Repo:resolve_sha(prefix)
local result = self:get_cached("resolve:" .. prefix, function(self) local result = self:get_cached("resolve:" .. prefix, function(self)
local out = util.exec( local out = util.git(
{ "git", "rev-parse", "--disambiguate=" .. prefix }, { "rev-parse", "--disambiguate=" .. prefix },
{ cwd = self.worktree, silent = true } { cwd = self.worktree, silent = true }
) )
local trimmed = out and vim.trim(out) or "" local trimmed = out and vim.trim(out) or ""
+29 -29
View File
@@ -460,17 +460,19 @@ local function action_stage()
if #paths == 0 then if #paths == 0 then
return return
end end
local cmd = { "git", "add", "--" } local args = { "add", "--" }
vim.list_extend(cmd, paths) vim.list_extend(args, paths)
vim.system( util.git(args, {
cmd, cwd = s.repo.worktree,
{ cwd = s.repo.worktree }, on_exit = function(result)
vim.schedule_wrap(function(obj) if result.code ~= 0 then
if obj.code ~= 0 then util.error(
util.error("git add failed: %s", vim.trim(obj.stderr or "")) "git add failed: %s",
end vim.trim(result.stderr or "")
end)
) )
end
end,
})
end end
local function action_unstage() local function action_unstage()
@@ -481,7 +483,7 @@ local function action_unstage()
if item.kind ~= "staged" then if item.kind ~= "staged" then
return return
end end
local cmd = { "git", "restore", "--staged", "--" } local args = { "restore", "--staged", "--" }
local entries local entries
if item.is_header then if item.is_header then
entries = s.repo.status:by_kind("staged") entries = s.repo.status:by_kind("staged")
@@ -494,22 +496,21 @@ local function action_unstage()
end end
for _, e in ipairs(entries) do for _, e in ipairs(entries) do
if e.orig then if e.orig then
table.insert(cmd, e.orig) table.insert(args, e.orig)
end end
table.insert(cmd, e.path) table.insert(args, e.path)
end end
vim.system( util.git(args, {
cmd, cwd = s.repo.worktree,
{ cwd = s.repo.worktree }, on_exit = function(result)
vim.schedule_wrap(function(obj) if result.code ~= 0 then
if obj.code ~= 0 then
util.error( util.error(
"git restore --staged failed: %s", "git restore --staged failed: %s",
vim.trim(obj.stderr or "") vim.trim(result.stderr or "")
) )
end end
end) end,
) })
end end
local function action_discard() local function action_discard()
@@ -542,18 +543,17 @@ local function action_discard()
elseif item.kind == "unstaged" then elseif item.kind == "unstaged" then
prompt = string.format("Discard changes to %s?", item.path) prompt = string.format("Discard changes to %s?", item.path)
action = function() action = function()
vim.system( util.git({ "checkout", "--", item.path }, {
{ "git", "checkout", "--", item.path }, cwd = s.repo.worktree,
{ cwd = s.repo.worktree }, on_exit = function(result)
vim.schedule_wrap(function(obj) if result.code ~= 0 then
if obj.code ~= 0 then
util.error( util.error(
"git checkout failed: %s", "git checkout failed: %s",
vim.trim(obj.stderr or "") vim.trim(result.stderr or "")
) )
end end
end) end,
) })
end end
else else
return return
+16 -26
View File
@@ -189,16 +189,7 @@ end
---@field cwd string? ---@field cwd string?
---@field stdin string? ---@field stdin string?
---@field silent boolean? ---@field silent boolean?
---@field on_done fun(stdout: string?)? ---@field on_exit fun(result: vim.SystemCompleted)?
---@param args string[]
---@param opts ow.Git.Util.ExecOpts?
---@return string?
function M.git(args, opts)
local cmd = { "git" }
vim.list_extend(cmd, args)
return M.exec(cmd, opts)
end
---@param cmd string[] ---@param cmd string[]
---@param opts ow.Git.Util.ExecOpts? ---@param opts ow.Git.Util.ExecOpts?
@@ -207,30 +198,29 @@ function M.exec(cmd, opts)
opts = opts or {} opts = opts or {}
local sys_opts = { cwd = opts.cwd, stdin = opts.stdin, text = true } local sys_opts = { cwd = opts.cwd, stdin = opts.stdin, text = true }
local function handle(result) if opts.on_exit then
vim.system(cmd, sys_opts, vim.schedule_wrap(opts.on_exit))
return nil
end
local result = vim.system(cmd, sys_opts):wait()
if result.code ~= 0 then if result.code ~= 0 then
if not opts.silent then if not opts.silent then
local label = cmd[2] and (cmd[1] .. " " .. cmd[2]) local label = cmd[2] and (cmd[1] .. " " .. cmd[2]) or cmd[1] or "?"
or cmd[1]
or "?"
M.error("%s failed: %s", label, vim.trim(result.stderr or "")) M.error("%s failed: %s", label, vim.trim(result.stderr or ""))
end end
return nil return nil
end end
return result.stdout or "" return result.stdout or ""
end end
if opts.on_done then ---@param args string[]
vim.system( ---@param opts ow.Git.Util.ExecOpts?
cmd, ---@return string?
sys_opts, function M.git(args, opts)
vim.schedule_wrap(function(result) local cmd = { "git" }
opts.on_done(handle(result)) vim.list_extend(cmd, args)
end) return M.exec(cmd, opts)
)
return nil
end
return handle(vim.system(cmd, sys_opts):wait())
end end
---@class ow.Git.Util.Emitter<T> ---@class ow.Git.Util.Emitter<T>