bashのprompt文字列をgit statusの状態で色変えする

# 目指したもの
Cmderのプロンプト表示をmsys2でも表示したいです。
こいつは賢くて、現在のgitリポジトリ状態を色で教えてくれます。
以下の図はCmderのプロンプト表示です。

---

<img width="251" alt="cmder1.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/113494/7a39e0ab-91ac-98ed-6acd-225a9723727c.png">


gitリポジトリが汚れた(dirty)状態はブランチの色が赤

---

<img width="240" alt="cmder2.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/113494/6b8035bf-47ce-24bc-b6a8-caea151cc55e.png">


gitリポジトリがきれいな(clean)状態はブランチの色が白

---

<img width="325" alt="cmder_prompt.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/113494/946ac1d2-9cc8-ed50-3b29-bae3f0f44ec4.png">

cmderでプロンプトを表す`PROMPT`という変数の、{git}{hg}とかいうやつがリポジトリを判断して色変えしているようです。

---

これをmsys2 shell上で行いたいです。

<img width="231" alt="msys1.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/113494/3f7e4af1-32e7-ac5e-79aa-8945d9401f90.png">


デフォルトのmsys2のプロンプト表示です。
ブランチ名の色変えどころか、ブランチ名の表示もありません。


# 環境
* OS: Windows 10 64bit
* ターミナルエミュレータ: [Cmder](http://cmder.net/)
* shell: [MSYS2](https://wiki.qt.io/MSYS2)
* git: version 2.12.1

私の環境だけでしょうか、有名だと思われる[`git-prompt.sh`](https://github.com/git/git/tree/master/contrib/completion)ではうまく色変えができませんでした。


# やること

## 使い方
1. `.bash_prompt`をホームディレクトリに作成します(内容は以下)。
2. `.bashrc`(または`.bash_profile`)に`.bash_prompt`を読み込ませるよう指示します(以下に例)。


## `.bash_prompt`の作成
以下をコピペってホームディレクトリ直下に保存し、`.bashrc`から読み込ませる(`source ~/.bash_aliases`)ようにします。

```bash:.bash_prompt
#!/bin/bash
#
# DESCRIPTION:
#
#   Set the bash prompt according to:
#    * the active virtualenv
#    * the branch/status of the current git repository
#    * the return value of the previous command
#    * the fact you just came from Windows and are used to having newlines in
#      your prompts.
#
# USAGE:
#
#   1. Save this file as ~/.bash_prompt
#   2. Add the following line to the end of your ~/.bashrc or ~/.bash_profile:
#        . ~/.bash_prompt
#
# LINEAGE:
#
#   Based on work by woods
#
#   https://gist.github.com/31967

# The various escape codes that we can use to color our prompt.
RED="\[\033[0;31m\]"
YELLOW="\[\033[1;33m\]"
GREEN="\[\033[0;32m\]"
BLUE="\[\033[1;34m\]"
LIGHT_RED="\[\033[1;31m\]"
LIGHT_GREEN="\[\033[1;32m\]"
WHITE="\[\033[1;37m\]"
LIGHT_GRAY="\[\033[1;30m\]"
COLOR_OCHRE="\[\033[38;5;95m\]"
COLOR_NONE="\[\e[0m\]"

# Detect whether the current directory is a git repository.
function is_git_repository {
  git branch > /dev/null 2>&1
}

# Determine the branch/state information for this git repository.
function set_git_branch {
  # Capture the output of the "git status" command.
  git_status="$(git status 2> /dev/null)"

  # Set color based on clean/staged/dirty.
  if [[ ${git_status} =~ "nothing to commit, working tree clean" ]]; then
    state="${GREEN}"
  elif [[ ${git_status} =~ "Changes to be committed:" ]]; then
    state="${YELLOW}"
  elif [[ ${git_status} =~ "Changes not staged for commit:" ]]; then
    state="${LIGHT_RED}"
  elif [[ ${git_status} =~ "Untracked files:" ]]; then
    state="${BLUE}"
  else
    state="${COLOR_OCHRE}"
  fi

  # Set arrow icon based on status against remote.
  remote_pattern="Your branch is (.*) of"
  if [[ ${git_status} =~ ${remote_pattern} ]]; then
    if [[ ${BASH_REMATCH[1]} == "ahead" ]]; then
      remote="^"
    else
      remote="v"
    fi
  else
    remote=""
  fi
  diverge_pattern="Your branch and (.*) have diverged"
  if [[ ${git_status} =~ ${diverge_pattern} ]]; then
    remote="^v"
  fi

  # Get the name of the branch.
  branch_pattern="^On branch ([^${IFS}]*)"
  if [[ ${git_status} =~ ${branch_pattern} ]]; then
    branch=${BASH_REMATCH[1]}
  fi

  # Set the final branch string.
  BRANCH="${state}(${branch})${remote}${COLOR_NONE} "
}

# Return the prompt symbol to use, colorized based on the return value of the
# previous command.
function set_prompt_symbol () {
  if test $1 -eq 0 ; then
      PROMPT_SYMBOL="${LIGHT_GRAY}\$${COLOR_NONE}"
  else
      PROMPT_SYMBOL="${LIGHT_RED}\$${COLOR_NONE}"
  fi
}

# Determine active Python virtualenv details.
function set_virtualenv () {
  if test -z "$VIRTUAL_ENV" ; then
      PYTHON_VIRTUALENV=""
  else
      PYTHON_VIRTUALENV="${LIGHT_GREEN}[`basename \"$VIRTUAL_ENV\"`]${COLOR_NONE} "
  fi
}

# Set the full bash prompt.
function set_bash_prompt () {
  # Set the PROMPT_SYMBOL variable. We do this first so we don't lose the
  # return value of the last command.
  set_prompt_symbol $?

  # Set the PYTHON_VIRTUALENV variable.
  set_virtualenv

  # Set the BRANCH variable.
  if is_git_repository ; then
    set_git_branch
  else
    BRANCH=''
  fi

  # Set the bash prompt variable.
  PS1="
${PYTHON_VIRTUALENV}${BLUE}@\h ${GREEN}\w${COLOR_NONE} ${BRANCH}
${PROMPT_SYMBOL} "
}

# Tell bash to execute this function just before displaying its prompt.
PROMPT_COMMAND=set_bash_prompt
```

## 実行結果

<img width="225" alt="msys3.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/113494/0da50636-cce2-d227-ab24-f51266f5f931.png">


* リポジトリがきれいな状態(ブランチの色がグリーン)
* リポジトリが汚れていて、ステージングされていない状態(ブランチの色がレッド)
* リポジトリが汚れていて、ステージングされいる状態(ブランチの色がイエロー)
* イエローの状態で`git commit`するとグリーンに戻ります。


<img width="216" alt="msys4.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/113494/43105851-da03-8250-0971-3f7c89f63e3f.png">


* リモートリポジトリとの差分確認を行い、aheadを`^`、behindを`v`の記号で教えてくれます。
* `git remote update`コマンドが必要かもしれません。 

> 画像の`gs`コマンドは私のaliasで`git status --short`と同義です。


# やったこと　
* [bash_prompt.sh](https://gist.github.com/insin/1425703)を今のgitのversionに合うように改造しました。
* 見づらいと思うので、どんな改造をしたか興味がある人だけ見てください。
* 行頭`<`マークが削除
* 行頭`>`マークが追加

```bash:diff抜粋
# 白いボヤがかかったような文字らしい。まだどういう状態かは見ていない
31,33c31,34
> COLOR_OCHRE="\[\033[38;5;95m\]"

# "Changes not staged for commit:"の状態も増やした
# git アップデートにより微妙にメッセージが変更になっている
46c47
<   if [[ ${git_status} =~ "working directory clean" ]]; then
---
>   if [[ ${git_status} =~ "nothing to commit, working tree clean" ]]; then
48c49
<   elif [[ ${git_status} =~ "Changes to be committed" ]]; then
---
>   elif [[ ${git_status} =~ "Changes to be committed:" ]]; then
50c51
<   else
---
>   elif [[ ${git_status} =~ "Changes not staged for commit:" ]]; then
51a53,54
>   else
>     state="${COLOR_OCHRE}"


# '#'マークはgitのアップデートにより不要
55c58
<   remote_pattern="# Your branch is (.*) of"
---
>   remote_pattern="Your branch is (.*) of"

# 環境依存文字'↑'とか'↓'とか嫌い
58c61
<       remote="↑"
---
>       remote="^"
60c63
<       remote="↓"
---
>       remote="v"

# '#'マークはgitのアップデートにより不要
65c68
<   diverge_pattern="# Your branch and (.*) have diverged"
---
>   diverge_pattern="Your branch and (.*) have diverged"
71c74
<   branch_pattern="^# On branch ([^${IFS}]*)"
---
>   branch_pattern="^On branch ([^${IFS}]*)"

# $マークの色変え
84c87
<       PROMPT_SYMBOL="\$"
---
>       PROMPT_SYMBOL="${LIGHT_GRAY}\$${COLOR_NONE}"

# 好みの色変え
95c98
<       PYTHON_VIRTUALENV="${BLUE}[`basename \"$VIRTUAL_ENV\"`]${COLOR_NONE} "
---
>       PYTHON_VIRTUALENV="${LIGHT_GREEN}[`basename \"$VIRTUAL_ENV\"`]${COLOR_NONE} "
117c120
< ${PYTHON_VIRTUALENV}${GREEN}\u@\h ${YELLOW}\w${COLOR_NONE} ${BRANCH}
---
> ${PYTHON_VIRTUALENV}${BLUE}@\h ${GREEN}\w${COLOR_NONE} ${BRANCH}
```

ここに載っていない`diff`はインデント変更とか簡単なものです。

よく見たら作成が6年も前、最後の更新が2011年12月…

今ではもっと工夫されたスクリプトありそう。
自分でひとまず作ってから、もっと最適化されたやつ見つけちゃうんだよなぁ。


追記2017/5/25

* `git status`で表示されるローカルリポジトリの状態が`Untracked files:`のときに、ブランチ名を青で表示する機能を追加しました。
* `git status`で表示されるリモートリポジトリの状態が`Your branch and (.*) have diverged`のときに、付加する記号を`↨`の環境依存文字から`^v`へ変更しました。