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