I track my dotfiles using this method:

  • A bare repository resides in $HOME/repos/dotfiles.
  • All my dotfiles reside in their normal location, e.g. $HOME/.vim/vimrc, not $HOME/repos/dotfiles/vimrc.
  • I run git --git-dir=$HOME/repos/dotfiles --work-tree=$HOME ... to manage things.

(Actually, I have a function g() that expands to the above command when I'm in $HOME, and to just git otherwise.)

Everything works great, except...

The problem: Zsh git filename completion doesn't work.


% pwd
% g status                                                                                                                                         ~
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   .vim/vimrc
        modified:   .xmonad/xmonad.hs

no changes added to commit (use "git add" and/or "git commit -a")
% g add <Tab>
Completing not a git repository

(The "Completing ..." stuff is due to zstyle ':completion:*' format $'%{\e[0;31m%}Completing %B%d%b%{\e[0m%}'.)

Notably, it would not suffice to somehow tell zsh's git completion to "move to/follow" the given value of --work-tree, as if git were being called from that directory, because explicitly doing that doesn't work either:

% cd repos/dotfiles
% g status
fatal: This operation must be run in a work tree
% g add <Tab>
Completing not a git repository

The question: Is there an easy way to get extend zsh's git completion to this kind of a case?


Sadly, its a bug in zsh completion for git. You can find discussion on 'zsh' mailing list here.

Daniel Shahaf did provided a patch for '_git':

diff --git a/Completion/Unix/Command/_git b/Completion/Unix/Command/_git
index 518e6d198..45a0fa622 100644
--- a/Completion/Unix/Command/_git
+++ b/Completion/Unix/Command/_git
@@ -6609,20 +6609,33 @@ __git_files_relative () {
 (( $+functions[__git_files] )) ||
 __git_files () {
   local compadd_opts opts tag description gitcdup gitprefix files expl
+  local pref

   zparseopts -D -E -a compadd_opts V: J: 1 2 n f X: M: P: S: r: R: q F:
   zparseopts -D -E -a opts -- -cached -deleted -modified -others -ignored -unmerged -killed x+: --exclude+:
   tag=$1 description=$2; shift 2

-  gitcdup=$(_call_program gitcdup git rev-parse --show-cdup 2>/dev/null)
-  __git_command_successful $pipestatus || return 1
+  case $(_call_program gitinworktree git rev-parse --is-inside-work-tree 2>/dev/null) in
+    (true)
+      gitcdup=$(_call_program gitcdup git rev-parse --show-cdup 2>/dev/null)
+      __git_command_successful $pipestatus || return 1

-  gitprefix=$(_call_program gitprefix git rev-parse --show-prefix 2>/dev/null)
-  __git_command_successful $pipestatus || return 1
+      gitprefix=$(_call_program gitprefix git rev-parse --show-prefix 2>/dev/null)
+      __git_command_successful $pipestatus || return 1
+      local pref=$gitcdup$gitprefix$PREFIX
+      ;;
+    (false)
+      local pref=
+      ;;
+    (*)
+      # XXX what to do?
+      return 1
+      ;;
+  esac

   # TODO: --directory should probably be added to $opts when --others is given.

-  local pref=$gitcdup$gitprefix$PREFIX

   # First allow ls-files to pattern-match in case of remote repository
   files=(${(0)"$(_call_program files git ls-files -z --exclude-standard ${(q)opts} -- ${(q)${pref:+$pref\\\*}} 2>/dev/null)"})
@@ -7585,7 +7598,8 @@ _git() {
-       (( $+opt_args[--git-dir] )) && local -x GIT_DIR=$opt_args[--git-dir]
+        (( $+opt_args[--git-dir] )) && local -x GIT_DIR=${(e)opt_args[--git-dir]}
+        (( $+opt_args[--work-tree] )) && local -x GIT_WORK_TREE=${(e)opt_args[--work-tree]}
        if ! _call_function ret _git-$words[1]; then
          if zstyle -T :completion:$curcontext: use-fallback; then
            _default && ret=0

It applies clearly to zsh 5.4.1 but it didn't work form me,YMMV.

I'll update this answer as the issue is being worked on.


With the above patch it does work, but where you put add is important - it has to be at the end:

git --git-dir=$HOME/.dotfiles --work-tree=$HOME/ add

One more thing worth noticing - its somewhat slow.

  • Thanks, but unfortunately the patch isn't working for me. I still get "not a git repository", even when taking care to put add at the end. I'll keep an eye on the mailing list, though, and hope that the issue gets satisfactorily resolved. – Brian Buccola Aug 12 '17 at 7:46

The lastest (2018-04-20) official git completion works well.


To install,

mkdir -p ~/.zsh && cd $_
wget https://raw.githubusercontent.com/git/git/master/contrib/completion/git-completion.bash
wget https://raw.githubusercontent.com/git/git/master/contrib/completion/git-completion.zsh
wget https://raw.githubusercontent.com/git/git/master/contrib/completion/git-prompt.sh

# The default behavior for 'git <TAB><TAB>' is to show a 10+ lines 'common commands' list.
# I prefer a full list.
# Better way to modify?
sed -i.bak '/^__git_zsh_cmd_common/,/{/ s/{/{\n\treturn/' _git

# add zshrc settings
vi ~/.zshrc
  fpath=(~/.zsh $fpath)
  # the next two lines are not needed if using oh-my-zsh
  autoload -Uz compinit


My previous answer is about oh-my-zsh's plugin gitfast. But it has bugs, and it's very outdated.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.