From 4caa1b84df5d19ec486d3cd6aecd05285d95f12b Mon Sep 17 00:00:00 2001 From: Oscar Wallberg Date: Tue, 28 Apr 2026 05:36:23 +0200 Subject: [PATCH] perf(git): run git log sync with bounded count --- lua/git/log.lua | 69 +++++++++++++++++++++++----------------- lua/git/sidebar.lua | 77 ++++++++++++++++++++++----------------------- 2 files changed, 78 insertions(+), 68 deletions(-) diff --git a/lua/git/log.lua b/lua/git/log.lua index 3ea68c1..29c80e9 100644 --- a/lua/git/log.lua +++ b/lua/git/log.lua @@ -6,44 +6,57 @@ local util = require("util") local M = {} local LOG_FORMAT = "%h %ad {%an}%d %s" +local DEFAULT_MAX_COUNT = 1000 + +---@class ow.Git.LogOpts +---@field max_count integer? cap on commits to show. Nil uses the default, <= 0 means "all" + +---@param opts ow.Git.LogOpts? +function M.show(opts) + opts = opts or {} + local max_count = opts.max_count + if max_count == nil then + max_count = DEFAULT_MAX_COUNT + end -function M.show() local _, worktree = repo.resolve_cwd() if not worktree then log.warning("not in a git repository") return end + local cmd = { + "git", + "log", + "--graph", + "--all", + "--decorate", + "--date=short", + "--format=format:" .. LOG_FORMAT, + } + if max_count > 0 then + table.insert(cmd, "--max-count=" .. max_count) + end + + local result = vim.system(cmd, { cwd = worktree, text = true }):wait() + if result.code ~= 0 then + log.error("git log failed: %s", vim.trim(result.stderr or "")) + return + end + local buf = git.new_scratch() vim.b[buf].git_worktree = worktree - - vim.system( - { - "git", - "log", - "--graph", - "--all", - "--decorate", - "--date=short", - "--format=format:" .. LOG_FORMAT, - }, - { cwd = worktree, text = true }, - vim.schedule_wrap(function(result) - if not vim.api.nvim_buf_is_valid(buf) then - return - end - if result.code ~= 0 then - log.error("git log failed: %s", vim.trim(result.stderr or "")) - return - end - local lines = util.split_lines(result.stdout or "") - vim.bo[buf].modifiable = true - vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines) - vim.bo[buf].modifiable = false - vim.bo[buf].modified = false - vim.bo[buf].filetype = "gitlog" - end) + vim.bo[buf].modifiable = true + vim.api.nvim_buf_set_lines( + buf, + 0, + -1, + false, + util.split_lines(result.stdout or "") ) + vim.bo[buf].modifiable = false + vim.bo[buf].modified = false + vim.bo[buf].filetype = "gitlog" end return M diff --git a/lua/git/sidebar.lua b/lua/git/sidebar.lua index 5a0839a..a4a5356 100644 --- a/lua/git/sidebar.lua +++ b/lua/git/sidebar.lua @@ -214,14 +214,13 @@ local function parse_porcelain(stdout) 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). +---Fill in the Unpushed/Unpulled groups from `git log` for any non-zero +---ahead/behind counter. Capped at 200 commits per range so a wildly +---divergent branch can't blow the sidebar's render budget. ---@param worktree string ---@param branch ow.Git.BranchInfo ---@param groups table ----@param callback fun(branch: ow.Git.BranchInfo, groups: table) -local function enrich_with_log(worktree, branch, groups, callback) +local function enrich_with_log(worktree, branch, groups) local fetches = {} if branch.upstream and branch.ahead > 0 then table.insert( @@ -235,45 +234,41 @@ local function enrich_with_log(worktree, branch, groups, callback) { section = "Unpulled", range = "HEAD..@{upstream}" } ) end - if #fetches == 0 then - callback(branch, groups) - return - end - local pending = #fetches + -- Submit both subprocesses before waiting so they run concurrently + -- rather than sequentially. Total time = max, not sum. + local pending = {} for _, f in ipairs(fetches) do - vim.system( - { + table.insert(pending, { + f = f, + sys = vim.system({ "git", "log", + "--max-count=200", "--format=%h %s", f.range, - }, - { cwd = worktree, text = true }, - vim.schedule_wrap(function(log_obj) - 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 "") - ) + }, { cwd = worktree, text = true }), + }) + end + for _, p in ipairs(pending) do + local result = p.sys:wait() + if result.code == 0 then + for line in (result.stdout or ""):gmatch("[^\r\n]+") do + local sha, subject = line:match("^(%S+)%s+(.+)$") + if sha then + table.insert(groups[p.f.section], { + section = p.f.section, + sha = sha, + subject = subject, + }) end - pending = pending - 1 - if pending == 0 then - callback(branch, groups) - end - end) - ) + end + else + log.error( + "git log %s failed: %s", + p.f.range, + vim.trim(result.stderr or "") + ) + end end end @@ -287,7 +282,8 @@ end 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) + enrich_with_log(worktree, branch, groups) + callback(branch, groups) return end vim.system( @@ -316,7 +312,8 @@ local function fetch_status(worktree, prefetched_stdout, callback) return end local branch, groups = parse_porcelain(obj.stdout or "") - enrich_with_log(worktree, branch, groups, callback) + enrich_with_log(worktree, branch, groups) + callback(branch, groups) end) ) end