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

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


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

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 indexbisect    -- find by binary search the change that introduced a bug...% which completecomplete () {        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 indexbisect    -- 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 completecomplete () {        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.bashzsh: 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/_tigtig-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/_gitgit-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 <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 scriptif [ -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        donefiGIT_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


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.


In your comment, you wrote, that you have installed the completion system from https://github.com/git/git/blob/master/contrib/completion/git-completion.bash , but for zsh, the correct sources should be the ones at https://github.com/git/git/blob/master/contrib/completion/git-completion.zsh

According to the installation instruction: git-completion.zsh must be sourced from .zshrc. The bash version must be accessible from it.