diff --git a/lua/git/repo.lua b/lua/git/repo.lua index 03201c7..8afacee 100644 --- a/lua/git/repo.lua +++ b/lua/git/repo.lua @@ -32,7 +32,7 @@ local global = util.Emitter.new() ---@field tabs table ---@field status ow.Git.Status ---@field private _events ow.Git.Util.Emitter ----@field private _watcher? uv.uv_fs_event_t +---@field private _watchers table ---@field private _schedule_refresh fun(self: ow.Git.Repo) ---@field private _refresh_handle ow.Git.Util.DebounceHandle ---@field private _cache table @@ -107,44 +107,101 @@ function Repo:get_cached(key, compute) return value end -function Repo:start_watcher() - local watcher, err = vim.uv.new_fs_event() +---@param path string +---@param on_event fun(filename: string?) +---@return uv.uv_fs_event_t? +local function start_fs_event(path, on_event) + local watcher = vim.uv.new_fs_event() if not watcher then - util.error( - "git: failed to create fs_event for %s: %s", - self.gitdir, - err - ) - return + return nil end - local ok, start_err = watcher:start( - self.gitdir, - { recursive = true }, - function(err_, filename) - if - err_ - or filename:match("^objects/") - or filename:match("^logs/") - then - return - end - self:refresh() + local ok = watcher:start(path, {}, function(err, filename) + if err then + return end - ) + on_event(filename) + end) if not ok then - util.error("failed to watch %s: %s", self.gitdir, tostring(start_err)) watcher:close() + return nil + end + return watcher +end + +---@private +---@param path string +---@param on_change fun() +function Repo:_watch_tree(path, on_change) + if self._watchers[path] then return end - self._watcher = watcher + local stat = vim.uv.fs_stat(path) + if not stat or stat.type ~= "directory" then + return + end + local watcher = start_fs_event(path, function(filename) + if not vim.uv.fs_stat(path) then + local w = self._watchers[path] --[[@as uv.uv_fs_event_t?]] + if w then + w:stop() + w:close() + self._watchers[path] = nil + end + return + end + on_change() + if filename then + vim.schedule(function() + self:_watch_tree(vim.fs.joinpath(path, filename), on_change) + end) + end + end) + if not watcher then + return + end + self._watchers[path] = watcher + local handle = vim.uv.fs_scandir(path) + if not handle then + return + end + while true do + local name, typ = vim.uv.fs_scandir_next(handle) + if not name then + break + end + if typ == "directory" then + self:_watch_tree(vim.fs.joinpath(path, name), on_change) + end + end +end + +function Repo:start_watcher() + self._watchers = {} + local top = start_fs_event(self.gitdir, function(filename) + if + filename + and (filename:match("^objects") or filename:match("^logs")) + then + return + end + self:refresh() + end) + if not top then + util.error("git: failed to watch %s", self.gitdir) + return + end + self._watchers[self.gitdir] = top + self:_watch_tree(vim.fs.joinpath(self.gitdir, "refs"), function() + self:refresh() + end) end function Repo:close() - if self._watcher then - self._watcher:stop() - self._watcher:close() - self._watcher = nil + for _, watcher in pairs(self._watchers) do + watcher:stop() + watcher:close() end + self._watchers = {} self._refresh_handle.close() end