completion: use 'awk' to strip trailing path components

During git-aware path completion we complete one path component at a
time, i.e. 'git add <TAB>' offers only 'dir/' at first, not
'dir/subdir/file' right away, just like Bash's own filename
completion.  However, since both 'git ls-files' and 'git diff-index'
dive deep into subdirectories, we have to strip all trailing path
components from the listed paths, keeping only the leading path
component.  This stripping is currently done in a shell loop in
__git_index_files(), which can take a significant amount of time when
it has to iterate through a large number of paths.

Replace this shell loop with a little 'awk' script using '/' as input
field separator and printing the first field, which produces the same
output much faster.

Listing all tracked files (12) and directories (23) at the top of the
worktree in linux.git (over 62k files), i.e. what's doing all the hard
work behind 'git rm <TAB>':

  Before this patch, best of five, using GNU awk on Linux:

    $ time cur= __git_complete_index_file

    real    0m2.149s
    user    0m1.307s
    sys     0m1.086s

  After:

    real    0m0.067s
    user    0m0.089s
    sys     0m0.023s

  Difference: -96.9%
  Speedup:     32.1x

Note that this could be done with 'sed', or even with 'cut', just as
well, but the upcoming patches require 'awk's scriptability.

Note also that this change means one more fork()+exec()ed process
during path completion, adding more overhead especially on Windows,
but a later patch will more than make up for it by eliminating two
other processes in the same function.

Signed-off-by: SZEDER Gábor <szeder.dev@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
SZEDER Gábor 2018-04-17 00:41:11 +02:00 committed by Junio C Hamano
parent a364e984d1
commit 105c0efff3

View File

@ -454,15 +454,12 @@ __git_ls_files_helper ()
# 3: List only paths matching this path component (optional).
__git_index_files ()
{
local root="$2" match="$3" file
local root="$2" match="$3"
__git_ls_files_helper "$root" "$1" "$match" |
while read -r file; do
case "$file" in
?*/*) echo "${file%%/*}" ;;
*) echo "$file" ;;
esac
done | sort | uniq
awk -F / '{
print $1
}' | sort | uniq
}
# __git_complete_index_file requires 1 argument: