4

I have git repository in /.git, ie root of my filesystem.

When I am in /etc/foo/ and do git status, git tells me that file ../fstab has changed.

When I want to use zsh completion for git command (still in /etc/foo/), ie:

git diff ../fs<TAB>

that works. But when I use absolute path, ie:

git diff /etc/fs<TAB>

then zsh does not complete the absolute path.

How can I tell zsh to complete absolute paths, as well as relative paths ?

I am using zsh version 5.7.1-1 on Debian Buster.

2 Answers 2

3

ZSH Completion:

Zsh completion are done with scripts usually located at /usr/share/zsh/5.5/functions/Completion/Unix (may differ depending on the distro) each command completion's script is named _commandName, Zsh include/handle those scripts with the environment variable $fpath similar to the variable $PATH, in this case the used script is _git, the location order on $fpath is important as Zsh use the first _git script that it find and ignore the others if present (also similar to $PATH).

Scripting:

Like explained on this QA and as an example, the following function prepends $PWD/ to any relative path before passing it to _files, which is the normal completion function for files.

_absolute_files () {
  local expansion=$PREFIX$SUFFIX; expansion=${(e)expansion}
  if [[ "${expansion%%/#}" != "${expansion:a}" ]]; then
    PREFIX="\$PWD/$PREFIX"
  fi
  _files "$@";
}

This works in many common cases, including recognizing paths starting with ~/ and such as absolute...

Solution:

The default git completion behavior does not include relative paths, we could edit its script and add a function like the one on the explanation above to add support for relative path or we could simply replace the default git completion with the completion plugin gitfast from ohmyzsh with the following steps:

Clone ohmyzsh to some location (let say /location):

git clone https://github.com/ohmyzsh/ohmyzsh.git

Edit ~/.zshrc and add at the bottom of the config file the following to include gitfast:

fpath=( /location/ohmyzsh/plugins/gitfast $fpath ) 

The order is important as explained before and here.

Update the completion cache by removing any ~/.zcompdump* then run compinit.

Alternative solution:

Editing /usr/share/zsh/5.5/functions/Completion/Unix/_git by applying the following patch on _git-diff function:

--- _git
+++ _git
@@ -766,6 +766,12 @@

   case $state in
     (from-to-file)
+  
+      if [[ $line[1] == *\/* ]]; then
+        _alternative 'files::_files' && ret=0
+        return ret
+      fi
+      
       # If "--" is part of $opt_args, this means it was specified before any
       # $words arguments. This means that no heads are specified in front, so
       # we need to complete *changed* files only.

3

Zsh can complete absolute paths just fine. Just try ls /etc/fsTab.

The problem lies with whatever _git completer you are using. (See footnote below.)

You can test this as follows:

  1. Make sure you have initialized compinit:
autoload -Uz compinit && compinit
  1. Type git diff (with a space at the end) and press CtrlX, followed by the letter H.

I get the following output:

tags in context :completion::complete:git-diff::
    argument-rest options  (_arguments _git-diff _git)
tags in context :completion::complete:git-diff:argument-rest:
    commit-ranges blobs-and-trees-in-treeish files blobs  (_git-diff _git) 
    heads commit-tags commit-objects                      (__git_commits __git_commit_ranges _git-diff _git) 
    heads-local heads-remote                              (__git_heads __git_commits __git_commit_ranges _git-diff _git) 
    messages                                              (_message __git_command_successful __git_heads_local __git_heads __git_commits __git_commit_ranges _git-diff _git) 
    heads-local                                           (__git_describe_branch __git_describe_commit __git_heads_local __git_heads __git_commits __git_commit_ranges _git-diff _git) 
    heads-remote                                          (__git_describe_branch __git_describe_commit __git_heads_remote __git_heads __git_commits __git_commit_ranges _git-diff _git) 
    messages                                              (_message __git_command_successful __git_tags_of_type __git_commit_tags __git_commits __git_commit_ranges _git-diff _git) 
    messages                                              (_message __git_command_successful __git_recent_commits __git_commit_objects_prefer_recent __git_commits __git_commit_ranges _git-diff _git) 
    changed-in-working-tree-files                         (__git_changed-in-working-tree_files _git-diff _git) 
    blob-tags blob-objects                                (__git_blobs _git-diff _git) 
    messages                                              (_message __git_command_successful __git_tags_of_type __git_blob_tags __git_blobs _git-diff _git)
tags in context :completion::complete:git::
    argument-rest  (_arguments _git)

Note that this does not include files, directories or globbed-files (which you will see when you type ls, followed by CtrlX+H). What it does include is changed-in-working-tree-files, which are generated by git diff -z --name-only --no-color (as you can see here), which produces paths that are relative to the root of the repo and not absolute paths.

Ergo, your absolute path will not get completed in the git context as it does not have the info there to do so.


Footnote: Are you using Zsh's own _git completer or one that might have been installed by Git? I highly recommend not using the one installed by Git, as it is not properly implemented by Zsh standards and thus rather buggy in certain parts. (It appears to be some kind of automatic – and flawed – port from Git's completer for Bash.) Check your fpath for any completer file called _git that doesn't belong to Zsh's installation and remove it.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .