Jak programowo ustalić, czy występują niezatwierdzone zmiany?

226

W pliku Makefile chciałbym wykonać pewne działania, jeśli wystąpią niezatwierdzone zmiany (w drzewie roboczym lub w indeksie). Jaki jest najczystszy i najskuteczniejszy sposób to zrobić? Polecenie kończące się z wartością zwracaną zero w jednym przypadku i niezerową w drugim byłoby odpowiednie dla moich celów.

Mogę git statusprzepuszczać i przesyłać dane wyjściowe grep, ale wydaje mi się, że musi istnieć lepszy sposób.

Daniel Stutzbach
źródło

Odpowiedzi:

288

AKTUALIZACJA : OP Daniel Stutzbach wskazuje w komentarzach, że to proste polecenie git diff-indexdziałało dla niego:

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

( nornagon wspomina w komentarzach, że jeśli istnieją pliki, które zostały dotknięte, ale których zawartość jest taka sama jak w indeksie, musisz uruchomić git update-index --refreshwcześniej git diff-index, w przeciwnym razie diff-indexniepoprawnie zgłosi, że drzewo jest brudne)

Następnie możesz zobaczyć „ Jak sprawdzić, czy polecenie się powiodło? ”, Jeśli używasz go w skrypcie bash:

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

Uwaga: jak skomentował przez Anthony Sottile

git diff-index HEAD ...zawiedzie w gałęzi, która nie ma zatwierdzeń (takich jak nowo zainicjowane repozytorium).
Znalazłem jedno obejściegit diff-index $(git write-tree) ...

I haridsvzwraca uwagę, w komentarzach , że git diff-filesna nowym pliku nie wykrywa go jako diff.
Wydaje się, że bezpieczniejszym rozwiązaniem jest git addnajpierw uruchomienie specyfikacji pliku, a następnie git diff-indexsprawdzenie, czy przed uruchomieniem coś zostało dodane do indeksu git commit.

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

I 6502 raportów w komentarzach:

Jednym z problemów, na które wpadłem, jest to, że git diff-indexbędą istniały różnice, a tak naprawdę nie ma żadnych, z wyjątkiem znaczników czasu plików.
Uruchomienie git diffraz rozwiązuje problem (co zaskakujące, w git diffrzeczywistości zmienia zawartość piaskownicy, co oznacza tutaj .git/index)

Te problemy ze znacznikami czasu mogą również wystąpić, jeśli git działa w oknie dokowanym .


Oryginalna odpowiedź:

„Programowo” oznacza, że nigdy nie należy polegać na porcelanowych poleceniach .
Zawsze polegaj na poleceniach hydraulicznych .

Zobacz także „ Sprawdzanie brudnego indeksu lub nieśledzonych plików za pomocą Git ”, aby znaleźć alternatywne rozwiązania (np. git status --porcelain)

Możesz czerpać inspirację z nowej „ require_clean_work_treefunkcji ”, która jest napisana w trakcie rozmowy ;) (początek października 2010 r.)

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
źródło
12
Zasada „hydraulika kontra porcelana do pisania skryptów” to lekcja, o której wielokrotnie powtarzał mi Jakub Narębski : „ Jak wyświetlić wszystkie dzienniki dla bieżącego projektu w git? ”, „ Git: dziennik zmian dzień po dniu ”, ...
VonC
18
Po kliknięciu niektóre łącza sugerujesz, znalazłem to, czego szukałem: git diff-index --quiet HEAD.
Daniel Stutzbach,
11
@DanielStutzbach: To może się nie powieść, jeśli masz plik wywołany HEADw katalogu roboczym. Lepsze wykorzystanie git diff-index --quiet HEAD --.
David Ongaro
7
A jednak instrukcja git status --helpstanów: --porcelain Podaj dane wyjściowe w łatwym do przeanalizowania formacie dla skryptów. Jest to podobne do krótkiego wyjścia, ale pozostanie stabilne w wersjach Git i niezależnie od konfiguracji użytkownika. Szczegóły poniżej.
Ed Randall
7
@VonC to naprawdę nie ma sensu. W ten sposób możesz przekręcić wszystko na odwrót. --porcelain daje wrażenie, że wkrótce się zepsuje. Jeśli tak nie jest, należy to nazwać hydrauliką, a nie porcelaną. Użycie --porcelain powoduje, że skrypt się nie psuje, co NIE czyni go skryptem porcelanowym ;-). Jeśli chcesz, aby twój skrypt się zepsuł, nie powinieneś używać --porcelain !!. Jest to więc całkowicie niezrozumiałe i odstrasza wszystkich.
Xennex81,
104

Podczas gdy inne rozwiązania są bardzo dokładne, jeśli chcesz czegoś naprawdę szybkiego i brudnego, spróbuj czegoś takiego:

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

Sprawdza tylko, czy w podsumowaniu statusu jest jakaś informacja wyjściowa.

Nepthar
źródło
7
pracuje dla mnie. użyj -n dla odwrotności (masz zmiany) np. `if [[-n $ (git status -s)]]; potem ... fi`
aaron
To działa, ale czy możesz powiedzieć, co [[ ... ]]właściwie robi składnia? Nigdy wcześniej czegoś takiego nie widziałem.
GMA
2
@EM kod powrotu git statusjest faktycznie ignorowany w tym teście. Patrzy tylko na wynik. Sprawdź tę stronę związaną z bash, aby uzyskać więcej informacji o tym [, [[jak testowanie działa w bash.
Nepthar
2
To prawie poprawna odpowiedź, ale dla skryptu lepiej użyć --porcelainparametru pokazanego tutaj
Mariusz Pawelski,
2
Możesz użyć, git status -s -uallaby dołączyć nieśledzone pliki.
barfuin
59

git diff --exit-codezwróci wartość niezerową, jeśli wystąpią jakiekolwiek zmiany; git diff --quietjest taki sam bez wyjścia. Ponieważ chcesz sprawdzić działające drzewo i indeks, użyj

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

Lub

git diff --quiet HEAD

Każdy z nich powie ci, czy istnieją niezatwierdzone zmiany, które zostały wprowadzone lub nie.

Josh Lee
źródło
6
Nie są równoważne. Jedno polecenie git diff --quite HEADpowie ci tylko, czy działające drzewo jest czyste, a nie czy indeks jest czysty. Na przykład, jeśli filezostał zmieniony między HEAD ~ i HEAD, to po git reset HEAD~ -- filetym nadal będzie kończył 0, mimo że w indeksie występują zmiany stopniowane (wt == HEAD, ale indeks! = HEAD).
Chris Johnsen,
2
Ostrzeżenie: nie złapie plików usuniętych z obszaru pomostowego za pomocą git rm, AFAICS.
nmr
24
Nowe (nieśledzone) pliki nie są wykrywane przez git diff --quiet && git diff --cached --quiet.
4LegsDrivenCat
17

Rozwijając odpowiedź @ Nepthara:

if [[ -z $(git status -s) ]]
then
  echo "tree is clean"
else
  echo "tree is dirty, please commit changes before running this"
  exit
fi
Travis Reeder
źródło
1
To jest dobre; Używam go do automatycznego zatwierdzania pojedynczych plików przez testowanie, $(git status -s "$file")a następnie w elseklauzuligit add "$file"; git commit -m "your autocommit process" "$file"
toddkaufmann
Jeśli zrobisz git status -sto git status --porcelain ; git clean -ndzamiast tego, pojawią się tutaj również katalogi śmieci, które są niewidoczne git status.
ecmanaut
4

Jak wskazano w innej odpowiedzi, wystarczy takie proste polecenie:

git diff-index --quiet HEAD --

Jeśli pominiesz dwa ostatnie myślniki, polecenie zakończy się niepowodzeniem, jeśli masz plik o nazwie HEAD.

Przykład:

#!/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...

Uwaga: to polecenie ignoruje nieśledzone pliki.

sanmai
źródło
2
Jak wskazano w komentarzach do tej odpowiedzi, nie wykrywa to nowo dodanych plików
minexew
Nie, wykrywa nowo dodane do plików indeksu. Właśnie sprawdziłem.
sanmai
Zobacz pytanie Nieśledzone pliki nie są zmianami . git addi git cleanna ratunek
sanmai
4

Stworzyłem kilka przydatnych aliasów git, aby wyświetlić listę plików niestacjonarnych i przemieszczanych:

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

Następnie możesz łatwo robić takie rzeczy jak:

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

Możesz uczynić go bardziej czytelnym, tworząc skrypt gdzieś na PATHnazwie git-has:

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

Teraz powyższe przykłady można uprościć:

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

Dla kompletności tutaj są podobne aliasy dla nieśledzonych i ignorowanych plików:

git config --global alias.untracked 'ls-files --exclude-standard --others'
git config --global alias.ignored 'ls-files --exclude-standard --others --ignored'
stk
źródło
2

Z pythonem i pakietem GitPython:

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

Zwraca wartość True, jeśli repozytorium nie jest czyste

Pablo
źródło
Pozwoliło to uniknąć niektórych problemów z „znacznikiem czasu” wspomnianych w innych komentarzach
Jason
1
Zauważ, że GitPython również korzysta z CLI git. Jeśli ustawisz LOGLEVEL=DEBUG, zobaczysz wszystkie polecenia Popena, których używa do uruchomieniagit diff
Jason
-3

Oto najlepszy, najczystszy sposób.

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
źródło
4
Nie, to nie jest najlepsze. git statusto polecenie „porcelanowe”. Nie używaj porcelanowych poleceń w skryptach, ponieważ mogą one zmieniać wersje git. Zamiast tego użyj poleceń „hydraulika”.
spuder
3
Myślę, że jeśli zaktualizowałeś go, aby używał git status --porcelain(który jest przeznaczony do tego celu - stabilnego formatu, który można analizować w skrypcie), być może również z -z (oddzielone zerą zamiast nowego wiersza?), Możesz zrobić coś użytecznego z tym pomysłem . @ codyc4321, zobacz szczegóły stackoverflow.com/questions/6976473/ ...
msouth