From 0fdcda98cf80e45350833da351a6a55ca64644e8 Mon Sep 17 00:00:00 2001 From: Chris Antos Date: Mon, 5 Dec 2022 13:52:14 -0800 Subject: [PATCH] Fix https://github.com/cmderdev/cmder/issues/2789 Holding ^C made git.exe hang while cmd.exe (Clink) updated the prompt. The prompt script had three problems: 1. It invoked `git config` every time a prompt was displayed, to figure out where to skip invoking `git status`. But it even did that if the current directory wasn't part of a git repo. 2. It invoked `git config` two times for every single prompt, to attempt to improve performance if the user disables `git status` coloring. But two times for every single prompt is expensive, so it has the opposite effect in the general case, and noticeably degrades performance. 3. It invoked `git config` using a blocking call, instead of using the async prompt support in Clink. That significantly reduced the benefit of having used async prompt filtering for `git status`. Now the `git config` invocations use async prompt filtering, which lets the prompt display instantaneously. It also now uses a timer to avoid invoking `git config` repeatedly when new prompts show up in rapid succession. Also, the `cmderGitStatusOptIn` variable is no longer leaked into the Lua global namespace. These changes resolve the issue: holding ^C is very fast and no longer causes git.exe to hang. --- vendor/clink.lua | 90 +++++++++++++++++++++++++++--------------------- 1 file changed, 50 insertions(+), 40 deletions(-) diff --git a/vendor/clink.lua b/vendor/clink.lua index d0e3e6c72..6a9058e1f 100644 --- a/vendor/clink.lua +++ b/vendor/clink.lua @@ -408,58 +408,72 @@ local function get_svn_status() return true end ---- --- Use a prompt coroutine to get git status in the background. --- Cache the info so we can reuse it next time to reduce flicker. ---- -local function get_git_info_table() - local info = clink_promptcoroutine(function () - return get_git_status() - end) - if not info then - info = cached_info.git_info or {} - else - cached_info.git_info = info - end - return info -end - --- -- Get the status of working dir -- @return {bool} --- +local last_git_status_time = nil +local last_git_status_setting = true local function get_git_status_setting() + local time = os.clock() + local last_time = last_git_status_time + last_git_status_time = time + if last_time and time >= 0 and time - last_time < 10 then + return last_git_status_setting + end + -- When async prompt filtering is available, check the -- prompt_overrideGitStatusOptIn config setting for whether to ignore the -- cmder.status and cmder.cmdstatus git config opt-in settings. if clink.promptcoroutine and io.popenyield and settings.get("prompt.async") then if prompt_overrideGitStatusOptIn then + last_git_status_setting = true return true end end - local gitStatusConfig = io.popen("git --no-pager config cmder.status 2>nul") - + local gitStatusConfig = io_popenyield("git --no-pager config cmder.status 2>nul") for line in gitStatusConfig:lines() do if string.match(line, 'false') then gitStatusConfig:close() + last_git_status_setting = false return false end end + gitStatusConfig:close() - local gitCmdStatusConfig = io.popen("git --no-pager config cmder.cmdstatus 2>nul") + local gitCmdStatusConfig = io_popenyield("git --no-pager config cmder.cmdstatus 2>nul") for line in gitCmdStatusConfig:lines() do if string.match(line, 'false') then gitCmdStatusConfig:close() + last_git_status_setting = false return false end end - gitStatusConfig:close() gitCmdStatusConfig:close() + last_git_status_setting = true return true end +--- +-- Use a prompt coroutine to get git status in the background. +-- Cache the info so we can reuse it next time to reduce flicker. +--- +local function get_git_info_table() + local info = clink_promptcoroutine(function () + -- Use git status if allowed. + local cmderGitStatusOptIn = get_git_status_setting() + return cmderGitStatusOptIn and get_git_status() or {} + end) + if not info then + info = cached_info.git_info or {} + else + cached_info.git_info = info + end + return info +end + local function git_prompt_filter() -- Don't do any git processing if the prompt doesn't want to show git info. @@ -477,7 +491,6 @@ local function git_prompt_filter() local git_dir = get_git_dir() local color - cmderGitStatusOptIn = get_git_status_setting() if git_dir then local branch = get_git_branch(git_dir) if branch then @@ -487,28 +500,25 @@ local function git_prompt_filter() cached_info.git_dir = git_dir cached_info.git_branch = branch end - -- Use git status if allowed. - if cmderGitStatusOptIn then - -- if we're inside of git repo then try to detect current branch - -- Has branch => therefore it is a git folder, now figure out status - local gitInfo = get_git_info_table() - local gitStatus = gitInfo.status - local gitConflict = gitInfo.conflict - - if gitStatus == nil then - color = colors.nostatus - elseif gitStatus then - color = colors.clean - else - color = colors.dirty - end - if gitConflict then - color = colors.conflict - end - else + -- If we're inside of git repo then try to detect current branch + -- Has branch => therefore it is a git folder, now figure out status + local gitInfo = get_git_info_table() + local gitStatus = gitInfo.status + local gitConflict = gitInfo.conflict + + if gitStatus == nil then color = colors.nostatus + elseif gitStatus then + color = colors.clean + else + color = colors.dirty end + + if gitConflict then + color = colors.conflict + end + clink.prompt.value = string.gsub(clink.prompt.value, "{git}", " "..color.."("..verbatim(branch)..")") return false end