Git-Commits sind einer der Eckpfeiler eines Git-Repositorys. Die Commit-Nachrichten sind wie ein Lebensprotokoll für das Repository. Während sich das Projekt/Repository im Laufe der Zeit entwickelt – sei es durch das Hinzufügen neuer Funktionen, das Beheben von Fehlern oder die Überarbeitung der Architektur – geben die Commit-Nachrichten Aufschluss darüber, was genau geändert wurde. Daher ist es entscheidend, dass diese Nachrichten die zugrunde liegende Änderung präzise und kurz wiedergeben. Die Commit-Historie kann leicht verfälscht werden. In diesem Artikel erfährst du, wie du sie korrigieren kannst!
Warum eine aussagekräftige Git-Commit-Historie wichtig ist
Was bewirkt ein Git-Commit? Git-Commit-Nachrichten sind die Fingerabdrücke, die du auf dem Code hinterlässt, den du bearbeitest. Wenn du heute einen Code festlegst, ist es wichtig, eine klare und aussagekräftige Commit-Nachricht zu schreiben, damit du diese auch später noch nachvollziehen kannst. Indem Git-Commits kontextabhängig isoliert werden, ist ein Fehler, der durch einen einzelnen Commit verursacht wurde, schneller zu finden. Zudem ist es einfacher, den Commit rückgängig zu machen, der den Fehler verursacht hat.
Bei der Arbeit an großen Projekten haben wir oft mit vielen verschiedenen Komponenten, die aktualisiert, hinzugefügt oder entfernt werden, zu tun. In solchen Fällen kann es schwierig sein, die Commit-Nachrichten zu pflegen, insbesondere wenn sich die Entwicklung über Tage, Wochen oder sogar Monate erstreckt. Um die Wartung eines übersichtlichen Commit-Verlaufs zu erleichtern, findest du nachfolgend die vier häufigsten Situationen, mit denen ein Entwickler(innen) bei der Arbeit an einem Git-Repository konfrontiert werden kann.
- Situation 1: Ich muss den letzten Commit ändern
- Situation 2: Ich muss einen bestimmten Commit ändern
- Situation 3: Ich muss Commits hinzufügen, entfernen oder kombinieren
- Situation 4: Mein Commit-Verlauf macht keinen Sinn, ich muss nochmal von vorne anfangen
Bevor wir jedoch tiefer eintauchen, werfen wir einen kurzen Blick auf einen typischen Entwicklungsablauf in unserer hypothetischen Ruby-Anwendung.
Hinweis: In diesem Artikel wird vorausgesetzt, dass du mit den Grundlagen von Git vertraut bist und weißt, wie Branches funktionieren, wie nicht übertragene Änderungen eines Branches zum Staging-Bereich hinzugefügt und wie Änderungen übertragen werden. Wenn du unsicher bist, bietet unsere Dokumentation einen guten Ausgangspunkt.
Beispielprojekt: Neue Navigationsansicht
Nachfolgend siehst du ein Ruby-on-Rails-Projekt, in dem eine Navigationsansicht auf der Homepage hinzugefügt werden muss. Dazu müssen mehrere Dateien aktualisiert und hinzugefügt werden. Im Folgenden findest du eine schrittweise Aufschlüsselung des gesamten Ablaufs:
- Du startest die Arbeit an einem Feature, indem du eine einzelne Datei aktualisierst. Zum Beispiel:
application_controller.rb
- Für dieses Feature musst du auch eine Ansicht aktualisieren:
index.html.haml
- Du hast einen Teilbereich hinzugefügt, der auf der Indexseite verwendet wird:
_navigation.html.haml
- Die Formatvorlagen für die Seite müssen ebenfalls aktualisiert werden, um den hinzugefügten Teil widerzuspiegeln:
styles.css.scss
- Das Feature ist nun mit den gewünschten Änderungen fertiggestellt. Jetzt musst du die Tests aktualisieren. Dazu gehören die folgenden Dateien:
application_controller_spec.rb
navigation_spec.rb
- Die Tests wurden aktualisiert und laufen wie erwartet. Jetzt werden die Änderungen übertragen.
Da alle Dateien zu verschiedenen Bereichen der Architektur gehören, werden die Änderungen isoliert voneinander übertragen. Dies gewährleistet, dass jede Übertragung einen spezifischen Kontext repräsentiert und in einer bestimmten Reihenfolge durchgeführt wird. Im Allgemeinen wird die Reihenfolge von Backend -> Frontend bevorzugt. Dies bedeutet, dass die meisten Backend-bezogenen Änderungen zuerst übertragen werden, gefolgt von der mittleren Schicht und dann von den Frontend-bezogenen Änderungen in den Git-Commits.
application_controller.rb
&application_controller_spec.rb
: Hinzufügen von Routen für die Navigation._navigation.html.haml
&navigation_spec.rb
: Ansicht der Seitennavigation.index.html.haml
: Navigation teilweise rendern.styles.css.scss
: Stile für die Navigation hinzufügen.
Nachdem die Änderungen übertragen wurden, wird eine Anfrage zur Zusammenführung mit dem Branch erstellt. Sobald du eine Merge-Anfrage geöffnet hast, wird sie in der Regel von deinem Peer überprüft, bevor die Änderungen im Master-Branch des Repositories zusammengeführt werden. Im Folgenden werden die verschiedenen Situationen beschrieben, die bei der Codeüberprüfung auftreten können.
Situation 1: Ändern des letzten Git-Commits
Im Fall, dass der Prüfer die Datei styles.css.scss
überprüft und eine Änderung vorgeschlagen hat, ist es recht einfach, die Änderung vorzunehmen, da die Stylesheet-Änderungen Teil des letzten Commits in deinem Branch sind. So kannst du damit umgehen:
- Führe die erforderlichen Änderungen an
styles.css.scss
direkt in deinem aktuellen Branch durch. - Sobald du mit den Änderungen fertig bist, füge sie zum Staging-Bereich hinzu, indem du
git add styles.css.scss
ausführst. - Nachdem die Änderungen bereitgestellt wurden, füge sie zu deinem letzten Commit hinzu, indem du
git commit --amend
ausführst.- Aufschlüsselung des Befehls: Mit dem
git commit
-Befehl werden alle Änderungen, die sich im Staging-Bereich befinden, dem letzten Commit hinzugefügt.
- Aufschlüsselung des Befehls: Mit dem
- Dadurch wird der von Git definierte Texteditor geöffnet, der die Commit-Nachricht „Stile für die Navigation hinzufügen” enthält, die bereits beim vorherigen Commit festgelegt wurde.
- Da nur die CSS-Deklaration aktualisiert wurde, muss die Commit-Nachricht nicht geändert werden. An dieser Stelle kannst du einfach speichern und den Texteditor, den Git für dich geöffnet hat, beenden. Deine Änderungen werden dann in den Commit übernommen.
Da du ein bestehendes Git-Commit geändert hast, müssen diese Änderungen zwangsweise in dein Repository übertragen werden. Dazu nutzt du git push --force-with-lease <remote_name> <branch_name>
. Dieser Befehl überschreibt das Commit Add styles for navigation
im entfernten Repository mit dem aktualisierten Commit, das gerade im lokalen Repository vorgenommen wurde.
Wenn mehrere Personen an einem Branch arbeiten, kann ein erzwungener Push von Branches dazu führen, dass andere Benutzer Probleme bekommen, wenn sie versuchen, ihre Änderungen auf einen entfernten Branch zu pushen, in dem bereits neue Commits gepusht wurden. Daher sollte diese Funktion mit Bedacht eingesetzt werden. Weitere Informationen zu den Force-Push-Optionen von Git findest du hier.
Situation 2: Ändern einer bestimmten Git-Commit-Änderung
In der vorherigen Situation war die Änderung des Git-Commits recht einfach, da nur der letzte Git-Commit geändert werden musste. Stell dir jedoch vor, ein Prüfer würde vorschlagen, etwas in _navigation.html.haml
zu ändern. In diesem Fall handelt es sich um den zweiten Commit von oben, sodass die Änderung nicht so direkt ist wie in der ersten Situation.
Jeder Commit in einem Branch wird durch eine eindeutige SHA-1-Hash-Zeichenkette identifiziert. Diese dient als eine Art eindeutige ID, die einen Commit von einem anderen unterscheidet. Du kannst alle vorherigen Commits zusammen mit ihren SHA-1-Hashes in einem Branch anzeigen, indem du den Befehl git log
ausführst. Das Ergebnis ist eine Liste von Commits, wobei die neuesten Commits ganz oben stehen.
commit aa0a35a867ed2094da60042062e8f3d6000e3952 (HEAD -> add-page-navigation)
Author: Kushal Pandya <[email protected]>
Date: Wed May 2 15:24:02 2018 +0530
Add styles for navigation
commit c22a3fa0c5cdc175f2b8232b9704079d27c619d0
Author: Kushal Pandya <[email protected]>
Date: Wed May 2 08:42:52 2018 +0000
Render navigation partial
commit 4155df1cdc7be01c98b0773497ff65c22ba1549f
Author: Kushal Pandya <[email protected]>
Date: Wed May 2 08:42:51 2018 +0000
Page Navigation View
commit 8d74af102941aa0b51e1a35b8ad731284e4b5a20
Author: Kushal Pandya <[email protected]>
Date: Wed May 2 08:12:20 2018 +0000
Add routes for navigation
An dieser Stelle kommt der Befehl git rebase
ins Spiel. Wenn wir einen bestimmten Commit mit git rebase
bearbeiten wollen, müssen wir zunächst unseren Branch neu erstellen, indem wir HEAD bis zu dem Punkt vor dem Commit zurücksetzen, den wir bearbeiten wollen. In unserem Fall müssen wir den Commit ändern, der Page Navigation View
lautet.
- Achte auf den Hash des Commits, der direkt vor dem Commit liegt, den wir ändern möchten. Kopiere den Hash und führe die folgenden Schritte aus:
- Verschiebe den Branch auf einen Commit vor unserem Ziel-Commit; führe
git rebase -i8d74af102941aa0b51e1a35b8ad731284e4b5a20
aus.- Aufschlüsselung der Git-Befehle: Hier führen wir den Git-Befehl
rebase
im interaktiven Modus aus und geben einen SHA-1-Hash als Commit an, auf denrebase
erfolgen soll.
- Aufschlüsselung der Git-Befehle: Hier führen wir den Git-Befehl
- Dieser Befehl führt den rebase-Befehl für Git im interaktiven Modus aus und öffnet den Texteditor, der alle Commits anzeigt, die auf den Commit folgen, auf den du den
rebase
durchführen möchtest. Der Texteditor sollte in etwa so aussehen:
pick 4155df1cdc7 Page Navigation View
pick c22a3fa0c5c Render navigation partial
pick aa0a35a867e Add styles for navigation
# Rebase 8d74af10294..aa0a35a867e onto 8d74af10294 (3 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove Git commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out
Beachte, dass jedem Commit das Wort pick
vorangestellt ist. Im Inhalt unten sind alle möglichen Schlüsselwörter aufgeführt, die du verwenden kannst. Da du eine Übertragung bearbeiten möchtest, musst du pick 4155df1cdc7 Page Navigation View in edit 4155df1cdc7 Page Navigation View
ändern. Speichere die Änderungen und verlasse den Editor.
Der Branch wird nun auf den Zeitpunkt vor der Commit-Übergabe zurückgesetzt, die die Datei _navigation.html.haml
enthielt. Öffne die Datei und führe die gewünschten Änderungen gemäß dem Feedback der Überprüfung durch. Sobald du mit den Änderungen fertig bist, füge sie zum Staging-Bereich hinzu, indem du git add _navigation.html.haml
ausführst.
Nachdem die Änderungen bereitgestellt wurden, ist es an der Zeit, den Branch HEAD wieder auf den ursprünglichen Commit zurückzusetzen, wobei auch die neu hinzugefügten Änderungen berücksichtigt werden. Führe git rebase --continue
aus. Dadurch wird dein Standardeditor im Terminal geöffnet und zeigt die Commit-Nachricht an, die während des Rebase bearbeitet wurde; in diesem Fall Page Navigation View
. Du kannst diese Nachricht ändern, wenn du möchtest – zunächst bleibt sie jedoch unverändert. Speichere und beende den Editor.
Jetzt zeigt Git alle Commits an, die auf den Commit folgten, den du gerade bearbeitet hast. Der Branch HEAD
ist jetzt wieder auf dem ursprünglichen obersten Commit. Er enthält auch die neuen Änderungen, die du an einem der Commits vorgenommen hast.
Da du erneut einen Commit geändert hast, der bereits im entfernten Repository vorhanden ist, musst du diesen Branch noch einmal mit git push --force-with-lease <remote_name> <branch_name>
pushen.
Situation 3: Hinzufügen, Entfernen oder Kombinieren von Git-Commits
Es kommt häufig vor, dass mehrere Commits gemacht wurden, nur um etwas zu korrigieren, das bereits zuvor committed wurde. Jetzt möchtest du diese Commits so weit wie möglich reduzieren und mit den ursprünglichen Commits kombinieren.
Dazu musst du einfach den interaktiven Rebase wie in den anderen Szenarien starten.
pick 4155df1cdc7 Page Navigation View
pick c22a3fa0c5c Render navigation partial
pick aa0a35a867e Add styles for navigation
pick 62e858a322 Fix a typo
pick 5c25eb48c8 Ops another fix
pick 7f0718efe9 Fix 2
pick f0ffc19ef7 Argh Another fix!
Nun stell dir vor, du möchtest all diese Korrekturen in c22a3fa0c5c Render navigation partial
kombinieren. Dazu musst du nur Folgendes tun:
- Verschiebe die Korrekturen nach oben, sodass sie sich direkt unter der Commit-Übergabe befinden, die du am Ende behalten möchtest.
- Ändere
pick
aufsquash
oderfixup
für jede der Korrekturen.
Hinweis: squash
behält die Commit-Nachrichten der Git-Fixes in der Beschreibung bei. fixup
vergisst die Commit-Nachrichten der Fixes und behält das Original bei.
Das Ergebnis sieht dann etwa so aus:
pick 4155df1cdc7 Page Navigation View
pick c22a3fa0c5c Render navigation partial
fixup 62e858a322 Fix a typo
fixup 5c25eb48c8 Ops another fix
fixup 7f0718efe9 Fix 2
fixup f0ffc19ef7 Argh Another fix!
pick aa0a35a867e Add styles for navigation
Speichere die Änderungen, beende den Editor und schon bist du fertig! Dies ist der resultierende Verlauf:
pick 4155df1cdc7 Page Navigation View
pick 96373c0bcf Render navigation partial
pick aa0a35a867e Add styles for navigation
Wie zuvor musst du jetzt nur noch git push --force-with-lease <remote_name> <branch_name>
ausführen, damit die Änderungen sichtbar sind.
Wenn du einen Git-Commit vollständig aus dem Branch entfernen möchtest, schreibe statt squash
oder fixup
einfach drop
oder lösche diese Zeile.
Vermeiden von Konflikten bei Git-Commits
Um Konflikte zu vermeiden, solltest du sicherstellen, dass die Commits, die du in der Zeitleiste nach vorne schiebst, nicht dieselben Dateien berühren, die von den Commits nach ihnen bearbeitet werden.
pick 4155df1cdc7 Page Navigation View
pick c22a3fa0c5c Render navigation partial
fixup 62e858a322 Fix a typo # this changes styles.css
fixup 5c25eb48c8 Ops another fix # this changes image/logo.svg
fixup 7f0718efe9 Fix 2 # this changes styles.css
fixup f0ffc19ef7 Argh Another fix! # this changes styles.css
pick aa0a35a867e Add styles for navigation # this changes index.html (no conflict)
Extra-Tipp: Schnelle Git commit fixups
Wenn du genau weißt, welchen Commit du reparieren möchtest, musst du beim Commit keine Zeit damit verschwenden, dir gute temporäre Namen für "Fix 1", "Fix 2", ..., "Fix 42" auszudenken.
Step 1: --fixup
Nachdem du die Änderungen vorgenommen hast, um das zu reparieren, was repariert werden muss, übergibst du einfach alle Änderungen mit Git wie folgt:
git commit --fixup c22a3fa0c5c
(Dies ist der Hash für den Commit c22a3fa0c5c Render navigation partial
)
Dadurch wird diese Commit-Nachricht erzeugt: fixup! Render navigation partial
.
Step 2: --autosquash
Einfaches interaktives Rebase. Du kannst git
die fixups
automatisch an der richtigen Stelle platzieren lassen.
git rebase -i 4155df1cdc7 --autosquash
Der Verlauf wird folgendermaßen dargestellt:
pick 4155df1cdc7 Page Navigation View
pick c22a3fa0c5c Render navigation partial
fixup 62e858a322 Fix a typo
fixup 5c25eb48c8 Ops another fix
fixup 7f0718efe9 Fix 2
fixup f0ffc19ef7 Argh Another fix!
pick aa0a35a867e Add styles for navigation
Du kannst sie einfach überprüfen und fortsetzen.
Falls du experimentierfreudig bist, besteht die Möglichkeit, ein nicht interaktives Rebase mit git rebase --autosquash
durchzuführen. Dies sollte jedoch mit Vorsicht geschehen, da du keine Möglichkeit hast, die Squashs vor ihrer Anwendung zu überprüfen, was potenziell zu unerwarteten Ergebnissen führen kann.
Situation 4: Der Verlauf meiner Git-Commits ergibt keinen Sinn, ich möchte von vorn beginnen!
Wenn wir an einer umfangreichen Funktion arbeiten, ist es üblich, dass wir mehrere Korrekturen und Rückmeldungen vornehmen, die häufig übertragen werden. Anstatt den Branch ständig zu ändern, können wir das Aufräumen der Git-Commits bis zum Ende der Entwicklung aufschieben.
In solchen Fällen erweisen sich Patch-Dateien als äußerst praktisch. Bevor Git-basierte Dienste wie GitLab den Entwickler(inne)n zur Verfügung standen, waren Patch-Dateien die wichtigste Methode, um bei der Zusammenarbeit an großen Open-Source-Projekten Code per E-Mail auszutauschen.
Angenommen, du hast einen solchen Branch (z. B. "add-page-navigation
"), in dem es tonnenweise Commits gibt, die die zugrunde liegenden Änderungen nicht klar vermitteln, dann kannst du folgendermaßen eine Patch-Datei für alle Änderungen erstellen, die du in diesem Branch vorgenommen hast:
- Um eine Patch-Datei zu erstellen, muss zunächst sichergestellt werden, dass der Branch alle Änderungen aus dem
master
-Branch enthält und keine Konflikte damit aufweist. - Um alle Änderungen vom Master-Branch in deinen
add-page-navigation
-Branch zu übernehmen, kannst du entwedergit rebase master
odergit merge master
ausführen, während du imadd-page-navigation-Branch
ausgecheckt bist. - Nun erstellst du die Patch-Datei. Führe
git diff master add-page-navigation > ~/add_page_navigation.patch
aus.- Aufschlüsselung des Befehls: Hier verwenden wir die Diff-Funktion von Git und fordern einen Vergleich zwischen dem
master
-Branch und demadd-page-navigation
-Branch an. Die Ausgabe wird über das ">
"-Symbol in eine Datei namensadd_page_navigation.patch
in unserem Benutzerverzeichnis (typischerweise ~/ in Unix-basierten Betriebssystemen) umgeleitet. - Du kannst einen beliebigen Pfad angeben, in dem die Datei gespeichert werden soll. Der Dateiname und die Erweiterung können beliebig gewählt werden.
- Sobald der Befehl ausgeführt wurde und keine Fehler angezeigt werden, wird die Patch-Datei erstellt.
- Checke jetzt den
master
-Branch aus; führegit checkout master
aus. - Du kannst den Branch
add-page-navigation
aus deinem lokalen Repository löschen, indem dugit branch -D add-page-navigation
ausführst. Denke daran, dass die Änderungen dieses Branchs bereits in einer erstellten Patch-Datei enthalten sind. - Erstelle nun einen neuen Branch mit demselben Namen (während
master
ausgecheckt ist); führegit checkout -b add-page-navigation
aus. - Zu diesem Zeitpunkt ist dies ein neuer Branch, der noch keine der Änderungen enthält.
- Zum Schluss werden die Änderungen aus der Patch-Datei übernommen;
git apply ~/add_page_navigation.patch
. - Hier werden alle Änderungen in einen Branch übernommen und erscheinen als "uncommitted", so, als ob du alle Änderungen vorgenommen hättest, aber keine der Änderungen tatsächlich in den Branch übernommen wurden.
- Jetzt kannst du einzelne Dateien oder nach Einflussbereich gruppierte Dateien in der gewünschten Reihenfolge mit prägnanten Commit-Nachrichten übertragen.
- Aufschlüsselung des Befehls: Hier verwenden wir die Diff-Funktion von Git und fordern einen Vergleich zwischen dem
Wie in vorherigen Situationen wurde der gesamte Branch geändert, also ist es an der Zeit, einen Push zu erzwingen!
Gründe, deine Git-Commit-Historie aufzuräumen
Basierend auf den beschriebenen Situationen, gibt es sechs wichtige Gründe, deine Git-Commit-Historie aufzuräumen:
- Bessere Nachvollziehbarkeit: Eine übersichtliche Commit-Historie erleichtert es Entwickler(innen)n, den Verlauf des Codes zu verstehen und Änderungen nachzuvollziehen.
- Effiziente Fehlerbehebung: Durch klare Commit-Nachrichten und eine strukturierte Historie können Fehler schneller identifiziert und behoben werden.
- Verbesserte Zusammenarbeit: Ein aufgeräumter Commit-Verlauf erleichtert die Zusammenarbeit im Team, da Entwickler(innen)schnell den Kontext und den Zweck früherer Änderungen verstehen können.
- Effizientere Wartung: Weniger Zeit wird mit der Suche nach spezifischen Änderungen und deren Kontext verschwendet, was die Wartung des Codes effizienter macht.
- Verbesserte Codequalität: Eine saubere Commit-Historie fördert bewusstere Entscheidungen beim Committen und trägt so zur Verbesserung der Codequalität bei.
- Gesamtproduktivität steigern: Indem Entwickler(innen) weniger Zeit mit der Suche nach Informationen in der Commit-Historie verbringen, können sie sich besser auf die eigentliche Entwicklung konzentrieren und die Gesamtproduktivität des Teams steigern.
Wenn du dich mit den Tipps vertraut gemacht hast, kannst du in der offiziellen Git-Dokumentation mehr über fortgeschrittene Konzepte zu diesem Thema erfahren. Viel Spaß mit Git!