bash completion: Support "divergence from upstream" messages in __git_ps1
Add a notification in the command prompt specifying whether (and optionally how far) your branch has diverged from its upstream. This is especially helpful in small teams that very frequently (forget to) push to each other. Support git-svn upstream detection as a special case, as migrators from centralised version control systems are especially likely to forget to push. Support for other types of upstream than SVN should be easy to add if anyone is so inclined. Signed-off-by: Andrew Sayers <andrew-git@pileofstuff.org> Acked-by: Shawn O. Pearce <spearce@spearce.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Andrew Sayers committed
Jun 17, 2010 at 22:32 UTC
6d158cba282f22fa1548af1188f78042fed30aed
1 file changed
+143
-1
contrib/completion/git-completion.bash
+143
-1
@@ -42,6 +42,24 @@
42
# set GIT_PS1_SHOWUNTRACKEDFILES to a nonempty value. If there're
43
# untracked files, then a '%' will be shown next to the branch name.
44
#
45
+# If you would like to see the difference between HEAD and its
46
+# upstream, set GIT_PS1_SHOWUPSTREAM="auto". A "<" indicates
47
+# you are behind, ">" indicates you are ahead, and "<>"
48
+# indicates you have diverged. You can further control
49
+# behaviour by setting GIT_PS1_SHOWUPSTREAM to a space-separated
50
+# list of values:
51
+# verbose show number of commits ahead/behind (+/-) upstream
52
+# legacy don't use the '--count' option available in recent
53
+# versions of git-rev-list
54
+# git always compare HEAD to @{upstream}
55
+# svn always compare HEAD to your SVN upstream
56
+# By default, __git_ps1 will compare HEAD to your SVN upstream
57
+# if it can find one, or @{upstream} otherwise. Once you have
58
+# set GIT_PS1_SHOWUPSTREAM, you can override it on a
59
+# per-repository basis by setting the bash.showUpstream config
60
+# variable.
61
+#
62
+#
63
# To submit patches:
64
#
65
# *) Read Documentation/SubmittingPatches
@@ -78,6 +96,125 @@ __gitdir ()
96
fi
97
}
98
99
+# stores the divergence from upstream in $p
100
+# used by GIT_PS1_SHOWUPSTREAM
101
+__git_ps1_show_upstream ()
102
+{
103
+ local key value
104
+ local svn_remote=() svn_url_pattern count n
105
+ local upstream=git legacy="" verbose=""
106
+
107
+ # get some config options from git-config
108
+ while read key value; do
109
+ case "$key" in
110
+ bash.showupstream)
111
+ GIT_PS1_SHOWUPSTREAM="$value"
112
+ if [[ -z "${GIT_PS1_SHOWUPSTREAM}" ]]; then
113
+ p=""
114
+ return
115
+ fi
116
+ ;;
117
+ svn-remote.*.url)
118
+ svn_remote[ $((${#svn_remote[@]} + 1)) ]="$value"
119
+ svn_url_pattern+="\\|$value"
120
+ upstream=svn+git # default upstream is SVN if available, else git
121
+ ;;
122
+ esac
123
+ done < <(git config -z --get-regexp '^(svn-remote\..*\.url|bash\.showupstream)$' 2>/dev/null | tr '\0\n' '\n ')
124
+
125
+ # parse configuration values
126
+ for option in ${GIT_PS1_SHOWUPSTREAM}; do
127
+ case "$option" in
128
+ git|svn) upstream="$option" ;;
129
+ verbose) verbose=1 ;;
130
+ legacy) legacy=1 ;;
131
+ esac
132
+ done
133
+
134
+ # Find our upstream
135
+ case "$upstream" in
136
+ git) upstream="@{upstream}" ;;
137
+ svn*)
138
+ # get the upstream from the "git-svn-id: ..." in a commit message
139
+ # (git-svn uses essentially the same procedure internally)
140
+ local svn_upstream=($(git log --first-parent -1 \
141
+ --grep="^git-svn-id: \(${svn_url_pattern:2}\)" 2>/dev/null))
142
+ if [[ 0 -ne ${#svn_upstream[@]} ]]; then
143
+ svn_upstream=${svn_upstream[ ${#svn_upstream[@]} - 2 ]}
144
+ svn_upstream=${svn_upstream%@*}
145
+ for ((n=1; "$n" <= "${#svn_remote[@]}"; ++n)); do
146
+ svn_upstream=${svn_upstream#${svn_remote[$n]}}
147
+ done
148
+
149
+ if [[ -z "$svn_upstream" ]]; then
150
+ # default branch name for checkouts with no layout:
151
+ upstream=${GIT_SVN_ID:-git-svn}
152
+ else
153
+ upstream=${svn_upstream#/}
154
+ fi
155
+ elif [[ "svn+git" = "$upstream" ]]; then
156
+ upstream="@{upstream}"
157
+ fi
158
+ ;;
159
+ esac
160
+
161
+ # Find how many commits we are ahead/behind our upstream
162
+ if [[ -z "$legacy" ]]; then
163
+ count="$(git rev-list --count --left-right \
164
+ "$upstream"...HEAD 2>/dev/null)"
165
+ else
166
+ # produce equivalent output to --count for older versions of git
167
+ local commits
168
+ if commits="$(git rev-list --left-right "$upstream"...HEAD 2>/dev/null)"
169
+ then
170
+ local commit behind=0 ahead=0
171
+ for commit in $commits
172
+ do
173
+ case "$commit" in
174
+ "<"*) let ++behind
175
+ ;;
176
+ *) let ++ahead
177
+ ;;
178
+ esac
179
+ done
180
+ count="$behind $ahead"
181
+ else
182
+ count=""
183
+ fi
184
+ fi
185
+
186
+ # calculate the result
187
+ if [[ -z "$verbose" ]]; then
188
+ case "$count" in
189
+ "") # no upstream
190
+ p="" ;;
191
+ "0 0") # equal to upstream
192
+ p="=" ;;
193
+ "0 "*) # ahead of upstream
194
+ p=">" ;;
195
+ *" 0") # behind upstream
196
+ p="<" ;;
197
+ *) # diverged from upstream
198
+ p="<>" ;;
199
+ esac
200
+ else
201
+ case "$count" in
202
+ "") # no upstream
203
+ p="" ;;
204
+ "0 0") # equal to upstream
205
+ p=" u=" ;;
206
+ "0 "*) # ahead of upstream
207
+ p=" u+${count#0 }" ;;
208
+ *" 0") # behind upstream
209
+ p=" u-${count% 0}" ;;
210
+ *) # diverged from upstream
211
+ p=" u+${count#* }-${count% *}" ;;
212
+ esac
213
+ fi
214
+
215
+}
216
+
217
+
218
# __git_ps1 accepts 0 or 1 arguments (i.e., format string)
219
# returns text to add to bash PS1 prompt (includes branch name)
220
__git_ps1 ()
@@ -132,6 +269,7 @@ __git_ps1 ()
269
local s
270
local u
271
local c
272
+ local p=""
273
274
if [ "true" = "$(git rev-parse --is-inside-git-dir 2>/dev/null)" ]; then
275
if [ "true" = "$(git rev-parse --is-bare-repository 2>/dev/null)" ]; then
@@ -159,10 +297,14 @@ __git_ps1 ()
297
u="%"
298
fi
299
fi
300
+
301
+ if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then
302
+ __git_ps1_show_upstream
303
+ fi
304
fi
305
306
local f="$w$i$s$u"
165
- printf "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r"
307
+ printf "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r$p"
308
fi
309
}
310