perf(git): piggyback sidebar status on the indicator's git status call

This commit is contained in:
2026-04-27 14:09:33 +02:00
parent 26b12a4371
commit 2ef9cb7c9e
2 changed files with 173 additions and 125 deletions
+13 -1
View File
@@ -142,17 +142,24 @@ end
---@param repo ow.Git.Repo ---@param repo ow.Git.Repo
local function do_refresh(repo) local function do_refresh(repo)
-- `--branch` adds the `## branch...upstream [ahead/behind]` line that
-- the sidebar parses; the per-buffer indicator only needs the XY +
-- path lines, so it ignores `##` lines below. Running with `--branch`
-- lets the sidebar reuse this single subprocess via the GitRefresh
-- data payload instead of spawning its own.
vim.system({ vim.system({
"git", "git",
"-c", "-c",
"core.quotePath=false", "core.quotePath=false",
"status", "status",
"--porcelain=v1", "--porcelain=v1",
"--branch",
}, { cwd = repo.worktree, text = true }, function(obj) }, { cwd = repo.worktree, text = true }, function(obj)
vim.schedule(function() vim.schedule(function()
local statuses = {} local statuses = {}
if obj.code == 0 then if obj.code == 0 then
for line in (obj.stdout or ""):gmatch("[^\r\n]+") do for line in (obj.stdout or ""):gmatch("[^\r\n]+") do
if line:sub(1, 2) ~= "##" then
local code = line:sub(1, 2) local code = line:sub(1, 2)
local path_part = line:sub(4) local path_part = line:sub(4)
local arrow = path_part:find(" -> ", 1, true) local arrow = path_part:find(" -> ", 1, true)
@@ -162,6 +169,7 @@ local function do_refresh(repo)
statuses[vim.fs.joinpath(repo.worktree, path_part)] = statuses[vim.fs.joinpath(repo.worktree, path_part)] =
format(code) format(code)
end end
end
else else
log.warning("git status failed: %s", vim.trim(obj.stderr or "")) log.warning("git status failed: %s", vim.trim(obj.stderr or ""))
end end
@@ -182,7 +190,11 @@ local function do_refresh(repo)
end end
vim.api.nvim_exec_autocmds("User", { vim.api.nvim_exec_autocmds("User", {
pattern = "GitRefresh", pattern = "GitRefresh",
data = { gitdir = repo.gitdir, worktree = repo.worktree }, data = {
gitdir = repo.gitdir,
worktree = repo.worktree,
porcelain_stdout = obj.code == 0 and obj.stdout or nil,
},
}) })
end) end)
end) end)
+64 -28
View File
@@ -142,18 +142,12 @@ local function parse_branch_line(line)
return info return info
end end
---@param worktree string ---Parse `git status --porcelain=v1 --branch` output into a (branch, groups)
---@param callback fun(branch: ow.Git.BranchInfo, groups: table<string, ow.Git.StatusEntry[]>) ---pair. `Unpushed` and `Unpulled` start empty here; ahead/behind commits are
local function fetch_status(worktree, callback) ---filled in by a follow-up `git log` once we know the upstream is set.
vim.system({ ---@param stdout string
"git", ---@return ow.Git.BranchInfo, table<string, ow.Git.StatusEntry[]>
"-c", local function parse_porcelain(stdout)
"core.quotePath=false",
"status",
"--porcelain=v1",
"--branch",
}, { cwd = worktree, text = true }, function(obj)
vim.schedule(function()
local branch = { ahead = 0, behind = 0 } local branch = { ahead = 0, behind = 0 }
local groups = { local groups = {
Untracked = {}, Untracked = {},
@@ -163,11 +157,7 @@ local function fetch_status(worktree, callback)
Unpushed = {}, Unpushed = {},
Unpulled = {}, Unpulled = {},
} }
if obj.code ~= 0 then for line in stdout:gmatch("[^\r\n]+") do
log.error("git status failed: %s", vim.trim(obj.stderr or ""))
end
if obj.code == 0 then
for line in (obj.stdout or ""):gmatch("[^\r\n]+") do
if line:sub(1, 2) == "##" then if line:sub(1, 2) == "##" then
branch = parse_branch_line(line) branch = parse_branch_line(line)
else else
@@ -215,7 +205,17 @@ local function fetch_status(worktree, callback)
end end
end end
end end
return branch, groups
end end
---Run the ahead/behind `git log` calls for any non-zero counters and call
---`callback(branch, groups)` once they all finish (or immediately when
---there's nothing to fetch).
---@param worktree string
---@param branch ow.Git.BranchInfo
---@param groups table<string, ow.Git.StatusEntry[]>
---@param callback fun(branch: ow.Git.BranchInfo, groups: table<string, ow.Git.StatusEntry[]>)
local function enrich_with_log(worktree, branch, groups, callback)
local fetches = {} local fetches = {}
if branch.upstream and branch.ahead > 0 then if branch.upstream and branch.ahead > 0 then
table.insert( table.insert(
@@ -240,16 +240,11 @@ local function fetch_status(worktree, callback)
"log", "log",
"--format=%h %s", "--format=%h %s",
f.range, f.range,
}, { cwd = worktree, text = true }, function( }, { cwd = worktree, text = true }, function(log_obj)
log_obj
)
vim.schedule(function() vim.schedule(function()
if log_obj.code == 0 then if log_obj.code == 0 then
for line in for line in (log_obj.stdout or ""):gmatch("[^\r\n]+") do
(log_obj.stdout or ""):gmatch("[^\r\n]+") local sha, subject = line:match("^(%S+)%s+(.+)$")
do
local sha, subject =
line:match("^(%S+)%s+(.+)$")
if sha then if sha then
table.insert(groups[f.section], { table.insert(groups[f.section], {
section = f.section, section = f.section,
@@ -272,6 +267,46 @@ local function fetch_status(worktree, callback)
end) end)
end) end)
end end
end
---Build the (branch, groups) tuple for the sidebar. When `prefetched_stdout`
---is provided (typical case: dispatched via the `User GitRefresh` autocmd
---that already ran `git status --porcelain=v1 --branch` for the indicator),
---we skip the duplicate subprocess. Otherwise the sidebar fetches its own.
---@param worktree string
---@param prefetched_stdout string?
---@param callback fun(branch: ow.Git.BranchInfo, groups: table<string, ow.Git.StatusEntry[]>)
local function fetch_status(worktree, prefetched_stdout, callback)
if prefetched_stdout then
local branch, groups = parse_porcelain(prefetched_stdout)
enrich_with_log(worktree, branch, groups, callback)
return
end
vim.system({
"git",
"-c",
"core.quotePath=false",
"status",
"--porcelain=v1",
"--branch",
}, { cwd = worktree, text = true }, function(obj)
vim.schedule(function()
if obj.code ~= 0 then
log.error("git status failed: %s", vim.trim(obj.stderr or ""))
local branch = { ahead = 0, behind = 0 }
local groups = {
Untracked = {},
Unstaged = {},
Staged = {},
Unmerged = {},
Unpushed = {},
Unpulled = {},
}
callback(branch, groups)
return
end
local branch, groups = parse_porcelain(obj.stdout or "")
enrich_with_log(worktree, branch, groups, callback)
end) end)
end) end)
end end
@@ -367,7 +402,8 @@ local function fingerprint(branch, groups)
end end
---@param bufnr integer ---@param bufnr integer
local function refresh(bufnr) ---@param prefetched_stdout string? porcelain output from a piggybacked GitRefresh
local function refresh(bufnr, prefetched_stdout)
local s = state[bufnr] local s = state[bufnr]
if not s then if not s then
return return
@@ -384,7 +420,7 @@ local function refresh(bufnr)
end end
end end
fetch_status(s.worktree, function(branch, groups) fetch_status(s.worktree, prefetched_stdout, function(branch, groups)
if not vim.api.nvim_buf_is_valid(bufnr) then if not vim.api.nvim_buf_is_valid(bufnr) then
return return
end end
@@ -874,7 +910,7 @@ local function open(worktree)
group = group, group = group,
callback = function(args) callback = function(args)
if args.data and args.data.gitdir == gitdir then if args.data and args.data.gitdir == gitdir then
refresh(bufnr) refresh(bufnr, args.data.porcelain_stdout)
end end
end, end,
}) })