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# 3a) In ~/.bashrc set PROMPT_COMMAND=__git_ps1 14# To customize the prompt, provide start/end arguments 15# PROMPT_COMMAND='__git_ps1 "\u@\h:\w" "\\\$ "' 16# 3b) Alternatively change your PS1 to call __git_ps1 as 17# command-substitution: 18# Bash: PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ ' 19# ZSH: PS1='[%n@%m %c$(__git_ps1 " (%s)")]\$ ' 20# the optional argument will be used as format string 21# 22# The argument to __git_ps1 will be displayed only if you are currently 23# in a git repository. The %s token will be the name of the current 24# branch. 25# 26# In addition, if you set GIT_PS1_SHOWDIRTYSTATE to a nonempty value, 27# unstaged (*) and staged (+) changes will be shown next to the branch 28# name. You can configure this per-repository with the 29# bash.showDirtyState variable, which defaults to true once 30# GIT_PS1_SHOWDIRTYSTATE is enabled. 31# 32# You can also see if currently something is stashed, by setting 33# GIT_PS1_SHOWSTASHSTATE to a nonempty value. If something is stashed, 34# then a '$' will be shown next to the branch name. 35# 36# If you would like to see if there're untracked files, then you can set 37# GIT_PS1_SHOWUNTRACKEDFILES to a nonempty value. If there're untracked 38# files, then a '%' will be shown next to the branch name. 39# 40# If you would like to see the difference between HEAD and its upstream, 41# set GIT_PS1_SHOWUPSTREAM="auto". A "<" indicates you are behind, ">" 42# indicates you are ahead, "<>" indicates you have diverged and "=" 43# indicates that there is no difference. You can further control 44# behaviour by setting GIT_PS1_SHOWUPSTREAM to a space-separated list 45# of values: 46# 47# verbose show number of commits ahead/behind (+/-) upstream 48# legacy don't use the '--count' option available in recent 49# versions of git-rev-list 50# git always compare HEAD to @{upstream} 51# svn always compare HEAD to your SVN upstream 52# 53# By default, __git_ps1 will compare HEAD to your SVN upstream if it can 54# find one, or @{upstream} otherwise. Once you have set 55# GIT_PS1_SHOWUPSTREAM, you can override it on a per-repository basis by 56# setting the bash.showUpstream config variable. 57# 58# If you would like a colored hint about the current dirty state, set 59# GIT_PS1_SHOWCOLORHINTS to a nonempty value. When tracked files are 60# modified, the branch name turns red, when all modifications are staged 61# the branch name turns yellow and when all changes are checked in, the 62# color changes to green. The colors are currently hardcoded in the function. 63 64# __gitdir accepts 0 or 1 arguments (i.e., location) 65# returns location of .git repo 66__gitdir () 67{ 68 # Note: this function is duplicated in git-completion.bash 69 # When updating it, make sure you update the other one to match. 70 if [ -z "${1-}" ]; then 71 if [ -n "${__git_dir-}" ]; then 72 echo "$__git_dir" 73 elif [ -n "${GIT_DIR-}" ]; then 74 test -d "${GIT_DIR-}" || return 1 75 echo "$GIT_DIR" 76 elif [ -d .git ]; then 77 echo .git 78 else 79 git rev-parse --git-dir 2>/dev/null 80 fi 81 elif [ -d "$1/.git" ]; then 82 echo "$1/.git" 83 else 84 echo "$1" 85 fi 86} 87 88# stores the divergence from upstream in $p 89# used by GIT_PS1_SHOWUPSTREAM 90__git_ps1_show_upstream () 91{ 92 local key value 93 local svn_remote svn_url_pattern count n 94 local upstream=git legacy="" verbose="" 95 96 svn_remote=() 97 # get some config options from git-config 98 local output="$(git config -z --get-regexp '^(svn-remote\..*\.url|bash\.showupstream)$' 2>/dev/null | tr '\0\n' '\n ')" 99 while read -r key value; do 100 case "$key" in 101 bash.showupstream) 102 GIT_PS1_SHOWUPSTREAM="$value" 103 if [[ -z "${GIT_PS1_SHOWUPSTREAM}" ]]; then 104 p="" 105 return 106 fi 107 ;; 108 svn-remote.*.url) 109 svn_remote[ $((${#svn_remote[@]} + 1)) ]="$value" 110 svn_url_pattern+="\\|$value" 111 upstream=svn+git # default upstream is SVN if available, else git 112 ;; 113 esac 114 done <<< "$output" 115 116 # parse configuration values 117 for option in ${GIT_PS1_SHOWUPSTREAM}; do 118 case "$option" in 119 git|svn) upstream="$option" ;; 120 verbose) verbose=1 ;; 121 legacy) legacy=1 ;; 122 esac 123 done 124 125 # Find our upstream 126 case "$upstream" in 127 git) upstream="@{upstream}" ;; 128 svn*) 129 # get the upstream from the "git-svn-id: ..." in a commit message 130 # (git-svn uses essentially the same procedure internally) 131 local svn_upstream=($(git log --first-parent -1 \ 132 --grep="^git-svn-id: \(${svn_url_pattern#??}\)" 2>/dev/null)) 133 if [[ 0 -ne ${#svn_upstream[@]} ]]; then 134 svn_upstream=${svn_upstream[ ${#svn_upstream[@]} - 2 ]} 135 svn_upstream=${svn_upstream%@*} 136 local n_stop="${#svn_remote[@]}" 137 for ((n=1; n <= n_stop; n++)); do 138 svn_upstream=${svn_upstream#${svn_remote[$n]}} 139 done 140 141 if [[ -z "$svn_upstream" ]]; then 142 # default branch name for checkouts with no layout: 143 upstream=${GIT_SVN_ID:-git-svn} 144 else 145 upstream=${svn_upstream#/} 146 fi 147 elif [[ "svn+git" = "$upstream" ]]; then 148 upstream="@{upstream}" 149 fi 150 ;; 151 esac 152 153 # Find how many commits we are ahead/behind our upstream 154 if [[ -z "$legacy" ]]; then 155 count="$(git rev-list --count --left-right \ 156 "$upstream"...HEAD 2>/dev/null)" 157 else 158 # produce equivalent output to --count for older versions of git 159 local commits 160 if commits="$(git rev-list --left-right "$upstream"...HEAD 2>/dev/null)" 161 then 162 local commit behind=0 ahead=0 163 for commit in $commits 164 do 165 case "$commit" in 166 "<"*) ((behind++)) ;; 167 *) ((ahead++)) ;; 168 esac 169 done 170 count="$behind $ahead" 171 else 172 count="" 173 fi 174 fi 175 176 # calculate the result 177 if [[ -z "$verbose" ]]; then 178 case "$count" in 179 "") # no upstream 180 p="" ;; 181 "0 0") # equal to upstream 182 p="=" ;; 183 "0 "*) # ahead of upstream 184 p=">" ;; 185 *" 0") # behind upstream 186 p="<" ;; 187 *) # diverged from upstream 188 p="<>" ;; 189 esac 190 else 191 case "$count" in 192 "") # no upstream 193 p="" ;; 194 "0 0") # equal to upstream 195 p=" u=" ;; 196 "0 "*) # ahead of upstream 197 p=" u+${count#0 }" ;; 198 *" 0") # behind upstream 199 p=" u-${count% 0}" ;; 200 *) # diverged from upstream 201 p=" u+${count#* }-${count% *}" ;; 202 esac 203 fi 204 205} 206 207 208# __git_ps1 accepts 0 or 1 arguments (i.e., format string) 209# when called from PS1 using command substitution 210# in this mode it prints text to add to bash PS1 prompt (includes branch name) 211# 212# __git_ps1 requires 2 arguments when called from PROMPT_COMMAND (pc) 213# in that case it _sets_ PS1. The arguments are parts of a PS1 string. 214# when both arguments are given, the first is prepended and the second appended 215# to the state string when assigned to PS1. 216# In this mode you can request colored hints using GIT_PS1_SHOWCOLORHINTS=true 217__git_ps1 () 218{ 219 local pcmode=no 220 #defaults/examples: 221 local ps1pc_start='\u@\h:\w ' 222 local ps1pc_end='\$ ' 223 local printf_format=' (%s)' 224 225 case "$#" in 226 2) pcmode=yes 227 ps1pc_start="$1" 228 ps1pc_end="$2" 229 ;; 230 0|1) printf_format="${1:-$printf_format}" 231 ;; 232 *) return 233 ;; 234 esac 235 236 local g="$(__gitdir)" 237 if [ -z "$g" ]; then 238 if [ $pcmode = yes ]; then 239 #In PC mode PS1 always needs to be set 240 PS1="$ps1pc_start$ps1pc_end" 241 fi 242 else 243 local r="" 244 local b="" 245 if [ -f "$g/rebase-merge/interactive" ]; then 246 r="|REBASE-i" 247 b="$(cat "$g/rebase-merge/head-name")" 248 elif [ -d "$g/rebase-merge" ]; then 249 r="|REBASE-m" 250 b="$(cat "$g/rebase-merge/head-name")" 251 else 252 if [ -d "$g/rebase-apply" ]; then 253 if [ -f "$g/rebase-apply/rebasing" ]; then 254 r="|REBASE" 255 elif [ -f "$g/rebase-apply/applying" ]; then 256 r="|AM" 257 else 258 r="|AM/REBASE" 259 fi 260 elif [ -f "$g/MERGE_HEAD" ]; then 261 r="|MERGING" 262 elif [ -f "$g/CHERRY_PICK_HEAD" ]; then 263 r="|CHERRY-PICKING" 264 elif [ -f "$g/BISECT_LOG" ]; then 265 r="|BISECTING" 266 fi 267 268 b="$(git symbolic-ref HEAD 2>/dev/null)" || { 269 270 b="$( 271 case "${GIT_PS1_DESCRIBE_STYLE-}" in 272 (contains) 273 git describe --contains HEAD ;; 274 (branch) 275 git describe --contains --all HEAD ;; 276 (describe) 277 git describe HEAD ;; 278 (* | default) 279 git describe --tags --exact-match HEAD ;; 280 esac 2>/dev/null)" || 281 282 b="$(cut -c1-7 "$g/HEAD" 2>/dev/null)..." || 283 b="unknown" 284 b="($b)" 285 } 286 fi 287 288 local w="" 289 local i="" 290 local s="" 291 local u="" 292 local c="" 293 local p="" 294 295 if [ "true" = "$(git rev-parse --is-inside-git-dir 2>/dev/null)" ]; then 296 if [ "true" = "$(git rev-parse --is-bare-repository 2>/dev/null)" ]; then 297 c="BARE:" 298 else 299 b="GIT_DIR!" 300 fi 301 elif [ "true" = "$(git rev-parse --is-inside-work-tree 2>/dev/null)" ]; then 302 if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ]; then 303 if [ "$(git config --bool bash.showDirtyState)" != "false" ]; then 304 git diff --no-ext-diff --quiet --exit-code || w="*" 305 if git rev-parse --quiet --verify HEAD >/dev/null; then 306 git diff-index --cached --quiet HEAD -- || i="+" 307 else 308 i="#" 309 fi 310 fi 311 fi 312 if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ]; then 313 git rev-parse --verify refs/stash >/dev/null 2>&1 && s="$" 314 fi 315 316 if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ]; then 317 if [ -n "$(git ls-files --others --exclude-standard)" ]; then 318 u="%" 319 fi 320 fi 321 322 if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then 323 __git_ps1_show_upstream 324 fi 325 fi 326 327 local f="$w$i$s$u" 328 if [ $pcmode = yes ]; then 329 PS1="$ps1pc_start(" 330 if [ -n "${GIT_PS1_SHOWCOLORHINT-}" ]; then 331 local c_red='\e[31m' 332 local c_green='\e[32m' 333 local c_yellow='\e[33m' 334 local c_lblue='\e[1;34m' 335 local c_purple='\e[35m' 336 local c_cyan='\e[36m' 337 local c_clear='\e[0m' 338 local branchstring="$c${b##refs/heads/}" 339 local branch_color="$c_green" 340 local flags_color="$c_cyan" 341 342 if [ "$w" = "*" ]; then 343 branch_color="$c_red" 344 elif [ -n "$i" ]; then 345 branch_color="$c_yellow" 346 fi 347 348 # Setting PS1 directly with \[ and \] around colors 349 # is necessary to prevent wrapping issues! 350 PS1="$PS1\[$branch_color\]$branchstring\[$c_clear\]" 351 if [ -n "$f" ]; then 352 PS1="$PS1 \[$flags_color\]$f\[$c_clear\]" 353 fi 354 else 355 PS1="$PS1$c${b##refs/heads/}${f:+ $f}$r$p" 356 fi 357 PS1="$PS1)$ps1pc_end" 358 else 359 # NO color option unless in PROMPT_COMMAND mode 360 printf -- "$printf_format" "$c${b##refs/heads/}${f:+ $f}$r$p" 361 fi 362 fi 363}