In diesem Artikel

Git gibt uns mit der fixup-Option eine einfache aber effektive Möglichkeit, kleine Versäumnisse nachträglich so zu korrigieren, dass sie gar nicht mehr auffallen. Wie das genau geht, wollen wir in diesem Artikel erforschen.

Mein Kollege Yannick Baron hat in seiner englischen Artikelserie zu Git bereits den interactive Rebase behandelt. Dort hat er auch die Möglichkeiten des fixup kurz angesprochen. Hier wollen wir uns etwas intensiver mit diesem Feature befassen.

Begleitender Screencast: Wie funktioniert der Git Fixup-Workflow? (Demo)

Eine saubere Historie

Der Fixup-Workflow dient in erster Linie dazu, die Git-Historie sauber zu halten. Yannick hat bereits ausgeführt, das eine saubere Historie zum einen viel zum Verständnis dazu beiträgt, warum der Code mit dem man gerade arbeitet so aussieht, wie er es tut. Zum anderen erlaubt das aber auch kleine Fixes "mal kurz" in einen anderen Branch zu ziehen, wenn wir das gerade benötigen.

Es wäre ungeschickt, wenn wir statt einem Commit am Ende vier bis fünf verschiedene Commits, schlimmstenfalls noch nicht einmal Zusammenhängende, cherry-picken müssten, weil zwei nachträgliche Commits noch Tippfehler und zwei weitere noch Bugs fixen, obwohl alles im gleichen Branch entstanden ist.

Git erlaubt uns daher sehr einfach, beim commiten mit der --fixup-Option solche Korrekturen nachträglich zu machen, und dann mit einem einfachen weiteren Arbeitsschritt, dem interaktiven Rebase mit der --autosquash-Option, diese Korrekturen an die richtige Stelle in der Historie zu schieben. Damit können wir sehr elegant eine saubere Historie erreichen, die unserem Team und uns selber die zukünftige Arbeit deutlich vereinfacht.

Das Versäumnis

Stellen wir uns vor, wir arbeiten an einem neuen E-Mail-Feature einer Benutzerverwaltung:

          A---B---C (feature)
         /
X---Y---Z (main)

In Commit A haben wir eine Konfigurationsklasse für die Mail-Settings hinzugefügt, die Mail-Sender-Klasse in B und in Commit C verwenden wir nun diese beiden neuen Klassen um bei einem Passwort-Reset auch wirklich eine E-Mail zu verschicken. Während des Testens fällt uns nun auf, dass sich in unserem Sender (Commit B) ein kleiner Fehler eingeschlichen hat, den wir nun reparieren müssen.

Wir könnten nun einfach den Fehler beheben und den Fix als D commiten, was zu folgender Historie führen würde:

d0297c fix sender and receiver mixup in email sender
c028f4 add mailing functionality for password reset
b032c2 add email sender class
a0f034 add configuration options for email

Hinweis: Die Commit-Hashes in diesem Beispiel sind nicht echt und beginnen zur leichteren Identifizierung immer passend zu den im Text erwähnten Commits.

Wohlgemerkt: Wir befinden uns noch bei der ersten Implementation des Features. Für unser Zukunfts-Ich ist es nicht wirklich relevant zu wissen, das wir gerade die Sender- und Empfänger Parameter eines Funktionsaufrufes verwechselt hatten. Schöner wäre es, wenn wir den Fix gleich in B "hineinschmuggeln" könnten, so dass die Datei sofort fehlerfrei hinzugefügt wurde.

Die Reparatur

Wie Yannick in seinem Artikel erklärt hat, können wir dies mit einem interaktive Rebase selbstverständlich auch nachträglich noch machen: Wir können die Commits umsortieren und squashen. Das können wir jetzt gleich machen, oder uns für später merken, das D eigentlich ein Fix für B ist. Die erste Möglichkeit reißt uns allerdings etwas aus unserem Workflow heraus (wir denken gerade ans Mail-Senden und nicht ans Rebasen) und die zweite Option erfordert ein zuverlässiges Gedächtnis.

Genau hier kommt der Fixup-Workflow zur Hilfe: Wir brauchen uns nicht jetzt sofort um den Rebase kümmern, sondern können weiter an unserem Feature arbeiten. Und anstelle D einfach so zu commiten und dann trotz unseres Elefanten-Gedächtnisses später vielleicht doch noch zu vergessen, dass diese Änderungen eigentlich noch in B hinein sollte, können wir Git schon jetzt beim Commit sagen, dass es diesen Commit später in B "hineinschmuggeln" soll, und zwar mit folgendem Befehl:

git commit --fixup <COMMIT HASH von B>

In unserem Beispielfall also: git commit --fixup b032c2.

Die Historie schaut dann - für den Moment - folgendermassen aus:

d01fd6 fixup! add email sender class
c028f4 add mailing functionality for password reset
b032c2 add email sender class
a0f034 add configuration options for email

Stellen wir uns vor, wir haben noch ein paar weitere Commits: Wir senden auch für die Benutzerregistrierung E-Mails (Commit E) und dann stellen wir fest, das wir eine Einstellung vergessen haben die wir auf der Konfigurationsklasse hinzufügen (Commit F) und in der Sender-Klasse verwenden müssen (Commit 0), so dass wir am Ende folgende Historie haben:

          A---B---C---D---E---F---0 (feature)
         /
X---Y---Z (main)

Unser Git Log zeigt folgendes an:

001a8e fixup! add email sender class
f0c72f fixup! add configuration options for email
e08dd7 add mailing functionality for user registration
d01fd6 fixup! add email sender class
c028f4 add mailing functionality for password reset
b032c2 add email sender class
a0f034 add configuration options for email

Das Aufräumen

Bevor wir unseren Feature-Branch jetzt pushen, wollen wir die Historie aufräumen. Und da diese Commits auch mit fixup! im Log stehen erinnern wir uns daran, so dass wir den folgenden Schritt auch nicht vergessen.

Mit einem herkömmlichen interaktiven Rebase müssten wir jetzt die Commits von Hand umsortieren und zusammenfassen. Dank unserer --fixup Commits kann Git uns dies alles jetzt allerdings schon ganz automatisch vorschlagen. Der Befehl sieht voll ausgeschrieben so aus:

git rebase --interactive --autosquash <base> [<branch>]

Base ist in unserem Beispiel der main-Branch. Unseren feature-branch müssen wir nicht explizit angeben weil wir gerade darauf arbeiten und ihn bereits ausgecheckt haben. Da wir die Interactive-Option auch abkürzen können und nicht voll ausschreiben müssen reicht es aus, nur folgendes zu tippen:

git rebase -i --autosquash main

In diesem interaktiven Rebase wird uns jetzt automatisch folgendes präsentiert:

pick a0f034 add configuration options for email
fixup f0c72f fixup! add configuration options for email
pick b032c2 add email sender class
fixup d01fd6 fixup! add email sender class
fixup 001a8e fixup! add email sender class
pick c028f4 add mailing functionality for password reset
pick e08dd7 add mailing functionality for user registration

Wenn dies so ausgeführt wird, sieht unsere Historie danach folgendermaßen aus:

          A'---B'---C'---E' (feature)
         /
X---Y---Z (main)
e18dd7 add mailing functionality for user registration
c128f4 add mailing functionality for password reset
b132c2 add email sender class
a1f034 add configuration options for email

Das Ergebnis ist eine sehr präzise und lesbare Historie. Unser Team und unser Zukunfts-ich wissen später genau was passiert ist, ohne das die uninteressanten Versäumnisse und deren Reparaturen vom eigentlich Feature ablenken würden.

Fazit

In diesem Artikel habe ich Euch den Fixup-Workflow von Git erklärt: Mit git commit --fixup <HASH> können wir Git sagen, dass die aktuellen Änderungen in einen anderen, durch den Hash spezifizierten Commit gehören. Am Ende lassen wir uns mit git rebase --interactive --autosquash <base> einen Vorschlag erarbeiten, wie die Commits in unserem Branch automatisch umsortiert und zusammengefasst werden sollen, und wenden diesen an.

Als Ergebnis haben wir eine saubere Historie, die nicht mehr darauf schließen lässt dass zwischenzeitlich ein paar Fehler da waren.

Wenn Sie auch weitere Artikel, Webinare und Screencasts unserer Experten nicht verpassen möchten, melden Sie sich hier zu unserem kostenlosen, monatlichen Dev-Newsletter an.

Related Articles

 | Yannick Baron

In a previous article, I showed you some of the capabilities of interactive rebase. I mentioned that you can also drop commits when rebasing. In this article, I would like to demonstrate scenarios in which that makes sense and a short-cut to achieve what we want. Article series…

Read article
 | Yannick Baron

Once you are comfortable with rebase, it is time to step up your game with interactive rebase. As introduced in the previous article, rebasing involves taking multiple commits and applying them to a base tip. Article Series Demystifying git Rebase Interactive Rebase Rebase Onto…

Read article
 | Yannick Baron

Working with git every day as our software of choice for version control, I always try to point out that familiarity with our toolset is important. The more comfortable you are with git, the easier it will be to integrate changes and follow their history. One highly debated…

Read article