Override bash completion for git clone

Anthony Sottile

builtin completion

The default completion for git clone (reproduced below) gives tab completion for --* options:

_git_clone ()
{
    case "$cur" in
    --*)
        __gitcomp_builtin clone
        return
        ;;
    esac
}

bash-completion 1.x (old bash)

(for a concrete instance, macos high sierra + brew installed bash-completion / git)

In the bash-completion 1.x world, to override this I would (in .bashrc / .bash_profile) define my own _git_clone completion function:

# https://github.com/scop/bash-completion/blob/d2f14a7/bash_completion#L498
__ltrim_colon_completions() {
    if [[ "$1" == *:* && "$COMP_WORDBREAKS" == *:* ]]; then
        # Remove colon-word prefix from COMPREPLY items
        local colon_word=${1%"${1##*:}"}
        local i=${#COMPREPLY[*]}
        while [[ $((--i)) -ge 0 ]]; do
            COMPREPLY[$i]=${COMPREPLY[$i]#"$colon_word"}
        done
    fi
}


_git_clone() {
    case "$cur" in
    --*)
        __gitcomp_builtin clone
        return
        ;;
    *)
        argc=0
        for word in "${words[@]}"; do
            case "$word" in
            git|clone|--*)
                continue
                ;;
            *)
                argc=$((argc + 1))
                ;;
            esac
        done

        if [ $argc -le 1 ]; then
            __gitcomp "https://github.com/git/git https://github.com/python/cpython"
            __ltrim_colon_completions "$cur"
        fi
        ;;
    esac
}

This works great:

(The sequence I typed here was git clone h<tab><tab>g<tab>)

$ git clone https://github.com/
//github.com/git/git          //github.com/python/cpython 
$ git clone https://github.com/git/git 

bash-completion 2.x

(for a concrete instance: stock ubuntu bionic (18.04))

In bash-completion 2.x, the model is flipped to a dynamically loaded configuration. This means that when git is tab completed, __load_completion fires, finds the git completion at the path it is installed and sources it.

Defining my own _git_clone completion function in a .bashrc / .bash_profile is now useless as it gets clobber by the dynamically sourced completion file.

I can define my own git completion in this directory:

local -a dirs=( ${BASH_COMPLETION_USER_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion}/completions )

(for example ~/.local/share/bash-completion/completions/git.bash). However this turns off all other git completion!

How do I make my custom clone tab completion work under this model (and have the default completion continue to work)?

Unacceptable solution(s):

  • Modify system packaged files: /usr/share/bash-completion/completions/git. This file is managed by apt.
Bsquare ℬℬ

The official FAQ of bash-completion contains very interesting information.

First, if you are 100% sure your $BASH_COMPLETION_USER_DIR and $XDG_DATA_HOME environment variable are empty, what you specified in your original question is a good place to add your own bash-completion scripts:

~/.local/share/bash-completion/completions/git

To be noted .bash extension not necessary.

The fact is that bash-completion scripts are loaded thanks to the /etc/profile.d/bash_completion.sh file.

If you perform something in your .bashrc file, you would somehow break something in the loading chain.

Nevertheless, if you override existing completion function, you still need to ensure the loading order is correct. So loading first bash-completion script is mandatory to ensure everything ends successfully. You can easily perform it, adding this initial instruction at the beginning of your ~/.local/share/bash-completion/completions/git file:

# Ensures git bash-completion is loaded before overriding any function (be careful to avoid endless loop).
! complete -p git &> /dev/null && [ ${ENDLESS_LOOP_SAFEGUARD:-0} -eq 0 ] && ENDLESS_LOOP_SAFEGUARD=1 BASH_COMPLETION_USER_DIR=/dev/null  _completion_loader git

First it checks if git bash-completion has already been loaded, and then if this is not the case, all the bash-completion git definition are loaded. Edit: the ENDLESS_LOOP_SAFEGUARD trick allows to avoid endless loop when this is the first time bash completion is loading git part.

If needed, you can get the usage:

complete --help

complete: complete [-abcdefgjksuv] [-pr] [-DE] [-o option] [-A action] [-G globpat] [-W wordlist] [-F function] [-C command] [-X filterpat] [-P prefix] [-S suffix] [name ...] Specify how arguments are to be completed by Readline.

For each NAME, specify how arguments are to be completed. If no options are supplied, existing completion specifications are printed in a way that allows them to be reused as input.

Options:

-p print existing completion specifications in a reusable format -r remove a completion specification for each NAME, or, if no NAMEs are supplied, all completion specifications -D apply the completions and actions as the default for commands without any specific completion defined -E apply the completions and actions to "empty" commands -- completion attempted on a blank line

When completion is attempted, the actions are applied in the order the uppercase-letter options are listed above. The -D option takes precedence over -E.

Exit Status: Returns success unless an invalid option is supplied or an error occurs.

Then, and only then, you can define whatever you want, including your old way to override git clone bash completion:

# Ensures git bash-completion is loaded before overriding any function (be careful to avoid endless loop).
! complete -p git &> /dev/null && [ ${ENDLESS_LOOP_SAFEGUARD:-0} -eq 0 ] && ENDLESS_LOOP_SAFEGUARD=1 BASH_COMPLETION_USER_DIR=/dev/null  _completion_loader git

# https://github.com/scop/bash-completion/blob/d2f14a7/bash_completion#L498
__ltrim_colon_completions() {
    if [[ "$1" == *:* && "$COMP_WORDBREAKS" == *:* ]]; then
        # Remove colon-word prefix from COMPREPLY items
        local colon_word=${1%"${1##*:}"}
        local i=${#COMPREPLY[*]}
        while [[ $((--i)) -ge 0 ]]; do
            COMPREPLY[$i]=${COMPREPLY[$i]#"$colon_word"}
        done
    fi
}


_git_clone() {
    case "$cur" in
    --*)
        __gitcomp_builtin clone
        return
        ;;
    *)
        argc=0
        for word in "${words[@]}"; do
            case "$word" in
            git|clone|--*)
                continue
                ;;
            *)
                argc=$((argc + 1))
                ;;
            esac
        done

        if [ $argc -le 1 ]; then
            __gitcomp "https://github.com/git/git https://github.com/python/cpython"
            __ltrim_colon_completions "$cur"
        fi
        ;;
    esac
}

Each time you perform change and want to check result, you just need to request bash-completion reload for git:

_completion_loader git

Such a way, you will never lost your change, because you let the package files untouched; and still can enhanced any existing bash-completion with your own functions.

Edit:

About your fear with _completion_loader function => , but after having checked the source code, this function exists since commit cad3abfc7, of 2015-07-15 20:53:05 so I guess it should be kept backward-compatible, but true without guarantee. I'll edit my anwer to propose some alternatives

As alternative, this should be another way to get your own git completion definition (to put at beginning of your own script):

# Ensures git bash-completion is loaded before overriding any function
# Be careful to avoid endless loop with dedicated $ENDLESS_LOOP_SAFEGUARD environment variable.
if ! complete -p git &> /dev/null && [ ${ENDLESS_LOOP_SAFEGUARD:-0} -eq 0 ]; then
    # Trick: define $BASH_COMPLETION_USER_DIR environment variable here to ensure bash-completion rule are loaded once.
    export BASH_COMPLETION_USER_DIR=/usr/share
    complete -D git

    unset BASH_COMPLETION_USER_DIR
    ENDLESS_LOOP_SAFEGUARD=1 complete -D git
fi

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

TOP Ranking

  1. 1

    Failed to listen on localhost:8000 (reason: Cannot assign requested address)

  2. 2

    How to import an asset in swift using Bundle.main.path() in a react-native native module

  3. 3

    Loopback Error: connect ECONNREFUSED 127.0.0.1:3306 (MAMP)

  4. 4

    pump.io port in URL

  5. 5

    Spring Boot JPA PostgreSQL Web App - Internal Authentication Error

  6. 6

    BigQuery - concatenate ignoring NULL

  7. 7

    ngClass error (Can't bind ngClass since it isn't a known property of div) in Angular 11.0.3

  8. 8

    Do Idle Snowflake Connections Use Cloud Services Credits?

  9. 9

    maven-jaxb2-plugin cannot generate classes due to two declarations cause a collision in ObjectFactory class

  10. 10

    Compiler error CS0246 (type or namespace not found) on using Ninject in ASP.NET vNext

  11. 11

    Can't pre-populate phone number and message body in SMS link on iPhones when SMS app is not running in the background

  12. 12

    Generate random UUIDv4 with Elm

  13. 13

    Jquery different data trapped from direct mousedown event and simulation via $(this).trigger('mousedown');

  14. 14

    Is it possible to Redo commits removed by GitHub Desktop's Undo on a Mac?

  15. 15

    flutter: dropdown item programmatically unselect problem

  16. 16

    Change dd-mm-yyyy date format of dataframe date column to yyyy-mm-dd

  17. 17

    EXCEL: Find sum of values in one column with criteria from other column

  18. 18

    Pandas - check if dataframe has negative value in any column

  19. 19

    How to use merge windows unallocated space into Ubuntu using GParted?

  20. 20

    Make a B+ Tree concurrent thread safe

  21. 21

    ggplotly no applicable method for 'plotly_build' applied to an object of class "NULL" if statements

HotTag

Archive