summaryrefslogtreecommitdiff
path: root/contrib
diff options
context:
space:
mode:
authorSZEDER Gábor <szeder.dev@gmail.com>2017-03-23 16:29:18 +0100
committerJunio C Hamano <gitster@pobox.com>2017-03-23 11:18:22 -0700
commite896369beb6c9cc9dc1a37d77cb9751be13ce959 (patch)
tree9e3794028ce2f1ad0ed2e22beb03a1bdb4b5b91a /contrib
parentb2b68114519bfbbba8d32c1f1afd181e01e0f28c (diff)
downloadgit-e896369beb6c9cc9dc1a37d77cb9751be13ce959.tar.gz
completion: let 'for-each-ref' and 'ls-remote' filter matching refs
When completing refs, several __git_refs() code paths list all the refs from the refs/{heads,tags,remotes}/ hierarchy and then __gitcomp_nl() iterates over those refs in a shell loop to filter out refs not matching the current ref to be completed. This comes with a considerable performance penalty when a repository contains a lot of refs but the current ref can be uniquely completed or when only a handful of refs match the current ref. Reduce the number of iterations in __gitcomp_nl() from the number of refs to the number of matching refs by specifying appropriate globbing patterns to 'git for-each-ref' and 'git ls-remote' to list only those refs that match the current ref to be completed. However, do so only when the ref to match is explicitly given as parameter, because the current word on the command line might contain a prefix like '--option=' or 'branch..'. The __git_complete_refs() and __git_complete_fetch_refspecs() helpers introduced previously in this patch series already call __git_refs() specifying this current ref parameter, so all their callsites, i.e. all places in the completion script doing refs completion, can benefit from this optimization. Furthermore, list only those symbolic and pseudo refs that match the current ref to be completed. Though it doesn't matter at all in itself performance-wise, it will allow us further significant optimizations later in this series. This speeds up refs completion considerably when there are a lot of non-matching refs to be filtered out. Uniquely completing a branch in a repository with 100k local branches, all packed, best of five: On Linux, before: $ time __git_complete_refs --cur=maste real 0m0.831s user 0m0.808s sys 0m0.028s After: real 0m0.119s user 0m0.104s sys 0m0.008s On Windows, before: real 0m1.480s user 0m1.031s sys 0m0.060s After: real 0m0.377s user 0m0.015s sys 0m0.030s Signed-off-by: SZEDER Gábor <szeder.dev@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Diffstat (limited to 'contrib')
-rw-r--r--contrib/completion/git-completion.bash41
1 files changed, 30 insertions, 11 deletions
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 5ee35d530e..976f805988 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -355,7 +355,8 @@ __git_tags ()
# 2: In addition to local refs, list unique branches from refs/remotes/ for
# 'git checkout's tracking DWIMery (optional; ignored, if set but empty).
# 3: Currently ignored.
-# 4: The current ref to be completed (optional).
+# 4: List only refs matching this word (optional; list all refs if unset or
+# empty).
#
# Use __git_complete_refs() instead.
__git_refs ()
@@ -364,6 +365,7 @@ __git_refs ()
local list_refs_from=path remote="${1-}"
local format refs pfx
local cur_="${4-$cur}"
+ local match="${4-}"
__git_find_repo_path
dir="$__git_repo_path"
@@ -390,23 +392,32 @@ __git_refs ()
if [[ "$cur_" == ^* ]]; then
pfx="^"
cur_=${cur_#^}
+ match=${match#^}
fi
case "$cur_" in
refs|refs/*)
format="refname"
- refs="${cur_%/*}"
+ refs=("$match*" "$match*/**")
track=""
;;
*)
for i in HEAD FETCH_HEAD ORIG_HEAD MERGE_HEAD; do
- if [ -e "$dir/$i" ]; then echo $pfx$i; fi
+ case "$i" in
+ $match*)
+ if [ -e "$dir/$i" ]; then
+ echo $pfx$i
+ fi
+ ;;
+ esac
done
format="refname:strip=2"
- refs="refs/tags refs/heads refs/remotes"
+ refs=("refs/tags/$match*" "refs/tags/$match*/**"
+ "refs/heads/$match*" "refs/heads/$match*/**"
+ "refs/remotes/$match*" "refs/remotes/$match*/**")
;;
esac
__git_dir="$dir" __git for-each-ref --format="$pfx%($format)" \
- $refs
+ "${refs[@]}"
if [ -n "$track" ]; then
# employ the heuristic used by git checkout
# Try to find a remote branch that matches the completion word
@@ -417,7 +428,7 @@ __git_refs ()
while read -r entry; do
eval "$entry"
ref="${ref#*/}"
- if [[ "$ref" == "$cur_"* ]]; then
+ if [[ "$ref" == "$match"* ]]; then
echo "$ref"
fi
done | sort | uniq -u
@@ -426,7 +437,7 @@ __git_refs ()
fi
case "$cur_" in
refs|refs/*)
- __git ls-remote "$remote" "$cur_*" | \
+ __git ls-remote "$remote" "$match*" | \
while read -r hash i; do
case "$i" in
*^{}) ;;
@@ -436,12 +447,20 @@ __git_refs ()
;;
*)
if [ "$list_refs_from" = remote ]; then
- echo "HEAD"
+ case "HEAD" in
+ $match*) echo "HEAD" ;;
+ esac
__git for-each-ref --format="%(refname:strip=2)" \
- "refs/remotes/$remote/" | sed -e "s#^$remote/##"
+ "refs/remotes/$remote/$match*" \
+ "refs/remotes/$remote/$match*/**" | sed -e "s#^$remote/##"
else
- __git ls-remote "$remote" HEAD \
- "refs/tags/*" "refs/heads/*" "refs/remotes/*" |
+ local query_symref
+ case "HEAD" in
+ $match*) query_symref="HEAD" ;;
+ esac
+ __git ls-remote "$remote" $query_symref \
+ "refs/tags/$match*" "refs/heads/$match*" \
+ "refs/remotes/$match*" |
while read -r hash i; do
case "$i" in
*^{}) ;;