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

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

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

11 ответов

Решение

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

git diff-index --quiet HEAD --

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

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

Примечание: как прокомментировал Anthony Sottile

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


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

Смотрите также " Проверка на наличие грязного индекса или неотслеживаемых файлов с помощью 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
}

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

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

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

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

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

Или же

git diff --quiet HEAD

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

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

if [[ -z $(git status -s) ]]
then
  echo "tree is clean"
else
  echo "tree is dirty, please commit changes before running this"
  exit
fi

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

Используйте предоставленные git status --porcelainкоторый предназначен для машинного анализа, несмотря на то, что некоторые люди (неправильно) говорят об обратном в комментариях. Если что-то появится вgit status, тогда я считаю рабочий каталог грязным. Так что проверяю на чистоту тестом[ -z "$(git status --porcelain=v1 2>/dev/null)" ], который также пройдет, если запускается вне каталога git.

Минимальный рабочий пример:

[ -z "$(git status --porcelain=v1 2>/dev/null)" ] && echo "git undirty"

Все, что появляется в git status(на данный момент) будет запускать этот тест правильно. В=v1 bit обеспечивает согласованный выходной формат для всех версий git.


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

Вдохновленный этим ответом. Выgrep линии git status --porcelain=v1выход. Первые два символа в каждой строке указывают на статус конкретного файла. После grepping вы подсчитываете, сколько людей имеют этот статус, передавая вывод вwc -l который считает количество строк.

Например, этот скрипт распечатает некоторую информацию, если он будет запущен в репозитории git.

#!/bin/sh
GS=$(git status --porcelain=v1 2>/dev/null) # Exit code 128 if not in git directory. Unfortunately this exit code is a bit generic but it should work for most purposes.
if [ $? -ne 128 ]; then
  function _count_git_pattern() {
    echo "$(grep "^$1" <<< $GS | wc -l)" 
  }                                           
  echo "There are $(_count_git_pattern "??") untracked files."                                 
  echo "There are $(_count_git_pattern " M") unstaged, modified files."
  echo "There are $(_count_git_pattern "M ")   staged, modified files."        
fi

Рабочее дерево "чистое", если

      git ls-files --deleted --modified --others --exclude-standard

ничего не возвращает.

Объяснение

  • --deleted проверить файлы, удаленные в рабочем дереве
  • --modified проверить файлы, измененные в рабочем дереве
  • --others проверьте наличие файлов, добавленных в рабочее дерево
  • --exclude-standard игнорировать в соответствии с обычным .gitignore, .git/info/exclude ... правила

Этот вывод пуст, если рабочее дерево чистое.

Я создал несколько удобных псевдонимов 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'

С Python и GitPython:

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

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

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

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...

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

Протестировано в терминале bash на Linux Ubuntu.

Сценарий оболочки для программной интерпретации вывода

...и сказать вам, если:

  1. у него была ошибка
  2. это показывает, что ваше рабочее дерево чистое (нет незафиксированных изменений) или
  3. это показывает, что ваше рабочее дерево грязное (у вас есть незафиксированные изменения).

Здесь есть отличный ответ: Unix и Llinux: определите, чист ли рабочий каталог Git из сценария . Мой ответ основан на этом.

Мы будем использовать опцию with, потому что она предназначена для анализа скриптами!

Отman git status(выделение добавлено):

--porcelain[=<version>]

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

The versionПараметр используется для указания версии формата. Это необязательно и по умолчанию используется исходный формат версии v1.

Итак, сделайте это:

Опция 1

      if output="$(git status --porcelain)" && [ -z "$output" ]; then
    echo "'git status --porcelain' had no errors AND the working directory" \
         "is clean."
else 
    echo "Working directory has UNCOMMITTED CHANGES."
fi

Первая часть,if output=$(git status --porcelain)потерпит неудачу и прыгнет вelseоговорка, еслиgit status --porcelainкоманда имеет ошибку. 2-я часть,&& [ -z "$output" ], тесты, чтобы увидеть, еслиoutputпеременная содержит пустой (zero-длина) строка. Если да, то все чисто и изменений нет.

Вариант 2

Однако обычно я предпочитаю отрицать тест с помощью-n(отличное от нуля) вместо-z(ноль) и сделайте это так:

      if output="$(git status --porcelain)" && [ -n "$output" ]; then
    echo "'git status --porcelain' had no errors AND the working directory" \
         "is dirty (has UNCOMMITTED changes)."
    # Commit the changes here
    git add -A
    git commit -m "AUTOMATICALLY COMMITTING UNCOMMITTED CHANGES"
fi

Вариант 3

Более детальный способ написания первого блока кода выше был бы таким:

      if ! git_status_output="$(git status --porcelain)"; then
    # `git status` had an error
    error_code="$?"
    echo "'git status' had an error: $error_code" 
    # exit 1  # (optional)
elif [ -z "$git_status_output" ]; then
    # Working directory is clean
    echo "Working directory is clean."
else
    # Working directory has uncommitted changes.
    echo "Working directory has UNCOMMITTED CHANGES."
    # exit 2  # (optional)
fi

Я протестировал весь приведенный выше код, скопировав и вставив целые блоки в свой терминал в репозитории в разных состояниях, и он отлично работает для всех трех условий:

  1. ваша команда неверна или написана с ошибкой
  2. чистый (нет незафиксированных изменений)
  3. git statusгрязный (у вас есть незафиксированные изменения)

Чтобы заставить'git status' had an errorвывод, просто опечатка--porcelainвариант, написав его как--porcelainnили что-то в этом роде, и вы увидите этот вывод в самом конце:

       'git status' had an error: 0

Вот лучший, самый чистый способ.

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
}
Другие вопросы по тегам