completion: split __git_ps1 into a separate script

bash-completion 1.90 shipped with support to load completions dynamically[1], which means the git completion script wouldn't be loaded until the user types 'git <tab>'--this creates a problem to people using __git_ps1(); that function won't be available when the shell is first created. For now distributions have workarounded this issue by moving the git completion to the "compatdir"[2]; this of course is not ideal. The solution, proposed by Kerrick Staley[3], is to split the git script in two; the part that deals with __git_ps1() in one (i.e. git-prompt.sh), and everything else in another (i.e. git-completion.bash). Another benefit of this is that zsh user that are not interested in the bash completion can use it for their prompts, which has been tried before[4]. The only slight issue is that __gitdir() would be duplicated, but this is probably not a big deal. So let's go ahead and move __git_ps1() to a new file. While at this, I took the liberty to reformat the help text in the new file. [1] http://anonscm.debian.org/gitweb/?p=bash-completion/bash-completion.git;a=commitdiff;h=99c4f7f25f50a7cb2fce86055bddfe389effa559 [2] http://projects.archlinux.org/svntogit/packages.git/commit/trunk?h=packages/git&id=974380fabb8f9f412990b17063bf578d98c44a82 [3] http://mid.gmane.org/CANaWP3w9KDu57aHquRRYt8td_haSWTBKs7zUHy-xu0B61gmr9A@mail.gmail.com [4] http://mid.gmane.org/1303824288-15591-1-git-send-email-mstormo@gmail.com Cc: Kerrick Staley <mail@kerrickstaley.com> Cc: Marius Storm-Olsen <mstormo@gmail.com> Cc: Ville Skyttä <ville.skytta@iki.fi> Cc: Dan McGee <dan@archlinux.org> Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Felipe Contreras committed May 22, 2012 at 22:46 UTC af31a456b4cd38f2630ed8e556e23954f806a3cc
3 files changed +290 -256
contrib/completion/git-completion.bash
+2 -255
@@ -20,46 +20,8 @@
20 # 1) Copy this file to somewhere (e.g. ~/.git-completion.sh).
21 # 2) Add the following line to your .bashrc/.zshrc:
22 # source ~/.git-completion.sh
23 -#
24 -# 3) Consider changing your PS1 to also show the current branch:
25 -# Bash: PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ '
26 -# ZSH: PS1='[%n@%m %c$(__git_ps1 " (%s)")]\$ '
27 -#
28 -# The argument to __git_ps1 will be displayed only if you
29 -# are currently in a git repository. The %s token will be
30 -# the name of the current branch.
31 -#
32 -# In addition, if you set GIT_PS1_SHOWDIRTYSTATE to a nonempty
33 -# value, unstaged (*) and staged (+) changes will be shown next
34 -# to the branch name. You can configure this per-repository
35 -# with the bash.showDirtyState variable, which defaults to true
36 -# once GIT_PS1_SHOWDIRTYSTATE is enabled.
37 -#
38 -# You can also see if currently something is stashed, by setting
39 -# GIT_PS1_SHOWSTASHSTATE to a nonempty value. If something is stashed,
40 -# then a '$' will be shown next to the branch name.
41 -#
42 -# If you would like to see if there're untracked files, then you can
43 -# set GIT_PS1_SHOWUNTRACKEDFILES to a nonempty value. If there're
44 -# untracked files, then a '%' will be shown next to the branch name.
45 -#
46 -# If you would like to see the difference between HEAD and its
47 -# upstream, set GIT_PS1_SHOWUPSTREAM="auto". A "<" indicates
48 -# you are behind, ">" indicates you are ahead, and "<>"
49 -# indicates you have diverged. You can further control
50 -# behaviour by setting GIT_PS1_SHOWUPSTREAM to a space-separated
51 -# list of values:
52 -# verbose show number of commits ahead/behind (+/-) upstream
53 -# legacy don't use the '--count' option available in recent
54 -# versions of git-rev-list
55 -# git always compare HEAD to @{upstream}
56 -# svn always compare HEAD to your SVN upstream
57 -# By default, __git_ps1 will compare HEAD to your SVN upstream
58 -# if it can find one, or @{upstream} otherwise. Once you have
59 -# set GIT_PS1_SHOWUPSTREAM, you can override it on a
60 -# per-repository basis by setting the bash.showUpstream config
61 -# variable.
62 -#
23 +# 3) Consider changing your PS1 to also show the current branch,
24 +# see git-prompt.sh for details.
25
26 if [[ -n ${ZSH_VERSION-} ]]; then
27 autoload -U +X bashcompinit && bashcompinit
@@ -89,221 +51,6 @@ __gitdir ()
51 fi
52 }
53
92 -# stores the divergence from upstream in $p
93 -# used by GIT_PS1_SHOWUPSTREAM
94 -__git_ps1_show_upstream ()
95 -{
96 - local key value
97 - local svn_remote svn_url_pattern count n
98 - local upstream=git legacy="" verbose=""
99 -
100 - svn_remote=()
101 - # get some config options from git-config
102 - local output="$(git config -z --get-regexp '^(svn-remote\..*\.url|bash\.showupstream)$' 2>/dev/null | tr '\0\n' '\n ')"
103 - while read -r key value; do
104 - case "$key" in
105 - bash.showupstream)
106 - GIT_PS1_SHOWUPSTREAM="$value"
107 - if [[ -z "${GIT_PS1_SHOWUPSTREAM}" ]]; then
108 - p=""
109 - return
110 - fi
111 - ;;
112 - svn-remote.*.url)
113 - svn_remote[ $((${#svn_remote[@]} + 1)) ]="$value"
114 - svn_url_pattern+="\\|$value"
115 - upstream=svn+git # default upstream is SVN if available, else git
116 - ;;
117 - esac
118 - done <<< "$output"
119 -
120 - # parse configuration values
121 - for option in ${GIT_PS1_SHOWUPSTREAM}; do
122 - case "$option" in
123 - git|svn) upstream="$option" ;;
124 - verbose) verbose=1 ;;
125 - legacy) legacy=1 ;;
126 - esac
127 - done
128 -
129 - # Find our upstream
130 - case "$upstream" in
131 - git) upstream="@{upstream}" ;;
132 - svn*)
133 - # get the upstream from the "git-svn-id: ..." in a commit message
134 - # (git-svn uses essentially the same procedure internally)
135 - local svn_upstream=($(git log --first-parent -1 \
136 - --grep="^git-svn-id: \(${svn_url_pattern#??}\)" 2>/dev/null))
137 - if [[ 0 -ne ${#svn_upstream[@]} ]]; then
138 - svn_upstream=${svn_upstream[ ${#svn_upstream[@]} - 2 ]}
139 - svn_upstream=${svn_upstream%@*}
140 - local n_stop="${#svn_remote[@]}"
141 - for ((n=1; n <= n_stop; n++)); do
142 - svn_upstream=${svn_upstream#${svn_remote[$n]}}
143 - done
144 -
145 - if [[ -z "$svn_upstream" ]]; then
146 - # default branch name for checkouts with no layout:
147 - upstream=${GIT_SVN_ID:-git-svn}
148 - else
149 - upstream=${svn_upstream#/}
150 - fi
151 - elif [[ "svn+git" = "$upstream" ]]; then
152 - upstream="@{upstream}"
153 - fi
154 - ;;
155 - esac
156 -
157 - # Find how many commits we are ahead/behind our upstream
158 - if [[ -z "$legacy" ]]; then
159 - count="$(git rev-list --count --left-right \
160 - "$upstream"...HEAD 2>/dev/null)"
161 - else
162 - # produce equivalent output to --count for older versions of git
163 - local commits
164 - if commits="$(git rev-list --left-right "$upstream"...HEAD 2>/dev/null)"
165 - then
166 - local commit behind=0 ahead=0
167 - for commit in $commits
168 - do
169 - case "$commit" in
170 - "<"*) ((behind++)) ;;
171 - *) ((ahead++)) ;;
172 - esac
173 - done
174 - count="$behind $ahead"
175 - else
176 - count=""
177 - fi
178 - fi
179 -
180 - # calculate the result
181 - if [[ -z "$verbose" ]]; then
182 - case "$count" in
183 - "") # no upstream
184 - p="" ;;
185 - "0 0") # equal to upstream
186 - p="=" ;;
187 - "0 "*) # ahead of upstream
188 - p=">" ;;
189 - *" 0") # behind upstream
190 - p="<" ;;
191 - *) # diverged from upstream
192 - p="<>" ;;
193 - esac
194 - else
195 - case "$count" in
196 - "") # no upstream
197 - p="" ;;
198 - "0 0") # equal to upstream
199 - p=" u=" ;;
200 - "0 "*) # ahead of upstream
201 - p=" u+${count#0 }" ;;
202 - *" 0") # behind upstream
203 - p=" u-${count% 0}" ;;
204 - *) # diverged from upstream
205 - p=" u+${count#* }-${count% *}" ;;
206 - esac
207 - fi
208 -
209 -}
210 -
211 -
212 -# __git_ps1 accepts 0 or 1 arguments (i.e., format string)
213 -# returns text to add to bash PS1 prompt (includes branch name)
214 -__git_ps1 ()
215 -{
216 - local g="$(__gitdir)"
217 - if [ -n "$g" ]; then
218 - local r=""
219 - local b=""
220 - if [ -f "$g/rebase-merge/interactive" ]; then
221 - r="|REBASE-i"
222 - b="$(cat "$g/rebase-merge/head-name")"
223 - elif [ -d "$g/rebase-merge" ]; then
224 - r="|REBASE-m"
225 - b="$(cat "$g/rebase-merge/head-name")"
226 - else
227 - if [ -d "$g/rebase-apply" ]; then
228 - if [ -f "$g/rebase-apply/rebasing" ]; then
229 - r="|REBASE"
230 - elif [ -f "$g/rebase-apply/applying" ]; then
231 - r="|AM"
232 - else
233 - r="|AM/REBASE"
234 - fi
235 - elif [ -f "$g/MERGE_HEAD" ]; then
236 - r="|MERGING"
237 - elif [ -f "$g/CHERRY_PICK_HEAD" ]; then
238 - r="|CHERRY-PICKING"
239 - elif [ -f "$g/BISECT_LOG" ]; then
240 - r="|BISECTING"
241 - fi
242 -
243 - b="$(git symbolic-ref HEAD 2>/dev/null)" || {
244 -
245 - b="$(
246 - case "${GIT_PS1_DESCRIBE_STYLE-}" in
247 - (contains)
248 - git describe --contains HEAD ;;
249 - (branch)
250 - git describe --contains --all HEAD ;;
251 - (describe)
252 - git describe HEAD ;;
253 - (* | default)
254 - git describe --tags --exact-match HEAD ;;
255 - esac 2>/dev/null)" ||
256 -
257 - b="$(cut -c1-7 "$g/HEAD" 2>/dev/null)..." ||
258 - b="unknown"
259 - b="($b)"
260 - }
261 - fi
262 -
263 - local w=""
264 - local i=""
265 - local s=""
266 - local u=""
267 - local c=""
268 - local p=""
269 -
270 - if [ "true" = "$(git rev-parse --is-inside-git-dir 2>/dev/null)" ]; then
271 - if [ "true" = "$(git rev-parse --is-bare-repository 2>/dev/null)" ]; then
272 - c="BARE:"
273 - else
274 - b="GIT_DIR!"
275 - fi
276 - elif [ "true" = "$(git rev-parse --is-inside-work-tree 2>/dev/null)" ]; then
277 - if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ]; then
278 - if [ "$(git config --bool bash.showDirtyState)" != "false" ]; then
279 - git diff --no-ext-diff --quiet --exit-code || w="*"
280 - if git rev-parse --quiet --verify HEAD >/dev/null; then
281 - git diff-index --cached --quiet HEAD -- || i="+"
282 - else
283 - i="#"
284 - fi
285 - fi
286 - fi
287 - if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ]; then
288 - git rev-parse --verify refs/stash >/dev/null 2>&1 && s="$"
289 - fi
290 -
291 - if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ]; then
292 - if [ -n "$(git ls-files --others --exclude-standard)" ]; then
293 - u="%"
294 - fi
295 - fi
296 -
297 - if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then
298 - __git_ps1_show_upstream
299 - fi
300 - fi
301 -
302 - local f="$w$i$s$u"
303 - printf -- "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r$p"
304 - fi
305 -}
306 -
54 __gitcomp_1 ()
55 {
56 local c IFS=$' \t\n'
contrib/completion/git-prompt.sh new
+287
@@ -0,0 +1,287 @@
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 + if [ -z "${1-}" ]; then
57 + if [ -n "${__git_dir-}" ]; then
58 + echo "$__git_dir"
59 + elif [ -n "${GIT_DIR-}" ]; then
60 + test -d "${GIT_DIR-}" || return 1
61 + echo "$GIT_DIR"
62 + elif [ -d .git ]; then
63 + echo .git
64 + else
65 + git rev-parse --git-dir 2>/dev/null
66 + fi
67 + elif [ -d "$1/.git" ]; then
68 + echo "$1/.git"
69 + else
70 + echo "$1"
71 + fi
72 +}
73 +
74 +# stores the divergence from upstream in $p
75 +# used by GIT_PS1_SHOWUPSTREAM
76 +__git_ps1_show_upstream ()
77 +{
78 + local key value
79 + local svn_remote svn_url_pattern count n
80 + local upstream=git legacy="" verbose=""
81 +
82 + svn_remote=()
83 + # get some config options from git-config
84 + local output="$(git config -z --get-regexp '^(svn-remote\..*\.url|bash\.showupstream)$' 2>/dev/null | tr '\0\n' '\n ')"
85 + while read -r key value; do
86 + case "$key" in
87 + bash.showupstream)
88 + GIT_PS1_SHOWUPSTREAM="$value"
89 + if [[ -z "${GIT_PS1_SHOWUPSTREAM}" ]]; then
90 + p=""
91 + return
92 + fi
93 + ;;
94 + svn-remote.*.url)
95 + svn_remote[ $((${#svn_remote[@]} + 1)) ]="$value"
96 + svn_url_pattern+="\\|$value"
97 + upstream=svn+git # default upstream is SVN if available, else git
98 + ;;
99 + esac
100 + done <<< "$output"
101 +
102 + # parse configuration values
103 + for option in ${GIT_PS1_SHOWUPSTREAM}; do
104 + case "$option" in
105 + git|svn) upstream="$option" ;;
106 + verbose) verbose=1 ;;
107 + legacy) legacy=1 ;;
108 + esac
109 + done
110 +
111 + # Find our upstream
112 + case "$upstream" in
113 + git) upstream="@{upstream}" ;;
114 + svn*)
115 + # get the upstream from the "git-svn-id: ..." in a commit message
116 + # (git-svn uses essentially the same procedure internally)
117 + local svn_upstream=($(git log --first-parent -1 \
118 + --grep="^git-svn-id: \(${svn_url_pattern#??}\)" 2>/dev/null))
119 + if [[ 0 -ne ${#svn_upstream[@]} ]]; then
120 + svn_upstream=${svn_upstream[ ${#svn_upstream[@]} - 2 ]}
121 + svn_upstream=${svn_upstream%@*}
122 + local n_stop="${#svn_remote[@]}"
123 + for ((n=1; n <= n_stop; n++)); do
124 + svn_upstream=${svn_upstream#${svn_remote[$n]}}
125 + done
126 +
127 + if [[ -z "$svn_upstream" ]]; then
128 + # default branch name for checkouts with no layout:
129 + upstream=${GIT_SVN_ID:-git-svn}
130 + else
131 + upstream=${svn_upstream#/}
132 + fi
133 + elif [[ "svn+git" = "$upstream" ]]; then
134 + upstream="@{upstream}"
135 + fi
136 + ;;
137 + esac
138 +
139 + # Find how many commits we are ahead/behind our upstream
140 + if [[ -z "$legacy" ]]; then
141 + count="$(git rev-list --count --left-right \
142 + "$upstream"...HEAD 2>/dev/null)"
143 + else
144 + # produce equivalent output to --count for older versions of git
145 + local commits
146 + if commits="$(git rev-list --left-right "$upstream"...HEAD 2>/dev/null)"
147 + then
148 + local commit behind=0 ahead=0
149 + for commit in $commits
150 + do
151 + case "$commit" in
152 + "<"*) ((behind++)) ;;
153 + *) ((ahead++)) ;;
154 + esac
155 + done
156 + count="$behind $ahead"
157 + else
158 + count=""
159 + fi
160 + fi
161 +
162 + # calculate the result
163 + if [[ -z "$verbose" ]]; then
164 + case "$count" in
165 + "") # no upstream
166 + p="" ;;
167 + "0 0") # equal to upstream
168 + p="=" ;;
169 + "0 "*) # ahead of upstream
170 + p=">" ;;
171 + *" 0") # behind upstream
172 + p="<" ;;
173 + *) # diverged from upstream
174 + p="<>" ;;
175 + esac
176 + else
177 + case "$count" in
178 + "") # no upstream
179 + p="" ;;
180 + "0 0") # equal to upstream
181 + p=" u=" ;;
182 + "0 "*) # ahead of upstream
183 + p=" u+${count#0 }" ;;
184 + *" 0") # behind upstream
185 + p=" u-${count% 0}" ;;
186 + *) # diverged from upstream
187 + p=" u+${count#* }-${count% *}" ;;
188 + esac
189 + fi
190 +
191 +}
192 +
193 +
194 +# __git_ps1 accepts 0 or 1 arguments (i.e., format string)
195 +# returns text to add to bash PS1 prompt (includes branch name)
196 +__git_ps1 ()
197 +{
198 + local g="$(__gitdir)"
199 + if [ -n "$g" ]; then
200 + local r=""
201 + local b=""
202 + if [ -f "$g/rebase-merge/interactive" ]; then
203 + r="|REBASE-i"
204 + b="$(cat "$g/rebase-merge/head-name")"
205 + elif [ -d "$g/rebase-merge" ]; then
206 + r="|REBASE-m"
207 + b="$(cat "$g/rebase-merge/head-name")"
208 + else
209 + if [ -d "$g/rebase-apply" ]; then
210 + if [ -f "$g/rebase-apply/rebasing" ]; then
211 + r="|REBASE"
212 + elif [ -f "$g/rebase-apply/applying" ]; then
213 + r="|AM"
214 + else
215 + r="|AM/REBASE"
216 + fi
217 + elif [ -f "$g/MERGE_HEAD" ]; then
218 + r="|MERGING"
219 + elif [ -f "$g/CHERRY_PICK_HEAD" ]; then
220 + r="|CHERRY-PICKING"
221 + elif [ -f "$g/BISECT_LOG" ]; then
222 + r="|BISECTING"
223 + fi
224 +
225 + b="$(git symbolic-ref HEAD 2>/dev/null)" || {
226 +
227 + b="$(
228 + case "${GIT_PS1_DESCRIBE_STYLE-}" in
229 + (contains)
230 + git describe --contains HEAD ;;
231 + (branch)
232 + git describe --contains --all HEAD ;;
233 + (describe)
234 + git describe HEAD ;;
235 + (* | default)
236 + git describe --tags --exact-match HEAD ;;
237 + esac 2>/dev/null)" ||
238 +
239 + b="$(cut -c1-7 "$g/HEAD" 2>/dev/null)..." ||
240 + b="unknown"
241 + b="($b)"
242 + }
243 + fi
244 +
245 + local w=""
246 + local i=""
247 + local s=""
248 + local u=""
249 + local c=""
250 + local p=""
251 +
252 + if [ "true" = "$(git rev-parse --is-inside-git-dir 2>/dev/null)" ]; then
253 + if [ "true" = "$(git rev-parse --is-bare-repository 2>/dev/null)" ]; then
254 + c="BARE:"
255 + else
256 + b="GIT_DIR!"
257 + fi
258 + elif [ "true" = "$(git rev-parse --is-inside-work-tree 2>/dev/null)" ]; then
259 + if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ]; then
260 + if [ "$(git config --bool bash.showDirtyState)" != "false" ]; then
261 + git diff --no-ext-diff --quiet --exit-code || w="*"
262 + if git rev-parse --quiet --verify HEAD >/dev/null; then
263 + git diff-index --cached --quiet HEAD -- || i="+"
264 + else
265 + i="#"
266 + fi
267 + fi
268 + fi
269 + if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ]; then
270 + git rev-parse --verify refs/stash >/dev/null 2>&1 && s="$"
271 + fi
272 +
273 + if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ]; then
274 + if [ -n "$(git ls-files --others --exclude-standard)" ]; then
275 + u="%"
276 + fi
277 + fi
278 +
279 + if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then
280 + __git_ps1_show_upstream
281 + fi
282 + fi
283 +
284 + local f="$w$i$s$u"
285 + printf -- "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r$p"
286 + fi
287 +}
t/t9903-bash-prompt.sh
+1 -1
@@ -7,7 +7,7 @@ test_description='test git-specific bash prompt functions'
7
8 . ./lib-bash.sh
9
10 -. "$GIT_BUILD_DIR/contrib/completion/git-completion.bash"
10 +. "$GIT_BUILD_DIR/contrib/completion/git-prompt.sh"
11
12 actual="$TRASH_DIRECTORY/actual"
13