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

[PATCH 21/21] completion: cache the path to the repository

From
SZEDER Gábor <szeder@ira.uka.de>
Date
Feb 25, 2016, 22:50 UTC
Message-ID
<1456440650-32623-22-git-send-email-szeder@ira.uka.de>
In-Reply-To
<1456440650-32623-1-git-send-email-szeder@ira.uka.de>

After the previous changes in this series there are only a handful of $(__gitdir) command substitutions left in the completion script, but there is still a bit of room for improvements:

  1. The command substitution involves the forking of a subshell,
     which has considerable overhead on some platforms.
  2. There are a few cases, where this command substitution is
     executed more than once during a single completion, which means
     multiple subshells and possibly multiple 'git rev-parse'
     executions.  __gitdir() is invoked twice while completing refs
     for e.g. 'git log', 'git rebase', 'gitk', or while completing
     remote refs for 'git fetch' or 'git push'.

Both of these points can be addressed by using the __git_find_repo_path() helper function introduced in the previous commit:

  1. __git_find_repo_path() stores the path to the repository in a
     variable instead of printing it, so the command substitution
     around the function can be avoided.  Or rather: the command
     substitution should be avoided to make the new value of the
     variable set inside the function visible to the callers.
     (Yes, there is now a command substitution inside
     __git_find_repo_path() around each 'git rev-parse', but that's
     executed only if necessary, and only once per completion, see
     point 2. below.)
  2. $__git_repo_path, the variable holding the path to the
     repository, is declared local in the toplevel completion
     functions __git_main() and __gitk_main().  Thus, once set, the
     path is visible in all completion functions, including all
     subsequent calls to __git_find_repo_path(), meaning that they
     wouldn't have to re-discover the path to the repository.

So call __git_find_repo_path() and use $__git_repo_path instead of the $(__gitdir) command substitution to access paths in the .git directory. Turn tests checking __gitdir()'s repository discovery into tests of __git_find_repo_path() such that only the tested function changes but the expected results don't, ensuring that repo discovery keeps working as it did before.

As __gitdir() is not used anymore in the completion script, mark it as deprecated and direct users' attention to __git_find_repo_path() and $__git_repo_path. Yet keep four __gitdir() tests to ensure that it handles success and failure of __git_find_repo_path() and that it still handles its optional remote argument.

Signed-off-by: SZEDER Gábor <szeder@ira.uka.de>
---
 contrib/completion/git-completion.bash |  46 ++++++----
 t/t9902-completion.sh                  | 155 +++++++++++++++++++++------------
 2 files changed, 126 insertions(+), 75 deletions(-)
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 69000a33bcfa..7300a2b64bab 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -39,6 +39,11 @@ esac
 # variable.
 __git_find_repo_path ()
 {
+	if [ -n "$__git_repo_path" ]; then
+		# we already know where it is
+		return
+	fi
+
 	if [ -n "${__git_C_args-}" ]; then
 		__git_repo_path="$(git "${__git_C_args[@]}" \
 			${__git_dir:+--git-dir="$__git_dir"} \
@@ -56,6 +61,7 @@ __git_find_repo_path ()
 	fi
 }
 
+# Deprecated: use __git_find_repo_path() and $__git_repo_path instead
 # __gitdir accepts 0 or 1 arguments (i.e., location)
 # returns location of .git repo
 __gitdir ()
@@ -350,10 +356,13 @@ __git_tags ()
 #    'git checkout's tracking DWIMery (optional; ignored, if set but empty).
 __git_refs ()
 {
-	local i hash dir="$(__gitdir)" track="${2-}"
+	local i hash dir track="${2-}"
 	local from_local=y remote="${1-}" named_remote=n
 	local format refs
 
+	__git_find_repo_path
+	dir="$__git_repo_path"
+
 	if [ -z "$dir" ] && [ -z "$remote" ]; then
 		return
 	fi
@@ -460,8 +469,8 @@ __git_refs_remotes ()
 
 __git_remotes ()
 {
-	local d="$(__gitdir)"
-	test -d "$d/remotes" && ls -1 "$d/remotes"
+	__git_find_repo_path
+	test -d "$__git_repo_path/remotes" && ls -1 "$__git_repo_path/remotes"
 	__git remote
 }
 
@@ -915,8 +924,8 @@ __git_whitespacelist="nowarn warn error error-all fix"
 
 _git_am ()
 {
-	local dir="$(__gitdir)"
-	if [ -d "$dir"/rebase-apply ]; then
+	__git_find_repo_path
+	if [ -d "$__git_repo_path"/rebase-apply ]; then
 		__gitcomp "--skip --continue --resolved --abort"
 		return
 	fi
@@ -999,7 +1008,8 @@ _git_bisect ()
 	local subcommands="start bad good skip reset visualize replay log run"
 	local subcommand="$(__git_find_on_cmdline "$subcommands")"
 	if [ -z "$subcommand" ]; then
-		if [ -f "$(__gitdir)"/BISECT_START ]; then
+		__git_find_repo_path
+		if [ -f "$__git_repo_path"/BISECT_START ]; then
 			__gitcomp "$subcommands"
 		else
 			__gitcomp "replay start"
@@ -1104,8 +1114,8 @@ _git_cherry ()
 
 _git_cherry_pick ()
 {
-	local dir="$(__gitdir)"
-	if [ -f "$dir"/CHERRY_PICK_HEAD ]; then
+	__git_find_repo_path
+	if [ -f "$__git_repo_path"/CHERRY_PICK_HEAD ]; then
 		__gitcomp "--continue --quit --abort"
 		return
 	fi
@@ -1486,10 +1496,10 @@ __git_log_date_formats="relative iso8601 rfc2822 short local default raw"
 _git_log ()
 {
 	__git_has_doubledash && return
+	__git_find_repo_path
 
-	local g="$(__gitdir)"
 	local merge=""
-	if [ -f "$g/MERGE_HEAD" ]; then
+	if [ -f "$__git_repo_path/MERGE_HEAD" ]; then
 		merge="--merge"
 	fi
 	case "$cur" in
@@ -1727,11 +1737,12 @@ _git_push ()
 
 _git_rebase ()
 {
-	local dir="$(__gitdir)"
-	if [ -f "$dir"/rebase-merge/interactive ]; then
+	__git_find_repo_path
+	if [ -f "$__git_repo_path"/rebase-merge/interactive ]; then
 		__gitcomp "--continue --skip --abort --edit-todo"
 		return
-	elif [ -d "$dir"/rebase-apply ] || [ -d "$dir"/rebase-merge ]; then
+	elif [ -d "$__git_repo_path"/rebase-apply ] || \
+	     [ -d "$__git_repo_path"/rebase-merge ]; then
 		__gitcomp "--continue --skip --abort"
 		return
 	fi
@@ -2355,8 +2366,8 @@ _git_reset ()
 
 _git_revert ()
 {
-	local dir="$(__gitdir)"
-	if [ -f "$dir"/REVERT_HEAD ]; then
+	__git_find_repo_path
+	if [ -f "$__git_repo_path"/REVERT_HEAD ]; then
 		__gitcomp "--continue --quit --abort"
 		return
 	fi
@@ -2720,9 +2731,10 @@ __gitk_main ()
 	__git_has_doubledash && return
 
 	local __git_repo_path
-	local g="$(__gitdir)"
+	__git_find_repo_path
+
 	local merge=""
-	if [ -f "$g/MERGE_HEAD" ]; then
+	if [ -f "$__git_repo_path/MERGE_HEAD" ]; then
 		merge="--merge"
 	fi
 	case "$cur" in
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index d44e72066328..458850641635 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -131,213 +131,216 @@ else
 	ROOT="$(pwd)"
 fi
 
-test_expect_success 'setup for __gitdir tests' '
+test_expect_success 'setup for __git_find_repo_path/__gitdir tests' '
 	mkdir -p subdir/subsubdir &&
 	git init otherrepo
 '
 
-test_expect_success '__gitdir - from command line (through $__git_dir)' '
+test_expect_success '__git_find_repo_path - from command line (through $__git_dir)' '
 	echo "$ROOT/otherrepo/.git" >expected &&
 	(
 		__git_dir="$ROOT/otherrepo/.git" &&
-		__gitdir >"$actual"
-	) &&
-	test_cmp expected "$actual"
-'
-
-test_expect_success '__gitdir - repo as argument' '
-	echo "otherrepo/.git" >expected &&
-	(
-		__gitdir "otherrepo" >"$actual"
-	) &&
-	test_cmp expected "$actual"
-'
-
-test_expect_success '__gitdir - remote as argument' '
-	echo "remote" >expected &&
-	(
-		__gitdir "remote" >"$actual"
+		__git_find_repo_path &&
+		echo "$__git_repo_path" >"$actual"
 	) &&
 	test_cmp expected "$actual"
 '
 
-test_expect_success '__gitdir - .git directory in cwd' '
+test_expect_success '__git_find_repo_path - .git directory in cwd' '
 	echo ".git" >expected &&
 	(
-		__gitdir >"$actual"
+		__git_find_repo_path &&
+		echo "$__git_repo_path" >"$actual"
 	) &&
 	test_cmp expected "$actual"
 '
 
-test_expect_success '__gitdir - .git directory in parent' '
+test_expect_success '__git_find_repo_path - .git directory in parent' '
 	echo "$ROOT/.git" >expected &&
 	(
 		cd subdir/subsubdir &&
-		__gitdir >"$actual"
+		__git_find_repo_path &&
+		echo "$__git_repo_path" >"$actual"
 	) &&
 	test_cmp expected "$actual"
 '
 
-test_expect_success '__gitdir - cwd is a .git directory' '
+test_expect_success '__git_find_repo_path - cwd is a .git directory' '
 	echo "." >expected &&
 	(
 		cd .git &&
-		__gitdir >"$actual"
+		__git_find_repo_path &&
+		echo "$__git_repo_path" >"$actual"
 	) &&
 	test_cmp expected "$actual"
 '
 
-test_expect_success '__gitdir - parent is a .git directory' '
+test_expect_success '__git_find_repo_path - parent is a .git directory' '
 	echo "$ROOT/.git" >expected &&
 	(
 		cd .git/refs/heads &&
-		__gitdir >"$actual"
+		__git_find_repo_path &&
+		echo "$__git_repo_path" >"$actual"
 	) &&
 	test_cmp expected "$actual"
 '
 
-test_expect_success '__gitdir - $GIT_DIR set while .git directory in cwd' '
+test_expect_success '__git_find_repo_path - $GIT_DIR set while .git directory in cwd' '
 	echo "$ROOT/otherrepo/.git" >expected &&
 	(
 		GIT_DIR="$ROOT/otherrepo/.git" &&
 		export GIT_DIR &&
-		__gitdir >"$actual"
+		__git_find_repo_path &&
+		echo "$__git_repo_path" >"$actual"
 	) &&
 	test_cmp expected "$actual"
 '
 
-test_expect_success '__gitdir - $GIT_DIR set while .git directory in parent' '
+test_expect_success '__git_find_repo_path - $GIT_DIR set while .git directory in parent' '
 	echo "$ROOT/otherrepo/.git" >expected &&
 	(
 		GIT_DIR="$ROOT/otherrepo/.git" &&
 		export GIT_DIR &&
 		cd subdir &&
-		__gitdir >"$actual"
+		__git_find_repo_path &&
+		echo "$__git_repo_path" >"$actual"
 	) &&
 	test_cmp expected "$actual"
 '
 
-test_expect_success '__gitdir - from command line while "git -C"' '
+test_expect_success '__git_find_repo_path - from command line while "git -C"' '
 	echo "$ROOT/.git" >expected &&
 	(
 		__git_dir="$ROOT/.git" &&
 		__git_C_args=(-C otherrepo) &&
-		__gitdir >"$actual"
+		__git_find_repo_path &&
+		echo "$__git_repo_path" >"$actual"
 	) &&
 	test_cmp expected "$actual"
 '
 
-test_expect_success '__gitdir - relative dir from command line and "git -C"' '
+test_expect_success '__git_find_repo_path - relative dir from command line and "git -C"' '
 	echo "$ROOT/otherrepo/.git" >expected &&
 	(
 		cd subdir &&
 		__git_dir="otherrepo/.git" &&
 		__git_C_args=(-C ..) &&
-		__gitdir >"$actual"
+		__git_find_repo_path &&
+		echo "$__git_repo_path" >"$actual"
 	) &&
 	test_cmp expected "$actual"
 '
 
-test_expect_success '__gitdir - $GIT_DIR set while "git -C"' '
+test_expect_success '__git_find_repo_path - $GIT_DIR set while "git -C"' '
 	echo "$ROOT/.git" >expected &&
 	(
 		GIT_DIR="$ROOT/.git" &&
 		export GIT_DIR &&
 		__git_C_args=(-C otherrepo) &&
-		__gitdir >"$actual"
+		__git_find_repo_path &&
+		echo "$__git_repo_path" >"$actual"
 	) &&
 	test_cmp expected "$actual"
 '
 
-test_expect_success '__gitdir - relative dir in $GIT_DIR and "git -C"' '
+test_expect_success '__git_find_repo_path - relative dir in $GIT_DIR and "git -C"' '
 	echo "$ROOT/otherrepo/.git" >expected &&
 	(
 		cd subdir &&
 		GIT_DIR="otherrepo/.git" &&
 		export GIT_DIR &&
 		__git_C_args=(-C ..) &&
-		__gitdir >"$actual"
+		__git_find_repo_path &&
+		echo "$__git_repo_path" >"$actual"
 	) &&
 	test_cmp expected "$actual"
 '
 
-test_expect_success '__gitdir - "git -C" while .git directory in cwd' '
+test_expect_success '__git_find_repo_path - "git -C" while .git directory in cwd' '
 	echo "$ROOT/otherrepo/.git" >expected &&
 	(
 		__git_C_args=(-C otherrepo) &&
-		__gitdir >"$actual"
+		__git_find_repo_path &&
+		echo "$__git_repo_path" >"$actual"
 	) &&
 	test_cmp expected "$actual"
 '
 
-test_expect_success '__gitdir - "git -C" while cwd is a .git directory' '
+test_expect_success '__git_find_repo_path - "git -C" while cwd is a .git directory' '
 	echo "$ROOT/otherrepo/.git" >expected &&
 	(
 		cd .git &&
 		__git_C_args=(-C .. -C otherrepo) &&
-		__gitdir >"$actual"
+		__git_find_repo_path &&
+		echo "$__git_repo_path" >"$actual"
 	) &&
 	test_cmp expected "$actual"
 '
 
-test_expect_success '__gitdir - "git -C" while .git directory in parent' '
+test_expect_success '__git_find_repo_path - "git -C" while .git directory in parent' '
 	echo "$ROOT/otherrepo/.git" >expected &&
 	(
 		cd subdir &&
 		__git_C_args=(-C .. -C otherrepo) &&
-		__gitdir >"$actual"
+		__git_find_repo_path &&
+		echo "$__git_repo_path" >"$actual"
 	) &&
 	test_cmp expected "$actual"
 '
 
-test_expect_success '__gitdir - non-existing path in "git -C"' '
+test_expect_success '__git_find_repo_path - non-existing path in "git -C"' '
 	(
 		__git_C_args=(-C non-existing) &&
-		test_must_fail __gitdir >"$actual"
+		test_must_fail __git_find_repo_path &&
+		printf "$__git_repo_path" >"$actual"
 	) &&
 	test_must_be_empty "$actual"
 '
 
-test_expect_success '__gitdir - non-existing path in $__git_dir' '
+test_expect_success '__git_find_repo_path - non-existing path in $__git_dir' '
 	(
 		__git_dir="non-existing" &&
-		test_must_fail __gitdir >"$actual"
+		test_must_fail __git_find_repo_path &&
+		printf "$__git_repo_path" >"$actual"
 	) &&
 	test_must_be_empty "$actual"
 '
 
-test_expect_success '__gitdir - non-existing $GIT_DIR' '
+test_expect_success '__git_find_repo_path - non-existing $GIT_DIR' '
 	(
 		GIT_DIR="$ROOT/non-existing" &&
 		export GIT_DIR &&
-		test_must_fail __gitdir >"$actual"
+		test_must_fail __git_find_repo_path &&
+		printf "$__git_repo_path" >"$actual"
 	) &&
 	test_must_be_empty "$actual"
 '
 
-test_expect_success '__gitdir - gitfile in cwd' '
+test_expect_success '__git_find_repo_path - gitfile in cwd' '
 	echo "$ROOT/otherrepo/.git" >expected &&
 	echo "gitdir: $ROOT/otherrepo/.git" >subdir/.git &&
 	test_when_finished "rm -f subdir/.git" &&
 	(
 		cd subdir &&
-		__gitdir >"$actual"
+		__git_find_repo_path &&
+		echo "$__git_repo_path" >"$actual"
 	) &&
 	test_cmp expected "$actual"
 '
 
-test_expect_success '__gitdir - gitfile in parent' '
+test_expect_success '__git_find_repo_path - gitfile in parent' '
 	echo "$ROOT/otherrepo/.git" >expected &&
 	echo "gitdir: $ROOT/otherrepo/.git" >subdir/.git &&
 	test_when_finished "rm -f subdir/.git" &&
 	(
 		cd subdir/subsubdir &&
-		__gitdir >"$actual"
+		__git_find_repo_path &&
+		echo "$__git_repo_path" >"$actual"
 	) &&
 	test_cmp expected "$actual"
 '
 
-test_expect_success SYMLINKS '__gitdir - resulting path avoids symlinks' '
+test_expect_success SYMLINKS '__git_find_repo_path - resulting path avoids symlinks' '
 	echo "$ROOT/otherrepo/.git" >expected &&
 	mkdir otherrepo/dir &&
 	test_when_finished "rm -rf otherrepo/dir" &&
@@ -345,21 +348,57 @@ test_expect_success SYMLINKS '__gitdir - resulting path avoids symlinks' '
 	test_when_finished "rm -f link" &&
 	(
 		cd link &&
-		__gitdir >"$actual"
+		__git_find_repo_path &&
+		echo "$__git_repo_path" >"$actual"
 	) &&
 	test_cmp expected "$actual"
 '
 
-test_expect_success '__gitdir - not a git repository' '
+test_expect_success '__git_find_repo_path - not a git repository' '
 	(
 		cd subdir/subsubdir &&
 		GIT_CEILING_DIRECTORIES="$ROOT" &&
 		export GIT_CEILING_DIRECTORIES &&
+		test_must_fail __git_find_repo_path &&
+		printf "$__git_repo_path" >"$actual"
+	) &&
+	test_must_be_empty "$actual"
+'
+
+test_expect_success '__gitdir - finds repo' '
+	echo "$ROOT/.git" >expected &&
+	(
+		cd subdir/subsubdir &&
+		__gitdir >"$actual"
+	) &&
+	test_cmp expected "$actual"
+'
+
+
+test_expect_success '__gitdir - returns error when cant find repo' '
+	(
+		__git_dir="non-existing" &&
 		test_must_fail __gitdir >"$actual"
 	) &&
 	test_must_be_empty "$actual"
 '
 
+test_expect_success '__gitdir - repo as argument' '
+	echo "otherrepo/.git" >expected &&
+	(
+		__gitdir "otherrepo" >"$actual"
+	) &&
+	test_cmp expected "$actual"
+'
+
+test_expect_success '__gitdir - remote as argument' '
+	echo "remote" >expected &&
+	(
+		__gitdir "remote" >"$actual"
+	) &&
+	test_cmp expected "$actual"
+'
+
 test_expect_success '__gitcomp - trailing space - options' '
 	test_gitcomp "--re" "--dry-run --reuse-message= --reedit-message=
 		--reset-author" <<-EOF
-- 
2.7.2.410.g92cb358
Previous: SZEDER Gábor
Message 25 of 25 in “completion: __gitdir()-related improvements”
  1. 00/21 completion: __gitdir()-related improvementsSZEDER Gábor, Feb 25, 2016
  2. 01/21 completion: improve __git_refs()'s in-code documentationSZEDER Gábor, Feb 25, 2016
  3. 02/21 completion tests: don't add test cruft to the test repositorySZEDER Gábor, Feb 25, 2016
  4. 03/21 completion tests: make the $cur variable local to the test helper functionsSZEDER Gábor, Feb 25, 2016
  5. 04/21 completion tests: consolidate getting path of current working directorySZEDER Gábor, Feb 25, 2016
  6. 05/21 completion tests: check __gitdir()'s output in the error casesSZEDER Gábor, Feb 25, 2016
  7. 06/21 completion tests: add tests for the __git_refs() helper functionSZEDER Gábor, Feb 25, 2016
  8. 07/21 completion: ensure that the repository path given on the command line existsSZEDER Gábor, Feb 25, 2016
  9. 08/21 completion: fix most spots not respecting 'git --git-dir=<path>'SZEDER Gábor, Feb 25, 2016
  10. 09/21 completion: respect 'git --git-dir=<path>' when listing remote refsSZEDER Gábor, Feb 25, 2016
  11. 10/21 completion: list refs from remote when remote's name matches a directorySZEDER Gábor, Feb 25, 2016
  12. 11/21 completion: don't list 'HEAD' when trying refs completion outside of a repoSZEDER Gábor, Feb 25, 2016
  13. 12/21 completion: list short refs from a remote given as a URLSZEDER Gábor, Feb 25, 2016
  14. 13/21 rev-parse: add '--absolute-git-dir' optionSZEDER Gábor, Feb 25, 2016
  15. Mike RappazzoApr 26, 2016
  16. Jeff KingApr 26, 2016
  17. SZEDER GáborMay 18, 2016
  18. 14/21 completion: don't offer commands when 'git --opt' needs an argumentSZEDER Gábor, Feb 25, 2016
  19. 15/21 completion: fix completion after 'git -C <path>'SZEDER Gábor, Feb 25, 2016
  20. 16/21 completion: respect 'git -C <path>'SZEDER Gábor, Feb 25, 2016
  21. 17/21 completion: don't use __gitdir() for git commandsSZEDER Gábor, Feb 25, 2016
  22. 18/21 completion: consolidate silencing errors from git commandsSZEDER Gábor, Feb 25, 2016
  23. 19/21 completion: don't guard git executions with __gitdir()SZEDER Gábor, Feb 25, 2016
  24. 20/21 completion: extract repository discovery from __gitdir()SZEDER Gábor, Feb 25, 2016
  25. 21/21 completion: cache the path to the repositorySZEDER Gábor, Feb 25, 2016

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.