为 git 克隆覆盖 bash 完成

Override bash completion for git clone

内置补全

默认 completion for git clone(转载如下)为 --* 选项提供制表符补全:

_git_clone ()
{
    case "$cur" in
    --*)
        __gitcomp_builtin clone
        return
        ;;
    esac
}

bash-完成1.x(旧bash)

(具体实例,macos high sierra + brew 安装bash-完成/git)

在 bash-completion 1.x 世界中,为了覆盖这个我会(在 .bashrc / .bash_profile 中)定义我自己的 _git_clone 完成函数:

# https://github.com/scop/bash-completion/blob/d2f14a7/bash_completion#L498
__ltrim_colon_completions() {
    if [[ "" == *:* && "$COMP_WORDBREAKS" == *:* ]]; then
        # Remove colon-word prefix from COMPREPLY items
        local colon_word=${1%"${1##*:}"}
        local i=${#COMPREPLY[*]}
        while [[ $((--i)) -ge 0 ]]; do
            COMPREPLY[$i]=${COMPREPLY[$i]#"$colon_word"}
        done
    fi
}


_git_clone() {
    case "$cur" in
    --*)
        __gitcomp_builtin clone
        return
        ;;
    *)
        argc=0
        for word in "${words[@]}"; do
            case "$word" in
            git|clone|--*)
                continue
                ;;
            *)
                argc=$((argc + 1))
                ;;
            esac
        done

        if [ $argc -le 1 ]; then
            __gitcomp "https://github.com/git/git https://github.com/python/cpython"
            __ltrim_colon_completions "$cur"
        fi
        ;;
    esac
}

效果很好:

(我在这里输入的顺序是git clone h<tab><tab>g<tab>

$ git clone https://github.com/
//github.com/git/git          //github.com/python/cpython 
$ git clone https://github.com/git/git 

bash-完成2.x

(具体实例:stock ubuntu bionic (18.04))

在 bash-完成 2.x 中,模型被翻转为 动态 加载配置。这意味着当 git 完成制表符时,__load_completion 触发,在安装路径找到 git 完成并获取它。

.bashrc / .bash_profile 中定义我自己的 _git_clone 完成函数现在没有用了,因为它被动态来源的完成文件破坏了。

我可以在this directory中定义我自己的git补全:

local -a dirs=( ${BASH_COMPLETION_USER_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion}/completions )

(例如 ~/.local/share/bash-completion/completions/git.bash)。但是,这会关闭所有其他 git 完成!

如何让我的自定义 clone 选项卡完成在此模型下工作(并让默认完成继续工作)?

不可接受的解决方案:

在您的 .bashrc / .bash_profile 中,您可以在重新定义 git clone 的完成之前强制加载 git 的默认完成:

if ! complete -p git &> /dev/null
then
    # Instead of hardcoding the name of the dynamic completion loader
    # function, you can obtain it by parsing the output of 'complete -p -D'
    _completion_loader git
fi

_git_clone() {
    COMPREPLY=("My own completion for 'git clone'")
}

编辑

上述方法的延迟加载版本(不会急切加载 git 的默认 bash 完成)如下:

if ! complete -p git &> /dev/null
then
    _my_git_clone()
    {
        COMPREPLY=("My own completion for 'git clone'")
    }

    # A placeholder for git completion that will load the real
    # completion script on first use     
    _my_git_comp_stub()
    {
        # Remove the old completion handler (which should be this very function)
        complete -r git

        # and load the git bash completion
        _completion_loader git

        # Rebind _git_clone to our own version
        eval 'function _git_clone() { _my_git_clone "$@"; }'

        # Tell the completion machinery to retry the completion attempt
        # using the updated completion handler
        return 124
    }

    # Install a lazy loading handler for git completion    
    complete -o bashdefault -o default -o nospace -F _my_git_comp_stub git
fi

bash-完成的official FAQ包含了非常有趣的信息。

首先,如果您 100% 确定您的 $BASH_COMPLETION_USER_DIR$XDG_DATA_HOME 环境变量是空的,那么您在原始问题中指定的是添加您自己的 bash 的好地方-完成脚本:

~/.local/share/bash-completion/completions/git

需要注意.bash不需要扩展。

事实是 bash-完成脚本加载感谢 /etc/profile.d/bash_completion.sh 文件。

如果您在 .bashrc 文件中执行某些操作,您会以某种方式破坏加载链中的某些内容。

尽管如此,如果您覆盖现有的完成功能,您仍然需要确保加载顺序是正确的。 所以首先加载 bash-completion 脚本是强制性的,以确保一切顺利结束。 您可以轻松地执行它,在 ~/.local/share/bash-completion/completions/git 文件的开头添加此初始指令:

# Ensures git bash-completion is loaded before overriding any function (be careful to avoid endless loop).
! complete -p git &> /dev/null && [ ${ENDLESS_LOOP_SAFEGUARD:-0} -eq 0 ] && ENDLESS_LOOP_SAFEGUARD=1 BASH_COMPLETION_USER_DIR=/dev/null  _completion_loader git

首先检查 git bash-completion 是否已经加载,然后如果没有加载,则所有 bash-completion git 定义被加载。 编辑:ENDLESS_LOOP_SAFEGUARD 技巧允许在第一次 bash 完成加载 git 部分时避免无限循环。

如有需要,可获取用法:

complete --help

complete: complete [-abcdefgjksuv] [-pr] [-DE] [-o option] [-A action] [-G globpat] [-W wordlist] [-F function] [-C command] [-X filterpat] [-P prefix] [-S suffix] [name ...] Specify how arguments are to be completed by Readline.

For each NAME, specify how arguments are to be completed. If no options are supplied, existing completion specifications are printed in a way that allows them to be reused as input.

Options:

-p print existing completion specifications in a reusable format -r remove a completion specification for each NAME, or, if no NAMEs are supplied, all completion specifications -D apply the completions and actions as the default for commands without any specific completion defined -E apply the completions and actions to "empty" commands -- completion attempted on a blank line

When completion is attempted, the actions are applied in the order the uppercase-letter options are listed above. The -D option takes precedence over -E.

Exit Status: Returns success unless an invalid option is supplied or an error occurs.

然后,也只有那时,您可以定义任何您想要的,包括您覆盖 git clone bash completion:

的旧方法
# Ensures git bash-completion is loaded before overriding any function (be careful to avoid endless loop).
! complete -p git &> /dev/null && [ ${ENDLESS_LOOP_SAFEGUARD:-0} -eq 0 ] && ENDLESS_LOOP_SAFEGUARD=1 BASH_COMPLETION_USER_DIR=/dev/null  _completion_loader git

# https://github.com/scop/bash-completion/blob/d2f14a7/bash_completion#L498
__ltrim_colon_completions() {
    if [[ "" == *:* && "$COMP_WORDBREAKS" == *:* ]]; then
        # Remove colon-word prefix from COMPREPLY items
        local colon_word=${1%"${1##*:}"}
        local i=${#COMPREPLY[*]}
        while [[ $((--i)) -ge 0 ]]; do
            COMPREPLY[$i]=${COMPREPLY[$i]#"$colon_word"}
        done
    fi
}


_git_clone() {
    case "$cur" in
    --*)
        __gitcomp_builtin clone
        return
        ;;
    *)
        argc=0
        for word in "${words[@]}"; do
            case "$word" in
            git|clone|--*)
                continue
                ;;
            *)
                argc=$((argc + 1))
                ;;
            esac
        done

        if [ $argc -le 1 ]; then
            __gitcomp "https://github.com/git/git https://github.com/python/cpython"
            __ltrim_colon_completions "$cur"
        fi
        ;;
    esac
}

每次执行更改并想查看结果时,只需请求bash-完成重载git:

_completion_loader git

这样一来,您就永远不会丢失零钱,因为您让包文件保持原样;并且仍然可以使用您自己的功能增强任何现有的 bash-completion。

编辑:

关于你对 _completion_loader 函数 => 的恐惧,但是在检查了源代码之后,这个函数自 2015-07-15 20:53:05cad3abfc7 提交以来就存在了,所以我想它应该被保留向后兼容,但不保证真实。我将编辑我的答案以提出一些替代方案

作为替代方案,这应该是另一种获取您自己的 git 完成定义的方法(放在您自己的脚本的开头):

# Ensures git bash-completion is loaded before overriding any function
# Be careful to avoid endless loop with dedicated $ENDLESS_LOOP_SAFEGUARD environment variable.
if ! complete -p git &> /dev/null && [ ${ENDLESS_LOOP_SAFEGUARD:-0} -eq 0 ]; then
    # Trick: define $BASH_COMPLETION_USER_DIR environment variable here to ensure bash-completion rule are loaded once.
    export BASH_COMPLETION_USER_DIR=/usr/share
    complete -D git

    unset BASH_COMPLETION_USER_DIR
    ENDLESS_LOOP_SAFEGUARD=1 complete -D git
fi

注意:有时,您不能“配置”,而必须提出补丁。
例如,Git 2.33(2021 年第 3 季度)修复了 git clone --rec* 的完成(如“--recurse-submodules”或“--recursive”)

参见 commit ca2d62b (16 Jul 2021) by Philippe Blain (phil-blain)
(由 Junio C Hamano -- gitster -- in commit fa8b225 合并,2021 年 7 月 28 日)

parse-options: don't complete option aliases by default

Signed-off-by: Philippe Blain

Since 'OPT_ALIAS' was created in 5c38742 (parse-options: don't emit , 2019-04-29, Git v2.22.0-rc1 -- merge) (parse-options: don't emit "ambiguous option" for aliases, 2019-04-29), 'git clone'(man) --git-completion-helper, which is used by the Bash completion script to list options accepted by clone (via '__gitcomp_builtin'), lists both '--recurse-submodules' and its alias '--recursive', which was not the case before since '--recursive' had the PARSE_OPT_HIDDEN flag set, and options with this flag are skipped by 'parse-options.c::show_gitcomp', which implements git --git-completion-helper.

This means that typing 'git clone --recurs<TAB>' will yield both '--recurse-submodules' and '--recursive', which is not ideal since both do the same thing, and so the completion should directly complete the canonical option.

At the point where 'show_gitcomp' is called in 'parse_options_step', 'preprocess_options' was already called in 'parse_options', so any aliases are now copies of the original options with a modified help text indicating they are aliases.

Helpfully, since 64cc539 ("parse-options: don't leak alias help messages", 2021-03-21, Git v2.32.0-rc0 -- merge listed in batch #7) these copies have the PARSE_OPT_FROM_ALIAS flag set, so check that flag early in 'show_gitcomp' and do not print them, unless the user explicitly requested that all completion be shown (by setting 'GIT_COMPLETION_SHOW_ALL').
After all, if we want to encourage the use of '--recurse-submodules' over '--recursive', we'd better just suggest the former.

The only other options alias is 'log' and friends' '--mailmap', which is an alias for '--use-mailmap', but the Bash completion helpers for these commands do not use '__gitcomp_builtin', and thus are unaffected by this change.

Test the new behavior in t9902-completion.sh.
As a side effect, this also tests the correct behavior of GIT_COMPLETION_SHOW_ALL, which was not tested before.
Note that since '__gitcomp_builtin' caches the options it shows, we need to re-source the completion script to clear that cache for the second test.