88# To enable:
99#
1010# 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
1313# 3a) Change your PS1 to call __git_ps1 as
1414# command-substitution:
1515# Bash: PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ '
3030# Optionally, you can supply a third argument with a printf
3131# format string to finetune the output of the branch status
3232#
33+ # See notes below about compatibility with other shells.
34+ #
3335# The repository status will be displayed only if you are currently in a
3436# git repository. The %s token is the placeholder for the shown status.
3537#
106108# directory is set up to be ignored by git, then set
107109# GIT_PS1_HIDE_IF_PWD_IGNORED to a nonempty value. Override this on the
108110# 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\$ '
109143
110144# check whether printf supports -v
111145__git_printf_supports_v=
112146printf -v __git_printf_supports_v -- ' %s' yes > /dev/null 2>&1
113147
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+
114154# stores the divergence from upstream in $p
115155# used by GIT_PS1_SHOWUPSTREAM
116156__git_ps1_show_upstream ()
117157{
118158 local key value
119- local svn_remote svn_url_pattern count n
159+ local svn_remotes= " " svn_url_pattern= " " count n
120160 local upstream_type=git legacy=" " verbose=" " name=" "
161+ local LF=" $__git_LF "
121162
122- svn_remote=()
123163 # get some config options from git-config
124164 local output=" $( git config -z --get-regexp ' ^(svn-remote\..*\.url|bash\.showupstream)$' 2> /dev/null | tr ' \0\n' ' \n ' ) "
125165 while read -r key value; do
126166 case " $key " in
127167 bash.showupstream)
128168 GIT_PS1_SHOWUPSTREAM=" $value "
129- if [[ -z " ${GIT_PS1_SHOWUPSTREAM} " ] ]; then
169+ if [ -z " ${GIT_PS1_SHOWUPSTREAM} " ]; then
130170 p=" "
131171 return
132172 fi
133173 ;;
134174 svn-remote.* .url)
135- svn_remote[ $(( ${ # svn_remote[@]} + 1 )) ]= " $value "
175+ svn_remotes= ${svn_remotes}${value}${LF} # URI\nURI\n...
136176 svn_url_pattern=" $svn_url_pattern \\ |$value "
137177 upstream_type=svn+git # default upstream type is SVN if available, else git
138178 ;;
139179 esac
140- done <<< " $output"
180+ done << -OUTPUT
181+ $output
182+ OUTPUT
141183
142184 # parse configuration values
143185 local option
@@ -154,33 +196,45 @@ __git_ps1_show_upstream ()
154196 case " $upstream_type " in
155197 git) upstream_type=" @{upstream}" ;;
156198 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"
169204
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
171221 # default branch name for checkouts with no layout:
172222 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?
174227 upstream_type=${svn_upstream#/ }
175- fi
176- elif [[ " svn+git" = " $upstream_type " ]]; then
228+ ;;
229+ esac
230+ elif [ " svn+git" = " $upstream_type " ]; then
177231 upstream_type=" @{upstream}"
178232 fi
179233 ;;
180234 esac
181235
182236 # Find how many commits we are ahead/behind our upstream
183- if [[ -z " $legacy " ] ]; then
237+ if [ -z " $legacy " ]; then
184238 count=" $( git rev-list --count --left-right \
185239 " $upstream_type " ...HEAD 2> /dev/null) "
186240 else
@@ -192,8 +246,8 @@ __git_ps1_show_upstream ()
192246 for commit in $commits
193247 do
194248 case " $commit " in
195- " <" * ) (( behind++ )) ;;
196- * ) (( ahead++ )) ;;
249+ " <" * ) behind= $ (( behind+ 1 )) ;;
250+ * ) ahead= $ (( ahead+ 1 )) ;;
197251 esac
198252 done
199253 count=" $behind $ahead "
@@ -203,7 +257,7 @@ __git_ps1_show_upstream ()
203257 fi
204258
205259 # calculate the result
206- if [[ -z " $verbose " ] ]; then
260+ if [ -z " $verbose " ]; then
207261 case " $count " in
208262 " " ) # no upstream
209263 p=" " ;;
@@ -229,10 +283,10 @@ __git_ps1_show_upstream ()
229283 * ) # diverged from upstream
230284 upstream=" |u+${count#* } -${count% * } " ;;
231285 esac
232- if [[ -n " $count " && -n " $name " ] ]; then
286+ if [ -n " $count " ] && [ -n " $name " ]; then
233287 __git_ps1_upstream_name=$( git rev-parse \
234288 --abbrev-ref " $upstream_type " 2> /dev/null)
235- if [ $pcmode = yes ] && [ $ps1_expanded = yes ]; then
289+ if [ " $pcmode " = yes ] && [ " $ps1_expanded " = yes ]; then
236290 upstream=" $upstream \$ {__git_ps1_upstream_name}"
237291 else
238292 upstream=" $upstream ${__git_ps1_upstream_name} "
@@ -251,25 +305,29 @@ __git_ps1_show_upstream ()
251305# their own color.
252306__git_ps1_colorize_gitstring ()
253307{
254- if [[ -n ${ZSH_VERSION-} ] ]; then
308+ if [ -n " ${ZSH_VERSION-} " ]; then
255309 local c_red=' %F{red}'
256310 local c_green=' %F{green}'
257311 local c_lblue=' %F{blue}'
258312 local c_clear=' %f'
259313 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} "
266324 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 "
269327 local flags_color=" $c_lblue "
270328
271329 local branch_color=" "
272- if [ $detached = no ]; then
330+ if [ " $detached " = no ]; then
273331 branch_color=" $ok_color "
274332 else
275333 branch_color=" $bad_color "
@@ -298,7 +356,7 @@ __git_ps1_colorize_gitstring ()
298356# variable, in that order.
299357__git_eread ()
300358{
301- test -r " $1 " && IFS=$' \r\n ' read -r " $2 " < " $1 "
359+ test -r " $1 " && IFS=$__git_CRLF read -r " $2 " < " $1 "
302360}
303361
304362# see if a cherry-pick or revert is in progress, if the user has committed a
@@ -346,7 +404,7 @@ __git_sequencer_status ()
346404__git_ps1 ()
347405{
348406 # preserve exit status
349- local exit=$?
407+ local exit=" $? "
350408 local pcmode=no
351409 local detached=no
352410 local ps1pc_start=' \u@\h:\w '
@@ -365,7 +423,7 @@ __git_ps1 ()
365423 ;;
366424 0|1) printf_format=" ${1:- $printf_format } "
367425 ;;
368- * ) return $exit
426+ * ) return " $exit "
369427 ;;
370428 esac
371429
@@ -403,7 +461,7 @@ __git_ps1 ()
403461 # incorrect.)
404462 #
405463 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
407465 [ -z " ${BASH_VERSION-} " ] || shopt -q promptvars || ps1_expanded=no
408466
409467 local repo_info rev_parse_exit_code
@@ -413,29 +471,30 @@ __git_ps1 ()
413471 rev_parse_exit_code=" $? "
414472
415473 if [ -z " $repo_info " ]; then
416- return $exit
474+ return " $exit "
417475 fi
418476
477+ local LF=" $__git_LF "
419478 local short_sha=" "
420479 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 * } "
423482 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 * } "
432491
433492 if [ " true" = " $inside_worktree " ] &&
434493 [ -n " ${GIT_PS1_HIDE_IF_PWD_IGNORED-} " ] &&
435494 [ " $( git config --bool bash.hideIfPwdIgnored) " != " false" ] &&
436495 git check-ignore -q .
437496 then
438- return $exit
497+ return " $exit "
439498 fi
440499
441500 local sparse=" "
@@ -485,14 +544,16 @@ __git_ps1 ()
485544 case " $ref_format " in
486545 files)
487546 if ! __git_eread " $g /HEAD" head; then
488- return $exit
547+ return " $exit "
489548 fi
490549
491- if [[ $head == " ref: " * ]]; then
550+ case $head in
551+ " ref: " * )
492552 head=" ${head# ref: } "
493- else
553+ ;;
554+ * )
494555 head=" "
495- fi
556+ esac
496557 ;;
497558 * )
498559 head=" $( git symbolic-ref HEAD 2> /dev/null) "
@@ -528,8 +589,8 @@ __git_ps1 ()
528589 fi
529590
530591 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
533594 conflict=" |CONFLICT"
534595 fi
535596
@@ -581,10 +642,10 @@ __git_ps1 ()
581642 fi
582643 fi
583644
584- local z=" ${GIT_PS1_STATESEPARATOR-" " } "
645+ local z=" ${GIT_PS1_STATESEPARATOR- } "
585646
586647 b=${b## refs/ heads/ }
587- if [ $pcmode = yes ] && [ $ps1_expanded = yes ]; then
648+ if [ " $pcmode " = yes ] && [ " $ps1_expanded " = yes ]; then
588649 __git_ps1_branch_name=$b
589650 b=" \$ {__git_ps1_branch_name}"
590651 fi
@@ -596,7 +657,7 @@ __git_ps1 ()
596657 local f=" $h$w$i$s$u$p "
597658 local gitstring=" $c$b ${f: +$z$f }${sparse} $r ${upstream}${conflict} "
598659
599- if [ $pcmode = yes ]; then
660+ if [ " $pcmode " = yes ]; then
600661 if [ " ${__git_printf_supports_v-} " != yes ]; then
601662 gitstring=$( printf -- " $printf_format " " $gitstring " )
602663 else
@@ -607,5 +668,5 @@ __git_ps1 ()
607668 printf -- " $printf_format " " $gitstring "
608669 fi
609670
610- return $exit
671+ return " $exit "
611672}
0 commit comments