Oh My Zsh git status 响应过慢的解决方案
ohmyzsh/ohmyzsh内置的众多主题中大部分都支持显示git
相关的信息。
比如默认的robbyrussell
主题:
你可以通过
grep -IRl '$(git_prompt_info)' ~/.oh-my-zsh/themes | sort -u | xargs -I{} basename {} .zsh-theme
列出Oh My Zsh所有支持显示git status
的主题。
不过当你git clone
一个有很多commit历史的Repository的时候,你就会发现Oh My Zsh在终端的体验极差,按一次回车可能需要得等好几秒。
原因是git status
这个命令占用了主要的耗时,git status
需要找到索引文件和工作区目录之间的差别,这就需要扫描所有的索引文件,产生大量的磁盘IO(stat(2)
)。
The most resource-intensive part of the
status
command is finding the difference between index and workdir (git_diff_index_to_workdir
in libgit2). Index is a list of all files in the git repository with their last modification times.
以i9-9900T(8C16T), 16G RAM, 512G M.2 NVME SSD
和chromium/chromium
为例,单次git status
耗时约800ms。
$ time git status --porcelain
git status --porcelain 0.65s user 0.66s system 166% cpu 0.785 total
以2015款MacBook Pro(i5-5287U 2C4T, 16G 1867MHz DDR3, 512G SATA SSD
)为例,单次耗时约5s。
$ time git status --porcelain
git status --porcelain 1.53s user 8.15s system 180% cpu 5.374 total
以ys
主题为例,其文件路径~/.oh-my-zsh/themes/ys.zsh-theme
,代码片断:
# ......
# Git info
local git_info='$(git_prompt_info)'
# ......
# Prompt format:
#
# PRIVILEGES USER @ MACHINE in DIRECTORY on git:BRANCH STATE [TIME] C:LAST_EXIT_CODE
# $ COMMAND
#
# For example:
#
# % ys @ ys-mbp in ~/.oh-my-zsh on git:master x [21:47:42] C:0
# $
PROMPT="
%{$terminfo[bold]$fg[blue]%}#%{$reset_color%} \
%(#,%{$bg[yellow]%}%{$fg[black]%}%n%{$reset_color%},%{$fg[cyan]%}%n) \
%{$fg[white]%}@ \
%{$fg[green]%}%m \
%{$fg[white]%}in \
%{$terminfo[bold]$fg[yellow]%}%~%{$reset_color%}\
${hg_info}\
${git_info}\
${venv_info}\
\
%{$fg[white]%}[%*] $exit_code
%{$terminfo[bold]$fg[red]%}$ %{$reset_color%}"
其git
信息的显示是通过调用$(git_prompt_info)
来实现。git_prompt_info
函数实现位于文件~/.oh-my-zsh/lib/git.zsh
:
# ......
function __git_prompt_git() {
GIT_OPTIONAL_LOCKS=0 command git "$@"
}
function git_prompt_info() {
# ......
echo "${ZSH_THEME_GIT_PROMPT_PREFIX}${ref}${upstream}$(parse_git_dirty)${ZSH_THEME_GIT_PROMPT_SUFFIX}"
}
# Checks if working tree is dirty
function parse_git_dirty() {
local STATUS
local -a FLAGS
FLAGS=('--porcelain')
if [[ "$(__git_prompt_git config --get oh-my-zsh.hide-dirty)" != "1" ]]; then
# ......
STATUS=$(__git_prompt_git status ${FLAGS} 2> /dev/null | tail -1)
fi
if [[ -n $STATUS ]]; then
echo "$ZSH_THEME_GIT_PROMPT_DIRTY"
else
echo "$ZSH_THEME_GIT_PROMPT_CLEAN"
fi
}
可以看出,最终git_prompt_info
会调用git status --porcelain ...
来获取git
相关的信息。
最近找到一个解决Oh My Zsh git status
过慢的方法:
利用romkatv/gitstatus作为git status
的后端用于显示git
当前目录的状态。
我们可以参考Using from Zsh一节的描述,利用source ~/gitstatus/gitstatus.prompt.zsh
来达到目的。不过其默认的主题样式可能并不是你需要的。
也可以考虑source ~/gitstatus/gitstatus.plugin.zsh
,自行定义提示信息。gitstatus.plugin.zsh
相对与gitstatus.prompt.zsh
而言更加地底层(low-level)。
一个比较好的方式是,romkatv/gitstatus组合我们现在正在使用的主题,这样可以做到改动最小,同时保留我们的使用习惯。
还是以ys
主题为例,我们可以这样修改~/.oh-my-zsh/themes/ys.zsh-theme
:
diff --git a/themes/ys.zsh-theme b/themes/ys.zsh-theme
index 303c898b..f91467eb 100644
--- a/themes/ys.zsh-theme
+++ b/themes/ys.zsh-theme
@@ -13,7 +13,8 @@ YS_VCS_PROMPT_DIRTY=" %{$fg[red]%}x"
YS_VCS_PROMPT_CLEAN=" %{$fg[green]%}o"
# Git info
-local git_info='$(git_prompt_info)'
+source ~/.gitstatus/gitstatus.prompt.zsh
+local git_info=' git:$GITSTATUS_PROMPT'
ZSH_THEME_GIT_PROMPT_PREFIX="${YS_VCS_PROMPT_PREFIX1}git${YS_VCS_PROMPT_PREFIX2}"
ZSH_THEME_GIT_PROMPT_SUFFIX="$YS_VCS_PROMPT_SUFFIX"
ZSH_THEME_GIT_PROMPT_DIRTY="$YS_VCS_PROMPT_DIRTY"
然后新开一个Terminal就可以看到效果了:
注意,~/.zshrc
里面设置的ZSH_THEME
需要是ys
。
当然,也可以直接使用gitstatus
作者的主题romkatv/powerlevel10k
。
替换之后,后台会启动gitstatusd
进程,其中-t
是线程数,默认是CPU逻辑核心数量的两倍。
$ ps aux | grep '[g]itstatusd'
lei 171244 0.0 0.0 6964 1068 pts/1 Sl 21:41 0:00 /home/lei/.cache/gitstatus/gitstatusd-linux-x86_64 -G v1.3.1 -s -1 -u -1 -c -1 -d -1 -v FATAL -t 32
The diffing algorithm in gitstatusd was designed from the ground up with the intention of using it concurrently from multiple threads. With a fast SSD,
status
is CPU bound, so taking advantage of all available CPU cores is an obvious way to yield results faster.
注意事项
-
建议复制
~/.oh-my-zsh/themes
内的主题,修改复制的主题。主要原因在于Oh My Zsh也是一个git Repository,每次更新的时候从上游拉取更新到本地,这种情况有可能导致merge冲突进而导致Oh My Zsh更新失败。以
ys.zsh-theme
主题为例,我们可以将其复制为ys-fastgit.zsh-theme
,再应用前述的修改之后,最后修改~/.zshrc
设置ZSH_THEME="ys-fastgit"
。
附录 - 完整的脚本
git clone --depth=1 https://github.com/romkatv/gitstatus.git ~/.gitstatus
cp ~/.oh-my-zsh/themes/ys.zsh-theme ~/.oh-my-zsh/themes/ys-fastgit.zsh-theme
# 用
# source ~/.gitstatus/gitstatus.prompt.zsh
# local git_info=' git:$GITSTATUS_PROMPT'
# 替换 ys-fastgit.zsh-theme 中的
# local git_info='$(git_prompt_info)'
# 修改 ~/.zshrc 使得 ZSH_THEME="ys-fastgit"