Git Reset vs. Revert vs. Rebase

Verständnis und Anwendung von Git-Commits: Reset, Revert und Rebase

In diesem Artikel werden wir uns eingehend mit den verschiedenen Möglichkeiten beschäftigen, wie Sie Ihre Commits in Git manipulieren können. Als Entwickler stoßen Sie häufig auf Situationen, in denen Sie zu einem früheren Commit zurückkehren müssen. Dabei ist es entscheidend, die Unterschiede zwischen den Git-Befehlen reset, revert und rebase zu verstehen. Lassen Sie uns diese Befehle und ihre Funktionen genauer betrachten.

Git Reset

Der Befehl git reset ist ein mächtiges Werkzeug, um Änderungen rückgängig zu machen und zu früheren Zuständen zurückzukehren. Er funktioniert wie eine Art Rollback-Funktion, mit der Sie zwischen verschiedenen Commits hin- und herspringen können. Es gibt drei Hauptmodi für die Ausführung von git reset: --soft, --mixed und --hard. Standardmäßig wird der --mixed-Modus verwendet. Bei der Verwendung von git reset spielen drei interne Git-Komponenten eine Rolle: der HEAD, der Staging-Bereich (Index) und das Arbeitsverzeichnis.

Das Arbeitsverzeichnis ist der Ort, an dem Sie an Ihren Dateien arbeiten und sie sich befinden. Sie können den Status mit git status einsehen. Der Staging-Bereich (Index) ist der Bereich, in dem Git alle Änderungen verfolgt. Diese Änderungen werden im .git-Verzeichnis gespeichert. Dateien werden mit git add "Dateiname" zum Staging-Bereich hinzugefügt. Der aktuelle Zweig in Git wird als HEAD bezeichnet, welcher immer auf den letzten Commit zeigt. Wenn Sie zu einem anderen Zweig wechseln, bewegt sich der HEAD ebenfalls mit.

Wir betrachten nun die Funktionsweise von git reset im --hard-, --soft– und --mixed-Modus genauer. Der --hard-Modus versetzt Ihren HEAD auf den angegebenen Commit, wobei das Arbeitsverzeichnis mit den Dateien dieses Commits überschrieben und der Staging-Bereich zurückgesetzt wird. Der --soft-Reset verändert nur den HEAD-Zeiger, während Dateien im Arbeitsverzeichnis und Staging-Bereich erhalten bleiben. Im --mixed-Modus (Standard) werden sowohl der HEAD-Zeiger als auch der Staging-Bereich zurückgesetzt.

Git Hard Reset

Der git reset --hard-Befehl wird verwendet, um den HEAD-Zeiger auf einen bestimmten Commit zu verschieben. Dadurch werden alle Commits, die nach dem angegebenen Commit erfolgten, entfernt und der Commit-Verlauf wird geändert.

Betrachten wir das folgende Beispiel. Zuerst werden drei neue Dateien hinzugefügt und committet.

$ git status
On branch master
Your branch is ahead of 'origin/master' by 2 commits.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
    

Nun erstellen wir 3 Dateien mit Inhalten:

$ vi file1.txt
$ vi file2.txt
$ vi file3.txt
   

Die neuen Dateien werden dem Repository hinzugefügt:

$ git add file*
   

Ein erneuter git status-Befehl zeigt die neuen Dateien im Staging-Bereich:

$ git status
On branch master
Your branch is ahead of 'origin/master' by 2 commits.
(use "git push" to publish your local commits)
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: file1.txt
new file: file2.txt
new file: file3.txt
  

Bevor wir den Commit ausführen, sehen wir uns das Commit-Log an:

$ git log --oneline
0db602e (HEAD -> master) one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test
  

Die Dateien werden nun committet:

$ git commit -m 'added 3 files'
[master d69950b] added 3 files
3 files changed, 3 insertions(+)
create mode 100644 file1.txt
create mode 100644 file2.txt
create mode 100644 file3.txt
  

Die neuen Dateien sind nun vorhanden:

$ git ls-files
demo
dummyfile
newfile
file1.txt
file2.txt
file3.txt
  

Nach dem Commit sind 4 Einträge im Log vorhanden, mit dem HEAD auf dem letzten:

$ git log --oneline
d69950b (HEAD -> master) added 3 files
0db602e one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test
  

Wenn wir file1.txt löschen und den Status abfragen, wird dies angezeigt:

$ git status
On branch master
Your branch is ahead of 'origin/master' by 3 commits.
(use "git push" to publish your local commits)
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
deleted: file1.txt
no changes added to commit (use "git add" and/or "git commit -a")
  

Nun führen wir den Hard-Reset durch:

$ git reset --hard
HEAD is now at d69950b added 3 files

Wenn wir den Status überprüfen, sehen wir, dass es nichts zu committen gibt und die gelöschte Datei wiederhergestellt wurde, da wir keinen Commit nach dem Löschen durchgeführt haben:

$ git status
On branch master
Your branch is ahead of 'origin/master' by 3 commits.
(use "git push" to publish your local commits)
nothing to commit, working tree clean

Das Log zeigt an, dass der HEAD auf den Commit „added 3 files“ zeigt:

$ git log
commit d69950b7ea406a97499e07f9b28082db9db0b387 (HEAD -> master)
Author: mrgeek <[email protected]>
Date:
Mon May 17 19:53:31 2020 +0530

added 3 files

commit 0db602e085a4d59cfa9393abac41ff5fd7afcb14
Author: mrgeek <[email protected]>
Date:
Mon May 17 01:04:13 2020 +0530

one more commit

commit 59c86c96a82589bad5ecba7668ad38aa684ab323
Author: mrgeek <[email protected]>
Date:
Mon May 17 00:54:53 2020 +0530

new commit

commit e2f44fca2f8afad8e4d73df6b72111f2f2fd71ad (origin/master, origin/HEAD)
Author: mrgeek <[email protected]>
Date:
Mon May 17 00:16:33 2020 +0530

test
  

Wir setzen nun den HEAD um einen Commit zurück, indem wir HEAD^ verwenden:

$ git reset --hard HEAD^
HEAD is now at 0db602e one more commit
  

Wir sehen, dass der HEAD nun auf einen früheren Commit zeigt:

$ git log --oneline
0db602e (HEAD -> master) one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test
 

Das Log zeigt, dass der Commit d69950b nicht mehr vorhanden ist:

$ git log
commit 0db602e085a4d59cfa9393abac41ff5fd7afcb14 (HEAD -> master)
Author: mrgeek <[email protected]>
Date:
Mon May 17 01:04:13 2020 +0530

one more commit

commit 59c86c96a82589bad5ecba7668ad38aa684ab323
Author: mrgeek <[email protected]>
Date:
Mon May 17 00:54:53 2020 +0530

new commit

commit e2f44fca2f8afad8e4d73df6b72111f2f2fd71ad (origin/master, origin/HEAD)
Author: mrgeek <[email protected]>
Date:
Mon May 17 00:16:33 2020 +0530

Test
  

Die drei neuen Dateien sind nicht mehr im Repository vorhanden, da sie nach dem Hard-Reset entfernt wurden:

$ git ls-files
demo
dummyfile
newfile
  

Git Soft Reset

Betrachten wir nun ein Beispiel für git reset --soft. Die 3 Dateien wurden erneut hinzugefügt und committet. Das Git-Log zeigt den neuesten Commit „soft reset“:

$ git log --oneline
aa40085 (HEAD -> master) soft reset
0db602e one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test
  

Details zum Commit können mit dem Befehl git log angezeigt werden:

$ git log
commit aa400858aab3927e79116941c715749780a59fc9 (HEAD -> master)
Author: mrgeek <[email protected]>
Date:
Mon May 17 21:01:36 2020 +0530

soft reset

commit 0db602e085a4d59cfa9393abac41ff5fd7afcb14
Author: mrgeek <[email protected]>
Date:
Mon May 17 01:04:13 2020 +0530

one more commit

commit 59c86c96a82589bad5ecba7668ad38aa684ab323
Author: mrgeek <[email protected]>
Date:
Mon May 17 00:54:53 2020 +0530

new commit

commit e2f44fca2f8afad8e4d73df6b72111f2f2fd71ad (origin/master, origin/HEAD)
Author: mrgeek <[email protected]>
Date:
Mon May 17 00:16:33 2020 +0530

test
  

Wir möchten nun zum älteren Commit mit SHA 0db602e085a4d59cfa9393abac41ff5fd7afcb14 zurückkehren. Dafür verwenden wir git reset --soft:

$ git reset --soft 0db602e085a4
  

Das Git-Log zeigt, dass der HEAD auf den angegebenen Commit zurückgesetzt wurde:

$ git log
commit 0db602e085a4d59cfa9393abac41ff5fd7afcb14 (HEAD -> master)
Author: mrgeek <[email protected]>
Date:
Mon May 17 01:04:13 2020 +0530

one more commit

commit 59c86c96a82589bad5ecba7668ad38aa684ab323
Author: mrgeek <[email protected]>
Date:
Mon May 17 00:54:53 2020 +0530

new commit

commit e2f44fca2f8afad8e4d73df6b72111f2f2fd71ad (origin/master, origin/HEAD)
Author: mrgeek <[email protected]>
Date:
Mon May 17 00:16:33 2020 +0530

test
 

Im Gegensatz zum Hard-Reset bleiben die Dateien des Commit aa400858aab3927e79116941c715749780a59fc9, bei dem 3 Dateien hinzugefügt wurden, im Arbeitsverzeichnis erhalten. Daher ist ein Soft-Reset in Situationen, in denen keine Daten verloren gehen sollen, vorzuziehen:

$ git ls-files
demo
dummyfile
file1.txt
file2.txt
file3.txt
newfile
  

Git Revert

Der git revert-Befehl dient dazu, Änderungen rückgängig zu machen, ähnlich wie reset. Der Hauptunterschied besteht darin, dass revert einen neuen Commit erstellt, um zu einem bestimmten Zustand zurückzukehren. git revert löscht keine Daten während des Wiederherstellungsprozesses und fügt einen neuen Commit im Verlauf hinzu.

Nehmen wir an, wir fügen 3 Dateien hinzu und committen diese:

$ git commit -m 'add 3 files again'
[master 812335d] add 3 files again
3 files changed, 3 insertions(+)
create mode 100644 file1.txt
create mode 100644 file2.txt
create mode 100644 file3.txt
  

Das Log zeigt den neuen Commit an:

$ git log --oneline
812335d (HEAD -> master) add 3 files again
0db602e one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test
  

Um zu einem früheren Commit, z.B. 59c86c9 new commit, zurückzukehren, führen wir folgenden Befehl aus:

$ git revert 59c86c9
  

Dadurch wird ein Editor geöffnet. Dort können Sie Ihre Commit-Nachricht hinzufügen. Nach dem Speichern und Schließen wird der Revert ausgeführt.

Revert "new commit"
This reverts commit 59c86c96a82589bad5ecba7668ad38aa684ab323.
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch master
# Your branch is ahead of 'origin/master' by 4 commits.
# (use "git push" to publish your local commits)
#
# Changes to be committed:
# modified: dummyfile
  

Nachdem wir gespeichert und geschlossen haben, erscheint diese Ausgabe:

$ git revert 59c86c9
[master af72b7a] Revert "new commit"
1 file changed, 1 insertion(+), 1 deletion(-)
  

Das Log zeigt, dass revert, im Gegensatz zu reset, einen neuen Commit hinzugefügt hat:

$ git log --oneline
af72b7a (HEAD -> master) Revert "new commit"
812335d add 3 files again
0db602e one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test
   

Das Git-Log enthält den gesamten Commit-Verlauf. Wenn Sie Commits aus dem Verlauf entfernen möchten, ist revert keine gute Wahl, aber wenn Sie die Commit-Änderungen im Verlauf beibehalten möchten, ist revert anstelle von reset der geeignete Befehl.

Git Rebase

git rebase ermöglicht es, Commits eines Zweigs auf einen anderen zu verschieben oder zu kombinieren. In der Praxis werden Features üblicherweise nicht direkt auf dem master-Branch entwickelt, sondern in einem separaten Feature-Branch. Wenn die Entwicklung abgeschlossen ist, werden diese Commits in den master-Branch integriert.

rebase kann manchmal verwirrend sein, da er der Zusammenführung sehr ähnlich ist. Sowohl Merge als auch Rebase haben das Ziel, Commits aus einem Feature-Branch in einen Master-Branch zu übernehmen. Angenommen, wir haben eine Situation wie in der folgenden Darstellung:

In einem Team mit mehreren Entwicklern, die an verschiedenen Feature-Branches arbeiten, kann dies schnell komplex werden. Anstatt einen Git-Merge durchzuführen, verwenden wir rebase, um die Commits des Feature-Branches auf den Master-Branch zu verschieben. rebase dupliziert die Commits des Feature-Branches auf dem Master-Branch.

Dadurch erhalten Sie eine klare, lineare Commit-Historie, die einfacher zu verfolgen ist. Auch bei einer großen Anzahl von Entwicklern bleibt der Commit-Verlauf übersichtlich.

Um dies praktisch zu zeigen, hier ein Beispiel. Der aktuelle master-Branch hat 4 Commits:

$ git log --oneline
812335d (HEAD -> master) add 3 files again
0db602e one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test
  

Wir erstellen einen neuen Zweig namens feature, der auf dem 2. Commit (59c86c9) basiert:

(master)
$ git checkout -b feature 59c86c9
Switched to a new branch 'feature'
  

Das Log des feature-Branches zeigt 2 Commits vom master:

(feature)
$ git log --oneline
59c86c9 (HEAD -> feature) new commit
e2f44fc (origin/master, origin/HEAD) test
  

Wir erstellen feature1.txt und fügen es zum feature-Branch hinzu:

(feature)
$ vi feature1.txt
(feature)
$ git add .
The file will have its original line endings in your working directory
(feature)
$ git commit -m 'feature 1'
[feature c639e1b] feature 1
1 file changed, 1 insertion(+)
create mode 100644 feature1.txt
  

Wir erstellen ein weiteres Feature feature2.txt und committen dies:

(feature)
$ vi feature2.txt
(feature)
$ git add .
The file will have its original line endings in your working directory
(feature)
$ git commit -m 'feature 2'
[feature 0f4db49] feature 2
1 file changed, 1 insertion(+)
create mode 100644 feature2.txt
  

Das Log des feature-Branches zeigt die zwei neuen Commits:

(feature)
$ git log --oneline
0f4db49 (HEAD -> feature) feature 2
c639e1b feature 1
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test
  

Um die zwei neuen Features zum master-Branch hinzuzufügen, verwenden wir rebase. Wir rebasen den feature-Branch gegen den master-Branch:

(feature)
$ git rebase master
Successfully rebased and updated refs/heads/feature.
  

Wir wechseln zum master-Branch:

(feature)
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 3 commits.
(use "git push" to publish your local commits)
  

Nun rebasen wir den master-Branch gegen den feature-Branch. Dadurch werden die Commits des feature-Branches zum master-Branch hinzugefügt:

(master)
$ git rebase feature
Successfully rebased and updated refs/heads/master.
  

Das Log des master-Branches zeigt, dass die Commits des feature-Branches erfolgreich hinzugefügt wurden:

(master)
$ git log --oneline
766c996 (HEAD -> master, feature) feature 2
c036a11 feature 1
812335d add 3 files again
0db602e one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test
  

Dies war eine umfassende Einführung in die Verwendung von reset, revert und rebase in Git.

Fazit

Dieser Artikel hat Ihnen hoffentlich einen detaillierten Überblick über die Git-Befehle reset, revert und rebase gegeben. Mit diesem Wissen können Sie nun Ihre Commits nach Bedarf manipulieren. Experimentieren Sie mit diesen Befehlen, um ein besseres Verständnis für deren Funktionsweise zu erlangen.