|
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 # Note: this function is duplicated in git-completion.bash |
|
57 # When updating it, make sure you update the other one to match. |
|
58 if [ -z "${1-}" ]; then |
|
59 if [ -n "${__git_dir-}" ]; then |
|
60 echo "$__git_dir" |
|
61 elif [ -n "${GIT_DIR-}" ]; then |
|
62 test -d "${GIT_DIR-}" || return 1 |
|
63 echo "$GIT_DIR" |
|
64 elif [ -d .git ]; then |
|
65 echo .git |
|
66 else |
|
67 git rev-parse --git-dir 2>/dev/null |
|
68 fi |
|
69 elif [ -d "$1/.git" ]; then |
|
70 echo "$1/.git" |
|
71 else |
|
72 echo "$1" |
|
73 fi |
|
74 } |
|
75 |
|
76 # stores the divergence from upstream in $p |
|
77 # used by GIT_PS1_SHOWUPSTREAM |
|
78 __git_ps1_show_upstream () |
|
79 { |
|
80 local key value |
|
81 local svn_remote svn_url_pattern count n |
|
82 local upstream=git legacy="" verbose="" |
|
83 |
|
84 svn_remote=() |
|
85 # get some config options from git-config |
|
86 local output="$(git config -z --get-regexp '^(svn-remote\..*\.url|bash\.showupstream)$' 2>/dev/null | tr '\0\n' '\n ')" |
|
87 while read -r key value; do |
|
88 case "$key" in |
|
89 bash.showupstream) |
|
90 GIT_PS1_SHOWUPSTREAM="$value" |
|
91 if [[ -z "${GIT_PS1_SHOWUPSTREAM}" ]]; then |
|
92 p="" |
|
93 return |
|
94 fi |
|
95 ;; |
|
96 svn-remote.*.url) |
|
97 svn_remote[ $((${#svn_remote[@]} + 1)) ]="$value" |
|
98 svn_url_pattern+="\\|$value" |
|
99 upstream=svn+git # default upstream is SVN if available, else git |
|
100 ;; |
|
101 esac |
|
102 done <<< "$output" |
|
103 |
|
104 # parse configuration values |
|
105 for option in ${GIT_PS1_SHOWUPSTREAM}; do |
|
106 case "$option" in |
|
107 git|svn) upstream="$option" ;; |
|
108 verbose) verbose=1 ;; |
|
109 legacy) legacy=1 ;; |
|
110 esac |
|
111 done |
|
112 |
|
113 # Find our upstream |
|
114 case "$upstream" in |
|
115 git) upstream="@{upstream}" ;; |
|
116 svn*) |
|
117 # get the upstream from the "git-svn-id: ..." in a commit message |
|
118 # (git-svn uses essentially the same procedure internally) |
|
119 local svn_upstream=($(git log --first-parent -1 \ |
|
120 --grep="^git-svn-id: \(${svn_url_pattern#??}\)" 2>/dev/null)) |
|
121 if [[ 0 -ne ${#svn_upstream[@]} ]]; then |
|
122 svn_upstream=${svn_upstream[ ${#svn_upstream[@]} - 2 ]} |
|
123 svn_upstream=${svn_upstream%@*} |
|
124 local n_stop="${#svn_remote[@]}" |
|
125 for ((n=1; n <= n_stop; n++)); do |
|
126 svn_upstream=${svn_upstream#${svn_remote[$n]}} |
|
127 done |
|
128 |
|
129 if [[ -z "$svn_upstream" ]]; then |
|
130 # default branch name for checkouts with no layout: |
|
131 upstream=${GIT_SVN_ID:-git-svn} |
|
132 else |
|
133 upstream=${svn_upstream#/} |
|
134 fi |
|
135 elif [[ "svn+git" = "$upstream" ]]; then |
|
136 upstream="@{upstream}" |
|
137 fi |
|
138 ;; |
|
139 esac |
|
140 |
|
141 # Find how many commits we are ahead/behind our upstream |
|
142 if [[ -z "$legacy" ]]; then |
|
143 count="$(git rev-list --count --left-right \ |
|
144 "$upstream"...HEAD 2>/dev/null)" |
|
145 else |
|
146 # produce equivalent output to --count for older versions of git |
|
147 local commits |
|
148 if commits="$(git rev-list --left-right "$upstream"...HEAD 2>/dev/null)" |
|
149 then |
|
150 local commit behind=0 ahead=0 |
|
151 for commit in $commits |
|
152 do |
|
153 case "$commit" in |
|
154 "<"*) ((behind++)) ;; |
|
155 *) ((ahead++)) ;; |
|
156 esac |
|
157 done |
|
158 count="$behind $ahead" |
|
159 else |
|
160 count="" |
|
161 fi |
|
162 fi |
|
163 |
|
164 # calculate the result |
|
165 if [[ -z "$verbose" ]]; then |
|
166 case "$count" in |
|
167 "") # no upstream |
|
168 p="" ;; |
|
169 "0 0") # equal to upstream |
|
170 p="=" ;; |
|
171 "0 "*) # ahead of upstream |
|
172 p=">" ;; |
|
173 *" 0") # behind upstream |
|
174 p="<" ;; |
|
175 *) # diverged from upstream |
|
176 p="<>" ;; |
|
177 esac |
|
178 else |
|
179 case "$count" in |
|
180 "") # no upstream |
|
181 p="" ;; |
|
182 "0 0") # equal to upstream |
|
183 p=" u=" ;; |
|
184 "0 "*) # ahead of upstream |
|
185 p=" u+${count#0 }" ;; |
|
186 *" 0") # behind upstream |
|
187 p=" u-${count% 0}" ;; |
|
188 *) # diverged from upstream |
|
189 p=" u+${count#* }-${count% *}" ;; |
|
190 esac |
|
191 fi |
|
192 |
|
193 } |
|
194 |
|
195 |
|
196 # __git_ps1 accepts 0 or 1 arguments (i.e., format string) |
|
197 # returns text to add to bash PS1 prompt (includes branch name) |
|
198 __git_ps1 () |
|
199 { |
|
200 local g="$(__gitdir)" |
|
201 if [ -n "$g" ]; then |
|
202 local r="" |
|
203 local b="" |
|
204 if [ -f "$g/rebase-merge/interactive" ]; then |
|
205 r="|REBASE-i" |
|
206 b="$(cat "$g/rebase-merge/head-name")" |
|
207 elif [ -d "$g/rebase-merge" ]; then |
|
208 r="|REBASE-m" |
|
209 b="$(cat "$g/rebase-merge/head-name")" |
|
210 else |
|
211 if [ -d "$g/rebase-apply" ]; then |
|
212 if [ -f "$g/rebase-apply/rebasing" ]; then |
|
213 r="|REBASE" |
|
214 elif [ -f "$g/rebase-apply/applying" ]; then |
|
215 r="|AM" |
|
216 else |
|
217 r="|AM/REBASE" |
|
218 fi |
|
219 elif [ -f "$g/MERGE_HEAD" ]; then |
|
220 r="|MERGING" |
|
221 elif [ -f "$g/CHERRY_PICK_HEAD" ]; then |
|
222 r="|CHERRY-PICKING" |
|
223 elif [ -f "$g/BISECT_LOG" ]; then |
|
224 r="|BISECTING" |
|
225 fi |
|
226 |
|
227 b="$(git symbolic-ref HEAD 2>/dev/null)" || { |
|
228 |
|
229 b="$( |
|
230 case "${GIT_PS1_DESCRIBE_STYLE-}" in |
|
231 (contains) |
|
232 git describe --contains HEAD ;; |
|
233 (branch) |
|
234 git describe --contains --all HEAD ;; |
|
235 (describe) |
|
236 git describe HEAD ;; |
|
237 (* | default) |
|
238 git describe --tags --exact-match HEAD ;; |
|
239 esac 2>/dev/null)" || |
|
240 |
|
241 b="$(cut -c1-7 "$g/HEAD" 2>/dev/null)..." || |
|
242 b="unknown" |
|
243 b="($b)" |
|
244 } |
|
245 fi |
|
246 |
|
247 local w="" |
|
248 local i="" |
|
249 local s="" |
|
250 local u="" |
|
251 local c="" |
|
252 local p="" |
|
253 |
|
254 if [ "true" = "$(git rev-parse --is-inside-git-dir 2>/dev/null)" ]; then |
|
255 if [ "true" = "$(git rev-parse --is-bare-repository 2>/dev/null)" ]; then |
|
256 c="BARE:" |
|
257 else |
|
258 b="GIT_DIR!" |
|
259 fi |
|
260 elif [ "true" = "$(git rev-parse --is-inside-work-tree 2>/dev/null)" ]; then |
|
261 if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ]; then |
|
262 if [ "$(git config --bool bash.showDirtyState)" != "false" ]; then |
|
263 git diff --no-ext-diff --quiet --exit-code || w="*" |
|
264 if git rev-parse --quiet --verify HEAD >/dev/null; then |
|
265 git diff-index --cached --quiet HEAD -- || i="+" |
|
266 else |
|
267 i="#" |
|
268 fi |
|
269 fi |
|
270 fi |
|
271 if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ]; then |
|
272 git rev-parse --verify refs/stash >/dev/null 2>&1 && s="$" |
|
273 fi |
|
274 |
|
275 if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ]; then |
|
276 if [ -n "$(git ls-files --others --exclude-standard)" ]; then |
|
277 u="%" |
|
278 fi |
|
279 fi |
|
280 |
|
281 if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then |
|
282 __git_ps1_show_upstream |
|
283 fi |
|
284 fi |
|
285 |
|
286 local f="$w$i$s$u" |
|
287 printf -- "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r$p" |
|
288 fi |
|
289 } |