git/list[1] front-page[2] threads[3] people[4] search[5] about
 

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

From
SZEDER Gábor <szeder@ira.uka.de>
Date
May 9, 2012, 00:44 UTC
Message-ID
<1336524290-30023-20-git-send-email-szeder@ira.uka.de>
In-Reply-To
<1336524290-30023-1-git-send-email-szeder@ira.uka.de>

__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@ira.uka.de>
---

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
Previous: Junio C HamanoNext: Andrew Sayers
Message 48 of 50 in “Bash prompt speedup”
  1. 00/19 Bash prompt speedupSZEDER Gábor, May 9, 2012
  2. 01/19 tests: move code to run tests under bash into a helper librarySZEDER Gábor, May 9, 2012
  3. 02/19 tests: add tests for the bash prompt functions in the completion scriptSZEDER Gábor, May 9, 2012
  4. Johannes SixtMay 9, 2012
  5. Junio C HamanoMay 9, 2012
  6. Johannes SixtMay 10, 2012
  7. Junio C HamanoMay 9, 2012
  8. SZEDER GáborMay 9, 2012
  9. 03/19 completion: use __gitdir() in _git_log()SZEDER Gábor, May 9, 2012
  10. Junio C HamanoMay 9, 2012
  11. SZEDER GáborMay 9, 2012
  12. 04/19 completion: respect $GIT_DIRSZEDER Gábor, May 9, 2012
  13. Johannes SixtMay 9, 2012
  14. Junio C HamanoMay 9, 2012
  15. 05/19 bash prompt: don't show the prompt when .git/HEAD is unreadableSZEDER Gábor, May 9, 2012
  16. Junio C HamanoMay 9, 2012
  17. SZEDER GáborMay 9, 2012
  18. 06/19 bash prompt: return early from __git_ps1() when not in a git repositorySZEDER Gábor, May 9, 2012
  19. 07/19 completion: make __gitdir() store repository path in $__git_dirSZEDER Gábor, May 9, 2012
  20. Junio C HamanoMay 9, 2012
  21. 08/19 completion: use $__git_dir instead of $(__gitdir)SZEDER Gábor, May 9, 2012
  22. Junio C HamanoMay 9, 2012
  23. SZEDER GáborMay 9, 2012
  24. Junio C HamanoMay 9, 2012
  25. SZEDER GáborMay 9, 2012
  26. 09/19 completion: platform-specific helper function to get physical pathSZEDER Gábor, May 9, 2012
  27. Johannes SixtMay 9, 2012
  28. 10/19 completion: use bash builtins to search for repositorySZEDER Gábor, May 9, 2012
  29. Junio C HamanoMay 9, 2012
  30. SZEDER GáborMay 9, 2012
  31. Junio C HamanoMay 9, 2012
  32. 11/19 bash prompt: use bash builtins to find out current branchSZEDER Gábor, May 9, 2012
  33. Junio C HamanoMay 9, 2012
  34. SZEDER GáborMay 9, 2012
  35. Junio C HamanoMay 9, 2012
  36. SZEDER GáborMay 9, 2012
  37. Junio C HamanoMay 9, 2012
  38. 12/19 bash prompt: use bash builtins to check whether inside git dirSZEDER Gábor, May 9, 2012
  39. Johannes SixtMay 9, 2012
  40. Junio C HamanoMay 9, 2012
  41. 13/19 bash prompt: check whether inside the worktree only when necessarySZEDER Gábor, May 9, 2012
  42. 14/19 bash prompt: use bash builtins to find out current branch during rebaseSZEDER Gábor, May 9, 2012
  43. 15/19 bash prompt: use bash builtins to get detached HEAD abbrev. object nameSZEDER Gábor, May 9, 2012
  44. 16/19 bash prompt: display stash and upstream state even inside the repositorySZEDER Gábor, May 9, 2012
  45. 17/19 bash prompt: use bash builtins to check stash stateSZEDER Gábor, May 9, 2012
  46. 18/19 bash prompt: avoid command substitution when checking for untracked filesSZEDER Gábor, May 9, 2012
  47. Junio C HamanoMay 9, 2012
  48. 19/19 bash prompt: alternative git prompt without command substitutionSZEDER Gábor, May 9, 2012
  49. Andrew SayersMay 9, 2012
  50. SZEDER GáborMay 9, 2012

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.