Der Befehl git reset ist ein komplexes und vielseitiges Werkzeug zum Rückgängigmachen von Änderungen. Diesen Befehl kannst du auf dreierlei Arten aufrufen. Das lässt sich mit folgenden Befehlszeilenargumenten ausdrücken: --soft, --mixed, --hard. Die drei Argumente entsprechen jeweils den drei Git-internen Mechanismen des Zustandsmanagements, nämlich dem Commit-Baum (HEAD), dem Staging-Index und dem Arbeitsverzeichnis.

git reset und die drei Bäume von Git

Um die richtige Anwendung von git reset zu verstehen, müssen wir daher zunächst die internen Zustandsmanagementsysteme in Git nachvollziehen. Manchmal werden diese Mechanismen die "drei Bäume" von Git genannt. "Baum" ist vielleicht nicht das ideale Wort, denn es handelt sich hier streng genommen nicht um herkömmliche Baumstrukturen von Daten. Git nutzt jedoch Knoten- und Pointer-basierte Datenstrukturen, um den zeitlichen Verlauf von Veränderungen aufzuzeichnen. Am besten lassen sich diese Mechanismen veranschaulichen, wenn wir in einem Repository ein Changeset erstellen und dieses durch die drei Bäume verfolgen. 

Zunächst einmal erstellen wir mit dem folgenden Befehl ein neues Repository:

$ mkdir git_reset_test
$ cd git_reset_test/
$ git init .
Initialized empty Git repository in /git_reset_test/.git/
$ touch reset_lifecycle_file
$ git add reset_lifecycle_file
$ git commit -m"initial commit"
[master (root-commit) d386d86] initial commit
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 reset_lifecycle_file

Im Code-Beispiel oben wird ein neues Git-Repository mit einer einzigen leeren Datei, reset_lifecycle_file, erstellt. Zu diesem Zeitpunkt gibt es in dem Beispiel-Repository nach dem Hinzufügen von reset_lifecycle_file einen einzigen Commit (d386d86).

The working directory

Den ersten Baum, den wir nun betrachten, ist das "Arbeitsverzeichnis". Dieser Baum wird mit dem lokalen Dateisystem synchronisiert und reflektiert unmittelbar die Änderungen an Datei- und Verzeichnisinhalten.


$ echo 'hello git reset' > reset_lifecycle_file
$ git status 
On branch master 
Changes not staged for commit: 
(use "git add ..." to update what will be committed) 
(use "git checkout -- ..." to discard changes in working directory) 
modified: reset_lifecycle_file

In unserem Demo-Repository ändern wir den Inhalt von reset_lifecycle_file und fügen neuen Inhalt hinzu. Mit dem Befehl git status sehen wir, dass Git die Änderungen an der Datei erkannt hat. Diese Änderungen gehören derzeit zum ersten Baum, dem "Arbeitsverzeichnis". Mit git status kannst du Änderungen am Arbeitsverzeichnis anzeigen lassen. Diese werden in Rot mit dem Präfix "geändert" dargestellt.

Staging-Index

Als Nächstes befassen wir uns mit dem Baum, der sich Staging-Index nennt. In diesem Baum werden Änderungen am Arbeitsverzeichnis verfolgt, die mit git add hinzugefügt wurden, um sie in den nächsten Commit aufzunehmen. Dieser Baum ist ein komplexer interner Caching-Mechanismus. Generell versucht Git, die Implementierungsdetail des Staging-Index vor dem Benutzer zu verbergen.

Um uns den Zustand des Staging-Index genauer anzeigen zu lassen, benötigen wir einen weniger bekannten Git-Befehl: git ls-files. Der Befehl git ls-files ist im Grunde genommen ein Werkzeug zur Fehlersuche, mit dem du den Zustand des Staging-Index untersuchen kannst.

git ls-files -s
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 reset_lifecycle_file

Hier haben wir git ls-files mit der Option -s bzw. --stage ausgeführt. Ohne die Option -s gibt git ls-files nur eine Namens- und Pfadliste von Dateien aus, die zurzeit zum Index gehören. Mit der Option -s werden zusätzliche Metadaten für die Dateien im Staging-Index angezeigt. Diese Metadaten enthalten die Modus-Bits, den Objektnamen und die Staging-Nummer der gestagten Inhalte. Hier interessiert uns nur der Objektname, nämlich der zweite Wert (d7d77c1b04b5edd5acfc85de0b592449e5303770). Das ist ein standardmäßiger Objekt-SHA-1-Hash in Git. Dabei handelt es sich um einen Hash der Dateiinhalte. Im Commit-Verlauf werden eigene Objekt-SHAs zur Erkennung von Pointern auf Commits und Refs gespeichert. Auch im Staging-Index gibt es zum Nachverfolgen von Dateiversionen im Index eigene Objekt-SHAs.

Als Nächstes werden wir die geänderte reset_lifecycle_file in den Staging-Index befördern.


$ git add reset_lifecycle_file 

$ git status 

On branch master Changes to be committed: 

(use "git reset HEAD ..." to unstage) 

modified: reset_lifecycle_file

Hier haben wir die Datei mit git add reset_lifecycle_file dem Staging-Index hinzugefügt. Wenn wir jetzt git status ausführen, wird reset_lifecycle_file unter "Changes to be committed" in grün angezeigt. Beachte unbedingt, dass git status keine getreue Darstellung des Staging-Index ist. Mit dem Befehl git status werden Änderungen zwischen dem Commit-Verlauf und dem Staging-Index angezeigt. Betrachten wir den Staging-Index hier einmal genauer.

$ git ls-files -s
100644 d7d77c1b04b5edd5acfc85de0b592449e5303770 0 reset_lifecycle_file

Hier sehen wir, dass der Objekt-SHA für reset_lifecycle_file von e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 zu d7d77c1b04b5edd5acfc85de0b592449e5303770 aktualisiert wurde.

Commit-Verlauf

Der letzte Baum ist der Commit-Verlauf. Mit dem Befehl git commit werden Änderungen einem dauerhaften Snapshot im Commit-Verlauf hinzugefügt. In diesem Snapshot wird auch der Zustand des Staging-Index zum Commit-Zeitpunkt aufgenommen.

$ git commit -am"update content of reset_lifecycle_file"
[master dc67808] update content of reset_lifecycle_file
1 file changed, 1 insertion(+)
$ git status
On branch master
nothing to commit, working tree clean

Hier haben wir einen neuen Commit mit der Nachricht "Update des Inhalts von lebenszyklus-reset-datei" erstellt. Dem Commit-Verlauf wurde ein Changeset hinzugefügt. git status zeigt uns, dass an dieser Stelle an keinem Baum Änderungen ausstehen. Mit git log wird der Commit-Verlauf angezeigt. Da wir diesen Changeset nun über die drei Bäume hinweg verfolgt haben, können wir jetzt mit git reset arbeiten.

Wie es funktioniert

An der Oberfläche zeigt git reset ein ähnliches Verhalten wie git checkout. Während git checkout nur auf den HEAD-Ref-Pointer angewendet wird, verschiebt git reset den HEAD-Ref-Pointer und den aktuellen Branch-Ref-Pointer. Das folgende Beispiel veranschaulicht dieses Verhalten:

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 und git reset b aus und vergleichen.

git checkout b

Mit git checkout verweist der Master-Ref weiterhin auf d. Der HEAD-Ref wurde entfernt und verweist jetzt auf Commit b. Das Repository befindet sich nun in einem Zustand mit "losgelöstem HEAD" (im Englischen "detached HEAD").

git reset b

git reset hingegen verschiebt sowohl den HEAD als auch die Branch-Refs zum angegebenen Commit.

git reset aktualisiert nicht nur die Commit-Ref-Pointer, sondern ändert auch den Zustand der drei Bäume. Ref-Pointer werden immer geändert. Das Update betrifft den dritten Baum, nämlich den Commit-Baum. Mit den Befehlszeilenargumenten --soft, --mixed und --hard kannst du die Änderungen am Staging-Index und am Arbeitsverzeichnis bestimmen.

Die wichtigsten Optionen

Mit git reset werden standardmäßig implizit die Argumente --mixed und HEAD mitaufgerufen. Wenn du git reset ausführst, ist das also dasselbe wie git reset --mixed HEAD. Dabei ist HEAD der angegebene Commit. Anstelle von HEAD kannst du auch einen beliebigen Git-SHA-1-Commit-Hash einsetzen.

--hard

Das ist die direkte, GEFÄHRLICHSTE und am häufigsten verwendete Option. Mit der Option --hard werden die Ref-Pointer des Commit-Verlaufs entsprechend dem angegebenen Commit aktualisiert. Der Staging-Index und das Arbeitsverzeichnis werden dann entsprechend auf den angegebenen Commit zurückgesetzt. Alle zuvor ausstehenden Änderungen am Staging-Index und am Arbeitsverzeichnis werden auf den Zustand des Commit-Baums zurückgesetzt. Das bedeutet, dass ausstehende Code-Änderungen im Staging-Index oder im Arbeitsverzeichnis verloren gehen.

Um das zu veranschaulichen, führen wir das zuvor erstellte Beispiel-Repository mit den drei Bäumen fort. Zunächst nehmen wie einige Änderungen an dem Repository vor. Führe folgende Befehle im Beispiel-Repository aus:

$ echo 'new file content' > new_file
$ git add new_file
$ echo 'changed content' >> reset_lifecycle_file

Mit diesen Befehlen haben wir eine neue Datei mit dem Namen new_file erstellt und sie dem Repository hinzugefügt. Außerdem wird der Inhalt der reset_lifecycle_file geändert. Nachdem wir diese Änderungen vorgenommen haben, sehen wir uns den Status des Repositorys mit git status an.

$ git status
On branch master
Changes to be committed:
(use "git reset HEAD ..." to unstage)
new file: new_file
Changes not staged for commit:
(use "git add ..." to update what will be committed)
(use "git checkout -- ..." to discard changes in working directory)
modified: reset_lifecycle_file

Wir sehen, dass nun Änderungen an dem Repository ausstehen. Im Staging-Index-Baum steht das Hinzufügen von new_file noch aus und im Arbeitsverzeichnis steht die Änderung von reset_lifecycle_file aus.

Bevor wir weitergehen, untersuchen wir zunächst den Zustand des Staging-Index:

$ git ls-files -s
100644 8e66654a5477b1bf4765946147c49509a431f963 0 new_file
100644 d7d77c1b04b5edd5acfc85de0b592449e5303770 0 reset_lifecycle_file

Wir sehen hier, dass new_file dem Index hinzugefügt wurde. Wir haben die reset_lifecycle_file aktualisiert, doch der Staging-Index-SHA (d7d77c1b04b5edd5acfc85de0b592449e5303770) bleibt unverändert. Das ist das erwartete Verhalten, da wir diese Änderungen nicht mit git add zum Staging-Index hinzugefügt haben. Diese Änderungen befinden sich im Arbeitsverzeichnis.

Nun führen wir git reset --hard aus, um den aktuellen Zustand des Repositorys zu erfahren.

$ git reset --hard
HEAD is now at dc67808 update content of reset_lifecycle_file
$ git status
On branch master
nothing to commit, working tree clean
$ git ls-files -s
100644 d7d77c1b04b5edd5acfc85de0b592449e5303770 0 reset_lifecycle_file

Hier haben wir mit der Option --hard eine vollständige Zurücksetzung durchgeführt. Der Git-Output zeigt an, dass HEAD auf den neuesten Commit, dc67808, verweist. Als Nächstes überprüfen wir den Zustand des Repositorys mit git status. Git meldet keine ausstehenden Änderungen. Wir kontrollieren auch den Zustand des Staging-Index und sehen, dass dieser auf einen Zeitpunkt zurückgesetzt wurde, an dem new_file noch nicht hinzugefügt worden war. Unsere Änderung an reset_lifecycle_file und das Hinzufügen von new_file wurden gelöscht. Eines sollte dir unbedingt bewusst sein: Dieser Datenverlust kann nicht rückgängig gemacht werden.

--mixed

So lautet der standardmäßige Betriebsmodus. Die Ref-Pointer werden aktualisiert. Der Staging-Index wird auf den Zustand des angegebenen Commits zurückgesetzt. Alle Änderungen, die im Staging-Index rückgängig gemacht wurden, werden in das Arbeitsverzeichnis verschoben. Machen wir weiter.

$ echo 'new file content' > new_file
$ git add new_file
$ echo 'append content' >> reset_lifecycle_file
$ git add reset_lifecycle_file
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD ..." to unstage)
new file: new_file
modified: reset_lifecycle_file
$ git ls-files -s
100644 8e66654a5477b1bf4765946147c49509a431f963 0 new_file
100644 7ab362db063f9e9426901092c00a3394b4bec53d 0 reset_lifecycle_file

Im Beispiel oben haben wir einige Änderungen am Repository vorgenommen. Zur Erinnerung: Wir haben eine new_file hinzugefügt und die Inhalte der reset_lifecycle_file geändert. Diese Änderungen wurden dann mit git add dem Staging-Index hinzugefügt. In diesem Zustand des Repositorys führen wir nun das Zurücksetzen durch.

$ git reset --mixed
$ git status
On branch master
Changes not staged for commit:
(use "git add ..." to update what will be committed)
(use "git checkout -- ..." to discard changes in working directory)
modified: reset_lifecycle_file
Untracked files:
(use "git add ..." to include in what will be committed)
new_file
no changes added to commit (use "git add" and/or "git commit -a")
$ git ls-files -s
100644 d7d77c1b04b5edd5acfc85de0b592449e5303770 0 reset_lifecycle_file

Hier haben wir einen "gemischten Reset" zum Zurücksetzen durchgeführt. Zur Wiederholung: --mixed ist der Standardmodus und bewirkt dasselbe wie git reset. Sehen wir uns den Output zu git status und git ls-files einmal genauer an: Der Staging-Index wurde auf einen Zustand zurückgesetzt, in dem reset_lifecycle_file die einzige Datei im Index ist. Der Objekt-SHA für reset_lifecycle_file wurde auf die vorherige Version zurückgesetzt.

Beachte hier, dass git status anzeigt, dass es Änderungen an reset_lifecycle_file und eine nicht verfolgte Datei gibt: new_file. Das ist eindeutig auf --mixed zurückzuführen. Der Staging-Index wurde zurückgesetzt und die ausstehenden Änderungen wurden in das Arbeitsverzeichnis verschoben. Vergleichen wir das, was beim Zurücksetzen mit --hard passiert: Der Staging-Index wurde zurückgesetzt – und auch das Arbeitsverzeichnis. Die dazugehörigen Updates sind verloren gegangen.

--soft

Mit dem Argument --soft werden die Ref-Pointer aktualisiert und das Zurücksetzen wird an dieser Stelle angehalten. Staging-Index und Arbeitsverzeichnis bleiben unberührt. Dieses Verhalten kann man leider nicht so leicht demonstrieren. Wir verwenden weiterhin unser Demo-Repository und bereiten uns auf einen "Soft-Reset" (Teil-Reset) vor.


$ git add reset_lifecycle_file 

$ git ls-files -s 

100644 67cc52710639e5da6b515416fd779d0741e3762e 0 reset_lifecycle_file 

$ git status 

On branch master 

Changes to be committed: 

(use "git reset HEAD ..." to unstage) 

modified: reset_lifecycle_file 

Untracked files: 

(use "git add ..." to include in what will be committed) 

new_file

Hier haben wir die geänderte reset_lifecycle_file wieder mit git add in den Staging-Index befördert. Anhand des Outputs von git ls-files können wir die Aktualisierung des Index überprüfen. Nach der Eingabe von git status werden "Changes to be committed" in Grün angezeigt. Die new_file aus den vorherigen Beispielen ist im Arbeitsverzeichnis als nicht verfolgte Datei im Umlauf. Da wir diese Datei für die folgenden Beispiele nicht mehr brauchen, führen wir schnell rm new_file aus und die Datei wird gelöscht.

In diesem Zustand des Repositorys führen wir nun einen Soft-Reset durch.

$ git reset --soft
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD ..." to unstage)
modified: reset_lifecycle_file
$ git ls-files -s
100644 67cc52710639e5da6b515416fd779d0741e3762e 0 reset_lifecycle_file

Hier haben wir einen "Soft-Reset" zum Zurücksetzen durchgeführt. Wenn wir den Repository-Status mit git status und git ls-files anzeigen lassen, erkennen wir, dass sich nichts geändert hat. Das ist das erwartete Verhalten. Mit einem Soft-Reset wird nur der Commit-Verlauf zurückgesetzt. Standardmäßig wird git reset mit dem HEAD als Ziel-Commit durchgeführt. Da unser Commit-Verlauf bereits am HEAD sitzt und wir daher implizit auf den HEAD zurücksetzen, hat sich nichts geändert.

Um --soft besser zu verstehen und zu benutzen, brauchen wir einen Ziel-Commit, der nicht der HEAD ist. Die reset_lifecycle_file ist noch im Staging-Index zwischengelagert. Erstellen wir also einen neuen Commit.

$ git commit -m"prepend content to reset_lifecycle_file"

Zu diesem Zeitpunkt sollte es in unserem Repository drei Commits geben. Machen wir einen kleinen Zeitsprung zurück zum ersten Commit. Dazu brauchen wir die ID des ersten Commits. Diese finden wir in den Ausgabedaten von git log.

$ git log
commit 62e793f6941c7e0d4ad9a1345a175fe8f45cb9df
Author: bitbucket 
Date: Fri Dec 1 15:03:07 2017 -0800
prepend content to reset_lifecycle_file
commit dc67808a6da9f0dec51ed16d3d8823f28e1a72a
Author: bitbucket 
Date: Fri Dec 1 10:21:57 2017 -0800
update content of reset_lifecycle_file
commit 780411da3b47117270c0e3a8d5dcfd11d28d04a4
Author: bitbucket 
Date: Thu Nov 30 16:50:39 2017 -0800
initial commit

Beachte, dass Commit-Verlaufs-IDs in jedem System eindeutig sind. Die Commit-IDs auf deiner Maschine werden also andere als in diesem Beispiel sein. Wichtig für unser Beispiel hier ist die Commit-ID 780411da3b47117270c0e3a8d5dcfd11d28d04a4. Diese ID gehört zum "ersten Commit". Wenn wir diese ID kennen, können wir sie für unseren Soft-Reset verwenden.

Bevor wir die Zeit zurückdrehen, sollten wir zunächst den aktuellen Zustand des Repositorys überprüfen.

$ git status && git ls-files -s
On branch master
nothing to commit, working tree clean
100644 67cc52710639e5da6b515416fd779d0741e3762e 0 reset_lifecycle_file

Hier haben wir die beiden Befehle git status und git ls-files -s zugleich ausgeführt. Daraufhin wird angezeigt, dass Änderungen im Repository ausstehen und die reset_lifecycle_file im Staging-Index auf dem Stand von Version 67cc52710639e5da6b515416fd779d0741e3762e ist. Behalten wir das im Hinterkopf. Nun führen wir einen Soft-Reset aus, um zum Zustand unseres ersten Commits zurückzukehren.

$git reset --soft 780411da3b47117270c0e3a8d5dcfd11d28d04a4
$ git status && git ls-files -s
On branch master
Changes to be committed:
(use "git reset HEAD ..." to unstage)
modified: reset_lifecycle_file
100644 67cc52710639e5da6b515416fd779d0741e3762e 0 reset_lifecycle_file

Mit dem Code oben werden ein "Soft-Reset" zum Zurücksetzen sowie die beiden Befehle git status und git ls-files zugleich durchgeführt. Ausgegeben wird der Zustand des Repositorys. Wenn wir diesen Output unter die Lupe nehmen, fällt uns etwas Interessantes auf. Zum einen gibt git status die Änderungen an reset_lifecycle_file an und hebt sie hervor, um anzuzeigen, dass die Änderungen für den nächsten Commit gestaged wurden. Zum anderen sehen wir, dass sich durch die git ls-files-Eingabe der Staging-Index nicht geändert hat und unser vorheriger SHA 67cc52710639e5da6b515416fd779d0741e3762e beibehalten wurde.

Mit git log erfahren mir Genaueres darüber, was bei dieser Zurücksetzung passiert ist:

$ git log
commit 780411da3b47117270c0e3a8d5dcfd11d28d04a4
Author: bitbucket 
Date: Thu Nov 30 16:50:39 2017 -0800
initial commit

Das Protokoll zeigt nun einen einzigen Commit im Commit-Verlauf an. Das veranschaulicht den --soft-Vorgang sehr gut. Bei der Ausführung von git reset-Befehlen wird immer zuerst der Commit-Baum zurückgesetzt. Unsere vorherigen Beispiele mit --hard und --mixed wurden beide auf den HEAD ausgeführt und haben die Zeit für den Commit-Baum nicht zurückgedreht. Mehr passiert bei einem Soft-Reset nicht.

Das mag verwirrend erscheinen, da mit git status geänderte Dateien angezeigt werden. Mit --soft bleibt der Staging-Index unberührt und die Updates an unserem Staging-Index blieben uns beim Zeitsprung durch den Commit-Verlauf erhalten. Das können wir uns mit git ls-files -s bestätigen lassen. Wir sehen dann, dass der SHA für die reset_lifecycle_file unverändert ist. Nicht vergessen: git status offenbart nicht den Zustand der "drei Bäume", sondern die Unterschiede zwischen ihnen. In diesem Fall sehen wir, dass der Staging-Index vor den Änderungen im Commit-Verlauf liegt, und es sieht aus, als hätten wir diese Änderungen bereits gestaged.

Zurücksetzen vs. Rückgängigmachen

Während git revert als die "sichere" Methode zum Rückgängigmachen von Änderungen gilt, kann git reset als die gefährliche Methode bezeichnet werden. Bei git reset besteht das ernsthafte Risiko, dass etwas verloren geht. Mit git reset werden Commits auf keinen Fall gelöscht, allerdings können sie "verwaisen", d. h., dass kein direkter Pfad von einem Ref mehr existiert, um einen Zugriff herzustellen. Solche verwaisten Commits können normalerweise mit git reflog ausfindig gemacht und wiederhergestellt werden. Nach einer internen Speicherbereinigung durch Git werden alle verwaisten Commits dauerhaft gelöscht. Standardmäßig führt Git die Speicherbereinigung alle 30 Tage durch. Der Commit-Verlauf ist einer der "drei Git-Bäume". Staging-Index und Arbeitsverzeichnis, die anderen beiden Bäume, sind weniger dauerhaft als Commits. Da du mit diesem Git-Befehl also riskierst, programmierten Code zu verlieren, solltest du ihn nur mit Bedacht einsetzen.

Eine Rückgängigmachung ist dabei zur sicheren Aufhebung von öffentlichen Commits gedacht, git reset hingegen zur Aufhebung lokaler Änderungen im Staging-Index und im Arbeitsverzeichnis. Da diese beiden Befehle zwei ganz unterschiedliche Funktionen haben, sind sie auch unterschiedlich implementiert: Eine Zurücksetzung entfernt ein Changeset vollständig. Bei einer Rückgängigmachung jedoch wird das ursprüngliche Changeset beibehalten und ein neuer Commit durchgeführt, um die Änderungen aufzuheben.

Öffentlichen Verlauf nicht zurücksetzen

Du solltest git reset <commit> niemals verwenden, wenn Snapshots nach dem in "<commit>" angegebenen Commit bereits in ein öffentliches Repository gepusht wurden. Denn sobald ein Commit veröffentlicht wurde, musst du davon ausgehen, dass deine Kollegen mit ihm arbeiten.

Das Entfernen eines Commit, den andere Teammitglieder entwickelt haben, stellt ein ernstes Problem für die Zusammenarbeit dar. Wenn sie versuchen, ihn mit deinem Repository zu synchronisieren, wird es aussehen, als ob ein großer Teil des Projektverlaufs plötzlich verschwunden wäre. Die nachstehende Sequenz zeigt, was beim Versuch passiert, einen öffentlichen Commit zurückzusetzen. Der origin/master-Branch ist die Version deine lokalen master-Branch im zentralen Repository.

Sobald du nach der Zurücksetzung neue Commits hinzufügst, nimmt Git an, dass dein lokaler Verlauf von origin/master abweicht. Der zur Synchronisierung deiner Repositorys erforderliche Merge-Commit wird deine Teamkollegen sehr wahrscheinlich verwirren und frustrieren.

Also: Wende git reset <commit> nur auf lokale Code-Experimente an, die nicht wie gewünscht funktioniert haben, niemals aber auf veröffentlichte Änderungen. Wenn du einen öffentlichen Commit korrigieren musst, verwende git revert. Dieser Befehl ist speziell für diesen Zweck vorgesehen.

Examples

git reset <file>

Entfernt die angegebene Datei aus der Staging-Umgebung, ohne dabei Änderungen am Arbeitsverzeichnis vorzunehmen. Damit wird die Datei aus der Staging-Umgebung entfernt, ohne Änderungen zu überschreiben.

git reset

Mit diesem Befehl setzt du die Staging-Umgebung auf den neuesten Commit zurück, lässt das Arbeitsverzeichnis jedoch unverändert. Es werden sämtliche Dateien aus der Staging-Umgebung entfernt, jedoch keine Änderungen überschrieben. So kannst du den in der Staging-Umgebung gehosteten Snapshot von Grund auf neu erstellen.

git reset --hard

Mit diesem Befehl setzt du die Staging-Umgebung und das Arbeitsverzeichnis auf den neuesten Commit zurück. Da das Flag --hard gesetzt ist, entfernt Git nicht nur die Änderungen aus der Staging-Umgebung, sondern überschreibt auch alle Änderungen im Arbeitsverzeichnis. Anders ausgedrückt: Alle noch nicht committeten Änderungen werden vollständig gelöscht. Du solltest diesen Befehl also nur verwenden, wenn du deine gesamte lokale Entwicklungsarbeit verwerfen möchtest.

git reset <commit> 

Mit diesem Befehl verschiebst du die Spitze des aktuellen Branches zurück auf den Commit und setzt die Staging-Umgebung entsprechend zurück, belässt das Arbeitsverzeichnis jedoch unverändert. Alle Änderungen, die seit dem <commit> vorgenommen wurden, verbleiben im Arbeitsverzeichnis. So kannst du den Projektverlauf in Form von klarer strukturierten, granulareren Snapshots erneut committen.

git reset --hard <commit> 

Mit diesem Befehl verschiebst du die Spitze des aktuellen Branches zurück auf den in <commit> angegebenen Commit und setzt sowohl die Staging-Umgebung als auch das Arbeitsverzeichnis auf dessen Stand zurück. Dadurch werden nicht nur alle nicht committeten Änderungen vollständig gelöscht, sondern auch alle nachfolgenden Commits.

Datei aus der Staging-Umgebung entfernen

Der Befehl git reset wird oft bei der Arbeit an Snapshots in der Staging-Umgebung verwendet. Im nächsten Beispiel nehmen wir an, dass du zwei Dateien hast: hello.py und main.py. Beide Dateien wurden dem Repository bereits hinzugefügt.

# Bearbeite hello.py und main.py.
# Stage alles im aktuellen Verzeichnis
"git add".
# Beachte, dass die Änderungen in hello.py und main.py
# in verschiedenen Snapshots committet werden sollten.
# Hebe das Staging von main.py auf.
git reset main.py
# Committe nur hello.py.
git commit -m "Nimm einige Änderungen an hello.py vor."
# Committe main.py in einem separaten Snapshot
git add main.py
git commit -m "Bearbeite main.py."

Mit git reset kannst du Änderungen aus der Staging-Umgebung entfernen, die keinen Bezug zum nächsten Commit haben. So kannst du sicherstellen, dass deine Commits keinen unnötigen Code enthalten.

Lokale Commits entfernen

Im nächsten Beispiel zeigen wir einen erweiterten Verwendungszweck. Es veranschaulicht, was passiert, wenn du eine Zeit lang an einem neuen Experiment gearbeitet hast, es dann aber komplett verwerfen willst, nachdem du einige Snapshots committet hast.

# Eine neue Datei mit dem Namen `foo.py` erstellen und Code einfügen
# Zum Projektverlauf committen
git add foo.py
git commit -m "Mit der Entwicklung eines verrückten Features beginnen"
# `foo.py` erneut bearbeiten und auch andere nachverfolgte Dateien ändern
# Einen weiteren Snapshot committen
git commit -a -m "Mit verrücktem Feature fortfahren"
# Beschließen, das Feature zu verwerfen und die entsprechenden Commits zu entfernen
git reset --hard HEAD~2

Der Befehl git reset HEAD~2 setzt den aktuellen Branch um 2 Commits zurück. Dadurch werden die beiden gerade erstellten Snapshots aus dem Projektverlauf entfernt. Denke daran: Diese Art Zurücksetzung solltest du ausschließlich auf noch nicht veröffentlichte Commits anwenden. Verwende diesen Befehl niemals, wenn du deine Commits bereits in ein gemeinsam genutztes Repository gepusht hast.

Summary

git reset ist ein starker Befehl zum Reviewen, der lokale Änderungen auf den Stand eines bestimmten Git-Repositorys zurücksetzt. git reset wirkt sich auf die "drei Bäume von Git" aus. Diese Bäume sind der Commit-Verlauf (HEAD), der Staging-Index und das Arbeitsverzeichnis. Es gibt drei Befehlszeilenoptionen, die den drei Bäumen entsprechen. Die Optionen --soft, --mixed und --hard können auf git reset angewendet werden.

In diesem Artikel haben wir anhand einiger anderer Git-Befehle gezeigt, wie das Zurücksetzen abläuft. Mehr über diese Befehle erfährst du auf den jeweiligen Seiten zu git status, git log, git add, git checkout, git reflog und git revert.

Ready to learn git reset?

Sieh dir dieses interaktive Tutorial an.

Jetzt loslegen