How do I get bash completion to work with aliases?

Viewed 35110

Case in point:

I'm a on mac with bash v3.2.17, I'm using git installed via macports with the bash_completion variant.

When I type git checkout m<tab>. for example, I get it completed to master.

However, I've got an alias to git checkout, gco. When I type gco m<tab>, I don't get the branch name autocompleted.

Ideally I'd like autocompletion to just magically work for all my aliases. Is it possible? Failing that, I'd like to manually customize it for each alias. So, how do I go about either?

15 Answers

In git-completion.bash there is a line:

complete -o default -o nospace -F _git git

Looking at that line (and the _git function) you can add this line to your .bash_profile:

complete -o default -o nospace -F _git_checkout gco

You could also try using Git aliases. For example, in my ~/.gitconfig file, I have a section that looks like this:

[alias]
        co = checkout

So you could type git co m<TAB>, and that should expand to git co master, which is the git checkout command.

This forum page shows a solution.

Put these lines into your .bashrc or .bash_profile:

# Author.: Ole J
# Date...: 23.03.2008
# License: Whatever

# Wraps a completion function
# make-completion-wrapper <actual completion function> <name of new func.>
#                         <command name> <list supplied arguments>
# eg.
#   alias agi='apt-get install'
#   make-completion-wrapper _apt_get _apt_get_install apt-get install
# defines a function called _apt_get_install (that's $2) that will complete
# the 'agi' alias. (complete -F _apt_get_install agi)
#
function make-completion-wrapper () {
    local function_name="$2"
    local arg_count=$(($#-3))
    local comp_function_name="$1"
    shift 2
    local function="
function $function_name {
    ((COMP_CWORD+=$arg_count))
    COMP_WORDS=( "$@" \${COMP_WORDS[@]:1} )
    "$comp_function_name"
    return 0
}"
    eval "$function"
}

# and now the commands that are specific to this SO question

alias gco='git checkout'

# we create a _git_checkout_mine function that will do the completion for "gco"
# using the completion function "_git"
make-completion-wrapper _git _git_checkout_mine git checkout

# we tell bash to actually use _git_checkout_mine to complete "gco"
complete -o bashdefault -o default -o nospace -F _git_checkout_mine gco

This solution is similar to balshetzer's script, but only this one actually works for me. (balshetzer's script had problems with some of my aliases.)

First, look up the original completion command. Example:

$ complete | grep git

complete -o bashdefault -o default -o nospace -F __git_wrap__git_main git

Now add these to your startup script (e.g. ~/.bashrc):

# copy the original statement, but replace the last command (git) with your alias (g)
complete -o bashdefault -o default -o nospace -F __git_wrap__git_main g

# load dynamically loaded completion functions (may not be required)
_completion_loader git

The _completion_loader line may not be required. But for some situations, the completion function is only loaded dynamically after you type the command and press TAB the first time. So if you haven't used the original command, and try the alias + TAB, you may get an error like "bash: completion: function '_docker' not found".

There are a lot of answers to this question and like myself I bet a lot of confused readers. For my case I had also had the requirement to have my dotfiles work on multiple platforms with different versions of Git. I also don't alias g=git but instead have g defined as a function.

To accomplish this I had to slap together different answers here into one solution. Although this reiterates the answers already I thought someone in my boat might find this compilation useful as I would have when I first came to this question.

This assumes older and newer Git completion, Ubuntu defaults, and brew install git on MacOS. In the later case the brew installed completions were not being processed by bash (something I will diagnose later).

# Alias g to git

g() {
  if [[ $# > 0 ]]; then
    git "$@"
  else
    git status -sb
  fi
}

# Preload git completion in Ubuntu which is normally lazy loaded but we need
# the __git_wrap__git_main function available for our completion.
if [[ -e /usr/share/bash-completion/completions/git ]]; then
  source /usr/share/bash-completion/completions/git
elif [[ -e /usr/local/etc/bash_completion.d/git-completion.bash ]]; then
  source /usr/local/etc/bash_completion.d/git-completion.bash
fi

if command_exists __git_complete; then
  __git_complete g _git
elif command_exists __git_wrap__git_main; then
  complete -o bashdefault -o default -o nospace -F __git_wrap__git_main g
fi

Felipe Contreras, who is already quite active for Git completion features (see Zsh completion in Git 2.30) proposes (for -- possibly -- Git 2.31, Q1 2021) a public function which will help with alias autocompletion.

His proposal:

Back in 2012 I argued for the introduction of a helper that would allow users to specify aliases like:

git_complete gf git_fetch

Back then there was pushback because there was no clear guideline for public functions (git_complete vs _git_complete vs _GIT_complete), and some aliases didn't actually work.

Fast-forward to 2020 and there's still no guideline for public functions, and those aliases still don't work (even though I sent the fixes).

This has not prevented people from using this function that is clearly needed to setup custom aliases (this page), and in fact it's the recommended way.

But it is cumbersome that the user must type:

__git_complete gf _git_fetch

Or worse:

__git_complete gk __gitk_main

8 years is more than enough time to stop waiting for the perfect to come; let's define a public function (with the same name) that is actually user-friendly:

__git_complete gf git_fetch
__git_complete gk gitk

While also maintaining backwards compatibility.

The logic is:

  1. If $2 exists, use it directly
  2. If not, check if __$2_main exists
  3. If not, check if _$2 exists
  4. If not, fail

You can bind Tab to alias-expand-line and complete (its default action) in ~/.inputrc. To do that you first need to bind each action to a key and then chain them together thus:

"\M-z":alias-expand-line
"\M-x":complete
TAB:"\M-z\M-x"

You can use whatever key combinations you like, I use the Meta one because it is free. See man 3 readline for more information.

Now, if you open a new terminal and type the alias:

gco m<TAB>

The line will be transformed into

git checkout master

Of course, Tab will still work as usual even if no alias is involved.

If you use alias g='git', i add this line of code in .bash_aliases

complete -o default -o nospace -F _git g

I know this question is about bash but in case you're using zsh this change to @hesky-fisher answer will fix it.

From

# For each defined alias, extract the necessary elements and use them
# to call wrap_alias.
eval "$(alias -p | sed -e 's/alias \([^=][^=]*\)='\''\([^ ][^ ]*\) *\(.*\)'\''/wrap_alias \1 \2 '\''\3'\'' /')"

to:

# For each defined alias, extract the necessary elements and use them
# to call wrap_alias.
eval "$(alias | sd '^([^=]+)=(.+)' 'wrap_alias $1=$2')"

You need to have sd (this can be replaced by sed but sd is much better in my opinion)

This is done because zsh doesn't have alias -p and alias output in zsh doesn't output

alias <something>=<value>

like in bash, but rather

<something>=<value>

BTW, I put the init code in the .zlogin file

Related