Skip to contentSkip to conversation

bash completion: Support "unpushed commits" warnings in __git_ps1

PATCH In review Andrew Sayers 11 participants 42 messages Jun 15, 2016 Documentation

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 bash completion: Support "unpushed commits" warnings in __git_ps1

People working in small teams sometimes forget to push their changes, causing general confusion. A gentle reminder in the command prompt should help.

Users migrating from centralised version control systems are especially likely to forget, so I've included git-svn support as a special case. Only SVN is supported because it's the only centralised version control system I have any experience with. The code is designed to make adding other version control systems easy for anyone that's interested.

---
 contrib/completion/git-completion.bash |   33 ++++++++++++++++++++++++++++++-
 1 files changed, 31 insertions(+), 2 deletions(-)
contrib/completion/git-completion.bash +31 -2
index d3fec32..4bb0fee 100755
@@ -48,6 +48,12 @@
 #       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 if there're unpushed commits, then
+#       you can set GIT_PS1_SHOWUNPUSHED to a nonempty value. If
+#       there're unpushed commits, then a '!' will be shown next to
+#       the branch name.  Setting GIT_PS1_SHOWUNPUSHED=svn will look
+#       for unpushed git-svn commits.
+#
 # To submit patches:
 #
 #    *) Read Documentation/SubmittingPatches
@@ -138,6 +144,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
@@ -167,12 +174,34 @@ __git_ps1 ()
 			      u="%"
 			   fi
 			fi
+
+			if [ -n "${GIT_PS1_SHOWUNPUSHED-}" ]; then
+			   local head
+			   local upstream
+			   if [ "${GIT_PS1_SHOWUNPUSHED-}" = "svn" ]; then # git-svn upstream checking
+			      local remote_branch=$( git config --get svn-remote.svn.url | sed 's/\//\\\//g' )
+			      upstream=$( git log | sed -ne "/^    git-svn-id: / { s/^    git-svn-id: $remote_branch\/\([^@]*\).*/\1/p ; q }" )
+			   else # git upstream checking
+			      upstream="@{upstream}"
+			   fi
+
+			   if git rev-parse --quiet "$upstream" HEAD 2>/dev/null | {
+			      read upstream
+			      read head
+			      [ -n "$head" -a -n "$upstream" -a "$head" != "$upstream" ]
+			   }; then
+			       p='!'
+			   else
+			       p=
+			   fi
+			fi
+
 		fi
 
 		if [ -n "${1-}" ]; then
-			printf "$1" "$c${b##refs/heads/}$w$i$s$u$r"
+			printf "$1" "$c${b##refs/heads/}$w$i$s$u$r$p"
 		else
-			printf " (%s)" "$c${b##refs/heads/}$w$i$s$u$r"
+			printf " (%s)" "$c${b##refs/heads/}$w$i$s$u$r$p"
 		fi
 	fi
 }
#1 lore raw
Quoted 2 lines · Andrew Sayers

People working in small teams sometimes forget to push their changes, causing general confusion. A gentle reminder in the command prompt should help.

I think this is a good idea but...

contrib/completion/git-completion.bash line 55

Quoted 5 lines
contrib/completion/git-completion.bash +5 -0
+#       If you would like to see if there're unpushed commits, then
+#       you can set GIT_PS1_SHOWUNPUSHED to a nonempty value. If
+#       there're unpushed commits, then a '!' will be shown next to
+#       the branch name.  Setting GIT_PS1_SHOWUNPUSHED=svn will look
+#       for unpushed git-svn commits.

At least the svn/git setting should definitely be configurable through git-config, so that it becomes per-repo configurable. Also, you could try to trigger the svn mode by default if a svn-remote.svn.url exists.

contrib/completion/git-completion.bash __git_ps1 () · line 183

Quoted 1 line
contrib/completion/git-completion.bash +1 -0
+			      upstream=$( git log | sed -ne "/^    git-svn-id: / { s/^    git-svn-id: $remote_branch\/\([^@]*\).*/\1/p ; q }" )

How about starting with

  git log -1 --first-parent --grep="^git-svn-id: $remote_branch"

which should be faster and is also closer to the truth. It still doesn't work if the repo was cloned with --no-metadata. Doing it right probably would require a new informational command in git-svn that reports the branch name.

Signature · 2 lines
Thomas Rast
trast@{inf,student}.ethz.ch
#2 lore raw
Quoted 5 lines · Thomas Rast

At least the svn/git setting should definitely be configurable through git-config, so that it becomes per-repo configurable. Also, you could try to trigger the svn mode by default if a svn-remote.svn.url exists.

I'm not averse to adding a config setting, or to exploring the "--no-metadata" case. I am concerned about the strict time constraints for code called in a command prompt though. For instance, calling git-svn could easily make your prompt take over half a second to respond because of all the Perl you'd have to compile. I'd also want a few real-world use cases and a nod from Shawn before diverging any further from the existing GIT_PS1_FOO= convention.

Having said all that, I'm quite happy to resubmit with GIT_PS1_SHOWUNPUSHED=svn treated like GIT_PS1_SHOWUNPUSHED=1 when $GIT_DIR/svn doesn't exist.

Quoted 8 lines

How about starting with

  git log -1 --first-parent --grep="^git-svn-id: $remote_branch"

which should be faster and is also closer to the truth. It still doesn't work if the repo was cloned with --no-metadata. Doing it right probably would require a new informational command in git-svn that reports the branch name.

Good idea about "-1" and "--grep", but multi-parent commits aren't compatible with SVN, so I don't see how "--first-parent" is useful here?
In fact I'm tempted to look for merge commits and print a big warning
when one is found.

I don't see how to actually split out the branch name without calling sed or using one of the disallowed ${} constructs. So I compared the following commands on my PC (2.1 GHz dual-core Athlon) using a repository with 100 empty commits between HEAD and a git-svn-id:

time git log | \
	sed -ne "/^    git-svn-id: / { s/^    git-svn-id: "\
	"$remote_branch\/\([^@]*\).*/\1/p ; q }"
time git log -1 --grep="^git-svn-id: $remote_branch" | \
	sed -ne "s/^    git-svn-id: $remote_branch\/\([^@]*\).*/\1/p"

The former averaged about 0.017 seconds, whereas the latter averaged about 0.015. So when I resubmit this patch, I'll include your version if merge commits are somehow useful to SVNers, or a merge warning otherwise.

Incidentally, a quick bit of etiquette - would you rather I submitted an RFC patch or two with these changes, or wait until the discussion has reached a more stable point?

- Andrew

#3 lore raw
Quoted 6 lines · Andrew Sayers

I'm not averse to adding a config setting, or to exploring the "--no-metadata" case. I am concerned about the strict time constraints for code called in a command prompt though. For instance, calling git-svn could easily make your prompt take over half a second to respond because of all the Perl you'd have to compile.

True, I forgot about that.

Quoted 7 lines

I'd also want a few
real-world use cases and a nod from Shawn before diverging any further from the existing GIT_PS1_FOO= convention.

Having said all that, I'm quite happy to resubmit with GIT_PS1_SHOWUNPUSHED=svn treated like GIT_PS1_SHOWUNPUSHED=1 when $GIT_DIR/svn doesn't exist.

There's the precedent of bash.showDirtyState that allows you to disable the +* indicators _per repository_. The latter idea also sounds reasonable, though I personally would find the configuration more useful. I have several repos that push to both git (for real work) and SVN (for buildbots) where I'd prefer the indicator to refer to git.

Quoted 8 lines
Quoted 3 lines

How about starting with

  git log -1 --first-parent --grep="^git-svn-id: $remote_branch"

Good idea about "-1" and "--grep", but multi-parent commits aren't compatible with SVN, so I don't see how "--first-parent" is useful here?
In fact I'm tempted to look for merge commits and print a big warning
when one is found.

Please don't; you'd immediately lose me as a user :-)

First, --first-parent is actually how git-svn looks for the git-svn-id, and any other search is liable to turn up wrong results. See working_head_info in git-svn.perl.

Second, the behaviour of git-svn with merges is very useful and well-defined. (Yes, there are stern warnings in the manpage, but if you know what you're doing just ignore them...)

'git svn dcommit' will first determine the correct upstream branch according to the above logic, i.e., it *always* commits to the first-parent ancestry. Then it commits the diff along this line of history (i.e., always diffing to the first parent) but it does rewrite the commits so that merges are preserved.

In pictures, if you have

    o---B---o---o---o    trunk
         \
          *---*---*---*    topic

and run 'git checkout trunk; git merge topic; git svn dcommit' you get

    o---B---o---o---o---M'    trunk
         \             /
          *---*---*---*    topic

(writing M' since this is already the rewritten version of an original merge M). M' looks to SVN like a 'git merge --squash topic' because git-svn doesn't (yet?) store the merge info.

So you get to keep the git merge info, which is very useful for later merges of 'topic'.

Note: NEVER run 'git checkout topic; git merge trunk'. git-svn will think (because of the first-parent rule) that you want to commit on top of B! I don't know what happens next but I expect it won't be too pretty.

Signature · 2 lines
Thomas Rast
trast@{inf,student}.ethz.ch
#4 lore raw

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

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.

---

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(-)
contrib/completion/git-completion.bash +57 -1
index 57245a8..1dc80fd 100755
@@ -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
 }
 
Signature · 1 line
1.7.0.4
#5 lore raw

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

contrib/completion/git-completion.bash __git_ps1 () · line 192

Quoted 9 lines · Andrew Sayers
contrib/completion/git-completion.bash +9 -0
+				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/}

This appears to be tailored to --stdlayout, and sadly doesn't work in the (probably fairly common) case of single-branch checkouts where git-svn only puts a "remotes/git-svn" branch.

I can't come up with any easy-but-robust method of checking that though. Perhaps it suffices to check that

git config 'svn-remote.svn.(branches|tags)'

comes up empty, and in this case simply use git-svn as the remote.

Signature · 2 lines
Thomas Rast
trast@{inf,student}.ethz.ch
#6 lore raw

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

In reply to Andrew Sayers

[I haven't followed the previous discussion, just this RFC.]

Andrew Sayers venit, vidit, dixit 08.06.2010 23:36:

Quoted 16 lines

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.

---

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

Quoted 7 lines

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.

I'd say it would make sense to you use the same order as "branch -vv" and "status -s", i.e. after the branch name.

Quoted 7 lines

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:

That is really illogical, please don't.

Quoted 5 lines

[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)")> '

Patient: If I move my arm like this then it hurts.
Doctor: Then don't do that ;)

Quoted 8 lines

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.

Quoted 9 lines

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.

I think a simple space would help already, i.e.
myrepo:master<> >

An alternative would be to use something like
myrepo:master+5-3

The numbers should make this distinguishable from the "--cached +". Of course it eats up more space and is slightly confusing unless it is read master = upstream + 5 -3.
OTOH it is very analogous to status -s and branch -vv.

Quoted 3 lines

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 :)

;)

Quoted 53 lines
 contrib/completion/git-completion.bash |   58 +++++++++++++++++++++++++++++++-
 1 files changed, 57 insertions(+), 1 deletions(-)
contrib/completion/git-completion.bash +28 -0
index 57245a8..1dc80fd 100755
@@ -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" ) )

contrib/completion/git-completion.bash __git_ps1 () · line 199

Quoted 10 lines
contrib/completion/git-completion.bash +10 -0
+					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 )"

Here you have all the info to do a "grep \<|wc -l" etc. instead of the below if you go for the +5-3.

Quoted 16 lines
contrib/completion/git-completion.bash +16 -0
+				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
+

contrib/completion/git-completion.bash __git_ps1 () · line 224

Quoted 10 lines
contrib/completion/git-completion.bash +3 -1
+			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
 }
 

Cheers,
Michael

#7 lore raw

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

Michael J Gruber venit, vidit, dixit 09.06.2010 11:17:
...

Quoted 7 lines

An alternative would be to use something like
myrepo:master+5-3

The numbers should make this distinguishable from the "--cached +". Of course it eats up more space and is slightly confusing unless it is read master = upstream + 5 -3.
OTOH it is very analogous to status -s and branch -vv.

...

Quoted 11 lines

contrib/completion/git-completion.bash __git_ps1 () · line 199

Quoted 2 lines
contrib/completion/git-completion.bash +2 -0
+				if p="$( git rev-list \
+						--left-right "$upstream"...HEAD 2>/dev/null )"

Here you have all the info to do a "grep \<|wc -l" etc. instead of the below if you go for the +5-3.

contrib/completion/git-completion.bash __git_ps1 () · line 204

Quoted 5 lines
contrib/completion/git-completion.bash +5 -0
+				then
+					case "$p" in
+						\<*\>*|\>*\<* ) p="<>" ;;
+						*\<*          ) p="<"  ;;
+						*\>*          ) p=">"  ;;

I mean something like this

					case "$p" in
						\<*\>*|\>*\<* ) p="+"$(echo "$p"|grep ">"|wc -l)"-"$(echo
"$p"|grep "<"|wc -l)  ;;
						*\<*          ) p="-"$(echo "$p"|grep "<"|wc -l)  ;;
						*\>*          ) p="+"$(echo "$p"|grep ">"|wc -l)  ;;

with

		printf "${1:- (%s)}" "$c${b##refs/heads/}$p${f:+ $f}$r"

resulting in a prompt like

[mjg@localhost git (mjg/sane-symlink-diff+4-153 *+%)]

on my branch "mjg/sane-symlink-diff".

Cheers,
Michael

#8 lore raw

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

Michael J Gruber venit, vidit, dixit 09.06.2010 22:48:

Quoted 6 lines
					case "$p" in
						\<*\>*|\>*\<* ) p="+"$(echo "$p"|grep ">"|wc -l)"-"$(echo
"$p"|grep "<"|wc -l)  ;;
						*\<*          ) p="-"$(echo "$p"|grep "<"|wc -l)  ;;
						*\>*          ) p="+"$(echo "$p"|grep ">"|wc -l)  ;;

...make this

grep -Fc ">"

etc. rather than

grep ">"|wc -l

etc. of course. Sorry for the bash-spam :)

Michael

#9 lore raw

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

In reply to Andrew Sayers
Quoted 72 lines

-----Original Message-----
From: [email protected] [mailto:[email protected]] On Behalf Of Andrew Sayers
Sent: den 8 juni 2010 23:37
To: Thomas Rast
Cc: Shawn O. Pearce; Git Mailing List
Subject: [RFC/PATCHv2] bash completion: Support "divergence from upstream" warnings in __git_ps1

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.

---

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(-)
contrib/completion/git-completion.bash +0 -0
index 57245a8..1dc80fd 100755

contrib/completion/git-completion.bash line 45

Quoted 4 lines
contrib/completion/git-completion.bash +1 -0
@@ -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

Typo: bitween -> between

Quoted 78 lines
contrib/completion/git-completion.bash +56 -2
+#       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
 }
 
--
Quoted 1 line

1.7.0.4

//Peter

#10 lore raw

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

In reply to Andrew Sayers
Quoted 4 lines · Andrew Sayers

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.

I hate to get all feature-bloat on you...

But could it state the number of commits as well please? :) :)

John

#11 lore raw

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

Quoted 10 lines · John Tapsell
Quoted 4 lines · Andrew Sayers

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.

I hate to get all feature-bloat on you...

But could it state the number of commits as well please? :) :)

I like the idea of ahead, at, behind, and diverged

but I would want to use unicode/utf8 as my terminal supports it, would use it.

⇅ UPWARDS ARROW LEFTWARDS OF DOWNWARDS ARROW is diverged.
↑ UPWARDS ARROW master is ahead.
↓ DOWNWARDS ARROW is master is behind.

Steve

#12 lore raw

cover bash completion: Support "divergence from upstream" warnings in __git_ps1

In reply to Andrew Sayers

contrib/completion/git-completion.bash __git_ps1 () · line 211

Quoted 11 lines · Andrew Sayers
contrib/completion/git-completion.bash +11 -0
+                                       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

I didn't really like this bit, since rev-list has to format the output only so you can match it. I realized that I too have written code in the past to detect which way this goes (in post-receive hooks), and/or the number of commits as per the request

Quoted 3 lines · John Tapsell

I hate to get all feature-bloat on you...

But could it state the number of commits as well please? :) :)

So here's an additional patch to provide rev-list logic that supports this feature. I have then tweaked your original patch to make use of it a u+7-5 style.

I'm not too picky about the exact style that it turns out to have in the end; I have used my own customized version ever since we started printing a space before the (master *+) dirtiness indicator. However, I think we should try to be as unambiguous and short as possible. Hence I picked the 'u' prefix to avoid ambiguity with the dirtiness indicator.

Andrew Sayers (1):
bash completion: Support "divergence from upstream" warnings in

    __git_ps1

Thomas Rast (1):
rev-list: introduce --count option

 Documentation/rev-list-options.txt     |    9 +++++
 builtin/rev-list.c                     |   16 +++++++++
 contrib/completion/git-completion.bash |   57 +++++++++++++++++++++++++++++++-
 revision.c                             |    2 +
 revision.h                             |    5 +++
 t/t6007-rev-list-cherry-pick-file.sh   |   29 ++++++++++++++++
 6 files changed, 117 insertions(+), 1 deletions(-)
#13 lore raw

PATCH 1/2 rev-list: introduce --count option

Add a --count option that, instead of actually listing the commits, merely counts them.

This is mostly geared towards script use, and to this end it acts specially when used with --left-right: it outputs the left and right counts separately. Previously, scripts would have to run a shell loop or small inline script over to achieve the same. (Without --left-right, a simple |wc -l does the job.)

---
 Documentation/rev-list-options.txt   |    9 +++++++++
 builtin/rev-list.c                   |   16 ++++++++++++++++
 revision.c                           |    2 ++
 revision.h                           |    5 +++++
 t/t6007-rev-list-cherry-pick-file.sh |   29 +++++++++++++++++++++++++++++
 5 files changed, 61 insertions(+), 0 deletions(-)
Documentation/rev-list-options.txt +9 -0
index b9fb7a8..066ade9 100644
@@ -98,6 +98,15 @@ you would get an output like this:
 This implies the '--topo-order' option by default, but the
 '--date-order' option may also be specified.
 
+ifdef::git-rev-list[]
+--count::
+	Print a number stating how many commits would have been
+	listed, and suppress all other output.  When used together
+	with '--left-right', instead print the counts for left and
+	right commits, separated by a tab.
+endif::git-rev-list[]
+
+
 ifndef::git-rev-list[]
 Diff Formatting
 ~~~~~~~~~~~~~~~
builtin/rev-list.c +16 -0
index 51ceb19..efe9360 100644
@@ -50,6 +50,15 @@ static void show_commit(struct commit *commit, void *data)
 
 	graph_show_commit(revs->graph);
 
+	if (revs->count) {
+		if (commit->object.flags & SYMMETRIC_LEFT)
+			revs->count_left++;
+		else
+			revs->count_right++;
+		finish_commit(commit, data);
+		return;
+	}
+
 	if (info->show_timestamp)
 		printf("%lu ", commit->date);
 	if (info->header_prefix)
@@ -400,5 +409,12 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
 			     quiet ? finish_object : show_object,
 			     &info);
 
+	if (revs.count) {
+		if (revs.left_right)
+			printf("%d\t%d\n", revs.count_left, revs.count_right);
+		else
+			printf("%d\n", revs.count_left + revs.count_right);
+	}
+
 	return 0;
 }
revision.c +2 -0
index b209d49..94fca7f 100644
@@ -1146,6 +1146,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
 		revs->boundary = 1;
 	} else if (!strcmp(arg, "--left-right")) {
 		revs->left_right = 1;
+	} else if (!strcmp(arg, "--count")) {
+		revs->count = 1;
 	} else if (!strcmp(arg, "--cherry-pick")) {
 		revs->cherry_pick = 1;
 		revs->limited = 1;
revision.h +5 -0
index 568f1c9..bafa728 100644
@@ -57,6 +57,7 @@ struct rev_info {
 			limited:1,
 			unpacked:1,
 			boundary:2,
+			count:1,
 			left_right:1,
 			rewrite_parents:1,
 			print_parents:1,
@@ -131,6 +132,10 @@ struct rev_info {
 
 	/* notes-specific options: which refs to show */
 	struct display_notes_opt notes_opt;
+
+	/* commit counts */
+	int count_left;
+	int count_right;
 };
 
 #define REV_TREE_SAME		0
t/t6007-rev-list-cherry-pick-file.sh +29 -0
index 4b8611c..b565638 100755
@@ -32,6 +32,23 @@ test_expect_success setup '
 	git tag B
 '
 
+cat >expect <<EOF
+<tags/B
+>tags/C
+EOF
+
+test_expect_success '--left-right' '
+	git rev-list --left-right B...C > actual &&
+	git name-rev --stdin --name-only --refs="*tags/*" \
+		< actual > actual.named &&
+	test_cmp actual.named expect
+'
+
+test_expect_success '--count' '
+	git rev-list --count B...C > actual &&
+	test "$(cat actual)" = 2
+'
+
 test_expect_success '--cherry-pick foo comes up empty' '
 	test -z "$(git rev-list --left-right --cherry-pick B...C -- foo)"
 '
@@ -54,4 +71,16 @@ test_expect_success '--cherry-pick with independent, but identical branches' '
 		HEAD...master -- foo)"
 '
 
+cat >expect <<EOF
+1	2
+EOF
+
+# Insert an extra commit to break the symmetry
+test_expect_success '--count --left-right' '
+	git checkout branch &&
+	test_commit D &&
+	git rev-list --count --left-right B...D > actual &&
+	test_cmp expect actual
+'
+
 test_done
Signature · 1 line
1.7.1.553.ge4d5c.dirty
#14 lore raw

PATCH 2/2 bash completion: Support "divergence from upstream" warnings in __git_ps1

In reply to Thomas Rast

From: Andrew Sayers <[email protected]>

Add a notification in the command prompt specifying whether you're ahead of or behind 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.

---
 contrib/completion/git-completion.bash |   57 +++++++++++++++++++++++++++++++-
 1 files changed, 56 insertions(+), 1 deletions(-)
contrib/completion/git-completion.bash +56 -1
index de5e6c1..49253a1 100755
@@ -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.  The
+#       difference will be shown as, e.g., "u+7-5" meaning that you
+#       are 7 commits ahead of and 5 commits behind the upstream.  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,56 @@ __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
+
+				p=$(git rev-list --count --left-right "$upstream"...HEAD 2>/dev/null)
+				debug_p="$p"
+				case "$p" in
+				"0	0"|"") # empty means no --count support or no upstream
+					p=
+					;;
+				"0	"*)
+					p="+${p#0	}"
+					;;
+				*"	0")
+					p="-${p%	0}"
+					;;
+				*)
+					p="+${p#*	}-${p%	*}"
+					;;
+				esac
+
+			fi
+
 		fi
 
 		local f="$w$i$s$u"
-		printf "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r"
+		printf "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r${p:+ u$p}"
 	fi
 }
 
Signature · 1 line
1.7.1.553.ge4d5c.dirty
#15 lore raw

Re: [PATCH 2/2] bash completion: Support "divergence from upstream" warnings in __git_ps1

Hi,

Quoted 7 lines · Thomas Rast

From: Andrew Sayers <[email protected]>

contrib/completion/git-completion.bash +0 -0
index de5e6c1..49253a1 100755

contrib/completion/git-completion.bash line 51

Quoted 10 lines
contrib/completion/git-completion.bash +7 -0
@@ -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.  The
+#       difference will be shown as, e.g., "u+7-5" meaning that you
+#       are 7 commits ahead of and 5 commits behind the upstream.  You
+#       can enable git-svn mode by setting GIT_PS1_SHOWUPSTREAM=svn
+#       and set the value per-repository with the bash.showUpstream
+#       variable.

I find the last sentence of this description ambiguous. What value should bash.showUpstream be set to? Do I really need to set both GIT_PS1_SHOWUPSTREAM and bash.showUpstream? What if GIT_PS1_SHOWUPSTREAM=foo and bash.showUpstream=svn?

Furthermore, I think it would be good to provide means to disable this feature for some repositories while keeping it enabled for others. In the current version I could either disable or enable it globally. Perhaps we could disable it when bash.showUpstream is set to an empty value.

Quoted 13 lines
contrib/completion/git-completion.bash +2 -0
+#
 # 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

contrib/completion/git-completion.bash __git_ps1 () · line 176

Quoted 9 lines
contrib/completion/git-completion.bash +6 -0
@@ -159,10 +168,56 @@ __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
+

Back in the old days when I was just learning programming, I got my ass kicked when I dared to reuse the same variable for different purposes. C'mon, just how much shorter it is to create one more variable than this two lines long comment?! ;) It could even be squashed together with the "local upstream" line.

contrib/completion/git-completion.bash __git_ps1 () · line 184

Quoted 8 lines
contrib/completion/git-completion.bash +8 -0
+				if p="$(git config --get bash.showUpstream)"
+				then
+					GIT_PS1_SHOWUPSTREAM="$p"
+				fi
+
+				local upstream
+
+				if [ "${GIT_PS1_SHOWUPSTREAM-}" = "svn" ]; then

No need to use default value here, because GIT_PS1_SHOWUPSTREAM has already been set above.

contrib/completion/git-completion.bash __git_ps1 () · line 193

Quoted 9 lines
contrib/completion/git-completion.bash +9 -0
+
+					# 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/}
+

Unnecessary empty lines before and after this block of code.

contrib/completion/git-completion.bash __git_ps1 () · line 199

Quoted 6 lines
contrib/completion/git-completion.bash +6 -0
+				else # git upstream checking
+					upstream="@{upstream}"
+				fi
+
+				p=$(git rev-list --count --left-right "$upstream"...HEAD 2>/dev/null)
+				debug_p="$p"

The leftover debugging mentioned by Michael.

Quoted 8 lines
contrib/completion/git-completion.bash +8 -0
+				case "$p" in
+				"0	0"|"") # empty means no --count support or no upstream
+					p=
+					;;
+				"0	"*)
+					p="+${p#0	}"
+					;;
+				*"	0")

contrib/completion/git-completion.bash __git_ps1 () · line 217

Quoted 10 lines
contrib/completion/git-completion.bash +9 -0
+					p="-${p%	0}"
+					;;
+				*)
+					p="+${p#*	}-${p%	*}"
+					;;
+				esac
+
+			fi
+
 		fi

Unnecessary empty lines before both fi.

Quoted 11 lines
contrib/completion/git-completion.bash +1 -1
 		local f="$w$i$s$u"
-		printf "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r"
+		printf "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r${p:+ u$p}"
 	fi
 }
 
Signature · 1 line
1.7.1.553.ge4d5c.dirty
#16 lore raw

v2 cover bash completion: Support "divergence from upstream" warnings in __git_ps1

[Argh. Or maybe it's an encoding problem?]

Quoted 5 lines · SZEDER Gabor

Furthermore, I think it would be good to provide means to disable this feature for some repositories while keeping it enabled for others. In the current version I could either disable or enable it globally. Perhaps we could disable it when bash.showUpstream is set to an empty value.

Well, I wanted to leave this to Andrew but since I'm already messing around with it, here's my take on it. I might be getting a bit feature creepy, but it should be prepared for all possible uses now.

The semantics now are that (as with e.g. GIT_PS1_SHOWDIRTYSTATE) you have to set the environment variable to get anything, but after that, the config *always* overrides (so you can disable again).

Furthermore, the SVN code tries remotes/git-svn first (for single-branch clones), and there are new features to set a certain ref or provide a small snippet of hook code.

Andrew Sayers (1):
bash completion: Support "divergence from upstream" warnings in

    __git_ps1

Thomas Rast (1):
rev-list: introduce --count option

 Documentation/rev-list-options.txt     |    9 +++
 builtin/rev-list.c                     |   16 ++++++
 contrib/completion/git-completion.bash |   89 +++++++++++++++++++++++++++++++-
 revision.c                             |    2 +
 revision.h                             |    5 ++
 t/t6007-rev-list-cherry-pick-file.sh   |   29 ++++++++++
 6 files changed, 149 insertions(+), 1 deletions(-)
#17 lore raw

vger doesn't like UTF-8 from send-email

Quoted 1 line · Thomas Rast

[Argh. Or maybe it's an encoding problem?]

First, sorry everyone on the Cc list for the triple post. I first blamed it on the fact that I was Cc'ing Gabor, but apparently the problem was in the content.

The files I handed to git-send-email were UTF-8, and I used my usual git alias to --cc Gabor on the first pass which also results in an UTF-8 encoded name.

I got this back from our university mail server:

[email protected]
vger.kernel.org #550 5.7.1 Content-Policy reject msg: Wrong MIME labeling on 8-bit character texts. BF:<H 0>; S1753608Ab0FLKCQ ##

AFAICT the original message did not declare an encoding:

Subject: [PATCH v2 0/2] bash completion: Support "divergence from upstream" warnings in __git_ps1
Date: Sat, 12 Jun 2010 12:02:14 +0200
Message-ID: <[email protected]>
X-Mailer: git-send-email 1.7.1.561.g94582
In-Reply-To: <20100612000002.GA30196@neumann>
References: <20100612000002.GA30196@neumann>
MIME-Version: 1.0
Content-Type: text/plain
Return-Path: [email protected]

It's hard to be 100% sure though because in the infinite wisdom of MS Exchange, the bounce came back with everything wrapped in a layer of HTML(!) and declared latin-1.

Is this a new vger policy, or am I hitting a send-email bug?

Signature · 2 lines
Thomas Rast
trast@{inf,student}.ethz.ch
#18 lore raw

Re: vger doesn't like UTF-8 from send-email

Quoted 13 lines · Thomas Rast

AFAICT the original message did not declare an encoding:

Subject: [PATCH v2 0/2] bash completion: Support "divergence from upstream" warnings in __git_ps1
Date: Sat, 12 Jun 2010 12:02:14 +0200
Message-ID: <[email protected]>
X-Mailer: git-send-email 1.7.1.561.g94582
In-Reply-To: <20100612000002.GA30196@neumann>
References: <20100612000002.GA30196@neumann>
MIME-Version: 1.0
Content-Type: text/plain
Return-Path: [email protected]
...
Is this a new vger policy, or am I hitting a send-email bug?

Let's assume the headers themselves are already properly encoded.

According to:

    http://www.faqs.org/rfcs/rfc2045.html

we have:

The proper Content-Transfer-Encoding
label must always be used.

and:

An encoding type of 7BIT requires that
the body is already in a 7bit mail-ready
representation. This is the default value
-- that is, "Content-Transfer-Encoding: 7BIT"
is assumed if the Content-Transfer-Encoding
header field is not present.

Moreover, according to:

    http://www.faqs.org/rfcs/rfc2046.html

we have:

4.1.2 Charset Parameter
...
The default character set, which must be assumed in the absence of a charset parameter, is US-ASCII.

So, your email is indeed incorrect in 2 ways if the body contains UTF-8 encoded data.

Quoted 1 line

From what I've skimmed, the mail user agent (MUA)---such as

send-email---could send your unmodified message body by producing these headers:

MIME-Version: 1.0
Content-type: text/plain; charset=utf-8
Content-transfer-encoding: 8bit

but only 7bit transfer encodings are guaranteed to make it intact to the destination; consequently, it would probably be a good idea for the MUA to transform your message into some 7bit encoding, preferably a human-readable one such as the 'quoted-printable' encoding; after such a transformation, the headers could be:

MIME-Version: 1.0
Content-type: text/plain; charset=utf-8
Content-transfer-encoding: quoted-printable

Sincerely,
Michael Witten

#19 lore raw

Re: vger doesn't like UTF-8 from send-email

Quoted 64 lines · Michael Witten
Quoted 13 lines · Thomas Rast

AFAICT the original message did not declare an encoding:

Subject: [PATCH v2 0/2] bash completion: Support "divergence from upstream" warnings in __git_ps1
Date: Sat, 12 Jun 2010 12:02:14 +0200
Message-ID: <[email protected]>
X-Mailer: git-send-email 1.7.1.561.g94582
In-Reply-To: <20100612000002.GA30196@neumann>
References: <20100612000002.GA30196@neumann>
MIME-Version: 1.0
Content-Type: text/plain
Return-Path: [email protected]
...
Is this a new vger policy, or am I hitting a send-email bug?

Let's assume the headers themselves are already properly encoded.

According to:

   http://www.faqs.org/rfcs/rfc2045.html

we have:

   The proper Content-Transfer-Encoding
   label must always be used.

and:

   An encoding type of 7BIT requires that
   the body is already in a 7bit mail-ready
   representation.  This is the default value
   -- that is, "Content-Transfer-Encoding: 7BIT"
   is assumed if the Content-Transfer-Encoding
   header field is not present.

Moreover, according to:

   http://www.faqs.org/rfcs/rfc2046.html

we have:

   4.1.2    Charset Parameter
   ...
   The default character set, which must be
   assumed in the absence of a charset parameter,
   is US-ASCII.

So, your email is indeed incorrect in 2 ways if the body contains UTF-8 encoded data.

From what I've skimmed, the mail user agent (MUA)---such as send-email---could send your unmodified message body by producing these headers:

   MIME-Version: 1.0
   Content-type: text/plain; charset=utf-8
   Content-transfer-encoding: 8bit

but only 7bit transfer encodings are guaranteed to make it intact to the destination; consequently, it would probably be a good idea for the MUA to transform your message into some 7bit encoding, preferably a human-readable one such as the 'quoted-printable' encoding; after such a transformation, the headers could be:

Quoted 4 lines

   MIME-Version: 1.0
   Content-type: text/plain; charset=utf-8
   Content-transfer-encoding: quoted-printable

QP-encoding is sometimes destructive, and as such not recommended for patches - in fact, Documentation/SubmittingPatches forbid it. For the cover-letter the destruction might not be an issue (IIRC it's some line-feeds that might be added because QP can a line longer than the maximum line-length), but special casing the encoding for cover-letters doesn't strike me as The Right Thing To Do(tm).

I think the only real alternative to 8-bit encoding is Base64, and it sacrifices human-readability. Dunno how bad that is, though.

Signature · 1 line
Erik "kusma" Faye-Lund
#20 lore raw

PATCH 1/1 send-email: ask about and declare 8bit mails

In reply to Thomas Rast

git-send-email passes on an 8bit mail as-is even if it does not declare a content-type. Because the user can edit email between format-patch and send-email, such invalid mails are unfortunately not very hard to come by.

Make git-send-email stop and ask about the encoding to use if it encounters any such mail. Also provide a configuration setting to permanently configure an encoding.

---

This takes care of what I ran into earlier today. However, there's another problem: format-patch doesn't even mark the patch 8bit if its patch contents (not log message) are non-ASCII. I'm really not sure what to do there.

On the practical hand, there's the problem that the entire log_tree_commit() call chain is geared towards printing on a file, at which time it's too late. So we would either have to go in and fix all of that to support formatting to a strbuf, or rewrite the patch if it turns out to be non-ASCII.

On the philosophical hand, we don't really care about file encodings so far, but this requires declaring one.

Either way, I think if vger doesn't accept format-patch;send-email, something is really wrong :-)

 Documentation/git-send-email.txt |    9 ++++
 git-send-email.perl              |   59 +++++++++++++++++++++++++++++
 t/t9001-send-email.sh            |   77 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 145 insertions(+), 0 deletions(-)
Documentation/git-send-email.txt +9 -0
index 12622fc..c283084 100644
@@ -101,6 +101,15 @@ See the CONFIGURATION section for 'sendemail.multiedit'.
 +
 The --to option must be repeated for each user you want on the to list.
 
+--8bit-encoding=<encoding>::
+	When encountering a non-ASCII message or subject that does not
+	declare its encoding, add headers/quoting to indicate it is
+	encoded in <encoding>.  Default is the value of the
+	'sendemail.assume8bitEncoding'; if that is unspecified, this
+	will be prompted for if any non-ASCII files are encountered.
++
+Note that no attempts whatsoever are made to validate the encoding.
+
 
 Sending
 ~~~~~~~
git-send-email.perl +59 -0

104 lines not on this page · raw message

t/t9001-send-email.sh +77 -0

81 lines not on this page · raw message

Signature · 1 line
1.7.1.557.gd161
#21 lore raw

Re: [PATCH] send-email: ask about and declare 8bit mails

Quoted 9 lines · Thomas Rast

git-send-email passes on an 8bit mail as-is even if it does not declare a content-type. Because the user can edit email between format-patch and send-email, such invalid mails are unfortunately not very hard to come by.

Make git-send-email stop and ask about the encoding to use if it encounters any such mail. Also provide a configuration setting to permanently configure an encoding.

Quoted 7 lines

---

This takes care of what I ran into earlier today. However, there's another problem: format-patch doesn't even mark the patch 8bit if its patch contents (not log message) are non-ASCII. I'm really not sure what to do there.

A project won't have uniform file encoding anyway, so even if we were to do something clever about this, it has to be per-patch. Perhaps

(0) use the attributes mechanism to allow projects to mark paths with encoding. E.g.

# everything in UTF-8 unless otherwise specified...
* encoding=UTF-8
Documentation/zh_CN/* encoding=big5

(1) for each patch, find the paths involved, and if their encodings are the same, perhaps promote that as the encoding used for the entire message;

(2) otherwise, if there is an 8-bit encoding involved in the paths, perhaps mark the entire message as 8-bit (binary???).

I have this suspicion that (2) is very rare (you cannot transmit such a patch as a plain text message reliably afaict, so it is not done in practice), and we would probably need to make a separate patchfile for groups of paths in each encoding and attach them as MIME multiparts (ugh).

Just thinkning aloud, before morning caffeine sinks in, so please take this with a grain of salt...

#22 lore raw

Re: [PATCH] send-email: ask about and declare 8bit mails

Quoted 7 lines · Junio C Hamano

(2) otherwise, if there is an 8-bit encoding involved in the paths, perhaps mark the entire message as 8-bit (binary???).

I have this suspicion that (2) is very rare (you cannot transmit such a patch as a plain text message reliably afaict, so it is not done in practice), and we would probably need to make a separate patchfile for groups of paths in each encoding and attach them as MIME multiparts (ugh).

So IIUC this would be the main/first obstacle? Seeing as we seem to do fine here but you both say 8bit is not reliable. (According to Wikipedia[*] all the big names support it though...)

Perhaps Quoted-Printable would work with minimal effort? We could leave it to send-email to do all the quoting, mailsplit or am all the unquoting and we retain (mostly) the readability of the original patches.

That still doesn't solve the problem that we might send (invalid utf8) binary data declared as utf8. I suppose to work around that, a more elaborate approach like

Quoted 10 lines

(0) use the attributes mechanism to allow projects to mark paths with encoding. E.g.

# everything in UTF-8 unless otherwise specified...
* encoding=UTF-8
Documentation/zh_CN/* encoding=big5

(1) for each patch, find the paths involved, and if their encodings are the same, perhaps promote that as the encoding used for the entire message;

is needed.

[*] http://en.wikipedia.org/wiki/8BITMIME#8BITMIME

Signature · 2 lines
Thomas Rast
trast@{inf,student}.ethz.ch
#23 lore raw

PATCH 1/1 bash completion: Support "divergence from upstream" messages in __git_ps1

In reply to Thomas Rast

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.

Also provide ways for the user to specify a custom upstream, or code that figures out the upstream.

Support for other types of upstream than SVN should be easy to add if anyone is so inclined.
---

This is based largely on Thomas' patch, but with some significant differences. Thanks once again Thomas.

I've made the quieter </> behaviour the default. A major use case for me will be over-the-shoulder checking for the rest of my team - I can probably add a couple of characters to their prompts without raising any eyebrows, but " u+1-2" is enough UI to provoke people's curiosity. If they're not interested in this feature, it will be harder for me to justify 6+ interesting characters than 2 boring ones. I haven't gone with Steven's ↑/↓ idea because I don't want to field complaints about "my terminal is Unicode-aware but those characters are unreadable in my default font". I'd rather people edit the source for that sort of thing.

I've added a message in the "equal to upstream" case, to differentiate it from the "no upstream" case. Again, this is an over-the-shoulder issue - when I see an "=" (or " u=") in someone's prompt, I don't have to patronise them about whether they've e.g. misconfigured their branch.

I've added a legacy mode to make the script work without "git rev-list --count". I really like the "git rev-list --count" option, but getting my team to run a patched version of git would be quite a bit more trouble than it's worth. If people strongly object to this feature then I can hide it better or remove it from the public patch.

The documentation for the "legacy" option currently reads "don't use the '--count' option available in recent versions of git-rev-list". If/when "--count" makes it into master, this could be changed to "compatibility mode for git versions less than <version when --count went in>".

I've made several efficiency improvements, only one of which is particularly interesting: instead of doing an echo, the code now sets p= directly. Admittedly this is messier, but $p is dynamically scoped and testing suggests that setting it knocks 10% or so off run-time.

The code should now handle multiple SVN repositories, by getting all svn-remote.*.url config options with a --get-regexp.

I like the "ref" option, but I'm not really sure when "eval" would be useful. I've changed it here to "cmd" so people are encouraged to put their work in a script.

I've tried to take Szeder's comments on board, but I'm not really sure what the problem with unnecessary empty lines is. If this is a convention I'm not aware of, could you explain in a bit more detail?

- Andrew

 contrib/completion/git-completion.bash |  144 +++++++++++++++++++++++++++++++-
 1 files changed, 143 insertions(+), 1 deletions(-)
contrib/completion/git-completion.bash +143 -1
index 57245a8..7e40f65 100755
@@ -42,6 +42,23 @@
 #       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 between HEAD and its
+#       upstream, set GIT_PS1_SHOWUPSTREAM to a nonempty value.  A "<"
+#       indicates you are behind, ">" indicates you are ahead, and
+#       "<>" indicates you have diverged.  You can further control the
+#       output by setting GIT_PS1_SHOWUPSTREAM to a space-separated
+#       list of values:
+#           git           compare HEAD to @{upstream}
+#           svn           compare HEAD to your SVN upstream
+#           ref=<ref>     compare HEAD to <ref>
+#           cmd=<command> compare HEAD to the output of <command>
+#           verbose       show number of commits ahead/behind (+/-) upstream
+#           legacy        don't use the '--count' option available in recent
+#                         versions of git-rev-list
+#       If none of 'git', 'svn', 'ref' or 'cmd' are specified, your SVN
+#       upstream will be used if configured, or your git upstream otherwise.
+#
+#
 # To submit patches:
 #
 #    *) Read Documentation/SubmittingPatches
@@ -78,6 +95,126 @@ __gitdir ()
 	fi
 }
 
+# stores the divergence from upstream in $p
+# used by GIT_PS1_SHOWUPSTREAM
+__git_ps1_show_upstream ()
+{
+	local cfg=( $( git config --get-regexp '^bash\.showUpstream$|^svn-remote\..*\.url$' 2>/dev/null ) )
+	local svn_remote=() svn_url_pattern count n
+	local upstream=git legacy verbose
+
+	# get some config options from git-config
+	for (( n=0; "$n" != "${#cfg[@]}"; ++n )); do
+		case "${cfg[$n]}" in
+			bash.showUpstream)
+				GIT_PS1_SHOWUPSTREAM="${cfg[$((n+1))]}"
+				if [[ -z "${GIT_PS1_SHOWUPSTREAM}" ]]; then
+					p=""
+					return
+				fi
+				;;
+			svn-remote.*.url)
+				svn_remote[ $(( ${#svn_remote[@]} + 1 )) ]="${cfg[$((n+1))]}"
+				svn_url_pattern+="\\|${cfg[$((n+1))]}"
+				upstream=svn # default upstream is SVN if available
+				;;
+		esac
+	done
+
+	# parse configuration values
+	for option in ${GIT_PS1_SHOWUPSTREAM}; do
+		case "$option" in
+			git|svn|"ref="*|"cmd="*) upstream="$option" ;;
+			verbose) verbose=1 ;;
+			legacy)  legacy=1  ;;
+		esac
+	done
+
+	# Find our upstream
+	case "$upstream" in
+		git)    upstream="@{upstream}" ;;
+		ref\=*) upstream="${option:4}" ;;
+		cmd\=*) upstream=$( "${option:4}" ) ;;
+		svn)
+			# get the upstream from the "git-svn-id: ..." in a commit message
+			# (git-svn uses essentially the same procedure internally)
+			upstream=( $(git log --first-parent -1 \
+					--grep="^git-svn-id: \(${svn_url_pattern:2}\)") )
+			if [[ -n "$upstream" ]]; then
+				upstream=${upstream[ ${#upstream[@]} - 2 ]}
+				upstream=${upstream%@*}
+				for (( n=1; "$n" <= "${#svn_remote[@]}"; ++n )); do
+					upstream=${upstream#${svn_remote[$n]}}
+				done
+
+				if [[ -z "$upstream" ]]; then
+					# default branch name for checkouts with no layout:
+					upstream=${GIT_SVN_ID:-git-svn}
+				else
+					upstream=${upstream#/}
+				fi
+
+			fi
+			;;
+	esac
+
+	# Find how many commits we are ahead/behind our upstream
+	if [[ -z "$legacy" ]]; then
+		count="$(git rev-list --count --left-right \
+				"$upstream"...HEAD 2>/dev/null)"
+	else
+		# produce equivalent output to --count for older versions of git
+		local commits
+		if commits="$( git rev-list --left-right "$upstream"...HEAD 2>/dev/null )"
+		then
+			local commit behind=0 ahead=0
+			for commit in $commits
+			do
+				case "$commit" in
+					"<"*) let ++behind
+						;;
+					*)    let ++ahead
+						;;
+				esac
+			done
+			count="$behind	$ahead"
+		else
+			count=""
+		fi
+	fi
+
+	# calculate the result
+	if [[ -z "$verbose" ]]; then
+		case "$count" in
+			"") # no upstream
+				p="" ;;
+			"0	0") # equal to upstream
+				p="=" ;;
+			"0	"*) # ahead of upstream
+				p=">" ;;
+			*"	0") # behind upstream
+				p="<" ;;
+			*)	    # diverged from upstream
+				p="<>" ;;
+		esac
+	else
+		case "$count" in
+			"") # no upstream
+				p="" ;;
+			"0	0") # equal to upstream
+				p=" u=" ;;
+			"0	"*) # ahead of upstream
+				p=" u+${count#0	}" ;;
+			*"	0") # behind upstream
+				p=" u-${count%	0}" ;;
+			*)	    # diverged from upstream
+				p=" u+${count#*	}-${count%	*}" ;;
+		esac
+	fi
+
+}
+
+
 # __git_ps1 accepts 0 or 1 arguments (i.e., format string)
 # returns text to add to bash PS1 prompt (includes branch name)
 __git_ps1 ()
@@ -132,6 +269,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 +297,14 @@ __git_ps1 ()
 			      u="%"
 			   fi
 			fi
+
+			if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then
+				__git_ps1_show_upstream
+			fi
 		fi
 
 		local f="$w$i$s$u"
-		printf "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r"
+		printf "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r$p"
 	fi
 }
 
Signature · 1 line
1.7.0.4
#24 lore raw

Re: [PATCH] bash completion: Support "divergence from upstream" messages in __git_ps1

Quoted 5 lines · Andrew Sayers

I've added a message in the "equal to upstream" case, to differentiate it from the "no upstream" case. Again, this is an over-the-shoulder issue - when I see an "=" (or " u=") in someone's prompt, I don't have to patronise them about whether they've e.g. misconfigured their branch.

I omitted it because I thought it would be too cluttery, but then my branches seem to rarely agree with their upstream.

Quoted 1 line

+ local cfg=( $( git config --get-regexp '^bash\.showUpstream$|^svn-remote\..*\.url$' 2>/dev/null ) )

Doesn't this break if the config value contains spaces? I don't know enough about bash arrays but in my simple tests, the array elements are split between words.

And with the new design, you practically *expect* the config key to contain spaces.

Along the same lines, I think

contrib/completion/git-completion.bash __gitdir () · line 111

Quoted 2 lines
contrib/completion/git-completion.bash +2 -0
+                               GIT_PS1_SHOWUPSTREAM="${cfg[$((n+1))]}"
+                               if [[ -z "${GIT_PS1_SHOWUPSTREAM}" ]]; then

can never trigger because bash will never see the empty config string.

Slightly more robust would be to use

  git config --get-regexp '^bash\.showUpstream$|^svn-remote\..*\.url$' \
    2>/dev/null |
  while read key value, do
    # stuff
  done

That still breaks in the case of values containing newlines, though.

Quoted 3 lines

I like the "ref" option, but I'm not really sure when "eval" would be useful. I've changed it here to "cmd" so people are encouraged to put their work in a script.

[...]

Quoted 1 line

+# cmd=<command> compare HEAD to the output of <command>

[...]

contrib/completion/git-completion.bash __gitdir () · line 137

Quoted 1 line
contrib/completion/git-completion.bash +1 -0
+		cmd\=*) upstream=$( "${option:4}" ) ;;

"Encourage" is a mild understatement; AFAICS the code doesn't work with more than single-word command any more.

The original intent was that the user could put a (very small) shell script directly in the configuration if the normal DWIMming doesn't fit his neds, perhaps most likely in the case of git-svn (do other remote helpers have the same problem?).

Having to wrap it in a script defeats that point, as it becomes almost as easy to edit the completion script. So I think if it can't eval, you might as well remove it.

BTW, please spell $(command) substitution without the spaces. Your current style does not match what is already in the file.

Signature · 2 lines
Thomas Rast
trast@{inf,student}.ethz.ch
#25 lore raw

PATCH v4 1/1 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.
---

This version removes ref= and cmd=/eval entirely, adds documentation and reaches once again into the forbidden bash bag to fix Thomas' issues.

I've used process substitution <(git config) instead of a simple pipe because it's the only way I know to maintain the value of a local variable. To demonstrate, this prints a blank line:

foo() {
local FOO
echo foo | while read ; do FOO=$REPLY ; done echo $FOO
}
foo

Whereas this prints 'foo':

foo() {
local FOO
while read ; do FOO=$REPLY ; done < <( echo foo ) echo $FOO
}
foo

While working on this patch, I found the following bug in 1.7.0.4:

$ git config --get-regexp '^(bash\.showUpstream)$'
bash.showupstream legacy verbose
$ git config --get-regexp '^(bash\.showUpstream|x)$'
bash.showupstream legacy verbose
$ git config --get-regexp '^(bash\.showupstream|\.)$'
bash.showupstream legacy verbose
$ git config --get-regexp '^(bash\.showUpstream|\.)$'

The last line should print the same value as all the others. This seems to be some weird issue with handling uppercase characters, but I've not yet had time to create a minimal test case or check it on master - I'll try to make some time tomorrow if it isn't a known issue.

 contrib/completion/git-completion.bash |  142 +++++++++++++++++++++++++++++++-
 1 files changed, 141 insertions(+), 1 deletions(-)
contrib/completion/git-completion.bash +141 -1
index 57245a8..dabcdaa 100755
@@ -42,6 +42,23 @@
 #       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 between HEAD and its
+#       upstream, set GIT_PS1_SHOWUPSTREAM to a nonempty value.  A "<"
+#       indicates you are behind, ">" indicates you are ahead, and
+#       "<>" indicates you have diverged.  You can further control
+#       behaviour by setting GIT_PS1_SHOWUPSTREAM to a space-separated
+#       list of values:
+#           git           compare HEAD to @{upstream}
+#           svn           compare HEAD to your SVN upstream
+#           verbose       show number of commits ahead/behind (+/-) upstream
+#           legacy        don't use the '--count' option available in recent
+#                         versions of git-rev-list
+#       By default, __git_ps1 will compare HEAD to your SVN upstream
+#       if it can find one, or @{upstream} otherwise.  You can
+#       override the value of GIT_PS1_SHOWUPSTREAM on a per-repository
+#       basis by setting the bash.showUpstream config variable.
+#
+#
 # To submit patches:
 #
 #    *) Read Documentation/SubmittingPatches
@@ -78,6 +95,124 @@ __gitdir ()
 	fi
 }
 
+# stores the divergence from upstream in $p
+# used by GIT_PS1_SHOWUPSTREAM
+__git_ps1_show_upstream ()
+{
+	local key value
+	local svn_remote=() svn_url_pattern count n
+	local upstream=git legacy verbose
+
+	# get some config options from git-config
+	while read key value; do
+		case "$key" in
+			bash.showupstream)
+				GIT_PS1_SHOWUPSTREAM="$value"
+				if [[ -z "${GIT_PS1_SHOWUPSTREAM}" ]]; then
+					p=""
+					return
+				fi
+				;;
+			svn-remote.*.url)
+				svn_remote[ $((${#svn_remote[@]} + 1)) ]="$value"
+				svn_url_pattern+="\\|$value"
+				upstream=svn # default upstream is SVN if available
+				;;
+		esac
+	done < <(git config -z --get-regexp '^(svn-remote\..*\.url|bash\.showupstream)$' 2>/dev/null | tr '\0\n' '\n ')
+
+	# parse configuration values
+	for option in ${GIT_PS1_SHOWUPSTREAM}; do
+		case "$option" in
+			git|svn) upstream="$option" ;;
+			verbose) verbose=1 ;;
+			legacy)  legacy=1  ;;
+		esac
+	done
+
+	# Find our upstream
+	case "$upstream" in
+		git)    upstream="@{upstream}" ;;
+		svn)
+			# get the upstream from the "git-svn-id: ..." in a commit message
+			# (git-svn uses essentially the same procedure internally)
+			upstream=($(git log --first-parent -1 \
+					--grep="^git-svn-id: \(${svn_url_pattern:2}\)"))
+			if [[ -n "$upstream" ]]; then
+				upstream=${upstream[ ${#upstream[@]} - 2 ]}
+				upstream=${upstream%@*}
+				for ((n=1; "$n" <= "${#svn_remote[@]}"; ++n)); do
+					upstream=${upstream#${svn_remote[$n]}}
+				done
+
+				if [[ -z "$upstream" ]]; then
+					# default branch name for checkouts with no layout:
+					upstream=${GIT_SVN_ID:-git-svn}
+				else
+					upstream=${upstream#/}
+				fi
+
+			fi
+			;;
+	esac
+
+	# Find how many commits we are ahead/behind our upstream
+	if [[ -z "$legacy" ]]; then
+		count="$(git rev-list --count --left-right \
+				"$upstream"...HEAD 2>/dev/null)"
+	else
+		# produce equivalent output to --count for older versions of git
+		local commits
+		if commits="$(git rev-list --left-right "$upstream"...HEAD 2>/dev/null)"
+		then
+			local commit behind=0 ahead=0
+			for commit in $commits
+			do
+				case "$commit" in
+					"<"*) let ++behind
+						;;
+					*)    let ++ahead
+						;;
+				esac
+			done
+			count="$behind	$ahead"
+		else
+			count=""
+		fi
+	fi
+
+	# calculate the result
+	if [[ -z "$verbose" ]]; then
+		case "$count" in
+			"") # no upstream
+				p="" ;;
+			"0	0") # equal to upstream
+				p="=" ;;
+			"0	"*) # ahead of upstream
+				p=">" ;;
+			*"	0") # behind upstream
+				p="<" ;;
+			*)	    # diverged from upstream
+				p="<>" ;;
+		esac
+	else
+		case "$count" in
+			"") # no upstream
+				p="" ;;
+			"0	0") # equal to upstream
+				p=" u=" ;;
+			"0	"*) # ahead of upstream
+				p=" u+${count#0	}" ;;
+			*"	0") # behind upstream
+				p=" u-${count%	0}" ;;
+			*)	    # diverged from upstream
+				p=" u+${count#*	}-${count%	*}" ;;
+		esac
+	fi
+
+}
+
+
 # __git_ps1 accepts 0 or 1 arguments (i.e., format string)
 # returns text to add to bash PS1 prompt (includes branch name)
 __git_ps1 ()
@@ -132,6 +267,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 +295,14 @@ __git_ps1 ()
 			      u="%"
 			   fi
 			fi
+
+			if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then
+				__git_ps1_show_upstream
+			fi
 		fi
 
 		local f="$w$i$s$u"
-		printf "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r"
+		printf "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r$p"
 	fi
 }
 
Signature · 1 line
1.7.0.4
#26 lore raw

Re: [PATCHv4] bash completion: Support "divergence from upstream" messages in __git_ps1

Quoted 10 lines · Andrew Sayers

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.
---

Sign-off?

Quoted 11 lines
contrib/completion/git-completion.bash +3 -0
index 57245a8..dabcdaa 100755
@@ -42,6 +42,23 @@
 #       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 between HEAD and its
+#       upstream, set GIT_PS1_SHOWUPSTREAM to a nonempty value.  A "<"
+#       indicates you are behind, ">" indicates you are ahead, and

contrib/completion/git-completion.bash line 57

Quoted 10 lines
contrib/completion/git-completion.bash +10 -0
+#       "<>" indicates you have diverged.  You can further control
+#       behaviour by setting GIT_PS1_SHOWUPSTREAM to a space-separated
+#       list of values:
+#           git           compare HEAD to @{upstream}
+#           svn           compare HEAD to your SVN upstream
+#           verbose       show number of commits ahead/behind (+/-) upstream
+#           legacy        don't use the '--count' option available in recent
+#                         versions of git-rev-list
+#       By default, __git_ps1 will compare HEAD to your SVN upstream
+#       if it can find one, or @{upstream} otherwise.

This feels somewhat weird.

I can sort-of read from the above that I can set the variable to a random string, e.g. "garbage", if I only want a simple show-upstream feature without frills (i.e. I don't want it to be verbose, I don't want it to restrict the comparison only to "git" upstream nor "svn" upstream, and I don't think I would ever use ancient git that lack "rev-list --count"). But the description does not assure me that the random string I happened to choose (in this case "garbage") is a safe one. Perhaps list (and implement) "default" as a safe, otherwise-no-op value?

How much overhead are we shaving if you specify "git" (without "svn") or "svn" (without "git") to the variable? I suspect that the bulk of the time is spent by reading from "git config" to look for svn-remote.*.url, which you seem to unconditionally do even when "git" was asked for anyway.

contrib/completion/git-completion.bash line 59

Quoted 3 lines
contrib/completion/git-completion.bash +3 -0
+#       You can
+#       override the value of GIT_PS1_SHOWUPSTREAM on a per-repository
+#       basis by setting the bash.showUpstream config variable.

That's totally backwards from it should be, isn't it?

Usually configuration variables are used to give you the default, and you use environment variables to override them.

Quoted 7 lines
contrib/completion/git-completion.bash +7 -0
+# stores the divergence from upstream in $p
+# used by GIT_PS1_SHOWUPSTREAM
+__git_ps1_show_upstream ()
+{
+	local key value
+	local svn_remote=() svn_url_pattern count n
+	local upstream=git legacy verbose

contrib/completion/git-completion.bash __gitdir () · line 114

Quoted 10 lines
contrib/completion/git-completion.bash +10 -0
+
+	# get some config options from git-config
+	while read key value; do
+		case "$key" in
+			bash.showupstream)
+				GIT_PS1_SHOWUPSTREAM="$value"
+				if [[ -z "${GIT_PS1_SHOWUPSTREAM}" ]]; then
+					p=""
+					return
+				fi

This is the "backwards" part.

contrib/completion/git-completion.bash __gitdir () · line 120

Quoted 6 lines
contrib/completion/git-completion.bash +6 -0
+				;;
+			svn-remote.*.url)
+				svn_remote[ $((${#svn_remote[@]} + 1)) ]="$value"
+				svn_url_pattern+="\\|$value"
+				upstream=svn # default upstream is SVN if available
+				;;

I expected that (1) when on a branch that is a fork of a svn upstream, you would use the svn magic; (2) otherwise when on a branch that is a fork of a git upstream, you would use "@{upstream}". That way, the users do not even have to say "git" or "svn" in GIT_PS1_SHOWUPSTREAM at all, no?

But that does not seem to be what is happening here. Your loop seems to force "upstream=svn" if I have one branch that is a fork from svn upstream, even if my current branch does not have anything to do with that branch nor svn upstream. Is that what was intended?

Oh, also, all of your case arms are one-indent too deep. Please write them like this:

	case foo in
        arm1)
        	stmt1
                ;;
	esac

contrib/completion/git-completion.bash __gitdir () · line 122

Quoted 2 lines
contrib/completion/git-completion.bash +2 -0
+		esac
+	done < <(git config -z --get-regexp '^(svn-remote\..*\.url|bash\.showupstream)$' 2>/dev/null | tr '\0\n' '\n ')

If you "tr" to trash "\0" anyway, do you need to run "config -z"?

contrib/completion/git-completion.bash __gitdir () · line 125

Quoted 2 lines
contrib/completion/git-completion.bash +2 -0
+	# parse configuration values
+	for option in ${GIT_PS1_SHOWUPSTREAM}; do

Is this safe under "set -u"? See 25a31f8 (bash-completion: Support running when set -u is enabled, 2009-01-15).

#27 lore raw

Re: [PATCHv4] bash completion: Support "divergence from upstream" messages in __git_ps1

Quoted 9 lines · Junio C Hamano

contrib/completion/git-completion.bash line 59

Quoted 3 lines · Andrew Sayers
contrib/completion/git-completion.bash +3 -0
+#       You can
+#       override the value of GIT_PS1_SHOWUPSTREAM on a per-repository
+#       basis by setting the bash.showUpstream config variable.

That's totally backwards from it should be, isn't it?

Usually configuration variables are used to give you the default, and you use environment variables to override them.

Not in the bash completion. The test for the environment variable is cheap, so you use that to enable the feature and can then use configs to tweak them at a per-repo level. There is precedent with GIT_PS1_SHOWDIRTYSTATE and bash.showDirtyState.

The comment above should state that this override only works if the environment variable is also enabled, though.

Signature · 2 lines
Thomas Rast
trast@{inf,student}.ethz.ch
#28 lore raw

v5 cover bash completion: Support "divergence from upstream" messages in __git_ps1

In reply to Junio C Hamano

I agree with all the points I haven't specifically replied to. The first patch makes the appropriate changes. The second patch fixes largely unrelated "set -u" issues I stumbled over while running tests.

Quoted 24 lines · Junio C Hamano
Quoted 11 lines
contrib/completion/git-completion.bash +3 -0
index 57245a8..dabcdaa 100755
@@ -42,6 +42,23 @@
 #       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 between HEAD and its
+#       upstream, set GIT_PS1_SHOWUPSTREAM to a nonempty value.  A "<"
+#       indicates you are behind, ">" indicates you are ahead, and

contrib/completion/git-completion.bash line 57

Quoted 10 lines
contrib/completion/git-completion.bash +10 -0
+#       "<>" indicates you have diverged.  You can further control
+#       behaviour by setting GIT_PS1_SHOWUPSTREAM to a space-separated
+#       list of values:
+#           git           compare HEAD to @{upstream}
+#           svn           compare HEAD to your SVN upstream
+#           verbose       show number of commits ahead/behind (+/-) upstream
+#           legacy        don't use the '--count' option available in recent
+#                         versions of git-rev-list
+#       By default, __git_ps1 will compare HEAD to your SVN upstream
+#       if it can find one, or @{upstream} otherwise.

This feels somewhat weird.

Quoted 8 lines

I can sort-of read from the above that I can set the variable to a random string, e.g. "garbage", if I only want a simple show-upstream feature without frills (i.e. I don't want it to be verbose, I don't want it to restrict the comparison only to "git" upstream nor "svn" upstream, and I don't think I would ever use ancient git that lack "rev-list --count"). But the description does not assure me that the random string I happened to choose (in this case "garbage") is a safe one. Perhaps list (and implement) "default" as a safe, otherwise-no-op value?

I agree this would improve the documentation, but I've used "auto" instead of "default", to give a hint that that the code is being a bit automagical. I don't see how adding code would help though - "GIT_PS1_SHOWUPSTREAM=auto" is already covered in the default case of an unrecognised string, and adding code to make "GIT_PS1_SHOWUPSTREAM=1" do nothing or print a warning would just confuse people that skip-read the documentation and set the value to see what happened.

Quoted 5 lines

How much overhead are we shaving if you specify "git" (without "svn") or "svn" (without "git") to the variable? I suspect that the bulk of the time is spent by reading from "git config" to look for svn-remote.*.url, which you seem to unconditionally do even when "git" was asked for anyway.

In my tests, a single invocation of git-config took an average of roughly 0.005s with a 30-line .git/config, and roughly 0.030s with a .git/config that contained about 17,600 extra nonsense lines (aaa = aaa, aab = aab, etc.). In both cases, the extra test for svn-remote.*.url made no significant difference to the time taken, whereas a second invocation of git config (obviously) doubled the time taken.

Checking the SVN upstream with git log --first-parent -1 --grep="^git-svn-id: \(${svn_url_pattern:2}\)" is actually quite a serious time issue, especially if you have made many commits since your upstream. A test with 100 empty commits since the SVN upstream took roughly 0.012 seconds on average. A test on git itself (>22,000 commits) took roughly 0.29 seconds to determine there was no SVN upstream.

Speed (and user confidence in speed) isn't the main reason to allow the user to force "git" or "svn". If someone had e.g. imported their old SVN history into a git project, or did clever git tricks on a branch they regularly merged into SVN, they would want to override the default behaviour. This is probably quite rare now I think about it, and I've rejigged the documentation a bit to reflect that.

Quoted 10 lines

contrib/completion/git-completion.bash line 59

Quoted 4 lines
contrib/completion/git-completion.bash +3 -0
+#       You can
+#       override the value of GIT_PS1_SHOWUPSTREAM on a per-repository
+#       basis by setting the bash.showUpstream config variable.

That's totally backwards from it should be, isn't it?

Usually configuration variables are used to give you the default, and you use environment variables to override them.

I basically agree with Thomas here. Going down that route without parsing all the options in one big --get-regexp mess would take O(n) time, where n consists mostly of config options I don't care about.

Quoted 12 lines

contrib/completion/git-completion.bash __gitdir () · line 120

Quoted 6 lines
contrib/completion/git-completion.bash +6 -0
+				;;
+			svn-remote.*.url)
+				svn_remote[ $((${#svn_remote[@]} + 1)) ]="$value"
+				svn_url_pattern+="\\|$value"
+				upstream=svn # default upstream is SVN if available
+				;;

I expected that (1) when on a branch that is a fork of a svn upstream, you would use the svn magic; (2) otherwise when on a branch that is a fork of a git upstream, you would use "@{upstream}". That way, the users do not even have to say "git" or "svn" in GIT_PS1_SHOWUPSTREAM at all, no?

Quoted 4 lines

But that does not seem to be what is happening here. Your loop seems to force "upstream=svn" if I have one branch that is a fork from svn upstream, even if my current branch does not have anything to do with that branch nor svn upstream. Is that what was intended?

I'm not sure I understand how you would detect if something is a fork of an SVN/git upstream. It's certainly deliberate not to do a git log if it's avoidable, for the efficiency reasons I mentioned above. But it seems like a good idea to use @{upstream} in the "auto" case if no SVN upstream was found, so I've changed the patch to do that. Please let me know if there's some applicable magic I'm not aware of :)

Quoted 4 lines

contrib/completion/git-completion.bash __gitdir () · line 122

Quoted 2 lines
contrib/completion/git-completion.bash +2 -0
+		esac
+	done < <(git config -z --get-regexp '^(svn-remote\..*\.url|bash\.showupstream)$' 2>/dev/null | tr '\0\n' '\n ')

If you "tr" to trash "\0" anyway, do you need to run "config -z"?

The tr is there to work around issues like this:

git config bash.showUpstream $'svn\nlegacy' git config bash.showUpstream | tr '\0\n' '\n '

The end result is a format that can be easily parsed with read. Without the -z, I'd have no way to tell the difference between the end of a config item and a literal newline.

Having said that, I have no strong opinion about which is the more appropriate thing to do here - slow down everyone's prompt to deal with an edge case, or break otherwise-valid behaviour because the solution is ugly. The latest patch still has it in - let me know if you'd prefer it out.

Quoted 6 lines

contrib/completion/git-completion.bash __gitdir () · line 125

Quoted 3 lines
contrib/completion/git-completion.bash +2 -0
+	# parse configuration values
+	for option in ${GIT_PS1_SHOWUPSTREAM}; do

Is this safe under "set -u"? See 25a31f8 (bash-completion: Support running when set -u is enabled, 2009-01-15).

This is safe under "set -u", as this function is only called if $GIT_PS1_SHOWUPSTREAM is defined. But testing showed several issues that make me suspect __git_ps1 never worked under "set -u". My second patch fixes those issues.

- Andrew

#29 lore raw

Re: [PATCHv5 0/2] bash completion: Support "divergence from upstream" messages in __git_ps1

Quoted 10 lines · Andrew Sayers
Quoted 8 lines

contrib/completion/git-completion.bash line 59

Quoted 3 lines
contrib/completion/git-completion.bash +3 -0
+#       You can
+#       override the value of GIT_PS1_SHOWUPSTREAM on a per-repository
+#       basis by setting the bash.showUpstream config variable.

That's totally backwards from it should be, isn't it?

Usually configuration variables are used to give you the default, and you use environment variables to override them.

I basically agree with Thomas here.

Ok.

Quoted 5 lines

... If someone had e.g. imported their old
SVN history into a git project, or did clever git tricks on a branch they regularly merged into SVN, they would want to override the default behaviour. This is probably quite rare now I think about it, and I've rejigged the documentation a bit to reflect that.

Yeah, I see.

But doesn't all of the above suggest the decision should be per branch? It is not too implausible to have a branch that is actively interacting with SVN upstream and another branch whose upstream has migrated from SVN and now managed by git. Say you and your pal are working with a project that is managed by SVN, and you use one of your branches to interact directly with SVN upstream. Your pal has a branch forked from the same SVN upstream, and one of your other branches is building on top of her work. When you are on the former branch, you would want to know how your work diverged from the SVN upstream; when you are on the latter branch, you would want to know how your work diverged from your pal's git branch that you are using as its upstream. No?

Which led me to this expectation:

Quoted 10 lines
Quoted 10 lines

contrib/completion/git-completion.bash __gitdir () · line 120

Quoted 5 lines
contrib/completion/git-completion.bash +5 -0
+			svn-remote.*.url)
+				svn_remote[ $((${#svn_remote[@]} + 1)) ]="$value"
+				svn_url_pattern+="\\|$value"
+				upstream=svn # default upstream is SVN if available
+				;;

I expected that (1) when on a branch that is a fork of a svn upstream, you would use the svn magic; (2) otherwise when on a branch that is a fork of a git upstream, you would use "@{upstream}". That way, the users do not even have to say "git" or "svn" in GIT_PS1_SHOWUPSTREAM at all, no?

I wonder if looking for "git-svn-id:" in the past log is the best you can do to see if a branch is forked from a remote that is managed by git-svn; for one thing, that would not work for "noMetadata" setting.

Quoted 6 lines
Quoted 1 line

If you "tr" to trash "\0" anyway, do you need to run "config -z"?

The tr is there to work around issues like this:

git config bash.showUpstream $'svn\nlegacy' git config bash.showUpstream | tr '\0\n' '\n '

Is that even an issue? Why should there be a LF in the value? I thought you defined it as a string with space separated magic tokens... Perhaps I am missing something?

#30 lore raw

Re: [PATCHv5 0/2] bash completion: Support "divergence from upstream" messages in __git_ps1

Quoted 13 lines · Junio C Hamano

But doesn't all of the above suggest the decision should be per branch? It is not too implausible to have a branch that is actively interacting with SVN upstream and another branch whose upstream has migrated from SVN and now managed by git. Say you and your pal are working with a project that is managed by SVN, and you use one of your branches to interact directly with SVN upstream. Your pal has a branch forked from the same SVN upstream, and one of your other branches is building on top of her work. When you are on the former branch, you would want to know how your work diverged from the SVN upstream; when you are on the latter branch, you would want to know how your work diverged from your pal's git branch that you are using as its upstream. No?

It sounds like you're asking for git-svn to set git.<branch>.{remote|upstream}, and for this script to ditch the SVN-specific workarounds. I have no problem with such a solution, but I also have no idea where to begin with it. Is there some reason we don't do this already?

A simpler 90% solution would be to switch the defaults around, so you always use @{upstream} if defined, or otherwise search for the SVN upstream. This enables every use case except noMetadata, and I suspect any solution to that one would be at least as complex as setting git.<branch>.{remote|upstream}.

Quoted 10 lines
Quoted 6 lines
Quoted 1 line

If you "tr" to trash "\0" anyway, do you need to run "config -z"?

The tr is there to work around issues like this:

git config bash.showUpstream $'svn\nlegacy' git config bash.showUpstream | tr '\0\n' '\n '

Is that even an issue? Why should there be a LF in the value? I thought you defined it as a string with space separated magic tokens... Perhaps I am missing something?

My concern was more with the robustness principle than anything - LFs aren't part of the format defined in the docs, and I can't think of a reason why people would need them, but there's no mechanical way to stop people putting them in there. If you're saying that git users can be trusted not to do anything so stupid (and/or that it's their problem if they do), then I'm happy to get rid of this.

- Andrew

#31 lore raw

PATCH v5 1/2 bash completion: Support "divergence from upstream" messages in __git_ps1

In reply to Junio C Hamano

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.

---
 contrib/completion/git-completion.bash |  144 +++++++++++++++++++++++++++++++-
 1 files changed, 143 insertions(+), 1 deletions(-)
contrib/completion/git-completion.bash +143 -1
index 57245a8..6e6f458 100755
@@ -42,6 +42,24 @@
 #       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 between HEAD and its
+#       upstream, set GIT_PS1_SHOWUPSTREAM="auto".  A "<" indicates
+#       you are behind, ">" indicates you are ahead, and "<>"
+#       indicates you have diverged.  You can further control
+#       behaviour by setting GIT_PS1_SHOWUPSTREAM to a space-separated
+#       list of values:
+#           verbose       show number of commits ahead/behind (+/-) upstream
+#           legacy        don't use the '--count' option available in recent
+#                         versions of git-rev-list
+#           git           always compare HEAD to @{upstream}
+#           svn           always compare HEAD to your SVN upstream
+#       By default, __git_ps1 will compare HEAD to your SVN upstream
+#       if it can find one, or @{upstream} otherwise.  Once you have
+#       set GIT_PS1_SHOWUPSTREAM, you can override it on a
+#       per-repository basis by setting the bash.showUpstream config
+#       variable.
+#
+#
 # To submit patches:
 #
 #    *) Read Documentation/SubmittingPatches
@@ -78,6 +96,125 @@ __gitdir ()
 	fi
 }
 
+# stores the divergence from upstream in $p
+# used by GIT_PS1_SHOWUPSTREAM
+__git_ps1_show_upstream ()
+{
+	local key value
+	local svn_remote=() svn_url_pattern count n
+	local upstream=git legacy="" verbose=""
+
+	# get some config options from git-config
+	while read key value; do
+		case "$key" in
+		bash.showupstream)
+			GIT_PS1_SHOWUPSTREAM="$value"
+			if [[ -z "${GIT_PS1_SHOWUPSTREAM}" ]]; then
+				p=""
+				return
+			fi
+			;;
+		svn-remote.*.url)
+			svn_remote[ $((${#svn_remote[@]} + 1)) ]="$value"
+			svn_url_pattern+="\\|$value"
+			upstream=svn+git # default upstream is SVN if available, else git
+			;;
+		esac
+	done < <(git config -z --get-regexp '^(svn-remote\..*\.url|bash\.showupstream)$' 2>/dev/null | tr '\0\n' '\n ')
+
+	# parse configuration values
+	for option in ${GIT_PS1_SHOWUPSTREAM}; do
+		case "$option" in
+		git|svn) upstream="$option" ;;
+		verbose) verbose=1 ;;
+		legacy)  legacy=1  ;;
+		esac
+	done
+
+	# Find our upstream
+	case "$upstream" in
+	git)    upstream="@{upstream}" ;;
+	svn*)
+		# get the upstream from the "git-svn-id: ..." in a commit message
+		# (git-svn uses essentially the same procedure internally)
+		local svn_upstream=($(git log --first-parent -1 \
+					--grep="^git-svn-id: \(${svn_url_pattern:2}\)" 2>/dev/null))
+		if [[ 0 -ne ${#svn_upstream[@]} ]]; then
+			svn_upstream=${svn_upstream[ ${#svn_upstream[@]} - 2 ]}
+			svn_upstream=${svn_upstream%@*}
+			for ((n=1; "$n" <= "${#svn_remote[@]}"; ++n)); do
+				svn_upstream=${svn_upstream#${svn_remote[$n]}}
+			done
+
+			if [[ -z "$svn_upstream" ]]; then
+				# default branch name for checkouts with no layout:
+				upstream=${GIT_SVN_ID:-git-svn}
+			else
+				upstream=${svn_upstream#/}
+			fi
+		elif [[ "svn+git" = "$upstream" ]]; then
+			upstream="@{upstream}"
+		fi
+		;;
+	esac
+
+	# Find how many commits we are ahead/behind our upstream
+	if [[ -z "$legacy" ]]; then
+		count="$(git rev-list --count --left-right \
+				"$upstream"...HEAD 2>/dev/null)"
+	else
+		# produce equivalent output to --count for older versions of git
+		local commits
+		if commits="$(git rev-list --left-right "$upstream"...HEAD 2>/dev/null)"
+		then
+			local commit behind=0 ahead=0
+			for commit in $commits
+			do
+				case "$commit" in
+				"<"*) let ++behind
+					;;
+				*)    let ++ahead
+					;;
+				esac
+			done
+			count="$behind	$ahead"
+		else
+			count=""
+		fi
+	fi
+
+	# calculate the result
+	if [[ -z "$verbose" ]]; then
+		case "$count" in
+		"") # no upstream
+			p="" ;;
+		"0	0") # equal to upstream
+			p="=" ;;
+		"0	"*) # ahead of upstream
+			p=">" ;;
+		*"	0") # behind upstream
+			p="<" ;;
+		*)	    # diverged from upstream
+			p="<>" ;;
+		esac
+	else
+		case "$count" in
+		"") # no upstream
+			p="" ;;
+		"0	0") # equal to upstream
+			p=" u=" ;;
+		"0	"*) # ahead of upstream
+			p=" u+${count#0	}" ;;
+		*"	0") # behind upstream
+			p=" u-${count%	0}" ;;
+		*)	    # diverged from upstream
+			p=" u+${count#*	}-${count%	*}" ;;
+		esac
+	fi
+
+}
+
+
 # __git_ps1 accepts 0 or 1 arguments (i.e., format string)
 # returns text to add to bash PS1 prompt (includes branch name)
 __git_ps1 ()
@@ -132,6 +269,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 +297,14 @@ __git_ps1 ()
 			      u="%"
 			   fi
 			fi
+
+			if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then
+				__git_ps1_show_upstream
+			fi
 		fi
 
 		local f="$w$i$s$u"
-		printf "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r"
+		printf "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r$p"
 	fi
 }
 
Signature · 1 line
1.7.0.4
#32 lore raw

PATCH v5 2/2 bash-completion: Fix __git_ps1 to work with "set -u"

In reply to Junio C Hamano

Define several variables in __git_ps1 to avoid errors under "set -u" semantics.

__git_ps1 seems to have been missed when the rest of the file was fixed in 25a31f8.

---
 contrib/completion/git-completion.bash |   16 ++++++++--------
 1 files changed, 8 insertions(+), 8 deletions(-)
contrib/completion/git-completion.bash +8 -8
index 6e6f458..337e4c9 100755
@@ -221,8 +221,8 @@ __git_ps1 ()
 {
 	local g="$(__gitdir)"
 	if [ -n "$g" ]; then
-		local r
-		local b
+		local r=""
+		local b=""
 		if [ -f "$g/rebase-merge/interactive" ]; then
 			r="|REBASE-i"
 			b="$(cat "$g/rebase-merge/head-name")"
@@ -264,12 +264,12 @@ __git_ps1 ()
 			}
 		fi
 
-		local w
-		local i
-		local s
-		local u
-		local c
-		local p
+		local w=""
+		local i=""
+		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
Signature · 1 line
1.7.0.4
#33 lore raw

PATCH v2 1/2 rev-list: introduce --count option

In reply to Thomas Rast

Add a --count option that, instead of actually listing the commits, merely counts them.

This is mostly geared towards script use, and to this end it acts specially when used with --left-right: it outputs the left and right counts separately. Previously, scripts would have to run a shell loop or small inline script over to achieve the same. (Without --left-right, a simple |wc -l does the job.)

---
 Documentation/rev-list-options.txt   |    9 +++++++++
 builtin/rev-list.c                   |   16 ++++++++++++++++
 revision.c                           |    2 ++
 revision.h                           |    5 +++++
 t/t6007-rev-list-cherry-pick-file.sh |   29 +++++++++++++++++++++++++++++
 5 files changed, 61 insertions(+), 0 deletions(-)
Documentation/rev-list-options.txt +9 -0
index b9fb7a8..066ade9 100644
@@ -98,6 +98,15 @@ you would get an output like this:
 This implies the '--topo-order' option by default, but the
 '--date-order' option may also be specified.
 
+ifdef::git-rev-list[]
+--count::
+	Print a number stating how many commits would have been
+	listed, and suppress all other output.  When used together
+	with '--left-right', instead print the counts for left and
+	right commits, separated by a tab.
+endif::git-rev-list[]
+
+
 ifndef::git-rev-list[]
 Diff Formatting
 ~~~~~~~~~~~~~~~
builtin/rev-list.c +16 -0
index 51ceb19..efe9360 100644
@@ -50,6 +50,15 @@ static void show_commit(struct commit *commit, void *data)
 
 	graph_show_commit(revs->graph);
 
+	if (revs->count) {
+		if (commit->object.flags & SYMMETRIC_LEFT)
+			revs->count_left++;
+		else
+			revs->count_right++;
+		finish_commit(commit, data);
+		return;
+	}
+
 	if (info->show_timestamp)
 		printf("%lu ", commit->date);
 	if (info->header_prefix)
@@ -400,5 +409,12 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
 			     quiet ? finish_object : show_object,
 			     &info);
 
+	if (revs.count) {
+		if (revs.left_right)
+			printf("%d\t%d\n", revs.count_left, revs.count_right);
+		else
+			printf("%d\n", revs.count_left + revs.count_right);
+	}
+
 	return 0;
 }
revision.c +2 -0
index f4b8b38..21b133c 100644
@@ -1146,6 +1146,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
 		revs->boundary = 1;
 	} else if (!strcmp(arg, "--left-right")) {
 		revs->left_right = 1;
+	} else if (!strcmp(arg, "--count")) {
+		revs->count = 1;
 	} else if (!strcmp(arg, "--cherry-pick")) {
 		revs->cherry_pick = 1;
 		revs->limited = 1;
revision.h +5 -0
index 568f1c9..bafa728 100644
@@ -57,6 +57,7 @@ struct rev_info {
 			limited:1,
 			unpacked:1,
 			boundary:2,
+			count:1,
 			left_right:1,
 			rewrite_parents:1,
 			print_parents:1,
@@ -131,6 +132,10 @@ struct rev_info {
 
 	/* notes-specific options: which refs to show */
 	struct display_notes_opt notes_opt;
+
+	/* commit counts */
+	int count_left;
+	int count_right;
 };
 
 #define REV_TREE_SAME		0
t/t6007-rev-list-cherry-pick-file.sh +29 -0
index 4b8611c..b565638 100755
@@ -32,6 +32,23 @@ test_expect_success setup '
 	git tag B
 '
 
+cat >expect <<EOF
+<tags/B
+>tags/C
+EOF
+
+test_expect_success '--left-right' '
+	git rev-list --left-right B...C > actual &&
+	git name-rev --stdin --name-only --refs="*tags/*" \
+		< actual > actual.named &&
+	test_cmp actual.named expect
+'
+
+test_expect_success '--count' '
+	git rev-list --count B...C > actual &&
+	test "$(cat actual)" = 2
+'
+
 test_expect_success '--cherry-pick foo comes up empty' '
 	test -z "$(git rev-list --left-right --cherry-pick B...C -- foo)"
 '
@@ -54,4 +71,16 @@ test_expect_success '--cherry-pick with independent, but identical branches' '
 		HEAD...master -- foo)"
 '
 
+cat >expect <<EOF
+1	2
+EOF
+
+# Insert an extra commit to break the symmetry
+test_expect_success '--count --left-right' '
+	git checkout branch &&
+	test_commit D &&
+	git rev-list --count --left-right B...D > actual &&
+	test_cmp expect actual
+'
+
 test_done
Signature · 1 line
1.7.1.561.g94582
#34 lore raw

PATCH v2 2/2 bash completion: Support "divergence from upstream" warnings in __git_ps1

In reply to Thomas Rast

From: Andrew Sayers <[email protected]>

Add a notification in the command prompt specifying whether you're ahead of or behind 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 migrators from centralised version control systems are especially likely to forget to push.

Also provide ways for the user to specify a custom upstream, or code that figures out the upstream.

Support for other types of upstream than SVN should be easy to add if anyone is so inclined.

---
 contrib/completion/git-completion.bash |   89 +++++++++++++++++++++++++++++++-
 1 files changed, 88 insertions(+), 1 deletions(-)
contrib/completion/git-completion.bash +88 -1
index 57245a8..a6cb435 100755
@@ -42,6 +42,17 @@
 #       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 between HEAD and its upstream,
+#       set GIT_PS1_SHOWUPSTREAM to one of the following:
+#           git          use @{upstream}
+#           svn          attempt to DWIM svn upstream for normal and --stdlayout
+#           ref <ref>    unconditionally use <ref>
+#           eval <code>  evaluate <code> which should print the commit to use
+#       Any other value DWIMs either svn or git, preferring svn if configured.
+#
+#       The difference will be shown as, e.g., "u+7-5" meaning that you are 7
+#       commits ahead of and 5 commits behind the upstream.
+#
 # To submit patches:
 #
 #    *) Read Documentation/SubmittingPatches
@@ -78,6 +89,77 @@ __gitdir ()
 	fi
 }
 
+__git_ps1_divergence_from_upstream ()
+{
+	local cfg
+	if cfg="$(git config --get bash.showUpstream)"
+	then
+		GIT_PS1_SHOWUPSTREAM="$cfg"
+	fi
+	if [ -n "${GIT_PS1_SHOWUPSTREAM}" ]; then
+		local upstream count
+		case "${GIT_PS1_SHOWUPSTREAM}" in
+		svn|git|"ref "*|"eval "*)
+			;;
+		*)
+			# try to dwim the type
+			if git config --get svn-remote.svn.url >/dev/null; then
+				GIT_PS1_SHOWUPSTREAM=svn
+			else
+				GIT_PS1_SHOWUPSTREAM=git
+			fi
+			;;
+		esac
+		case "${GIT_PS1_SHOWUPSTREAM}" in
+		git)
+			upstream="@{upstream}"
+			;;
+		svn)
+			local url
+			# git-svn upstream checking: if it has a
+			# remotes/git-svn, that is probably the upstream.
+			# Otherwise try to figure out the branch for
+			# --stdlayout repos.
+			if ! upstream="$(git rev-parse remotes/git-svn 2>/dev/null)"; then
+				url="$(git config --get svn-remote.svn.url)"
+				upstream=( $(git log --first-parent -1 \
+						 --grep="^git-svn-id: $url") )
+				if [ -n "$upstream" ]; then
+					upstream=${upstream[ ${#upstream[@]} - 2 ]}
+					upstream=${upstream%@*}
+					upstream=${upstream#*$url/}
+				fi
+			fi
+			;;
+		"eval "*)
+			# custom shell command that determines upstream
+			upstream="$(eval "${GIT_PS1_SHOWUPSTREAM#eval }")"
+			;;
+		"ref "*)
+			upstream="${GIT_PS1_SHOWUPSTREAM#ref }"
+			;;
+		esac
+
+		count=$(git rev-list --count --left-right \
+				"$upstream"...HEAD 2>/dev/null)
+		case "$count" in
+		"0	0"|"")
+			# empty = no upstream or no --count
+			;;
+		"0	"*)
+			echo "+${count#0	}"
+			;;
+		*"	0")
+			echo "-${count%	0}"
+			;;
+		*)
+			echo "+${count#*	}-${count%	*}"
+			;;
+		esac
+	fi
+}
+
+
 # __git_ps1 accepts 0 or 1 arguments (i.e., format string)
 # returns text to add to bash PS1 prompt (includes branch name)
 __git_ps1 ()
@@ -132,6 +214,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 +242,14 @@ __git_ps1 ()
 			      u="%"
 			   fi
 			fi
+
+			if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then
+				p="$(__git_ps1_divergence_from_upstream)"
+			fi
 		fi
 
 		local f="$w$i$s$u"
-		printf "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r"
+		printf "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r${p:+ u$p}"
 	fi
 }
 
Signature · 1 line
1.7.1.561.g94582
#35 lore raw

Re: [PATCH v2 2/2] bash completion: Support "divergence from upstream" warnings in __git_ps1

Hi,

Quoted 5 lines · Thomas Rast
contrib/completion/git-completion.bash +0 -0
index 57245a8..a6cb435 100755

contrib/completion/git-completion.bash line 51

Quoted 10 lines
contrib/completion/git-completion.bash +7 -0
@@ -42,6 +42,17 @@
 #       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 between HEAD and its upstream,
+#       set GIT_PS1_SHOWUPSTREAM to one of the following:
+#           git          use @{upstream}
+#           svn          attempt to DWIM svn upstream for normal and --stdlayout
+#           ref <ref>    unconditionally use <ref>
+#           eval <code>  evaluate <code> which should print the commit to use
+#       Any other value DWIMs either svn or git, preferring svn if configured.

Something like this should go in there somewhere:

The bash.showUpstream config variable can be used to override the value of GIT_PS1_SHOWUPSTREAM on a per-repository basis.

Quoted 7 lines
contrib/completion/git-completion.bash +4 -0
+#
+#       The difference will be shown as, e.g., "u+7-5" meaning that you are 7
+#       commits ahead of and 5 commits behind the upstream.
+#
 # To submit patches:
 #
 #    *) Read Documentation/SubmittingPatches
#36 lore raw

Re: [PATCH v2 2/2] bash completion: Support "divergence from upstream" warnings in __git_ps1

In reply to Thomas Rast

contrib/completion/git-completion.bash line 50

Quoted 6 lines · Thomas Rast
contrib/completion/git-completion.bash +6 -0
+#       If you would like to see the difference between HEAD and its upstream,
+#       set GIT_PS1_SHOWUPSTREAM to one of the following:
+#           git          use @{upstream}
+#           svn          attempt to DWIM svn upstream for normal and --stdlayout
+#           ref <ref>    unconditionally use <ref>
+#           eval <code>  evaluate <code> which should print the commit to use

This looks somewhat overengineered, although "git" and "svn" are probably useful in real life. I especially wonder if a fixed <ref> is useful at all. Wouldn't the choice of "other" branch always depend on the current branch?

#37 lore raw

Re: [PATCH v2 2/2] bash completion: Support "divergence from upstream" warnings in __git_ps1

Quoted 13 lines · Junio C Hamano

contrib/completion/git-completion.bash line 50

Quoted 6 lines · Thomas Rast
contrib/completion/git-completion.bash +6 -0
+#       If you would like to see the difference between HEAD and its upstream,
+#       set GIT_PS1_SHOWUPSTREAM to one of the following:
+#           git          use @{upstream}
+#           svn          attempt to DWIM svn upstream for normal and --stdlayout
+#           ref <ref>    unconditionally use <ref>
+#           eval <code>  evaluate <code> which should print the commit to use

This looks somewhat overengineered, although "git" and "svn" are probably useful in real life. I especially wonder if a fixed <ref> is useful at all. Wouldn't the choice of "other" branch always depend on the current branch?

You're probably right. I had 'ref' early on to test around, and then made 'eval' to allow for arcane git-svn setups, but now that it seems Andrew has a nice way of matching those, we can also just drop it.

Signature · 2 lines
Thomas Rast
trast@{inf,student}.ethz.ch
#38 lore raw

Re: [PATCH 0/2] bash completion: Support "divergence from upstream" warnings in __git_ps1

In reply to Thomas Rast

Thomas Rast venit, vidit, dixit 10.06.2010 13:47:

Quoted 42 lines

contrib/completion/git-completion.bash __git_ps1 () · line 211

Quoted 11 lines · Andrew Sayers
contrib/completion/git-completion.bash +11 -0
+                                       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

I didn't really like this bit, since rev-list has to format the output only so you can match it. I realized that I too have written code in the past to detect which way this goes (in post-receive hooks), and/or the number of commits as per the request

Quoted 3 lines · John Tapsell

I hate to get all feature-bloat on you...

But could it state the number of commits as well please? :) :)

So here's an additional patch to provide rev-list logic that supports this feature. I have then tweaked your original patch to make use of it a u+7-5 style.

I'm not too picky about the exact style that it turns out to have in the end; I have used my own customized version ever since we started printing a space before the (master *+) dirtiness indicator. However, I think we should try to be as unambiguous and short as possible. Hence I picked the 'u' prefix to avoid ambiguity with the dirtiness indicator.

Andrew Sayers (1):
bash completion: Support "divergence from upstream" warnings in

    __git_ps1

Thomas Rast (1):
rev-list: introduce --count option

Quoted 8 lines
 Documentation/rev-list-options.txt     |    9 +++++
 builtin/rev-list.c                     |   16 +++++++++
 contrib/completion/git-completion.bash |   57 +++++++++++++++++++++++++++++++-
 revision.c                             |    2 +
 revision.h                             |    5 +++
 t/t6007-rev-list-cherry-pick-file.sh   |   29 ++++++++++++++++
 6 files changed, 117 insertions(+), 1 deletions(-)

Checked and tested. After setting DIFF, even the tests pass! But we can probably do without the debug_p line ;)

#39 lore raw
In reply to Andrew Sayers
Quoted 11 lines · Andrew Sayers

I don't see how to actually split out the branch name without calling sed or using one of the disallowed ${} constructs. So I compared the following commands on my PC (2.1 GHz dual-core Athlon) using a repository with 100 empty commits between HEAD and a git-svn-id:

time git log | \
	sed -ne "/^    git-svn-id: / { s/^    git-svn-id: "\
	"$remote_branch\/\([^@]*\).*/\1/p ; q }"
time git log -1 --grep="^git-svn-id: $remote_branch" | \
	sed -ne "s/^    git-svn-id: $remote_branch\/\([^@]*\).*/\1/p"

What disallowed ${} constructs?

First, ${var#pattern}, ${var##pattern}, ${var%pattern} and ${var%%pattern} are POSIX, so you can use it in git shell scripts.

Second, this is _bash_ completion, so you can use also bash-specific expansion, like ${var/pattern/string} or ${var//pattern/string}.

Signature · 3 lines
Jakub Narebski
Poland
ShadeHawk on #git
#40 lore raw
Quoted 2 lines · Jakub Narebski

What disallowed ${} constructs?

My mistake, I misread that part of CodingGuidelines. The following bash-specific implementation takes about 0.013 seconds:

BRANCH=( $( git log --first-parent -1 \
		--grep="^git-svn-id: $remote_branch" ) )

BRANCH=${BRANCH[ ${#BRANCH[@]} - 2 ]}
BRANCH=${BRANCH%@*}
BRANCH=${BRANCH#*$remote_branch/}

Using an array here saves about 0.001 of a second.

- Andrew

#41 lore raw
In reply to Andrew Sayers
Quoted 2 lines · Andrew Sayers

People working in small teams sometimes forget to push their changes, causing general confusion. A gentle reminder in the command prompt should help.

[...]

Quoted 1 line

+# there're unpushed commits, then a '!' will be shown next to

BTW, here's another idea: how about using '>' and '<' to signal being ahead and behind upstream, resp. (and both if they diverged)?

(I think the hard question here is whether it's helpful enough to weigh against the confusion of having it show nothing, because the user hasn't fetched after an update on the remote...)

Signature · 2 lines
Thomas Rast
trast@{inf,student}.ethz.ch