覆盖gitclone的bash完成



内置完成

git clone的默认完成(如下所示)为--*选项提供选项卡完成:

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

bash完成1.x(旧bash)

(举个具体的例子,macos high-sierra+brew安装bash completion/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 [[ "$1" == *:* && "$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

(具体例子:股票ubuntu仿生(18.04))

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

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

我可以在以下目录中定义我自己的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选项卡完成在此模型下工作(并使默认完成继续工作)?

不可接受的解决方案:

  • 修改系统打包文件:/usr/share/bash-completion/completions/git。该文件由apt管理

bash完成的官方常见问题解答包含非常有趣的信息。

首先,如果您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完成脚本,以确保一切顺利结束。您可以很容易地执行它,在~/.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完成是否已经加载,然后如果不是这样,则加载所有bash完成git定义。编辑:当bash完成第一次加载git部分时,ENDLESS_LOOP_SAFEGUARD技巧可以避免无休止的循环。

如果需要,您可以获得用法:

complete --help

complete:complete[-abcdefgjksuv][-pr][-DE][-o选项][-A操作][-G globpat][-W单词表][-F函数][-C命令][-X filterpat][-P前缀][-S后缀][名称…]指定Readline如何完成参数。

对于每个NAME,指定参数的完成方式。如果没有选项提供,现有的竣工规范以这样的方式打印允许它们被重新用作输入。

选项:

-p以可重复使用的格式打印现有的完工规范-r删除每个NAME的填写说明,或者,如果没有提供了姓名,所有填写规范-D将完成和操作作为命令的默认值没有定义任何特定的完成-E将完成和操作应用于"空"命令--尝试在空白行上完成

当尝试完成时,操作将按上面列出了大写字母选项。-D选项采用优先于-E。

退出状态:除非提供了无效选项或出现错误,否则返回成功。

然后,也只有到那时,你才能定义你想要的任何东西,包括覆盖git克隆bash完成的旧方法:

# 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 [[ "$1" == *:* && "$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:的bash完成重载即可

_completion_loader git

这样,你就永远不会丢失你的零钱,因为你让包文件原封不动;并且仍然可以使用自己的功能来增强任何现有的bash完成。

编辑:

关于你对_completion_loader函数=>的恐惧,但在检查了源代码后,这个函数从2015-07-15 20:53:05的commit cad3abfc7开始就存在了,所以我想它应该保持向后兼容,但这是真的,没有保证。我将编辑我的简历,提出一些替代

作为替代方案,这应该是获得自己的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

.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

注意:有时,您不能"配置";,但必须提出一个补丁
例如,Git 2.33(2021年第三季度)修复了git clone --rec*的完成(如"--recurse-submodules"或"--recursive")

请参阅Philippe Blain(phil-blain)提交的ca2d62b(2021年7月16日)
(由Junio C Hamano合并——gitster-提交fa8b225,2021年7月28日)

parse-options:默认情况下不完成选项别名

签字人:Philippe Blain

由于'OPT_ALIAS'是在5c38742中创建的(parse-options:不发射,2019-04-29,Git v2.22.0-rc1-merge)(解析选项:别名不发射"模糊选项",2019-04.29),'git clone'(man)--git-completion-helper,Bash完成脚本使用它来列出克隆接受的选项(通过'__gitcomp_builtin'),列出了"--recurse子模块"及其别名"--recurrent",但由于"--rec递归"设置了PARSE_OPT_HIDDEN标志,所以以前的情况并非如此,并且实现git--git完成帮助程序的"parse-options.c::show_gitcomp"跳过了具有此标志的选项。

这意味着键入"git clone--recurs<TAB>"将同时生成"--recurse-submodules"one_answers"--recursive",这并不理想,因为两者都做相同的事情,因此完成应该直接完成规范选项。

在'parse_options_step'中调用'show_gitcomp'时,'preprocess_options'已在'parse_options'中调用,因此任何别名现在都是原始选项的副本,并带有修改的帮助文本,指示它们是别名。

有用的是,由于64cc539("parse-options:不要泄露别名帮助消息",2021-03-21,Git v2.32.0-rc0——在第7批中列出的merge),这些副本设置了PARSE_OPT_FROM_ALIAS标志,因此请在"show_gitcomp"早期检查该标志,不要打印它们,除非用户明确要求显示所有完成(通过设置"GIT_COMPLETION_SHOW_ALL")
毕竟,如果我们想鼓励使用"--recurse-submodules"而不是"--recursive",我们最好建议使用前者。

唯一的其他选项别名是"log"和朋友"--mailmap",这是"--use-mailmap"的别名,但这些命令的Bash完成助手不使用"__gitcomp_builtin",因此不受此更改的影响。

测试t9902 completion.sh.中的新行为。
作为副作用,这也测试了以前未测试的GIT_COMPLETION_SHOW_ALL,的正确行为
请注意,由于'__gitcomp_builtin'缓存了它显示的选项,因此我们需要重新获取完成脚本的源代码,以便为第二次测试清除该缓存。

最新更新