[PATCH 19/19] bash prompt: alternative git prompt without command substitution

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



__git_ps1() prints the branch name, status indicators, etc. to stdout,
therefore it has to be included in $PS1 through a command substitution
to display that information in the prompt.  The configuration is
straightforward, but it imposes the overhead of fork()ing a subshell
for the command substitution.

However, bash has the $PROMPT_COMMAND shell variable, which "if set,
the value is executed as a command prior to issuing each primary
prompt" (quoted from bash man page).  Its value isn't executed in a
subshell but in the context of the "main" shell, hence (non-local)
variables set in invoked shell functions are available when expanding
$PS1.  We can use this facility to avoid that command substitution for
__git_ps1().

So split out the meat of __git_ps1() into the new
__git_prompt_command() function, which stores the branch name & co.
in the $__git_ps1_string variable.  This function, as its name
suggests, should be included in $PROMPT_COMMAND, and $__git_ps1_string
should in turn be included in $PS1 with a bit of a twist to put the
parentheses around it:

   PROMPT_COMMAND=__git_prompt_command
   PS1='[\u@\h \W${__git_ps1_string:+ ($__git_ps1_string)}]\$ '

Turn __git_ps1() into a wrapper around __git_prompt_command() such
that it's functionality remains unaltered, so already configured
prompts won't break.

The whole series speeds up the bash prompt on Windows/MinGW
immensely, in many cases brings it down to around 10ms on my
machine while in powersave mode.  Here are some timing results in
three common scenarios (repeated 10 times, because the after cases
were too fast to measure a single execution accurately with 'time'):

In my home directory, i.e. not in a git repository, before:

    /c/Users/szeder
    $ time for i in {0..9} ; do prompt=$(__git_ps1) ; done

    real    0m0.952s
    user    0m0.214s
    sys     0m0.444s

  After:

    /c/Users/szeder
    $ time for i in {0..9} ; do __git_prompt_command ;
           prompt=${__git_ps1_string:+ ($__git_ps1_string)} ; done

    real    0m0.718s
    user    0m0.136s
    sys     0m0.354s

  After, with discovery across filesystems enabled:

    /c/Users/szeder
    $ time for i in {0..9} ; do __git_prompt_command ;
           prompt=${__git_ps1_string:+ ($__git_ps1_string)} ; done

    real    0m0.078s
    user    0m0.016s
    sys     0m0.062s

At the top of a work tree, before:

    /c/Users/szeder/repo (master)
    $ time for i in {0..9} ; do prompt=$(__git_ps1) ; done

    real    0m2.901s
    user    0m0.391s
    sys     0m1.468s

  After:

    /c/Users/szeder/repo (master)
    $ time for i in {0..9} ; do __git_prompt_command ;
           prompt=${__git_ps1_string:+ ($__git_ps1_string)} ; done

    real    0m0.094s
    user    0m0.047s
    sys     0m0.047s

In a subdirectory, stash indicator enabled, before:

    /c/Users/szeder/repo/subdir (master $)
    $ time for i in {0..9} ; do prompt=$(__git_ps1) ; done

    real    0m4.118s
    user    0m0.468s
    sys     0m2.056s

  After:

    /c/Users/szeder/repo/subdir (master $)
    $ time for i in {0..9} ; do __git_prompt_command ;
           prompt=${__git_ps1_string:+ ($__git_ps1_string)} ; done

    real    0m0.858s
    user    0m0.152s
    sys     0m0.322s

  After, discovery across filesystems enabled:

    /c/Users/szeder/repo/subdir (master $)
    $ time for i in {0..9} ; do __git_prompt_command ;
           prompt=${__git_ps1_string:+ ($__git_ps1_string)} ; done

    real    0m0.109s
    user    0m0.047s
    sys     0m0.063s

Well, that's about 97% improvement.

The performance gain on Linux is smaller, the latter case goes down
from 0.264s to 0.047, but since it was fast enough to begin with I
won't lengthen this commit message with further timing results on
Linux.

Signed-off-by: SZEDER Gábor <szeder@xxxxxxxxxx>
---

We had some discussions recently about putting user-facing functions into
a separate "namespace".  This patch doesn't take that into account, but
once a consensus is reached __git_prompt_command() should be put in that
namespace.

 contrib/completion/git-completion.bash | 25 +++++++++++++++++++++----
 1 file changed, 21 insertions(+), 4 deletions(-)

diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 5ea19018..1c29f3d0 100755
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -29,6 +29,11 @@
 #       are currently in a git repository.  The %s token will be
 #       the name of the current branch.
 #
+#       Alternatively, to make the above Bash prompt a bit faster:
+#               PROMPT_COMMAND=__git_prompt_command
+#               PS1='[\u@\h \W${__git_ps1_string:+ ($__git_ps1_string)}]\$ '
+#               GIT_DISCOVERY_ACROSS_FILESYSTEM=true
+#
 #       In addition, if you set GIT_PS1_SHOWDIRTYSTATE to a nonempty
 #       value, unstaged (*) and staged (+) changes will be shown next
 #       to the branch name.  You can configure this per-repository
@@ -258,11 +263,12 @@ __git_ps1_show_upstream ()
 }
 
 
-# __git_ps1 accepts 0 or 1 arguments (i.e., format string)
-# returns text to add to bash PS1 prompt (includes branch name)
-__git_ps1 ()
+# Stores the text to be added to the bash prompt (branch name, status
+# indicators, etc.) in the $__git_ps1_string variable.
+__git_prompt_command ()
 {
 	local __git_dir=""
+	__git_ps1_string=""
 	__gitdir >/dev/null
 	if [ -z "$__git_dir" ]; then
 		return
@@ -365,7 +371,18 @@ __git_ps1 ()
 	fi
 
 	local f="$w$i$s$u"
-	printf -- "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r$p"
+	__git_ps1_string="$c${b##refs/heads/}${f:+ $f}$r$p"
+}
+
+# __git_ps1 accepts 0 or 1 arguments (i.e., format string)
+# returns text to add to bash PS1 prompt (includes branch name)
+__git_ps1 ()
+{
+	local __git_ps1_string
+	__git_prompt_command
+	if [ -n "$__git_ps1_string" ]; then
+		printf -- "${1:- (%s)}" "$__git_ps1_string"
+	fi
 }
 
 __gitcomp_1 ()
-- 
1.7.10.1.541.gb1be298

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]