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, and "<>" indicates you have diverged. You 38# can further control behaviour by setting GIT_PS1_SHOWUPSTREAM to a 39# space-separated list of values: 40# 41# verbose show number of commits ahead/behind (+/-) upstream 42# legacy don't use the '--count' option available in recent 43# versions of git-rev-list 44# git always compare HEAD to @{upstream} 45# svn always compare HEAD to your SVN upstream 46# 47# By default, __git_ps1 will compare HEAD to your SVN upstream if it can 48# find one, or @{upstream} otherwise. Once you have set 49# GIT_PS1_SHOWUPSTREAM, you can override it on a per-repository basis by 50# setting the bash.showUpstream config variable. 51 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. 58if[-z"${1-}"];then 59if[-n"${__git_dir-}"];then 60echo"$__git_dir" 61elif[-n"${GIT_DIR-}"];then 62test -d"${GIT_DIR-}"||return1 63echo"$GIT_DIR" 64elif[-d .git ];then 65echo .git 66else 67 git rev-parse --git-dir2>/dev/null 68fi 69elif[-d"$1/.git"];then 70echo"$1/.git" 71else 72echo"$1" 73fi 74} 75 76# stores the divergence from upstream in $p 77# used by GIT_PS1_SHOWUPSTREAM 78__git_ps1_show_upstream () 79{ 80local key value 81local svn_remote svn_url_pattern count n 82local upstream=git legacy="" verbose="" 83 84 svn_remote=() 85# get some config options from git-config 86local output="$(git config -z --get-regexp '^(svn-remote\..*\.url|bash\.showupstream)$' 2>/dev/null | tr '\0\n' '\n')" 87whileread -r key value;do 88case"$key"in 89 bash.showupstream) 90 GIT_PS1_SHOWUPSTREAM="$value" 91if[[-z"${GIT_PS1_SHOWUPSTREAM}"]];then 92 p="" 93return 94fi 95;; 96 svn-remote.*.url) 97 svn_remote[$((${#svn_remote[@]} + 1)) ]="$value" 98 svn_url_pattern+="\\|$value" 99 upstream=svn+git # default upstream is SVN if available, else git 100;; 101esac 102done<<<"$output" 103 104# parse configuration values 105for option in${GIT_PS1_SHOWUPSTREAM};do 106case"$option"in 107 git|svn) upstream="$option";; 108 verbose) verbose=1;; 109 legacy) legacy=1;; 110esac 111done 112 113# Find our upstream 114case"$upstream"in 115 git) upstream="@{upstream}";; 116 svn*) 117# get the upstream from the "git-svn-id: ..." in a commit message 118# (git-svn uses essentially the same procedure internally) 119local svn_upstream=($(git log --first-parent -1 \ 120--grep="^git-svn-id: \(${svn_url_pattern#??}\)"2>/dev/null)) 121if[[0-ne${#svn_upstream[@]}]];then 122 svn_upstream=${svn_upstream[ ${#svn_upstream[@]} - 2 ]} 123 svn_upstream=${svn_upstream%@*} 124local n_stop="${#svn_remote[@]}" 125for((n=1; n <= n_stop; n++));do 126 svn_upstream=${svn_upstream#${svn_remote[$n]}} 127done 128 129if[[-z"$svn_upstream"]];then 130# default branch name for checkouts with no layout: 131 upstream=${GIT_SVN_ID:-git-svn} 132else 133 upstream=${svn_upstream#/} 134fi 135elif[["svn+git"="$upstream"]];then 136 upstream="@{upstream}" 137fi 138;; 139esac 140 141# Find how many commits we are ahead/behind our upstream 142if[[-z"$legacy"]];then 143 count="$(git rev-list --count --left-right \ 144 "$upstream"...HEAD 2>/dev/null)" 145else 146# produce equivalent output to --count for older versions of git 147local commits 148if commits="$(git rev-list --left-right "$upstream"...HEAD 2>/dev/null)" 149then 150local commit behind=0 ahead=0 151for commit in$commits 152do 153case"$commit"in 154"<"*) ((behind++)) ;; 155*) ((ahead++)) ;; 156esac 157done 158 count="$behind$ahead" 159else 160 count="" 161fi 162fi 163 164# calculate the result 165if[[-z"$verbose"]];then 166case"$count"in 167"")# no upstream 168 p="";; 169"0 0")# equal to upstream 170 p="=";; 171"0 "*)# ahead of upstream 172 p=">";; 173*" 0")# behind upstream 174 p="<";; 175*)# diverged from upstream 176 p="<>";; 177esac 178else 179case"$count"in 180"")# no upstream 181 p="";; 182"0 0")# equal to upstream 183 p=" u=";; 184"0 "*)# ahead of upstream 185 p=" u+${count#0 }";; 186*" 0")# behind upstream 187 p=" u-${count% 0}";; 188*)# diverged from upstream 189 p=" u+${count#* }-${count% *}";; 190esac 191fi 192 193} 194 195 196# __git_ps1 accepts 0 or 1 arguments (i.e., format string) 197# returns text to add to bash PS1 prompt (includes branch name) 198__git_ps1 () 199{ 200local g="$(__gitdir)" 201if[-n"$g"];then 202local r="" 203local b="" 204if[-f"$g/rebase-merge/interactive"];then 205 r="|REBASE-i" 206 b="$(cat "$g/rebase-merge/head-name")" 207elif[-d"$g/rebase-merge"];then 208 r="|REBASE-m" 209 b="$(cat "$g/rebase-merge/head-name")" 210else 211if[-d"$g/rebase-apply"];then 212if[-f"$g/rebase-apply/rebasing"];then 213 r="|REBASE" 214elif[-f"$g/rebase-apply/applying"];then 215 r="|AM" 216else 217 r="|AM/REBASE" 218fi 219elif[-f"$g/MERGE_HEAD"];then 220 r="|MERGING" 221elif[-f"$g/CHERRY_PICK_HEAD"];then 222 r="|CHERRY-PICKING" 223elif[-f"$g/BISECT_LOG"];then 224 r="|BISECTING" 225fi 226 227 b="$(git symbolic-ref HEAD 2>/dev/null)"|| { 228 229 b="$( 230 case "${GIT_PS1_DESCRIBE_STYLE-}" in 231 (contains) 232 git describe --contains HEAD ;; 233 (branch) 234 git describe --contains --all HEAD ;; 235 (describe) 236 git describe HEAD ;; 237 (* | default) 238 git describe --tags --exact-match HEAD ;; 239 esac 2>/dev/null)"|| 240 241 b="$(cut -c1-7 "$g/HEAD" 2>/dev/null)..."|| 242 b="unknown" 243 b="($b)" 244} 245fi 246 247local w="" 248local i="" 249local s="" 250local u="" 251local c="" 252local p="" 253 254if["true"="$(git rev-parse --is-inside-git-dir 2>/dev/null)"];then 255if["true"="$(git rev-parse --is-bare-repository 2>/dev/null)"];then 256 c="BARE:" 257else 258 b="GIT_DIR!" 259fi 260elif["true"="$(git rev-parse --is-inside-work-tree 2>/dev/null)"];then 261if[-n"${GIT_PS1_SHOWDIRTYSTATE-}"];then 262if["$(git config --bool bash.showDirtyState)"!="false"];then 263 git diff--no-ext-diff --quiet --exit-code|| w="*" 264if git rev-parse --quiet --verify HEAD >/dev/null;then 265 git diff-index --cached --quiet HEAD --|| i="+" 266else 267 i="#" 268fi 269fi 270fi 271if[-n"${GIT_PS1_SHOWSTASHSTATE-}"];then 272 git rev-parse --verify refs/stash >/dev/null 2>&1&& s="$" 273fi 274 275if[-n"${GIT_PS1_SHOWUNTRACKEDFILES-}"];then 276if[-n"$(git ls-files --others --exclude-standard)"];then 277 u="%" 278fi 279fi 280 281if[-n"${GIT_PS1_SHOWUPSTREAM-}"];then 282 __git_ps1_show_upstream 283fi 284fi 285 286local f="$w$i$s$u" 287printf --"${1:- (%s)}""$c${b##refs/heads/}${f:+ $f}$r$p" 288fi 289}