Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

git completion in zsh: __git_func_wrap:3: : not found

git-completion.zsh and git-completion.bash are installed automatically when running 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

/usr/local/share/zsh/site-functions is included in 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

For some reasons, sometimes when I type git reba and press 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

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
}

What the default completion is:

❯ complete -p git
complete -o bashdefault -o default -o nospace -F __git_wrap_tig tig
complete _bash bash

Continue inspect:

❯ type __git_wrap_tig
__git_wrap_tig is a shell function from /usr/local/share/zsh/site-functions/tig-completion.bash

The thing is I cannot find this function in 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

Looks like there is some changed recently: https://github.com/jonas/tig/commit/26ab51d28133354bfaa94d064bff37d29b3c30e3

but where is __git_wrap_tig function?

PS: As I said above, this problem is not happen every time. Sometimes, when I opened a new tab and check the default completion and it is just:

❯ complete -p git
complete _bash bash

and git completion worked as expected.


Reply to @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

and one more invocation of __git_complete is in 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 
like image 961
quanta Avatar asked Jul 07 '20 01:07

quanta


People also ask

What is git completion zsh?

fpath : The git-completion. zsh is a function file, not designed to be sourced like the bash script. This command appends the ~/. zsh directory onto the shell's function lookup list.

How do I use autocomplete in zsh?

zsh-autocomplete adds real-time type-ahead autocompletion to Zsh. Find as you type, then press Tab to insert the top completion, Shift Tab to insert the bottom one, or ↓ / PgDn to select another completion.

Can I use bash completion in zsh?

for ZSH usersZsh can handle bash completions functions. The latest development version of zsh has a function bashcompinit, that when run will allow zsh to read bash completion specifications and functions.


2 Answers

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.

  • https://raw.githubusercontent.com/jonas/tig/91912eb97da4f6907015dab41ef9bba315730854/contrib/tig-completion.zsh
  • https://raw.githubusercontent.com/jonas/tig/c72aa4dab21077231a97dcca8e3821d7b35fe7db/contrib/tig-completion.bash
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

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
    
  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
    }
    
  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
        }
    

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

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

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

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*

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

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
    }
    
  • tig-completion.bash
    #compdef git gitk
    
    # zsh completion wrapper for git
    #
    # Copyright (c) 2012-2013 Felipe Contreras <[email protected]>
    #
    # 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
    
    
like image 63
spazm Avatar answered Sep 25 '22 00:09

spazm


The __git_complete function contains this:

__git_complete ()
{
    local wrapper="__git_wrap${2}"
    eval "$wrapper () { __git_func_wrap $2 ; }"
}

The new code of tig calls it like this:

__git_complete tig _tig 

This code is effectively creating a wrapper called __git_wrap_tig.

__git_wrap_tig { __git_func_wrap _tig }

However, these functions are not meant to be used by the Zsh completion.

The Zsh completion (latest version here) is meant to be sourcing the bash completion by itself, and then calling __tig_main directly, bypassing any wrappers.

The problem is that the main tig function must be called __tig_main, not _tig. I've sent a patch to fix this and other discrepancies with the official git completion.

If you install all the latest relevant files:

  1. git-completion.bash
  2. git-completion.zsh (as _git)
  3. tig-completion.bash
  4. tig-completion.zsh (as _tig)

Into your fpath (e.g. ~/.zsh/), it should work correctly.

like image 24
FelipeC Avatar answered Sep 21 '22 00:09

FelipeC