]> git.ipfire.org Git - thirdparty/git.git/commitdiff
completion: let 'ls-files' and 'diff-index' filter matching paths
authorSZEDER Gábor <szeder.dev@gmail.com>
Mon, 16 Apr 2018 22:41:10 +0000 (00:41 +0200)
committerJunio C Hamano <gitster@pobox.com>
Tue, 17 Apr 2018 03:49:36 +0000 (12:49 +0900)
During git-aware path completion, e.g. 'git rm dir/fil<TAB>', both
'git ls-files' and 'git diff-index' list all paths in the given 'dir/'
matching certain criteria (cached, modified, untracked, etc.)
appropriate for the given git command, even paths whose names don't
begin with 'fil'.  This comes with a considerable performance
penalty when the directory in question contains a lot of paths, but
the current word can be uniquely completed or when only a handful of
those paths match the current word.

Reduce the number of iterations in this codepath from the number of
paths to the number of matching paths by specifying an appropriate
globbing pattern to 'git ls-files' and 'git diff-index' to list only
paths that match the current word to be completed.

Note that both commands treat backslashes as escape characters in
their file arguments, e.g. to preserve the literal meaning of globbing
characters, so we have to double every backslash in the globbing
pattern.  This is why one of the path completion tests specifically
checks the completion of a path containing a literal backslash
character (that test still fails, though, because both commands output
such paths enclosed in double quotes and the special characters
escaped; a later patch in this series will deal with those).

This speeds up path completion considerably when there are a lot of
non-matching paths to be filtered out.  Uniquely completing a tracked
filename at the top of the worktree in linux.git (over 62k files),
i.e. what's doing all the hard work behind 'git rm Mak<TAB>' to
complete 'Makefile':

  Before this patch, best of five, on Linux:

    $ time cur=Mak __git_complete_index_file

    real    0m2.159s
    user    0m1.299s
    sys     0m1.089s

  After:

    real    0m0.033s
    user    0m0.023s
    sys     0m0.015s

  Difference: -98.5%
  Speedup:     65.4x

Signed-off-by: SZEDER Gábor <szeder.dev@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
contrib/completion/git-completion.bash
t/t9902-completion.sh

index cdcf8b9c37b811a5635bc941f5a0e737e71bc771..a46c176e18b683ec298308203d5031682e843fa2 100644 (file)
@@ -436,11 +436,11 @@ __git_ls_files_helper ()
 {
        if [ "$2" == "--committable" ]; then
                __git -C "$1" -c core.quotePath=false diff-index \
-                       --name-only --relative HEAD
+                       --name-only --relative HEAD -- "${3//\\/\\\\}*"
        else
                # NOTE: $2 is not quoted in order to support multiple options
                __git -C "$1" -c core.quotePath=false ls-files \
-                       --exclude-standard $2
+                       --exclude-standard $2 -- "${3//\\/\\\\}*"
        fi
 }
 
@@ -451,11 +451,12 @@ __git_ls_files_helper ()
 #    If provided, only files within the specified directory are listed.
 #    Sub directories are never recursed.  Path must have a trailing
 #    slash.
+# 3: List only paths matching this path component (optional).
 __git_index_files ()
 {
-       local root="$2" file
+       local root="$2" match="$3" file
 
-       __git_ls_files_helper "$root" "$1" |
+       __git_ls_files_helper "$root" "$1" "$match" |
        while read -r file; do
                case "$file" in
                ?*/*) echo "${file%%/*}" ;;
@@ -483,7 +484,7 @@ __git_complete_index_file ()
                cur_="$dequoted_word"
        esac
 
-       __gitcomp_file "$(__git_index_files "$1" "$pfx")" "$pfx" "$cur_"
+       __gitcomp_file "$(__git_index_files "$1" "$pfx" "$cur_")" "$pfx" "$cur_"
 }
 
 # Lists branches from the local repository.
index f8fcedae2c2c18545b4087371d708f15ec920955..562c88e9a914217514e1a62ebbc10a964f9df2e7 100755 (executable)
@@ -1515,6 +1515,7 @@ test_expect_success 'complete files - UTF-8 in ls-files output' '
                        "árvíztűrő/Сайн яваарай"
 '
 
+# Testing with a path containing a backslash is important.
 if test_have_prereq !MINGW &&
    mkdir 'New\Dir' 2>/dev/null &&
    touch 'New\Dir/New"File.c' 2>/dev/null