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. 70if[-z"${1-}"];then 71if[-n"${__git_dir-}"];then 72echo"$__git_dir" 73elif[-n"${GIT_DIR-}"];then 74test -d"${GIT_DIR-}"||return1 75echo"$GIT_DIR" 76elif[-d .git ];then 77echo .git 78else 79 git rev-parse --git-dir2>/dev/null 80fi 81elif[-d"$1/.git"];then 82echo"$1/.git" 83else 84echo"$1" 85fi 86} 87 88# stores the divergence from upstream in $p 89# used by GIT_PS1_SHOWUPSTREAM 90__git_ps1_show_upstream () 91{ 92local key value 93local svn_remote svn_url_pattern count n 94local upstream=git legacy="" verbose="" 95 96 svn_remote=() 97# get some config options from git-config 98local output="$(git config -z --get-regexp '^(svn-remote\..*\.url|bash\.showupstream)$' 2>/dev/null | tr '\0\n' '\n')" 99whileread -r key value;do 100case"$key"in 101 bash.showupstream) 102 GIT_PS1_SHOWUPSTREAM="$value" 103if[[-z"${GIT_PS1_SHOWUPSTREAM}"]];then 104 p="" 105return 106fi 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;; 113esac 114done<<<"$output" 115 116# parse configuration values 117for option in${GIT_PS1_SHOWUPSTREAM};do 118case"$option"in 119 git|svn) upstream="$option";; 120 verbose) verbose=1;; 121 legacy) legacy=1;; 122esac 123done 124 125# Find our upstream 126case"$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) 131local svn_upstream=($(git log --first-parent -1 \ 132--grep="^git-svn-id: \(${svn_url_pattern#??}\)"2>/dev/null)) 133if[[0-ne${#svn_upstream[@]}]];then 134 svn_upstream=${svn_upstream[ ${#svn_upstream[@]} - 2 ]} 135 svn_upstream=${svn_upstream%@*} 136local n_stop="${#svn_remote[@]}" 137for((n=1; n <= n_stop; n++));do 138 svn_upstream=${svn_upstream#${svn_remote[$n]}} 139done 140 141if[[-z"$svn_upstream"]];then 142# default branch name for checkouts with no layout: 143 upstream=${GIT_SVN_ID:-git-svn} 144else 145 upstream=${svn_upstream#/} 146fi 147elif[["svn+git"="$upstream"]];then 148 upstream="@{upstream}" 149fi 150;; 151esac 152 153# Find how many commits we are ahead/behind our upstream 154if[[-z"$legacy"]];then 155 count="$(git rev-list --count --left-right \ 156 "$upstream"...HEAD 2>/dev/null)" 157else 158# produce equivalent output to --count for older versions of git 159local commits 160if commits="$(git rev-list --left-right "$upstream"...HEAD 2>/dev/null)" 161then 162local commit behind=0 ahead=0 163for commit in$commits 164do 165case"$commit"in 166"<"*) ((behind++)) ;; 167*) ((ahead++)) ;; 168esac 169done 170 count="$behind$ahead" 171else 172 count="" 173fi 174fi 175 176# calculate the result 177if[[-z"$verbose"]];then 178case"$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="<>";; 189esac 190else 191case"$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% *}";; 202esac 203fi 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{ 219local pcmode=no 220#defaults/examples: 221local ps1pc_start='\u@\h:\w ' 222local ps1pc_end='\$ ' 223local printf_format=' (%s)' 224 225case"$#"in 2262) pcmode=yes 227 ps1pc_start="$1" 228 ps1pc_end="$2" 229;; 2300|1) printf_format="${1:-$printf_format}" 231;; 232*)return 233;; 234esac 235 236local g="$(__gitdir)" 237if[-z"$g"];then 238if[$pcmode=yes];then 239#In PC mode PS1 always needs to be set 240 PS1="$ps1pc_start$ps1pc_end" 241fi 242else 243local r="" 244local b="" 245if[-f"$g/rebase-merge/interactive"];then 246 r="|REBASE-i" 247 b="$(cat "$g/rebase-merge/head-name")" 248elif[-d"$g/rebase-merge"];then 249 r="|REBASE-m" 250 b="$(cat "$g/rebase-merge/head-name")" 251else 252if[-d"$g/rebase-apply"];then 253if[-f"$g/rebase-apply/rebasing"];then 254 r="|REBASE" 255elif[-f"$g/rebase-apply/applying"];then 256 r="|AM" 257else 258 r="|AM/REBASE" 259fi 260elif[-f"$g/MERGE_HEAD"];then 261 r="|MERGING" 262elif[-f"$g/CHERRY_PICK_HEAD"];then 263 r="|CHERRY-PICKING" 264elif[-f"$g/BISECT_LOG"];then 265 r="|BISECTING" 266fi 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} 286fi 287 288local w="" 289local i="" 290local s="" 291local u="" 292local c="" 293local p="" 294 295if["true"="$(git rev-parse --is-inside-git-dir 2>/dev/null)"];then 296if["true"="$(git rev-parse --is-bare-repository 2>/dev/null)"];then 297 c="BARE:" 298else 299 b="GIT_DIR!" 300fi 301elif["true"="$(git rev-parse --is-inside-work-tree 2>/dev/null)"];then 302if[-n"${GIT_PS1_SHOWDIRTYSTATE-}"];then 303if["$(git config --bool bash.showDirtyState)"!="false"];then 304 git diff--no-ext-diff --quiet --exit-code|| w="*" 305if git rev-parse --quiet --verify HEAD >/dev/null;then 306 git diff-index --cached --quiet HEAD --|| i="+" 307else 308 i="#" 309fi 310fi 311fi 312if[-n"${GIT_PS1_SHOWSTASHSTATE-}"];then 313 git rev-parse --verify refs/stash >/dev/null 2>&1&& s="$" 314fi 315 316if[-n"${GIT_PS1_SHOWUNTRACKEDFILES-}"];then 317if[-n"$(git ls-files --others --exclude-standard)"];then 318 u="%" 319fi 320fi 321 322if[-n"${GIT_PS1_SHOWUPSTREAM-}"];then 323 __git_ps1_show_upstream 324fi 325fi 326 327local f="$w$i$s$u" 328if[$pcmode=yes];then 329 PS1="$ps1pc_start(" 330if[-n"${GIT_PS1_SHOWCOLORHINT-}"];then 331local c_red='\e[31m' 332local c_green='\e[32m' 333local c_yellow='\e[33m' 334local c_lblue='\e[1;34m' 335local c_purple='\e[35m' 336local c_cyan='\e[36m' 337local c_clear='\e[0m' 338local branchstring="$c${b##refs/heads/}" 339local branch_color="$c_green" 340local flags_color="$c_cyan" 341 342if["$w"="*"];then 343 branch_color="$c_red" 344elif[-n"$i"];then 345 branch_color="$c_yellow" 346fi 347 348# Setting PS1 directly with \[ and \] around colors 349# is necessary to prevent wrapping issues! 350 PS1="$PS1\[$branch_color\]$branchstring\[$c_clear\]" 351if[-n"$f"];then 352 PS1="$PS1\[$flags_color\]$f\[$c_clear\]" 353fi 354else 355 PS1="$PS1$c${b##refs/heads/}${f:+ $f}$r$p" 356fi 357 PS1="$PS1)$ps1pc_end" 358else 359# NO color option unless in PROMPT_COMMAND mode 360printf --"$printf_format""$c${b##refs/heads/}${f:+ $f}$r$p" 361fi 362fi 363}