git-prompt.sh 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. # bash/zsh git prompt support
  2. #
  3. # Copyright (C) 2006,2007 Shawn O. Pearce <spearce@spearce.org>
  4. # Distributed under the GNU General Public License, version 2.0.
  5. #
  6. # This script allows you to see the current branch in your prompt.
  7. #
  8. # To enable:
  9. #
  10. # 1) Copy this file to somewhere (e.g. ~/.git-prompt.sh).
  11. # 2) Add the following line to your .bashrc/.zshrc:
  12. # source ~/.git-prompt.sh
  13. # 3) Change your PS1 to also show the current branch:
  14. # Bash: PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ '
  15. # ZSH: PS1='[%n@%m %c$(__git_ps1 " (%s)")]\$ '
  16. #
  17. # The argument to __git_ps1 will be displayed only if you are currently
  18. # in a git repository. The %s token will be the name of the current
  19. # branch.
  20. #
  21. # In addition, if you set GIT_PS1_SHOWDIRTYSTATE to a nonempty value,
  22. # unstaged (*) and staged (+) changes will be shown next to the branch
  23. # name. You can configure this per-repository with the
  24. # bash.showDirtyState variable, which defaults to true once
  25. # GIT_PS1_SHOWDIRTYSTATE is enabled.
  26. #
  27. # You can also see if currently something is stashed, by setting
  28. # GIT_PS1_SHOWSTASHSTATE to a nonempty value. If something is stashed,
  29. # then a '$' will be shown next to the branch name.
  30. #
  31. # If you would like to see if there're untracked files, then you can set
  32. # GIT_PS1_SHOWUNTRACKEDFILES to a nonempty value. If there're untracked
  33. # files, then a '%' will be shown next to the branch name.
  34. #
  35. # If you would like to see the difference between HEAD and its upstream,
  36. # set GIT_PS1_SHOWUPSTREAM="auto". A "<" indicates you are behind, ">"
  37. # indicates you are ahead, "<>" indicates you have diverged and "="
  38. # indicates that there is no difference. You can further control
  39. # behaviour by setting GIT_PS1_SHOWUPSTREAM to a space-separated list
  40. # of values:
  41. #
  42. # verbose show number of commits ahead/behind (+/-) upstream
  43. # legacy don't use the '--count' option available in recent
  44. # versions of git-rev-list
  45. # git always compare HEAD to @{upstream}
  46. # svn always compare HEAD to your SVN upstream
  47. #
  48. # By default, __git_ps1 will compare HEAD to your SVN upstream if it can
  49. # find one, or @{upstream} otherwise. Once you have set
  50. # GIT_PS1_SHOWUPSTREAM, you can override it on a per-repository basis by
  51. # setting the bash.showUpstream config variable.
  52. # __gitdir accepts 0 or 1 arguments (i.e., location)
  53. # returns location of .git repo
  54. __gitdir ()
  55. {
  56. # Note: this function is duplicated in git-completion.bash
  57. # When updating it, make sure you update the other one to match.
  58. if [ -z "${1-}" ]; then
  59. if [ -n "${__git_dir-}" ]; then
  60. echo "$__git_dir"
  61. elif [ -n "${GIT_DIR-}" ]; then
  62. test -d "${GIT_DIR-}" || return 1
  63. echo "$GIT_DIR"
  64. elif [ -d .git ]; then
  65. echo .git
  66. else
  67. git rev-parse --git-dir 2>/dev/null
  68. fi
  69. elif [ -d "$1/.git" ]; then
  70. echo "$1/.git"
  71. else
  72. echo "$1"
  73. fi
  74. }
  75. # stores the divergence from upstream in $p
  76. # used by GIT_PS1_SHOWUPSTREAM
  77. __git_ps1_show_upstream ()
  78. {
  79. local key value
  80. local svn_remote svn_url_pattern count n
  81. local upstream=git legacy="" verbose=""
  82. svn_remote=()
  83. # get some config options from git-config
  84. local output="$(git config -z --get-regexp '^(svn-remote\..*\.url|bash\.showupstream)$' 2>/dev/null | tr '\0\n' '\n ')"
  85. while read -r key value; do
  86. case "$key" in
  87. bash.showupstream)
  88. GIT_PS1_SHOWUPSTREAM="$value"
  89. if [[ -z "${GIT_PS1_SHOWUPSTREAM}" ]]; then
  90. p=""
  91. return
  92. fi
  93. ;;
  94. svn-remote.*.url)
  95. svn_remote[ $((${#svn_remote[@]} + 1)) ]="$value"
  96. svn_url_pattern+="\\|$value"
  97. upstream=svn+git # default upstream is SVN if available, else git
  98. ;;
  99. esac
  100. done <<< "$output"
  101. # parse configuration values
  102. for option in ${GIT_PS1_SHOWUPSTREAM}; do
  103. case "$option" in
  104. git|svn) upstream="$option" ;;
  105. verbose) verbose=1 ;;
  106. legacy) legacy=1 ;;
  107. esac
  108. done
  109. # Find our upstream
  110. case "$upstream" in
  111. git) upstream="@{upstream}" ;;
  112. svn*)
  113. # get the upstream from the "git-svn-id: ..." in a commit message
  114. # (git-svn uses essentially the same procedure internally)
  115. local svn_upstream=($(git log --first-parent -1 \
  116. --grep="^git-svn-id: \(${svn_url_pattern#??}\)" 2>/dev/null))
  117. if [[ 0 -ne ${#svn_upstream[@]} ]]; then
  118. svn_upstream=${svn_upstream[ ${#svn_upstream[@]} - 2 ]}
  119. svn_upstream=${svn_upstream%@*}
  120. local n_stop="${#svn_remote[@]}"
  121. for ((n=1; n <= n_stop; n++)); do
  122. svn_upstream=${svn_upstream#${svn_remote[$n]}}
  123. done
  124. if [[ -z "$svn_upstream" ]]; then
  125. # default branch name for checkouts with no layout:
  126. upstream=${GIT_SVN_ID:-git-svn}
  127. else
  128. upstream=${svn_upstream#/}
  129. fi
  130. elif [[ "svn+git" = "$upstream" ]]; then
  131. upstream="@{upstream}"
  132. fi
  133. ;;
  134. esac
  135. # Find how many commits we are ahead/behind our upstream
  136. if [[ -z "$legacy" ]]; then
  137. count="$(git rev-list --count --left-right \
  138. "$upstream"...HEAD 2>/dev/null)"
  139. else
  140. # produce equivalent output to --count for older versions of git
  141. local commits
  142. if commits="$(git rev-list --left-right "$upstream"...HEAD 2>/dev/null)"
  143. then
  144. local commit behind=0 ahead=0
  145. for commit in $commits
  146. do
  147. case "$commit" in
  148. "<"*) ((behind++)) ;;
  149. *) ((ahead++)) ;;
  150. esac
  151. done
  152. count="$behind $ahead"
  153. else
  154. count=""
  155. fi
  156. fi
  157. # calculate the result
  158. if [[ -z "$verbose" ]]; then
  159. case "$count" in
  160. "") # no upstream
  161. p="" ;;
  162. "0 0") # equal to upstream
  163. p="=" ;;
  164. "0 "*) # ahead of upstream
  165. p=">" ;;
  166. *" 0") # behind upstream
  167. p="<" ;;
  168. *) # diverged from upstream
  169. p="<>" ;;
  170. esac
  171. else
  172. case "$count" in
  173. "") # no upstream
  174. p="" ;;
  175. "0 0") # equal to upstream
  176. p=" u=" ;;
  177. "0 "*) # ahead of upstream
  178. p=" u+${count#0 }" ;;
  179. *" 0") # behind upstream
  180. p=" u-${count% 0}" ;;
  181. *) # diverged from upstream
  182. p=" u+${count#* }-${count% *}" ;;
  183. esac
  184. fi
  185. }
  186. # __git_ps1 accepts 0 or 1 arguments (i.e., format string)
  187. # returns text to add to bash PS1 prompt (includes branch name)
  188. __git_ps1 ()
  189. {
  190. local g="$(__gitdir)"
  191. if [ -n "$g" ]; then
  192. local r=""
  193. local b=""
  194. if [ -f "$g/rebase-merge/interactive" ]; then
  195. r="|REBASE-i"
  196. b="$(cat "$g/rebase-merge/head-name")"
  197. elif [ -d "$g/rebase-merge" ]; then
  198. r="|REBASE-m"
  199. b="$(cat "$g/rebase-merge/head-name")"
  200. else
  201. if [ -d "$g/rebase-apply" ]; then
  202. if [ -f "$g/rebase-apply/rebasing" ]; then
  203. r="|REBASE"
  204. elif [ -f "$g/rebase-apply/applying" ]; then
  205. r="|AM"
  206. else
  207. r="|AM/REBASE"
  208. fi
  209. elif [ -f "$g/MERGE_HEAD" ]; then
  210. r="|MERGING"
  211. elif [ -f "$g/CHERRY_PICK_HEAD" ]; then
  212. r="|CHERRY-PICKING"
  213. elif [ -f "$g/BISECT_LOG" ]; then
  214. r="|BISECTING"
  215. fi
  216. b="$(git symbolic-ref HEAD 2>/dev/null)" || {
  217. b="$(
  218. case "${GIT_PS1_DESCRIBE_STYLE-}" in
  219. (contains)
  220. git describe --contains HEAD ;;
  221. (branch)
  222. git describe --contains --all HEAD ;;
  223. (describe)
  224. git describe HEAD ;;
  225. (* | default)
  226. git describe --tags --exact-match HEAD ;;
  227. esac 2>/dev/null)" ||
  228. b="$(cut -c1-7 "$g/HEAD" 2>/dev/null)..." ||
  229. b="unknown"
  230. b="($b)"
  231. }
  232. fi
  233. local w=""
  234. local i=""
  235. local s=""
  236. local u=""
  237. local c=""
  238. local p=""
  239. if [ "true" = "$(git rev-parse --is-inside-git-dir 2>/dev/null)" ]; then
  240. if [ "true" = "$(git rev-parse --is-bare-repository 2>/dev/null)" ]; then
  241. c="BARE:"
  242. else
  243. b="GIT_DIR!"
  244. fi
  245. elif [ "true" = "$(git rev-parse --is-inside-work-tree 2>/dev/null)" ]; then
  246. if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ]; then
  247. if [ "$(git config --bool bash.showDirtyState)" != "false" ]; then
  248. git diff --no-ext-diff --quiet --exit-code || w="*"
  249. if git rev-parse --quiet --verify HEAD >/dev/null; then
  250. git diff-index --cached --quiet HEAD -- || i="+"
  251. else
  252. i="#"
  253. fi
  254. fi
  255. fi
  256. if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ]; then
  257. git rev-parse --verify refs/stash >/dev/null 2>&1 && s="$"
  258. fi
  259. if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ]; then
  260. if [ -n "$(git ls-files --others --exclude-standard)" ]; then
  261. u="%%"
  262. fi
  263. fi
  264. if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then
  265. __git_ps1_show_upstream
  266. fi
  267. fi
  268. local f="$w$i$s$u"
  269. printf -- "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r$p"
  270. fi
  271. }