ZSH autocompletion for git unstage
30 Aug 2012I recently switched to git as my main version control system (I used to use Mercurial before that). I quickly grasped the concept of the staging area and used the git add
command extensively to add before committing.
And zsh ships with nice git autocompletion features, and suggests files to add with git add
when you press Tab.
Git unstage
What is missing from the basic git commands is a way to unstage a file from the staging area. Well, I can do it with git reset HEAD
, but this command is a bit tedious to type.
So I created an alias, named git unstage
that does just that. I just added the following lines to my ~/.gitconfig
, under the [alias]
header :
unstage = reset HEAD
I can now easily add files to the staging area with git add
and remove them with git unstage
Autocompletion
At this point, git unstage
autocompletion does not work. It simply suggest all files in the current directory, while I'd like it to suggest only files in the staging area.
When you create a git alias, you cannot simply add a zsh autocomplete method for that alias (meaning, you cannot create a _git-unstage
method), you have to hook your custom autocomplete logic to the underlying git method your alias refers to. In this case, this is the git reset
method.
So, I created my own _git-reset
autocompletion function. Actually, I borrowed the one already defined in /usr/share/zsh/functions/Completion/Unix/_git
and tweaked it a little bit.
I created a file named _git-reset
and put it in my $FPATH
so zsh will load it when asked for autocompletion and it will overwrite the _git-reset
method already defined in _git
.
Here is the full content of the file :
#compdef git-reset
_git-reset () {
local curcontext=$curcontext state line
typeset -A opt_args
_arguments -C -S -A '-*' \
'(-q --quiet)'{-q,--quiet}'[be quiet, only report errors]' \
'::commit:__git_revisions' \
- reset-head \
'( --soft --hard --merge --keep)--mixed[reset the index but not the working tree (default)]' \
'(--mixed --hard --merge --keep)--soft[do not touch the index file nor the working tree]' \
'(--mixed --soft --merge --keep)--hard[match the working tree and index to the given tree]' \
'(--mixed --soft --hard --keep)--merge[reset out of a conflicted merge]' \
'(--mixed --soft --hard --merge )--keep[like --hard, but keep local working tree changes]' \
- reset-paths \
'(-p --patch)'{-p,--patch}'[select diff hunks to remove from the index]' \
'*::file:->files' && ret=0
case $state in
(files)
local commit
if [[ -n $line[1] ]] && __git_is_committish $line[1]; then
commit=$line[1]
else
commit=HEAD
fi
# Suggest files in index if `git reset HEAD`
if [[ $line[1] = HEAD ]]; then
__git_changed_files
else
__git_tree_files . $commit
fi
ret=0
;;
esac
}
_git-reset "$@"
As you may have noticed this script is an almost exact copy/paste from the original _git-reset
script. The only modification I've done is in those lines :
# Suggest files in index if `git reset HEAD`
if [[ $line[1] = HEAD ]]; then
__git_changed_files
else
__git_tree_files . $commit
fi
What it does is checking the first argument of git reset
, and if it's HEAD
, it suggests files in the staging area (__git_changed_files
) instead of files in the current repo (__git_tree_files
).
It took me quite a bit of time to figure out which method to use to get files in the staging area as I had been looking for a __git_staged_files
for quite a while and finally discovered that the __git_changed_files
was actually what I was looking for.
Conclusion
The git unstage
alias is quite common, you can find it in a lot of books and websites teaching git. But it becomes much more usable once zsh does the autocomplete for you and you can easily select files to unstage that way.
Want to add something ? Feel free to get in touch on Twitter : @pixelastic