Переопределить завершение bash для git clone

встроенное завершение

дополнение для git clone (воспроизведено ниже) дает завершение табуляции для --* опций:

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

bash-completion 1.x (старый bash)

(для конкретного случая macos high sierra + brew установил bash-completion / git)

В мире bash-completion 1.x, чтобы переопределить это, я бы (в .bashrc/.bash_profile) определил свою собственную функцию завершения _git_clone:

# 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
}

Это прекрасно работает:

(Последовательность, которую я набрал здесь, была 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-дополнение 2.x

(для конкретного примера: стоковый Ubuntu Bionic (18.04))

В bash-completion 2.x модель переключается на динамически загружаемую конфигурацию. Это означает, что при завершении вкладки git срабатывает __load_completion , находит завершение git по пути его установки и получает его.

Определение моей собственной функции завершения _git_clone в .bashrc / .bash_profile теперь бесполезно, поскольку она забивается файлом завершения из динамических источников.

Я могу определить собственное завершение git в этом каталоге:

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

(например, ~/.local/share/bash-completion/completions/git.bash). Однако это отключает все остальные git завершения!

Как сделать так, чтобы в этой модели работало мое индивидуальное завершение вкладок clone (и продолжало работать завершение по умолчанию)?

Неприемлемые решения:

  • Изменить системные упакованные файлы: /usr/share/bash-completion/completions/git. Этот файл управляется apt.

person Anthony Sottile    schedule 22.08.2018    source источник
comment
Позвонить __load_completion самостоятельно, а затем переопределить, как раньше?   -  person o11c    schedule 22.01.2019
comment
@ o11c вызов функции с двойным подчеркиванием кажется довольно хрупким   -  person Anthony Sottile    schedule 22.01.2019


Ответы (2)


официальный FAQ bash-completion содержит очень интересную информацию.

Во-первых, если вы на 100 % уверены, что ваши переменные среды $BASH_COMPLETION_USER_DIR и $XDG_DATA_HOME пусты, то, что вы указали в своем исходном вопросе, является хорошим местом для добавления ваших собственных сценариев завершения bash:

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

Следует отметить, что расширение .bash не требуется.

Дело в том, что скрипты bash-дополнения загружаются благодаря файлу /etc/profile.d/bash_completion.sh.

Если вы что-то сделаете в своем .bashrc файле, вы как-то сломаете что-то в цепочке загрузки.

Тем не менее, если вы переопределяете существующую функцию завершения, вам все равно необходимо обеспечить правильный порядок загрузки. Поэтому загрузка первого сценария завершения bash обязательна, чтобы убедиться, что все завершилось успешно. Вы можете легко это сделать, добавив эту начальную инструкцию в начало вашего ~/.local/share/bash-completion/completions/git файла:

# 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

Сначала он проверяет, загружено ли уже git bash-completion, а затем, если это не так, загружаются все определения git для bash-completion. Изменить: трюк ENDLESS_LOOP_SAFEGUARD позволяет избежать бесконечного цикла, когда это первый раз, когда завершение bash загружает часть git.

При необходимости вы можете получить использование:

complete --help

Complete: Complete [-abcdefgjksuv] [-pr] [-DE] [-o опция] [-A действие] [-G globpat] [-W список слов] [-F функция] [-C команда] [-X filterpat] [-P префикс] [-S суффикс] [имя ...] Укажите, как аргументы должны быть дополнены Readline.

Для каждого ИМЯ укажите, как аргументы должны быть завершены. Если параметры не указаны, существующие спецификации завершения распечатываются таким образом, чтобы их можно было повторно использовать в качестве входных данных.

Опции:

-p распечатать существующие спецификации завершения в повторно используемом формате -r удалить спецификацию завершения для каждого ИМЯ или, если ИМЕНА не указаны, все спецификации завершения -D применить завершения и действия по умолчанию для команд без какого-либо определенного завершения -E применять завершения и действия к «пустым» командам — попытка завершения на пустой строке

При попытке завершения действия применяются в том порядке, в котором параметры заглавных букв перечислены выше. Параметр -D имеет приоритет над параметром -E.

Состояние выхода: возвращает успех, если не указан недопустимый параметр или не возникает ошибка.

Тогда и только тогда вы можете определить все, что хотите, включая ваш старый способ переопределить завершение git clone bash:

# 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
}

Каждый раз, когда вы вносите изменения и хотите проверить результат, вам просто нужно запросить перезагрузку bash-completion для git:

_completion_loader git

Таким образом, вы никогда не потеряете свои изменения, потому что вы оставляете файлы пакета нетронутыми; и все еще можете улучшить любое существующее bash-дополнение своими собственными функциями.

Изменить:

О вашем страхе с _completion_loader function => , но после проверки исходного кода эта функция существует с момента коммита cad3abfc7 из 2015-07-15 20:53:05, поэтому я думаю, что он должен быть обратно совместимым, но это правда без гарантии. Я отредактирую свой ответ, чтобы предложить несколько альтернатив

В качестве альтернативы, это должен быть другой способ получить собственное определение завершения git (поместить в начало вашего собственного скрипта):

# 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
person Bsquare ℬℬ    schedule 22.01.2019
comment
Вы проверили, что он работает? Мне кажется, что будет бесконечная рекурсия. - person Leon; 22.01.2019
comment
Да, я работал над этим локально, чтобы убедиться, что мой ответ правильный. Я хоть про бесконечную рекурсию но ее нет, потому что _completion_loader вызывается тогда и только тогда, когда функции git bash-completion еще не загружены. - person Bsquare ℬℬ; 22.01.2019
comment
Но когда _completion_loader вызывается в первый раз, функции завершения git bash еще не загружены, и поэтому он должен повторить поиск и снова загрузить определенный пользователем файл, после чего у вас будет бесконечная рекурсия. - person Leon; 22.01.2019
comment
@ Леон, ты прав. Я просто добавил трюк с выделенной переменной ENDLESS_LOOP_SAFEGUARD, позволяющей защититься от него. - person Bsquare ℬℬ; 22.01.2019
comment
Тем не менее я не думаю, что это работает так, как задумано. Какой файл будет _completion_loader загружаться при его выполнении? Тот, который появляется раньше в пути поиска, т. Е. Определенный пользователем сценарий завершения bash, поэтому общесистемный сценарий вообще не будет загружен. - person Leon; 22.01.2019
comment
С другой стороны, этот подход не будет работать (даже после устранения указанных ошибок) для несколько более старых версий завершения bash (например, версии 2.1, используемой в Ubuntu 16.04), где не предусмотрены механизмы для ленивой загрузки пользовательских дополнений. . - person Leon; 22.01.2019
comment
Это намного ближе к тому, что я хочу - единственный оставшийся бит, который получает это полное расстояние, - это функция _completion_loader - есть ли способ, которым я могу дразнить это из complete или чего-то подобного, чтобы это не ломалось, когда это имя функции изменения? или где-то указано, что _completion_loader всегда будет тем, что я хочу? - person Anthony Sottile; 23.01.2019
comment
Я понимаю ваш страх, но после проверки исходного кода эта функция существует с момента коммита cad3abfc7 из 2015-07-15 20:53:05, поэтому я думаю, что она должна быть обратно совместима, но правда без гарантии. Я отредактирую свой ответ, чтобы предложить несколько альтернатив. - person Bsquare ℬℬ; 23.01.2019
comment
@AnthonySottile Не могли бы вы подтвердить, что это решение вообще работает для вас? Насколько я понимаю, наличие этого кода в ~/.local/share/bash-completion/completions/git предотвращает загрузку завершения bash по умолчанию для git, т. е. у него та же проблема, что и в вашем вопросе (Однако это отключает все остальные завершения git!) - person Leon; 23.01.2019
comment
Теперь, когда я попробовал это, это не работает, однако небольшая настройка исправляет это: ! complete -p git && BASH_COMPLETION_USER_DIR=/dev/null _completion_loader git - person Anthony Sottile; 23.01.2019
comment
Забавно, я работал над альтернативой, близкой к этому. Обновление ответа выполнено. - person Bsquare ℬℬ; 23.01.2019
comment
@AnthonySottile Поздравляю! Вы пришли к решению, которое я имел в виду, когда начал работать над своим ответом, но которое мне не понравилось по ряду причин (например, из-за того, что оно не работает для версии 2.1 дополнения bash). - person Leon; 23.01.2019
comment
@AnthonySottile Вы довольны этим решением? - person Bsquare ℬℬ; 23.01.2019
comment
Текущий код в ответе не был обновлен битами BASH_COMPLETION_USER_DIR=..., поэтому он не работает так, как написано, но это привело меня к тому, что действительно работает: D - person Anthony Sottile; 23.01.2019
comment
Я только что отредактировал свой ответ (обратите внимание, что мой вариант был добавлен в конце). - person Bsquare ℬℬ; 23.01.2019
comment
@AnthonySottile Итак, я должен считать, что вы принимаете мой ответ? - person Bsquare ℬℬ; 23.01.2019

В ваших .bashrc / .bash_profile вы можете принудительно загрузить завершения по умолчанию для git перед переопределением завершения для git clone:

if ! complete -p git &> /dev/null
then
    # Instead of hardcoding the name of the dynamic completion loader
    # function, you can obtain it by parsing the output of 'complete -p -D'
    _completion_loader git
fi

_git_clone() {
    COMPREPLY=("My own completion for 'git clone'")
}

ИЗМЕНИТЬ

Далее следует лениво загружаемая версия описанного выше подхода (которая не с готовностью загружает завершения bash по умолчанию для git):

if ! complete -p git &> /dev/null
then
    _my_git_clone()
    {
        COMPREPLY=("My own completion for 'git clone'")
    }

    # A placeholder for git completion that will load the real
    # completion script on first use     
    _my_git_comp_stub()
    {
        # Remove the old completion handler (which should be this very function)
        complete -r git

        # and load the git bash completion
        _completion_loader git

        # Rebind _git_clone to our own version
        eval 'function _git_clone() { _my_git_clone "$@"; }'

        # Tell the completion machinery to retry the completion attempt
        # using the updated completion handler
        return 124
    }

    # Install a lazy loading handler for git completion    
    complete -o bashdefault -o default -o nospace -F _my_git_comp_stub git
fi
person Leon    schedule 22.01.2019
comment
это похоже на взлом - я предполагаю, что новая система отложенной загрузки должна была предотвратить потенциально дорогостоящую нетерпеливую загрузку, подобную этой? однако он работает (и есть заметная пауза при запуске оболочки). вы на самом деле не объяснили, почему это работает или как можно было бы анализировать вывод complete -p -D - person Anthony Sottile; 22.01.2019
comment
@AnthonySottile * как можно проанализировать вывод complete -p -D * Это примечание добавлено на тот случай, если вы работаете со средой, в которой функция ленивой загрузки названа по-другому (мне еще предстоит узнать о такой настройке). С другой стороны, нет гарантии, что обработчик завершения по умолчанию (настроенный с помощью complete -D) выполняет ленивую загрузку — на самом деле это может быть функция, выполняющая тривиальное завершение. - person Leon; 22.01.2019
comment
@AnthonySottile Ленивая загрузка дополнений bash была изобретена, чтобы сократить время запуска, затрачиваемое на загрузку всех определений завершения. В этом случае загружаются только дополнения, определенные для git. Однако, поскольку этот скрипт довольно большой и приводит к заметной паузе при запуске оболочки, я добавил в свой ответ лениво загружаемую версию. - person Leon; 22.01.2019