From 104479187cb2d3839ef1c5d7bd011344c8bf0b97 Mon Sep 17 00:00:00 2001 From: Oscar Wallberg Date: Thu, 7 May 2026 16:25:32 +0200 Subject: [PATCH] perf(git/repo): cache ref/file lookups, invalidate on status refresh --- lua/git/cmd.lua | 29 ++++++++++------- lua/git/repo.lua | 85 ++++++++++++++++++++++++++++++------------------ 2 files changed, 71 insertions(+), 43 deletions(-) diff --git a/lua/git/cmd.lua b/lua/git/cmd.lua index f8b9555..cfb1ed4 100644 --- a/lua/git/cmd.lua +++ b/lua/git/cmd.lua @@ -317,22 +317,26 @@ end ---@param dir string ---@return string[] local function list_files(r, dir) - local cmd = { "git", "ls-files" } - if dir ~= "" then - table.insert(cmd, dir) - end - local out = util.exec(cmd, { cwd = r.worktree, silent = true }) - return out and util.split_lines(out) or {} + return r:get_cached("files:" .. dir, function(self) + local cmd = { "git", "ls-files" } + if dir ~= "" then + table.insert(cmd, dir) + end + local out = util.exec(cmd, { cwd = self.worktree, silent = true }) + return out and util.split_lines(out) or {} + end) end ---@param r ow.Git.Repo ---@return string[] local function list_remotes(r) - local out = util.exec( - { "git", "remote" }, - { cwd = r.worktree, silent = true } - ) - return out and util.split_lines(out) or {} + return r:get_cached("remotes", function(self) + local out = util.exec( + { "git", "remote" }, + { cwd = self.worktree, silent = true } + ) + return out and util.split_lines(out) or {} + end) end ---@type table @@ -469,7 +473,8 @@ function M.complete_rev(arg_lead) local colon = arg_lead:find(":", 1, true) if not colon then - local refs = r:list_refs() + local refs = {} + vim.list_extend(refs, r:list_refs()) vim.list_extend(refs, r:list_pseudo_refs()) vim.list_extend(refs, r:list_stash_refs()) return prefix_filter(refs, arg_lead) diff --git a/lua/git/repo.lua b/lua/git/repo.lua index e0a4a1d..03201c7 100644 --- a/lua/git/repo.lua +++ b/lua/git/repo.lua @@ -35,6 +35,7 @@ local global = util.Emitter.new() ---@field private _watcher? uv.uv_fs_event_t ---@field private _schedule_refresh fun(self: ow.Git.Repo) ---@field private _refresh_handle ow.Git.Util.DebounceHandle +---@field private _cache table local Repo = {} Repo.__index = Repo @@ -56,6 +57,7 @@ function Repo:_fetch_status() STATUS_CMD, { cwd = self.worktree, text = true }, vim.schedule_wrap(function(obj) + self._cache = {} if obj.code ~= 0 then util.error("git status failed: %s", vim.trim(obj.stderr or "")) return @@ -82,6 +84,7 @@ function Repo.new(gitdir, worktree) tabs = {}, status = status.parse(""), _events = util.Emitter.new(), + _cache = {}, }, Repo) self._schedule_refresh, self._refresh_handle = util.debounce(Repo._fetch_status, 50) @@ -90,6 +93,20 @@ function Repo.new(gitdir, worktree) return self end +---@generic T +---@param key string +---@param compute fun(self: ow.Git.Repo): T +---@return T +function Repo:get_cached(key, compute) + local hit = self._cache[key] + if hit ~= nil then + return hit + end + local value = compute(self) + self._cache[key] = value + return value +end + function Repo:start_watcher() local watcher, err = vim.uv.new_fs_event() if not watcher then @@ -168,18 +185,20 @@ end ---@return string[] function Repo:list_refs() - local out = util.exec({ - "git", - "for-each-ref", - "--format=%(refname:short)", - "refs/heads", - "refs/tags", - "refs/remotes", - }, { cwd = self.worktree, silent = true }) - if not out then - return {} - end - return util.split_lines(out) + return self:get_cached("refs", function(self) + local out = util.exec({ + "git", + "for-each-ref", + "--format=%(refname:short)", + "refs/heads", + "refs/tags", + "refs/remotes", + }, { cwd = self.worktree, silent = true }) + if not out then + return {} + end + return util.split_lines(out) + end) end local PSEUDO_REFS = { @@ -194,31 +213,35 @@ local PSEUDO_REFS = { ---@return string[] function Repo:list_pseudo_refs() - local refs = {} - for _, name in ipairs(PSEUDO_REFS) do - if name == "HEAD" or vim.uv.fs_stat(self.gitdir .. "/" .. name) then - table.insert(refs, name) + return self:get_cached("pseudo_refs", function(self) + local refs = {} + for _, name in ipairs(PSEUDO_REFS) do + if name == "HEAD" or vim.uv.fs_stat(self.gitdir .. "/" .. name) then + table.insert(refs, name) + end end - end - return refs + return refs + end) end ---@return string[] function Repo:list_stash_refs() - if not vim.uv.fs_stat(self.gitdir .. "/refs/stash") then - return {} - end - local refs = { "stash" } - local out = util.exec( - { "git", "stash", "list", "--pretty=format:%gd" }, - { cwd = self.worktree, silent = true } - ) - if out then - for _, entry in ipairs(util.split_lines(out)) do - table.insert(refs, entry) + return self:get_cached("stash_refs", function(self) + if not vim.uv.fs_stat(self.gitdir .. "/refs/stash") then + return {} end - end - return refs + local refs = { "stash" } + local out = util.exec( + { "git", "stash", "list", "--pretty=format:%gd" }, + { cwd = self.worktree, silent = true } + ) + if out then + for _, entry in ipairs(util.split_lines(out)) do + table.insert(refs, entry) + end + end + return refs + end) end ---@param rev string