Extending git's bash completion for your own commands

Posted on Sun 08 November 2015 in Extending git

Git has pretty decent bash completion, and you can easily extend it for your own git scripts. It also provides some useful bash functions for making writing your bash completions easy. In this article we'll write a bash completion function for the fictional git-frotz command, which behaves like this:

$ git fritz -h
Fix your repos that are on the fritz

Usage:
    git fritz [--branch=HEAD] [--noop] (wibble|wobble)
    git fritz shimmy <file>

The basics

To integrate your command with git, its name should start with git-, it should be on your $PATH and it should have a manpage.

$ mv git-fritz ~/bin
$ exqport PATH="~/bin:$PATH"
$ PAGER= git fritz --help
GIT-FRITZ(1)                        Git Manual                       GIT-FRITZ(1)

NAME
       git-fritz - Fix your repos that are on the fritz

SYNOPSIS
       git fritz [--branch=HEAD] [--noop] (wibble|wobble)
       git fritz shimmy <file>

DESCRIPTION
       A description would go here

Showing the manpage when you ask for --help is one of the things git does for commands that integrate. No need to write code yourself. Now let's write our completion.

You don't actually have to write a full completion function, git's own completion functions will call the shell function _git_fritz automatically when completing a git fritz command. So all you need to do is create such a function and make sure it's loaded when your shell starts. So we'll put it in a file that we'll load from ~/.bashrc.

_git_fritz() {
    # Actual code goes here
    ...
    # And we'll make bash not use its default filename completion
    compopt +o default
}

The compopt call is to prevent bash from doing filename completion if your completion function returns nothing.

Handling options

So now let's do something: we'll handle our options using the helper functions from git's bash completion.

_git_fritz() {
    case "${cur}" in
        --branch=*)
            __gitcomp_nl "$(__git_heads)"
            ;;
        --*)
            __gitcomp "--branch= --noop --help"
            ;;
    esac
    # And we'll make bash not use its default filename completion
    compopt +o default
}

Here you see three of the utility functions git's bash completion provides:

  • __gitcomp makes it easy to specify possible completions in a string
  • __gitcomp_nl does the same for strings where options are separated by newlines, which means you can easily use outputs of other git commands
  • __git_heads gives you all branches, in a format usable by the bash completion.

Handling positional arguments

Handling positional arguments is not much harder than options. So let's complete our bash completion.

_git_fritz() {
    if [ -n "$(__git_find_on_cmdline shimmy)" ]; then
        _filedir
        return
    fi
    case "${cur}" in
        --branch=*)
            __gitcomp_nl "$(__git_heads)"
            ;;
        --*)
            __gitcomp "--branch= --noop --help"
            ;;
        *)
            __gitcomp "wibble wobble shimmy"
    esac
    # And we'll make bash not use its default filename completion
    compopt +o default
}

If the user has already provided shimmy as an argument, we'll let bash do its filename completion. Otherwise we'll the options completion we wrote before, but also complete the wibble/wobble/shimmy argument.

That's it! Our bash completion for git-fritz is now complete. For more examples of git completions, look at git's own bash completion, where you can also find all the helper functions I used and more.

For a more complicated third party command with bash-completion integration, you can look at git-spindle, which provides integration with GitHub, GitLab and BitBucket.