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


243

Як я можу перевірити, чи є в моєму сховищі git які-небудь незапущені зміни:

  1. Зміни додані до індексу, але не здійснені
  2. Невилучені файли

зі сценарію?

git-status здається, завжди повертає нуль при версії 1.6.4.2 git.


3
статус git повернеться 1, якщо є незмічені модифіковані файли. Але в цілому я вважаю, що інструменти для git не є особливо ретельними зі статусом повернення. EG git diff повертає 0, чи є різниці чи ні.
інтуїтоване

11
@intuited: Якщо вам потрібно diffвказати на наявність чи відсутність відмінностей, а не на успішному виконанні команди, тоді вам потрібно використовувати --exit-codeабо --quiet. Команди git, як правило, дуже відповідають поверненню нульового або ненульового коду виходу, що свідчить про успіх команди.
CB Bailey

1
@Charles Bailey: Ей чудово, я пропустив цей варіант. Дякую! Я здогадуюсь, що я ніколи не потребував цього для цього, або я, мабуть, би спробував це зробити. Радий, що виправили мене :)
інтуїтивно,

1
Роберт - це дуже заплутана тема для спільноти (не допомагає, що "порцеляна" в різних контекстах використовується по-різному). Вибрана відповідь ігнорує надійну відповідь у 2,5 рази, яка є більш надійною та відповідає дизайну git. Ви бачили відповідь @ChrisJ?
Майк

1
@Robert - Сподіваюся, ви можете подумати про зміну прийнятої відповіді. Вибраний вами покладається на більш крихкі команди порцеляни git; фактична правильна відповідь (також із 2 -кратними відгуками) покладається на правильні команди "сантехніка" git. Цю правильну відповідь спочатку Кріс Джонсен. Я доводжу це, оскільки сьогодні - це вже 3-й раз, коли мені довелося когось посилати на цю сторінку, але я не можу просто вказати на відповідь, я поясню, чому прийнята відповідь є неоптимальною / граничною невірною. Дякую!
Майк

Відповіді:


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 до запиту про термінал

Покращено підказку Shell Shept


8
Варто відзначити, що завершення git bash оснащено функцією оболонки для того, щоб робити майже все, що ви робите зі своїм підказом - __git_ps1. У ньому відображаються назви гілок, включаючи спеціальне оброблення, якщо ви перебуваєте в процесі перезавантаження, змінення, об'єднання чи бісектії. І ви можете встановити змінну середовища, GIT_PS1_SHOWDIRTYSTATEщоб отримати зірочку для нестандартних змін та плюс для поетапних змін. (Я думаю, ви також можете отримати його для вказівки відслідковуваних файлів та дати деякий git-describeвихід)
Cascabel

8
Попередження: git diff --shortstatдасть помилковий мінус, якщо зміни вже є в індексі.
Марко Топольник

4
git status --porcelainбажано, тому що git diff --shortstatне буде ловити новостворені порожні файли. Ви можете спробувати його в будь-якому чистому робочому дереві:touch foo && git diff --shortstat
Campadrenalin

7
НІ - порцеляна означає, що вихід призначений для людей і легко руйнується !! див. відповідь @ChrisJohnsen, яка правильно використовує стабільні, зручні для сценарії варіанти.
Майк

7
@mike зі сторінки "git-status man" про варіант порцеляни: "Надайте вихід у простому для розбору форматі для сценаріїв. Це схоже на короткий вихід, але залишатиметься стабільним у всіх версіях Git та незалежно від конфігурації користувача. "
itadok

399

Ключ для надійного "сценарію" Git - використання команд "сантехніка".

Розробники дбають про зміну команд сантехніки, щоб переконатися, що вони забезпечують дуже стабільні інтерфейси (тобто задана комбінація стану репозиторію, stdin, параметрів командного рядка, аргументів тощо) дасть однаковий вихід у всіх версіях 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потреба! Це важливо, якщо щось торкається файлів без змін.
Голий

2
@RobertSiemer Під "локальним" я мав на увазі файли у вашому поточному каталозі , які можуть бути нижче його основного сховища git. У --porcelainрішенні перераховані всі неполічені файли, що знаходяться у всьому сховищі git (мінус git-ігноровані файли), навіть якщо ви знаходитесь в одному з його підкаталогів.
Ерік О Лебігот

139

Якщо припустити, що ви на 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, залежно від сценарію, який ви пишете.


1
Для мене всі інші команди давали різні результати на одне і те ж представлення між Linux та Windows. Ця команда дала мені однаковий вихід в обох.
Адарша

6
Дякуємо, що відповідали на запитання і не блукали далі і ніколи, не даючи чіткої відповіді.
NateS

Для сценарію ручного розгортання комбінуйте це test -n "$(git diff origin/$branch)", щоб запобігти дозволу місцевих
комісій


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/isissue/115 Завдяки @paulirish на цій публікації за спільний доступ


4

Чому б не інкапсулювати ' git statusзі сценарієм, який:

  • проаналізує вихід цієї команди
  • поверне відповідний код помилки на основі того, що вам потрібно

Таким чином, ви можете використовувати цей "розширений" статус у своєму сценарії.


Як згадує 0xfe у своїй чудовій відповіді , git status --porcelainце важливо для будь-якого рішення на основі сценарію

--porcelain

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


Тому що я ледачий, певно. Я подумав, що для цього є вбудований, як це здається досить часто зустрічається випадок використання.
Роберт Мунтяну

Я розмістив рішення на основі вашої пропозиції, хоча я не дуже задоволений цим.
Роберт Мунтяну

4

Один можливість зробити сам, оновлений відповідно до пропозицій 0xfe

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

Як зазначив Кріс Джонсен , це працює лише на Git 1.7.0 або новіших версій.


6
Проблема з цим полягає в тому, що ви не можете надійно сподіватися на рядок "робочий каталог чистим" у наступних версіях. Прапор --porcelain був призначений для розбору, тому кращим рішенням було б: вихід $ (git status --porcelain | wc -l)
0xfe

@ 0xfe - Ви знаєте, коли --porcelainпрапор було додано? Не працює з 1.6.4.2.
Роберт Мунтяну

@Robert: спробуйте git status --shortтоді.
VonC

3
git status --porcelainі git status --shortобидва вони були введені в 1.7.0. --porcelainВін був запроваджений спеціально для того, щоб git status --shortнадалі міняти його формат. Таким чином, git status --shortбуде страждати така ж проблема, як git status(вихід може змінитися в будь-який час, оскільки це не команда "сантехніка").
Кріс Джонсен

@Chris, дякую за довідкову інформацію. Я оновив відповідь, щоб відобразити найкращий спосіб зробити це від 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 }}

Це може виглядати трохи складно, і я вдячний, якщо хтось знайде короткий варіант, який підтримує бажану поведінку:

  • відсутність вихідного та успішного коду виходу, якщо парне все в порядку
  • вихідний код 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 чи ні. Що зручно для підказок оболонки тощо.


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.

. Caveat: файли, що не відслідковуються, не вважаються "брудними", оскільки, як зазначається в сторінці сторінки, вона дбає лише про робоче дерево.


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після тестування коду помилки, щоб знеструмити все назад.

Питання специфічно "зі сценарію" ... не годиться міняти індекс лише для перевірки на брудний стан.
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
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.