zshでpowerlineとvcs_info(git)を組み合せたプロンプト設定


前から改修したかったプロンプトをようやく改修したのでその時のメモ。
powerlineとvcs_info(git)を組み合せたプロンプト設定について紹介していて
よく見るoh-my-zshやpowerline-shellなどのツールは使用しない素のzsh設定でやってます。

## プロンプトに導入したこと
* プロンプトのpowerline化
* (git)addしているかチェック
* (git)commitしているかチェック
* (git)コンフリクトしていないかチェック
* (git)push漏れがないかチェック
* (git)pullできるか(最新の状態)チェック

## 実際の導入後のプロンプト

![toukou1.gif](https://qiita-image-store.s3.amazonaws.com/0/37724/397f42f8-2ed0-3a2d-3313-3ef5c85ed02a.gif "toukou1.gif")

## 設定詳細
### プロンプトのpowerline化
powerline化するにあたって、フォントの準備が必要になります
↓からMenlo-Powerline.otfをダウンロードしました。
https://gist.github.com/qrush/1595572#file-menlo-powerline-otf
ダウンロードしたフォントをクリックすると「フォントをインストール」ボタンが出るので
インストールしてターミナルのフォント設定をMenlo-Powerlineに変更します。

zshプロンプト構成は以下のように設定してます

```zsh
#   %{%B%}...%{%b%}: 「...」を太字にする。
#   %K{red}...%{%k%}: 「...」を赤の背景色にする。
#   %{%F{cyan}%}...%{%f%}: 「...」をシアン色の文字にする。
#   %n: ユーザ名
#   %?: 最後に実行したコマンドの終了ステータス
#   %(x.true-text.false-text): xが真のときはtrue-textになり
#                              偽のときはfalse-textになる。
PROMPT_HOST='%{%b%F{gray}%K{blue}%} %(?.%{%F{green}%}✔.%{%F{red}%}✘)%{%F{black}%} %n %{%F{blue}%}'
PROMPT_DIR='%{%F{black}%} %~%  '
PROMPT_SU='%(!.%{%k%F{blue}%K{black}%}⮀%{%F{yellow}%} ⚡ %{%k%F{black}%}.%{%k%F{magenta}%})⮀%{%f%k%b%}'
PROMPT='
%}$PROMPT_HOST$(update_git_info)$PROMPT_DIR$PROMPT_SU
{blue}%} %{%F{black}%}$ %{%k%F{blue}⮀%{%f%k%b%} '
SPROMPT='${WHITE}%r is correct? [n,y,a,e]: %{$reset_color%}'
```

### gitのadd, commit, コンフリトのチェック
add, commit, コンフリトのチェックはzshにバンドルされている
バージョン管理ツールのvcs_info利用すると実現できます。

```zsh
autoload vcs_info
# gitのチェックを有効にする
zstyle ":vcs_info:*" enable git
# commitしていない変更をチェックする
zstyle ":vcs_info:git:*" check-for-changes true
# gitリポジトリに対して、変更情報とリポジトリ情報を表示する
zstyle ":vcs_info:git:*" formats "⭠ %r ⮁ %b%u%c"
# gitリポジトリに対して、コンフリクトなどの情報を表示する
zstyle ":vcs_info:git:*" actionformats "⭠ %r ⮁ %b%u%c ⮁ %a"
# addしていない変更があることを示す文字列
zstyle ":vcs_info:git:*" unstagedstr " ⮁ Unstaged"
# commitしていないstageがあることを示す文字列
zstyle ":vcs_info:git:*" stagedstr " ⮁ Staged"
```

### gitのpush漏れ、pullできるかのチェック
push漏れについては、「git rev-list 追跡ブランチ..ローカルブランチ 」で
現在のpush数が割り出せるのでこれで判定してます。

pullできるかについては、ローカルブランチHEADと追跡ブランチの状態に差分が有り
且つ現在ローカルブランチにてcommitしていない状態であることを確認することで判定してます。

また、push、pullのチェックは追跡ブランチが存在することが前提になるので
git_is_track_branch関数で確認するようにしています。

最後にupdate_git_info関数でgit関連のプロンプト表示についての情報をまとめ整形するようにしてます。

```zsh
git_is_track_branch(){
    if [ "$(git remote 2>/dev/null)" != "" ]; then
        local target_tracking_branch="origin/$(git rev-parse --abbrev-ref HEAD)"
        for tracking_branch in $(git branch -ar) ; do
            if [ "$target_tracking_branch" = "$tracking_branch" ]; then
                echo "true"
            fi
        done
    fi
}

git_info_pull(){
    if [ -n "$(git_is_track_branch)" ]; then
        local current_branch="$(git rev-parse --abbrev-ref HEAD)"
        local head_rev="$(git rev-parse HEAD)"
        local origin_rev="$(git rev-parse origin/$current_branch)"
        if [ "$head_rev" != "$origin_rev" ] && [ "$(git_info_push)" = "" ]; then
                echo " ⮁ Can Be Pulled"
        fi
    fi
}

git_info_push(){
    if [ -n "$(git_is_track_branch)" ]; then
        local current_branch="$(git rev-parse --abbrev-ref HEAD)"
        local push_count=$(git rev-list origin/"$current_branch".."$current_branch" 2>/dev/null | wc -l)
        if [ "$push_count" -gt 0 ]; then
            echo " ⮁ Can Be Pushed($push_count)"
        fi
    fi
}

function update_git_info() {
    LANG=en_US.UTF-8 vcs_info
    _vcs_info=$vcs_info_msg_0_
    _git_info_push=$(git_info_push)
    _git_info_pull=$(git_info_pull)
    if [ -n "$_vcs_info" ]; then
        local BG_COLOR=green

        if [ -n "$_git_info_push" ] || [ -n "$_git_info_pull" ]; then
          BG_COLOR=yellow
          FG_COLOR=black
        fi

        if [[ -n `echo $_vcs_info | grep -Ei "merge|unstaged|staged" 2> /dev/null` ]]; then
            BG_COLOR=red
            FG_COLOR=white
        fi
        echo "%{%K{$BG_COLOR}%}⮀%{%F{$FG_COLOR}%} $_vcs_info$_git_info_push$_git_info_pull %{%F{$BG_COLOR}%K{magenta}%}⮀"
    else
       echo "%{%K{magenta}%}⮀"
    fi
}
```


## すべてをまとめたプロンプト設定は以下になります

```zsh
autoload vcs_info
zstyle ":vcs_info:*" enable git
zstyle ":vcs_info:git:*" check-for-changes true
zstyle ":vcs_info:git:*" formats "⭠ %r ⮁ %b%u%c"
zstyle ":vcs_info:git:*" actionformats "⭠ %r ⮁ %b%u%c ⮁ %a"
zstyle ":vcs_info:git:*" unstagedstr " ⮁ Unstaged"
zstyle ":vcs_info:git:*" stagedstr " ⮁ Staged"

git_is_track_branch(){
    if [ "$(git remote 2>/dev/null)" != "" ]; then
        local target_tracking_branch="origin/$(git rev-parse --abbrev-ref HEAD)"
        for tracking_branch in $(git branch -ar) ; do
            if [ "$target_tracking_branch" = "$tracking_branch" ]; then
                echo "true"
            fi
        done
    fi
}

git_info_pull(){
    if [ -n "$(git_is_track_branch)" ]; then
        local current_branch="$(git rev-parse --abbrev-ref HEAD)"
        local head_rev="$(git rev-parse HEAD)"
        local origin_rev="$(git rev-parse origin/$current_branch)"
        if [ "$head_rev" != "$origin_rev" ] && [ "$(git_info_push)" = "" ]; then
                echo " ⮁ Can Be Pulled"
        fi
    fi
}

git_info_push(){
    if [ -n "$(git_is_track_branch)" ]; then
        local current_branch="$(git rev-parse --abbrev-ref HEAD)"
        local push_count=$(git rev-list origin/"$current_branch".."$current_branch" 2>/dev/null | wc -l)
        if [ "$push_count" -gt 0 ]; then
            echo " ⮁ Can Be Pushed($push_count)"
        fi
    fi
}

function update_git_info() {
    LANG=en_US.UTF-8 vcs_info
    _vcs_info=$vcs_info_msg_0_
    _git_info_push=$(git_info_push)
    _git_info_pull=$(git_info_pull)
    if [ -n "$_vcs_info" ]; then
        local BG_COLOR=green

        if [ -n "$_git_info_push" ] || [ -n "$_git_info_pull" ]; then
          BG_COLOR=yellow
          FG_COLOR=black
        fi

        if [[ -n `echo $_vcs_info | grep -Ei "merge|unstaged|staged" 2> /dev/null` ]]; then
            BG_COLOR=red
            FG_COLOR=white
        fi
        echo "%{%K{$BG_COLOR}%}⮀%{%F{$FG_COLOR}%} $_vcs_info$_git_info_push$_git_info_pull %{%F{$BG_COLOR}%K{magenta}%}⮀"
    else
       echo "%{%K{magenta}%}⮀"
    fi
}

PROMPT_HOST='%{%b%F{gray}%K{blue}%} %(?.%{%F{green}%}✔.%{%F{red}%}✘)%{%F{black}%} %n %{%F{blue}%}'
PROMPT_DIR='%{%F{black}%} %~%  '
PROMPT_SU='%(!.%{%k%F{blue}%K{black}%}⮀%{%F{yellow}%} ⚡ %{%k%F{black}%}.%{%k%F{magenta}%})⮀%{%f%k%b%}'
PROMPT='
%{%f%b%k%}$PROMPT_HOST$(update_git_info)$PROMPT_DIR$PROMPT_SU
%{%f%b%K{blue}%} %{%F{black}%}$ %{%k%F{blue}⮀%{%f%k%b%} '
SPROMPT='${WHITE}%r is correct? [n,y,a,e]: %{$reset_color%}'
```