Вопрос или проблема
В моей оболочке 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
, и если бы он был, он был бы воспринят как команда для выполнения (и проверки ее статуса выполнения).
Обратите внимание, что вторая команда выполняется только если первая была успешной.
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, что является важным аспектом в работе разработчиков и администраторов систем.