git rebase

In diesem Artikel befassen wir uns mit dem Befehl git rebase in aller Tiefe. Der Rebase-Befehl wurde auch in Einrichten eines Repositorys und Umarbeiten von Verläufen angesprochen. Auf dieser Seite sehen wir uns die Konfiguration und Ausführung von git rebase genauer an. Darüber hinaus stellen wir häufige Anwendungsfälle vor und weisen auf Fallstricke hin.

Rebase ist eines der beiden Git-Dienstprogramme für die Integration von Änderungen von einem Branch in einen anderen. Bei dem anderen Dienstprogramm zur Integration von Änderungen handelt es sich um git merge. Merge ist immer ein nach vorne ausgerichteter Änderungsansatz. Rebase dagegen kann die Geschichte (bzw. den Verlauf) umschreiben. Eine detaillierte Gegenüberstellung von Merge und Rebase findest du in Merging vs. Rebasing. Rebase verfügt über zwei Hauptmodi: manuell und interaktiv. Darauf werden wir unten noch tiefer eingehen.

Was ist git rebase?

Rebasing ist das Verschieben oder Zusammenführen einer Abfolge von Commits in einem neuen Basis-Commit. Dies ist äußerst nützlich und kann vor dem Hintergrund eines Feature Branch Workflows leicht visualisiert werden. Der allgemeine Prozess kann wie folgt dargestellt werden:

Git-Tutorial: Git Rebase

Aus inhaltlicher Sicht ändert Rebasing die Basis deines Branch von einem Commit zu einem anderen, sodass es so aussieht, als ob du deinen Branch von einem anderen Commit erstellt hättest. Intern bewerkstelligt Git dies, indem neue Commits erstellt werden, die dann auf die angegebene Basis angewendet werden. Es ist enorm wichtig zu verstehen, dass die Branches zwar gleich aussehen, aber aus völlig neuen Commits bestehen.

Anwendung

Der Hauptgrund für das Rebasing ist die Wahrung eines linearen Projektverlaufs. Stell dir z. B. vor, der Haupt-Branch hat sich seit Beginn deiner Arbeit an einem Feature-Branch weiterentwickelt. Du möchtest in deinem Feature-Branch die neuesten Updates des Haupt-Branch haben, aber der Verlauf deines Branch soll sauber bleiben, damit es so aussieht, als ob du am neuesten Haupt-Branch arbeitest. Dies hat den Vorteil, dass du deinen Feature-Branch sauber zurück in den Haupt-Branch mergen kannst. Warum wollen wir einen sauberen Verlauf? Die Vorteile eines sauberen Verlaufs sind bei der Untersuchung von Git auf den Anfangspunkt einer Regression deutlich spürbar. Ein realitätsnäheres Szenario wäre Folgendes:

  1. Im Haupt-Branch wurde ein Fehler entdeckt. Ein Feature, das bisher gut funktioniert hat, läuft nun nicht mehr.
  2. Ein Entwickler untersucht den Verlauf des Haupt-Branch mit git log und kann aufgrund des sauberen Verlaufs schnell den Projektverlauf überblicken.
  3. Der Entwickler kann mit git log nicht herausfinden, wann der Fehler eingeführt wurde, und führt deshalb git bisect aus.
  4. Da der Git-Verlauf sauber ist, kann git bisect auf der Suche nach der Regression einen präzisen Commit-Satz vergleichen. Der Entwickler findet den Commit, der den Fehler eingeführt hat, schnell und kann daraufhin sofort entsprechend handeln.

Erfahre mehr zu git log und git bisect auf den jeweiligen Seiten zu ihrer Verwendung.

Zur Integration deines Features in den Haupt-Branch hast du zwei Optionen: einen direkten Merge oder erst ein Rebasing mit anschließendem Merge. Die erste Option führt zu einem 3-Wege-Merge und einem Merge-Commit, die zweite Option dagegen führt zu einem Fast-Forward-Merge und einem exakt linearen Verlauf. Im folgenden Diagramm ist dargestellt, wie Rebasing in den Haupt-Branch einen Fast-Forward-Merge erleichtert.

Git Rebase: Branch auf Master

Rebasing ist eine übliche Methode zur Integration von Upstream-Änderungen in dein lokales Repository. Das Pullen von Upstream-Änderungen mit "git merge" führt jedes Mal, wenn du den Projektfortschritt ansiehst, zu einem überflüssigen Merge-Commit. Rebasing kommt dagegen der Aussage "Ich möchte meine Änderungen auf die bereits erledigten Arbeit der anderen stützen" gleich.

Kein Rebasing von öffentlichen Verläufen

Wie zuvor in Umarbeiten von Verläufen erläutert, solltest du Commit niemals rebasen, nachdem sie in ein öffentliches Repository gepusht wurden. Das Rebasing würde die alten Commits durch neue ersetzen und es würde so aussehen, als ob dieser Teil deines Projektverlaufs plötzlich verschwunden ist.

Git Rebase Standard vs. Git Rebase Interactive

Der interaktive Modus von "git rebase" wird ausgeführt, indem der Befehl mit dem Argument -- i kombiniert wird. Dieses steht für "interaktiv". Ohne diesen Zusatz wird der Befehl im Standardmodus ausgeführt. Nehmen wir für beide Fälle an, wir haben einen separaten Feature Branch erstellt.

# Create a feature branch based off of main 
git checkout -b feature_branch main
# Edit files 
git commit -a -m "Adds new feature" 

Im Standardmodus wendet "git rebase" Commits in deinem aktuellen Arbeits-Branch automatisch auf den angegebenen Branch-Head an.

git rebase <base>

This automatically rebases the current branch onto <base>, which can be any kind of commit reference (for example an ID, a branch name, a tag, or a relative reference to HEAD).

Das Ausführen von git rebase mit dem Zusatz -i startet eine interaktive Rebasing-Sitzung. Anstatt alle Commit blind in die neue Basis zu verschieben, hast du beim interaktiven Rebasing die Möglichkeit den Prozess für die einzelnen Commits zu beeinflussen. Du kannst deinen Verlauf bereinigen, indem du den vorhandene Commits entfernst, aufteilst und änderst. Der Modus ist also im Prinzip wie Git commit --amend auf Steroiden.

git rebase --interactive <base>

This rebases the current branch onto <base> but uses an interactive rebasing session. This opens an editor where you can enter commands (described below) for each commit to be rebased. These commands determine how individual commits will be transferred to the new base. You can also reorder the commit listing to change the order of the commits themselves. Once you've specified commands for each commit in the rebase, Git will begin playing back commits applying the rebase commands. The rebasing edit commands are as follows:




pick 2231360 some old commit
pick ee2adc2 Adds new feature


# Rebase 2cf755d..ee2adc2 onto 2cf755d (9 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 commit

Weitere Rebase-Befehle

Wie auf der Seite Umarbeiten von Verläufen beschrieben, können ältere und auch mehrere Commits sowie committete Dateien und mehrere Nachrichten mithilfe von Rebasing geändert werden. Dies sind die gebräuchlichsten Anwendungen von git rebase. Es gibt jedoch noch weitere Optionen, die in komplexeren Anwendungsfällen von Nutzen sind.

  • git rebase -- d bedeutet, dass der Commit während des Abspielens vom finalen Commit-Block entfernt wird.
  • git rebase -- p verändert den Commit nicht. Die Commit-Nachricht oder der Inhalt ändert sich nicht und der Commit bleibt weiterhin als individueller Commit im Branch-Verlauf erhalten.
  • git rebase -- x führt während des Abspielens für jeden markierten Commit ein Shell-Skript aus. Ein nützlicher Use Case wäre z. B. das Ausführen der Test-Suite deiner Codebasis für bestimmte Commits zur Identifizierung von Regressionen während eines Rebasings.

Zusammenfassung

Mit interaktivem Rebasing hast du die volle Kontrolle über das Aussehen deines Projektverlaufs. Das gewährt Entwicklern die Freiheit, "chaotische" Verläufe zu generieren, während sie sich auf die Codeerstellung konzentrieren, und die Verläufe anschließend zu säubern.

Die meisten Entwickler verwenden gerne für den letzten Schliff an einem Feature Branch interaktives Rebasing, bevor sie ihn in die Haupt-Codebasis mergen. Auf diese Weise können sie unwichtige und überflüssige Commits entfernen bzw. squashen und sicherstellen, dass alles in Ordnung ist, bevor ein Commit in den "offiziellen" Projektverlauf durchgeführt wird. Für Außenstehende wird es so aussehen, als ob das gesamte Feature in einer einzigen Reihe gut geplanter Commits entwickelt wurde.

Die wahre Stärke des interaktiven Rebasing zeigt sich im Verlauf des resultierenden Haupt-Branch. Für Außenstehende sieht es so aus, als seist du ein brillanter Entwickler, der das neue Feature auf Anhieb mit der perfekten Anzahl von Commits implementiert hat. So kann interaktives Rebasing einen Projektverlauf sauber und aussagekräftig halten.

Konfigurationsoptionen

Mit git config können einige Rebase-Eigenschaften eingerichtet werden. Diese Optionen ändern das Erscheinungsbild der git rebase-Ausgabe.

  • rebase.stat: Eine Boolesche Variable, die standardmäßig auf "Falsch" gesetzt ist. Mit der Option wechselst du zur Anzeige von Diffstat-Inhalten, die darstellen, was sich seit dem letzten Rebasing geändert hat.
  • rebase.autoSquash: Ein Boolescher Wert, der das --autosquash-Verhalten umschaltet.
  • rebase.missingCommitsCheck: Kann auf verschiedene Werte gesetzt werden, die jeweils das Verhalten bezüglich fehlender Commits ändern.
warn Zeigt im interaktiven Modus eine Warnmeldung an, die vor entfernten Commits warnt

error

Stoppt das Rebasing und zeigt eine Warnmeldung zu entfernten Commits an

ignore

In den Standardeinstellungen werden Warnungen zu fehlenden Commits ignoriert.
  • rebase.instructionFormat: Ein git log-Format-String zur Formatierung der interaktiven Rebase-Anzeige

Erweiterte Rebasing-Anwendung

Das Befehlszeilenargument --onto kann an git rebase angefügt werden. Im git rebase --onto-Modus sieht der Befehl folgendermaßen aus:

 git rebase --onto <newbase> <oldbase>

Die Option --onto ermöglicht eine leistungsstärkere Form des Rebasing, da spezifische Referenzen als Spitze einer Rebase hinzugefügt werden können.
Nehmen wir an, wir haben ein Beispiel-Repo mit folgenden Branches:


   o---o---o---o---o  main
        \
         o---o---o---o---o  featureA
              \
               o---o---o  featureB

featureB basiert auf featureA, trotzdem ist featureB nicht von Änderungen an featureA abhängig und könnte auch auf Basis des Haupt-Branch erstellt worden sein.

 git rebase --onto main featureA featureB

featureA is the <oldbase>. main becomes the <newbase> and featureB is reference for what HEAD of the <newbase> will point to. The results are then:

 
                      o---o---o  featureB
                     /
    o---o---o---o---o  main
     \
      o---o---o---o---o  featureA
                           

Die Gefahren von Rebase

Ein Problem bei der Arbeit mit Git Rebase sind die zunehmenden Merge-Konflikte in einem Rebase-Workflow. Diese treten auf, wenn ein langlebiger Branch sich immer weiter vom Haupt-Branch entfernt. Letzten Endes wirst du auf den Haupt-Branch rebasen wollen und dieser enthält möglicherweise viele neue Commits, mit denen deine Branch-Änderungen in Konflikt geraten könnten. Hier kann leicht Abhilfe geschaffen werden, indem du für deinen Branch regelmäßig Rebasings mit dem Haupt-Branch und häufigere Commits durchführst. Die Befehlszeilenargumente --continue und --abort können an git rebase angefügt werden, um im Rahmen der Lösung von Konflikten das Rebasing fortzuführen oder zurückzusetzen.

Eine größere Gefahr beim Rebasing sind verlorene Commits beim interaktiven Umarbeiten des Verlaufs. Das Ausführen von "git rebase" im interaktiven Modus zusammen mit Unterbefehlen wie "squash" oder "drop" entfernt Commits vom unmittelbaren Branch-Protokoll. Auf den ersten Blick wirkt dies, als ob die Commits für immer verschwunden sind. Doch mit git reflog können diese Commits wiederhergestellt und das komplette Rebasing rückgängig gemacht werden. Für weitere Informationen, wie du mit git reflog verlorene Commits wiederfindest, siehe die Seite zur "Git reflog"-Dokumentation.

Git Rebase ist an sich nicht wirklich gefährlich. Die wahren Gefahren lauern beim Ausführen von interaktiven Rebases zum Umarbeiten von Verläufen und anschließendem erzwungenen Pushen der Ergebnisse in einen Remote Branch, der gemeinsam mit anderen Benutzern verwendet wird. Dieses Muster sollte vermieden werden, da dabei die Arbeit anderer Remote-Benutzer überschrieben werden kann, wenn diese einen Pull durchführen.

Wiederherstellen nach einem Upstream-Rebasing

Wenn ein anderer Benutzer ein Rebasing durchgeführt und dann einen Push zu dem Branch erzwungen hat, zu dem auch du committest, wird der Befehl git pull alle Commits überschreiben, die du auf Basis dieses vorigen Branch, dessen Spitze zwangsgepusht wurde, erstellt hast. Zum Glück erhältst du mit dem Befehl git reflog den Reflog des Remote Branch. Im Reflog des Remote Branch findest du eine Ref mit dem Status vor dem Rebasing. Anschließend kannst du für deinen Branch mit der Option --onto ein Rebasing auf diese Remote-Ref durchführen wie im Abschnitt zur erweiterten Rebasing-Anwendung beschrieben.

Zusammenfassung

In diesem Artikel haben wir die Nutzung von git rebase behandelt. Wir haben grundlegende und erweiterte Anwendungsfälle sowie Beispiele für Fortgeschrittene besprochen. Wichtige Punkte dabei waren:

  • Standard- und interaktiver Modus von git rebase im Vergleich
  • Konfigurationsoptionen für git rebase
  • git rebase --onto
  • git rebase und verlorene Commits

Wir haben uns die Nutzung von git rebase in Kombination mit anderen Befehlen wie git reflog, git fetch und git push angesehen. Auf den jeweiligen Seiten zu diesen Befehlen erhältst du weitere Informationen hierzu.

Du möchtest mit Git arbeiten?

Sieh dir dieses interaktive Tutorial an.

Jetzt loslegen