Zurücksetzen, Auschecken und Rückgängigmachen | Git-Tutorials von Atlassian

Zurücksetzen, Auschecken und Rückgängigmachen

Die Befehle git reset, git checkout und git revert zählen zu den nützlichsten Werkzeugen in deinem Git-Repertoire. Sie dienen alle dazu, eine bestimmte Art Änderung in deinem Repository rückgängig zu machen. Die beiden ersten Befehle kannst du entweder auf Commits oder einzelne Dateien anwenden.

Das sie sehr ähnlich sind, kommt es bei der Auswahl des richtigen Befehls für das jeweilige Entwicklungsszenario leicht zu Verwechslungen. In diesem Artikel vergleichen wir die geläufigsten Konfigurationen von git reset, git checkout und git revert. Jetzt kannst du hoffentlich mit all diesen Befehlen sicher durch dein Repository navigieren.

Die drei Bäume von Git

Es ist hilfreich, sich vorzustellen, wie sich ein Commit auf die drei Mechanismen des Statusmanagements in einem Git-Repository auswirkt: auf das Arbeitsverzeichnis, den gestagten Snapshot und den Commit-Verlauf. Diese Komponenten werden auch als "die drei Bäume von Git" bezeichnet. Auf der Seite zu git reset gehen wir genauer auf die drei Bäume ein. Behalte diese Mechanismen beim weiteren Lesen im Hinterkopf.

Beim Checkout wird der HEAD-Ref-Pointer zu einem bestimmten Commit verschoben. Dieser Vorgang wird im folgenden Beispiel veranschaulicht:

Verschieben des HEAD-Ref-Pointers zu einem bestimmten Commit

In diesem Beispiel sehen wir eine Reihe an Commits im Master-Branch. Der HEAD-Ref und der Master-Branch-Ref verweisen derzeit auf Commit d. Führen wir nun git checkout b aus.

Reihe an Commits im Master-Branch

Das ist ein Update am "Commit-Verlauf"-Baum. Der Befehl git checkout ist auf Commit- oder Dateiebene anwendbar. Bei einem Checkout auf Dateiebene wird der Inhalt der Datei mit dem des entsprechenden Commit aktualisiert.

Beim Rückgängigmachen mit "revert" wird ein neuer Commit erstellt, der den angegebenen Commit umkehrt. git revert ist nur auf Commit-Ebene anwendbar und funktioniert nicht auf Dateiebene.

Beim Zurücksetzen mit "reset" werden die "drei Bäume" auf den Zustand des Repositorys zurückgesetzt, der einem bestimmten Commit entspricht. Für die drei Bäume gibt es drei verschiedene Arten des Zurücksetzens.

"Checkout" und "reset" werden normalerweise verwendet, um lokale oder private Änderungen rückgängig zu machen. Dadurch wird der Verlauf eines Repositorys verändert, was beim Pushen zu gemeinsam genutzten Remote-Repositorys zu Konflikten führen kann. Das Rückgängigmachen mit "revert" gilt als sichere Methode zum Zurücksetzen öffentlicher Änderungen, da ein neuer Verlauf erstellt wird, der remote gemeinsam genutzt werden kann. Der ursprüngliche Verlauf, der eventuell von Mitgliedern des Remote-Teams genutzt wird, wird so nicht überschrieben.

Im Überblick: git reset vs. revert vs. checkout

In der folgenden Tabelle werden die häufigsten Anwendungen für diese Befehle zusammengefasst. Im Laufe deiner Arbeit mit Git wirst du sicherlich ein paar dieser Befehle gebrauchen können. Halte diesen Überblick also stets griffbereit.

Befehl Umfang Häufige Anwendungsfälle
git reset Commit-Ebene Verwerfen von Commits in einem privaten Branch oder von noch nicht committeten Änderungen
git reset Dateiebene Entfernen einer Datei aus der Staging-Umgebung
git checkout Commit-Ebene Wechseln zwischen Branches oder Ansehen alter Snapshots
git checkout Dateiebene Verwerfen von Änderungen im Arbeitsverzeichnis
git revert Commit-Ebene Rückgängigmachen von Commits in einem öffentlichen Branch
git revert Dateiebene Nicht verfügbar

Vorgänge auf Commit-Ebene

Mit den Parametern, die du auf git reset und git checkout anwendest, legst du den jeweiligen Umfang fest. Wenn du als Parameter keinen Dateipfad angibst, wird der Befehl auf ganze Commits angewendet. Darum geht es in diesem Abschnitt. Beachte, dass es zu git revert kein Gegenstück gibt, das auf Dateiebene funktioniert.

Einen bestimmten Commit mit "reset" zurücksetzen

Auf Commit-Ebene ist das Zurücksetzen eine Möglichkeit, die Spitze eines Branch zu einem anderen Commit zu verschieben. Hiermit können Commits vom aktuellen Branch entfernt werden. Der folgende Befehl verschiebt z. B. den hotfix Branch zwei Commits zurück.

git checkout hotfix
git reset HEAD~2

Die beiden Commits am Ende von hotfix sind nun verwaiste Commits, d. h. von diesem Branch losgelöst. Sie werden bei der nächsten Speicherbereinigung von Git gelöscht. Anders ausgedrückt: Damit gibst du an, dass diese Commits verworfen werden sollen. Das lässt sich wie folgt veranschaulichen:

Zurücksetzen des hotfix-Branches auf HEAD-2

Mit git reset lassen sich auf einfache Weise Änderungen rückgängig machen, die noch nicht mit anderen geteilt wurden. Dies ist der Befehl erster Wahl, wenn du mit der Arbeit an einem Feature begonnen hast und dir plötzlich denkst: "Ach, du meine Güte, was mache ich hier eigentlich? Ich sollte besser ganz von vorne anfangen."

Neben dem Verschieben des aktuellen Branch, kannst du über git reset mit den folgenden Zusätzen auch den Snapshot aus der Staging-Umgebung und/oder das Arbeitsverzeichnis ändern:

  • --soft: Der Snapshot der Staging-Umgebung und das Arbeitsverzeichnis bleiben unverändert.
  • --mixed: Der Snapshot aus der Staging-Umgebung wird zur Übereinstimmung mit dem angegebenen Commit aktualisiert, aber das Arbeitsverzeichnis bleibt unberührt. Dies ist die Standardoption.
  • --hard: Der gestagte Snapshot und das Arbeitsverzeichnis werden auf den angegebenen Commit zurückgesetzt.
     

Die Vorstellung, dass diese Flags den Umfang des git reset-Vorgangs festlegen, erleichtert dir womöglich das Verständnis. Ausführlichere Erklärungen findest du auf der Seite git reset.

Ältere Commits auschecken

Mit dem Befehl git checkout wird das Repository aktualisiert, um dem Zustand an einer bestimmten Stelle des Projektverlaufs zu entsprechen. Wenn du dabei einen Branch-Namen angibst, kannst du zwischen verschiedenen Branches wechseln.

git checkout hotfix

Der obige Befehl verschiebt den HEAD lediglich auf einen anderen Branch und aktualisiert das Arbeitsverzeichnis entsprechend. Das birgt die Gefahr, dass lokale Änderungen überschrieben werden. Deshalb zwingt dich Git, alle Änderungen im Arbeitsverzeichnis, die während des Checkouts verloren gehen, zu committen oder zu stashen. Mit git checkout werden – im Gegensatz zu git reset – Branches nicht verschoben.

Verschieben des HEAD von master zu hotfix

Du kannst beliebige Commits auch auschecken, indem du die Commit-Referenz anstelle des Branches anwendest. Das Ergebnis ist genau dasselbe wie beim Auschecken eines Branches: Die HEAD-Referenz wird zum angegebenen Commit verschoben. Der folgende Befehl zum Beispiel checkt den überübergeordneten Commit des aktuellen Commits aus:

git checkout HEAD~2
Verschieben von HEAD zu einem beliebigen Commit

Das ist nützlich, wenn du einen schnellen Blick in eine ältere Version deines Projekts werfen willst. Da jedoch keine Branch-Referenz zum aktuellen HEAD besteht, befindest du dich in einem Zustand mit losgelöstem HEAD (im Englischen "detached HEAD"). Das kann riskant sein, wenn du einen neuen Commit hinzufügen willst. Wenn du dann zu einem anderen Branch wechselst, kannst du anschließend nicht mehr zum HEAD zurückkehren. Daher solltest du immer einen neuen Branch erstellen, bevor du Commits zu einem losgelösten HEAD hinzufügst.

Öffentliche Commits mit "revert" rückgängig machen

Mit "revert" machst du einen Commit rückgängig, indem ein neuer Commit erstellt wird. Das ist eine sichere Methode zum Rückgängigmachen von Änderungen, denn dabei ist ausgeschlossen, dass der Commit-Verlauf überschrieben wird. Zum Beispiel werden mit folgendem Befehl die Änderungen im zweitletzten Commit ermittelt, ein neuer Commit erstellt, der diese Änderung rückgängig macht, und der neue Commit dem bestehenden Projekt angehängt.

git checkout hotfix
git revert HEAD~2

Das lässt sich wie folgt veranschaulichen:

Rückgängigmachen des zweitletzten Commits

Im Gegensatz hierzu wird mit git reset der bestehende Commit-Verlauf verändert. Deshalb sollte git revert zum Rückgängigmachen von Änderungen an einem öffentlichen Branch genutzt werden und git reset sollte dem Rückgängigmachen von Änderungen an einem privaten Branch vorbehalten bleiben.

Du kannst dir git revert auch als Tool zum Rückgängigmachen von committeten Änderungen und git reset HEAD zum Rückgängigmachen von nicht committeten Änderungen merken.

Bei git revert, wie auch schon bei git checkout, besteht die Gefahr, dass Dateien im Arbeitsverzeichnis überschrieben werden. Daher musst du Änderungen, die während des "revert"-Vorgangs verloren gehen könnten, committen oder stashen.

Vorgänge auf Dateiebene

Die Befehle git reset und git checkout akzeptieren auch einen optionalen Dateipfad als Parameter. Dies ändert ihr Verhalten erheblich. Anstatt auf ganze Snapshots werden sie nur auf einzelne Dateien angewendet.

Eine bestimmte Datei mit "git reset" zurücksetzen

In Kombination mit einem Dateipfad aktualisiert git reset den Snapshot aus der Staging-Umgebung, damit dieser mit der Version des angegebenen Commits übereinstimmt. Mit diesem Befehl wird z. B. die Version von foo.py im zweitletzten Commit abgerufen und in die Staging-Umgebung überführt, damit sie in den nächsten Commit aufgenommen wird.

git reset HEAD~2 foo.py

Wie die Version von git reset auf Commit-Ebene wird dies eher mit HEAD verwendet als mit einem beliebigen Commit. Das Ausführen von git reset HEAD foo.py entfernt foo.py aus der Staging-Umgebung. Die darin enthaltenen Änderungen sind weiterhin im Arbeitsverzeichnis vorhanden.

Verschieben einer Datei vom Commit-Verlauf in den Snapshot aus der Staging-Umgebung

Die Optionen --soft, --mixed und --hard wirken sich nicht auf die Version auf Dateiebene von git reset aus, da der Snapshot in der Staging-Umgebung immer aktualisiert wird und das Arbeitsverzeichnis niemals aktualisiert wird.

Eine Datei mit "git checkout" auschecken

Das Auschecken einer Datei ähnelt der Nutzung von git reset mit einem Dateipfad, allerdings wird statt dem Staging-Bereich das Arbeitsverzeichnis aktualisiert. Anders als bei der Commit-Level-Version dieses Befehls wird die HEADReferenz nicht bewegt, sodass du nicht zwischen Branches wechseln kannst.

Verschieben einer Datei vom Commit-Verlauf in das Arbeitsverzeichnis

Der folgende Befehl sorgt beispielsweise dafür, dass foo.py im Arbeitsverzeichnis an den zweitletzten Commit angeglichen wird:

git checkout HEAD~2 foo.py

Genauso wie bei der Ausführung von git checkout auf Commit-Ebene können hiermit alte Versionen eines Projekts eingesehen werden, aber der Anwendungsbereich ist auf die spezifizierte Datei beschränkt.

Wenn du die ausgecheckte Datei in die Staging-Umgebung verschiebst und committest, führt dies dazu, dass du sie auf die alte Dateiversion zurücksetzt. Beachte, dass dabei alle nachfolgenden Änderungen an der Datei entfernt werden, während beim Befehl git revert nur die Änderungen rückgängig gemacht werden, die vom angegebenen Commit eingeführt wurden.

Wie git reset wird dies üblicherweise mit HEAD als Commit-Referenz verwendet. Beispielsweise werden mit git checkout HEAD foo.py die Änderungen an foo.py verworfen, die sich nicht in der Staging-Umgebung befinden. Dieses Verhalten ähnelt git reset HEAD --hard, bezieht sich aber nur auf die spezifizierte Datei.

Summary

Du solltest nun alle Werkzeuge kennen, die du zum Rückgängigmachen von Änderungen in einem Git-Repository benötigst. Die Befehle git reset, git checkout und git revert können Verwirrung stiften. Doch wenn du weißt, welche Auswirkungen sie auf das Arbeitsverzeichnis, den gestagten Snapshot und den Commit-Verlauf haben, kannst du dir leichter merken, welcher Befehl für deine Entwicklungsaufgabe der passende ist.