zsh подсказка: проверить, находитесь ли внутри репозитория git и не игнорируется ли git

Вопрос или проблема

В моей оболочке zsh я динамически изменяю приглашение в зависимости от того, нахожусь ли я внутри репозитория git или нет. Я использую следующую команду git для проверки:

if $(git rev-parse --is-inside-work-tree >/dev/null 2>&1); then
...

Теперь я также хочу отличать, игнорируется ли текущая директория git. Поэтому я добавил еще одну проверку в мое выражение if:

if $(git rev-parse --is-inside-work-tree >/dev/null 2>&1) && ! $(git check-ignore . >/dev/null 2>&1); then
...

Это работает хорошо, но мне интересно, могу ли я упростить это до одной команды git. Так как приглашение обновляется при каждом нажатии ENTER, это заметно замедляет оболочку на некоторых более медленных машинах.

ОБНОВЛЕНИЕ

Принятое решение от @Stephen Kitt отлично работает, за исключением следующей ситуации:

Я использую репозиторий на разных файловых системах. Допустим, git находится в /.git (потому что я хочу отслеживать свои конфигурационные файлы в /etc), но я также хочу отслеживать некоторые файлы в /var/foo, который находится на другом разделе/файловой системе.

Когда я нахожусь в / и выполняю следующую команду, все работает как ожидалось, и я получаю код возврата 1 (потому что /var/foo отслеживается):

# git check-ignore -q /var/foo

Но когда я нахожусь где-либо в /var, та же команда завершается с кодом ошибки 128 и следующим сообщением об ошибке:

# git check-ignore -q /var/foo
fatal: not a git repository (or any parent up to mount point /)
Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).

Но я думаю, что это проблема только с командой check-ignore. В противном случае git, кажется, работает нормально через файловую систему. Я могу отслеживать файлы в /var/foo нормально.

Ожидаемое поведение должно быть таковым, что git check-ignore -q /var/foo возвращает 1, а git check-ignore -q /var/bar возвращает 0, если он не отслеживается.

как я могу исправить эту проблему?

git check-ignore . завершится с кодом выхода 128, если . не находится в репозитории git (или возникает любая другая ошибка), и с кодом выхода 1 только если путь не игнорируется. Поэтому вы можете проверять только последнее:

git check-ignore -q . 2>/dev/null; if [ "$?" -ne "1" ]; then ... 

Внутри then вы обрабатываете случай, когда . игнорируется или не находится в git репозитории.

Чтобы это работало через границы файловых систем, установите GIT_DISCOVERY_ACROSS_FILESYSTEM в true:

GIT_DISCOVERY_ACROSS_FILESYSTEM=true git check-ignore -q . 2>/dev/null; if [ "$?" -ne "1" ]; then ...

Сначала это должно быть:

if
  git rev-parse --is-inside-work-tree >/dev/null 2>&1 &&
    ! git check-ignore . >/dev/null 2>&1
then...

Замещение команд ($(...)) используется для получения результата команды. Здесь результата нет, так как вы перенаправляете его в /dev/null, и если бы он был, он был бы воспринят как команда для выполнения (и проверки ее статуса выполнения).

Обратите внимание, что вторая команда выполняется только если первая была успешной.

@StephenKitt показал, как объединить эти две команды в одну команду git, но если это все еще не помогает с точки зрения производительности, вы можете всегда кэшировать информацию в ассоциативном массиве.

typeset -A is_git # объявляется глобально в вашем ~/.zshrc
if (( ! $+is_git[$PWD] )); then
  git check-ignore -q . 2> /dev/null
  is_git[$PWD]=$(( $? == 1 ))
fi

if (( $is_git[$PWD] )); then...

Этот кэш может стать устаревшим, если вы переименуете директории, удалите директории .git или измените список игнорируемых файлов. Вы можете его аннулировать с помощью is_git=() или перезапустив zsh с помощью exec zsh.

Другой вариант — выполнять эту проверку только при входе в директорию, используя chpwd hook:

check_git() {
  git check-ignore -q . 2> /dev/null
  (( is_git = $? == 1 ))
}
chpwd_functions+=(check_git)

А затем, в вашем сценарии приглашения, это просто вопрос:

if (( is_git )); then...

Для удобства я обернул проверку репозитория в алиас is_git:

alias is_git="git rev-parse --is-inside-work-tree >/dev/null 2>&1"

Затем я мог использовать его вместе с несколькими другими алиасами, связанными с git, в моем приглашении zsh, чтобы включать и выключать их так:

export prompt=$'%{\e[34m%}%m %~%{\e[0m%} $(is_git && echo $(git_ahead_behind)$(gitb)$(git_dirty))\n%h|%T %# '

Ответ или решение

Вопрос о динамическом изменении подсказки в оболочке zsh в зависимости от того, находитесь ли вы внутри репозитория git и не игнорируется ли текущая директория, представляет собой классическую проблему настройки рабочей среды для разработчиков. Правильная настройка оболочки может существенно повысить производительность и удобство работы, особенно если учесть специфические условия, такие как кросс-файловая система управления версиями. Давайте более подробно рассмотрим, как можно решить эту задачу, следуя концепции TEA (Теория, Пример, Применение).

Теория

Основная задача состоит в том, чтобы определить, находимся ли мы внутри репозитория Git, и не игнорируется ли текущий каталог системой Git. Для этого обычно используются команды git rev-parse --is-inside-work-tree и git check-ignore. Первая команда служит для проверки, находимся ли мы внутри рабочей директории Git, а вторая — для выяснения, игнорируется ли указанный путь.

Команда git rev-parse --is-inside-work-tree возвращает успешный статус, если вы находитесь внутри репозитория, и ошибку в противном случае. Команда git check-ignore принимает путь и возвращает успешный статус (0), если путь игнорируется, иначе — 1. Таким образом, для проверки игнорирования необходимо обратить внимание на статус выхода.

Дополнительно учитывается переменная окружения GIT_DISCOVERY_ACROSS_FILESYSTEM, которая позволяет Gitу определять корневую директорию репозитория через файловые системы. Это важно, когда части репозитория находятся на разных файловых системах.

Пример

Просмотрев предложенные решения, которые включают использование команды git check-ignore совместно с установкой переменной GIT_DISCOVERY_ACROSS_FILESYSTEM, мы можем предложить следующее:

GIT_DISCOVERY_ACROSS_FILESYSTEM=true
if git rev-parse --is-inside-work-tree &>/dev/null && ! git check-ignore -q . &>/dev/null; then
  # Ваш код здесь, если мы внутри репозитория и текущий каталог не игнорируется
fi

Эта строка проверяет, находитесь ли вы внутри репозитория и не игнорируется ли текущая директория без использования команд подстановки $(...), что также сокращает затраты времени на выполнение команд и обрабатывает возможные ошибки с переменной среды.

Применение

Для лучшей производительности можно использовать кэширование результатов. Это особенно важно, если вы работаете на медленных машинах, где каждый запрос к Git может вызывать заметные задержки. Один из способов — использование ассоциативных массивов Zsh:

typeset -A is_git

if (( ! $+is_git[$PWD] )); then
  git check-ignore -q . 2>/dev/null
  is_git[$PWD]=$(( $? == 1 ))
fi

if (( $is_git[$PWD] )); then
  # Ваш код здесь
fi

Однако нужно помнить, что кэш может устареть, если структура каталогов изменится. В таких случаях его нужно будет сбросить через is_git=() или перезапустить сессию Zsh.

Альтернативный подход — использование хуков Zsh, таких как chpwd, который автоматически вызывает функцию при изменении каталога:

check_git() {
  git check-ignore -q . 2>/dev/null
  (( is_git = $? == 1 ))
}
chpwd_functions+=(check_git)

Теперь в вашей функции подсказки вы можете просто проверять статус is_git.

Настройка оболочки таким способом дает не только прирост производительности, но и более четкое понимание состояния текущей рабочей директории относительно системы Git, что является важным аспектом в работе разработчиков и администраторов систем.

Оцените материал
Добавить комментарий

Капча загружается...