Wie stelle ich programmgesteuert fest, ob nicht festgeschriebene Änderungen vorliegen?

226

In einem Makefile möchte ich bestimmte Aktionen ausführen, wenn nicht festgeschriebene Änderungen vorliegen (entweder im Arbeitsbaum oder im Index). Was ist der sauberste und effizienteste Weg, dies zu tun? Ein Befehl, der in einem Fall mit einem Rückgabewert von Null und in dem anderen von einem Wert ungleich Null beendet wird, würde meinen Zwecken entsprechen.

Ich kann git statusden Ausgang durchlaufen lassen grep, aber ich denke, es muss einen besseren Weg geben.

Daniel Stutzbach
quelle
4
Mögliches Duplikat von Mit Git
jb

Antworten:

288

UPDATE : Das OP Daniel Stutzbach weist in den Kommentaren darauf hin, dass dieser einfache Befehl git diff-indexfür ihn funktioniert hat:

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

( nornagon erwähnt in den Kommentaren, dass Sie, wenn Dateien berührt wurden, deren Inhalt jedoch mit denen im Index übereinstimmt , git update-index --refreshzuvor ausgeführt werden müssen git diff-index, andernfalls diff-indexwird fälschlicherweise gemeldet, dass der Baum verschmutzt ist.)

Sie können dann sehen, wie Sie überprüfen, ob ein Befehl erfolgreich war, wenn Sie ihn in einem Bash-Skript verwenden:

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

Hinweis: wie von Anthony Sottile kommentiert

git diff-index HEAD ...schlägt in einem Zweig fehl, der keine Commits hat (z. B. in einem neu initialisierten Repository).
Eine Problemumgehung, die ich gefunden habe, istgit diff-index $(git write-tree) ...

Und haridsvweist in den Kommentaren darauf hin, dass git diff-fileseine neue Datei diese nicht als Unterschied erkennt.
Der sicherere Ansatz scheint darin zu bestehen, zuerst git adddie Dateispezifikation auszuführen und dann git diff-indexzu prüfen, ob dem Index vor dem Ausführen etwas hinzugefügt wurde git commit.

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

Und 6502 Berichte in den Kommentaren:

Ein Problem, auf das ich gestoßen bin, ist, git diff-indexdass es Unterschiede gibt, wenn es tatsächlich keine gibt, außer Zeitstempeln der Dateien.
Das git diffeinmalige Ausführen löst das Problem (überraschenderweise git diffändert sich tatsächlich der Inhalt der Sandbox, was hier bedeutet .git/index)

Diese Zeitstempelprobleme können auch auftreten, wenn git im Docker ausgeführt wird .


Ursprüngliche Antwort:

"Programmatisch" bedeutet, sich niemals auf Porzellanbefehle zu verlassen .
Verlassen Sie sich immer auf Installationsbefehle .

Siehe auch " Suchen nach einem schmutzigen Index oder nicht verfolgten Dateien mit Git " für Alternativen (wie git status --porcelain)

Sie können sich von der neuen " require_clean_work_treeFunktion " inspirieren lassen, die gerade geschrieben wird ;) (Anfang Oktober 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
}
VonC
quelle
12
Das Prinzip "Sanitär vs. Porzellan für Skripte " ist eine Lektion, die Jakub Narębski mir wiederholt sagte : " Wie liste ich das gesamte Protokoll für das aktuelle Projekt in Git auf? ", " Git: Changelog Tag für Tag ", ...
VonC
18
Nachdem ich auf einige der von Ihnen vorgeschlagenen Links geklickt hatte, fand ich, wonach ich suchte : git diff-index --quiet HEAD.
Daniel Stutzbach
11
@ DanielStutzbach: Das könnte fehlschlagen, wenn Sie eine Datei HEADim Arbeitsverzeichnis haben. Besser nutzen git diff-index --quiet HEAD --.
David Ongaro
7
Und doch heißt es im Handbuch unter git status --help: --porcelain Geben Sie die Ausgabe in einem einfach zu analysierenden Format für Skripte an. Dies ähnelt der kurzen Ausgabe, bleibt jedoch in allen Git-Versionen und unabhängig von der Benutzerkonfiguration stabil. Siehe unten für Details.
Ed Randall
7
@VonC das macht wirklich keinen Sinn. Auf diese Weise können Sie alles in die umgekehrte Richtung drehen. - Porzellan vermittelt den Eindruck, dass es bald brechen wird. Wenn dies nicht der Fall ist, sollte es als Sanitär bezeichnet werden, nicht als Porzellan. Wenn Sie --porcelain verwenden, wird Ihr Skript nicht beschädigt, wodurch es NICHT zu einem Porzellanskript wird ;-). Wenn Sie wollten, dass Ihr Skript kaputt geht, sollten Sie --porcelain !! nicht verwenden. Also ist es völlig unverständlich und wirft alle ab.
Xennex81
104

Während die anderen Lösungen sehr gründlich sind, versuchen Sie Folgendes, wenn Sie etwas wirklich schnelles und schmutziges wollen:

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

Es wird nur geprüft, ob die Statusübersicht eine Ausgabe enthält.

Nepthar
quelle
7
funktioniert bei mir. benutze -n für die Umkehrung (du hast Änderungen) zB `if [[-n $ (git status -s)]]; dann ... fi`
aaron
Das funktioniert, aber können Sie sagen, was die [[ ... ]]Syntax tatsächlich tut? Ich habe so etwas noch nie gesehen.
GMA
2
@EM Der Rückkehrcode von git statuswird in diesem Test tatsächlich ignoriert. Es wird nur die Ausgabe betrachtet. Schauen Sie sich diese bash ähnliche Seite für weitere Informationen auf [, [[und wie funktioniert in der Bash testen.
Nepthar
2
Dies ist fast die richtige Antwort, aber für das Skript ist es besser, --porcelainParameter wie hier
Mariusz Pawelski
2
Möglicherweise möchten Sie nicht verfolgte git status -s -uallDateien einschließen.
Barfuin
59

git diff --exit-codegibt bei Änderungen ungleich Null zurück; git diff --quietist das gleiche ohne Ausgabe. Verwenden Sie, da Sie nach dem Arbeitsbaum und dem Index suchen möchten

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

Oder

git diff --quiet HEAD

Entweder wird man Ihnen sagen, ob es nicht festgeschriebene Änderungen gibt, die bereitgestellt werden oder nicht.

Josh Lee
quelle
6
Diese sind nicht gleichwertig. Der einzelne Befehl git diff --quite HEADzeigt nur an, ob der Arbeitsbaum sauber ist, nicht, ob der Index sauber ist. Wenn beispielsweise filezwischen HEAD ~ und HEAD geändert wurde, wird danach git reset HEAD~ -- fileimmer noch 0 beendet, obwohl im Index abgestufte Änderungen vorhanden sind (wt == HEAD, aber index! = HEAD).
Chris Johnsen
2
Achtung, dies fängt keine Dateien ab, die mit git rm, AFAICS aus dem Staging-Bereich entfernt wurden.
nmr
24
Neue (nicht verfolgte) Dateien werden von nicht erkannt git diff --quiet && git diff --cached --quiet.
4LegsDrivenCat
17

Erweiterung der Antwort von @ Nepthar:

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
quelle
1
Das ist gut; Ich benutze es, um einzelne Dateien durch Testen $(git status -s "$file")und dann in der elseKlauselgit add "$file"; git commit -m "your autocommit process" "$file"
Toddkaufmann
Wenn Sie das machen git status -sein git status --porcelain ; git clean -ndstattdessen wird Junk - Verzeichnisse auch hier aufgetaucht, die unsichtbar sind git status.
Ecmanaut
4

Wie in einer anderen Antwort erwähnt, ist ein so einfacher Befehl ausreichend:

git diff-index --quiet HEAD --

Wenn Sie die letzten beiden Striche weglassen, schlägt der Befehl fehl, wenn Sie eine Datei mit dem Namen haben HEAD.

Beispiel:

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

Achtung: Dieser Befehl ignoriert nicht verfolgte Dateien.

Sanmai
quelle
2
Wie in den Kommentaren zu dieser Antwort erwähnt, werden neu hinzugefügte Dateien nicht
erkannt
Nein, es erkennt neu hinzugefügte Indexdateien. Gerade nachgeschaut.
Sanmai
Siehe Frage. Nicht verfolgte Dateien sind keine Änderungen . git addund git cleanzur Rettung
Sanmai
4

Ich habe einige praktische Git-Aliase erstellt, um nicht bereitgestellte und bereitgestellte Dateien aufzulisten:

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

Dann können Sie ganz einfach Dinge tun wie:

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

Sie können es besser lesbar machen, indem Sie irgendwo auf Ihrem PATHAufruf ein Skript erstellen git-has:

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

Nun können die obigen Beispiele vereinfacht werden zu:

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

Der Vollständigkeit halber sind hier ähnliche Aliase für nicht verfolgte und ignorierte Dateien aufgeführt:

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

Mit Python und dem GitPython-Paket:

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

Gibt True zurück, wenn das Repository nicht sauber ist

Pablo
quelle
Dadurch wurden einige der in anderen Kommentaren erwähnten "Zeitstempel" -Probleme vermieden
Jason,
1
Beachten Sie, dass GitPython auch nur die Git-CLI verwendet. Wenn Sie festlegen LOGLEVEL=DEBUG, sehen Sie alle Popen-Befehle, mit denen es ausgeführt wirdgit diff
Jason
-3

Hier ist der beste und sauberste Weg.

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
quelle
4
Nein, das ist nicht das Beste. git statusist ein 'Porzellan'-Befehl. Verwenden Sie keine Porzellanbefehle in Skripten, da diese zwischen Git-Versionen wechseln können. Verwenden Sie stattdessen 'Sanitär'-Befehle.
Spuder
3
Ich denke, wenn Sie es für die Verwendung aktualisiert haben git status --porcelain(was für diesen Zweck gedacht ist - ein stabiles Format, das Sie in einem Skript analysieren können), möglicherweise auch mit -z (nulltrennend anstelle von Zeilenumbruch?), Könnten Sie mit dieser Idee etwas Nützliches tun . @ codyc4321 siehe stackoverflow.com/questions/6976473/… für Details
msouth