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