Git bash-дополнение с поддержкой имени файла?

есть ли сценарий завершения bash, который поддерживает завершение имени файла? Я использую в основном mercurial и могу ввести:

hg diff test/test_<tab>

и он покажет/завершит все измененные тестовые файлы. Это работает для большинства подкоманд, т. е. hg add <tab><tab> будет отображать только неотслеживаемые файлы. Это очень удобно.

Сценарий bash из git contrib не поддерживает это. Есть ли альтернативы или как вы работаете с git в командной строке?

Редактировать 2015

git-completion.bash поддерживает завершение полного имени файла, начиная с ~1.8.2


person olt    schedule 17.05.2011    source источник
comment
Обычно я сначала использую git status, а затем знаю, что набрать для git diff. (Но чаще всего я использую gitk для просмотра различий.)   -  person Paŭlo Ebermann    schedule 17.05.2011
comment
Примечание. В Git 2.18 (второй квартал 2018 г.) завершение имени файла будет выполняться быстрее. См. мой ответ ниже.   -  person VonC    schedule 02.05.2018


Ответы (4)


Итак, давайте посмотрим, как это делает скрипт завершения Mercurial bash.

Это важная часть:

_hg_status()
{
    local files="$(_hg_cmd status -n$1 .)"
    local IFS=$'\n'
    COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$files' -- "$cur"))
}

Он вызывается здесь:

_hg_command_specific()
{
    case "$cmd" in 
    [...]
    diff)
        _hg_status "mar"
    ;;
    [...]
    esac
    return 0
}

Таким образом, это просто вызов hg status -nmar и использование вывода в виде списка файлов для завершения.

Я думаю, было бы несложно пропатчить нечто подобное в скрипт завершения git - нам нужно изменить __git_diff здесь, чтобы не выполнять простое завершение имени файла + ветки, а вместо этого вызывать git status.


Команды

git status --porcelain | grep '^.[^ ?]' | cut -b 4-

(для git diff --cached) и

git status --porcelain | grep '^[^ ?]' | cut -b 4-

(для git diff) вроде правильно выводит (если нет переименований).

Однако они оба бесполезны при переходе к чему-либо, кроме HEAD.

Более общим способом было бы использование

git diff --relative --name-only [--cached] [commit1] [commit2]]

где commit1 и commit2 (и, возможно, --cached) взяты из уже заданной командной строки diff.


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

git diff -- <tab>
git diff --cached -- <tab>
git diff HEAD^^ -- <tab>
git diff origin/master master -- <tab>

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

# Completion for the file argument for git diff.
# It completes only files actually changed. This might be useful
# as completion for other commands as well.
#
# The idea comes from the bash completion for Mercurial (hg),
# which does something similar (but more simple, only difference of
# working directory to HEAD and/or index, if I understand right).
# It (the idea) was brought to us by the question
#      http://stackoverflow.com/q/6034472/600500
#  from "olt".
__git_complete_changed_files()
{
  #
  # We use "git diff --name-only --relative" to generate the list,
  # but this needs the same --cached and <commit> arguments as the
  # command line being constructed.
  #


    # first grab arguments like --cached and any commit arguments.

    local -a args=()
    local finish=false

    for (( i=1 ; i < cword ; i++)) do
    local current_arg=${words[$i]}
    #  echo checking $current_arg >&2
       case $current_arg in
           --cached)
               args+=( $current_arg )
               ;;
           --)
               # finish parsing arguments, the rest are file names
               break
               ;;
           -*)
               # other options are ignored
               ;;
           *)
               if git cat-file -e $current_arg 2> /dev/null
               then
                   case $( git cat-file -t $current_arg ) in
                       commit|tag)
                       # commits and tags are added to the command line.
                           args+=( $current_arg )
                           # echo adding $current_arg >&2
                           ;;
                       *)
                   esac
               fi
               ;;
       esac
    done

    # now we can call `git diff`

    COMPREPLY=( $( compgen \
        -W "$( git diff --name-only --relative "${args[@]}" -- )" -- $cur ) )
}

_git_diff ()
{
    if __git_has_doubledash
    then
        # complete for the file part: only changed files
        __git_complete_changed_files
    else
    case "$cur" in
    --*)
        __gitcomp "--cached --staged --pickaxe-all --pickaxe-regex
            --base --ours --theirs --no-index
            $__git_diff_common_options
            "
        return
        ;;
    esac
    __git_complete_revlist_file
    fi
}

Обновление: похоже, что этот патч не нужен в этой форме, так как текущий способ завершения файлов более полезен для людей, которые хотят проверить, есть ли изменения в каком-либо подкаталоге (например, завершение, когда вывод diff может быть пустым). Это может быть принято, если связано с некоторой переменной конфигурации (по умолчанию используется текущее поведение). Также отступы должны быть адаптированы к стандарту (см. ответ от Хунио С. Хамано).

Я мог бы попробовать еще раз, но не могу гарантировать это в ближайшем будущем. Если кто-то еще захочет это сделать, не стесняйтесь взять мой код, изменить его и отправить снова.

person Paŭlo Ebermann    schedule 17.05.2011

Это решает проблему git diff <tab> для меня, поместите следующее в .bashrc:

alias gid='git diff'
__gdiff () {
    local cur prev opts
    COMPREPLY=()
    cur="${COMP_WORDS[COMP_CWORD]}"
    prev="${COMP_WORDS[COMP_CWORD-1]}"
    opts=$(git status --porcelain | grep '^.[^ ?]' | cut -b 4-)

    case "${prev}" in
        gid)
            COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
            ;;
    esac
}
complete -F __gdiff gid

А затем сделайте gid <tab> вместо git diff <tab>. Это может быть упрощено, но, кажется, хорошо работает как быстрое решение.

person Kjell Andreassen    schedule 18.11.2013

Не совсем ваш желаемый ответ, но я хотел сообщить вам, что скоро fish (дружественная интерактивная оболочка) предоставит вам поддержку завершения имени файла git из коробки. В настоящее время он находится в мастере, и скоро выйдет версия 2.3.0.

https://github.com/fish-shell/fish-shell/issues/901
https://github.com/fish-shell/fish-shell/pull/2364
https://github.com/fish-shell/fish-shell/commit/c5c59d4acb00674bc37198468b5978f69484c628

Если у вас такой статус:

$ git status
modified: ../README.md
$ git add <tab>
:/README.md 

введите здесь описание изображения

Вы также можете просто ввести README и нажать Tab, и он вставит его для вас, если это единственное совпадение. Здорово черт возьми!

person Elijah Lynn    schedule 21.03.2016
comment
См. мое обновление 2015 года в сообщении: git-completion.bash поддерживает полное завершение имени файла, начиная с ~ 1.8.2. - person olt; 23.03.2016

С 2011 года, как комментирует OP, Git поддерживает полное завершение имени файла, начиная с ~ 1.8.2.

Но с Git 2.18 (Q2 2018) завершение оболочки (в contrib/), которое дает список путей, было несколько оптимизировано.

См. commit 78a2d21 (4 апреля 2018 г.) от Clemens Buchacher (drizzd).
(объединено Хунио C Хамано -- gitster -- в commit 3a940e9, 25 апреля 2018 г.)

completion: улучшить ls-files производительность фильтра

Из вывода ls-files мы удаляем все, кроме самого левого компонента пути, а затем удаляем дубликаты. Мы делаем это в цикле while, который является узким местом в производительности при большом количестве итераций (например, для 60000 файлов в linux.git).

$ COMP_WORDS=(git status -- ar) COMP_CWORD=3; time _git

real    0m11.876s
user    0m4.685s
sys     0m6.808s

Замена цикла командой cut значительно повышает производительность:

$ COMP_WORDS=(git status -- ar) COMP_CWORD=3; time _git

real    0m1.372s
user    0m0.263s
sys     0m0.167s

Измерения проводились с помощью Msys2 bash, который используется Git для Windows.

При фильтрации вывода ls-files мы стараемся не касаться абсолютных путей. Это избыточно, потому что ls-files никогда не будет выводить абсолютные пути. Удалите ненужные операции.

Изначально о проблеме сообщалось Git для Windows, проблема 1533.


Код обхода каталогов имел избыточные рекурсивные вызовы, что делало его характеристики производительности экспоненциальными по отношению к глубине дерева, что было исправлено в Git 2.27 (второй квартал 2020 г.).

См. commit c0af173, зафиксировать 95c11ec, , коммит 1684644, зафиксировать 8d92fb2, зафиксировать 2df179d, зафиксировать 0126d14, зафиксировать cd129ee, зафиксировать 446f46d , коммит 7260c7b, commit ce5c61a (01 апреля 2020 г.), автор Элайджа Ньюрен (newren).
См. коммит 0bbd0e8 (01 апр. 2020) Деррик Столи (derrickstolee).
(объединено Хунио С Хамано -- gitster -- в commit 6eacc39, 29 апреля 2020 г.)

completion: исправить 'git add' на путях в неотслеживаемом каталоге.

Подписано: Элайджа Ньюрен

Как сообщается в списке рассылки git, начиная с git-2.25,

git add untracked-dir/

была завершена вкладка

git add untracked-dir/./

Причиной этого было то, что с commit b9670c1f5e (dir: исправить проверки в каталоге с общим префиксом , 19 декабря 2019 г., Git v2.25.0-rc0 -- слияние),

git ls-files -o --directory untracked-dir/

(или эквивалент git -C untracked-dir ls-files -o --directory) начал сообщать

untracked-dir/

вместо того, чтобы перечислять пути под этим каталогом.

Возможно, также стоит отметить, что настоящая команда, о которой идет речь, была

git -C untracked-dir ls-files -o --directory '*'

что эквивалентно:

git ls-files -o --directory 'untracked-dir/*'

который ведет себя так же для целей этой проблемы ('*' может соответствовать пустой строке), но становится актуальным для предлагаемого исправления.

Сначала, основываясь на отчете, я решил попытаться рассматривать это как регрессию и попытался найти способ восстановить старое поведение, не нарушая других вещей или, по крайней мере, нарушая как можно меньше.
Однако в В конце концов, я не мог придумать способ сделать это, который не просто вызвал бы гораздо больше проблем, чем решил.

Старое поведение было ошибкой:

  • Хотя старый git избегал очистки чего-либо с помощью git clean -f .git, он стирал все в этом каталоге с помощью git clean -f .git/.
    Несмотря на разницу в используемой команде, это имеет значение, поскольку точно такое же изменение, которое исправило очистку, изменило поведение ls-файлов. .
  • Старый git сообщал о разных результатах, основанных исключительно на наличии или отсутствии завершающей косой черты для $SUBDIR в команде git ls-files -o --directory $SUBDIR.
  • Старый git нарушал задокументированное поведение, заключающееся в отказе от рекурсии в каталогах, которые соответствовали спецификации пути, когда было указано --directory.
  • И, в конце концов, commit b9670c1f5e (dir: исправление проверок каталога общих префиксов, 2019 г. -12-19, Git v2.25.0-rc0) не упустил из виду эту проблему; в нем прямо указано, что поведение команды было изменено, чтобы привести ее в соответствие с документацией.

(Кроме того, если это поможет, несмотря на то, что этот коммит был объединен в серии 2.25, об этой ошибке не сообщалось ни в цикле 2.25, ни даже в течение большей части цикла 2.26 — о ней сообщалось за день до выпуска 2.26.

Таким образом, влияние изменения, по крайней мере, несколько невелико.)

Вместо того, чтобы полагаться на ошибку ls-files в сообщении о неправильном содержимом, измените вызов ls-файлов, используемых git-completion, чтобы он захватывал пути на одну глубину глубже.

Для этого замените «$DIR/*» (соответствует $DIR/ плюс 0 или более символов) на «$DIR/?*» (соответствует $DIR/ плюс 1 или более символов).

Обратите внимание, что символ '?' не следует добавлять при попытке завершить имя файла (например, символ 'git ls-files -o --directory merge.c?*"' would not correctly return "[merge.c](https://github.com/git/git/blob/c0af173a136785b3cfad4bd414b2fb10a130760a/merge.c)" when such a file exists), so we have to make sure to add the '?`' только в тех случаях, когда указанный путь является каталогом.


Предупреждение: Git 2.29 (4 квартал 2020 г.) исправляет регрессию, появившуюся в цикле 2.27.

См. commit cada730 (20 июля 2020 г.) от Мартин Огрен (none).
(объединено Хунио С. Хамано -- gitster -- в commit 82fafc7, 30 июля 2020 г.)

dir: проверьте пути перед возвратом path_excluded

Подготовил: Андреас Шваб
Проверил: Элайджа Ньюрен
Подписал: Мартин Огрен

В 95c11ecc73 (исправить подверженный ошибкам API fill_directory(); сделать так, чтобы он возвращал только совпадения, 2020- 01 апреля, Git v2.27.0-rc0 -- слияние, указанное в пакет № 5), мы научили fill_directory(), а точнее treat_path(), проверять любые пути, чтобы мы могли бы упростить абонентов.

Но при этом мы добавили слишком ранний возврат для исключенного случая. В итоге мы не проверяем пути, а это означает, что мы возвращаем path_excluded вместо того, чтобы вернуть path_none. В результате git status --ignored -- pathspec (man) могут показывать пути, которые на самом деле не соответствуют pathspec.

Переместите исключенную проверку вниз после того, как мы проверили все пути.

person VonC    schedule 01.05.2018