perf(git): piggyback sidebar status on the indicator's git status call
This commit is contained in:
+20
-8
@@ -142,25 +142,33 @@ 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
|
||||||
local code = line:sub(1, 2)
|
if line:sub(1, 2) ~= "##" then
|
||||||
local path_part = line:sub(4)
|
local code = line:sub(1, 2)
|
||||||
local arrow = path_part:find(" -> ", 1, true)
|
local path_part = line:sub(4)
|
||||||
if arrow then
|
local arrow = path_part:find(" -> ", 1, true)
|
||||||
path_part = path_part:sub(arrow + 4)
|
if arrow then
|
||||||
|
path_part = path_part:sub(arrow + 4)
|
||||||
|
end
|
||||||
|
statuses[vim.fs.joinpath(repo.worktree, path_part)] =
|
||||||
|
format(code)
|
||||||
end
|
end
|
||||||
statuses[vim.fs.joinpath(repo.worktree, path_part)] =
|
|
||||||
format(code)
|
|
||||||
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 ""))
|
||||||
@@ -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)
|
||||||
|
|||||||
+153
-117
@@ -142,9 +142,146 @@ local function parse_branch_line(line)
|
|||||||
return info
|
return info
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---Parse `git status --porcelain=v1 --branch` output into a (branch, groups)
|
||||||
|
---pair. `Unpushed` and `Unpulled` start empty here; ahead/behind commits are
|
||||||
|
---filled in by a follow-up `git log` once we know the upstream is set.
|
||||||
|
---@param stdout string
|
||||||
|
---@return ow.Git.BranchInfo, table<string, ow.Git.StatusEntry[]>
|
||||||
|
local function parse_porcelain(stdout)
|
||||||
|
local branch = { ahead = 0, behind = 0 }
|
||||||
|
local groups = {
|
||||||
|
Untracked = {},
|
||||||
|
Unstaged = {},
|
||||||
|
Staged = {},
|
||||||
|
Unmerged = {},
|
||||||
|
Unpushed = {},
|
||||||
|
Unpulled = {},
|
||||||
|
}
|
||||||
|
for line in stdout:gmatch("[^\r\n]+") do
|
||||||
|
if line:sub(1, 2) == "##" then
|
||||||
|
branch = parse_branch_line(line)
|
||||||
|
else
|
||||||
|
local x = line:sub(1, 1)
|
||||||
|
local y = line:sub(2, 2)
|
||||||
|
local rest = line:sub(4)
|
||||||
|
local orig
|
||||||
|
local arrow = rest:find(" -> ", 1, true)
|
||||||
|
if arrow then
|
||||||
|
orig = rest:sub(1, arrow - 1)
|
||||||
|
rest = rest:sub(arrow + 4)
|
||||||
|
end
|
||||||
|
local entry = {
|
||||||
|
section = nil,
|
||||||
|
path = rest,
|
||||||
|
orig = orig,
|
||||||
|
x = x,
|
||||||
|
y = y,
|
||||||
|
}
|
||||||
|
if x == "?" and y == "?" then
|
||||||
|
entry.section = "Untracked"
|
||||||
|
table.insert(groups.Untracked, entry)
|
||||||
|
elseif repo.UNMERGED[x .. y] then
|
||||||
|
entry.section = "Unmerged"
|
||||||
|
table.insert(groups.Unmerged, entry)
|
||||||
|
else
|
||||||
|
if x ~= " " then
|
||||||
|
table.insert(groups.Staged, {
|
||||||
|
section = "Staged",
|
||||||
|
path = entry.path,
|
||||||
|
orig = entry.orig,
|
||||||
|
x = entry.x,
|
||||||
|
y = entry.y,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
if y ~= " " then
|
||||||
|
table.insert(groups.Unstaged, {
|
||||||
|
section = "Unstaged",
|
||||||
|
path = entry.path,
|
||||||
|
orig = entry.orig,
|
||||||
|
x = entry.x,
|
||||||
|
y = entry.y,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return branch, groups
|
||||||
|
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 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[]>)
|
---@param callback fun(branch: ow.Git.BranchInfo, groups: table<string, ow.Git.StatusEntry[]>)
|
||||||
local function fetch_status(worktree, callback)
|
local function enrich_with_log(worktree, branch, groups, callback)
|
||||||
|
local fetches = {}
|
||||||
|
if branch.upstream and branch.ahead > 0 then
|
||||||
|
table.insert(
|
||||||
|
fetches,
|
||||||
|
{ section = "Unpushed", range = "@{upstream}..HEAD" }
|
||||||
|
)
|
||||||
|
end
|
||||||
|
if branch.upstream and branch.behind > 0 then
|
||||||
|
table.insert(
|
||||||
|
fetches,
|
||||||
|
{ section = "Unpulled", range = "HEAD..@{upstream}" }
|
||||||
|
)
|
||||||
|
end
|
||||||
|
if #fetches == 0 then
|
||||||
|
callback(branch, groups)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local pending = #fetches
|
||||||
|
for _, f in ipairs(fetches) do
|
||||||
|
vim.system({
|
||||||
|
"git",
|
||||||
|
"log",
|
||||||
|
"--format=%h %s",
|
||||||
|
f.range,
|
||||||
|
}, { cwd = worktree, text = true }, function(log_obj)
|
||||||
|
vim.schedule(function()
|
||||||
|
if log_obj.code == 0 then
|
||||||
|
for line in (log_obj.stdout or ""):gmatch("[^\r\n]+") do
|
||||||
|
local sha, subject = line:match("^(%S+)%s+(.+)$")
|
||||||
|
if sha then
|
||||||
|
table.insert(groups[f.section], {
|
||||||
|
section = f.section,
|
||||||
|
sha = sha,
|
||||||
|
subject = subject,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log.error(
|
||||||
|
"git log %s failed: %s",
|
||||||
|
f.range,
|
||||||
|
vim.trim(log_obj.stderr or "")
|
||||||
|
)
|
||||||
|
end
|
||||||
|
pending = pending - 1
|
||||||
|
if pending == 0 then
|
||||||
|
callback(branch, groups)
|
||||||
|
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({
|
vim.system({
|
||||||
"git",
|
"git",
|
||||||
"-c",
|
"-c",
|
||||||
@@ -154,124 +291,22 @@ local function fetch_status(worktree, callback)
|
|||||||
"--branch",
|
"--branch",
|
||||||
}, { cwd = worktree, text = true }, function(obj)
|
}, { cwd = worktree, text = true }, function(obj)
|
||||||
vim.schedule(function()
|
vim.schedule(function()
|
||||||
local branch = { ahead = 0, behind = 0 }
|
|
||||||
local groups = {
|
|
||||||
Untracked = {},
|
|
||||||
Unstaged = {},
|
|
||||||
Staged = {},
|
|
||||||
Unmerged = {},
|
|
||||||
Unpushed = {},
|
|
||||||
Unpulled = {},
|
|
||||||
}
|
|
||||||
if obj.code ~= 0 then
|
if obj.code ~= 0 then
|
||||||
log.error("git status failed: %s", vim.trim(obj.stderr or ""))
|
log.error("git status failed: %s", vim.trim(obj.stderr or ""))
|
||||||
end
|
local branch = { ahead = 0, behind = 0 }
|
||||||
if obj.code == 0 then
|
local groups = {
|
||||||
for line in (obj.stdout or ""):gmatch("[^\r\n]+") do
|
Untracked = {},
|
||||||
if line:sub(1, 2) == "##" then
|
Unstaged = {},
|
||||||
branch = parse_branch_line(line)
|
Staged = {},
|
||||||
else
|
Unmerged = {},
|
||||||
local x = line:sub(1, 1)
|
Unpushed = {},
|
||||||
local y = line:sub(2, 2)
|
Unpulled = {},
|
||||||
local rest = line:sub(4)
|
}
|
||||||
local orig
|
|
||||||
local arrow = rest:find(" -> ", 1, true)
|
|
||||||
if arrow then
|
|
||||||
orig = rest:sub(1, arrow - 1)
|
|
||||||
rest = rest:sub(arrow + 4)
|
|
||||||
end
|
|
||||||
local entry = {
|
|
||||||
section = nil,
|
|
||||||
path = rest,
|
|
||||||
orig = orig,
|
|
||||||
x = x,
|
|
||||||
y = y,
|
|
||||||
}
|
|
||||||
if x == "?" and y == "?" then
|
|
||||||
entry.section = "Untracked"
|
|
||||||
table.insert(groups.Untracked, entry)
|
|
||||||
elseif repo.UNMERGED[x .. y] then
|
|
||||||
entry.section = "Unmerged"
|
|
||||||
table.insert(groups.Unmerged, entry)
|
|
||||||
else
|
|
||||||
if x ~= " " then
|
|
||||||
table.insert(groups.Staged, {
|
|
||||||
section = "Staged",
|
|
||||||
path = entry.path,
|
|
||||||
orig = entry.orig,
|
|
||||||
x = entry.x,
|
|
||||||
y = entry.y,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
if y ~= " " then
|
|
||||||
table.insert(groups.Unstaged, {
|
|
||||||
section = "Unstaged",
|
|
||||||
path = entry.path,
|
|
||||||
orig = entry.orig,
|
|
||||||
x = entry.x,
|
|
||||||
y = entry.y,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local fetches = {}
|
|
||||||
if branch.upstream and branch.ahead > 0 then
|
|
||||||
table.insert(
|
|
||||||
fetches,
|
|
||||||
{ section = "Unpushed", range = "@{upstream}..HEAD" }
|
|
||||||
)
|
|
||||||
end
|
|
||||||
if branch.upstream and branch.behind > 0 then
|
|
||||||
table.insert(
|
|
||||||
fetches,
|
|
||||||
{ section = "Unpulled", range = "HEAD..@{upstream}" }
|
|
||||||
)
|
|
||||||
end
|
|
||||||
if #fetches == 0 then
|
|
||||||
callback(branch, groups)
|
callback(branch, groups)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local pending = #fetches
|
local branch, groups = parse_porcelain(obj.stdout or "")
|
||||||
for _, f in ipairs(fetches) do
|
enrich_with_log(worktree, branch, groups, callback)
|
||||||
vim.system({
|
|
||||||
"git",
|
|
||||||
"log",
|
|
||||||
"--format=%h %s",
|
|
||||||
f.range,
|
|
||||||
}, { cwd = worktree, text = true }, function(
|
|
||||||
log_obj
|
|
||||||
)
|
|
||||||
vim.schedule(function()
|
|
||||||
if log_obj.code == 0 then
|
|
||||||
for line in
|
|
||||||
(log_obj.stdout or ""):gmatch("[^\r\n]+")
|
|
||||||
do
|
|
||||||
local sha, subject =
|
|
||||||
line:match("^(%S+)%s+(.+)$")
|
|
||||||
if sha then
|
|
||||||
table.insert(groups[f.section], {
|
|
||||||
section = f.section,
|
|
||||||
sha = sha,
|
|
||||||
subject = subject,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
log.error(
|
|
||||||
"git log %s failed: %s",
|
|
||||||
f.range,
|
|
||||||
vim.trim(log_obj.stderr or "")
|
|
||||||
)
|
|
||||||
end
|
|
||||||
pending = pending - 1
|
|
||||||
if pending == 0 then
|
|
||||||
callback(branch, groups)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
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,
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user