読者です 読者をやめる 読者になる 読者になる

GeekFactory

int128.hatenablog.com

git statusを利用したリポジトリ情報のプロンプト表示

zsh

zshでGitのステータス情報をプロンプトに表示するにはvcs_infoを使う方法が一般的ですが、vcs_infoで得られる情報には限りがあります。そこで、git statusを使ってプロンプトを表示する方法を調べてみました。なお、OS XのGit 2.3.2で確認しています。

git statusで得られる情報

git status --porcelain --branch を実行するとブランチ、ワーキングツリー、インデックスの状態が得られます。--porcelainを付けることで機械処理に適したフォーマットが得られます。このコマンドの実行結果は下記の2つの部分で構成されます。

ローカルブランチの状態(1行目)

ローカルブランチの状態 表示
リモートブランチと同期している ## master...origin/master
リモートブランチより古い ## master...origin/master [behind 1]
リモートブランチより新しい ## master...origin/master [ahead 1]

ファイルの状態(2行目以降)

更新されたファイルの一覧が表示されます。ファイルパスの左側には状態を表す2文字のフラグが表示されます。1文字目はインデックス、2文字目はワーキングツリーの状態を表します。フラグの意味は下表の通り定義されています。

フラグ ファイルの状態
M 変更された
A 追加された
D 削除された
R リネームされた
C コピーされた
U 変更されたがマージされていない
? インデックスもしくはワーキングツリーに追加されていない

既存のファイルを変更したが git add でインデックスを更新していない場合は下記になります。

% git status --porcelain --branch
## master...origin/master
 M README.md

この後にgit addでインデックスを更新すると下記になります。

% git add README.md
% git status --porcelain --branch
## master...origin/master
M  README.md

新しくファイルを追加したがまだgit addしていない場合は下記になります。

% git add newfile
% git status --porcelain --branch
## master...origin/master
?? newfile

リポジトリがない場所でgit statusを実行すると、下記のエラーメッセージが表示されて、終了ステータスが0以外になります。

% git status
fatal: Not a git repository (or any of the parent directories): .git

git statusの情報を利用する

zshでgit statusの情報を利用するには、まず実行結果を行単位に区切って配列に入れると扱いやすくなります。

git_status=("${(f)$(git status --porcelain --branch 2> /dev/null)}")

ローカルブランチの状態は1行目で得られます。先頭に##が付くので取り除いておきます。

typeset -A git_info
git_info[branch]="${${git_status[1]}#\#\# }"

ファイルの状態は2行目以降で得られます。ここでは下記の情報を算出します。

  • changed: 更新されたファイルの数(先頭に??以外が付いているファイル)
  • untracked: 追加されたがリポジトリで追跡されていないファイル(先頭に??が付いているファイル)
  • clean: ワーキングツリーがクリーンな状態かどうか
shift git_status
git_info[changed]=${#git_status:#\?\?*}
git_info[untracked]=$(( $#git_status - ${git_info[changed]} ))
git_info[clean]=$(( $#git_status == 0 ))

これでプロンプトに必要な情報が揃いました。連想配列git_infoに入っている情報をもとにプロンプトを組み立てていきます。なお、連想配列emojiには適当な絵文字が入っている前提です。

local git_indicator
git_indicator=("${emoji[git]}  %{%F{blue}%}${git_info[branch]}%{%f%}")
(( ${git_info[clean]}     )) && git_indicator+=("${emoji[git_clean]}")
(( ${git_info[changed]}   )) && git_indicator+=("${emoji[git_changed]}  %{%F{yellow}%}${git_info[changed]} changed%{%f%}")
(( ${git_info[untracked]} )) && git_indicator+=("${emoji[git_untracked]}  %{%F{red}%}${git_info[untracked]} untracked%{%f%}")

出来上がったプロンプト

f:id:int128:20150714234302p:plain

.zshrcからプロンプト設定を抜粋したものを下記に示します。

# .zshrc
autoload -Uz add-zsh-hook

setopt prompt_subst

typeset -A emoji
emoji[ok]=$'\U2705'
emoji[error]=$'\U274C'
emoji[git]=$'\U1F500'
emoji[git_changed]=$'\U1F37A'
emoji[git_untracked]=$'\U1F363'
emoji[git_clean]=$'\U2728'
emoji[right_arrow]=$'\U2794'

function _vcs_git_indicator () {
  typeset -A git_info
  local git_indicator git_status
  git_status=("${(f)$(git status --porcelain --branch 2> /dev/null)}")
  (( $? == 0 )) && {
    git_info[branch]="${${git_status[1]}#\#\# }"
    shift git_status
    git_info[changed]=${#git_status:#\?\?*}
    git_info[untracked]=$(( $#git_status - ${git_info[changed]} ))
    git_info[clean]=$(( $#git_status == 0 ))

    git_indicator=("${emoji[git]}  %{%F{blue}%}${git_info[branch]}%{%f%}")
    (( ${git_info[clean]}     )) && git_indicator+=("${emoji[git_clean]}")
    (( ${git_info[changed]}   )) && git_indicator+=("${emoji[git_changed]}  %{%F{yellow}%}${git_info[changed]} changed%{%f%}")
    (( ${git_info[untracked]} )) && git_indicator+=("${emoji[git_untracked]}  %{%F{red}%}${git_info[untracked]} untracked%{%f%}")
  }
  _vcs_git_indicator="${git_indicator}"
}

add-zsh-hook precmd _vcs_git_indicator

function {
  local dir='%{%F{blue}%B%}%~%{%b%f%}'
  local now='%{%F{yellow}%}%D{%b %e %a %R %Z}%{%f%}'
  local rc="%(?,${emoji[ok]} ,${emoji[error]}  %{%F{red}%}%?%{%f%})"
  local user='%{%F{green}%}%n%{%f%}'
  local host='%{%F{green}%}%m%{%f%}'
  [ "$SSH_CLIENT" ] && local via="${${=SSH_CLIENT}[1]} %{%B%}${emoji[right_arrow]}%{%b%} "
  local git='$_vcs_git_indicator'
  local mark=$'\n%# '
  PROMPT="$dir $user($via$host) $rc $git$mark"
  RPROMPT="$now"
}

まとめ

git statusを利用することでプロンプトに多彩な情報を表示できます。また、文字列処理やリスト処理などのzshの強力な機能を利用することで、zshの組み込みコマンドだけでgit statusの結果を処理できます。