Skip to content

Commit a31e626

Browse files
szedergitster
authored andcommitted
completion: optimize refs completion
After a unique command or option is completed, in most cases it is a good thing to add a trailing a space, but sometimes it doesn't make sense, e.g. when the completed word is an option taking an argument ('--option=') or a configuration section ('core.'). Therefore the completion script uses the '-o nospace' option to prevent bash from automatically appending a space to unique completions, and it has the __gitcomp() function to add that trailing space only when necessary. See 72e5e98 (bash: Add space after unique command name is completed., 2007-02-04), 78d4d6a (bash: Support unique completion on git-config., 2007-02-04), and b339177 (bash: Support unique completion when possible., 2007-02-04). __gitcomp() therefore iterates over all possible completion words it got as argument, and checks each word whether a trailing space is necessary or not. This is ok for commands, options, etc., i.e. when the number of words is relatively small, but can be noticeably slow for large number of refs. However, while options might or might not need that trailing space, refs are always handled uniformly and always get that trailing space (or a trailing '.' for 'git config branch.<head>.'). Since refs listed by __git_refs() & co. are separated by newline, this allows us some optimizations with 'compgen'. So, add a specialized variant of __gitcomp() that only deals with possible completion words separated by a newline and uniformly appends the trailing space to all words using 'compgen -S " "' (or any other suffix, if specified), so no iteration over all words is needed. But we need to fiddle with IFS, because the default IFS containing a space would cause the added space suffix to be stripped off when compgen's output is stored in the COMPREPLY array. Therefore we use only newline as IFS, hence the requirement for the newline-separated possible completion words. Convert all callsites of __gitcomp() where it's called with refs, i.e. when it gets the output of either __git_refs(), __git_heads(), __git_tags(), __git_refs2(), __git_refs_remotes(), or the odd 'git for-each-ref' somewhere in _git_config(). Also convert callsites where it gets other uniformly handled newline separated word lists, i.e. either remotes from __git_remotes(), names of set configuration variables from __git_config_get_set_variables(), stashes, or commands. Here are some timing results for dealing with 10000 refs. Before: $ refs="$(__git_refs ~/tmp/git/repo-with-10k-refs/)" $ time __gitcomp "$refs" real 0m1.134s user 0m1.060s sys 0m0.130s After: $ time __gitcomp_nl "$refs" real 0m0.373s user 0m0.360s sys 0m0.020s Signed-off-by: SZEDER Gábor <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent f674bb8 commit a31e626

File tree

1 file changed

+70
-45
lines changed

1 file changed

+70
-45
lines changed

contrib/completion/git-completion.bash

Lines changed: 70 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,31 @@ __gitcomp ()
512512
esac
513513
}
514514

515+
# Generates completion reply with compgen from newline-separated possible
516+
# completion words by appending a space to all of them.
517+
# It accepts 1 to 4 arguments:
518+
# 1: List of possible completion words, separated by a single newline.
519+
# 2: A prefix to be added to each possible completion word (optional).
520+
# 3: Generate possible completion matches for this word (optional).
521+
# 4: A suffix to be appended to each possible completion word instead of
522+
# the default space (optional). If specified but empty, nothing is
523+
# appended.
524+
__gitcomp_nl ()
525+
{
526+
local s=$'\n' IFS=' '$'\t'$'\n'
527+
local cur_="$cur" suffix=" "
528+
529+
if [ $# -gt 2 ]; then
530+
cur_="$3"
531+
if [ $# -gt 3 ]; then
532+
suffix="$4"
533+
fi
534+
fi
535+
536+
IFS=$s
537+
COMPREPLY=($(compgen -P "${2-}" -S "$suffix" -W "$1" -- "$cur_"))
538+
}
539+
515540
# __git_heads accepts 0 or 1 arguments (to pass to __gitdir)
516541
__git_heads ()
517542
{
@@ -716,15 +741,15 @@ __git_complete_revlist_file ()
716741
*...*)
717742
pfx="${cur_%...*}..."
718743
cur_="${cur_#*...}"
719-
__gitcomp "$(__git_refs)" "$pfx" "$cur_"
744+
__gitcomp_nl "$(__git_refs)" "$pfx" "$cur_"
720745
;;
721746
*..*)
722747
pfx="${cur_%..*}.."
723748
cur_="${cur_#*..}"
724-
__gitcomp "$(__git_refs)" "$pfx" "$cur_"
749+
__gitcomp_nl "$(__git_refs)" "$pfx" "$cur_"
725750
;;
726751
*)
727-
__gitcomp "$(__git_refs)"
752+
__gitcomp_nl "$(__git_refs)"
728753
;;
729754
esac
730755
}
@@ -764,7 +789,7 @@ __git_complete_remote_or_refspec ()
764789
c=$((++c))
765790
done
766791
if [ -z "$remote" ]; then
767-
__gitcomp "$(__git_remotes)"
792+
__gitcomp_nl "$(__git_remotes)"
768793
return
769794
fi
770795
if [ $no_complete_refspec = 1 ]; then
@@ -789,23 +814,23 @@ __git_complete_remote_or_refspec ()
789814
case "$cmd" in
790815
fetch)
791816
if [ $lhs = 1 ]; then
792-
__gitcomp "$(__git_refs2 "$remote")" "$pfx" "$cur_"
817+
__gitcomp_nl "$(__git_refs2 "$remote")" "$pfx" "$cur_"
793818
else
794-
__gitcomp "$(__git_refs)" "$pfx" "$cur_"
819+
__gitcomp_nl "$(__git_refs)" "$pfx" "$cur_"
795820
fi
796821
;;
797822
pull)
798823
if [ $lhs = 1 ]; then
799-
__gitcomp "$(__git_refs "$remote")" "$pfx" "$cur_"
824+
__gitcomp_nl "$(__git_refs "$remote")" "$pfx" "$cur_"
800825
else
801-
__gitcomp "$(__git_refs)" "$pfx" "$cur_"
826+
__gitcomp_nl "$(__git_refs)" "$pfx" "$cur_"
802827
fi
803828
;;
804829
push)
805830
if [ $lhs = 1 ]; then
806-
__gitcomp "$(__git_refs)" "$pfx" "$cur_"
831+
__gitcomp_nl "$(__git_refs)" "$pfx" "$cur_"
807832
else
808-
__gitcomp "$(__git_refs "$remote")" "$pfx" "$cur_"
833+
__gitcomp_nl "$(__git_refs "$remote")" "$pfx" "$cur_"
809834
fi
810835
;;
811836
esac
@@ -1084,7 +1109,7 @@ _git_archive ()
10841109
return
10851110
;;
10861111
--remote=*)
1087-
__gitcomp "$(__git_remotes)" "" "${cur##--remote=}"
1112+
__gitcomp_nl "$(__git_remotes)" "" "${cur##--remote=}"
10881113
return
10891114
;;
10901115
--*)
@@ -1115,7 +1140,7 @@ _git_bisect ()
11151140

11161141
case "$subcommand" in
11171142
bad|good|reset|skip|start)
1118-
__gitcomp "$(__git_refs)"
1143+
__gitcomp_nl "$(__git_refs)"
11191144
;;
11201145
*)
11211146
COMPREPLY=()
@@ -1146,9 +1171,9 @@ _git_branch ()
11461171
;;
11471172
*)
11481173
if [ $only_local_ref = "y" -a $has_r = "n" ]; then
1149-
__gitcomp "$(__git_heads)"
1174+
__gitcomp_nl "$(__git_heads)"
11501175
else
1151-
__gitcomp "$(__git_refs)"
1176+
__gitcomp_nl "$(__git_refs)"
11521177
fi
11531178
;;
11541179
esac
@@ -1195,7 +1220,7 @@ _git_checkout ()
11951220
if [ -n "$(__git_find_on_cmdline "$flags")" ]; then
11961221
track=''
11971222
fi
1198-
__gitcomp "$(__git_refs '' $track)"
1223+
__gitcomp_nl "$(__git_refs '' $track)"
11991224
;;
12001225
esac
12011226
}
@@ -1212,7 +1237,7 @@ _git_cherry_pick ()
12121237
__gitcomp "--edit --no-commit"
12131238
;;
12141239
*)
1215-
__gitcomp "$(__git_refs)"
1240+
__gitcomp_nl "$(__git_refs)"
12161241
;;
12171242
esac
12181243
}
@@ -1266,7 +1291,7 @@ _git_commit ()
12661291
;;
12671292
--reuse-message=*|--reedit-message=*|\
12681293
--fixup=*|--squash=*)
1269-
__gitcomp "$(__git_refs)" "" "${cur#*=}"
1294+
__gitcomp_nl "$(__git_refs)" "" "${cur#*=}"
12701295
return
12711296
;;
12721297
--untracked-files=*)
@@ -1297,7 +1322,7 @@ _git_describe ()
12971322
"
12981323
return
12991324
esac
1300-
__gitcomp "$(__git_refs)"
1325+
__gitcomp_nl "$(__git_refs)"
13011326
}
13021327

13031328
__git_diff_common_options="--stat --numstat --shortstat --summary
@@ -1456,7 +1481,7 @@ _git_grep ()
14561481
;;
14571482
esac
14581483

1459-
__gitcomp "$(__git_refs)"
1484+
__gitcomp_nl "$(__git_refs)"
14601485
}
14611486

14621487
_git_help ()
@@ -1514,7 +1539,7 @@ _git_ls_files ()
15141539

15151540
_git_ls_remote ()
15161541
{
1517-
__gitcomp "$(__git_remotes)"
1542+
__gitcomp_nl "$(__git_remotes)"
15181543
}
15191544

15201545
_git_ls_tree ()
@@ -1610,7 +1635,7 @@ _git_merge ()
16101635
__gitcomp "$__git_merge_options"
16111636
return
16121637
esac
1613-
__gitcomp "$(__git_refs)"
1638+
__gitcomp_nl "$(__git_refs)"
16141639
}
16151640

16161641
_git_mergetool ()
@@ -1630,7 +1655,7 @@ _git_mergetool ()
16301655

16311656
_git_merge_base ()
16321657
{
1633-
__gitcomp "$(__git_refs)"
1658+
__gitcomp_nl "$(__git_refs)"
16341659
}
16351660

16361661
_git_mv ()
@@ -1661,7 +1686,7 @@ _git_notes ()
16611686
,*)
16621687
case "${words[cword-1]}" in
16631688
--ref)
1664-
__gitcomp "$(__git_refs)"
1689+
__gitcomp_nl "$(__git_refs)"
16651690
;;
16661691
*)
16671692
__gitcomp "$subcommands --ref"
@@ -1670,7 +1695,7 @@ _git_notes ()
16701695
;;
16711696
add,--reuse-message=*|append,--reuse-message=*|\
16721697
add,--reedit-message=*|append,--reedit-message=*)
1673-
__gitcomp "$(__git_refs)" "" "${cur#*=}"
1698+
__gitcomp_nl "$(__git_refs)" "" "${cur#*=}"
16741699
;;
16751700
add,--*|append,--*)
16761701
__gitcomp '--file= --message= --reedit-message=
@@ -1689,7 +1714,7 @@ _git_notes ()
16891714
-m|-F)
16901715
;;
16911716
*)
1692-
__gitcomp "$(__git_refs)"
1717+
__gitcomp_nl "$(__git_refs)"
16931718
;;
16941719
esac
16951720
;;
@@ -1717,12 +1742,12 @@ _git_push ()
17171742
{
17181743
case "$prev" in
17191744
--repo)
1720-
__gitcomp "$(__git_remotes)"
1745+
__gitcomp_nl "$(__git_remotes)"
17211746
return
17221747
esac
17231748
case "$cur" in
17241749
--repo=*)
1725-
__gitcomp "$(__git_remotes)" "" "${cur##--repo=}"
1750+
__gitcomp_nl "$(__git_remotes)" "" "${cur##--repo=}"
17261751
return
17271752
;;
17281753
--*)
@@ -1760,7 +1785,7 @@ _git_rebase ()
17601785

17611786
return
17621787
esac
1763-
__gitcomp "$(__git_refs)"
1788+
__gitcomp_nl "$(__git_refs)"
17641789
}
17651790

17661791
_git_reflog ()
@@ -1771,7 +1796,7 @@ _git_reflog ()
17711796
if [ -z "$subcommand" ]; then
17721797
__gitcomp "$subcommands"
17731798
else
1774-
__gitcomp "$(__git_refs)"
1799+
__gitcomp_nl "$(__git_refs)"
17751800
fi
17761801
}
17771802

@@ -1853,23 +1878,23 @@ _git_config ()
18531878
{
18541879
case "$prev" in
18551880
branch.*.remote)
1856-
__gitcomp "$(__git_remotes)"
1881+
__gitcomp_nl "$(__git_remotes)"
18571882
return
18581883
;;
18591884
branch.*.merge)
1860-
__gitcomp "$(__git_refs)"
1885+
__gitcomp_nl "$(__git_refs)"
18611886
return
18621887
;;
18631888
remote.*.fetch)
18641889
local remote="${prev#remote.}"
18651890
remote="${remote%.fetch}"
1866-
__gitcomp "$(__git_refs_remotes "$remote")"
1891+
__gitcomp_nl "$(__git_refs_remotes "$remote")"
18671892
return
18681893
;;
18691894
remote.*.push)
18701895
local remote="${prev#remote.}"
18711896
remote="${remote%.push}"
1872-
__gitcomp "$(git --git-dir="$(__gitdir)" \
1897+
__gitcomp_nl "$(git --git-dir="$(__gitdir)" \
18731898
for-each-ref --format='%(refname):%(refname)' \
18741899
refs/heads)"
18751900
return
@@ -1916,7 +1941,7 @@ _git_config ()
19161941
return
19171942
;;
19181943
--get|--get-all|--unset|--unset-all)
1919-
__gitcomp "$(__git_config_get_set_variables)"
1944+
__gitcomp_nl "$(__git_config_get_set_variables)"
19201945
return
19211946
;;
19221947
*.*)
@@ -1942,7 +1967,7 @@ _git_config ()
19421967
;;
19431968
branch.*)
19441969
local pfx="${cur%.*}." cur_="${cur#*.}"
1945-
__gitcomp "$(__git_heads)" "$pfx" "$cur_" "."
1970+
__gitcomp_nl "$(__git_heads)" "$pfx" "$cur_" "."
19461971
return
19471972
;;
19481973
guitool.*.*)
@@ -1971,7 +1996,7 @@ _git_config ()
19711996
pager.*)
19721997
local pfx="${cur%.*}." cur_="${cur#*.}"
19731998
__git_compute_all_commands
1974-
__gitcomp "$__git_all_commands" "$pfx" "$cur_"
1999+
__gitcomp_nl "$__git_all_commands" "$pfx" "$cur_"
19752000
return
19762001
;;
19772002
remote.*.*)
@@ -1984,7 +2009,7 @@ _git_config ()
19842009
;;
19852010
remote.*)
19862011
local pfx="${cur%.*}." cur_="${cur#*.}"
1987-
__gitcomp "$(__git_remotes)" "$pfx" "$cur_" "."
2012+
__gitcomp_nl "$(__git_remotes)" "$pfx" "$cur_" "."
19882013
return
19892014
;;
19902015
url.*.*)
@@ -2285,7 +2310,7 @@ _git_remote ()
22852310

22862311
case "$subcommand" in
22872312
rename|rm|show|prune)
2288-
__gitcomp "$(__git_remotes)"
2313+
__gitcomp_nl "$(__git_remotes)"
22892314
;;
22902315
update)
22912316
local i c='' IFS=$'\n'
@@ -2303,7 +2328,7 @@ _git_remote ()
23032328

23042329
_git_replace ()
23052330
{
2306-
__gitcomp "$(__git_refs)"
2331+
__gitcomp_nl "$(__git_refs)"
23072332
}
23082333

23092334
_git_reset ()
@@ -2316,7 +2341,7 @@ _git_reset ()
23162341
return
23172342
;;
23182343
esac
2319-
__gitcomp "$(__git_refs)"
2344+
__gitcomp_nl "$(__git_refs)"
23202345
}
23212346

23222347
_git_revert ()
@@ -2327,7 +2352,7 @@ _git_revert ()
23272352
return
23282353
;;
23292354
esac
2330-
__gitcomp "$(__git_refs)"
2355+
__gitcomp_nl "$(__git_refs)"
23312356
}
23322357

23332358
_git_rm ()
@@ -2426,7 +2451,7 @@ _git_stash ()
24262451
COMPREPLY=()
24272452
;;
24282453
show,*|apply,*|drop,*|pop,*|branch,*)
2429-
__gitcomp "$(git --git-dir="$(__gitdir)" stash list \
2454+
__gitcomp_nl "$(git --git-dir="$(__gitdir)" stash list \
24302455
| sed -n -e 's/:.*//p')"
24312456
;;
24322457
*)
@@ -2560,7 +2585,7 @@ _git_tag ()
25602585
i="${words[c]}"
25612586
case "$i" in
25622587
-d|-v)
2563-
__gitcomp "$(__git_tags)"
2588+
__gitcomp_nl "$(__git_tags)"
25642589
return
25652590
;;
25662591
-f)
@@ -2576,13 +2601,13 @@ _git_tag ()
25762601
;;
25772602
-*|tag)
25782603
if [ $f = 1 ]; then
2579-
__gitcomp "$(__git_tags)"
2604+
__gitcomp_nl "$(__git_tags)"
25802605
else
25812606
COMPREPLY=()
25822607
fi
25832608
;;
25842609
*)
2585-
__gitcomp "$(__git_refs)"
2610+
__gitcomp_nl "$(__git_refs)"
25862611
;;
25872612
esac
25882613
}

0 commit comments

Comments
 (0)