Git のための最強 Bashプロンプト！

「git bash PS1」とか「git commit 忘れる」とかで検索している君！！

なにはともあれ、このBashプロンプトを見てくれ！！**どうだい、クールだろ？**[^1]

[^1]: ついカッとなり、いささかお見苦しい表現の記事となっておりますが、ご笑覧いただければ幸いです。

![オレBash.png](https://qiita-image-store.s3.amazonaws.com/0/179154/456150d6-1a95-4268-15a4-69672aeb2af5.png)

「Git Bash ブランチ」とか「Git Stash Bash」とかでググると、断片的な情報が山ほど出てくる。だが**このBashプロンプトは完全版**だ。私や君に必要な機能は全て備わっている。

また、後述するようにこのプロンプトでは**未コミットのファイルがあると警告**を表示できる。世の中には [git-remindというツールがあるらしい](https://qiita.com/suin/items/4932dd3f822ea8da8a01)が、このBashプロンプトを使う限りgit-remind の出番はないだろう。

# 機能

**現在のブランチ名**が表示されるから、master にコミットしちまうなんてポカは起きえない。ついでに、まれによく使う`HEAD` の現在のコミットIDも表示している。

![ブランチ.png](https://qiita-image-store.s3.amazonaws.com/0/179154/3990ceba-41ca-e4d7-5255-e80d7d8c914d.png)

また、

- 未コミットのファイル
- ファイルに変更
- stageされたファイルあり
- stashあり

といった状態も表示されるから、いちいち `git` を実行しなくてもいい。
`git stash list` なんてもう何年も使っていない。

![状態.png](https://qiita-image-store.s3.amazonaws.com/0/179154/5beff1fa-0e79-7024-507f-0cfb2d7f18f3.png)

ついでに、個人的な好みで時刻や前回の終了コードなんかも表示している。

どうだい、お前さんも使ってみたいだろう？

# イカシたプロンプトを出すコマンド

クールなBashプロンプトの**Gitの情報を表示する**には以下のスクリプトを使えば良い。`~/bin/git-ps` とでもいう名前で保存して、実行権限をつけてくれ。

```shell
#!/bin/bash
set -euC

C() {
  case $1 in
    black)    echo -e -n "\033[1;30m";;
    red)      echo -e -n "\033[1;31m";;
    green)    echo -e -n "\033[1;32m";;
    yellow)   echo -e -n "\033[1;33m";;
    blue)     echo -e -n "\033[1;34m";;
    magenta)  echo -e -n "\033[1;35m";;
    cyan)     echo -e -n "\033[1;36m";;
    white)    echo -e -n "\033[1;37m";;
    *) echo -e -n "\033[0m";;
  esac
}

status() {
  local modified=0
  local cached=0
  local untracked=0

  while read -r line; do
    if [ "$line" = '_?_?_' ]; then
      untracked=1
      continue
    fi

    if [[ "$line" =~ ^_[^[:space:]]_.?_ ]]; then
      cached=1
    fi

    if [[ "$line" =~ ^_._[^[:space:]]_ ]]; then
      modified=1
    fi
  done < <(git status --short | cut -b -2 | sed -e 's/\(.\)\(.*\)/_\1_\2_/')

  if [ $modified -ne 0 ]; then
    C red
    echo -n 'M'
  fi

  if [ $cached -ne 0 ]; then
    C green
    echo -n 'C'
  fi

  if [ $untracked -ne 0 ]; then
    C red
    echo -n '?'
  fi

  if [ -n "$(git stash list)" ]; then
    C cyan
    echo -n 'S'
  fi
  C reset
}

branch() {
  local branch
  branch="$(git branch 2>/dev/null | grep '^\*' | sed -e "s/^* //")"
  if [[ "${branch}" =~ ^bug- ]]; then
    C green
  elif [[ "${branch}" =~ ^atc- ]]; then
    C cyan
  elif [[ "${branch}" =~ ^tmp ]]; then
    C magenta
  elif [[ "${branch}" = "(detached from hde/master)" ]]; then
    C yellow
  else
    C white
  fi
  echo -n "${branch}"
  C reset
}

hash() {
  git log --pretty=format:'%h' -n 1
}

git_ps() {
  if ! git status --ignore-submodules &>/dev/null; then
    return
  fi
  echo "git[$(status)]=$(branch):$(hash)"
}

if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
  git_ps
fi
```

# プロンプトの設定方法

Bashのプロンプトは、環境変数 `PS1` によって設定可能だ。

単にデフォルトのプロンプトにGitの情報を追加したいだけなら、`~/.bashrc` に

```shell
export PS1='$(git-ps)\n[\u@\h \W]\\$ '
```

なんて定義を追加すればOKだ。

`PS1`には「`\u` でユーザー名」など、特殊文字が使えるが [bashのプロンプトを変更するには - @IT](http://www.atmarkit.co.jp/flinux/rensai/linuxtips/002cngprmpt.html) などに一覧表がある。

また、`PS1`はプロンプト表示毎に評価されるから、全体をダブルクオート`"` ではなく `'` で囲むことに注意だ。

ちなみに、こっちは私が普段使っている、完全版のPS1だ。Gitの他に、RailsやJava、Scala、たまに使うSVNなどのためのコードも含んでいる。

あとは自由にカスタマイズしてくれ！

```shell
#!/bin/bash
# PS1の設定

C() {
  case $1 in
    black)    echo -e -n "\033[1;30m";;
    red)      echo -e -n "\033[1;31m";;
    green)    echo -e -n "\033[1;32m";;
    yellow)   echo -e -n "\033[1;33m";;
    blue)     echo -e -n "\033[1;34m";;
    magenta)  echo -e -n "\033[1;35m";;
    cyan)     echo -e -n "\033[1;36m";;
    white)    echo -e -n "\033[1;37m";;
    *) echo -e -n "\033[0m";;
  esac
}

ps1_svn() {
  if ! svn info &> /dev/null; then
    return
  fi
  echo -n ' svn'
}

ps1_rails() {
  if [ -z "${RAILS_ENV-}" ]; then
    return
  fi
  echo -n " RAILS_ENV=$(C white)$RAILS_ENV$(C reset)"
}

ps1_play() {
  if [ -z "${PLAY_ENV-}" ]; then
    return
  fi
  echo -n " PLAY_ENV=$(C white)$PLAY_ENV$(C reset)"
}

ps1_skinny() {
  if [ -z "${SKINNY_ENV-}" ]; then
    return
  fi
  echo -n " SKINNY_ENV=$(C white)$SKINNY_ENV$(C reset)"
}

ps1_login() {
  echo -n "${USER}@"
  echo -n -e "$(C "cyan")$(hostname -s)$(C reset)"
}

ps1_date() {
  date +'%F %H:%M:%S'
}

ps1_exit_status() {
  local END_CODE=$1
  echo -n ' $?='
  if [ 0 -ne "${END_CODE}" ]; then
    echo -n "$(C red)${END_CODE}$(C reset)"
  else
    echo -n 0
  fi
}

ps1_git() {
  if ! git status --ignore-submodules &>/dev/null; then
    return
  fi
  echo -n ' '
  git-ps
}

ps1_java() {
  if [ -n "${JAVA_HOME}" ]; then
    local version
    version=$(java -version |& head -1 | cut -d ' ' -f 3 | tr -d '"')
    echo -n " java=$(C white)${version}$(C reset)"
  fi
}

ps1_docker() {
  if [ -z "${DOCKER_MACHINE_NAME}" ]; then
    return
  fi

  local docker_host_ip
  docker_host_ip="${DOCKER_HOST/tcp:\/\//}"
  docker_host_ip="${docker_host_ip/:*/}"
  echo -n " docker=[${docker_host_ip}]"

  if [ -n "${COMPOSE_FILE}" ]; then
    echo -n ","
    echo -n docker-compose.qa-hosted.yml | sed -e 's/^docker-compose.//' -e 's/.yml$//'
  fi
}

ps1_prompt() {
  if [ "$(id -u)" -eq 0 ]; then
    echo -n "$(C red)# $(C reset)"
  else
    echo -n "$(C "${HOST_COLOR}")$ $(C reset)"
  fi
}

ps1_path() {
  echo " $(pwd | sed -e "s|^${HOME%/}|~|")"
}

function ps1_info() {
  local END_CODE=$?
  echo "$(ps1_login)$(ps1_exit_status "$END_CODE")$(ps1_git)$(ps1_svn)$(ps1_rails)$(ps1_skinny)$(ps1_play)$(ps1_java)$(ps1_docker)"
  echo "$(ps1_date)$(ps1_path)"
}

PS1='$(ps1_info)'


# カーソル位置がずれる問題を回避するため、ここの部分だけは文字列連結にする
if [ "$(id -u)" -eq 0 ]; then
  PS1="${PS1}\n\[$(C red)\]#\[$(red)\] " # rootの場合はシャープ
else
  PS1="${PS1}\n\[$(C cyan)\]\$\[$(C reset)\] " # 非rootユーザの場合はドル
fi

export PS1
```

なお、私はサーバーで Bash を使用している関係上、開発用PCでも Bash を使用しています。Zsh や Fish などを使用されている方は適当に読み替えてください。