Sprawdzanie brudnego indeksu lub nieśledzonych plików za pomocą Git

243

Jak mogę sprawdzić, czy mam jakieś niezatwierdzone zmiany w moim repozytorium git:

  1. Zmiany dodane do indeksu, ale nie zatwierdzone
  2. Nieśledzone pliki

ze skryptu?

git-status wydaje się, że zawsze zwraca zero w wersji git 1.6.4.2.

Robert Munteanu
źródło
3
Status git zwróci 1, jeśli istnieją zmodyfikowane pliki niestacjonarne. Ale ogólnie uważam, że narzędzia git nie są szczególnie dokładne ze statusem zwrotu. EG git diff zwraca 0, niezależnie od tego, czy występują różnice.
intuicyjnie
11
@intuited: Jeśli chcesz diffwskazać obecność lub brak różnic zamiast udanego uruchomienia polecenia, musisz użyć --exit-codelub --quiet. Polecenia git są zasadniczo bardzo spójne ze zwracaniem zerowego lub niezerowego kodu wyjścia, aby wskazać powodzenie polecenia.
CB Bailey,
1
@Charles Bailey: Cześć, tęskniłem za tą opcją. Dzięki! Wydaje mi się, że tak naprawdę nigdy tego nie potrzebowałem, bo prawdopodobnie poszukałbym za to strony. Cieszę się, że mnie poprawiłeś :)
intuicyjnie,
1
Robert - jest to bardzo mylący temat dla społeczności (nie pomaga, że ​​„porcelana” jest używana inaczej w różnych kontekstach). Wybrana odpowiedź ignoruje 2,5-krotnie wyższą odpowiedź, która jest bardziej niezawodna i zgodna z projektem git. Czy widziałeś odpowiedź @ChrisJ?
Mike
1
@Robert - Mam nadzieję, że możesz rozważyć zmianę zaakceptowanej odpowiedzi. Ten, który wybrałeś, opiera się na delikatniejszych poleceniach gitowych „porcelany”; faktyczna poprawna odpowiedź (także z dwukrotnymi poprawkami) zależy od poprawnych poleceń git „hydraulika”. Ta poprawna odpowiedź została pierwotnie napisana przez Chrisa Johnsena. Mówię o tym, ponieważ dzisiaj muszę po raz trzeci skierować kogoś na tę stronę, ale nie mogę po prostu wskazać odpowiedzi, wyjaśniam, dlaczego zaakceptowana odpowiedź jest nieoptymalna / błędna na granicy. Dziękuję Ci!
Mike

Odpowiedzi:

173

Świetny czas! Napisałem post na blogu o tym dokładnie kilka dni temu, kiedy wymyśliłem, jak dodać informacje o statusie git do mojego monitu.

Oto co robię:

  1. W przypadku brudnego statusu:

    # Returns "*" if the current git branch is dirty.
    function evil_git_dirty {
      [[ $(git diff --shortstat 2> /dev/null | tail -n1) != "" ]] && echo "*"
    }
  2. W przypadku nieśledzonych plików (zauważ --porcelainflagę, do git statusktórej dajesz dobre wyniki analizowania):

    # Returns the number of untracked files
    
    function evil_git_num_untracked_files {
      expr `git status --porcelain 2>/dev/null| grep "^??" | wc -l` 
    }

Chociaż git diff --shortstatjest to wygodniejsze, możesz również użyć git status --porcelaindo uzyskania brudnych plików:

# 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)

Uwaga: 2>/dev/nullOdfiltrowuje komunikaty o błędach, dzięki czemu można używać tych poleceń w katalogach innych niż git. (Po prostu wrócą 0po liczbę plików.)

Edytuj :

Oto posty:

Dodawanie informacji o statusie Git do terminala

Ulepszone polecenie powłoki z obsługą Gita

0xfe
źródło
8
Warto zauważyć, że zakończenie git bash zawiera funkcję powłoki umożliwiającą wykonywanie praktycznie tego, co robisz za pomocą polecenia - __git_ps1. Wyświetla nazwy oddziałów, w tym specjalne traktowanie, jeśli jesteś w trakcie zmiany bazy, zastosowania, scalenia lub podziału na dwie części. I możesz ustawić zmienną środowiskową, GIT_PS1_SHOWDIRTYSTATEaby uzyskać gwiazdkę dla zmian niestacjonarnych i plus dla zmian etapowych. (Wydaje mi się, że można go również wskazać na nieśledzone pliki i dać trochę git-describedanych wyjściowych)
Cascabel
8
Ostrzeżenie: git diff --shortstatdaje fałszywie ujemny wynik, jeśli zmiany są już w indeksie.
Marko Topolnik
4
git status --porcelainjest preferowane, ponieważ git diff --shortstatnie przechwytuje nowo utworzonych pustych plików. Możesz go wypróbować w każdym czystym, działającym drzewie:touch foo && git diff --shortstat
Campadrenalin
7
NIE - porcelana oznacza, że ​​produkcja jest przeznaczona dla ludzi i łatwo się psuje !! zobacz odpowiedź @ChrisJohnsen, która poprawnie używa stabilnych, przyjaznych skryptom opcji.
Mike,
7
@mike ze strony podręcznika git-status o opcji porcelany: „Daj wynik w łatwym do przeanalizowania formacie dla skryptów. Jest to podobne do krótkiego wyniku, ale pozostanie stabilne we wszystkich wersjach Git i niezależnie od konfiguracji użytkownika. „
itsadok
399

Kluczem do niezawodnego „skryptowania” Gita jest użycie poleceń „hydraulicznych”.

Deweloperzy zwracają uwagę podczas zmiany poleceń hydraulicznych, aby upewnić się, że zapewniają bardzo stabilne interfejsy (tj. Dana kombinacja stanu repozytorium, standardowego wejścia, opcji wiersza poleceń, argumentów itp. Spowoduje wygenerowanie tego samego wyniku we wszystkich wersjach Gita, w których polecenie / opcja istnieje). Nowe warianty danych wyjściowych w komendach hydraulicznych można wprowadzić za pomocą nowych opcji, ale to nie może wprowadzić żadnych problemów dla programów, które zostały już napisane w stosunku do starszych wersji (nie korzystałyby z nowych opcji, ponieważ nie istniały (a przynajmniej były nieużywane) w momencie pisania skryptu).

Niestety „codzienne” polecenia Git to polecenia „porcelanowe”, więc większość użytkowników Gita może nie być zaznajomiona z poleceniami hydraulicznymi. Rozróżnienie między porcelaną a poleceniem hydrauliki jest wykonane na głównej stronie man git (patrz podrozdziały zatytułowane Polecenia wysokiego poziomu (porcelana) i Polecenia niskiego poziomu (hydraulika) .


Aby dowiedzieć się o niezatwierdzonych zmianach, prawdopodobnie będziesz potrzebować git diff-index(porównaj indeks (i być może śledzone fragmenty działającego drzewa) z jakimś innym drzewiastym (np. HEAD)), Może git diff-files(porównaj działające drzewo z indeksem) i ewentualnie git ls-files(wylistuj pliki; np. Lista nieśledzona , nieignorowane pliki).

(Zauważ, że w poniższych poleceniach HEAD --użyto zamiast, HEADponieważ w przeciwnym razie polecenie nie powiedzie się, jeśli istnieje plik o nazwie HEAD.)

Aby sprawdzić, czy repozytorium wprowadziło zmiany (jeszcze nie zatwierdzone):

git diff-index --quiet --cached HEAD --
  • Jeśli wyjdzie z, 0wtedy nie będzie żadnych różnic ( 1oznacza, że ​​były różnice).

Aby sprawdzić, czy działające drzewo ma zmiany, które można wprowadzić:

git diff-files --quiet
  • Kod wyjścia jest taki sam jak dla git diff-index( 0== brak różnic; 1== różnice).

Aby sprawdzić, czy kombinacja indeksu i plików śledzonych w drzewie roboczym ma zmiany w odniesieniu do HEAD:

git diff-index --quiet HEAD --
  • To jest jak połączenie dwóch poprzednich. Jedną z głównych różnic jest to, że nadal będzie zgłaszać „brak różnic”, jeśli masz zmianę etapową, którą „cofnąłeś” w działającym drzewie (wróciłeś do zawartości, która jest w nim HEAD). W tej samej sytuacji oba oddzielne polecenia zwracałyby raporty „obecnych różnic”.

Wspomniałeś także o nieśledzonych plikach. Możesz oznaczać „nieśledzony i niezauważony” lub zwykły „nieśledzony” (w tym pliki ignorowane). Tak czy inaczej, git ls-filesnarzędzie do pracy:

W przypadku „nieśledzonego” (obejmie pliki ignorowane, jeśli są obecne):

git ls-files --others

W przypadku „nieśledzonych i niezauważonych”:

git ls-files --exclude-standard --others

Moją pierwszą myślą jest sprawdzenie, czy te komendy mają dane wyjściowe:

test -z "$(git ls-files --others)"
  • Jeśli zakończy działanie, oznacza 0to, że nie ma żadnych nieśledzonych plików. Jeśli zakończy działanie, oznacza 1to, że pliki nie są śledzone.

Istnieje niewielka szansa, że ​​przełoży się to na nieprawidłowe wyjścia z git ls-filesraportów na „brak nieśledzonych plików” (oba powodują niezerowe wyjścia z powyższego polecenia). Trochę bardziej niezawodna wersja może wyglądać następująco:

u="$(git ls-files --others)" && test -z "$u"
  • Pomysł jest taki sam jak w poprzednim poleceniu, ale umożliwia git ls-filesrozprzestrzenianie się nieoczekiwanych błędów . W takim przypadku niezerowe wyjście może oznaczać „istnieją nieśledzone pliki” lub może oznaczać błąd. Jeśli zamiast tego chcesz, aby wyniki „błąd” były połączone z wynikiem „brak nieśledzonych plików”, użyj test -n "$u"(gdzie wyjście z 0oznacza „niektóre nieśledzone pliki”, a niezerowe oznacza błąd lub „brak nieśledzonych plików”).

Innym pomysłem jest użycie --error-unmatchniezerowego wyjścia, gdy nie ma nieśledzonych plików. Wiąże się to również z ryzykiem, że „brak nieśledzonych plików” (wyjście 1) z „wystąpił błąd” (wyjście niezerowe, ale prawdopodobnie 128). Ale sprawdzanie 0vs 1vs niezerowych kodów wyjścia jest chyba dość wytrzymała:

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

Każdy z powyższych git ls-filesprzykładów może wziąć, --exclude-standardjeśli chcesz wziąć pod uwagę tylko nieśledzone i niezauważone pliki.

Chris Johnsen
źródło
5
Chciałbym zaznaczyć, że git ls-files --othersdaje to lokalne nieśledzone pliki, podczas git status --porcelaingdy zaakceptowana odpowiedź daje wszystkie nieśledzone pliki znajdujące się w repozytorium git. Nie jestem jednym z tych, których chciał oryginalny plakat, ale różnica między nimi jest interesująca.
Eric O Lebigot,
1
@phunehehe: Musisz podać specyfikację ścieżki --error-unmatch. Spróbuj (np.) git ls-files --other --error-unmatch --exclude-standard .(Zwróć uwagę na kropkę, odnosi się ona do cwd; uruchom ją z katalogu najwyższego poziomu działającego drzewa).
Chris Johnsen
8
@phs: Być może trzeba będzie zrobić git update-index -q --refreshto, diff-indexaby uniknąć pewnych „fałszywych alarmów” spowodowanych przez niezgodne informacje stat (2).
Chris Johnsen
1
Ugryzła mnie git update-indexpotrzeba! Jest to ważne, jeśli coś dotyka plików bez wprowadzania modyfikacji.
Nakedible
2
@RobertSiemer Przez „lokalny” miałem na myśli pliki w bieżącym katalogu , które mogą znajdować się poniżej głównego repozytorium git. Te --porcelainwykazy rozwiązanie wszystkich Nieśledzone pliki znajdujące się w całym repozytorium git (minus git-ignorowane pliki), nawet jeśli jesteś w jednym z jego podkatalogów.
Eric O Lebigot,
139

Zakładając, że korzystasz z git 1.7.0 lub nowszej ...

Po przeczytaniu wszystkich odpowiedzi na tej stronie i przeprowadzeniu niektórych eksperymentów myślę, że metoda, która trafia we właściwe połączenie poprawności i zwięzłości, to:

test -n "$(git status --porcelain)"

Chociaż git pozwala na wiele niuansów między tym, co jest śledzone, ignorowane, nieśledzone, ale niezauważone itd., Uważam, że typowym przypadkiem jest automatyzacja skryptów kompilacji, w których chcesz zatrzymać wszystko, jeśli twoja kasa nie jest czysta.

W takim przypadku warto zasymulować, co zrobiłby programista: wpisz git statusi spójrz na wynik. Ale nie chcemy polegać na wyświetlaniu określonych słów, dlatego używamy --porcelaintrybu wprowadzonego w 1.7.0; po włączeniu czysty katalog nie powoduje wyjścia.

Następnie używamy, test -naby sprawdzić, czy było jakieś wyjście, czy nie.

To polecenie zwróci 1, jeśli katalog roboczy jest czysty, i 0, jeśli zostaną wprowadzone zmiany. Możesz zmienić na -na, -zjeśli chcesz mieć coś przeciwnego. Jest to przydatne do połączenia tego z poleceniem w skrypcie. Na przykład:

test -z "$(git status --porcelain)" || red-alert "UNCLEAN UNCLEAN"

To skutecznie mówi „albo nie ma żadnych zmian, które należy wprowadzić, albo uruchomić alarm”; ten linijka może być lepsza niż instrukcja if, w zależności od pisanego skryptu.

benzado
źródło
1
Dla mnie wszystkie inne polecenia dawały różne wyniki dla tego samego repozytorium między Linuksem a Windows. To polecenie dało mi taki sam wynik w obu.
Adarsha,
6
Dziękuję za udzielenie odpowiedzi na pytanie i nie włóczenie się bez końca, nigdy nie udzielając jasnej odpowiedzi.
NateS
W przypadku skryptu ręcznego wdrażania połącz to z test -n "$(git diff origin/$branch)", aby zapobiec dopuszczeniu lokalnych zatwierdzeń podczas wdrażania
Erik Aronesty,
14

Implementacja z odpowiedzi VonC :

if [[ -n $(git status --porcelain) ]]; then echo "repo is dirty"; fi
Dean Rather
źródło
8

Przejrzałem kilka z tych odpowiedzi ... (i miał różne problemy z * nix i Windows, co było moim wymaganiem) ... okazało się, że następujące działało dobrze ...

git diff --no-ext-diff --quiet --exit-code

Aby sprawdzić kod wyjścia w * nix

echo $?   
#returns 1 if the repo has changes (0 if clean)

Aby sprawdzić kod wyjścia w oknie $

echo %errorlevel% 
#returns 1 if the repos has changes (0 if clean) 

Źródło: https://github.com/sindresorhus/pure/issues/115 Dzięki @paulirish w tym poście za udostępnienie

mlo55
źródło
4

Dlaczego nie „zamknąć” git statusskryptu, który:

  • przeanalizuje dane wyjściowe tego polecenia
  • zwróci odpowiedni kod błędu w zależności od potrzeb

W ten sposób możesz użyć tego statusu „ulepszonego” w skrypcie.


Jak 0xfe wspomina w swojej doskonałej odpowiedzi , git status --porcelainjest pomocny w każdym rozwiązaniu opartym na skryptach

--porcelain

Daj wynik w stabilnym, łatwym do przeanalizowania formacie dla skryptów.
Obecnie jest to identyczne --short output, ale z pewnością nie zmieni się w przyszłości, dzięki czemu jest bezpieczne dla skryptów.

VonC
źródło
Bo prawdopodobnie jestem leniwy. Myślałem, że jest do tego wbudowany, ponieważ wydaje się, że jest to dość często spotykany przypadek użycia.
Robert Munteanu
Zamieściłem rozwiązanie oparte na twojej sugestii, chociaż nie jestem z niego bardzo zadowolony.
Robert Munteanu
4

Jedna możliwość majsterkowania, zaktualizowana zgodnie z sugestią 0xfe

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

Jak zauważył Chris Johnsen , działa to tylko na Git 1.7.0 lub nowszym.

Robert Munteanu
źródło
6
Problem polega na tym, że w przyszłych wersjach nie można niezawodnie oczekiwać, że ciąg „katalog roboczy czysty”. Flaga --porcelain była przeznaczona do analizowania, więc lepszym rozwiązaniem byłoby: wyjście $ (status git --porcelain | wc -l)
0xfe
@ 0xfe - Czy wiesz, kiedy --porcelainflaga została dodana? Nie działa z 1.6.4.2.
Robert Munteanu
@Robert: spróbuj git status --short.
VonC
3
git status --porcelaini git status --shortoba zostały wprowadzone w 1.7.0. --porcelainZostał wprowadzony specjalnie, aby umożliwić git status --shortzmianę formatu w przyszłości. Wystąpiłby więc git status --shortten sam problem co git status(wyjście może się zmienić w dowolnym momencie, ponieważ nie jest to polecenie „hydrauliczne”).
Chris Johnsen
@Chris, dzięki za informacje w tle. Zaktualizowałem odpowiedź, aby odzwierciedlić najlepszy sposób robienia tego od Gita 1.7.0.
Robert Munteanu
2

Często potrzebowałem prostego sposobu na niepowodzenie kompilacji, jeśli pod koniec wykonywania istnieją zmodyfikowane pliki śledzenia lub pliki nieśledzone, które nie są ignorowane.

Jest to bardzo ważne, aby uniknąć sytuacji, w której kompilacje produkują resztki.

Jak dotąd najlepsze polecenie, na którym skończyłem, wygląda następująco:

 test -z "$(git status --porcelain | tee /dev/fd/2)" || \
     {{ echo "ERROR: git unclean at the end, failing build." && return 1 }}

Może to wyglądać nieco skomplikowane i byłbym wdzięczny, gdyby ktoś znalazł wariant skrócony, który zachowuje pożądane zachowanie:

  • brak kodu wyjściowego i kodu zakończenia, jeśli wszystko jest w porządku
  • kod wyjścia 1, jeśli się nie powiedzie
  • komunikat o błędzie na stderr wyjaśniający, dlaczego nie działa
  • wyświetl listę plików powodujących awarię, ponownie stderr.
sorin
źródło
2

@ eduard-wirch odpowiedź była dość kompletna, ale ponieważ chciałem sprawdzić oba jednocześnie, oto mój ostateczny wariant.

        set -eu

        u="$(git ls-files --others)"
        if ! git diff-index --name-only --quiet HEAD -- || [ -z "${u:-}" ]; then
            dirty="-dirty"
        fi

Gdy nie wykonujemy przy użyciu zestawu -e lub równoważnego, możemy zamiast tego zrobić u="$(git ls-files --others)" || exit 1(lub zwrócić, jeśli to działa dla używanej funkcji)

Pliki nieśledzone są ustawiane tylko wtedy, gdy polecenie zakończy się poprawnie.

po czym możemy sprawdzić obie właściwości i ustawić zmienną (lub cokolwiek innego).

oliver
źródło
1

Jest to wariant bardziej przyjazny dla powłoki, aby dowiedzieć się, czy w repozytorium istnieją jakieś nieśledzone pliki:

# Works in bash and zsh
if [[ "$(git status --porcelain 2>/dev/null)" = *\?\?* ]]; then
  echo untracked files
fi

To nie rozwidla drugiego procesu grepi nie wymaga sprawdzenia, czy jesteś w repozytorium git, czy nie. Co jest przydatne w przypadku monitów powłoki itp.

docwhat
źródło
1

Możesz też zrobić

git describe --dirty

. Na końcu doda słowo „-dirty”, jeśli wykryje brudne drzewo robocze. Według 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.

. Uwaga: nieśledzone pliki nie są uważane za „brudne”, ponieważ, jak stwierdza strona podręcznika, zależy tylko na działającym drzewie.

Linus Arver
źródło
0

Nie może być lepsze połączenie z odpowiedzi z tego wątku .. ale działa to dla mnie ... w twoim .gitconfig„s [alias]sekcji ...

          # 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
Alex Gray
źródło
0

Najprostszy automatyczny test, którego używam do wykrywania stanu nieczystości = wszelkie zmiany, w tym nieśledzone pliki :

git add --all
git diff-index --exit-code HEAD

UWAGA:

  • Bez add --all diff-index nie zauważa nieśledzonych plików.
  • Zwykle uruchamiam git resetpo przetestowaniu kodu błędu, aby cofnąć scenę.
uvsmtid
źródło
Pytanie dotyczy konkretnie „skryptu” ... zmiana indeksu nie jest po prostu sprawdzana pod kątem stanu „brudnego”.
ScottJ
@ ScottJ, gdy rozwiązuje problem, nie wszyscy muszą koniecznie ściśle określać, czy indeks można modyfikować, czy nie. Rozważ przypadek zautomatyzowanego zadania, który dotyczy źródeł automatycznej łatki z numerem wersji i stwórz tag - wystarczy, że upewnisz się, że żadne inne lokalne modyfikacje nie przeszkadzają (niezależnie od tego, czy są w indeksie, czy nie). Jak dotąd był to niezawodny test na wszelkie zmiany, w tym nieśledzone pliki .
uvsmtid
-3

Oto najlepszy, najczystszy sposób. Wybrana odpowiedź nie działała dla mnie z jakiegoś powodu, nie wykryła wprowadzonych zmian, które były nowymi plikami, które nie zostały zatwierdzone.

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