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 aftergit
, thentig
completion works andgit
completion is broken. - If
tig
completion is activated beforegit
, 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
- initial state. working.
complete
not defined.% which complete
- 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}
- not working.
complete
function defined referencing bashcomplete () { 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:
- git-completion.bash
- git-completion.zsh (as _git)
- tig-completion.bash
- 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.