java_user
java_user

Reputation: 85

How to enable auto-completion of git commands if you are using aliases in .bash_profile?

I have git aliases in .bash_profile and would like to enable auto completion when I use these commands (eg: auto complete branch names). This works for me if I use the full git commands but not when I use aliases.

I've heard that auto completion is possible if you use aliases configured through .gitconfig but those aliases force the use of git whereas the aliases I've configured in .bash_profile have "git" substituted as "g" which is more convenient.

Upvotes: 3

Views: 1605

Answers (1)

mmlr
mmlr

Reputation: 2145

The bash completions for various commands usually come in a bash_completions package or are provided with the individual commands. Where they are installed is somewhat OS dependent, in Ubuntu they are collected in various places, starting at /etc/bash_completion. This script is sourced from the global bash.bashrc and ultimately pulls in various other scripts that define completions for various commands. Ultimately they all call the complete command to define when to invoke what completion.

The completions for git (again in Ubuntu, locations may vary) are installed to /usr/share/bash-completion/completions/git. This file comes from the git source repository and defines functions for completions of the individual commands and ultimately a __git_complete function that takes a command and a completion function and installs the completion for that command:

__git_complete git __git_main

The above adds completions for git to call __git_main (also defined in that file). As all of the completions are sourced, the used functions are all available in your shell and you can therefore use the same call to add git completions to your alias:

__git_complete g __git_main

If your alias wraps a certain git subcommand instead, use one of the various subcommand completions defined in that file. If you had an alias ga to mean git add you would use _git_add completions instead:

__git_complete ga _git_add

Note that the __git_complete function (as presumably all of the functions in that file) are private and not meant to be used directly. The function is labeled with a corresponding warning:

This is NOT a public function; use at your own risk.

Which basically means, updating your git and/or bash_completions may change that completions file and how the above functions are named and work may change, which would then break your setup. You should therefore only use the above if you are prepared to redo that setup every so often.

Note also that you might have to explicitly source that git completions file from your .bashrc or .profile for the functions to become available:

source /usr/share/bash-completion/completions/git

Special Cases

Some of the git completions actually inspect the current command line to decide what to complete next. Examples of that are anything that complete remotes or refspecs, i.e. fetch, pull, remote and push. In such cases it isn't enough to complete the alias with the corresponding function, as the alias will hide the actual git command and the completion will therefore not know how to proceed. As an example from the comments:

$ alias gpr='git pull --rebase'
$ __git_complete gpr _git_pull
$ gpr o[TAB]
$ gpr origin m[TAB]
$ gpr origin m

In this case the remote (origin in this case) can still be completed, but the refspec fails to complete. This is due to the missing "pull" command on the command line.

This case cannot fully be solved, but a workaround that requires an extra tab can be built. Instead of directly hooking into the git completion, a wrapper function is defined that first expands the alias with the wanted command line and then passes on for further completions:

function _gpr {
    if [ $COMP_CWORD = 1 ]
    then
        COMPREPLY=('pull --rebase ')
        return 0
    fi

    __git_func_wrap __git_main
}

The wrapper function checks if the current word is the first one and in that case sets the COMPREPLY variable to the desired command line expansion (see the documentation for programmable completion for the exact details). Note the extra space at the end that is needed because the function will be installed with the nospace option that prevents automatic insertion of a space.

The wrapper function is then installed as a completion for the desired alias:

complete -o bashdefault -o default -o nospace -F _gpr gpr

The -F _gpr arguments specify the wrapper function to be called and the last argument (gpr) the command to be completed (see the full documentation of the complete builtin for the other options).

With the wrapper completion in place, the above session now becomes:

$ gpr [TAB]
$ gpr pull --rebase o[TAB]
$ gpr pull --rebase origin m[TAB]
$ gpr pull --rebase origin master

An extra tab is needed right after the alias to first expand to the desired command line that was previously fully hidden in the alias. From there on, the completions work as expected.

Note that, since the desired command line is now part of the actual command line, the alias should only point at git and not at git pull --rebase. This is also the reason why the wrapper function always passes the completion on to __git_main and not the individual subcommand completion. The alias definition becomes just:

alias gpr=git

Combined shell and git Aliases

A nice alternative to the above special case is to combine shell and git aliases. The git completions expand git aliases (i.e. aliases created with git config alias.<name> '<command>') for the purpose of completion. Configuring a git alias for the actual command:

$ git config --global alias.pr 'pull --rebase'

And then installing a shorthand shell alias for git:

$ alias g=git

And hooking that up to __git_main completion using the original method:

$ __git_complete g __git_main

Allows for this session:

$ g pr o[TAB]
$ g pr origin m[TAB]
$ g pr origin master

Upvotes: 8

Related Questions