zsh 中的 git 完成:__git_func_wrap:3:: 未找到

qua*_*nta 7 git zsh autocomplete tig

git-completion.zshgit-completion.bash在运行时自动安装brew install git

? ls -l /usr/local/share/zsh/site-functions/_git
lrwxr-xr-x 56 quanta  7 Jul 18:54 /usr/local/share/zsh/site-functions/_git -> ../../../Cellar/git/2.27.0/share/zsh/site-functions/_git

? ls -l /usr/local/share/zsh/site-functions/git-completion.bash
lrwxr-xr-x 71 quanta  7 Jul 18:54 /usr/local/share/zsh/site-functions/git-completion.bash -> ../../../Cellar/git/2.27.0/share/zsh/site-functions/git-completion.bash
Run Code Online (Sandbox Code Playgroud)

/usr/local/share/zsh/site-functions包括在fpath

? echo $fpath
/usr/local/share/zsh-completions
/usr/local/share/zsh/site-functions
/usr/share/zsh/site-functions
/usr/share/zsh/5.7.1/functions
Run Code Online (Sandbox Code Playgroud)

由于某些原因,有时当我输入git reba并按下tab

? git reba
__git_func_wrap:3: : not found
__git_func_wrap:3: : not found    

? type __git_func_wrap
__git_func_wrap is a shell function from /usr/local/share/zsh/site-functions/git-completion.bash
Run Code Online (Sandbox Code Playgroud)

https://github.com/git/git/blob/master/contrib/completion/git-completion.bash#L3517-L3522

? grep -A5 '^__git_func_wrap' /usr/local/share/zsh/site-functions/git-completion.bash
__git_func_wrap ()
{
    local cur words cword prev
    _get_comp_words_by_ref -n =: cur words cword prev
    $1
}
Run Code Online (Sandbox Code Playgroud)

默认完成是什么:

? complete -p git
complete -o bashdefault -o default -o nospace -F __git_wrap_tig tig
complete _bash bash
Run Code Online (Sandbox Code Playgroud)

继续检查:

? type __git_wrap_tig
__git_wrap_tig is a shell function from /usr/local/share/zsh/site-functions/tig-completion.bash
Run Code Online (Sandbox Code Playgroud)

问题是我在tig-completion.bash 中找不到这个功能

tig: stable 2.5.1 (bottled), HEAD
Text interface for Git repositories
https://jonas.github.io/tig/
/usr/local/Cellar/tig/2.5.1 (15 files, 875.9KB) *
  Poured from bottle on 2020-07-06 at 16:01:38
From: https://github.com/Homebrew/homebrew-core/blob/HEAD/Formula/tig.rb
==> Dependencies
Required: readline ?
==> Options
--HEAD
    Install HEAD version
==> Caveats
A sample of the default configuration has been installed to:
  /usr/local/opt/tig/share/tig/examples/tigrc
to override the system-wide default configuration, copy the sample to:
  /usr/local/etc/tigrc

Bash completion has been installed to:
  /usr/local/etc/bash_completion.d

zsh completions and functions have been installed to:
  /usr/local/share/zsh/site-functions
Run Code Online (Sandbox Code Playgroud)

看起来最近有一些变化:https : //github.com/jonas/tig/commit/26ab51d28133354bfaa94d064bff37d29b3c30e3

__git_wrap_tig功能在哪里?

PS:正如我上面所说,这个问题不是每次都会发生。有时,当我打开一个新选项卡并检查默认完成时,它只是:

? complete -p git
complete _bash bash
Run Code Online (Sandbox Code Playgroud)

并且git完成工作按预期进行。


回复@user1934428:

? grep '__git_complete ' /usr/local/share/zsh/site-functions/git-completion.bash
__git_complete ()
__git_complete git __git_main
__git_complete gitk __gitk_main
__git_complete git.exe __git_main
Run Code Online (Sandbox Code Playgroud)

另一个调用__git_complete是在tig-completion.bash 中

# we use internal git-completion functions, so wrap _tig for all necessary
# variables (like cword and prev) to be defined
__git_complete tig _tig 
Run Code Online (Sandbox Code Playgroud)

spa*_*azm 8

TL;DR

This is a problem with the tig completion definitions, and not with the git completion definitions.

Activating completion on tig breaks completion for git.

  • If tig is activated after git, then tig completion works and git completion is broken.
  • If tig completion is activated before git, then they are both broken.

Mitigation:

Install the old versions of the completion scripts.

Unlink _tig and tig-completion.bash in /usr/local/share/zsh/site-functions and replace with these older versions. Rename tig-completion.zsh as _tig when downloading.

cd /usr/local/share/zsh/site-functions && \
rm -f _tig tig-completion.bash && \
wget -O _tig https://raw.githubusercontent.com/jonas/tig/91912eb97da4f6907015dab41ef9bba315730854/contrib/tig-completion.zsh && \
wget -O tig-completion.bash https://raw.githubusercontent.com/jonas/tig/c72aa4dab21077231a97dcca8e3821d7b35fe7db/contrib/tig-completion.bash
Run Code Online (Sandbox Code Playgroud)

Solution:

TODO: File issue with tig. This is a regression with the new completion script as implemented in jonas/tig#960

States:

I start with git tab completion working, and then at some point the shell "goes bad." I actually have three states

  1. initial state. working. complete not defined.
    % which complete
    
    Run Code Online (Sandbox Code Playgroud)
  2. still working after a first tab completion which creates a definition for complete
    % git <TAB>
    add       -- add file contents to the index
    bisect    -- find by binary search the change that introduced a bug
    ...
    % which complete
    complete () {
            return 0
    }
    
    Run Code Online (Sandbox Code Playgroud)
  3. not working. complete function defined referencing bash
        complete () {
            emulate -L zsh
            local args void cmd print remove
            args=("$@")
            zparseopts -D -a void o: A: G: W: C: F: P: S: X: a b c d e f g j k u v p=print r=remove
            if [[ -n $print ]]
            then
                    printf 'complete %2$s %1$s\n' "${(@kv)_comps[(R)_bash*]#* }"
            elif [[ -n $remove ]]
            then
                for cmd
                do
                        unset "_comps[$cmd]"
                done
            else
                    compdef _bash_complete\ ${(j. .)${(q)args[1,-1-$#]}} "$@"
            fi
        }
    
    Run Code Online (Sandbox Code Playgroud)

Research

complete() function:

unsetting the complete function unset -f complete does not magically fix it. I think this may leave me with no completion for git?

virtual envs

I jump in and out of virtual envs, and thought that was related, but a controlled example of jumping in and out and manually setting VIRTUAL_ENV and etc did not bleed over and affect the completion system.

distraction, not related

local variables

Digging further I found a lot of local variables set in the third case, "bad shell."

I removed each of these local variables without any positive effect:

% unset REPLY
% unset __git_repo_path
% unset __tig_commands
% unset __tig_options
% unset _ack_raw_types
% unset $_cmd_variant
% unset _cmd_variant
Run Code Online (Sandbox Code Playgroud)

tig

Progress! I can move from state 1 to state 2 by invoking completion on tig:

% git <TAB>
add       -- add file contents to the index
bisect    -- find by binary search the change that introduced a bug
...
% tig <TAB>
% git <TAB>
__git_func_wrap:3: : not found
Run Code Online (Sandbox Code Playgroud)

related broken state by completing with tig first:

% tig <TAB>
__git_complete:5: command not found: complete
% which complete
complete () {
        emulate -L zsh
        local args void cmd print remove
        args=("$@")
        zparseopts -D -a void o: A: G: W: C: F: P: S: X: a b c d e f g j k u v p=print r=remove
        if [[ -n $print ]]
        then
                printf 'complete %2$s %1$s\n' "${(@kv)_comps[(R)_bash*]#* }"
        elif [[ -n $remove ]]
        then
                for cmd
                do
                        unset "_comps[$cmd]"
                done
        else
                compdef _bash_complete\ ${(j. .)${(q)args[1,-1-$#]}} "$@"
        fi
}
% git <TAB>
__git_func_wrap:3: : not found
Run Code Online (Sandbox Code Playgroud)

fpath and tig completion

% echo $fpath
/usr/local/share/zsh/site-functions /usr/share/zsh/site-functions /usr/share/zsh/5.7.1/functions

% for f in $fpath; do ls $f/*tig*; done | cat
/usr/local/share/zsh/site-functions/_tig
/usr/local/share/zsh/site-functions/tig-completion.bash
zsh: no matches found: /usr/share/zsh/site-functions/*tig*
zsh: no matches found: /usr/share/zsh/5.7.1/functions/*tig*

Run Code Online (Sandbox Code Playgroud)

Brew sources for site-functions for git, tig

  • tig completions from tig version 2.5.1
  • git completions from git version 2.28.0
% cd /usr/local/share/zsh/site-functions
% ls -l *tig*
_tig -> ../../../Cellar/tig/2.5.1/share/zsh/site-functions/_tig
tig-completion.bash -> ../../../Cellar/tig/2.5.1/share/zsh/site-functions/tig-completion.bash
% ls -l *git*
_git -> ../../../Cellar/git/2.28.0/share/zsh/site-functions/_git
git-completion.bash -> ../../../Cellar/git/2.28.0/share/zsh/site-functions/git-completion.bash
Run Code Online (Sandbox Code Playgroud)

Tig completions in /usr/local/share/zsh/site-functions

  • _tig
    #compdef tig
    #
    # zsh completion wrapper for tig
    # ==============================
    #
    # You need to install this script to zsh fpath with tig-completion.bash.
    #
    # The recommended way to install this script is to copy this and tig-completion.bash
    # to '~/.zsh/_tig' and '~/.zsh/tig-completion.bash' and
    # then add following to your ~/.zshrc file:
    #
    #  fpath=(~/.zsh $fpath)
    
    
    _tig () {
      local e
      e=$(dirname ${funcsourcetrace[1]%:*})/git-completion.bash
      if [ -f $e ]; then
        GIT_SOURCING_ZSH_COMPLETION=y . $e
      fi
      e=$(dirname ${funcsourcetrace[1]%:*})/tig-completion.bash
      if [ -f $e ]; then
        . $e
      fi
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • tig-completion.bash
    #compdef git gitk
    
    # zsh completion wrapper for git
    #
    # Copyright (c) 2012-2013 Felipe Contreras <felipe.contreras@gmail.com>
    #
    # You need git's bash completion script installed somewhere, by default it
    # would be the location bash-completion uses.
    #
    # If your script is somewhere else, you can configure it on your ~/.zshrc:
    #
    #  zstyle ':completion:*:*:git:*' script ~/.git-completion.zsh
    #
    # The recommended way to install this script is to make a copy of it in
    # ~/.zsh/ directory as ~/.zsh/git-completion.zsh and then add the following
    # to your ~/.zshrc file:
    #
    #  fpath=(~/.zsh $fpath)
    
    complete ()
    {
            # do nothing
            return 0
    }
    
    zstyle -T ':completion:*:*:git:*' tag-order && \
            zstyle ':completion:*:*:git:*' tag-order 'common-commands'
    
    zstyle -s ":completion:*:*:git:*" script script
    if [ -z "$script" ]; then
            local -a locations
            local e
            locations=(
                    $(dirname ${funcsourcetrace[1]%:*})/git-completion.bash
                    '/etc/bash_completion.d/git' # fedora, old debian
                    '/usr/share/bash-completion/completions/git' # arch, ubuntu, new debian
                    '/usr/share/bash-completion/git' # gentoo
                    )
            for e in $locations; do
                    test -f $e && script="$e" && break
            done
    fi
    GIT_SOURCING_ZSH_COMPLETION=y . "$script"
    
    __gitcomp ()
    {
            emulate -L zsh
    
            local cur_="${3-$cur}"
    
            case "$cur_" in
            --*=)
                    ;;
            *)
                    local c IFS=$' \t\n'
                    local -a array
                    for c in ${=1}; do
                            c="$c${4-}"
                            case $c in
                            --*=*|*.) ;;
                            *) c="$c " ;;
                            esac
                            array+=("$c")
                    done
                    compset -P '*[=:]'
                    compadd -Q -S '' -p "${2-}" -a -- array && _ret=0
                    ;;
            esac
    }
    
    __gitcomp_direct ()
    {
            emulate -L zsh
    
            local IFS=$'\n'
            compset -P '*[=:]'
            compadd -Q -- ${=1} && _ret=0
    }
    
    __gitcomp_nl ()
    {
            emulate -L zsh
    
            local IFS=$'\n'
            compset -P '*[=:]'
            compadd -Q -S "${4- }" -p "${2-}" -- ${=1} && _ret=0
    }
    
    __gitcomp_nl_append ()
    {
            emulate -L zsh
    
            local IFS=$'\n'
            compadd -Q -S "${4- }" -p "${2-}" -- ${=1} && _ret=0
    }
    
    __gitcomp_file_direct ()
    {
            emulate -L zsh
    
            local IFS=$'\n'
            compset -P '*[=:]'
            compadd -f -- ${=1} && _ret=0
    }
    
    __gitcomp_file ()
    {
            emulate -L zsh
    
            local IFS=$'\n'
            compset -P '*[=:]'
            compadd -p "${2-}" -f -- ${=1} && _ret=0
    }
    
    __git_zsh_bash_func ()
    {
            emulate -L ksh
    
            local command=$1
    
            local completion_func="_git_${command//-/_}"
            declare -f $completion_func >/dev/null && $completion_func && return
    
            local expansion=$(__git_aliased_command "$command")
            if [ -n "$expansion" ]; then
                    words[1]=$expansion
                    completion_func="_git_${expansion//-/_}"
                    declare -f $completion_func >/dev/null && $completion_func
            fi
    }
    
    __git_zsh_cmd_common ()
    {
            local -a list
            list=(
            add:'add file contents to the index'
            bisect:'find by binary search the change that introduced a bug'
            branch:'list, create, or delete branches'
            checkout:'checkout a branch or paths to the working tree'
            clone:'clone a repository into a new directory'
            commit:'record changes to the repository'
            diff:'show changes between commits, commit and working tree, etc'
            fetch:'download objects and refs from another repository'
            grep:'print lines matching a pattern'
            init:'create an empty Git repository or reinitialize an existing one'
            log:'show commit logs'
            merge:'join two or more development histories together'
            mv:'move or rename a file, a directory, or a symlink'
            pull:'fetch from and merge with another repository or a local branch'
            push:'update remote refs along with associated objects'
            rebase:'forward-port local commits to the updated upstream head'
            reset:'reset current HEAD to the specified state'
            restore:'restore working tree files'
            rm:'remove files from the working tree and from the index'
            show:'show various types of objects'
            status:'show the working tree status'
            switch:'switch branches'
            tag:'create, list, delete or verify a tag object signed with GPG')
            _describe -t common-commands 'common commands' list && _ret=0
    }
    
    __git_zsh_cmd_alias ()
    {
            local -a list
            list=(${${${(0)"$(git config -z --get-regexp '^alias\.')"}#alias.}%$'\n'*})
            _describe -t alias-commands 'aliases' list $* && _ret=0
    }
    
    __git_zsh_cmd_all ()
    {
            local -a list
            emulate ksh -c __git_compute_all_commands
            list=( ${=__git_all_commands} )
            _describe -t all-commands 'all commands' list && _ret=0
    }
    
    __git_zsh_main ()
    {
            local curcontext="$curcontext" state state_descr line
            typeset -A opt_args
            local -a orig_words
    
            orig_words=( ${words[@]} )
    
            _arguments -C \
                    '(-p --paginate --no-pager)'{-p,--paginate}'[pipe all output into ''less'']' \
                    '(-p --paginate)--no-pager[do not pipe git output into a pager]' \
                    '--git-dir=-[set the path to the repository]: :_directories' \
                    '--bare[treat the repository as a bare repository]' \
                    '(- :)--version[prints the git suite version]' \
                    '--exec-path=-[path to where your core git programs are installed]:: :_directories' \
                    '--html-path[print the path where git''s HTML documentation is installed]' \
                    '--info-path[print the path where the Info files are installed]' \
                    '--man-path[print the manpath (see `man(1)`) for the man pages]' \
                    '--work-tree=-[set the path to the working tree]: :_directories' \
                    '--namespace=-[set the git namespace]' \
                    '--no-replace-objects[do not use replacement refs to replace git objects]' \
                    '(- :)--help[prints the synopsis and a list of the most commonly used commands]: :->arg' \
                    '(-): :->command' \
                    '(-)*:: :->arg' && return
    
            case $state in
            (command)
                    _alternative \
                             'alias-commands:alias:__git_zsh_cmd_alias' \
                             'common-commands:common:__git_zsh_cmd_common' \
                             'all-commands:all:__git_zsh_cmd_all' && _ret=0
                    ;;
            (arg)
                    local command="${words[1]}" __git_dir
    
                    if (( $+opt_args[--bare] )); then
                            __git_dir='.'
                    else
                            __git_dir=${opt_args[--git-dir]}
                    fi
    
                    (( $+opt_args[--help] )) && command='help'
    
                    words=( ${orig_words[@]} )
    
                    __git_zsh_bash_func $command
                    ;;
            esac
    }
    
    _git ()
    {
            local _ret=1
            local cur cword prev
    
            cur=${words[CURRENT]}
            prev=${words[CURRENT-1]}
            let cword=CURRENT-1
    
            if (( $+functions[__${service}_zsh_main] )); then
                    __${service}_zsh_main
            else
                    emulate ksh -c __${service}_main
            fi
    
            let _ret && _default && _ret=0
            return _ret
    }
    
    _git
    
    
    Run Code Online (Sandbox Code Playgroud)