Как программно определить, есть ли незафиксированные изменения?

227

В Makefile я хотел бы выполнить определенные действия, если есть незафиксированные изменения (либо в рабочем дереве, либо в индексе). Какой самый чистый и эффективный способ сделать это? Команда, которая завершается с возвращаемым значением, равным нулю в одном случае и ненулевым значением в другом, подойдет моим целям.

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

Даниэль Штутцбах
источник

Ответы:

290

ОБНОВЛЕНИЕ : ОП Даниэль Штутцбах отмечает в комментариях, что эта простая команда git diff-indexработала для него:

git update-index --refresh 
git diff-index --quiet HEAD --

( Норнагон упоминает в комментариях, что, если есть файлы, к которым было произведено прикосновение, но содержимое которых такое же, как в индексе, вам нужно будет выполнить их git update-index --refreshраньше git diff-index, иначе diff-indexбудет неверно сообщено, что дерево загрязнено)

Затем вы можете увидеть « Как проверить, успешно ли выполнена команда? », Если вы используете ее в скрипте bash:

git diff-index --quiet HEAD -- || echo "untracked"; // do something about it

Примечание: в качестве комментировал от Энтони Sottile

git diff-index HEAD ...потерпит неудачу на ветви, которая не имеет коммитов (например, недавно инициализированный репозиторий).
Один из обходных путей, который я нашел, этоgit diff-index $(git write-tree) ...

А haridsvточки из в комментариях , что git diff-filesна новом файле не определяет его как дифф.
Кажется, что более безопасный подход - git addсначала запустить спецификацию файла, а затем использовать, git diff-indexчтобы увидеть, было ли что-либо добавлено в индекс перед запуском git commit.

git add ${file_args} && \
git diff-index --cached --quiet HEAD || git commit -m '${commit_msg}'

И 6502 сообщения в комментариях:

Одна проблема, с которой я столкнулся, заключается в том, что git diff-indexэто скажет, что есть различия, когда их действительно нет, за исключением временных отметок файлов.
Запуск, как git diffтолько решит проблему (довольно удивительно, git diffдействительно изменяет содержание песочницы, означая здесь .git/index)

Эти проблемы с отметками времени также могут возникать, если git работает в Docker .


Оригинальный ответ:

«Программно» означает никогда не полагаться на фарфоровые команды .
Всегда полагайтесь на сантехнические команды .

См. Также « Проверка на наличие грязного индекса или неотслеживаемых файлов с помощью Git » для альтернатив (например git status --porcelain)

Вы можете черпать вдохновение из новой « require_clean_work_treeфункции », которая написана так, как мы говорим ;) (начало октября 2010 года)

require_clean_work_tree () {
    # Update the index
    git update-index -q --ignore-submodules --refresh
    err=0

    # Disallow unstaged changes in the working tree
    if ! git diff-files --quiet --ignore-submodules --
    then
        echo >&2 "cannot $1: you have unstaged changes."
        git diff-files --name-status -r --ignore-submodules -- >&2
        err=1
    fi

    # Disallow uncommitted changes in the index
    if ! git diff-index --cached --quiet HEAD --ignore-submodules --
    then
        echo >&2 "cannot $1: your index contains uncommitted changes."
        git diff-index --cached --name-status -r --ignore-submodules HEAD -- >&2
        err=1
    fi

    if [ $err = 1 ]
    then
        echo >&2 "Please commit or stash them."
        exit 1
    fi
}
VonC
источник
12
Принцип «сантехника против фарфора для сценариев» - это урок, который Якуб Наренбский неоднократно упоминал мне: « Как вывести весь журнал для текущего проекта в git? », « Git: changelog день за днем », ...
VonC
18
После нажатия некоторых ссылок , которые вы предлагаете, я нашел то , что искал: git diff-index --quiet HEAD.
Даниэль Штутцбах
11
@DanielStutzbach: Это может не сработать, если у вас есть файл, вызываемый HEADв рабочем каталоге. Лучше использовать git diff-index --quiet HEAD --.
Дэвид Онгаро
7
И, тем не менее, руководство в git status --helpсостояниях: --porcelain Предоставляет вывод в простом для анализа формате для сценариев. Это похоже на короткий вывод, но останется стабильным во всех версиях Git и независимо от конфигурации пользователя. Смотрите ниже для деталей.
Эд Рэндалл
7
@VonC это действительно не имеет смысла. Таким образом, вы можете повернуть все в обратном направлении. - фарфор создает впечатление, что скоро сломается. Если это не так, его следует называть сантехникой, а не фарфором. Использование --porcelain приводит к тому, что ваш скрипт не ломается, что делает его НЕ фарфоровым скриптом ;-). Если вы хотите, чтобы ваш скрипт сломался, вы не должны использовать --porcelain !!. Так что совершенно непонятно это и всех отталкивает.
Xennex81
104

Хотя другие решения очень основательны, если вы хотите что-то действительно быстрое и грязное, попробуйте что-то вроде этого:

[[ -z $(git status -s) ]]

Он просто проверяет, есть ли какие-либо выходные данные в сводке состояния.

Nepthar
источник
7
работает для меня. используйте -n для обратного (у вас есть изменения), например `if [[-n $ (git status -s)]]; то ... fi`
Aaron
Это работает, но вы можете сказать, что на [[ ... ]]самом деле делает синтаксис? Я никогда не видел ничего подобного раньше.
GMA
2
@EM код возврата git statusфактически игнорируется в этом тесте. Это только смотрит на вывод. Посетите эту страницу, связанную с bash, для получения дополнительной информации о том [, [[как работает тестирование в bash.
Нептар
2
Это почти правильный ответ, но для сценария лучше использовать --porcelainпараметр, как показано здесь
Mariusz Pawelski
2
Вы можете использовать git status -s -uallдля включения неотслеживаемых файлов.
Барфуин
59

git diff --exit-codeвернет ненулевое значение, если будут какие-либо изменения; git diff --quietто же самое без выхода. Поскольку вы хотите проверить рабочее дерево и индекс, используйте

git diff --quiet && git diff --cached --quiet

Или

git diff --quiet HEAD

Любой из них скажет вам, есть ли незавершенные изменения, которые поставлены или нет.

Джош Ли
источник
6
Это не эквивалентно. Одна команда git diff --quite HEADтолько скажет вам, чистое ли рабочее дерево, а не чистый ли индекс. Например, если fileбыло изменено значение между HEAD ~ и HEAD, то после git reset HEAD~ -- fileэтого он все равно выйдет 0, даже если в индексе имеются поэтапные изменения (wt == HEAD, но index! = HEAD).
Крис Джонсен
2
Предупреждение, это не будет ловить файлы, удаленные из области подготовки с помощью git rm, AFAICS.
мр
24
Новые (неотслеживаемые) файлы не обнаруживаются git diff --quiet && git diff --cached --quiet.
4LegsDrivenCat
17

Расширяя ответ @ Nepthar:

if [[ -z $(git status -s) ]]
then
  echo "tree is clean"
else
  echo "tree is dirty, please commit changes before running this"
  exit
fi
Трэвис Ридер
источник
1
Это хорошо; Я использую его для $(git status -s "$file")elsegit add "$file"; git commit -m "your autocommit process" "$file"
автоматической
Если вы сделаете git status -sэто git status --porcelain ; git clean -ndвместо этого, здесь также будут появляться ненужные каталоги, которые невидимы для git status.
экманавт
4

Как указано в другом ответе, достаточно простой команды:

git diff-index --quiet HEAD --

Если вы пропустите последние две черты, команда не выполнится, если у вас есть файл с именем HEAD .

Пример:

#!/bin/bash
set -e
echo -n "Checking if there are uncommited changes... "
trap 'echo -e "\033[0;31mFAILED\033[0m"' ERR
git diff-index --quiet HEAD --
trap - ERR
echo -e "\033[0;32mAll set!\033[0m"

# continue as planned...

Слово предостережения: эта команда игнорирует неотслеживаемые файлы.

оборота санмай
источник
2
Как отмечено в комментариях к этому ответу, это не обнаруживает новые добавленные файлы
minexew
Нет, он обнаруживает новые добавленные в индексные файлы. Только что проверил.
sanmai
Смотри вопрос. Не отслеживаемые файлы не являются изменениями . git addи git cleanна помощь
sanmai
4

Я создал несколько удобных псевдонимов git, чтобы вывести список неподготовленных и промежуточных файлов:

git config --global alias.unstaged 'diff --name-only'
git config --global alias.staged 'diff --name-only --cached'

Тогда вы можете легко сделать такие вещи, как:

[[ -n "$(git unstaged)" ]] && echo unstaged files || echo NO unstaged files
[[ -n "$(git staged)" ]] && echo staged files || echo NO staged files

Вы можете сделать его более читабельным, создав скрипт где-то на вашем PATHвызываемом git-has:

#!/bin/bash
[[ $(git "$@" | wc -c) -ne 0 ]]

Теперь приведенные выше примеры можно упростить до:

git has unstaged && echo unstaged files || echo NO unstaged files
git has staged && echo staged files || echo NO staged files

Для полноты здесь аналогичные псевдонимы для неотслеживаемых и игнорируемых файлов:

git config --global alias.untracked 'ls-files --exclude-standard --others'
git config --global alias.ignored 'ls-files --exclude-standard --others --ignored'
клейкий
источник
2

С python и пакетом GitPython:

import git
git.Repo(path).is_dirty(untracked_files=True)

Возвращает True, если хранилище не является чистым

Pablo
источник
Это позволило избежать некоторых проблем с «отметкой времени», упомянутых в других комментариях
Jason
1
Обратите внимание, что GitPython также просто использует git CLI. Если вы установите, LOGLEVEL=DEBUGвы увидите все команды Popen, которые он использует для запускаgit diff
Jason
-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
источник
4
Нет, это не самый лучший. git statusэто «фарфоровая» команда. Не используйте фарфоровые команды в скриптах, так как они могут меняться в разных версиях git. Вместо этого используйте команды «сантехника».
spuder
3
Я думаю, что если вы обновили его для использования git status --porcelain(что предназначено для этой цели - стабильный формат, который вы можете анализировать в скрипте), возможно, также с -z (с нулевым разделением вместо новой строки?), Вы могли бы сделать что-то полезное с этой идеей , @ codyc4321 см stackoverflow.com/questions/6976473/... подробности
msouth