zsh には vcs-info というものがあり、これを利用すると Git や Mercurial のブランチ名などをプロンプトに表示することができます。 (例: http://d.hatena.ne.jp/mollifier/20090814/p1)
しかし使ってみるとわかるのですが、Cygwin だと結構遅いです。 シェルのプロンプトが表示されるたびにちょくちょく待たされる感じになってしまい、かなりストレスを感じます。
どうにかならないものかと思っていたのですが、ひとまず実用上問題なさそうなところまで高速化できたので共有しておきます。 (私が Mercurial メインで使っているので、Mercurial を使う際の高速化が中心です。Git だとまだ改善の余地が多いかと思います)
vcs_info はたくさんのバージョン管理システムに対応していますが、 現在のディレクトリがそれぞれのシステムに対応しているか調べるため、 多くのシステムを有効にしていると重くなってしまいます。
デフォルトでは全部有効になっているので、使うものだけを指定するようにします。
zstyle ':vcs_info:*' enable hg git
ここでは Mercurial と Git を指定しています。 ここに書かれた順にチェックがされるので、Mercurial の作業コピーで作業することが多い人は、 このように hg を先に書いた方がレスポンスが速くなります。
調べてみたところ、Cygwin だとコマンド置換 $(...) が遅いのが一番のネックになっているようです。 例えば私の環境では
zsh -fc 'repeat 100 $()'
が、Linux(Debian) では 0.04 秒ほどで終了するのに対し、Cygwin では 4 秒弱かかりました。
そこで、なるべくコマンド置換をしないように修正します。
--- /usr/share/zsh/4.3.12/functions/VCS_INFO_bydir_detect 2011-08-08 04:59:41.001000000 +0900 +++ ./VCS_INFO_bydir_detect 2012-01-19 21:52:35.293434600 +0900 @@ -6,7 +6,7 @@ local dirname=$1 local basedir="." realbasedir file -realbasedir="$(VCS_INFO_realpath ${basedir})" +realbasedir="${basedir:A}" while [[ ${realbasedir} != '/' ]]; do [[ -r ${realbasedir} ]] || return 1 if [[ -n ${vcs_comm[detect_need_file]} ]] ; then @@ -20,7 +20,7 @@ fi basedir=${basedir}/.. - realbasedir="$(VCS_INFO_realpath ${basedir})" + realbasedir="${basedir:A}" done [[ ${realbasedir} == "/" ]] && return 1
現在のディレクトリが Mercurial 管理下にあるかどうかを調べるため、 .hg ディレクトリがあるかどうかを現在のディレクトリからルートディレクトリまで順に探しています。
../ 混じりのパスを実際のパスに直すため VCS_INFO_realpath という関数を使っているのですが、 これは変数展開の A 修飾子でできるのでそちらを使います。
--- /usr/share/zsh/4.3.12/functions/VCS_INFO_formats 2011-08-08 04:59:41.001000000 +0900 +++ ./VCS_INFO_formats 2012-01-20 02:42:04.744074700 +0900 @@ -29,7 +29,16 @@ ) hook_com[base-name]="${${hook_com[base]}:t}" hook_com[base-name_orig]="${hook_com[base_name]}" -hook_com[subdir]="$(VCS_INFO_reposub ${hook_com[base]})" + +# コマンド置換が遅い Cygwin で高速化するために VCS_INFO_reposub を展開 +#hook_com[subdir]="$(VCS_INFO_reposub ${hook_com[base]})" +local base=${hook_com[base]%%/##} +if [[ ${PWD} == ${base}/* ]]; then + hook_com[subdir]=${PWD#$base/} +else + hook_com[subdir]='.' +fi + hook_com[subdir_orig]="${hook_com[subdir]}" VCS_INFO_hook 'post-backend'
VCS_INFO_reposub はここでしか使われていなかったので、中身を展開してしまいました。
Mercurial だけを使うならここまででいいのですが、とりあえず Git に対しても vcs-info を使うようにしていたところ、 特にバージョン管理下にないディレクトリにいるときに重い状態でした。
これは、バージョン管理下にないディレクトリに対しては、Mercurial のワーキングコピーであるかどうかのチェックと Git のワーキングコピーであるかどうかのチェックが両方走っていて、Git のチェックが重かったのが原因でした。
よって次の修正を加えています。
--- /usr/share/zsh/4.3.12/functions/VCS_INFO_detect_git 2011-08-08 04:59:46.001000000 +0900 +++ ./VCS_INFO_detect_git 2012-01-23 17:40:41.272189100 +0900 @@ -6,8 +6,10 @@ [[ $1 == '--flavours' ]] && { print -l git-p4 git-svn; return 0 } -if VCS_INFO_check_com ${vcs_comm[cmd]} && ${vcs_comm[cmd]} rev-parse --is-inside-work-tree &> /dev/null ; then - vcs_comm[gitdir]="$(${vcs_comm[cmd]} rev-parse --git-dir 2> /dev/null)" || return 1 +if VCS_INFO_check_com ${vcs_comm[cmd]} ; then + local gitdir + ${vcs_comm[cmd]} rev-parse --git-dir 2> /dev/null | read gitdir || return 1 + vcs_comm[gitdir]=$gitdir if [[ -d ${vcs_comm[gitdir]}/svn ]] ; then vcs_comm[overwrite_name]='git-svn' elif [[ -d ${vcs_comm[gitdir]}/refs/remotes/p4 ]] ; then vcs_comm[overwrite_name]='git-p4' ; fi return 0
現在のディレクトリが Git の管理下にあるかどうかを調べているのですが、 git rev-parse --git-dir の戻り値をチェックすれば git rev-parse --is-inside-work-tree は不要だと思うので前者だけにしました。 コマンド置換が read になっているのは、ほぼ気分の問題です。(微妙に速くなってる気もしますが、大して変わりません)
use-simple true の設定にするときは hexdump を忘れずに入れるようにしましょう。これがないと速くなりません。util-linux パッケージにあります。 (ただし、check-for-changes を有効にするときは use-simple false でないといけません)
Cygwin 上で zsh の vcs_info の高速化を試みました。
この修正を加えたスクリプトは https://github.com/kyanagi/faster-vcs-info にあります。(zsh 4.3.12 のスクリプトを修正したものです)
使う際は適当なディレクトリに置いて、そのディレクトリを$fpath の頭の方(システムの vcs_info より優先されるように)に加えてください。