diff --git a/doc/nvim-tree-lua.txt b/doc/nvim-tree-lua.txt index 5cdaad72bd1..e2c894d10bc 100644 --- a/doc/nvim-tree-lua.txt +++ b/doc/nvim-tree-lua.txt @@ -626,6 +626,7 @@ Git integration with icons and colors. *nvim-tree.git.timeout* Kills the git process after some time if it takes too long. + Git integration will be disabled after 10 git jobs exceed this timeout. Type: `number`, Default: `400` (ms) You will still need to set |renderer.icons.show.git| `= true` or diff --git a/lua/nvim-tree/git/init.lua b/lua/nvim-tree/git/init.lua index 7bf7f542d85..c5be4ef02f1 100644 --- a/lua/nvim-tree/git/init.lua +++ b/lua/nvim-tree/git/init.lua @@ -176,6 +176,13 @@ function M.purge_state() M.cwd_to_project_root = {} end +--- Disable git integration permanently +function M.disable_git_integration() + log.line("git", "disabling git integration") + M.purge_state() + M.config.git.enable = false +end + function M.setup(opts) M.config.git = opts.git M.config.filesystem_watchers = opts.filesystem_watchers diff --git a/lua/nvim-tree/git/runner.lua b/lua/nvim-tree/git/runner.lua index f71ac71f1dc..664b8c73553 100644 --- a/lua/nvim-tree/git/runner.lua +++ b/lua/nvim-tree/git/runner.lua @@ -1,9 +1,13 @@ local log = require "nvim-tree.log" local utils = require "nvim-tree.utils" +local notify = require "nvim-tree.notify" local Runner = {} Runner.__index = Runner +local timeouts = 0 +local MAX_TIMEOUTS = 5 + function Runner:_parse_status_output(status, path) -- replacing slashes if on windows if vim.fn.has "win32" == 1 then @@ -66,38 +70,41 @@ function Runner:_log_raw_output(output) end function Runner:_run_git_job() - local handle, pid + local handle local stdout = vim.loop.new_pipe(false) local stderr = vim.loop.new_pipe(false) local timer = vim.loop.new_timer() - local function on_finish(rc) + local function on_finish(rc, signal) + log.line("git", "rc=%d, signal=%s", tostring(rc), tostring(signal)) self.rc = rc or 0 - if timer:is_closing() or stdout:is_closing() or stderr:is_closing() or (handle and handle:is_closing()) then - return + + if timer and not timer:is_closing() then + timer:stop() + timer:close() + end + if stdout and not stdout:is_closing() then + stdout:read_stop() + stdout:close() end - timer:stop() - timer:close() - stdout:read_stop() - stderr:read_stop() - stdout:close() - stderr:close() - if handle then + if stderr and not stderr:is_closing() then + stderr:read_stop() + stderr:close() + end + if handle and not handle:is_closing() then handle:close() end - - pcall(vim.loop.kill, pid) end local opts = self:_getopts(stdout, stderr) log.line("git", "running job with timeout %dms", self.timeout) log.line("git", "git %s", table.concat(utils.array_remove_nils(opts.args), " ")) - handle, pid = vim.loop.spawn( + handle = vim.loop.spawn( "git", opts, - vim.schedule_wrap(function(rc) - on_finish(rc) + vim.schedule_wrap(function(rc, signal) + on_finish(rc, signal) end) ) @@ -105,7 +112,11 @@ function Runner:_run_git_job() self.timeout, 0, vim.schedule_wrap(function() - on_finish(-1) + log.line("git", "timed out, killing") + self.timed_out = true + if handle then + handle:kill "sigkill" + end end) ) @@ -131,7 +142,7 @@ end function Runner:_wait() local function is_done() - return self.rc ~= nil + return self.rc ~= nil or self.timed_out end while not vim.wait(30, is_done) do @@ -149,7 +160,8 @@ function Runner.run(opts) list_ignored = opts.list_ignored, timeout = opts.timeout or 400, output = {}, - rc = nil, -- -1 indicates timeout + rc = nil, + timed_out = false, }, Runner) self:_run_git_job() @@ -157,8 +169,19 @@ function Runner.run(opts) log.profile_end(profile) - if self.rc == -1 then + if self.timed_out then log.line("git", "job timed out %s %s", opts.project_root, opts.path) + timeouts = timeouts + 1 + if timeouts == MAX_TIMEOUTS then + notify.warn( + string.format( + "%d git jobs have timed out after %dms, disabling git integration. Please consider increasing git.timeout", + timeouts, + opts.timeout + ) + ) + require("nvim-tree.git").disable_git_integration() + end elseif self.rc ~= 0 then log.line("git", "job fail rc %d %s %s", self.rc, opts.project_root, opts.path) else