Verifica di un indice sporco o di file non tracciati con Git


243

Come posso verificare se sono presenti modifiche non confermate nel mio repository git:

  1. Modifiche aggiunte all'indice ma non impegnate
  2. File non tracciati

da una sceneggiatura?

git-status sembra restituire sempre zero con la versione 1.6.4.2 di git.


3
lo stato git restituirà 1 se ci sono file modificati non messi in scena. Ma in generale, trovo che gli strumenti git non siano particolarmente accurati con lo stato di ritorno. EG git diff restituisce 0 indipendentemente dalla presenza di differenze.
intuito il

11
@intuited: se è necessario diffindicare la presenza o l'assenza di differenze anziché l'esecuzione corretta del comando, è necessario utilizzare --exit-codeo --quiet. I comandi git sono generalmente molto coerenti con la restituzione di un codice di uscita zero o diverso da zero per indicare l'esito positivo del comando.
CB Bailey,

1
@Charles Bailey: Ehi grande, ho perso questa opzione. Grazie! Immagino di non averne mai avuto davvero bisogno per farlo, o probabilmente avrei cercato la manpage per farlo. Sono contento che tu mi abbia corretto :)
intuito il

1
robert - questo è un argomento molto confuso per la comunità (non aiuta che la "porcellana" sia usata in modo diverso in contesti diversi). La risposta che hai selezionato ignora una risposta 2,5 volte più votata che è più robusta e segue il design git. Hai visto la risposta di @ChrisJ?
mike,

1
@Robert - Spero che potresti prendere in considerazione la possibilità di cambiare la tua risposta accettata. Quello che hai selezionato si basa su comandi "porcellana" più fragili; la risposta corretta effettiva (anche con 2x i voti positivi) si basa sui comandi git 'idraulici' corretti. Quella risposta corretta è stata originariamente da Chris Johnsen. Ho sollevato questo problema perché oggi è la terza volta che devo riferire qualcuno a questa pagina, ma non posso semplicemente indicare la risposta, ho spiegato perché la risposta accettata è subottimale / borderline errata. Grazie!
mike

Risposte:


173

Grande tempismo! Ho scritto un post sul blog esattamente questo pochi giorni fa, quando ho capito come aggiungere le informazioni sullo stato di git al mio prompt.

Ecco cosa faccio:

  1. Per lo stato sporco:

    # Returns "*" if the current git branch is dirty.
    function evil_git_dirty {
      [[ $(git diff --shortstat 2> /dev/null | tail -n1) != "" ]] && echo "*"
    }
  2. Per file non tracciati (nota il --porcelainflag a git statuscui ti dà un buon output analizzabile):

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

Sebbene git diff --shortstatsia più conveniente, puoi anche usare git status --porcelainper ottenere file sporchi:

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

Nota: 2>/dev/nullfiltra i messaggi di errore in modo da poter utilizzare questi comandi su directory non git. (Torneranno semplicemente 0per il conteggio dei file.)

Modifica :

Ecco i post:

Aggiunta di informazioni sullo stato di Git al prompt del terminale

Prompt della shell abilitato per Git


8
Vale la pena notare che il completamento di git bash viene fornito con una funzione shell per fare praticamente quello che stai facendo con il tuo prompt - __git_ps1. Mostra i nomi delle filiali, incluso un trattamento speciale se sei in procinto di rebase, am-apply, merge o bisect. E puoi impostare la variabile d'ambiente GIT_PS1_SHOWDIRTYSTATEper ottenere un asterisco per le modifiche non messe in scena e per le modifiche organizzate. (Penso che puoi anche ottenerlo per indicare file non tracciati e darti un po 'di git-describeoutput)
Cascabel,

8
Un avvertimento: git diff --shortstatdarà un falso negativo se le modifiche sono già nell'indice.
Marko Topolnik,

4
git status --porcelainè preferibile, perché git diff --shortstatnon catturerà i file vuoti appena creati. Puoi provarlo su qualsiasi albero di lavoro pulito:touch foo && git diff --shortstat
Campadrenalin,

7
NO - porcellana significa che l'uscita è per l' uomo e si rompe facilmente !! vedere la risposta di @ChrisJohnsen, che utilizza correttamente le opzioni stabili e intuitive.
mike,

7
@mike dalla pagina man di git-status sull'opzione di porcellana: "Dai l'output in un formato facile da analizzare per gli script. Questo è simile all'output breve, ma rimarrà stabile tra le versioni di Git e indipendentemente dalla configurazione dell'utente. "
aggiunto il

400

La chiave per un "scripting" affidabile di Git è usare i comandi "idraulici".

Gli sviluppatori si preoccupano quando cambiano i comandi idraulici per assicurarsi che forniscano interfacce molto stabili (cioè una data combinazione di stato del repository, stdin, opzioni della riga di comando, argomenti, ecc. Produrrà lo stesso output in tutte le versioni di Git dove il comando / opzione esiste). Nuove variazioni di output nei comandi idraulici possono essere introdotte tramite nuove opzioni, ma ciò non può comportare problemi per i programmi che sono già stati scritti su versioni precedenti (non utilizzerebbero le nuove opzioni, poiché non esistevano (o almeno lo erano non utilizzato) al momento della stesura dello script).

Sfortunatamente i comandi Git "di tutti i giorni" sono i comandi "porcellana", quindi la maggior parte degli utenti Git potrebbe non avere familiarità con i comandi idraulici. La distinzione tra comando di porcellana e comando idraulico viene fatta nella manpage git principale (vedere le sottosezioni intitolate Comandi di alto livello (porcellana) e Comandi di basso livello (impianto idraulico) .


Per scoprire modifiche senza compromessi, sarà probabilmente necessario git diff-index(confrontare l'indice (e forse i bit tracciati dell'albero di lavoro) con qualche altro albero (ad es. HEAD)), Forse git diff-files(confrontare l'albero di lavoro con l'indice) e possibilmente git ls-files(elencare i file; ad es. Elenco non monitorato , file non firmati).

(Si noti che nei comandi seguenti HEAD --viene utilizzato anziché HEADperché altrimenti il ​​comando non riesce se esiste un file denominato HEAD.)

Per verificare se un repository ha messo in scena modifiche (non ancora impegnate) utilizzare questo:

git diff-index --quiet --cached HEAD --
  • Se esce con 0allora non c'erano differenze ( 1significa che c'erano differenze).

Per verificare se un albero di lavoro ha delle modifiche che potrebbero essere messe in scena:

git diff-files --quiet
  • Il codice di uscita è lo stesso di git diff-index( 0== nessuna differenza; 1== differenze).

Per verificare se la combinazione dell'indice e dei file tracciati nell'albero di lavoro presenta delle modifiche rispetto a HEAD:

git diff-index --quiet HEAD --
  • Questo è come una combinazione dei due precedenti. Una differenza principale è che riporterà comunque "nessuna differenza" se si dispone di una modifica graduale che si è "annullata" nella struttura di lavoro (tornando ai contenuti presenti HEAD). In questa stessa situazione, i due comandi separati restituirebbero entrambi rapporti di "differenze presenti".

Hai anche citato file non tracciati. Potresti significare "non tracciato e non firmato", oppure potresti semplicemente indicare "non tracciato" (compresi i file ignorati). In entrambi i casi, git ls-filesè lo strumento per il lavoro:

Per "non tracciato" (includerà i file ignorati, se presenti):

git ls-files --others

Per "non tracciato e non firmato":

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

Il mio primo pensiero è quello di verificare se questi comandi hanno un output:

test -z "$(git ls-files --others)"
  • Se esce con 0allora non ci sono file non tracciati. Se esce con 1allora ci sono file non tracciati.

Vi è una piccola possibilità che ciò traduca le uscite anomale da git ls-filesin report "nessun file non tracciato" (entrambi comportano uscite diverse da zero del comando sopra). Una versione un po 'più robusta potrebbe apparire così:

u="$(git ls-files --others)" && test -z "$u"
  • L'idea è la stessa del comando precedente, ma consente la git ls-filespropagazione di errori imprevisti . In questo caso un'uscita diversa da zero potrebbe significare "ci sono file non tracciati" oppure potrebbe essersi verificato un errore. Se si desidera invece i risultati di "errore" in combinazione con il risultato "nessun file non monitorato", utilizzare test -n "$u"(dove exit 0indica "alcuni file non monitorati" e un valore diverso da zero significa errore o "nessun file non monitorato").

Un'altra idea è quella di utilizzare --error-unmatchper causare un'uscita diversa da zero quando non ci sono file non tracciati. Ciò comporta anche il rischio di confondere "nessun file non monitorato" (uscita 1) con "si è verificato un errore" (uscita diversa da zero, ma probabilmente 128). Ma verificare i codici di uscita 0vs. 1vs. diversi da zero è probabilmente abbastanza robusto:

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

Uno qualsiasi degli git ls-filesesempi sopra può prendere --exclude-standardse si desidera prendere in considerazione solo file non tracciati e non firmati.


5
Vorrei sottolineare che git ls-files --othersfornisce file locali non tracciati, mentre la git status --porcelainrisposta accettata fornisce tutti i file non tracciati che si trovano nel repository git. Non sono quale di questi il ​​poster originale volesse, ma la differenza tra entrambi è interessante.
Eric O Lebigot,

1
@phunehehe: devi fornire un pathspec con --error-unmatch. Prova (ad es.) git ls-files --other --error-unmatch --exclude-standard .(Nota il periodo finale, si riferisce al CWD; eseguilo dalla directory di livello superiore dell'albero di lavoro).
Chris Johnsen,

8
@phs: potrebbe essere necessario fare git update-index -q --refreshprima diff-indexdi evitare alcuni "falsi positivi" causati da informazioni stat (2) non corrispondenti.
Chris Johnsen,

1
Sono stato morso dal git update-indexbisogno! Questo è importante se qualcosa tocca i file senza apportare modifiche.
Nakedible,

2
@RobertSiemer Per "locale" intendevo i file nella directory corrente , che potrebbe trovarsi al di sotto del repository git principale. La --porcelainsoluzione elenca tutti i file non tracciati trovati nell'intero repository git (meno i file ignorati da git), anche se ci si trova all'interno di una delle sue sottodirectory.
Eric O Lebigot,

140

Supponendo di essere su git 1.7.0 o successivo ...

Dopo aver letto tutte le risposte in questa pagina e alcuni esperimenti, penso che il metodo che colpisce la giusta combinazione di correttezza e brevità sia:

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

Mentre git tiene conto di molte sfumature tra ciò che viene tracciato, ignorato, non tracciato ma non firmato, e così via, credo che il tipico caso d'uso sia quello di automatizzare gli script di build, in cui si desidera interrompere tutto se il checkout non è pulito.

In tal caso, ha senso simulare cosa farebbe il programmatore: digita git statuse guarda l'output. Ma non vogliamo fare affidamento su parole specifiche visualizzate, quindi utilizziamo la --porcelainmodalità introdotta in 1.7.0; quando abilitato, una directory pulita non produce output.

Quindi usiamo test -nper vedere se c'è stato qualche output o no.

Questo comando restituirà 1 se la directory di lavoro è pulita e 0 se ci sono modifiche da impegnare. Puoi cambiare -nin a -zse vuoi il contrario. Ciò è utile per concatenarlo a un comando in uno script. Per esempio:

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

Questo dice effettivamente "o non ci sono modifiche da apportare o attivare un allarme"; questo one-liner potrebbe essere preferibile a un'istruzione if a seconda della sceneggiatura che stai scrivendo.


1
Per me tutti gli altri comandi davano risultati diversi sullo stesso rappresentante tra Linux e Windows. Questo comando mi ha dato lo stesso output in entrambi.
Adarsha,

6
Grazie per aver risposto alla domanda e non aver gironzolato all'infinito, senza mai fornire una risposta chiara.
NateS,

Per uno script di distribuzione manuale, combinalo con questo test -n "$(git diff origin/$branch)", per aiutare a impedire il commit di commit locali in una distribuzione
Erik Aronesty

14

Un'implementazione dalla risposta di VonC :

if [[ -n $(git status --porcelain) ]]; then echo "repo is dirty"; fi

8

Ho dato un'occhiata ad alcune di queste risposte ... (e ho avuto vari problemi su * nix e windows, che era un requisito che avevo) ... trovato che il seguente funzionava bene ...

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

Per controllare il codice di uscita in * nix

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

Per controllare il codice di uscita nella finestra $

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

Fonte da https://github.com/sindresorhus/pure/issues/115 Grazie a @paulirish su quel post per la condivisione


4

Perché non incapsulare " git statuscon uno script che:

  • analizzerà l'output di quel comando
  • restituirà il codice di errore appropriato in base a ciò di cui hai bisogno

In questo modo, puoi usare quello stato "migliorato" nel tuo script.


Come menziona 0xfe nella sua eccellente risposta , git status --porcelainè fondamentale in qualsiasi soluzione basata su script

--porcelain

Fornisci l'output in un formato stabile e facile da analizzare per gli script.
Attualmente questo è identico a --short output, ma è garantito che non cambierà in futuro, rendendolo sicuro per gli script.


Perché sono pigro, probabilmente. Ho pensato che ci fosse un built-in per questo, in quanto sembra un caso d'uso piuttosto frequente.
Robert Munteanu,

Ho pubblicato una soluzione in base al tuo suggerimento, sebbene non ne sia estremamente soddisfatto.
Robert Munteanu,

4

Una possibilità fai-da-te, aggiornata per seguire il suggerimento di 0xfe

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

Come notato da Chris Johnsen , questo funziona solo su Git 1.7.0 o versioni successive.


6
Il problema è che non puoi aspettarti in modo affidabile la stringa 'working directory clean' nelle versioni future. La bandiera --porcelain era pensata per l'analisi, quindi una soluzione migliore sarebbe: exit $ (git status --porcelain | wc -l)
0xfe

@ 0xfe - Sai quando --porcelainè stata aggiunta la bandiera? Non funziona con 1.6.4.2.
Robert Munteanu,

@Robert: prova git status --shortquindi.
VonC,

3
git status --porcelaine git status --shortsono stati entrambi introdotti in 1.7.0. --porcelainÈ stato introdotto appositamente per consentire git status --shortdi variare il suo formato in futuro. Quindi, git status --shortsubirebbe lo stesso problema di git status(l'output può cambiare in qualsiasi momento poiché non è un comando 'idraulico').
Chris Johnsen,

@Chris, grazie per le informazioni di base. Ho aggiornato la risposta per riflettere il modo migliore di farlo a partire da Git 1.7.0.
Robert Munteanu,

2

Avevo bisogno abbastanza spesso di un modo semplice per fallire una compilazione se alla fine dell'esecuzione ci fossero file tracciati modificati o file non tracciati che non venivano ignorati.

Questo è abbastanza importante per evitare il caso in cui le build producano gli avanzi.

Finora, il miglior comando che ho finito per usare assomiglia a:

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

Può sembrare un po 'complesso e gradirei se qualcuno trovasse una variante in cortocircuito che mantenga il comportamento desiderato:

  • nessun codice di uscita di uscita e di successo se evenything è in ordine
  • codice di uscita 1 se fallisce
  • messaggio di errore su stderr che spiega perché non riesce
  • visualizza l'elenco dei file che causano l'errore, di nuovo stderr.

2

La risposta di @ eduard-wirch è stata abbastanza completa, ma poiché volevo controllare entrambi allo stesso tempo, ecco la mia variante finale.

        set -eu

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

Quando non eseguiamo usando set -e o equivalenti, possiamo invece fare un u="$(git ls-files --others)" || exit 1(o restituire se funziona per una funzione usata)

Quindi untracked_files, viene impostato solo se il comando ha esito positivo.

dopodiché, possiamo verificare entrambe le proprietà e impostare una variabile (o qualsiasi altra cosa).


1

Questa è una variante più shell da usare per scoprire se eventuali esistono file non monitorate nel repository:

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

Questo non effettua il fork di un secondo processo grepe non necessita di un controllo per verificare se ci si trova in un repository git o meno. È utile per le richieste della shell, ecc.


1

Puoi anche fare

git describe --dirty

. Aggiungerà la parola "-dirty" alla fine se rileva un albero di lavoro sporco. Secondo 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.

. Avvertenza: i file non tracciati non sono considerati "sporchi", perché, come afferma la manpage, si preoccupa solo dell'albero di lavoro.


0

Ci può essere una migliore combinazione di risposte da questa discussione .. ma questo funziona per me ... per il vostro .gitconfig's [alias]sezione ...

          # 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

Il test automatico più semplice che utilizzo per rilevare lo stato sporco = eventuali modifiche inclusi i file non monitorati :

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

NOTA:

  • Senza add --all diff-index non nota i file non tracciati.
  • Di solito, corro git resetdopo aver testato il codice di errore per rimuovere tutto dal palco.

La domanda è specificamente "da uno script" ... non è una buona idea cambiare l'indice solo per verificare lo stato sporco.
ScottJ,

@ScottJ, quando risolve il problema, non tutti sono necessariamente severi sulla possibilità di modificare l'indice. Prendi in considerazione il caso di un lavoro automatizzato che riguarda le fonti di patch automatiche con il numero di versione e crea un tag: tutto ciò che devi assicurarti è che nessuna altra modifica locale rimanga sulla strada (indipendentemente dal fatto che siano nell'indice o meno). Finora, si è trattato di un test affidabile per eventuali modifiche, inclusi file non monitorati .
uvsmtid

-3

Ecco il modo migliore e più pulito. La risposta selezionata non ha funzionato per me per qualche motivo, non ha raccolto le modifiche messe in scena che erano nuovi file che non sono stati impegnati.

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
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.