refactor(git): unify around the Repo abstraction

This commit is contained in:
2026-05-02 22:45:44 +02:00
parent 8bd674622e
commit be1d7ace50
14 changed files with 671 additions and 586 deletions
+122 -23
View File
@@ -23,8 +23,10 @@ local function git_cmds()
if cached_cmds then
return cached_cmds
end
local result = vim
.system({ "git", "--list-cmds=main,others,alias" }, { text = true })
local result = vim.system(
{ "git", "--list-cmds=main,others,alias" },
{ text = true }
)
:wait()
if result.code ~= 0 then
util.error("git --list-cmds failed: %s", vim.trim(result.stderr or ""))
@@ -70,30 +72,31 @@ local function place_split(name)
return buf
end
---@param worktree string
---@param r ow.Git.Repo
---@param args string[]
---@param conf ow.Git.SplitHandler
local function run_in_split(worktree, args, conf)
local function run_in_split(r, args, conf)
local cmd = { "git" }
vim.list_extend(cmd, args)
util.exec(cmd, {
cwd = worktree,
cwd = r.worktree,
on_done = function(stdout)
if not stdout then
return
end
local name = "[git " .. table.concat(args, " ") .. "]"
local buf = place_split(name)
vim.b[buf].git_worktree = worktree
vim.b[buf].git_sha = nil
vim.b[buf].git_parent_sha = nil
repo.attach(buf, r)
object.attach_dispatch(buf)
local state = r:state(buf) --[[@as -nil]]
state.sha = nil
state.parent_sha = nil
if conf.needs_rev then
local user_rev = first_positional(args, 2) or "HEAD"
local sha = repo.rev_parse(worktree, user_rev, true)
local sha = r:rev_parse(user_rev, true)
if sha then
vim.b[buf].git_sha = sha
vim.b[buf].git_parent_sha =
repo.rev_parse(worktree, user_rev .. "^", true)
state.sha = sha
state.parent_sha = r:rev_parse(user_rev .. "^", true)
end
end
vim.bo[buf].filetype = conf.ft
@@ -111,14 +114,14 @@ local function run_in_split(worktree, args, conf)
})
end
---@param worktree string
---@param r ow.Git.Repo
---@param args string[]
local function run_to_messages(worktree, args)
local function run_to_messages(r, args)
local cmd = { "git" }
vim.list_extend(cmd, args)
vim.system(
cmd,
{ cwd = worktree, text = true },
{ cwd = r.worktree, text = true },
vim.schedule_wrap(function(obj)
local out = vim.trim(obj.stdout or "")
local err = vim.trim(obj.stderr or "")
@@ -175,8 +178,8 @@ end
---@param args string[]
function M.run(args)
local _, worktree = repo.current_repo()
if not worktree then
local r = repo.find()
if not r then
util.warning("not in a git repository")
return
end
@@ -190,10 +193,10 @@ function M.run(args)
if sub == "show" then
local arg = first_positional(args, 2)
if arg and arg:find(":", 1, true) then
object.open_object(worktree, arg)
object.open_object(r, arg)
return
end
run_in_split(worktree, args, { ft = "git", needs_rev = true })
run_in_split(r, args, { ft = "git", needs_rev = true })
return
end
@@ -201,22 +204,118 @@ function M.run(args)
if vim.list_contains(args, "-p") then
local rev = first_positional(args, 2)
if rev then
object.open_object(worktree, rev)
object.open_object(r, rev)
return
end
end
run_in_split(worktree, args, { ft = "git", needs_rev = true })
run_in_split(r, args, { ft = "git", needs_rev = true })
return
end
local conf = sub and SPLIT_HANDLERS[sub]
if conf then
run_in_split(worktree, args, conf)
run_in_split(r, args, conf)
else
run_to_messages(worktree, args)
run_to_messages(r, args)
end
end
---@param arg_lead string
---@return string[]
function M.complete_rev(arg_lead)
local r = repo.find()
if not r then
return {}
end
local stage, stage_path_lead = arg_lead:match("^:([0-3]):(.*)$")
if stage then
local out = util.exec(
{ "git", "ls-files", "--stage" },
{ cwd = r.worktree, silent = true }
)
if not out then
return {}
end
local matches = {}
for _, line in ipairs(util.split_lines(out)) do
local row_stage, row_path = line:match("^%S+ %S+ (%d)\t(.*)$")
if
row_stage == stage
and row_path
and row_path:sub(1, #stage_path_lead) == stage_path_lead
then
table.insert(matches, ":" .. stage .. ":" .. row_path)
end
end
return matches
end
local colon = arg_lead:find(":", 1, true)
if not colon then
local matches = {}
for _, ref in ipairs(r:list_refs()) do
if ref:sub(1, #arg_lead) == arg_lead then
table.insert(matches, ref)
end
end
return matches
end
local rev = arg_lead:sub(1, colon - 1)
local path_lead = arg_lead:sub(colon + 1)
local dir, name_lead = path_lead:match("^(.*/)([^/]*)$")
dir = dir or ""
name_lead = name_lead or path_lead
if rev ~= "" then
local cmd = { "git", "ls-tree", rev }
if dir ~= "" then
table.insert(cmd, dir)
end
local out = util.exec(cmd, { cwd = r.worktree, silent = true })
if not out then
return {}
end
local matches = {}
for _, line in ipairs(util.split_lines(out)) do
local typ, full_path = line:match("^%S+ (%S+) %S+\t(.*)$")
if typ and full_path then
local basename = dir == "" and full_path
or full_path:sub(#dir + 1)
if typ == "tree" then
basename = basename .. "/"
end
if basename:sub(1, #name_lead) == name_lead then
table.insert(matches, rev .. ":" .. dir .. basename)
end
end
end
return matches
end
local cmd = { "git", "ls-files" }
if dir ~= "" then
table.insert(cmd, dir)
end
local out = util.exec(cmd, { cwd = r.worktree, silent = true })
if not out then
return {}
end
local matches = {}
local seen = {}
for _, full_path in ipairs(util.split_lines(out)) do
local rel = dir == "" and full_path or full_path:sub(#dir + 1)
local slash = rel:find("/", 1, true)
local segment = slash and rel:sub(1, slash) or rel
if not seen[segment] and segment:sub(1, #name_lead) == name_lead then
seen[segment] = true
table.insert(matches, ":" .. dir .. segment)
end
end
return matches
end
---@param arg_lead string
---@param cmd_line string
---@return string[]