From d6293026259a29c9b6858a59c509fcc58a74e84c Mon Sep 17 00:00:00 2001 From: Oscar Wallberg Date: Wed, 20 May 2026 06:25:14 +0200 Subject: [PATCH] perf(git): cache index blob sha, drop rev-parse from the edit path --- lua/git/core/repo.lua | 28 +++++++++++++++++++++++----- lua/git/hunks.lua | 3 +-- test/git/repo_test.lua | 26 ++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 7 deletions(-) diff --git a/lua/git/core/repo.lua b/lua/git/core/repo.lua index 6766a00..b9fcef1 100644 --- a/lua/git/core/repo.lua +++ b/lua/git/core/repo.lua @@ -123,6 +123,16 @@ local function affects_resolve(relpath) or relpath == "FETCH_HEAD" end +---@private +---@param prefix string +function Repo:_clear_cache_prefix(prefix) + for key in pairs(self._cache) do + if vim.startswith(key, prefix) then + self._cache[key] = nil + end + end +end + ---@private ---@param relpath string function Repo:_invalidate(relpath) @@ -132,11 +142,10 @@ function Repo:_invalidate(relpath) end end if affects_resolve(relpath) then - for key in pairs(self._cache) do - if vim.startswith(key, "resolve:") then - self._cache[key] = nil - end - end + self:_clear_cache_prefix("resolve:") + end + if relpath == "index" then + self:_clear_cache_prefix("index:") end end @@ -587,6 +596,15 @@ function Repo:rev_parse(rev, short) return trimmed ~= "" and trimmed or nil end +---@param rel string worktree-relative path +---@return string? +function Repo:index_sha(rel) + local sha = self:get_cached("index:" .. rel, function(self) + return self:rev_parse(":" .. rel, false) or false + end) + return sha or nil +end + ---@alias ow.Git.Repo.ResolveStatus "ok"|"ambiguous"|"missing" ---@param abbrev string diff --git a/lua/git/hunks.lua b/lua/git/hunks.lua index 6249261..418b2e1 100644 --- a/lua/git/hunks.lua +++ b/lua/git/hunks.lua @@ -262,8 +262,7 @@ local function recompute(buf) if not state then return end - local r = state.repo - local new_sha = r:rev_parse(":" .. state.rel, true) + local new_sha = state.repo:index_sha(state.rel) if not new_sha then state.index = nil state.index_sha = nil diff --git a/test/git/repo_test.lua b/test/git/repo_test.lua index 48a77fe..b22ab12 100644 --- a/test/git/repo_test.lua +++ b/test/git/repo_test.lua @@ -71,6 +71,32 @@ t.test("get_cached memoizes by key", function() t.truthy(v1 == v2, "second call should return cached table") end) +t.test("index_sha returns the blob sha and caches it", function() + local dir = h.make_repo({ a = "x\n" }) + local r = assert(require("git.core.repo").resolve(dir)) + local sha = r:index_sha("a") + t.truthy(sha and #sha > 0, "index_sha returns the stage-0 blob sha") + t.truthy(r._cache["index:a"] ~= nil, "the result is cached") + t.eq(r:index_sha("a"), sha, "a cached call returns the same sha") +end) + +t.test("index_sha caches a negative result for an untracked path", function() + local dir = h.make_repo({ a = "x\n" }) + local r = assert(require("git.core.repo").resolve(dir)) + t.eq(r:index_sha("nope"), nil, "an untracked path has no index sha") + t.eq(r._cache["index:nope"], false, "the negative result is cached") +end) + +t.test("index_sha cache clears when the index is written", function() + local dir = h.make_repo({ a = "x\n" }) + local r = assert(require("git.core.repo").resolve(dir)) + r:index_sha("a") + t.truthy(r._cache["index:a"] ~= nil, "sha is cached before the stage") + t.write(dir, "a", "y\n") + h.git(dir, "add", "a") + wait_cleared(r, "index:a", 2000) +end) + t.test("cache clears after top-level .git change (commit)", function() local dir = h.make_repo({ a = "x" }) local r = assert(require("git.core.repo").resolve(dir))