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 72e5e989 (bash: Add space after unique command name is completed., 2007-02-04), 78d4d6a2 (bash: Support unique completion on git-config., 2007-02-04), and b3391775 (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 <szeder@ira.uka.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

SZEDER Gábor committed Oct 15, 2011 at 14:57 UTC a31e62629a1408bca540f505f2dc4ab8a9999f5d
1 file changed +70 -45
contrib/completion/git-completion.bash
+70 -45
@@ -512,6 +512,31 @@ __gitcomp ()
512 esac
513 }
514
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 +
540 # __git_heads accepts 0 or 1 arguments (to pass to __gitdir)
541 __git_heads ()
542 {
@@ -716,15 +741,15 @@ __git_complete_revlist_file ()
741 *...*)
742 pfx="${cur_%...*}..."
743 cur_="${cur_#*...}"
719 - __gitcomp "$(__git_refs)" "$pfx" "$cur_"
744 + __gitcomp_nl "$(__git_refs)" "$pfx" "$cur_"
745 ;;
746 *..*)
747 pfx="${cur_%..*}.."
748 cur_="${cur_#*..}"
724 - __gitcomp "$(__git_refs)" "$pfx" "$cur_"
749 + __gitcomp_nl "$(__git_refs)" "$pfx" "$cur_"
750 ;;
751 *)
727 - __gitcomp "$(__git_refs)"
752 + __gitcomp_nl "$(__git_refs)"
753 ;;
754 esac
755 }
@@ -764,7 +789,7 @@ __git_complete_remote_or_refspec ()
789 c=$((++c))
790 done
791 if [ -z "$remote" ]; then
767 - __gitcomp "$(__git_remotes)"
792 + __gitcomp_nl "$(__git_remotes)"
793 return
794 fi
795 if [ $no_complete_refspec = 1 ]; then
@@ -789,23 +814,23 @@ __git_complete_remote_or_refspec ()
814 case "$cmd" in
815 fetch)
816 if [ $lhs = 1 ]; then
792 - __gitcomp "$(__git_refs2 "$remote")" "$pfx" "$cur_"
817 + __gitcomp_nl "$(__git_refs2 "$remote")" "$pfx" "$cur_"
818 else
794 - __gitcomp "$(__git_refs)" "$pfx" "$cur_"
819 + __gitcomp_nl "$(__git_refs)" "$pfx" "$cur_"
820 fi
821 ;;
822 pull)
823 if [ $lhs = 1 ]; then
799 - __gitcomp "$(__git_refs "$remote")" "$pfx" "$cur_"
824 + __gitcomp_nl "$(__git_refs "$remote")" "$pfx" "$cur_"
825 else
801 - __gitcomp "$(__git_refs)" "$pfx" "$cur_"
826 + __gitcomp_nl "$(__git_refs)" "$pfx" "$cur_"
827 fi
828 ;;
829 push)
830 if [ $lhs = 1 ]; then
806 - __gitcomp "$(__git_refs)" "$pfx" "$cur_"
831 + __gitcomp_nl "$(__git_refs)" "$pfx" "$cur_"
832 else
808 - __gitcomp "$(__git_refs "$remote")" "$pfx" "$cur_"
833 + __gitcomp_nl "$(__git_refs "$remote")" "$pfx" "$cur_"
834 fi
835 ;;
836 esac
@@ -1084,7 +1109,7 @@ _git_archive ()
1109 return
1110 ;;
1111 --remote=*)
1087 - __gitcomp "$(__git_remotes)" "" "${cur##--remote=}"
1112 + __gitcomp_nl "$(__git_remotes)" "" "${cur##--remote=}"
1113 return
1114 ;;
1115 --*)
@@ -1115,7 +1140,7 @@ _git_bisect ()
1140
1141 case "$subcommand" in
1142 bad|good|reset|skip|start)
1118 - __gitcomp "$(__git_refs)"
1143 + __gitcomp_nl "$(__git_refs)"
1144 ;;
1145 *)
1146 COMPREPLY=()
@@ -1146,9 +1171,9 @@ _git_branch ()
1171 ;;
1172 *)
1173 if [ $only_local_ref = "y" -a $has_r = "n" ]; then
1149 - __gitcomp "$(__git_heads)"
1174 + __gitcomp_nl "$(__git_heads)"
1175 else
1151 - __gitcomp "$(__git_refs)"
1176 + __gitcomp_nl "$(__git_refs)"
1177 fi
1178 ;;
1179 esac
@@ -1195,7 +1220,7 @@ _git_checkout ()
1220 if [ -n "$(__git_find_on_cmdline "$flags")" ]; then
1221 track=''
1222 fi
1198 - __gitcomp "$(__git_refs '' $track)"
1223 + __gitcomp_nl "$(__git_refs '' $track)"
1224 ;;
1225 esac
1226 }
@@ -1212,7 +1237,7 @@ _git_cherry_pick ()
1237 __gitcomp "--edit --no-commit"
1238 ;;
1239 *)
1215 - __gitcomp "$(__git_refs)"
1240 + __gitcomp_nl "$(__git_refs)"
1241 ;;
1242 esac
1243 }
@@ -1266,7 +1291,7 @@ _git_commit ()
1291 ;;
1292 --reuse-message=*|--reedit-message=*|\
1293 --fixup=*|--squash=*)
1269 - __gitcomp "$(__git_refs)" "" "${cur#*=}"
1294 + __gitcomp_nl "$(__git_refs)" "" "${cur#*=}"
1295 return
1296 ;;
1297 --untracked-files=*)
@@ -1297,7 +1322,7 @@ _git_describe ()
1322 "
1323 return
1324 esac
1300 - __gitcomp "$(__git_refs)"
1325 + __gitcomp_nl "$(__git_refs)"
1326 }
1327
1328 __git_diff_common_options="--stat --numstat --shortstat --summary
@@ -1456,7 +1481,7 @@ _git_grep ()
1481 ;;
1482 esac
1483
1459 - __gitcomp "$(__git_refs)"
1484 + __gitcomp_nl "$(__git_refs)"
1485 }
1486
1487 _git_help ()
@@ -1514,7 +1539,7 @@ _git_ls_files ()
1539
1540 _git_ls_remote ()
1541 {
1517 - __gitcomp "$(__git_remotes)"
1542 + __gitcomp_nl "$(__git_remotes)"
1543 }
1544
1545 _git_ls_tree ()
@@ -1610,7 +1635,7 @@ _git_merge ()
1635 __gitcomp "$__git_merge_options"
1636 return
1637 esac
1613 - __gitcomp "$(__git_refs)"
1638 + __gitcomp_nl "$(__git_refs)"
1639 }
1640
1641 _git_mergetool ()
@@ -1630,7 +1655,7 @@ _git_mergetool ()
1655
1656 _git_merge_base ()
1657 {
1633 - __gitcomp "$(__git_refs)"
1658 + __gitcomp_nl "$(__git_refs)"
1659 }
1660
1661 _git_mv ()
@@ -1661,7 +1686,7 @@ _git_notes ()
1686 ,*)
1687 case "${words[cword-1]}" in
1688 --ref)
1664 - __gitcomp "$(__git_refs)"
1689 + __gitcomp_nl "$(__git_refs)"
1690 ;;
1691 *)
1692 __gitcomp "$subcommands --ref"
@@ -1670,7 +1695,7 @@ _git_notes ()
1695 ;;
1696 add,--reuse-message=*|append,--reuse-message=*|\
1697 add,--reedit-message=*|append,--reedit-message=*)
1673 - __gitcomp "$(__git_refs)" "" "${cur#*=}"
1698 + __gitcomp_nl "$(__git_refs)" "" "${cur#*=}"
1699 ;;
1700 add,--*|append,--*)
1701 __gitcomp '--file= --message= --reedit-message=
@@ -1689,7 +1714,7 @@ _git_notes ()
1714 -m|-F)
1715 ;;
1716 *)
1692 - __gitcomp "$(__git_refs)"
1717 + __gitcomp_nl "$(__git_refs)"
1718 ;;
1719 esac
1720 ;;
@@ -1717,12 +1742,12 @@ _git_push ()
1742 {
1743 case "$prev" in
1744 --repo)
1720 - __gitcomp "$(__git_remotes)"
1745 + __gitcomp_nl "$(__git_remotes)"
1746 return
1747 esac
1748 case "$cur" in
1749 --repo=*)
1725 - __gitcomp "$(__git_remotes)" "" "${cur##--repo=}"
1750 + __gitcomp_nl "$(__git_remotes)" "" "${cur##--repo=}"
1751 return
1752 ;;
1753 --*)
@@ -1760,7 +1785,7 @@ _git_rebase ()
1785
1786 return
1787 esac
1763 - __gitcomp "$(__git_refs)"
1788 + __gitcomp_nl "$(__git_refs)"
1789 }
1790
1791 _git_reflog ()
@@ -1771,7 +1796,7 @@ _git_reflog ()
1796 if [ -z "$subcommand" ]; then
1797 __gitcomp "$subcommands"
1798 else
1774 - __gitcomp "$(__git_refs)"
1799 + __gitcomp_nl "$(__git_refs)"
1800 fi
1801 }
1802
@@ -1853,23 +1878,23 @@ _git_config ()
1878 {
1879 case "$prev" in
1880 branch.*.remote)
1856 - __gitcomp "$(__git_remotes)"
1881 + __gitcomp_nl "$(__git_remotes)"
1882 return
1883 ;;
1884 branch.*.merge)
1860 - __gitcomp "$(__git_refs)"
1885 + __gitcomp_nl "$(__git_refs)"
1886 return
1887 ;;
1888 remote.*.fetch)
1889 local remote="${prev#remote.}"
1890 remote="${remote%.fetch}"
1866 - __gitcomp "$(__git_refs_remotes "$remote")"
1891 + __gitcomp_nl "$(__git_refs_remotes "$remote")"
1892 return
1893 ;;
1894 remote.*.push)
1895 local remote="${prev#remote.}"
1896 remote="${remote%.push}"
1872 - __gitcomp "$(git --git-dir="$(__gitdir)" \
1897 + __gitcomp_nl "$(git --git-dir="$(__gitdir)" \
1898 for-each-ref --format='%(refname):%(refname)' \
1899 refs/heads)"
1900 return
@@ -1916,7 +1941,7 @@ _git_config ()
1941 return
1942 ;;
1943 --get|--get-all|--unset|--unset-all)
1919 - __gitcomp "$(__git_config_get_set_variables)"
1944 + __gitcomp_nl "$(__git_config_get_set_variables)"
1945 return
1946 ;;
1947 *.*)
@@ -1942,7 +1967,7 @@ _git_config ()
1967 ;;
1968 branch.*)
1969 local pfx="${cur%.*}." cur_="${cur#*.}"
1945 - __gitcomp "$(__git_heads)" "$pfx" "$cur_" "."
1970 + __gitcomp_nl "$(__git_heads)" "$pfx" "$cur_" "."
1971 return
1972 ;;
1973 guitool.*.*)
@@ -1971,7 +1996,7 @@ _git_config ()
1996 pager.*)
1997 local pfx="${cur%.*}." cur_="${cur#*.}"
1998 __git_compute_all_commands
1974 - __gitcomp "$__git_all_commands" "$pfx" "$cur_"
1999 + __gitcomp_nl "$__git_all_commands" "$pfx" "$cur_"
2000 return
2001 ;;
2002 remote.*.*)
@@ -1984,7 +2009,7 @@ _git_config ()
2009 ;;
2010 remote.*)
2011 local pfx="${cur%.*}." cur_="${cur#*.}"
1987 - __gitcomp "$(__git_remotes)" "$pfx" "$cur_" "."
2012 + __gitcomp_nl "$(__git_remotes)" "$pfx" "$cur_" "."
2013 return
2014 ;;
2015 url.*.*)
@@ -2285,7 +2310,7 @@ _git_remote ()
2310
2311 case "$subcommand" in
2312 rename|rm|show|prune)
2288 - __gitcomp "$(__git_remotes)"
2313 + __gitcomp_nl "$(__git_remotes)"
2314 ;;
2315 update)
2316 local i c='' IFS=$'\n'
@@ -2303,7 +2328,7 @@ _git_remote ()
2328
2329 _git_replace ()
2330 {
2306 - __gitcomp "$(__git_refs)"
2331 + __gitcomp_nl "$(__git_refs)"
2332 }
2333
2334 _git_reset ()
@@ -2316,7 +2341,7 @@ _git_reset ()
2341 return
2342 ;;
2343 esac
2319 - __gitcomp "$(__git_refs)"
2344 + __gitcomp_nl "$(__git_refs)"
2345 }
2346
2347 _git_revert ()
@@ -2327,7 +2352,7 @@ _git_revert ()
2352 return
2353 ;;
2354 esac
2330 - __gitcomp "$(__git_refs)"
2355 + __gitcomp_nl "$(__git_refs)"
2356 }
2357
2358 _git_rm ()
@@ -2426,7 +2451,7 @@ _git_stash ()
2451 COMPREPLY=()
2452 ;;
2453 show,*|apply,*|drop,*|pop,*|branch,*)
2429 - __gitcomp "$(git --git-dir="$(__gitdir)" stash list \
2454 + __gitcomp_nl "$(git --git-dir="$(__gitdir)" stash list \
2455 | sed -n -e 's/:.*//p')"
2456 ;;
2457 *)
@@ -2560,7 +2585,7 @@ _git_tag ()
2585 i="${words[c]}"
2586 case "$i" in
2587 -d|-v)
2563 - __gitcomp "$(__git_tags)"
2588 + __gitcomp_nl "$(__git_tags)"
2589 return
2590 ;;
2591 -f)
@@ -2576,13 +2601,13 @@ _git_tag ()
2601 ;;
2602 -*|tag)
2603 if [ $f = 1 ]; then
2579 - __gitcomp "$(__git_tags)"
2604 + __gitcomp_nl "$(__git_tags)"
2605 else
2606 COMPREPLY=()
2607 fi
2608 ;;
2609 *)
2585 - __gitcomp "$(__git_refs)"
2610 + __gitcomp_nl "$(__git_refs)"
2611 ;;
2612 esac
2613 }