Lernpfad
Git Squash Commits: Ein Leitfaden mit Beispielen
"Commit early, commit often" ist ein beliebtes Mantra in der Softwareentwicklung, wenn man Git verwendet. So wird sichergestellt, dass jede Änderung gut dokumentiert ist, die Zusammenarbeit verbessert und die Entwicklung des Projekts leichter nachvollziehbar wird. Das kann aber auch zu einem Übermaß an Commits führen.
An dieser Stelle kommt die Bedeutung von ins Spiel. Beim Squashing von Commits werden mehrere Commit-Einträge zu einem einzigen, zusammenhängenden Commit zusammengefasst.
Werde Dateningenieur
Nehmen wir zum Beispiel an, dass wir an einem Feature arbeiten, das ein Anmeldeformular implementiert, und wir erstellen die folgenden vier Commits:
Sobald das Feature fertiggestellt ist, sind diese Commits für das Gesamtprojekt zu detailliert. Wir müssen in Zukunft nicht wissen, dass wir auf einen Fehler gestoßen sind, der während der Entwicklung behoben wurde. Um einen sauberen Verlauf im Hauptzweig zu gewährleisten, fassen wir diese Commits zu einem einzigen Commit zusammen:
Wie man Commits in Git zerdrückt: Interaktiver Rebase
Die gängigste Methode, um Commits zu zerdrücken, ist ein interaktiver Rebase. Wir starten es mit dem Befehl:
git rebase -i HEAD~<number_of_commits>
Ersetze durch die Anzahl der Commits, die wir zerdrücken wollen.
In unserem Fall haben wir vier Commits, also lautet der Befehl:
git rebase -i HEAD~4
Wenn du diesen Befehl ausführst, wird ein interaktiver Befehlszeileneditor geöffnet:
Im oberen Bereich werden die Commits angezeigt, während der untere Bereich Kommentare dazu enthält, wie man Commits zerdrücken kann.
Wir haben vier Zusagen. Für jeden müssen wir entscheiden, welcher Befehl ausgeführt werden soll. Wir kümmern uns um die Befehle pick
(p
) und squash
(s
). Um diese vier Commits in einen einzigen Commit zusammenzufassen, können wir den ersten Commit auswählen und die restlichen drei Commits entfernen.
Wir wenden die Befehle an, indem wir den Text vor jedem Commit ändern, insbesondere ändern wir pick
in s
oder squash
für den zweiten, dritten und vierten Commit. Um diese Änderungen vorzunehmen, müssen wir den "INSERT"-Modus im Befehlszeilen-Texteditor aufrufen, indem wir die Taste i
auf der Tastatur drücken:
Nachdem du i
gedrückt hast, erscheint unten der Text -- INSERT --
, der anzeigt, dass wir in den Einfügemodus gewechselt haben. Jetzt können wir den Cursor mit den Pfeiltasten bewegen, Zeichen löschen und wie in einem normalen Texteditor schreiben:
Wenn wir mit den Änderungen zufrieden sind, müssen wir den Einfügemodus beenden, indem wir die Taste Esc
auf der Tastatur drücken. Im nächsten Schritt speichern wir unsere Änderungen und verlassen den Editor. Dazu drücken wir zuerst die Taste :
, um dem Editor zu signalisieren, dass wir einen Befehl ausführen wollen:
Am unteren Rand des Editors sehen wir jetzt ein Semikolon :
, das uns auffordert, einen Befehl einzufügen. Um die Änderungen zu speichern, verwenden wir den Befehl w
, der für "schreiben" steht. Um den Editor zu schließen, benutze q
, was für "quit" steht. Diese Befehle können kombiniert und zusammen getippt werden wq
:
Um den Befehl auszuführen, drücken wir die Taste Enter
. Mit dieser Aktion wird der aktuelle Editor geschlossen und ein neuer Editor geöffnet, in den wir die Commit-Nachricht für den neu gequetschten Commit eingeben können. Der Editor zeigt eine Standardmeldung an, die die Meldungen der vier Commits enthält, die wir zerdrücken:
Ich empfehle, die Nachricht so zu ändern, dass sie genau die Änderungen widerspiegelt, die durch diese kombinierten Commits implementiert wurden - schließlich ist es der Zweck des Squashings, eine saubere und leicht lesbare Geschichte zu erhalten.
Um mit dem Editor zu interagieren und die Nachricht zu bearbeiten, drücken wir erneut i
, um in den Bearbeitungsmodus zu gelangen und die Nachricht nach unseren Wünschen zu bearbeiten.
In diesem Fall ersetzen wir die Commit-Meldung durch "Implementiere das Anmeldeformular". Um den Bearbeitungsmodus zu verlassen, drücken wir Esc
. Dann speicherst du die Änderungen, indem du :
drückst, den Befehl wq
eingibst und Enter
drückst.
Wie man den Commit-Verlauf anzeigt
Im Allgemeinen kann es schwierig sein, die gesamte Commit-Historie abzurufen. Um die Commit-Historie einzusehen, können wir den Befehl git log
verwenden. In dem genannten Beispiel würde vor dem Squash die Ausführung des Befehls git log
angezeigt:
Um in der Liste der Commits zu navigieren, benutze die Pfeiltasten nach oben und unten. Um zu beenden, drücke q
.
Wir können git log
nutzen, um den Erfolg des Squash zu bestätigen. Wenn du sie nach dem Squash ausführst, wird ein einzelner Commit mit der neuen Nachricht angezeigt:
Gequetschte Übertragung verschieben
Der obige Befehl wird auf das lokale Repository angewendet. Um das Remote-Repository zu aktualisieren, müssen wir unsere Änderungen pushen. Da wir jedoch den Commit-Verlauf geändert haben, müssen wir den Push mit der Option --force
erzwingen:
git push --force origin feature/login-form
Ein erzwungener Push überschreibt die Commit-Historie auf dem entfernten Zweig und stört möglicherweise andere, die an diesem Zweig arbeiten. Es ist eine gute Praxis, mit dem Team zu kommunizieren, bevor du das tust.
Eine sicherere Methode, Push zu erzwingen, die das Risiko einer Störung von Mitwirkenden verringert, ist die Option --force-with-lease
zu verwenden:
git push --force-with-lease origin feature/login-form
Diese Option stellt sicher, dass wir den Push nur dann erzwingen, wenn der entfernte Zweig seit unserem letzten Fetch oder Pull nicht aktualisiert wurde.
Bestimmte Commits unterdrücken
Stell dir vor, wir haben fünf Zusagen:
Angenommen, wir wollen die Commits 1, 2 und 5 behalten und die Commits 3 und 4 löschen.
Wenn du interaktives Rebase verwendest, werden Commits, die zum Squashing markiert sind, mit dem direkt vorangehenden Commit kombiniert. In diesem Fall bedeutet das, dass wir Commit4
so zerquetschen wollen, dass es in Commit3
aufgeht.
Dazu müssen wir einen interaktiven Rebase initiieren, der diese beiden Commits einschließt. In diesem Fall reichen drei Commits aus, also verwenden wir den Befehl:
git rebase -i HEAD~3
Dann setzen wir Commit4
auf s
, so dass es mit Commit3
gequetscht wird:
Nachdem wir diesen Befehl ausgeführt und die Commits aufgelistet haben, stellen wir fest, dass die Commits 3 und 4 zusammengelegt wurden, während der Rest unverändert bleibt.
Squashing von einem bestimmten Commit
Im Befehl git rebase -i HEAD~3
ist der Teil HEAD
eine Abkürzung für die letzte Übertragung. Die ~3
Syntax wird verwendet, um einen Vorgänger eines Commits anzugeben. Zum Beispiel bezieht sich HEAD~1
auf die übergeordnete Seite des HEAD
Commits.
Bei einem interaktiven Rebase werden alle Vorgänger-Commits bis zu dem im Befehl angegebenen Commit berücksichtigt. Beachte, dass der angegebene Commit nicht enthalten ist:
Anstatt HEAD zu verwenden, können wir auch direkt einen Commit-Hash angeben. Zum Beispiel hat Commit2
einen Hash von dbf3cc118d6d7c08ef9c4a326b26dbb1e3fe9ddf
, so dass der Befehl:
git rebase -i dbf3cc118d6d7c08ef9c4a326b26dbb1e3fe9ddf
würde einen Rebase starten, der alle Commits berücksichtigt, die nach Commit2
gemacht wurden. Wenn wir also einen Rebase bei einem bestimmten Commit starten und diesen Commit einbeziehen wollen, können wir den Befehl verwenden:
git rebase -i <commit-hash>~1
Auflösen von Konflikten beim Squashing von Commits
Wenn wir Commits zerdrücken, fassen wir mehrere Änderungen in einem einzigen Commit zusammen, was zu Konflikten führen kann, wenn sich die Änderungen überschneiden oder stark voneinander abweichen. Hier sind einige häufige Szenarien, in denen Konflikte entstehen können:
- Überschneidende Änderungen: Wenn zwei oder mehr Commits, die gequetscht werden sollen, dieselben Zeilen einer Datei oder eng miteinander verbundene Zeilen geändert haben, kann Git diese Änderungen möglicherweise nicht automatisch abgleichen.
- Unterschiedliche Zustandsänderungen: Wenn ein Commit ein bestimmtes Stück Code hinzufügt und ein anderer Commit dasselbe Stück Code ändert oder löscht, kann das Zerdrücken dieser Commits zu Konflikten führen, die gelöst werden müssen.
- Umbenennen und Ändern von: Wenn ein Commit eine Datei umbenennt und nachfolgende Commits Änderungen an dem alten Namen vornehmen, kann das Zerdrücken dieser Commits Git verwirren und einen Konflikt verursachen.
- Änderungen an Binärdateien: Binärdateien lassen sich mit textbasierten Diff-Tools nicht gut zusammenführen. Wenn mehrere Commits dieselbe Binärdatei ändern und wir versuchen, sie zu zerdrücken, kann ein Konflikt entstehen, weil Git diese Änderungen nicht automatisch abgleichen kann.
- Komplexe Geschichte: Wenn die Commits eine komplexe Historie mit mehreren Merges, Branches oder Rebases haben, kann das Zerdrücken der Commits zu Konflikten führen, da die Änderungen nicht linear verlaufen.
Beim Squashing versucht Git, jede Änderung nacheinander anzuwenden. Wenn es während des Prozesses auf Konflikte stößt, hält es an und ermöglicht uns, diese zu lösen.
Konflikte werden mit den Konfliktmarkern <<<<<<
und >>>>>>
gekennzeichnet. Um mit den Konflikten umzugehen, müssen wir die Dateien öffnen und sie manuell auflösen, indem wir auswählen, welchen Teil des Codes wir behalten wollen.
Nachdem wir die Konflikte gelöst haben, müssen wir die gelösten Dateien mit dem Befehl git add
bereitstellen. Dann können wir das Rebase mit dem folgenden Befehl fortsetzen:
git rebase --continue
Weitere Informationen zu Git-Konflikten findest du in diesem Tutorial über wie man Merge-Konflikte in Git auflöst.
Alternativen zum Squashing mit Rebase
Der Befehl git merge --squash
ist eine alternative Methode zu git rebase -i
, um mehrere Commits zu einem einzigen Commit zusammenzufassen. Dieser Befehl ist besonders nützlich, wenn wir Änderungen aus einem Zweig in den Hauptzweig zusammenführen und dabei alle einzelnen Commits in einen einzigen zusammenfassen wollen. Hier ist ein Überblick darüber, wie du mit git merge
squashen kannst:
- Wir navigieren zu dem Zielzweig, in den wir die Änderungen einfügen wollen.
- Wir führen den Befehl
git merge --squash
aus und ersetzendurch den Namen des Zweigs.
- Wir übertragen die Änderungen mit
git commit
, um einen einzigen Commit zu erstellen, der alle Änderungen des Feature-Branches enthält.
Nehmen wir zum Beispiel an, wir wollen die Änderungen des Zweigs feature/login-form
in main
als einen einzigen Commit einbinden:
git checkout main
git merge --squash feature-branch
git commit -m "Implement login form"
Dies sind die Einschränkungen dieses Ansatzes im Vergleich zu git rebase -i
:
- Granularität der Kontrolle: Weniger Kontrolle über einzelne Commits. Mit rebase können wir auswählen, welche Commits zusammengeführt werden sollen, während merge alle Änderungen in einem Commit zusammenfasst.
- Zwischenzeitliche Geschichte: Wenn du die Zusammenführung verwendest, geht die individuelle Commit-Historie aus dem Feature-Zweig im Hauptzweig verloren. Das kann es schwieriger machen, die inkrementellen Änderungen zu verfolgen, die während der Entwicklung des Features vorgenommen wurden.
- Überprüfung vor der Mittelbindung: Da alle Änderungen als ein einziger Änderungssatz aufgeführt werden, können wir nicht jeden Commit einzeln überprüfen oder testen, bevor wir ihn zerdrücken. Dies ist anders als bei einem interaktiven Rebase, bei dem jeder Commit nacheinander überprüft und getestet werden kann.
Fazit
Häufige und kleine Commits in den Entwicklungsworkflow einzubauen, fördert die Zusammenarbeit und eine klare Dokumentation, kann aber auch die Projekthistorie durcheinander bringen. Das Squashing von Commits schafft ein Gleichgewicht, indem es die wichtigen Meilensteine bewahrt und gleichzeitig den Lärm kleinerer iterativer Änderungen eliminiert.
Um mehr über Git zu erfahren, empfehle ich diese Ressourcen:
Lerne Data Engineering mit diesen Kursen!
Lernpfad
Professioneller Dateningenieur
Kurs