Skip to main content

Git leicht gemacht: Verlauf mit Rebase ändern – Teil I

Artikel von
Headshot of Or Kamara

Or Kamara

9. Juni 2020

0 Min. Lesezeit

Vor Kurzem habe ich an einem umfangreichen Feature gearbeitet, das mehrere Änderungen in vielen Bereichen des Codes umfasste. Damit der PR für dieses Feature gut lesbar blieb, habe ich die gesamte Funktionalität auf mehrere Commits aufgeteilt. Schon bald merkte ich, dass diese Aufteilung nicht ausreichte: In jedem dieser Commits musste ich noch zahlreiche Änderungen vornehmen – etwa wegen Problemen, die ich gefunden hatte, oder aufgrund von CR-Fixes.

Zu diesem Zeitpunkt kannte ich bereits die Grundlagen des magischen interaktiven git-rebase-Befehls, mit dem sich der Verlauf „umschreiben“ lässt. Die nötigen Änderungen waren jedoch komplexer, als es meine Grundkenntnisse zuließen. Deshalb begann ich, mich näher mit den Optionen dieses Befehls zu beschäftigen.

Falls Sie den Befehl noch gar nicht kennen: So sieht die Ausgabe nach dem Ausführen von git rebase -i BRANCH aus:

pick 271c66e feat: 1st commit
pick 51588eb feat: 2nd commit
pick 4b339f2 feat: 3rd commit

# Rebase 24aa86a..4b339f2 onto 24aa86a (3 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
...

Ich erinnere mich noch genau daran, wie ich diesen Befehl zum ersten Mal ausgeführt habe. Meine Reaktion war ungefähr: Okayyyy, was geht denn hier vor? Jemand hatte mir eine interaktive und einfache Möglichkeit versprochen, meine Commits zu ändern, aber ich sah nur eine sehr komplizierte Oberfläche … Ich musste mir wirklich Zeit nehmen, um mich über jede einzelne Option zu informieren, denn nachdem ich diesen Bildschirm verlassen hatte, hätte ich all meine Arbeit verlieren können (#ScaredDeveloper).

Selbst den Bildschirm zu schließen und alles abzubrechen, war nicht einfach … Ich musste googeln, wie das geht, und fand heraus, dass ich einfach alle Commit-Zeilen löschen musste, damit nichts geändert wird – so einfach (und hässlich). Mein erster Gedanke war: „Moment, ich vertraue nicht unbedingt allem, was ich bei Google lese. Was, wenn ich das mache und meine ganze Arbeit verschwindet?“

Mir wurde klar, dass mir nichts anderes übrig blieb – also begann ich, diese Befehle genauer zu untersuchen.

In diesem Artikel möchte ich auf verschiedene Probleme eingehen, die Entwickler:innen bei der Arbeit an PRs mit mehreren Commits lösen müssen. Die einzelnen Bestandteile dieser Befehle erkläre ich nicht, denn es gibt zahlreiche Artikel, die erläutern, was im Hintergrund passiert. Stattdessen stelle ich einige Szenarien vor, die uns allen bei der Arbeit mit git begegnen können, und zeige jeweils die passende Lösung mit git rebase -i.

Da das Thema recht umfangreich ist, teile ich diesen Blogbeitrag in zwei Teile auf. In diesem Teil erkläre ich die Grundlagen von Rebase und stelle zwei gängige Szenarien vor. Im zweiten Teil zeige ich vier weitere typische Szenarien und ziehe ein Fazit.

Aber genug der Vorrede – starten wir mit dem ersten Teil!

Rebase kurz erklärt

Betrachten wir folgendes Szenario: Sie und ein Freund arbeiten an einem spannenden neuen Projekt. Sie haben gerade das Repo erstellt und den ersten Commit mit dem Namen A angelegt. Jetzt geht es ans Programmieren. Um die Arbeit effizienter zu gestalten, teilen Sie sie auf: Sie sind für das Haupt-Feature zuständig, Ihr Freund entwickelt das Framework der App.

Sie starten also beide beim selben Punkt: Commit A. Nach fünf Stunden intensiver Arbeit möchten Sie Ihre Änderungen fertigstellen und in master zusammenführen. Ihr Freund führt als Erster einen Merge durch und erstellt dabei auf Basis von A drei neue Commits: B + C + D. Nun sind Sie an der Reihe, Ihre gesamte Arbeit aus dem FeatureBranch in master zu übernehmen: die Commits E + F + G. So sieht das aus:

      E----F----G FeatureBranch
     /
    A----B----C----D master

Um die von Ihnen entwickelte Funktionalität zu testen, müssen Sie einige Funktionen des Frameworks verwenden, genauer gesagt aus Commit D. Mit anderen Worten: Sie müssen die Basis Ihres Branches von Commit A auf Commit D ändern – so, als hätten Sie Ihren Branch von Anfang an auf dem neuesten Stand erstellt:

                     E----F----G FeatureBranch
                    /
    A----B----C----D master

Genau dafür brauchen wir git rebase: um eine Reihe von Commits auf eine neue Basis zu verschieben. Im Hintergrund erstellt git neue Commits und wendet sie auf die angegebene neue Basis an.

Genauer gesagt geht es in diesem Artikel um den interaktiven Modus.

Wenn Sie rebase mit dem Flag -i ausführen, können Sie einzelne Commits im Rahmen des grundlegenden Rebase-Prozesses ändern – hier beginnt die Magie! Im interaktiven Modus können Sie für jeden Ihrer Commits die gewünschte Aktion festlegen: zusammenführen, aufteilen, umbenennen, neu anordnen und mehr.

Anhand gängiger Szenarien, in denen git rebase nützlich sein kann, sehen wir uns die einzelnen Optionen genauer an.

Typische Szenarien (und wie Sie damit umgehen!)

Sehen wir uns einige typische Szenarien an, in denen Sie den Verlauf Ihres Codes ändern möchten, und erfahren Sie, wie das mit Git geht:

1. Eine alte Commit-Nachricht ändern (reword)

Sie haben mehrere Codeänderungen vorgenommen und perfekt auf verschiedene Commits verteilt. Dann fällt Ihnen auf, dass eine der Commit-Nachrichten einen Tippfehler enthält:

* 3d320d9 (HEAD -> feat/git-history) feat: 3rd commit
* 03d03e9 feat: 3nd commit
* 271c66e feat: 1st commit
* 24aa86a (master) Init: Git History

Wie Sie wahrscheinlich sehen, lautet die Nachricht des zweiten Commits 3nd commit (hoppla) – das korrigieren wir jetzt.

Wäre der Tippfehler im letzten Commit, könnten wir den Befehl git commit --amend verwenden. Da er sich jedoch in der Mitte unseres git-Graphen befindet, müssen wir etwas Komplizierteres tun.

In diesem Fall verwenden wir den Befehl git rebase -i master (master kann auch ein anderer Branch sein) und wählen für den zweiten Commit (a21bd1d) die Option reword:

pick 271c66e feat: 1st commit
reword 03d03e9 feat: 3nd commit
pick 3d320d9 feat: 3rd commit

Nachdem Sie die Datei gespeichert haben, öffnet sich ein neuer Editor, in dem Sie die Commit-Nachricht bearbeiten können:

feat: 3nd commit

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Sun May 17 16:28:13 2020 +0300
#
# interactive rebase in progress; onto 24aa86a
# Last commands done (2 commands done):
#    pick 271c66e feat: 1st commit
#    r 03d03e9 feat: 3nd commit
# Next command to do (1 remaining command):
#    pick 3d320d9 feat: 3rd commit
# You are currently editing a commit while rebasing branch 'feat/git-history' on '24aa86a'.
#
# Changes to be committed:
#       modified:   b.txt
#

Ändern Sie nun die erste Zeile in die gewünschte neue Commit-Nachricht, feat: 2nd commit, und speichern Sie die Datei erneut.

Der neue git-Graph ist jetzt korrigiert:

* fdb5f71 (HEAD -> feat/git-history) feat: 3rd commit
* e2952e7 feat: 2nd commit
* 271c66e feat: 1st commit
* 24aa86a (master) Init: Git History

2. Den Inhalt eines alten Commits ändern (squash, fixup)

Sie haben CR-Kommentare von einem Kollegen erhalten und müssen einige kleine Änderungen vornehmen: eine neue Datei hinzufügen und den Inhalt einer vorhandenen Datei ändern.

Wie Sie wahrscheinlich wissen, sollte jeder Commit einen logischen Zusammenhang abbilden und nicht einfach nur Dateiänderungen enthalten. Wir sollten vermeiden, Commit-Nachrichten wie CR fixes oder in unserem Fall sogar really small CR fixes zu verwenden. Besser ist es, alle Commits zusammenzuführen, sodass jeder Commit am Ende alle relevanten Änderungen enthält – einschließlich der CR-Fixes.

Wie geht das? Hier kommen die Flags squash und fixup ins Spiel.

Erstellen Sie zunächst temporäre Commits mit den erforderlichen Änderungen:

* 17a73c1 (HEAD -> feat/git-history) CR fix: add new file to 2nd commit
* 5641a86 CR fix: content of file in 1st commit
* fdb5f71 feat: 3rd commit
* e2952e7 feat: 2nd commit
* 271c66e feat: 1st commit
* 24aa86a (master) Init: Git History

Im obigen Beispiel haben wir zwei neue Commits erstellt:

  • 5641a86 – ändert den Inhalt der Datei, die wir im ersten Commit erstellt haben

  • 17a73c1 – fügt dem zweiten Commit eine neue Datei hinzu

Wir möchten jedoch vermeiden, dass der git-Graph so aussieht. Stattdessen wollen wir 5641a86 mit 271c66e und 17a73c1 mit e2952e7 zusammenführen. Dazu führen wir den Befehl git rebase -i master aus. Wie beim letzten Mal sehen wir zunächst diesen Bildschirm:

pick 271c66e feat: 1st commit
pick e2952e7 feat: 2nd commit
pick fdb5f71 feat: 3rd commit
pick 5641a86 cr fix: content of file in 1st commit
pick 17a73c1 cr fix: add new file to 2nd commit

Ändern wir nun die Reihenfolge der Zeilen entsprechend der gewünschten Reihenfolge der Commits und verwenden die Option squash für die Commits 5641a86 und 17a73c1:

pick 271c66e feat: 1st commit
squash 5641a86 cr fix: content of file in 1st commit
pick e2952e7 feat: 2nd commit
squash 17a73c1 cr fix: add new file to 2nd commit
pick fdb5f71 feat: 3rd commit

Aufgrund dieser Änderungen führt git die Commits aus – von 271c66e bis fdb5f71 – und fügt den Inhalt der mit squash markierten Commits in den jeweils vorherigen Commit ein. Der git-Graph sieht nun genau so aus, wie wir es wollten!

* 1b2f5b1 (HEAD -> feat/git-history) feat: 3rd commit
* 6871683 feat: 2nd commit
* 682c27f feat: 1st commit
* 24aa86a (master) Init: Git History

Anstelle von squash können wir auch fixup verwenden. Beide Optionen bewirken im Wesentlichen dasselbe. Der Unterschied: fixup verwirft die Commit-Protokollnachricht.

Ein wichtiger Hinweis: Beim Zusammenführen von Commits kann es manchmal zu Code-Konflikten kommen. Wenn git Konflikte feststellt, müssen Sie diese beheben, die Änderungen stagen und den Rebase-Vorgang mit git rebase --continue fortsetzen. Wenn Sie unsicher sind, können Sie jederzeit git rebase --abort ausführen. Dieser Befehl beendet den Rebase-Vorgang und setzt Ihr Repository auf den ursprünglichen Stand zurück – also auf den Stand vor dem Ausführen von git rebase -i.

Zusammenfassung

Das war der erste Teil unserer Anleitung dazu, wie Sie Ihren git-Verlauf mit Rebase ändern können! Im zweiten Teil behandeln wir vier weitere typische Szenarien und geben Tipps für den Umgang damit.

Starten Sie mit Capture the Flag

Erfahren Sie in unserem virtuellen On-Demand-Workshop für Einsteiger, wie Sie Capture-the-Flag-Herausforderungen lösen.

Gepostet in:

Weiterlesen

Blog

Stadium-Sommer: Die Snyk Connect Fan Zone Tour

Die Fan Zone Tour von Snyk brachte KI-Security-Workshops, Networking und freundschaftlichen Wettbewerb in 8 Städte und 3 virtuelle Sessions. Die Teilnehmenden bauten ihre Kompetenzen aus, tauschten Ideen aus und entwickelten sich gemeinsam weiter.

Blog

Snyk VulnBench JS 1.0: Finden LLMs zweimal dieselben Fehler?

Snyk VulnBench JS 1.0: 300 wiederholte Scans zeigen, dass LLM-Sicherheitsergebnisse von Durchlauf zu Durchlauf variieren, während SAST und Modelle unterschiedliche Sicherheitslücken aufdecken.

feature insights context
Blog

KI-Sicherheit gemeinsam mit unseren Kunden gestalten: 5 Erkenntnisse aus dem Design-Partner-Programm von Evo

Lernen Sie 5 wichtige Erkenntnisse aus Snyks Design-Partner-Programm für Evo kennen. Erfahren Sie, wie KI-Erkennung, Risikoanalyse und automatisierte Richtlinien Teams dabei helfen, generative KI abzusichern und die Ausbreitung von KI-Anwendungen im großen Maßstab zu steuern.