completion: don't disambiguate short refs
authorSZEDER Gábor <szeder.dev@gmail.com>
Thu, 23 Mar 2017 15:29:17 +0000 (16:29 +0100)
committerJunio C Hamano <gitster@pobox.com>
Thu, 23 Mar 2017 18:18:22 +0000 (11:18 -0700)
When the completion script lists short refs it does so using the 'git
for-each-ref' format 'refname:short', which makes sure that all listed
refs are unambiguous. While disambiguating refs is technically
correct in this case, as opposed to the cases discussed in the
previous patch, this disambiguation involves several stat() syscalls
for each ref, thus, unfortunately, comes at a steep cost especially on
Windows and/or when there are a lot of refs to be listed. A user of
Git for Windows reported[1] 'git checkout <TAB>' taking ~11 seconds in
a repository with just about 4000 refs.

However, it's questionable whether ambiguous refs are really that bad
to justify that much extra cost:

- Ambiguous refs are not that common,
- even if a repository contains ambiguous refs, they only hurt when
the user actually happens to want to do something with one of the
ambiguous refs, and
- the issue can be easily circumvented by renaming those ambiguous
refs.

- On the other hand, apparently not that many refs are needed to
make refs completion unacceptably slow on Windows,
- and this slowness bites each and every time the user attempts refs
completion, even when the repository doesn't contain any ambiguous
refs.
- Furthermore, circumventing the issue might not be possible or
might be considerably more difficult and requires various
trade-offs (e.g. working in a repository with only a few selected
important refs while keeping a separate repository with all refs
for reference).

Arguably, in this case the benefits of technical correctness are
rather minor compared to the price we pay for it, and we are better
off opting for performance over correctness.

Use the 'git for-each-ref' format 'refname:strip=2' to list short refs
to spare the substantial cost of disambiguating.

This speeds up refs completion considerably. 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 0m1.662s
user 0m1.368s
sys 0m0.296s

After:

real 0m0.831s
user 0m0.808s
sys 0m0.028s

On Windows, before:

real 0m12.457s
user 0m1.016s
sys 0m0.092s

After:

real 0m1.480s
user 0m1.031s
sys 0m0.060s

[1] - https://github.com/git-for-windows/git/issues/524

Signed-off-by: SZEDER Gábor <szeder.dev@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
contrib/completion/git-completion.bash
index e129f674e3ebd3b1ae928358ae8d1aad9223fc3d..5ee35d530ed799867b1200bcc7c07e8cca3e4d33 100644 (file)
@@ -401,7 +401,7 @@ __git_refs ()
                        for i in HEAD FETCH_HEAD ORIG_HEAD MERGE_HEAD; do
                                if [ -e "$dir/$i" ]; then echo $pfx$i; fi
                        done
-                       format="refname:short"
+                       format="refname:strip=2"
                        refs="refs/tags refs/heads refs/remotes"
                        ;;
                esac
@@ -412,7 +412,7 @@ __git_refs ()
                        # Try to find a remote branch that matches the completion word
                        # but only output if the branch name is unique
                        local ref entry
-                       __git for-each-ref --shell --format="ref=%(refname:short)" \
+                       __git for-each-ref --shell --format="ref=%(refname:strip=2)" \
                                "refs/remotes/" | \
                        while read -r entry; do
                                eval "$entry"
@@ -437,7 +437,7 @@ __git_refs ()
        *)
                if [ "$list_refs_from" = remote ]; then
                        echo "HEAD"
-                       __git for-each-ref --format="%(refname:short)" \
+                       __git for-each-ref --format="%(refname:strip=2)" \
                                "refs/remotes/$remote/" | sed -e "s#^$remote/##"
                else
                        __git ls-remote "$remote" HEAD \