Skip to content

Commit 7b00342

Browse files
szedergitster
authored andcommitted
completion: fill COMPREPLY directly when completing paths
During git-aware path completion, when a lot of path components have to be listed, a significant amount of time is spent in __gitcomp_file(), or more accurately in the shell loop of __gitcompappend(), iterating over all the path components filtering path components matching the current word to be completed, adding prefix path components, and placing the resulting matching paths into the COMPREPLY array. Now, a previous patch in this series made 'git ls-files' and 'git diff-index' list only paths matching the current word to be completed, so an additional filtering in __gitcomp_file() is not necessary anymore. Adding the prefix path components could be done much more efficiently in __git_index_files()'s 'awk' script while stripping trailing path components and removing duplicates and quoting. And then the resulting paths won't require any more filtering or processing before being handed over to Bash, so we could fill the COMPREPLY array directly. Unfortunately, we can't simply use the __gitcomp_direct() helper function to do that, because __gitcomp_file() does one additional thing: it tells Bash that we are doing filename completion, so the shell will kindly do four important things for us: 1. Append a trailing space to all filenames. 2. Append a trailing '/' to all directory names. 3. Escape any meta, globbing, separator, etc. characters. 4. List only the current path component when listing possible completions (i.e. 'dir/subdir/f<TAB>' will list 'file1', 'file2', etc. instead of the whole 'dir/subdir/file1', 'dir/subdir/file2'). While we could let __git_index_files()'s 'awk' script take care of the first two points, the third one gets tricky, and we absolutely need the shell's support for the fourth. Add the helper function __gitcomp_file_direct(), which, just like __gitcomp_direct(), fills the COMPREPLY array with prefiltered and preprocessed paths without any additional processing, without a shell loop, with just one single compound assignment, and, similar to __gitcomp_file(), tells Bash and ZSH that we are doing filename completion. Extend __git_index_files()'s 'awk' script a bit to prepend any prefix path components to all listed paths. Finally, modify __git_complete_index_file() to feed __git_index_files()'s output to ___gitcomp_file_direct() instead of __gitcomp_file(). After this patch there is no shell loop left in the path completion code path. This speeds up path completion when there are a lot of paths matching the current word to be completed. In a pathological repository with 100k files in a single directory, listing all those files: Before this patch, best of five, using GNU awk on Linux: $ time cur=dir/ __git_complete_index_file real 0m0.983s user 0m1.004s sys 0m0.033s After: real 0m0.313s user 0m0.341s sys 0m0.029s Difference: -68.2% Speedup: 3.1x To see the benefits of the whole patch series, the same command with v2.17.0: real 0m2.736s user 0m2.472s sys 0m0.610s Difference: -88.6% Speedup: 8.7x Note that this patch changes the output of the __git_index_files() helper function by unconditionally prepending the prefix path components to every listed path. This would break users' completion scriptlets that directly run: __gitcomp_file "$(__git_index_files ...)" "$pfx" "$cur_" because that would add the prefix path components once more. However, __git_index_files() is kind of a "helper function of a helper function", and users' completion scriptlets should have been using __git_complete_index_file() for git-aware path completion in the first place, so this is likely doesn't worth worrying about. Signed-off-by: SZEDER Gábor <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 193757f commit 7b00342

File tree

2 files changed

+39
-4
lines changed

2 files changed

+39
-4
lines changed

contrib/completion/git-completion.bash

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,23 @@ __gitcomp_nl ()
406406
__gitcomp_nl_append "$@"
407407
}
408408

409+
# Fills the COMPREPLY array with prefiltered paths without any additional
410+
# processing.
411+
# Callers must take care of providing only paths that match the current path
412+
# to be completed and adding any prefix path components, if necessary.
413+
# 1: List of newline-separated matching paths, complete with all prefix
414+
# path componens.
415+
__gitcomp_file_direct ()
416+
{
417+
local IFS=$'\n'
418+
419+
COMPREPLY=($1)
420+
421+
# use a hack to enable file mode in bash < 4
422+
compopt -o filenames +o nospace 2>/dev/null ||
423+
compgen -f /non-existing-dir/ > /dev/null
424+
}
425+
409426
# Generates completion reply with compgen from newline-separated possible
410427
# completion filenames.
411428
# It accepts 1 to 3 arguments:
@@ -457,14 +474,14 @@ __git_index_files ()
457474
local root="$2" match="$3"
458475

459476
__git_ls_files_helper "$root" "$1" "$match" |
460-
awk -F / '{
477+
awk -F / -v pfx="${2//\\/\\\\}" '{
461478
paths[$1] = 1
462479
}
463480
END {
464481
for (p in paths) {
465482
if (substr(p, 1, 1) != "\"") {
466483
# No special characters, easy!
467-
print p
484+
print pfx p
468485
continue
469486
}
470487
@@ -483,7 +500,7 @@ __git_index_files ()
483500
# skip it.
484501
continue
485502
else
486-
print p
503+
print pfx p
487504
}
488505
}
489506
function dequote(p, bs_idx, out, esc, esc_idx, dec) {
@@ -547,7 +564,7 @@ __git_complete_index_file ()
547564
cur_="$dequoted_word"
548565
esac
549566

550-
__gitcomp_file "$(__git_index_files "$1" "$pfx" "$cur_")" "$pfx" "$cur_"
567+
__gitcomp_file_direct "$(__git_index_files "$1" "$pfx" "$cur_")"
551568
}
552569

553570
# Lists branches from the local repository.
@@ -3348,6 +3365,15 @@ if [[ -n ${ZSH_VERSION-} ]]; then
33483365
compadd -Q -S "${4- }" -p "${2-}" -- ${=1} && _ret=0
33493366
}
33503367

3368+
__gitcomp_file_direct ()
3369+
{
3370+
emulate -L zsh
3371+
3372+
local IFS=$'\n'
3373+
compset -P '*[=:]'
3374+
compadd -Q -f -- ${=1} && _ret=0
3375+
}
3376+
33513377
__gitcomp_file ()
33523378
{
33533379
emulate -L zsh

contrib/completion/git-completion.zsh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,15 @@ __gitcomp_nl_append ()
9393
compadd -Q -S "${4- }" -p "${2-}" -- ${=1} && _ret=0
9494
}
9595

96+
__gitcomp_file_direct ()
97+
{
98+
emulate -L zsh
99+
100+
local IFS=$'\n'
101+
compset -P '*[=:]'
102+
compadd -Q -f -- ${=1} && _ret=0
103+
}
104+
96105
__gitcomp_file ()
97106
{
98107
emulate -L zsh

0 commit comments

Comments
 (0)