Come faccio a determinare a livello di codice se ci sono modifiche senza commit?


226

In un Makefile, vorrei eseguire determinate azioni in caso di modifiche non impegnate (nella struttura di lavoro o nell'indice). Qual è il modo più pulito ed efficiente per farlo? Un comando che esce con un valore di ritorno pari a zero in un caso e diverso da zero nell'altro sarebbe adatto ai miei scopi.

Posso eseguire git statuse reindirizzare l'output grep, ma sento che ci deve essere un modo migliore.


Risposte:


289

AGGIORNAMENTO : l'OP Daniel Stutzbach sottolinea nei commenti che questo semplice comando ha git diff-indexfunzionato per lui:

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

( nornagon menziona nei commenti che, se ci sono file che sono stati toccati, ma i cui contenuti sono gli stessi dell'indice, dovrai eseguirli git update-index --refreshprima git diff-index, altrimenti diff-indexsegnalerai erroneamente che l'albero è sporco)

Puoi quindi vedere " Come verificare se un comando è riuscito? " Se lo stai usando in uno script bash:

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

Nota: come commentato da Anthony Sottile

git diff-index HEAD ...fallirà su un ramo che non ha commit (come un repository appena inizializzato).
Una soluzione alternativa che ho trovato ègit diff-index $(git write-tree) ...

E haridsvsottolinea nei commenti che git diff-filessu un nuovo file non lo rileva come diff.
L'approccio più sicuro sembra essere quello di eseguire git addprima le specifiche del file e quindi utilizzare git diff-indexper vedere se qualcosa è stato aggiunto all'indice prima di essere eseguito git commit.

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

E 6502 rapporti nei commenti:

Un problema in cui mi sono imbattuto è che git diff-indexdirà che ci sono differenze quando in effetti non ce ne sono, tranne che per i timestamp dei file.
Eseguire git diffuna volta risolto il problema (abbastanza sorprendentemente, in git diffrealtà cambia il contenuto del sandbox, nel senso qui .git/index)

Questi problemi di data e ora possono anche verificarsi se git è in esecuzione nella finestra mobile .


Risposta originale:

"Programmaticamente" significa che mai e poi mai fare affidamento sui comandi di porcellana .
Affidati sempre ai comandi idraulici .

Vedi anche " Verifica di un indice sporco o file non tracciati con Git " per alternative (come git status --porcelain)

Puoi prendere ispirazione dalla nuova " require_clean_work_treefunzione " che è scritta mentre parliamo ;) (inizio ottobre 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
Il principio "idraulica contro porcellana per lo scripting" è una lezione che Jakub Narębski mi ha ripetutamente citato: " Come elencare tutti i registri del progetto corrente in git? ", " Git: changelog day by day ", ...
VonC

18
Dopo aver cliccato alcuni dei collegamenti lei suggerisce, ho trovato quello che cercavo: git diff-index --quiet HEAD.
Daniel Stutzbach,

11
@DanielStutzbach: ciò potrebbe non riuscire se si dispone di un file chiamato HEADnella directory di lavoro. Uso migliore git diff-index --quiet HEAD --.
David Ongaro,

7
Eppure il manuale negli git status --helpstati: --porcelain Fornisce 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. Vedi sotto per i dettagli.
Ed Randall,

7
@VonC che non ha davvero senso. In questo modo puoi girare tutto nel suo rovescio. --porcelain ti dà l'impressione che presto si spezzerà. In caso contrario, dovrebbe essere chiamato impianto idraulico, non porcellana. L'uso di --porcelain fa sì che la tua sceneggiatura non si rompa, il che NON la rende una sceneggiatura di porcellana ;-). Se vuoi che la tua sceneggiatura si interrompa, non dovresti usare --porcelain !!. Quindi è completamente incomprensibile e butta via tutti.
Xennex81

104

Mentre le altre soluzioni sono molto accurate, se vuoi qualcosa di veramente veloce e sporco, prova qualcosa del genere:

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

Controlla solo se c'è qualche output nel riepilogo dello stato.


7
per me va bene. usa -n per l'inverso (hai delle modifiche) es. `if [[-n $ (git status -s)]]; poi ... fi`
aaron,

Funziona, ma puoi dire cosa [[ ... ]]sta facendo la sintassi? Non ho mai visto niente del genere prima d'ora.
GMA

2
@EM il codice di ritorno di git statusviene effettivamente ignorato in questo test. Guarda solo l'output. Controlla questa pagina correlata bash per più informazioni [, [[e come testare opere in bash.
Nettuno

2
Questa è quasi la risposta giusta, ma per lo script è meglio usare il --porcelainparametro come mostrato qui
Mariusz Pawelski,

2
Potresti voler utilizzare git status -s -uallper includere file non tracciati.
Barfuin,

59

git diff --exit-coderestituirà valori diversi da zero in caso di modifiche; git diff --quietè lo stesso senza output. Dal momento che si desidera verificare l'albero di lavoro e l'indice, utilizzare

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

O

git diff --quiet HEAD

Uno dei due ti dirà se ci sono cambiamenti non impegnati che vengono messi in scena o meno.


6
Quelli non sono equivalenti. Il singolo comando git diff --quite HEADti dirà solo se l'albero di lavoro è pulito, non se l'indice è pulito. Ad esempio, se è filestato modificato tra HEAD ~ e HEAD, quindi dopo git reset HEAD~ -- file, uscirà comunque 0 anche se sono presenti modifiche graduali nell'indice (wt == HEAD, ma indice! = HEAD).
Chris Johnsen,

2
Attenzione, questo non catturerà i file rimossi dall'area di gestione temporanea con git rm, AFAICS.
nm

24
I nuovi file (non tracciati) non vengono rilevati da git diff --quiet && git diff --cached --quiet.
4LegsDrivenCat

17

Espandendo la risposta di @ Nepthar:

if [[ -z $(git status -s) ]]
then
  echo "tree is clean"
else
  echo "tree is dirty, please commit changes before running this"
  exit
fi

1
Questo è buono; Lo uso per autocommit di singoli file testando $(git status -s "$file")e poi nella elseclausolagit add "$file"; git commit -m "your autocommit process" "$file"
toddkaufmann

Se fate che git status -sun git status --porcelain ; git clean -ndposto, le directory di spazzatura saranno emerso anche qui, che sono invisibili a git status.
ecmanaut,

4

Come indicato in altre risposte, è sufficiente un comando del genere:

git diff-index --quiet HEAD --

Se si omettono gli ultimi due trattini, il comando fallirebbe se si dispone di un file denominato HEAD.

Esempio:

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

Avvertenza: questo comando ignora i file non tracciati.


2
Come indicato nei commenti a tale risposta, questo non rileva i file aggiunti di recente
minexew,

No, rileva i nuovi aggiunti ai file di indice. Ho appena controllato.
sanmai,

Vedi la domanda I file non tracciati non sono modifiche . git adde git cleanin soccorso
sanmai, il

4

Ho creato alcuni pratici alias git per elencare i file non messi in scena e messi in scena:

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

Quindi puoi facilmente fare cose come:

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

Puoi renderlo più leggibile creando uno script da qualche parte sul tuo PATHchiamato git-has:

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

Ora gli esempi precedenti possono essere semplificati per:

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

Per completezza qui ci sono alias simili per file non tracciati e ignorati:

git config --global alias.untracked 'ls-files --exclude-standard --others'
git config --global alias.ignored 'ls-files --exclude-standard --others --ignored'

2

Con python e il pacchetto GitPython:

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

Restituisce True se il repository non è pulito


Ciò evitò alcuni dei problemi di "timestamp" menzionati in altri commenti
Jason,

1
Nota che GitPython sta usando anche la CLI git. Se imposti LOGLEVEL=DEBUG, vedrai tutti i comandi Popen che utilizza per l'esecuzionegit diff
Jason il

-3

Ecco il modo migliore e più pulito.

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
No, questo non è il migliore. git statusè un comando "porcellana". Non usare i comandi di porcellana negli script poiché possono cambiare tra le versioni di git. Usa invece i comandi "idraulici".
spuder,

3
Penso che se lo aggiorni per usare git status --porcelain(che è pensato per questo scopo - un formato stabile che puoi analizzare in uno script), possibilmente anche con -z (separato da null anziché newline?) Potresti fare qualcosa di utile con questa idea . @ codyc4321 vedono stackoverflow.com/questions/6976473/... per i dettagli
msouth
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.