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

[PATCH 10/19] completion: use bash builtins to search for repository

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

When not at the top of the work tree or when no path is specified by 'git --git-dir=...' or $GIT_DIR, the __gitdir() helper function finds out the physical path to the '.git' directory by running the '$(git rev-parse --git-dir)' command substitution. fork()ing a subshell and fork()+exec()ing a git command take some time; during the same amount of time we can stat a lot of files and directories using bash builtins, likely finding the '.git' directory faster.

This patch extends __gitdir() to search for a '.git' directory using only bash builtins. Like the existing check for '.git' in the current working directory or the previously added check for $GIT_DIR, this search is not that thorough either, as it doesn't check whether the found '.git' directory or the directory pointed to by a gitfile is a valid '.git' repository.

Since git doesn't search for a .git directory beyond filesystem boundaries and beyond paths specified in $GIT_CEILING_DIRECTORIES, the search in __gitdir() should not do that either. However, bash doesn't provide builtins to check that two paths are on the same filesystem, so we can't limit the search cheaply to one filesystem. Therefore, __gitdir() will only use bash builtins when neither of these limits are active, i.e. $GIT_DISCOVERY_ACROSS_FILESYSTEM is set and $GIT_CEILING_DIRECTORIES is empty, otherwise it will fall back on executing 'git rev-parse'. $GIT_CEILING_DIRECTORIES is empty by default, but users have to set $GIT_DISCOVERY_ACROSS_FILESYSTEM explicitly to enable this optimisation, when they find its consequences acceptable.

Of course, stat()ing in C is much faster than in bash, so there is a point when bash builtins will be slower than '$(git rev-parse --git-dir)' despite all the fork()s+exec() overhead. On MinGW this overhead is considerable, and the builtins version is faster even at a depth of 50 directories. On Linux the builtins version is only faster for paths less than 10 directories deeper than the toplevel; with my usage patterns that covers 99.9% of the prompts displayed.

Signed-off-by: SZEDER Gábor <szeder@ira.uka.de>
---
 contrib/completion/git-completion.bash | 30 ++++++++++++
 t/t9903-bash-prompt.sh                 | 83 ++++++++++++++++++++++++++++++++++
 2 files changed, 113 insertions(+)
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index bd7d39e3..dd69e56e 100755
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -89,13 +89,43 @@ fi
 __gitdir ()
 {
 	if [ -z "${1-}" ]; then
+		local gitfile
+
 		if [ -n "${__git_dir-}" ]; then
 			:
 		elif [ -n "${GIT_DIR-}" ]; then
 			test -d "${GIT_DIR-}" || return 1
 			__git_dir="$GIT_DIR"
+		elif [ -f .git ]; then
+			read gitfile <.git 2>/dev/null || return 1
+			__git_dir="${gitfile#gitdir: }"
 		elif [ -d .git ]; then
 			__git_dir=.git
+		elif [ -d refs ] && [ -r HEAD ] && \
+				[ -d "${GIT_OBJECT_DIRECTORY:-objects}" ]; then
+			__git_dir=.
+		elif [ -n "${GIT_DISCOVERY_ACROSS_FILESYSTEM-}" -a \
+				-z "${GIT_CEILING_DIRECTORIES-}" ]; then
+			local p
+			__git_pwd_p p
+			while true; do
+				p="${p%/*}"
+				if [ -f "$p/.git" ]; then
+					read gitfile <"$p/.git" 2>/dev/null || return 1
+					__git_dir="${gitfile#gitdir: }"
+					break
+				elif [ -d "$p/.git" ]; then
+					__git_dir="$p/.git"
+					break
+				elif [ -d "$p/refs" ] && [ -r "$p/HEAD" ] && \
+						[ -d "${GIT_OBJECT_DIRECTORY:-$p/objects}" ]; then
+					__git_dir="${p:-/}"
+					break
+				fi
+				if [ -z "$p" ]; then
+					return 1
+				fi
+			done
 		else
 			__git_dir="$(git rev-parse --git-dir 2>/dev/null)" || return 1
 		fi
diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh
index 3d722b25..ffa22d39 100755
--- a/t/t9903-bash-prompt.sh
+++ b/t/t9903-bash-prompt.sh
@@ -105,6 +105,18 @@ test_expect_success 'gitdir - .git directory in parent' '
 	test_cmp expected "$actual_var"
 '
 
+test_expect_success 'gitdir - .git directory in parent - with builtins' '
+	echo "$TRASH_DIRECTORY/.git" > expected &&
+	(
+		GIT_DISCOVERY_ACROSS_FILESYSTEM=true &&
+		cd subdir/subsubdir &&
+		__gitdir > "$actual" &&
+		echo "$__git_dir" > "$actual_var"
+	) &&
+	test_cmp expected "$actual" &&
+	test_cmp expected "$actual_var"
+'
+
 test_expect_success 'gitdir - cwd is a .git directory' '
 	echo "." > expected &&
 	(
@@ -116,6 +128,20 @@ test_expect_success 'gitdir - cwd is a .git directory' '
 	test_cmp expected "$actual_var"
 '
 
+test_expect_success 'gitdir - cwd is a .git directory - GIT_OBJECT_DIRECTORY' '
+	echo "." > expected &&
+	mv .git/objects _objects &&
+	test_when_finished "mv _objects .git/objects" &&
+	(
+		GIT_OBJECT_DIRECTORY="$TRASH_DIRECTORY/_objects" &&
+		cd .git &&
+		__gitdir > "$actual" &&
+		echo "$__git_dir" > "$actual_var"
+	) &&
+	test_cmp expected "$actual" &&
+	test_cmp expected "$actual_var"
+'
+
 test_expect_success 'gitdir - parent is a .git directory' '
 	echo "$TRASH_DIRECTORY/.git" > expected &&
 	(
@@ -127,6 +153,33 @@ test_expect_success 'gitdir - parent is a .git directory' '
 	test_cmp expected "$actual_var"
 '
 
+test_expect_success 'gitdir - parent is a .git directory - with builtins' '
+	echo "$TRASH_DIRECTORY/.git" > expected &&
+	(
+		GIT_DISCOVERY_ACROSS_FILESYSTEM=true &&
+		cd .git/refs/heads &&
+		__gitdir > "$actual" &&
+		echo "$__git_dir" > "$actual_var"
+	) &&
+	test_cmp expected "$actual" &&
+	test_cmp expected "$actual_var"
+'
+
+test_expect_success 'gitdir - parent is a .git directory - with builtins and GIT_OBJECT_DIRECTORY' '
+	echo "$TRASH_DIRECTORY/.git" > expected &&
+	mv .git/objects _objects &&
+	test_when_finished "mv _objects .git/objects" &&
+	(
+		GIT_OBJECT_DIRECTORY="$TRASH_DIRECTORY/_objects" &&
+		GIT_DISCOVERY_ACROSS_FILESYSTEM=true &&
+		cd .git/refs/heads &&
+		__gitdir > "$actual" &&
+		echo "$__git_dir" > "$actual_var"
+	) &&
+	test_cmp expected "$actual" &&
+	test_cmp expected "$actual_var"
+'
+
 test_expect_success 'gitdir - $GIT_DIR set while .git directory in cwd' '
 	echo "$TRASH_DIRECTORY/otherrepo/.git" > expected &&
 	(
@@ -187,6 +240,20 @@ test_expect_success 'gitdir - gitfile in parent' '
 	test_cmp expected "$actual_var"
 '
 
+test_expect_success 'gitdir - gitfile in parent - with builtins' '
+	echo "$TRASH_DIRECTORY/otherrepo/.git" > expected &&
+	echo "gitdir: $TRASH_DIRECTORY/otherrepo/.git" > subdir/.git &&
+	test_when_finished "rm -f subdir/.git" &&
+	(
+		GIT_DISCOVERY_ACROSS_FILESYSTEM=true &&
+		cd subdir/subsubdir &&
+		__gitdir > "$actual" &&
+		echo "$__git_dir" > "$actual_var"
+	) &&
+	test_cmp expected "$actual" &&
+	test_cmp expected "$actual_var"
+'
+
 test_expect_success SYMLINKS 'gitdir - resulting path avoids symlinks' '
 	echo "$TRASH_DIRECTORY/otherrepo/.git" > expected &&
 	mkdir otherrepo/dir &&
@@ -202,6 +269,22 @@ test_expect_success SYMLINKS 'gitdir - resulting path avoids symlinks' '
 	test_cmp expected "$actual_var"
 '
 
+test_expect_success SYMLINKS 'gitdir - resulting path avoids symlinks - with builtins' '
+	echo "$TRASH_DIRECTORY/otherrepo/.git" >expected &&
+	mkdir otherrepo/dir &&
+	test_when_finished "rm -rf otherrepo/dir" &&
+	ln -s otherrepo/dir link &&
+	test_when_finished "rm -f link" &&
+	(
+		GIT_DISCOVERY_ACROSS_FILESYSTEM=true &&
+		cd link &&
+		__gitdir > "$actual" &&
+		echo "$__git_dir" > "$actual_var"
+	) &&
+	test_cmp expected "$actual" &&
+	test_cmp expected "$actual_var"
+'
+
 test_expect_success 'gitdir - not a git repository' '
 	(
 		cd subdir/subsubdir &&
-- 
1.7.10.1.541.gb1be298
Previous: Johannes SixtNext: Junio C Hamano
Message 28 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.