Score:3

Bash completion for git branches broken after upgrading to 21.10

nl flag

I can't remember where I got this bit of script from, but my .bashrc contains the following lines:

# set up autocomplete for git aliases
if [ -f "/usr/share/bash-completion/completions/git" ]; then
  source /usr/share/bash-completion/completions/git
  __git_complete gc _git_checkout
  __git_complete gp _git_pull
else
  echo "Error loading git completions"
fi

git_branch() {
  git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/(\1)/'
}

gc and gp are aliases for git checkout and git pull respectively.

I have a /usr/share/bash-completion-completions/git so when I source ~/.bashrc there is nothing echoed to the command line.

Prior to upgrading to 21.10 (21.04, 20.04, and 18.04 worked fine), I could gc feat<tab><tab> and get a list of branches starting with feat, but now I get a weird error after each <tab>:

$ gc featbash: [: -lt: unary operator expected   // first tab after 'gc feat'
bash: [: : integer expression expected
bash: [: -lt: unary operator expected
bash: [: -lt: unary operator expected
ure/bash: [: -lt: unary operator expected        // second tab after 'ure/' (this was returned by the first tab)
bash: [: : integer expression expected
bash: [: -lt: unary operator expected
bash: [: -lt: unary operator expected            // third tab after this line
bash: [: -lt: unary operator expected
bash: [: : integer expression expected
bash: [: -lt: unary operator expected
bash: [: -lt: unary operator expected

Display all 136 possibilities? (y or n)

Any idea what is causing this?


UPDATE 1:

I only get this error when using the aliases - completion using the full command git checkout feat<tab> works fine. When I vim ~/.bash_aliases it prompted me to recover a previous version and delete the swap file. This I did and everything looks fine, but I still get the error.


UPDATE 2:

Tried upgrading, then removing/reinstalling git and bash-completion to no avail.

bash-completion is already the newest version (1:2.11-2ubuntu1).
git is already the newest version (1:2.32.0-1ubuntu1).

UPDATE 3:

Did a set -xv to enable verbose/debug. Below is not the full dump but includes the portion where the messages are created:

$ gc feat+ __git_func_wrap _git_checkout
+ local cur words cword prev
+ _get_comp_words_by_ref -n =: cur words cword prev
+ local exclude flag i OPTIND=1
+ words=()
+ local cur cword words
+ upargs=()
+ upvars=()
+ local upargs upvars vcur vcword vprev vwords
+ getopts c:i:n:p:w: flag -n =: cur words cword prev
+ case $flag in
+ exclude==:
+ getopts c:i:n:p:w: flag -n =: cur words cword prev
+ [[ 6 -ge 3 ]]
+ case ${!OPTIND} in
+ vcur=cur
+ (( OPTIND += 1 ))
+ [[ 6 -ge 4 ]]
+ case ${!OPTIND} in
+ vwords=words
+ (( OPTIND += 1 ))
+ [[ 6 -ge 5 ]]
+ case ${!OPTIND} in
+ vcword=cword
+ (( OPTIND += 1 ))
+ [[ 6 -ge 6 ]]
+ case ${!OPTIND} in
+ vprev=prev
+ (( OPTIND += 1 ))
+ [[ 6 -ge 7 ]]
+ __get_cword_at_cursor_by_ref =: words cword cur
+ words=()
+ local cword words
+ __reassemble_comp_words_by_ref =: words cword
+ local exclude i j line ref
+ [[ -n =: ]]
+ exclude='[=:]'
+ printf -v cword %s 1
+ [[ -v exclude ]]
+ line='gc feat'
+ (( i = 0, j = 0 ))
+ (( i < 2 ))
+ [[ 0 -gt 0 ]]
+ ref='words[0]'
+ printf -v 'words[0]' %s gc
+ line=' feat'
+ (( i == COMP_CWORD ))
+ (( i++, j++ ))
+ (( i < 2 ))
+ [[ 1 -gt 0 ]]
+ [[ feat == +([=:]) ]]
+ ref='words[1]'
+ printf -v 'words[1]' %s feat
+ line=
+ (( i == COMP_CWORD ))
+ printf -v cword %s 1
+ (( i++, j++ ))
+ (( i < 2 ))
+ (( i == COMP_CWORD ))
+ local i cur= index=7 'lead=gc feat'
+ [[ 7 -gt 0 ]]
+ [[ -n gc feat ]]
+ [[ -n gcfeat ]]
+ cur='gc feat'
+ (( i = 0 ))
+ (( i <= cword ))
+ [[ 7 -ge 2 ]]
+ [[ gc != \g\c ]]
+ (( i < cword ))
+ local old_size=7
+ cur=' feat'
+ local new_size=5
+ (( index -= old_size - new_size ))
+ (( ++i ))
+ (( i <= cword ))
+ [[ 5 -ge 4 ]]
+ [[  fea != \f\e\a\t ]]
+ cur=feat
+ (( index > 0 ))
+ (( index-- ))
+ [[ 4 -ge 4 ]]
+ [[ feat != \f\e\a\t ]]
+ (( i < cword ))
+ (( ++i ))
+ (( i <= cword ))
+ [[ -n feat ]]
+ [[ ! -n feat ]]
+ (( index < 0 ))
+ local words cword cur
+ _upvars -a2 words gc feat -v cword 1 -v cur feat
+ (( 10 ))
+ (( 10 ))
+ case $1 in
+ [[ -n 2 ]]
+ printf %d 2
+ [[ -n words ]]
+ unset -v words
+ eval 'words=("${@:3:2}")'
words=("${@:3:2}")
++ words=("${@:3:2}")
+ shift 4
+ (( 6 ))
+ case $1 in
+ [[ -n cword ]]
+ unset -v cword
+ eval 'cword="$3"'
cword="$3"
++ cword=1
+ shift 3
+ (( 3 ))
+ case $1 in
+ [[ -n cur ]]
+ unset -v cur
+ eval 'cur="$3"'
cur="$3"
++ cur=feat
+ shift 3
+ (( 0 ))
+ [[ -v vcur ]]
+ upvars+=("$vcur")
+ upargs+=(-v $vcur "$cur")
+ [[ -v vcword ]]
+ upvars+=("$vcword")
+ upargs+=(-v $vcword "$cword")
+ [[ -v vprev ]]
+ [[ 1 -ge 1 ]]
+ upvars+=("$vprev")
+ upargs+=(-v $vprev "${words[cword - 1]}")
+ [[ -v vwords ]]
+ upvars+=("$vwords")
+ upargs+=(-a${#words[@]} $vwords ${words+"${words[@]}"})
+ (( 4 ))
+ local cur cword prev words
+ _upvars -v cur feat -v cword 1 -v prev gc -a2 words gc feat
+ (( 13 ))
+ (( 13 ))
+ case $1 in
+ [[ -n cur ]]
+ unset -v cur
+ eval 'cur="$3"'
cur="$3"
++ cur=feat
+ shift 3
+ (( 10 ))
+ case $1 in
+ [[ -n cword ]]
+ unset -v cword
+ eval 'cword="$3"'
cword="$3"
++ cword=1
+ shift 3
+ (( 7 ))
+ case $1 in
+ [[ -n prev ]]
+ unset -v prev
+ eval 'prev="$3"'
prev="$3"
++ prev=gc
+ shift 3
+ (( 4 ))
+ case $1 in
+ [[ -n 2 ]]
+ printf %d 2
+ [[ -n words ]]
+ unset -v words
+ eval 'words=("${@:3:2}")'
words=("${@:3:2}")
++ words=("${@:3:2}")
+ shift 4
+ (( 0 ))
+ _git_checkout
+ __git_has_doubledash
+ local c=1
+ '[' 1 -lt 1 ']'
+ return 1
++ __git_checkout_default_dwim_mode
++ local last_option dwim_opt=--dwim
++ '[' '' = 1 ']'
+++ __git_find_on_cmdline --no-track
+++ local word c= show_idx
+++ test 1 -gt 1
+++ local wordlist=--no-track
+++ '[' -lt 1 ']'
bash: [: -lt: unary operator expected
++ '[' -n '' ']'
+++ __git config --type=bool checkout.guess
+++ git config --type=bool checkout.guess
++ '[' '' = false ']'
+++ __git_find_last_on_cmdline '--guess --no-guess'
+++ local word c=1 show_idx
+++ test 1 -gt 1
+++ local 'wordlist=--guess --no-guess'
+++ '[' 1 -gt '' ']'
bash: [: : integer expression expected
++ last_option=
++ case "$last_option" in
++ echo --dwim
+ local dwim_opt=--dwim
+ case "$prev" in
+ case "$cur" in
++ __git_find_on_cmdline '-b -B -d --detach --orphan'
++ local word c= show_idx
++ test 1 -gt 1
++ local 'wordlist=-b -B -d --detach --orphan'
++ '[' -lt 1 ']'
bash: [: -lt: unary operator expected
+ '[' -n '' ']'
++ __git_find_on_cmdline --track
++ local word c= show_idx
++ test 1 -gt 1
++ local wordlist=--track
++ '[' -lt 1 ']'
bash: [: -lt: unary operator expected

Note this isn't a full dump, just the bits up to and including where the error messages are generated. I tried tracking one of the error messages back through the script and found this in /usr/share/bash-completion/completions/git:

# Check whether one of the given words is present on the command line,
# and print the first word found.
#
# Usage: __git_find_on_cmdline [<option>]... "<wordlist>"
# --show-idx: Optionally show the index of the found word in the $words array.
__git_find_on_cmdline ()
{
        local word c="$__git_cmd_idx" show_idx

        while test $# -gt 1; do
                case "$1" in
                --show-idx)     show_idx=y ;;
                *)              return 1 ;;
                esac
                shift
        done
        local wordlist="$1"

        while [ $c -lt $cword ]; do
                for word in $wordlist; do
                        if [ "$word" = "${words[c]}" ]; then
                                if [ -n "${show_idx-}" ]; then
                                        echo "$c $word"
                                else
                                        echo "$word"
                                fi
                                return
                        fi
                done
                ((c++))
        done
}

Looks like maybe the line local word c="$__git_cmd_idx" show_idx is to blame, as c looks empty to the -lt comparison later on triggering the unary operator expected.

Why would this no longer work after the update?

Score:4
cn flag

This got posted as a bug on bash completion's GitHub. The problem has been solved in Git 2.33.0.

Until this fix has trickled down into the Ubuntu repositories, you can make a workaround as follows:

In file /usr/share/bash-completion/completions/git, change

__git_func_wrap ()
{
    local cur words cword prev

into

__git_func_wrap ()
{
    local cur words cword prev __git_cmd_idx=1
mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.