Git Fixup: Wie repariere ich meine Historie?

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.

In diesem Artikel:

sg
Sebastian Gingter ist Consultant und “Erklärbär” bei Thinktecture. Seine Spezialgebiete sind Backend-Systeme mit .NET Core und IdentityServer.

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:

				
					 // Vom Main-Branch ableitender Feature-Branch
          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:

				
					 // weitere Commits in den Feature-Branch
          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:

				
					 // Feature-Branch nach dem Aufräumen
          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.

 

Kostenloser
Newsletter

Aktuelle Artikel, Screencasts, Webinare und Interviews unserer Experten für Sie

Verpassen Sie keine Inhalte zu Angular, .NET Core, Blazor, Azure und Kubernetes und melden Sie sich zu unserem kostenlosen monatlichen Dev-Newsletter an.

Newsletter Anmeldung
Diese Artikel könnten Sie interessieren
Tools
YB_300x300

Rebase Onto – When Dropping Commits Makes Sense: Git In Practice – Part 3

In the previous articles of this series, 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.
26.01.2021
Tools
YB_300x300

Interactive Rebase: Git In Practice – Part 2

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. In an interactive rebase, we have a lot more control over what we want to do with the affected commits. Some of the options include re-ordering the commits, rewording a commit message, dropping commits, merging several commits into a single one, and editing a past commit.
11.12.2020
Essentials
YB_300x300

Code Quality: Automate Linting, Formatting And More By Sharing Git Hooks

There he is. Bob. The new guy in the office. Time to on-board him onto the flagship project of the company. Sounds like a job for Kevin. Kevin helps out Bob to get setup. Providing him with the appropriate access rights, cloning the repository, and making sure Bob's seat is nice and comfy. After Bob has the project up and running, it is his time to shine and work on the first ticket. He fires up his IDE, touches a couple of files, resolves the issues, commits, pushes, and opens up a merge request for Kevin to review.
25.11.2020
Tools
YB_300x300

Demystifying Git Rebase: Git in Practice – Part 1

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.
12.11.2020
API
favicon

Fiddler In Action: Mocking And Manipulating API Behavior With A Local Proxy Server – Part 5

In this five-part article series, you learn how to manipulate your API behavior with a local proxy server. So far, this series has covered an introduction, taught you how to set up and configure Charles Proxy, and you have seen it in action. After presenting the Fiddler setup, we will now take a look at it in action.
20.10.2020
Tools
favicon

Fiddler Setup & Configuration: Mocking And Manipulating API Behavior With A Local Proxy Server – Part 4

In this five-part article series, you will learn how to manipulate your API behavior with a local proxy server. After an introduction, learning how to set up and configure Charles Proxy, and seeing it in action, we are now taking a look at the Fiddler setup and configuration.
29.09.2020