Як я можу програмно визначити, чи є зміни, що не відбулися?


226

У Makefile я хотів би виконати певні дії, якщо є неспроможні зміни (або в робочому дереві, або в індексі). Який найчистіший та найефективніший спосіб це зробити? Команда, яка виходить зі зворотним значенням нуля в одному випадку і ненульовим в іншому, відповідала б моїм цілям.

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


Відповіді:


288

UPDATE : ВП Daniel Stutzbach вказує в коментарях , що ця проста команда git diff-indexпрацювала для нього:

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

( Nornagon згадує в коментарях , що, якщо є файли , які були порушені, але зміст яких такого ж , як в індексі, вам потрібно запустити 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
}

12
Принцип "сантехніка проти порцеляни для сценаріїв" - це урок, який Якуб Нарбський неодноразово згадував мені: " Як перелічити весь журнал для поточного проекту в git? ", " Git: змінити день у день ", ...
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) ]]

Він просто перевіряє, чи є який-небудь вихід у резюме стану.


7
працює для мене. використовувати -n для зворотного (у вас є зміни), наприклад, `if [[-n $ (git status -s)]]; тоді ... fi`
aaron

Це працює, але чи можете ви сказати, що [[ ... ]]насправді робить синтаксис? Я ніколи раніше такого подібного не бачив.
GMA

2
@EM код повернення git statusфактично ігнорується в цьому тесті. Він дивиться лише на вихід. Перегляньте цю сторінку, пов’язану з bash, щоб дізнатися більше про те [, [[як працює тестування в bash.
Нептар

2
Це майже правильна відповідь, але для сценарію краще використовувати --porcelainпараметр, як показано тут
Маріуш Павельський

2
Ви можете використовувати git status -s -uallдля включення файлів, що не відслідковуються.
barfuin

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, але індекс! = HEAD).
Кріс Джонсен

2
Попередження, це не буде захоплювати файли, видалені з області постановки за допомогою git rm, AFAICS.
nmr

24
Нові (відслідковувані) файли не виявляються git diff --quiet && git diff --cached --quiet.
4LegsDrivenCat

17

Розгортання відповіді @ Нептара:

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")а потім у elseпунктіgit add "$file"; git commit -m "your autocommit process" "$file"
toddkaufmann

Якщо ви зробите 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, якщо сховище не є чистим


Це дозволило уникнути деяких питань «мітки часу», згаданих в інших коментарях
Джейсон

1
Зауважте, що GitPython також просто використовує git CLI. Якщо ви встановите, LOGLEVEL=DEBUGви побачите всі команди 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
}

4
Ні, це не найкраще. git status- команда 'порцеляна'. Не використовуйте порцелянові команди в скриптах, оскільки вони можуть змінюватися між версіями git. Замість цього використовуйте команди "сантехніка".
спудер

3
Я думаю, якби ви оновили його для використання git status --porcelain(що призначено для цієї мети - стабільний формат, який ви можете розбирати в сценарії), можливо, також з -z (розділений з нулем замість нового рядка?), Ви могли б зробити щось корисне з цією ідеєю . @ Codyc4321 см stackoverflow.com/questions/6976473 / ... подробиці
msouth
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.