refactor(git): make the git module self-contained under git.util
This commit is contained in:
+4
-5
@@ -1,7 +1,6 @@
|
||||
local git = require("git")
|
||||
local log = require("log")
|
||||
local repo = require("git.repo")
|
||||
local util = require("util")
|
||||
local util = require("git.util")
|
||||
|
||||
local M = {}
|
||||
|
||||
@@ -23,7 +22,7 @@ local cached_cmds
|
||||
---@param result vim.SystemCompleted
|
||||
local function populate_cached_cmds(result)
|
||||
if result.code ~= 0 then
|
||||
log.error("git --list-cmds failed: %s", vim.trim(result.stderr or ""))
|
||||
util.error("git --list-cmds failed: %s", vim.trim(result.stderr or ""))
|
||||
return
|
||||
end
|
||||
cached_cmds = {}
|
||||
@@ -140,7 +139,7 @@ local function run_in_split(worktree, args, conf)
|
||||
vim.bo[buf].modifiable = false
|
||||
vim.bo[buf].modified = false
|
||||
if obj.code ~= 0 then
|
||||
log.error(
|
||||
util.error(
|
||||
"git %s failed: %s",
|
||||
args[1] or "",
|
||||
vim.trim(obj.stderr or "")
|
||||
@@ -216,7 +215,7 @@ end
|
||||
function M.run(args)
|
||||
local _, worktree = repo.resolve_cwd()
|
||||
if not worktree then
|
||||
log.warning("not in a git repository")
|
||||
util.warning("not in a git repository")
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
+4
-5
@@ -1,6 +1,5 @@
|
||||
local editor = require("git.editor")
|
||||
local git = require("git")
|
||||
local log = require("log")
|
||||
local repo = require("git.repo")
|
||||
|
||||
local M = {}
|
||||
@@ -10,7 +9,7 @@ function M.commit(opts)
|
||||
local amend = opts and opts.amend or false
|
||||
local _, worktree = repo.resolve_cwd()
|
||||
if not worktree then
|
||||
log.warning("not in a git repository")
|
||||
util.warning("not in a git repository")
|
||||
return
|
||||
end
|
||||
|
||||
@@ -46,7 +45,7 @@ function M.commit(opts)
|
||||
local out = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
|
||||
local fw, werr = io.open(file_path, "w")
|
||||
if not fw then
|
||||
log.error("failed to write %s: %s", file_path, werr or "")
|
||||
util.error("failed to write %s: %s", file_path, werr or "")
|
||||
return
|
||||
end
|
||||
fw:write(table.concat(out, "\n"))
|
||||
@@ -68,12 +67,12 @@ function M.commit(opts)
|
||||
vim.api.nvim_buf_delete(proxy_buf, { force = true })
|
||||
end
|
||||
if result.code ~= 0 then
|
||||
log.error("git commit failed: %s", vim.trim(result.stderr or ""))
|
||||
util.error("git commit failed: %s", vim.trim(result.stderr or ""))
|
||||
return
|
||||
end
|
||||
local out = vim.trim(result.stdout or "")
|
||||
if out ~= "" then
|
||||
log.info("%s", out)
|
||||
util.info("%s", out)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
+10
-14
@@ -1,6 +1,5 @@
|
||||
local log = require("log")
|
||||
local repo = require("git.repo")
|
||||
local util = require("util")
|
||||
local util = require("git.util")
|
||||
|
||||
local M = {}
|
||||
|
||||
@@ -15,7 +14,7 @@ local function attach_index_writer(buf, worktree, path)
|
||||
vim.api.nvim_buf_get_lines(buf, 0, -1, false),
|
||||
"\n"
|
||||
) .. "\n"
|
||||
local hash_stdout = util.system_sync(
|
||||
local hash_stdout = util.exec(
|
||||
{ "git", "hash-object", "-w", "--stdin" },
|
||||
{ cwd = worktree, stdin = body }
|
||||
)
|
||||
@@ -26,7 +25,7 @@ local function attach_index_writer(buf, worktree, path)
|
||||
local mode = vim.b[buf].git_index_mode
|
||||
if not mode then
|
||||
mode = "100644"
|
||||
local ls = util.system_sync(
|
||||
local ls = util.exec(
|
||||
{ "git", "ls-files", "-s", "--", path },
|
||||
{ cwd = worktree, silent = true }
|
||||
)
|
||||
@@ -42,7 +41,7 @@ local function attach_index_writer(buf, worktree, path)
|
||||
-- (mode,sha,path), which doesn't survive paths containing a
|
||||
-- comma.
|
||||
if
|
||||
not util.system_sync({
|
||||
not util.exec({
|
||||
"git",
|
||||
"update-index",
|
||||
"--cacheinfo",
|
||||
@@ -87,7 +86,7 @@ function M.read_uri(buf)
|
||||
|
||||
local worktree = vim.b[buf].git_worktree or select(2, repo.resolve_cwd())
|
||||
if not worktree then
|
||||
log.error("git BufReadCmd %s: cannot resolve worktree", name)
|
||||
util.error("git BufReadCmd %s: cannot resolve worktree", name)
|
||||
return
|
||||
end
|
||||
vim.b[buf].git_worktree = worktree
|
||||
@@ -104,10 +103,7 @@ function M.read_uri(buf)
|
||||
end
|
||||
|
||||
local revspec = ref == "index" and (":" .. path) or (ref .. ":" .. path)
|
||||
local stdout = util.system_sync(
|
||||
{ "git", "show", revspec },
|
||||
{ cwd = worktree }
|
||||
)
|
||||
local stdout = util.exec({ "git", "show", revspec }, { cwd = worktree })
|
||||
if stdout then
|
||||
vim.api.nvim_buf_set_lines(buf, 0, -1, false, util.split_lines(stdout))
|
||||
end
|
||||
@@ -207,21 +203,21 @@ function M.split(opts)
|
||||
local cur_buf = vim.api.nvim_get_current_buf()
|
||||
local cur_path = vim.api.nvim_buf_get_name(cur_buf)
|
||||
if cur_path == "" then
|
||||
log.warning("no file in current buffer")
|
||||
util.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)")
|
||||
util.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")
|
||||
util.warning("not in a git repository")
|
||||
return
|
||||
end
|
||||
local rel = vim.fs.relpath(worktree, cur_path)
|
||||
if not rel then
|
||||
log.warning("file is outside the worktree")
|
||||
util.warning("file is outside the worktree")
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
+1
-3
@@ -1,5 +1,3 @@
|
||||
local log = require("log")
|
||||
|
||||
local M = {}
|
||||
|
||||
local SENTINEL = "__NVIM_GIT_EDIT__"
|
||||
@@ -59,7 +57,7 @@ local function build_stderr_handler(on_open)
|
||||
end
|
||||
local ok, err = pcall(on_open, abs_path, done)
|
||||
if not ok then
|
||||
log.error("git.editor on_open failed: %s", tostring(err))
|
||||
util.error("git.editor on_open failed: %s", tostring(err))
|
||||
done()
|
||||
end
|
||||
end)
|
||||
|
||||
+3
-4
@@ -1,7 +1,6 @@
|
||||
local git = require("git")
|
||||
local log = require("log")
|
||||
local repo = require("git.repo")
|
||||
local util = require("util")
|
||||
local util = require("git.util")
|
||||
|
||||
local M = {}
|
||||
|
||||
@@ -21,7 +20,7 @@ function M.show(opts)
|
||||
|
||||
local _, worktree = repo.resolve_cwd()
|
||||
if not worktree then
|
||||
log.warning("not in a git repository")
|
||||
util.warning("not in a git repository")
|
||||
return
|
||||
end
|
||||
|
||||
@@ -38,7 +37,7 @@ function M.show(opts)
|
||||
table.insert(cmd, "--max-count=" .. max_count)
|
||||
end
|
||||
|
||||
local stdout = util.system_sync(cmd, { cwd = worktree })
|
||||
local stdout = util.exec(cmd, { cwd = worktree })
|
||||
if not stdout then
|
||||
return
|
||||
end
|
||||
|
||||
+8
-6
@@ -1,5 +1,4 @@
|
||||
local log = require("log")
|
||||
local util = require("util")
|
||||
local util = require("git.util")
|
||||
|
||||
local M = {}
|
||||
|
||||
@@ -76,7 +75,7 @@ function M.resolve(path)
|
||||
f:close()
|
||||
local gitdir = content:match("gitdir:%s*(%S+)")
|
||||
if not gitdir then
|
||||
log.warning(".git file at %s has no `gitdir:` line", found)
|
||||
util.warning(".git file at %s has no `gitdir:` line", found)
|
||||
return nil
|
||||
end
|
||||
if not gitdir:match("^/") then
|
||||
@@ -104,7 +103,7 @@ end
|
||||
---@field buffers table<integer, true> set of registered buffer numbers
|
||||
---@field watcher? uv.uv_fs_event_t
|
||||
---@field refresh fun(self: ow.Git.Repo)
|
||||
---@field refresh_handle ow.Util.DebounceHandle
|
||||
---@field refresh_handle ow.Git.Util.DebounceHandle
|
||||
local Repo = {}
|
||||
Repo.__index = Repo
|
||||
|
||||
@@ -188,7 +187,10 @@ local function do_refresh(repo)
|
||||
end
|
||||
end
|
||||
else
|
||||
log.warning("git status failed: %s", vim.trim(obj.stderr or ""))
|
||||
util.warning(
|
||||
"git status failed: %s",
|
||||
vim.trim(obj.stderr or "")
|
||||
)
|
||||
end
|
||||
local dirty = false
|
||||
for buf in pairs(repo.buffers) do
|
||||
@@ -336,7 +338,7 @@ function M.rev_parse(worktree, ref, short)
|
||||
table.insert(cmd, "--short")
|
||||
end
|
||||
table.insert(cmd, ref)
|
||||
local stdout = util.system_sync(cmd, { cwd = worktree, silent = true })
|
||||
local stdout = util.exec(cmd, { cwd = worktree, silent = true })
|
||||
local trimmed = stdout and vim.trim(stdout) or ""
|
||||
return trimmed ~= "" and trimmed or nil
|
||||
end
|
||||
|
||||
+3
-4
@@ -1,8 +1,7 @@
|
||||
local diff = require("git.diff")
|
||||
local git = require("git")
|
||||
local log = require("log")
|
||||
local repo = require("git.repo")
|
||||
local util = require("util")
|
||||
local util = require("git.util")
|
||||
|
||||
local M = {}
|
||||
|
||||
@@ -109,7 +108,7 @@ end
|
||||
---@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")
|
||||
util.warning("no index line; cannot determine blob SHAs")
|
||||
return
|
||||
end
|
||||
local parent = ctx.parent_ref or "0"
|
||||
@@ -152,7 +151,7 @@ function M.open_commit(worktree, ref, opts)
|
||||
return
|
||||
end
|
||||
|
||||
local stdout = util.system_sync({ "git", "show", ref }, { cwd = worktree })
|
||||
local stdout = util.exec({ "git", "show", ref }, { cwd = worktree })
|
||||
if not stdout then
|
||||
return
|
||||
end
|
||||
|
||||
+8
-9
@@ -1,6 +1,5 @@
|
||||
local diff = require("git.diff")
|
||||
local git = require("git")
|
||||
local log = require("log")
|
||||
local repo = require("git.repo")
|
||||
|
||||
local M = {}
|
||||
@@ -263,7 +262,7 @@ local function enrich_with_log(worktree, branch, groups)
|
||||
end
|
||||
end
|
||||
else
|
||||
log.error(
|
||||
util.error(
|
||||
"git log %s failed: %s",
|
||||
p.f.range,
|
||||
vim.trim(result.stderr or "")
|
||||
@@ -298,7 +297,7 @@ local function fetch_status(worktree, prefetched_stdout, callback)
|
||||
{ cwd = worktree, text = true },
|
||||
vim.schedule_wrap(function(obj)
|
||||
if obj.code ~= 0 then
|
||||
log.error("git status failed: %s", vim.trim(obj.stderr or ""))
|
||||
util.error("git status failed: %s", vim.trim(obj.stderr or ""))
|
||||
local branch = { ahead = 0, behind = 0 }
|
||||
local groups = {
|
||||
Untracked = {},
|
||||
@@ -761,7 +760,7 @@ local function action_stage()
|
||||
{ cwd = s.worktree },
|
||||
vim.schedule_wrap(function(obj)
|
||||
if obj.code ~= 0 then
|
||||
log.error("git add failed: %s", vim.trim(obj.stderr or ""))
|
||||
util.error("git add failed: %s", vim.trim(obj.stderr or ""))
|
||||
end
|
||||
end)
|
||||
)
|
||||
@@ -786,7 +785,7 @@ local function action_unstage()
|
||||
{ cwd = s.worktree },
|
||||
vim.schedule_wrap(function(obj)
|
||||
if obj.code ~= 0 then
|
||||
log.error(
|
||||
util.error(
|
||||
"git restore --staged failed: %s",
|
||||
vim.trim(obj.stderr or "")
|
||||
)
|
||||
@@ -802,7 +801,7 @@ local function action_discard()
|
||||
end
|
||||
---@cast entry ow.Git.FileEntry
|
||||
if entry.section == "Staged" then
|
||||
log.warning("file has staged changes; unstage first with 'u'")
|
||||
util.warning("file has staged changes; unstage first with 'u'")
|
||||
return
|
||||
end
|
||||
|
||||
@@ -821,7 +820,7 @@ local function action_discard()
|
||||
local target = vim.fs.joinpath(s.worktree, entry.path)
|
||||
local rc = vim.fn.delete(target, is_dir and "rf" or "")
|
||||
if rc ~= 0 then
|
||||
log.error("failed to delete %s", entry.path)
|
||||
util.error("failed to delete %s", entry.path)
|
||||
end
|
||||
refresh(vim.api.nvim_get_current_buf())
|
||||
end
|
||||
@@ -833,7 +832,7 @@ local function action_discard()
|
||||
{ cwd = s.worktree },
|
||||
vim.schedule_wrap(function(obj)
|
||||
if obj.code ~= 0 then
|
||||
log.error(
|
||||
util.error(
|
||||
"git checkout failed: %s",
|
||||
vim.trim(obj.stderr or "")
|
||||
)
|
||||
@@ -950,7 +949,7 @@ function M.toggle()
|
||||
end
|
||||
local _, worktree = repo.resolve_cwd()
|
||||
if not worktree then
|
||||
log.warning("not in a git repository")
|
||||
util.warning("not in a git repository")
|
||||
return
|
||||
end
|
||||
open(worktree)
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
local M = {}
|
||||
|
||||
---@param fmt string
|
||||
---@param ... any
|
||||
function M.error(fmt, ...)
|
||||
vim.notify(fmt:format(...), vim.log.levels.ERROR)
|
||||
end
|
||||
|
||||
---@param fmt string
|
||||
---@param ... any
|
||||
function M.warning(fmt, ...)
|
||||
vim.notify(fmt:format(...), vim.log.levels.WARN)
|
||||
end
|
||||
|
||||
---@param fmt string
|
||||
---@param ... any
|
||||
function M.info(fmt, ...)
|
||||
vim.notify(fmt:format(...), vim.log.levels.INFO)
|
||||
end
|
||||
|
||||
---@param fmt string
|
||||
---@param ... any
|
||||
function M.debug(fmt, ...)
|
||||
vim.notify(fmt:format(...), vim.log.levels.DEBUG)
|
||||
end
|
||||
|
||||
---Split a string on newlines, dropping the trailing empty element that an
|
||||
---input ending in `\n` produces. Convenient for slicing subprocess stdout
|
||||
---into a list of lines without a phantom blank at the end.
|
||||
---@param content string
|
||||
---@return string[]
|
||||
function M.split_lines(content)
|
||||
local lines = vim.split(content, "\n", { plain = true, trimempty = false })
|
||||
if #lines > 0 and lines[#lines] == "" then
|
||||
table.remove(lines)
|
||||
end
|
||||
return lines
|
||||
end
|
||||
|
||||
---@class ow.Git.Util.DebounceHandle
|
||||
---@field cancel fun()
|
||||
---@field flush fun()
|
||||
---@field pending fun(): boolean
|
||||
---@field close fun()
|
||||
|
||||
---@generic F: fun(...)
|
||||
---@param fn F
|
||||
---@param delay integer
|
||||
---@return F, ow.Git.Util.DebounceHandle
|
||||
function M.debounce(fn, delay)
|
||||
local timer = assert(vim.uv.new_timer())
|
||||
local args ---@type table?
|
||||
local gen = 0
|
||||
local fired_gen = 0
|
||||
|
||||
local cb_main = vim.schedule_wrap(function()
|
||||
-- Identity check: the libuv fire may have been superseded by a
|
||||
-- re-arm or a cancel between the timer firing and this scheduled
|
||||
-- callback running.
|
||||
if fired_gen ~= gen or args == nil then
|
||||
return
|
||||
end
|
||||
local a = args
|
||||
args = nil
|
||||
fn(vim.F.unpack_len(a))
|
||||
end)
|
||||
|
||||
local cb_uv = function()
|
||||
fired_gen = gen
|
||||
cb_main()
|
||||
end
|
||||
|
||||
local function call(...)
|
||||
args = vim.F.pack_len(...)
|
||||
gen = gen + 1
|
||||
timer:start(delay, 0, cb_uv)
|
||||
end
|
||||
|
||||
return call,
|
||||
{
|
||||
cancel = function()
|
||||
timer:stop()
|
||||
args = nil
|
||||
end,
|
||||
flush = function()
|
||||
if args == nil then
|
||||
return
|
||||
end
|
||||
timer:stop()
|
||||
local a = args
|
||||
args = nil
|
||||
fn(vim.F.unpack_len(a))
|
||||
end,
|
||||
pending = function()
|
||||
return args ~= nil
|
||||
end,
|
||||
close = function()
|
||||
timer:stop()
|
||||
if not timer:is_closing() then
|
||||
timer:close()
|
||||
end
|
||||
args = nil
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
---@class ow.Git.ExecOpts
|
||||
---@field cwd string?
|
||||
---@field stdin string?
|
||||
---@field silent boolean? suppress the auto-log on non-zero exit
|
||||
---@field on_done fun(stdout: string?)? if set, run async and deliver stdout (or nil on failure) here on the main loop instead of returning sync
|
||||
|
||||
---Run a system command. Default is sync: returns stdout on success or
|
||||
---nil on failure (logging stderr unless `opts.silent`). When
|
||||
---`opts.on_done` is set, runs async via `vim.schedule_wrap` and
|
||||
---delivers the same stdout-or-nil value to that callback instead.
|
||||
---
|
||||
---Async mode returns nil immediately. Callers that need access to the
|
||||
---raw stderr / exit code in the failure path should opt out of this
|
||||
---helper and use `vim.system` directly.
|
||||
---@param cmd string[]
|
||||
---@param opts ow.Git.ExecOpts?
|
||||
---@return string?
|
||||
function M.exec(cmd, opts)
|
||||
opts = opts or {}
|
||||
local sys_opts = { cwd = opts.cwd, stdin = opts.stdin, text = true }
|
||||
|
||||
local function handle(result)
|
||||
if result.code ~= 0 then
|
||||
if not opts.silent then
|
||||
local label = cmd[2] and (cmd[1] .. " " .. cmd[2])
|
||||
or cmd[1]
|
||||
or "?"
|
||||
M.error("%s failed: %s", label, vim.trim(result.stderr or ""))
|
||||
end
|
||||
return nil
|
||||
end
|
||||
return result.stdout or ""
|
||||
end
|
||||
|
||||
if opts.on_done then
|
||||
vim.system(
|
||||
cmd,
|
||||
sys_opts,
|
||||
vim.schedule_wrap(function(result)
|
||||
opts.on_done(handle(result))
|
||||
end)
|
||||
)
|
||||
return nil
|
||||
end
|
||||
return handle(vim.system(cmd, sys_opts):wait())
|
||||
end
|
||||
|
||||
return M
|
||||
Reference in New Issue
Block a user