Проверка на наличие грязного индекса или неотслеживаемых файлов с помощью Git

243

Как я могу проверить, есть ли у меня какие-либо незафиксированные изменения в моем git-репозитории:

  1. Изменения добавлены в индекс, но не зафиксированы
  2. Неотслеживаемые файлы

из скрипта?

git-status кажется, всегда возвращает ноль с версией git 1.6.4.2.

Роберт Мунтяну
источник
3
Состояние git вернет 1, если есть неизмененные измененные файлы. Но в целом я нахожу, что инструменты git не особенно тщательны с состоянием возврата. Например, git diff возвращает 0 независимо от того, есть ли различия.
интуитивно
11
@intuited: если вам нужно diffуказать наличие или отсутствие различий, а не успешное выполнение команды, вам нужно использовать --exit-codeили --quiet. Команды git, как правило, очень согласуются с возвратом нулевого или ненулевого кода завершения, чтобы указать успешность команды.
CB Bailey
1
@Charles Bailey: Привет, отлично, я пропустил этот вариант. Спасибо! Я полагаю, что мне никогда не нужно было это делать, иначе я бы, вероятно, нашел для этого справочную страницу. Рад, что вы меня поправили :)
интуитивно
1
Роберт - это очень запутанная тема для сообщества (не помогает то, что «фарфор» используется по-разному в разных контекстах). Выбранный вами ответ игнорирует в 2,5 раза более высокий голос, более надежный и соответствующий дизайну git. Вы видели ответ @ChrisJ?
Майк
1
@ Роберт - Я надеюсь, что вы могли бы изменить свой принятый ответ. Тот, который вы выбрали, основан на более хрупких командах git 'фарфор'; фактический правильный ответ (также с 2 -кратным повышением голосов) основан на правильных командах git 'plumbing'. Этот правильный ответ изначально был Крис Джонсен. Я поднимаю этот вопрос, поскольку сегодня я уже третий раз должен был отослать кого-то на эту страницу, но я не могу просто указать на ответ, я объяснил, почему принятый ответ является неоптимальным / пограничным неверным. Спасибо!
Майк

Ответы:

173

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

Вот что я делаю:

  1. Для грязного статуса:

    # Returns "*" if the current git branch is dirty.
    function evil_git_dirty {
      [[ $(git diff --shortstat 2> /dev/null | tail -n1) != "" ]] && echo "*"
    }
  2. Для неотслеживаемых файлов (обратите внимание на --porcelainфлаг, с git statusкоторым вы можете получить хороший анализируемый вывод):

    # Returns the number of untracked files
    
    function evil_git_num_untracked_files {
      expr `git status --porcelain 2>/dev/null| grep "^??" | wc -l` 
    }

Хотя git diff --shortstatэто более удобно, вы также можете использовать git status --porcelainдля получения грязных файлов:

# Get number of files added to the index (but uncommitted)
expr $(git status --porcelain 2>/dev/null| grep "^M" | wc -l)

# Get number of files that are uncommitted and not added
expr $(git status --porcelain 2>/dev/null| grep "^ M" | wc -l)

# Get number of total uncommited files
expr $(git status --porcelain 2>/dev/null| egrep "^(M| M)" | wc -l)

Примечание: 2>/dev/nullФильтрует сообщения об ошибках, чтобы вы могли использовать эти команды в не-git каталогах. (Они просто вернутся 0для подсчета файлов.)

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

Вот посты:

Добавление информации о состоянии Git в подсказку терминала

Улучшенная подсказка оболочки с поддержкой Git

0xfe
источник
8
Стоит отметить, что завершение git bash поставляется с функцией оболочки, которая выполняет почти все, что вы делаете с вашим приглашением - __git_ps1. Он показывает имена веток, включая особый режим, если вы находитесь в процессе ребазирования, am-apply, merge или bisect. И вы можете установить переменную среды, GIT_PS1_SHOWDIRTYSTATEчтобы получить звездочку для не поэтапных изменений и плюс для поэтапных изменений. (Я думаю, что вы также можете получить его, чтобы указать неотслеживаемые файлы и дать вам некоторый git-describeвывод)
Cascabel
8
Предупреждение: git diff --shortstatдаст ложный отрицательный результат, если изменения уже внесены в индекс.
Марко Топольник
4
git status --porcelainявляется предпочтительным, потому git diff --shortstatчто не будет ловить вновь созданные пустые файлы. Вы можете попробовать это в любом чистом рабочем дереве:touch foo && git diff --shortstat
Кампадреналин
7
НЕТ - фарфор означает, что продукция предназначена для людей и легко ломается! см. ответ @ChrisJohnsen, в котором правильно используются стабильные, удобные для сценариев параметры.
Майк
7
@mike со страницы руководства git-status о параметре фарфора: «Дайте вывод в простом для анализа формате для сценариев. Это похоже на короткий вывод, но останется стабильным во всех версиях Git и независимо от конфигурации пользователя. "
itadok
400

Ключом к надежному «написанию сценариев» Git является использование «сантехнических» команд.

При изменении сантехнических команд разработчики заботятся о том, чтобы они обеспечивали очень стабильные интерфейсы (т. Е. Заданная комбинация состояния репозитория, стандартного ввода, параметров командной строки, аргументов и т. Д. Будет производить одинаковый вывод во всех версиях Git, где команда / вариант существует). Новые варианты вывода в сантехнических командах могут быть введены с помощью новых параметров, но это не может создавать проблем для программ, которые уже были написаны для более старых версий (они не будут использовать новые параметры, поскольку их не было (или, по крайней мере, не используется) на момент написания сценария).

К сожалению, «каждодневные» команды Git - это «фарфоровые» команды, поэтому большинство пользователей Git могут быть не знакомы с сантехническими командами. Различия между командами фарфора и сантехники выполняются на главной странице руководства git (см. Подразделы « Команды высокого уровня (фарфор)» и « Команды низкого уровня (сантехника)» .


Чтобы узнать о несвязанных изменениях, вам, вероятно, понадобится git diff-index(сравнить индекс (и, возможно, отслеживаемые биты рабочего дерева) с некоторыми другими древовидными (например HEAD)), возможно git diff-files(сравнить рабочее дерево с индексом) и, возможно, git ls-files(список файлов; например, список без отслеживания , проигнорированные файлы).

(Обратите внимание, что в приведенных ниже командах HEAD --используется вместо, HEADпотому что в противном случае команда не выполняется, если существует файл с именем HEAD.)

Чтобы проверить, что хранилище внесло изменения (еще не зафиксировано), используйте это:

git diff-index --quiet --cached HEAD --
  • Если он выходит с, 0то не было никаких различий ( 1значит, были различия).

Чтобы проверить, есть ли в рабочем дереве изменения, которые можно поставить:

git diff-files --quiet
  • Код выхода такой же, как и для git diff-index( 0== без различий; 1== отличий).

Чтобы проверить, изменяется ли комбинация индекса и отслеживаемых файлов в рабочем дереве по отношению к HEAD:

git diff-index --quiet HEAD --
  • Это как комбинация двух предыдущих. Одно из главных отличий состоит в том, что он по-прежнему будет сообщать об отсутствии различий, если у вас есть поэтапное изменение, которое вы «отменили» в рабочем дереве (возвращаясь к содержимому, которое находится в нем HEAD). В этой же ситуации обе отдельные команды будут возвращать отчеты о «существующих различиях».

Вы также упомянули неотслеживаемые файлы. Вы можете иметь в виду «неотслеживаемый и игнорируемый», или вы можете иметь в виду просто «неотслеживаемый» (включая игнорируемые файлы). В любом случае, git ls-filesинструмент для работы:

Для «неотслеживаемых» (включая игнорируемые файлы, если они есть):

git ls-files --others

Для «неотслеживаемых и неопознанных»:

git ls-files --exclude-standard --others

Моя первая мысль - просто проверить, есть ли у этих команд вывод:

test -z "$(git ls-files --others)"
  • Если он завершается с, 0то нет неотслеживаемых файлов. Если он завершается с, 1то есть неотслеживаемые файлы.

Существует небольшая вероятность того, что это преобразует git ls-filesаварийные выходы из отчетов «без отслеживания файлов» (оба результата приводят к ненулевым выходам вышеуказанной команды). Более надежная версия может выглядеть так:

u="$(git ls-files --others)" && test -z "$u"
  • Идея та же, что и в предыдущей команде, но она позволяет распространяться неожиданным ошибкам git ls-files. В этом случае ненулевой выход может означать «есть неотслеживаемые файлы» или это может означать, что произошла ошибка. Если вы хотите, чтобы результаты «ошибка» сочетались с результатом «нет неотслеживаемых файлов», используйте test -n "$u"(где выход 0означает «некоторые неотслеживаемые файлы», а ненулевое значение означает ошибку или «нет неотслеживаемых файлов»).

Другая идея заключается в использовании --error-unmatchдля ненулевого выхода, когда нет неотслеживаемых файлов. Это также рискует связать «нет неотслеживаемых файлов» (выход 1) с «произошла ошибка» (выход ненулевой, но, вероятно, 128). Но проверка « 0против» и « 1против нуля» кодов выхода, вероятно, достаточно надежна:

git ls-files --others --error-unmatch . >/dev/null 2>&1; ec=$?
if test "$ec" = 0; then
    echo some untracked files
elif test "$ec" = 1; then
    echo no untracked files
else
    echo error from ls-files
fi

Любой из приведенных выше git ls-filesпримеров можно взять, --exclude-standardесли вы хотите рассмотреть только неотслеживаемые и незарегистрированные файлы.

Крис Джонсен
источник
5
Я хотел бы отметить, что git ls-files --othersвыдает локальные неотслеживаемые файлы, а git status --porcelainиз принятого ответа - все неотслеживаемые файлы, которые находятся в репозитории git. Я не тот, который хотел оригинальный плакат, но разница между ними интересна.
Эрик О Лебиго
1
@phunehehe: Вам нужно указать спецификацию пути --error-unmatch. Попробуйте (например) git ls-files --other --error-unmatch --exclude-standard .(обратите внимание на конечный период, он относится к cwd; запустите его из каталога верхнего уровня рабочего дерева).
Крис Джонсен
8
@phs: Возможно, вам придется сделать это git update-index -q --refreshперед тем, diff-indexчтобы избежать «ложных срабатываний», вызванных несовпадением информации stat (2).
Крис Джонсен
1
Я был укушен git update-indexнеобходимостью! Это важно, если что-то касается файлов без внесения изменений.
Nakedible
2
@RobertSiemer Под «локальным» я имел в виду файлы в вашем текущем каталоге , который может быть ниже его основного репозитория git. В --porcelainсписках решений все неотслеживаемые файлов , найденных в целом репозиторий (минус ГИТ-игнорируемые файлы), даже если вы находитесь в одном из его подкаталогов.
Эрик О Лебиго
140

Предполагая, что вы находитесь на Git 1.7.0 или более поздней версии ...

Прочитав все ответы на этой странице и немного поэкспериментировав, я думаю, что метод, который находит правильное сочетание правильности и краткости:

test -n "$(git status --porcelain)"

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

В этом случае имеет смысл смоделировать действия программиста: наберите git statusи посмотрите на результат. Но мы не хотим полагаться на показ определенных слов, поэтому мы используем --porcelainрежим, введенный в 1.7.0; при включении чистый каталог не приводит к выводу.

Затем мы используем, test -nчтобы увидеть, был ли какой-либо вывод или нет.

Эта команда вернет 1, если рабочий каталог чистый, и 0, если есть изменения, которые нужно зафиксировать. Вы можете изменить -nк , -zесли вы хотите наоборот. Это полезно для связывания этого с командой в сценарии. Например:

test -z "$(git status --porcelain)" || red-alert "UNCLEAN UNCLEAN"

Это фактически говорит: «либо нет никаких изменений, которые нужно сделать, либо выведите сигнал тревоги»; эта однострочная строка может быть предпочтительнее оператора if в зависимости от сценария, который вы пишете.

benzado
источник
1
Для меня все остальные команды давали разные результаты в одном и том же повторении между Linux и Windows. Эта команда дала мне одинаковый вывод в обоих.
Адарша
6
Спасибо за то, что ответили на вопрос и не болтаете снова и снова, никогда не давая четкого ответа.
NateS
Для сценария ручного развертывания, объедините это с test -n "$(git diff origin/$branch)", чтобы помочь предотвратить локальные коммиты в развертывании
Эрик Аронести
14

Реализация из ответа VonC :

if [[ -n $(git status --porcelain) ]]; then echo "repo is dirty"; fi
Дин Ратер
источник
8

Посмотрел несколько из этих ответов ... (и имел различные проблемы с * nix и windows, что было моим требованием) ... нашел, что следующее работает хорошо ...

git diff --no-ext-diff --quiet --exit-code

Чтобы проверить код выхода в * nix

echo $?   
#returns 1 if the repo has changes (0 if clean)

Проверить код выхода в окне $

echo %errorlevel% 
#returns 1 if the repos has changes (0 if clean) 

Получено с https://github.com/sindresorhus/pure/issues/115 Благодаря @paulirish на этом посту, для обмена

mlo55
источник
4

Почему бы не инкапсулировать git statusсценарий, который:

  • проанализирует вывод этой команды
  • вернет соответствующий код ошибки в зависимости от того, что вам нужно

Таким образом, вы можете использовать этот «расширенный» статус в вашем скрипте.


Как 0xfe упоминает в своем превосходном ответе , он git status --porcelainиграет важную роль в любом решении на основе сценариев

--porcelain

Дайте вывод в стабильном, простом для анализа формате для скриптов.
В настоящее время это идентично --short output, но гарантированно не изменится в будущем, что делает его безопасным для сценариев.

VonC
источник
Потому что я ленивый, наверное. Я подумал, что для этого есть встроенная программа, так как это выглядит довольно часто встречающимся вариантом использования.
Роберт Мунтяну
Я разместил решение, основанное на вашем предложении, хотя я не очень доволен им.
Роберт Мунтяну
4

Возможность "Сделай сам", обновлена ​​в соответствии с предложением 0xfe

#!/bin/sh
exit $(git status --porcelain | wc -l) 

Как отметил Крис Джонсен , это работает только на Git 1.7.0 или новее.

Роберт Мунтяну
источник
6
Проблема в том, что вы не можете с уверенностью ожидать, что строка «рабочий каталог будет чистой» в будущих версиях. Флаг --porcelain предназначался для разбора, поэтому лучшим решением было бы: exit $ (git status --porcelain | wc -l)
0xfe
@ 0xfe - Ты знаешь, когда --porcelainбыл добавлен флаг? Не работает с 1.6.4.2.
Роберт Мунтяну
@ Роберт: попробуй git status --shortтогда.
VonC
3
git status --porcelainи git status --shortоба были введены в 1.7.0. --porcelainОн был введен специально, чтобы позволить git status --shortизменить его формат в будущем. Таким образом, git status --shortпострадает та же проблема, что и git status(выход может измениться в любое время, так как это не команда «слесарного дела»).
Крис Джонсен
@ Крис, спасибо за справочную информацию. Я обновил ответ, чтобы отразить лучший способ сделать это в Git 1.7.0.
Роберт Мунтяну
2

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

Это очень важно для избежания случая, когда сборки производят остатки.

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

 test -z "$(git status --porcelain | tee /dev/fd/2)" || \
     {{ echo "ERROR: git unclean at the end, failing build." && return 1 }}

Это может выглядеть немного сложным, и я был бы признателен, если бы кто-нибудь нашел укороченный вариант, который поддерживает желаемое поведение:

  • нет выходных данных и выходного кода succcess, если даже что-то в порядке
  • код выхода 1, если он не работает
  • сообщение об ошибке на stderr с объяснением причины сбоя
  • отобразить список файлов, вызвавших сбой, снова stderr.
Сорин
источник
2

Ответ @ eduard-wirch был довольно полным, но, поскольку я хотел проверить оба варианта одновременно, вот мой последний вариант.

        set -eu

        u="$(git ls-files --others)"
        if ! git diff-index --name-only --quiet HEAD -- || [ -z "${u:-}" ]; then
            dirty="-dirty"
        fi

Когда не выполняется с использованием set -e или эквивалентного, мы вместо этого можем сделать u="$(git ls-files --others)" || exit 1(или вернуть, если это работает для используемой функции)

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

после чего мы можем проверить оба свойства и установить переменную (или любую другую).

Оливер
источник
1

Это более скорлупа дружественный вариант для выяснения , если какие - либо неотслеживаемые файлы существуют в хранилище:

# Works in bash and zsh
if [[ "$(git status --porcelain 2>/dev/null)" = *\?\?* ]]; then
  echo untracked files
fi

Это не является вторым процессом grepи не требует проверки, находитесь ли вы в git-репозитории или нет. Что удобно для подсказок оболочки и т. Д.

docwhat
источник
1

Вы также можете сделать

git describe --dirty

, В конце он добавит слово «-dirty», если обнаружит грязное рабочее дерево. По словам git-describe(1):

   --dirty[=<mark>]
       Describe the working tree. It means describe HEAD and appends <mark> (-dirty by default) if
       the working tree is dirty.

, Предостережение: неотслеживаемые файлы не считаются «грязными», потому что, как говорится на man-странице, он заботится только о рабочем дереве.

Линус Арвер
источник
0

Там может быть лучшей комбинацией ответов из этой темы .. но это работает для меня ... для вашего .gitconfig«s [alias]раздела ...

          # git untracked && echo "There are untracked files!"
untracked = ! git status --porcelain 2>/dev/null | grep -q "^??"
          # git unclean && echo "There are uncommited changes!"
  unclean = ! ! git diff --quiet --ignore-submodules HEAD > /dev/null 2>&1
          # git dirty && echo "There are uncommitted changes OR untracked files!"
    dirty = ! git untracked || git unclean
Алекс Грей
источник
0

Простейший автоматический тест, который я использую для обнаружения грязного состояния = любые изменения, включая неотслеживаемые файлы :

git add --all
git diff-index --exit-code HEAD

НОТА:

  • Без add --all diff-indexне замечает неотслеживаемых файлов.
  • Обычно я запускаю git resetпосле проверки кода ошибки, чтобы вернуть все обратно.
uvsmtid
источник
Вопрос, в частности, «из сценария» ... не очень хорошая идея менять индекс только для проверки на грязное состояние.
ScottJ
@ ScottJ, когда это решает проблему, не все строго следят за тем, можно ли изменить индекс или нет. Рассмотрим случай автоматической работы, который касается автоматических исправлений источников с номером версии и создания тега - все, что вам нужно, это убедиться, что никакие другие локальные модификации не останутся на пути (независимо от того, присутствуют они в индексе или нет). До сих пор это был надежный тест на любые изменения, включая неотслеживаемые файлы .
Uvsmtid
-3

Вот лучший, самый чистый способ. Выбранный ответ по какой-то причине у меня не сработал, он не собирал инсценированные изменения, которые были новыми файлами, которые не были зафиксированы.

function git_dirty {
    text=$(git status)
    changed_text="Changes to be committed"
    untracked_files="Untracked files"

    dirty=false

    if [[ ${text} = *"$changed_text"* ]];then
        dirty=true
    fi

    if [[ ${text} = *"$untracked_files"* ]];then
        dirty=true
    fi

    echo $dirty
}
codyc4321
источник