Subject: [RFC/PATCHv2] bash completion: Support "divergence from upstream" warnings in __git_ps1

From: Andrew Sayers <[email-masked]>
Date: Tue, 08 Jun 2010 22:36:49 +0100

Add a notification in the command prompt specifying whether you're ahead of
(>), behind (<), diverged from (<>) or at (=) your upstream.  This is
especially helpful in small teams that (forget to) push to each other very
frequently.

Support git-svn upstream detection as a special case, as migraters 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 <[email-masked]>
---

This patch includes Thomas Rast's feedback - thanks Thomas for the education :)

This patch makes unashamed use of shell arrays and substring expansion that
would normally not be allowed.  As Jakub Narebski mentioned, this is probably ok
in a bash-specific script.

Unlike other prompt options, I've put the divergence characters on the left of the
branch name.  I'm really not sure about this, and I'd like to hear people's
opinions.

This patch produces output like this when I have unpushed commits:

[andrew@pc myrepo >master] # my master is ahead of upstream

Intuitively, I like having a ">" when I'm ahead, although it would be more
logical to have something like this:

[andrew@pc myrepo <master] # upstream less-than master

Putting the symbol on the right makes this problem go away, but looks ridiculous
if you use a prompt like PS1='\W:$(__git_ps1 "(%s)")> '

myrepo:master>> # master greater-than upstream
myrepo:master<> # master less-than upstream
myrepo:master<>> # master and upstream have diverged

I'd rather not rely on colour prompts to clear this up - using colour as the
only way to convey important information to the user rarely ends well.

Adding a "u" to the symbol could also clear this up:

[andrew@pc myrepo u<master] # upstream less-than master

Using "u<", "u=", "u>" and "<>" would mean that the prompt always used either
two or zero characters, which would keep prompts lined up over time.  But it
would also eat horizontal space for an issue you'd stop seeing after a few
minutes.

Finally, my apologies to anyone that tried to apply my previous patch - to make
a long story short, it turns out I need this feature more than I realised :)

 contrib/completion/git-completion.bash |   58 +++++++++++++++++++++++++++++++-
 1 files changed, 57 insertions(+), 1 deletions(-)

diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 57245a8..1dc80fd 100755
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -42,6 +42,14 @@
 #       set GIT_PS1_SHOWUNTRACKEDFILES to a nonempty value. If there're
 #       untracked files, then a '%' will be shown next to the branch name.
 #
+#       If you would like to see the difference bitween HEAD and its
+#       upstream, set GIT_PS1_SHOWUPSTREAM to a nonempty value.
+#       Unpushed commits (>), unmerged commits (<), both (<>) and
+#       neither (=) will be shown on the left of the branch name.  You
+#       can enable git-svn mode by setting GIT_PS1_SHOWUPSTREAM=svn
+#       and set the value per-repository with the bash.showUpstream
+#       variable.
+#
 # To submit patches:
 #
 #    *) Read Documentation/SubmittingPatches
@@ -132,6 +140,7 @@ __git_ps1 ()
 		local s
 		local u
 		local c
+		local p
 
 		if [ "true" = "$(git rev-parse --is-inside-git-dir 2>/dev/null)" ]; then
 			if [ "true" = "$(git rev-parse --is-bare-repository 2>/dev/null)" ]; then
@@ -159,10 +168,57 @@ __git_ps1 ()
 			      u="%"
 			   fi
 			fi
+
+			if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then
+
+				# Note: 'p' is used as a temporary throughout this block,
+				# before finally being assigned its correct value
+
+				if p="$(git config --get bash.showUpstream)"
+				then
+					GIT_PS1_SHOWUPSTREAM="$p"
+				fi
+
+				local upstream
+
+				if [ "${GIT_PS1_SHOWUPSTREAM-}" = "svn" ]; then
+
+					# git-svn upstream checking
+					p="$( git config --get svn-remote.svn.url )"
+					upstream=( $( git log --first-parent -1 \
+						--grep="^git-svn-id: $p" ) )
+					upstream=${upstream[ ${#upstream[@]} - 2 ]}
+					upstream=${upstream%@*}
+					upstream=${upstream#*$p/}
+
+				else # git upstream checking
+				  upstream="@{upstream}"
+				fi
+
+				if p="$( git rev-list \
+						--left-right "$upstream"...HEAD 2>/dev/null )"
+				then
+					case "$p" in
+						\<*\>*|\>*\<* ) p="<>" ;;
+						*\<*          ) p="<"  ;;
+						*\>*          ) p=">"  ;;
+						""            ) p="="  ;;
+
+						# the following case shouldn't be possible
+						# if you see this, please report it as a bug
+						* ) p="?ERROR($p)?" ;;
+
+					esac
+				else
+					p=""
+				fi
+
+			fi
+
 		fi
 
 		local f="$w$i$s$u"
-		printf "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r"
+		printf "${1:- (%s)}" "$c$p${b##refs/heads/}${f:+ $f}$r"
 	fi
 }
 
-- 
1.7.0.4