completion: load completion file for external subcommand

Adding external subcommands to Git is as easy as to put an executable
file git-foo into PATH. Packaging such subcommands for a Linux
distribution can be achieved by unpacking the executable into /usr/bin
of the user's system. Adding system-wide completion scripts for new
subcommands, however, can be a bit tricky.

Since bash-completion started to use dynamical loading of completion
scripts since v1.90 (preview of v2.0), it is no longer sufficient to
drop a completion script of a subcommand into the standard completions
path, /usr/share/bash-completion/completions, since this script will not
be loaded if called as a git subcommand.

For example, look at https://bugs.gentoo.org/544722. To give a short
summary: The popular git-flow subcommand provides a completion script,
which gets installed as /usr/share/bash-completion/completions/git-flow.

If you now type into a Bash shell:

    git flow <TAB>

You will not get any completions, because bash-completion only loads
completions for git and git has no idea that git-flow is defined in
another file. You have to load this script manually or trigger the
dynamic loader with:

    git-flow <TAB> # Please notice the dash instead of whitespace

This will not complete anything either, because it only defines a Bash
function, without generating completions. But now the correct completion
script has been loaded and the first command can use the completions.

So, the goal is now to teach the git completion script to consider the
possibility of external completion scripts for subcommands, but of
course without breaking current workflows.

I think the easiest method is to use a function that was defined by
bash-completion v1.90, namely _completion_loader. It will take care of
loading the correct script if present. Afterwards, the git completion
script behaves as usual.

_completion_loader was introduced in commit 20c05b43 of bash-completion
(https://github.com/scop/bash-completion.git) back in 2011, so it should
be available in even older LTS distributions. This function searches for
external completion scripts not only in the default path
/usr/share/bash-completion/completions, but also in the user's home
directory via $XDG_DATA_HOME and in a user specified directory via
$BASH_COMPLETION_USER_DIR.

The only "drawback" (if it even can be called as such) is, that if
_completion_loader does not find a completion script, it automatically
registers a minimal function for basic path completion. In practice,
however, this will not matter, because in this case the given command is
a git command in its dashed form, e.g. 'git-diff-index', and those have
been deprecated for a long time.

This way we can leverage bash-completion's dynamic loading for git
subcommands and make it easier for developers to distribute custom
completion scripts.

Signed-off-by: Florian Gamböck <mail@floga.de>
Acked-by: SZEDER Gábor <szeder.dev@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Florian Gamböck 2018-04-29 18:42:43 +02:00 committed by Junio C Hamano
parent 468165c1d8
commit 085e2ee0e6

View File

@ -3096,12 +3096,22 @@ __git_main ()
fi fi
local completion_func="_git_${command//-/_}" local completion_func="_git_${command//-/_}"
if ! declare -f $completion_func >/dev/null 2>/dev/null &&
declare -f _completion_loader >/dev/null 2>/dev/null
then
_completion_loader "git-$command"
fi
declare -f $completion_func >/dev/null 2>/dev/null && $completion_func && return declare -f $completion_func >/dev/null 2>/dev/null && $completion_func && return
local expansion=$(__git_aliased_command "$command") local expansion=$(__git_aliased_command "$command")
if [ -n "$expansion" ]; then if [ -n "$expansion" ]; then
words[1]=$expansion words[1]=$expansion
completion_func="_git_${expansion//-/_}" completion_func="_git_${expansion//-/_}"
if ! declare -f $completion_func >/dev/null 2>/dev/null &&
declare -f _completion_loader >/dev/null 2>/dev/null
then
_completion_loader "git-$expansion"
fi
declare -f $completion_func >/dev/null 2>/dev/null && $completion_func declare -f $completion_func >/dev/null 2>/dev/null && $completion_func
fi fi
} }