8
8
# To enable:
9
9
#
10
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
11
+ # 2) Add the following line to your .bashrc/.zshrc/.profile :
12
+ # . ~/.git-prompt.sh # dot path/to/this-file
13
13
# 3a) Change your PS1 to call __git_ps1 as
14
14
# command-substitution:
15
15
# Bash: PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ '
30
30
# Optionally, you can supply a third argument with a printf
31
31
# format string to finetune the output of the branch status
32
32
#
33
+ # See notes below about compatibility with other shells.
34
+ #
33
35
# The repository status will be displayed only if you are currently in a
34
36
# git repository. The %s token is the placeholder for the shown status.
35
37
#
106
108
# directory is set up to be ignored by git, then set
107
109
# GIT_PS1_HIDE_IF_PWD_IGNORED to a nonempty value. Override this on the
108
110
# repository level by setting bash.hideIfPwdIgnored to "false".
111
+ #
112
+ # Compatibility with other shells (beyond bash/zsh):
113
+ #
114
+ # We require posix-ish shell plus "local" support, which is most
115
+ # shells (even pdksh), but excluding ksh93 (because no "local").
116
+ #
117
+ # Prompt integration might differ between shells, but the gist is
118
+ # to load it once on shell init with '. path/to/git-prompt.sh',
119
+ # set GIT_PS1* vars once as needed, and either place $(__git_ps1..)
120
+ # inside PS1 once (0/1 args), or, before each prompt is displayed,
121
+ # call __git_ps1 (2/3 args) which sets PS1 with the status embedded.
122
+ #
123
+ # Many shells support the 1st method of command substitution,
124
+ # though some might need to first enable cmd substitution in PS1.
125
+ #
126
+ # When using colors, each escape sequence is wrapped between byte
127
+ # values 1 and 2 (control chars SOH, STX, respectively), which are
128
+ # invisible at the output, but for bash/readline they mark 0-width
129
+ # strings (SGR color sequences) when calculating the on-screen
130
+ # prompt width, to maintain correct input editing at the prompt.
131
+ #
132
+ # To replace or disable the 0-width markers, set GIT_PS1_COLOR_PRE
133
+ # and GIT_PS1_COLOR_POST to other markers, or empty (nul) to not
134
+ # use markers. For instance, some shells support '\[' and '\]' as
135
+ # start/end markers in PS1 - when invoking __git_ps1 with 3/4 args,
136
+ # but it may or may not work in command substitution mode. YMMV.
137
+ #
138
+ # If the shell doesn't support 0-width markers and editing behaves
139
+ # incorrectly when using colors in __git_ps1, then, other than
140
+ # disabling color, it might be solved using multi-line prompt,
141
+ # where the git status is not at the last line, e.g.:
142
+ # PS1='\n\w \u@\h$(__git_ps1 " (%s)")\n\$ '
109
143
110
144
# check whether printf supports -v
111
145
__git_printf_supports_v=
112
146
printf -v __git_printf_supports_v -- ' %s' yes > /dev/null 2>&1
113
147
148
+ # like __git_SOH=$'\001' etc but works also in shells without $'...'
149
+ eval " $( printf '
150
+ __git_SOH="\001" __git_STX="\002" __git_ESC="\033"
151
+ __git_LF="\n" __git_CRLF="\r\n"
152
+ ' ) "
153
+
114
154
# stores the divergence from upstream in $p
115
155
# used by GIT_PS1_SHOWUPSTREAM
116
156
__git_ps1_show_upstream ()
117
157
{
118
158
local key value
119
- local svn_remote svn_url_pattern count n
159
+ local svn_remotes= " " svn_url_pattern= " " count n
120
160
local upstream_type=git legacy=" " verbose=" " name=" "
161
+ local LF=" $__git_LF "
121
162
122
- svn_remote=()
123
163
# get some config options from git-config
124
164
local output=" $( git config -z --get-regexp ' ^(svn-remote\..*\.url|bash\.showupstream)$' 2> /dev/null | tr ' \0\n' ' \n ' ) "
125
165
while read -r key value; do
126
166
case " $key " in
127
167
bash.showupstream)
128
168
GIT_PS1_SHOWUPSTREAM=" $value "
129
- if [[ -z " ${GIT_PS1_SHOWUPSTREAM} " ] ]; then
169
+ if [ -z " ${GIT_PS1_SHOWUPSTREAM} " ]; then
130
170
p=" "
131
171
return
132
172
fi
133
173
;;
134
174
svn-remote.* .url)
135
- svn_remote[ $(( ${ # svn_remote[@]} + 1 )) ]= " $value "
175
+ svn_remotes= ${svn_remotes}${value}${LF} # URI\nURI\n...
136
176
svn_url_pattern=" $svn_url_pattern \\ |$value "
137
177
upstream_type=svn+git # default upstream type is SVN if available, else git
138
178
;;
139
179
esac
140
- done <<< " $output"
180
+ done << -OUTPUT
181
+ $output
182
+ OUTPUT
141
183
142
184
# parse configuration values
143
185
local option
@@ -154,33 +196,45 @@ __git_ps1_show_upstream ()
154
196
case " $upstream_type " in
155
197
git) upstream_type=" @{upstream}" ;;
156
198
svn* )
157
- # get the upstream from the "git-svn-id: ..." in a commit message
158
- # (git-svn uses essentially the same procedure internally)
159
- local -a svn_upstream
160
- svn_upstream=($( git log --first-parent -1 \
161
- --grep=" ^git-svn-id: \(${svn_url_pattern# ??} \)" 2> /dev/null) )
162
- if [[ 0 -ne ${# svn_upstream[@]} ]]; then
163
- svn_upstream=${svn_upstream[${#svn_upstream[@]} - 2]}
164
- svn_upstream=${svn_upstream%@* }
165
- local n_stop=" ${# svn_remote[@]} "
166
- for (( n= 1 ; n <= n_stop; n++ )) ; do
167
- svn_upstream=${svn_upstream# ${svn_remote[$n]} }
168
- done
199
+ # successful svn-upstream resolution:
200
+ # - get the list of configured svn-remotes ($svn_remotes set above)
201
+ # - get the last commit which seems from one of our svn-remotes
202
+ # - confirm that it is from one of the svn-remotes
203
+ # - use $GIT_SVN_ID if set, else "git-svn"
169
204
170
- if [[ -z " $svn_upstream " ]]; then
205
+ # get upstream from "git-svn-id: UPSTRM@N HASH" in a commit message
206
+ # (git-svn uses essentially the same procedure internally)
207
+ local svn_upstream=" $(
208
+ git log --first-parent -1 \
209
+ --grep=" ^git-svn-id: \(${svn_url_pattern# ??} \)" 2> /dev/null
210
+ ) "
211
+
212
+ if [ -n " $svn_upstream " ]; then
213
+ # extract the URI, assuming --grep matched the last line
214
+ svn_upstream=${svn_upstream##* $LF } # last line
215
+ svn_upstream=${svn_upstream#*: } # UPSTRM@N HASH
216
+ svn_upstream=${svn_upstream%@* } # UPSTRM
217
+
218
+ case ${LF}${svn_remotes} in
219
+ * " ${LF}${svn_upstream}${LF} " * )
220
+ # grep indeed matched the last line - it's our remote
171
221
# default branch name for checkouts with no layout:
172
222
upstream_type=${GIT_SVN_ID:- git-svn}
173
- else
223
+ ;;
224
+ * )
225
+ # the commit message includes one of our remotes, but
226
+ # it's not at the last line. is $svn_upstream junk?
174
227
upstream_type=${svn_upstream#/ }
175
- fi
176
- elif [[ " svn+git" = " $upstream_type " ]]; then
228
+ ;;
229
+ esac
230
+ elif [ " svn+git" = " $upstream_type " ]; then
177
231
upstream_type=" @{upstream}"
178
232
fi
179
233
;;
180
234
esac
181
235
182
236
# Find how many commits we are ahead/behind our upstream
183
- if [[ -z " $legacy " ] ]; then
237
+ if [ -z " $legacy " ]; then
184
238
count=" $( git rev-list --count --left-right \
185
239
" $upstream_type " ...HEAD 2> /dev/null) "
186
240
else
@@ -192,8 +246,8 @@ __git_ps1_show_upstream ()
192
246
for commit in $commits
193
247
do
194
248
case " $commit " in
195
- " <" * ) (( behind++ )) ;;
196
- * ) (( ahead++ )) ;;
249
+ " <" * ) behind= $ (( behind+ 1 )) ;;
250
+ * ) ahead= $ (( ahead+ 1 )) ;;
197
251
esac
198
252
done
199
253
count=" $behind $ahead "
@@ -203,7 +257,7 @@ __git_ps1_show_upstream ()
203
257
fi
204
258
205
259
# calculate the result
206
- if [[ -z " $verbose " ] ]; then
260
+ if [ -z " $verbose " ]; then
207
261
case " $count " in
208
262
" " ) # no upstream
209
263
p=" " ;;
@@ -229,10 +283,10 @@ __git_ps1_show_upstream ()
229
283
* ) # diverged from upstream
230
284
upstream=" |u+${count#* } -${count% * } " ;;
231
285
esac
232
- if [[ -n " $count " && -n " $name " ] ]; then
286
+ if [ -n " $count " ] && [ -n " $name " ]; then
233
287
__git_ps1_upstream_name=$( git rev-parse \
234
288
--abbrev-ref " $upstream_type " 2> /dev/null)
235
- if [ $pcmode = yes ] && [ $ps1_expanded = yes ]; then
289
+ if [ " $pcmode " = yes ] && [ " $ps1_expanded " = yes ]; then
236
290
upstream=" $upstream \$ {__git_ps1_upstream_name}"
237
291
else
238
292
upstream=" $upstream ${__git_ps1_upstream_name} "
@@ -251,25 +305,29 @@ __git_ps1_show_upstream ()
251
305
# their own color.
252
306
__git_ps1_colorize_gitstring ()
253
307
{
254
- if [[ -n ${ZSH_VERSION-} ] ]; then
308
+ if [ -n " ${ZSH_VERSION-} " ]; then
255
309
local c_red=' %F{red}'
256
310
local c_green=' %F{green}'
257
311
local c_lblue=' %F{blue}'
258
312
local c_clear=' %f'
259
313
else
260
- # Using \001 and \002 around colors is necessary to prevent
261
- # issues with command line editing/browsing/completion!
262
- local c_red=$' \001 \e [31m\002 '
263
- local c_green=$' \001 \e [32m\002 '
264
- local c_lblue=$' \001 \e [1;34m\002 '
265
- local c_clear=$' \001 \e [0m\002 '
314
+ # \001 (SOH) and \002 (STX) are 0-width substring markers
315
+ # which bash/readline identify while calculating the prompt
316
+ # on-screen width - to exclude 0-screen-width esc sequences.
317
+ local c_pre=" ${GIT_PS1_COLOR_PRE-$__git_SOH }${__git_ESC} ["
318
+ local c_post=" m${GIT_PS1_COLOR_POST-$__git_STX } "
319
+
320
+ local c_red=" ${c_pre} 31${c_post} "
321
+ local c_green=" ${c_pre} 32${c_post} "
322
+ local c_lblue=" ${c_pre} 1;34${c_post} "
323
+ local c_clear=" ${c_pre} 0${c_post} "
266
324
fi
267
- local bad_color=$c_red
268
- local ok_color=$c_green
325
+ local bad_color=" $c_red "
326
+ local ok_color=" $c_green "
269
327
local flags_color=" $c_lblue "
270
328
271
329
local branch_color=" "
272
- if [ $detached = no ]; then
330
+ if [ " $detached " = no ]; then
273
331
branch_color=" $ok_color "
274
332
else
275
333
branch_color=" $bad_color "
@@ -298,7 +356,7 @@ __git_ps1_colorize_gitstring ()
298
356
# variable, in that order.
299
357
__git_eread ()
300
358
{
301
- test -r " $1 " && IFS=$' \r\n ' read -r " $2 " < " $1 "
359
+ test -r " $1 " && IFS=$__git_CRLF read -r " $2 " < " $1 "
302
360
}
303
361
304
362
# see if a cherry-pick or revert is in progress, if the user has committed a
@@ -346,7 +404,7 @@ __git_sequencer_status ()
346
404
__git_ps1 ()
347
405
{
348
406
# preserve exit status
349
- local exit=$?
407
+ local exit=" $? "
350
408
local pcmode=no
351
409
local detached=no
352
410
local ps1pc_start=' \u@\h:\w '
@@ -365,7 +423,7 @@ __git_ps1 ()
365
423
;;
366
424
0|1) printf_format=" ${1:- $printf_format } "
367
425
;;
368
- * ) return $exit
426
+ * ) return " $exit "
369
427
;;
370
428
esac
371
429
@@ -403,7 +461,7 @@ __git_ps1 ()
403
461
# incorrect.)
404
462
#
405
463
local ps1_expanded=yes
406
- [ -z " ${ZSH_VERSION-} " ] || [[ -o PROMPT_SUBST ]] || ps1_expanded=no
464
+ [ -z " ${ZSH_VERSION-} " ] || eval ' [[ -o PROMPT_SUBST ]]' || ps1_expanded=no
407
465
[ -z " ${BASH_VERSION-} " ] || shopt -q promptvars || ps1_expanded=no
408
466
409
467
local repo_info rev_parse_exit_code
@@ -413,29 +471,30 @@ __git_ps1 ()
413
471
rev_parse_exit_code=" $? "
414
472
415
473
if [ -z " $repo_info " ]; then
416
- return $exit
474
+ return " $exit "
417
475
fi
418
476
477
+ local LF=" $__git_LF "
419
478
local short_sha=" "
420
479
if [ " $rev_parse_exit_code " = " 0" ]; then
421
- short_sha=" ${repo_info##* $' \n ' } "
422
- repo_info=" ${repo_info% $' \n ' * } "
480
+ short_sha=" ${repo_info##* $LF } "
481
+ repo_info=" ${repo_info% $LF * } "
423
482
fi
424
- local ref_format=" ${repo_info##* $' \n ' } "
425
- repo_info=" ${repo_info% $' \n ' * } "
426
- local inside_worktree=" ${repo_info##* $' \n ' } "
427
- repo_info=" ${repo_info% $' \n ' * } "
428
- local bare_repo=" ${repo_info##* $' \n ' } "
429
- repo_info=" ${repo_info% $' \n ' * } "
430
- local inside_gitdir=" ${repo_info##* $' \n ' } "
431
- local g=" ${repo_info% $' \n ' * } "
483
+ local ref_format=" ${repo_info##* $LF } "
484
+ repo_info=" ${repo_info% $LF * } "
485
+ local inside_worktree=" ${repo_info##* $LF } "
486
+ repo_info=" ${repo_info% $LF * } "
487
+ local bare_repo=" ${repo_info##* $LF } "
488
+ repo_info=" ${repo_info% $LF * } "
489
+ local inside_gitdir=" ${repo_info##* $LF } "
490
+ local g=" ${repo_info% $LF * } "
432
491
433
492
if [ " true" = " $inside_worktree " ] &&
434
493
[ -n " ${GIT_PS1_HIDE_IF_PWD_IGNORED-} " ] &&
435
494
[ " $( git config --bool bash.hideIfPwdIgnored) " != " false" ] &&
436
495
git check-ignore -q .
437
496
then
438
- return $exit
497
+ return " $exit "
439
498
fi
440
499
441
500
local sparse=" "
@@ -485,14 +544,16 @@ __git_ps1 ()
485
544
case " $ref_format " in
486
545
files)
487
546
if ! __git_eread " $g /HEAD" head; then
488
- return $exit
547
+ return " $exit "
489
548
fi
490
549
491
- if [[ $head == " ref: " * ]]; then
550
+ case $head in
551
+ " ref: " * )
492
552
head=" ${head# ref: } "
493
- else
553
+ ;;
554
+ * )
494
555
head=" "
495
- fi
556
+ esac
496
557
;;
497
558
* )
498
559
head=" $( git symbolic-ref HEAD 2> /dev/null) "
@@ -528,8 +589,8 @@ __git_ps1 ()
528
589
fi
529
590
530
591
local conflict=" " # state indicator for unresolved conflicts
531
- if [[ " ${GIT_PS1_SHOWCONFLICTSTATE-} " == " yes" ] ] &&
532
- [[ $( git ls-files --unmerged 2> /dev/null) ] ]; then
592
+ if [ " ${GIT_PS1_SHOWCONFLICTSTATE-} " = " yes" ] &&
593
+ [ " $( git ls-files --unmerged 2> /dev/null) " ]; then
533
594
conflict=" |CONFLICT"
534
595
fi
535
596
@@ -581,10 +642,10 @@ __git_ps1 ()
581
642
fi
582
643
fi
583
644
584
- local z=" ${GIT_PS1_STATESEPARATOR-" " } "
645
+ local z=" ${GIT_PS1_STATESEPARATOR- } "
585
646
586
647
b=${b## refs/ heads/ }
587
- if [ $pcmode = yes ] && [ $ps1_expanded = yes ]; then
648
+ if [ " $pcmode " = yes ] && [ " $ps1_expanded " = yes ]; then
588
649
__git_ps1_branch_name=$b
589
650
b=" \$ {__git_ps1_branch_name}"
590
651
fi
@@ -596,7 +657,7 @@ __git_ps1 ()
596
657
local f=" $h$w$i$s$u$p "
597
658
local gitstring=" $c$b ${f: +$z$f }${sparse} $r ${upstream}${conflict} "
598
659
599
- if [ $pcmode = yes ]; then
660
+ if [ " $pcmode " = yes ]; then
600
661
if [ " ${__git_printf_supports_v-} " != yes ]; then
601
662
gitstring=$( printf -- " $printf_format " " $gitstring " )
602
663
else
@@ -607,5 +668,5 @@ __git_ps1 ()
607
668
printf -- " $printf_format " " $gitstring "
608
669
fi
609
670
610
- return $exit
671
+ return " $exit "
611
672
}
0 commit comments