completion: don't use __gitdir() for git commands

Several completion functions contain the following pattern to run git commands respecting the path to the repository specified on the command line: git --git-dir="$(__gitdir)" <cmd> <options> This imposes the overhead of fork()ing a subshell for the command substitution and potentially fork()+exec()ing 'git rev-parse' inside __gitdir(). Now, if neither '--gitdir=<path>' nor '-C <path>' options are specified on the command line, then those git commands are perfectly capable to discover the repository on their own. If either one or both of those options are specified on the command line, then, again, the git commands could discover the repository, if we pass them all of those options from the command line. This means we don't have to run __gitdir() at all for git commands and can spare its fork()+exec() overhead. Use Bash parameter expansions to check the $__git_dir variable and $__git_C_args array and to assemble the appropriate '--git-dir=<path>' and '-C <path>' options if either one or both are present on the command line. These parameter expansions are, however, rather long, so instead of changing all git executions and make already long lines even longer, encapsulate running git with '--git-dir=<path> -C <path>' options into the new __git() wrapper function. Furthermore, this wrapper function will also enable us to silence error messages from git commands uniformly in one place in a later commit. There's one tricky case, though: in __git_refs() local refs are listed with 'git for-each-ref', where "local" is not necessarily the repository we are currently in, but it might mean a remote repository in the filesystem (e.g. listing refs for 'git fetch /some/other/repo <TAB>'). Use one-shot variable assignment to override $__git_dir with the path of the repository where the refs should come from. Although one-shot variable assignments in front of shell functions are to be avoided in our scripts in general, in the Bash completion script we can do that safely. Signed-off-by: SZEDER Gábor <szeder.dev@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

SZEDER Gábor committed Feb 3, 2017 at 03:48 UTC 1cd23e9e0568eca4f6d2d3c6228942acd937da11
1 file changed +31 -29
contrib/completion/git-completion.bash
+31 -29
@@ -61,6 +61,14 @@ __gitdir ()
61 fi
62 }
63
64 +# Runs git with all the options given as argument, respecting any
65 +# '--git-dir=<path>' and '-C <path>' options present on the command line
66 +__git ()
67 +{
68 + git ${__git_C_args:+"${__git_C_args[@]}"} \
69 + ${__git_dir:+--git-dir="$__git_dir"} "$@"
70 +}
71 +
72 # The following function is based on code from:
73 #
74 # bash_completion - programmable completion functions for bash 3.2+
@@ -287,13 +295,11 @@ __gitcomp_file ()
295 # argument, and using the options specified in the second argument.
296 __git_ls_files_helper ()
297 {
290 - local dir="$(__gitdir)"
291 -
298 if [ "$2" == "--committable" ]; then
293 - git ${__git_C_args:+"${__git_C_args[@]}"} --git-dir="$dir" -C "$1" diff-index --name-only --relative HEAD
299 + __git -C "$1" diff-index --name-only --relative HEAD
300 else
301 # NOTE: $2 is not quoted in order to support multiple options
296 - git ${__git_C_args:+"${__git_C_args[@]}"} --git-dir="$dir" -C "$1" ls-files --exclude-standard $2
302 + __git -C "$1" ls-files --exclude-standard $2
303 fi 2>/dev/null
304 }
305
@@ -323,8 +329,7 @@ __git_heads ()
329 {
330 local dir="$(__gitdir)"
331 if [ -d "$dir" ]; then
326 - git --git-dir="$dir" for-each-ref --format='%(refname:short)' \
327 - refs/heads
332 + __git for-each-ref --format='%(refname:short)' refs/heads
333 return
334 fi
335 }
@@ -333,8 +338,7 @@ __git_tags ()
338 {
339 local dir="$(__gitdir)"
340 if [ -d "$dir" ]; then
336 - git --git-dir="$dir" for-each-ref --format='%(refname:short)' \
337 - refs/tags
341 + __git for-each-ref --format='%(refname:short)' refs/tags
342 return
343 fi
344 }
@@ -385,14 +389,14 @@ __git_refs ()
389 refs="refs/tags refs/heads refs/remotes"
390 ;;
391 esac
388 - git --git-dir="$dir" for-each-ref --format="$pfx%($format)" \
392 + __git_dir="$dir" __git for-each-ref --format="$pfx%($format)" \
393 $refs
394 if [ -n "$track" ]; then
395 # employ the heuristic used by git checkout
396 # Try to find a remote branch that matches the completion word
397 # but only output if the branch name is unique
398 local ref entry
395 - git --git-dir="$dir" for-each-ref --shell --format="ref=%(refname:short)" \
399 + __git for-each-ref --shell --format="ref=%(refname:short)" \
400 "refs/remotes/" | \
401 while read -r entry; do
402 eval "$entry"
@@ -406,7 +410,7 @@ __git_refs ()
410 fi
411 case "$cur" in
412 refs|refs/*)
409 - git --git-dir="$dir" ls-remote "$remote" "$cur*" 2>/dev/null | \
413 + __git ls-remote "$remote" "$cur*" 2>/dev/null | \
414 while read -r hash i; do
415 case "$i" in
416 *^{}) ;;
@@ -417,10 +421,10 @@ __git_refs ()
421 *)
422 if [ "$list_refs_from" = remote ]; then
423 echo "HEAD"
420 - git --git-dir="$dir" for-each-ref --format="%(refname:short)" \
424 + __git for-each-ref --format="%(refname:short)" \
425 "refs/remotes/$remote/" 2>/dev/null | sed -e "s#^$remote/##"
426 else
423 - git --git-dir="$dir" ls-remote "$remote" HEAD \
427 + __git ls-remote "$remote" HEAD \
428 "refs/tags/*" "refs/heads/*" "refs/remotes/*" 2>/dev/null |
429 while read -r hash i; do
430 case "$i" in
@@ -447,7 +451,7 @@ __git_refs2 ()
451 __git_refs_remotes ()
452 {
453 local i hash
450 - git --git-dir="$(__gitdir)" ls-remote "$1" 'refs/heads/*' 2>/dev/null | \
454 + __git ls-remote "$1" 'refs/heads/*' 2>/dev/null | \
455 while read -r hash i; do
456 echo "$i:refs/remotes/$1/${i#refs/heads/}"
457 done
@@ -457,7 +461,7 @@ __git_remotes ()
461 {
462 local d="$(__gitdir)"
463 test -d "$d/remotes" && ls -1 "$d/remotes"
460 - git --git-dir="$d" remote
464 + __git remote
465 }
466
467 # Returns true if $1 matches the name of a configured remote, false otherwise.
@@ -523,7 +527,7 @@ __git_complete_revlist_file ()
527 *) pfx="$ref:$pfx" ;;
528 esac
529
526 - __gitcomp_nl "$(git ${__git_C_args:+"${__git_C_args[@]}"} --git-dir="$(__gitdir)" ls-tree "$ls" 2>/dev/null \
530 + __gitcomp_nl "$(__git ls-tree "$ls" 2>/dev/null \
531 | sed '/^100... blob /{
532 s,^.* ,,
533 s,$, ,
@@ -801,7 +805,7 @@ __git_compute_porcelain_commands ()
805 __git_get_config_variables ()
806 {
807 local section="$1" i IFS=$'\n'
804 - for i in $(git --git-dir="$(__gitdir)" config --name-only --get-regexp "^$section\..*" 2>/dev/null); do
808 + for i in $(__git config --name-only --get-regexp "^$section\..*" 2>/dev/null); do
809 echo "${i#$section.}"
810 done
811 }
@@ -819,8 +823,7 @@ __git_aliases ()
823 # __git_aliased_command requires 1 argument
824 __git_aliased_command ()
825 {
822 - local word cmdline=$(git --git-dir="$(__gitdir)" \
823 - config --get "alias.$1" 2>/dev/null)
826 + local word cmdline=$(__git config --get "alias.$1" 2>/dev/null)
827 for word in $cmdline; do
828 case "$word" in
829 \!gitk|gitk)
@@ -896,7 +899,7 @@ __git_get_option_value ()
899 done
900
901 if [ -n "$config_key" ] && [ -z "$result" ]; then
899 - result="$(git --git-dir="$(__gitdir)" config "$config_key")"
902 + result="$(__git config "$config_key")"
903 fi
904
905 echo "$result"
@@ -1237,7 +1240,7 @@ _git_commit ()
1240 return
1241 esac
1242
1240 - if git --git-dir="$(__gitdir)" rev-parse --verify --quiet HEAD >/dev/null; then
1243 + if __git rev-parse --verify --quiet HEAD >/dev/null; then
1244 __git_complete_index_file "--committable"
1245 else
1246 # This is the first commit
@@ -1839,7 +1842,7 @@ _git_send_email ()
1842 case "$prev" in
1843 --to|--cc|--bcc|--from)
1844 __gitcomp "
1842 - $(git --git-dir="$(__gitdir)" send-email --dump-aliases 2>/dev/null)
1845 + $(__git send-email --dump-aliases 2>/dev/null)
1846 "
1847 return
1848 ;;
@@ -1871,7 +1874,7 @@ _git_send_email ()
1874 ;;
1875 --to=*|--cc=*|--bcc=*|--from=*)
1876 __gitcomp "
1874 - $(git --git-dir="$(__gitdir)" send-email --dump-aliases 2>/dev/null)
1877 + $(__git send-email --dump-aliases 2>/dev/null)
1878 " "" "${cur#--*=}"
1879 return
1880 ;;
@@ -1966,7 +1969,7 @@ __git_config_get_set_variables ()
1969 c=$((--c))
1970 done
1971
1969 - git --git-dir="$(__gitdir)" config $config_file --name-only --list 2>/dev/null
1972 + __git config $config_file --name-only --list 2>/dev/null
1973 }
1974
1975 _git_config ()
@@ -2001,9 +2004,8 @@ _git_config ()
2004 remote.*.push)
2005 local remote="${prev#remote.}"
2006 remote="${remote%.push}"
2004 - __gitcomp_nl "$(git --git-dir="$(__gitdir)" \
2005 - for-each-ref --format='%(refname):%(refname)' \
2006 - refs/heads)"
2007 + __gitcomp_nl "$(__git for-each-ref
2008 + --format='%(refname):%(refname)' refs/heads)"
2009 return
2010 ;;
2011 pull.twohead|pull.octopus)
@@ -2591,12 +2593,12 @@ _git_stash ()
2593 if [ $cword -eq 3 ]; then
2594 __gitcomp_nl "$(__git_refs)";
2595 else
2594 - __gitcomp_nl "$(git --git-dir="$(__gitdir)" stash list \
2596 + __gitcomp_nl "$(__git stash list \
2597 | sed -n -e 's/:.*//p')"
2598 fi
2599 ;;
2600 show,*|apply,*|drop,*|pop,*)
2599 - __gitcomp_nl "$(git --git-dir="$(__gitdir)" stash list \
2601 + __gitcomp_nl "$(__git stash list \
2602 | sed -n -e 's/:.*//p')"
2603 ;;
2604 *)