Merging vs. rebasing
Der Befehl git rebase
hat den Ruf, magisches Git-Hokuspokus zu sein, von dem Anfänger die Finger lassen sollten, aber er kann einem Entwicklungsteam das Leben erheblich erleichtern, wenn er vorsichtig verwendet wird. Wir vergleichen die beiden Befehle git rebase
und git merge
und schauen uns alle Möglichkeiten an, wie du Rebasing in einem typischen Git-Workflow implementieren kannst.
Conceptual overview
Du solltest wissen, dass die Befehle git rebase
und git merge
dasselbe Problem lösen. Beide Befehle sind dazu gedacht, Änderungen von einem Branch in einen anderen zu integrieren, sie tun es nur auf unterschiedliche Weise.
Überlege, was passiert, wenn du in einem dedizierten Branch an einem neuen Feature arbeitest und ein anderes Teammitglied den main
-Branch mit neuen Commits aktualisiert. Dies führt zu einem geforkten Verlauf, was jedem bekannt sein sollte, der Git schon mal als Tool für die Zusammenarbeit verwendet hat.
Nehmen wir einmal an, dass die neuen Commits im main
-Branch für das Feature, an dem du arbeitest, relevant sind. Um die neuen Commits in deinen feature
-Branch zu integrieren, hast du die Wahl zwischen "git merge" und "git rebase".
Zugehöriges Material
Verschieben eines vollständigen Git-Repositorys
Lösung anzeigen
Git kennenlernen mit Bitbucket Cloud
The merge option
Der einfachste Weg ist, den main
-Branch in den Feature-Branch zu mergen, etwa mit dem folgenden Befehl:
git checkout feature
git merge main
Du kannst es auch in einem Einzeiler zusammenfassen:
git merge feature main
Dabei entsteht ein neuer Merge-Commit im Feature
Branch, der den Verlauf beider Branches vereint und eine Branch-Struktur erstellt, die wie folgt aussieht:
Merging ist praktisch, weil es sich um einen nicht destruktiven Vorgang handelt. Die vorhanden Branches werden in keiner Weise geändert. Dadurch werden auch die potenziellen Tücken des Rebasing (siehe unten) umgangen.
Andererseits bedeutet dies, dass der feature
-Branch jedes Mal, wenn du Upstream-Änderungen integrieren musst, einen irrelevanten Merge-Commit erhält. Wenn der main
-Branch sehr aktiv ist, kann dies den Verlauf deines Feature-Branch ziemlich durcheinanderbringen. Dieses Problem kann zwar mit erweiterten git log
-Optionen gemindert werden, andere Entwickler können dann aber den Projektverlauf nicht so leicht nachvollziehen.
The rebase option
Als Alternative zum Mergen kannst du den feature
-Branch auf den main
-Branch mithilfe der folgenden Befehle rebasen:
git checkout feature
git rebase main
Der gesamte feature
-Branch wird zur Spitze des main
-Branch verschoben und alle neuen Commits werden effektiv in den main
-Branch integriert. Anstatt einen Merge-Commit zu nutzen, wird der Projektverlauf beim Rebasing neu geschrieben, indem für jeden Commit im originalen Branch völlig neue Commits erstellt werden.
Der Hauptvorteil von "git rebase" vs. "git merge" liegt darin, dass du einen wesentlich übersichtlicheren Verlauf erhältst. Erstens werden dabei unnötige Merge-Commits eliminiert, die für git merge
erforderlich sind. Zweitens führt Rebasing zu einem völlig linearen Projektverlauf, wie du am obigen Diagramm erkennen kannst. Du kannst von der Spitze des Features
ohne Verzweigungen bis hinunter zum Beginn des Projekts gehen. Somit kannst du mit Befehlen wie git log
, git bisect
und gitk
einfacher durch dein Projekt navigieren.
Allerdings bringt dieser makellose Commit-Verlauf zwei Kompromisse mit sich, nämlich Sicherheit und Nachverfolgbarkeit. Wenn du dich nicht an die goldene Regel des Rebasing hältst, kann das Neuschreiben des Projektverlaufs möglicherweise katastrophale Folgen für deinen Kollaborations-Workflow haben. Daneben geht beim Rebasing Kontext verloren, den ein Merge-Commit bietet, somit kannst du nicht erkennen, wann Upstream-Änderungen in das Feature integriert wurden.
Interactive rebasing
Interaktives Rebasing gibt dir die Möglichkeit, Commits zu modifizieren, wenn sie in den neuen Branch verschoben werden. Das ist sogar noch leistungsstärker als ein automatisiertes Rebasing, da du damit die vollständige Kontrolle über den Commit-Verlauf des Branch erhältst. Diese Option wird normalerweise genutzt, um einen unübersichtlichen Verlauf zu bereinigen, bevor ein Feature-Branch in den main
-Branch gemergt wird.
Um eine interaktive Rebasing-Sitzung zu beginnen, gibst du die Option i
für den Befehl git rebase
an:
git checkout feature
git rebase -i main
Damit wird ein Texteditor geöffnet, in dem alle zu verschiebenden Commits aufgeführt sind:
pick 33d5b7a Message for commit #1
pick 9480b3d Message for commit #2
pick 5c67e61 Message for commit #3
Diese Liste legt genau fest, wie der Branch nach dem Rebasing aussehen wird. Durch die Änderung des pick-
Befehls und/oder die Umsortierung der Einträge kannst du den Branch-Verlauf so gestalten, wie du es gerne möchtest. Wenn der zweite Commit beispielsweise ein kleines Problem im ersten Commit behebt, kannst du mit dem Befehl fixup
beide in einem einzelnen Commit zusammenfassen:
pick 33d5b7a Message for commit #1
fixup 9480b3d Message for commit #2
pick 5c67e61 Message for commit #3
Wenn du die Datei speicherst und schließt, führt Git das Rebasing nach deinen Anweisungen durch. Das Ergebnis ist ein Projektverlauf, der ähnlich wie dieser aussieht:
Das Eliminieren nicht signifikanter Commits wie hier macht den Verlauf des Features bei "git rebase" im Gegensatz zu git merge
viel verständlicher.
The golden rule of rebasing
Wenn wir einmal verstanden haben, was Rebasing ist, müssen wir direkt lernen, wann wir es nicht tun dürfen. Die goldene Regel lautet, dass git rebase
nie auf öffentlichen Branches genutzt werden darf.
Überlege einmal, was passiert, wenn du den main
-Branch auf deinen feature
-Branch rebasen würdest:
Das Rebasing verschiebt alle Commits in main
-Branch zur Spitze des feature
-Branches. Das Problem ist, dass dies nur in deinem Repository erfolgte. Alle anderen Entwickler arbeiten noch mit dem ursprünglichen main
-Branch. Da das Rebasing zu völlig neuen Commits führt, wird Git annehmen, dass der Verlauf deines main
-Branches von dem aller anderen abweicht.
Der einzige Weg, die beiden main
-Branches zu synchronisieren, besteht darin, sie wieder zusammen zu mergen. Daraus resultieren ein zusätzlicher Merge-Commit und zwei Sets von Commits, die dieselben Änderungen enthalten (die ursprünglichen und die aus dem rebasten Branch). Wir stellen fest: Die Situation wird unübersichtlich.
Bevor du also git rebase
ausführst, solltest du dich immer fragen, ob noch jemand diesen Branch ansieht. Wird dies bejaht, nimm die Hände von der Tastatur und überlege, ob es eine nicht destruktive Methode für deine Änderungen gibt (z. B. den Befehl git revert
). Andernfalls kannst du den Verlauf neu schreiben, wie du magst.
Force-pushing
Wenn du versuchst, den rebasten main
-Branch zurück in ein Remote-Repository zu pushen, wird Git dich davon abhalten, denn es würden Konflikte mit dem Remote-main
-Branch entstehen. Allerdings kannst du das Pushen mit der Option --force
erzwingen:
# Be very careful with this command! git push --force
Damit wird der Remote-main
-Branch überschrieben und in Übereinstimmung mit dem rebasten Haupt-Branch in deinem Repository gebracht, was die Dinge für den Rest des Teams sehr verwirrend macht. Sei mit diesem Befehl also vorsichtig und verwende ihn nur, wenn du genau weißt, was du tust.
Ein Pushing solltest du eigentlich nur dann erzwingen, wenn du ein lokales Cleanup durchgeführt hast, nachdem ein privater feature-Branch in ein Remote-Repository gepusht wurde (z. B. zu Backup-Zwecken). Das ist wie: "Hoppla, ich wollte die Originalversion eigentlich nicht zum feature-Branch pushen. Hier ist die aktuelle Version." Ich wiederhole noch einmal: Niemand arbeitet mit den Commits der Originalversion des feature-Branch.
Workflow walkthrough
Das Rebasing kann in deinen bestehenden Git-Workflow integriert werden, und zwar in dem Maße, wie es zu deinem Team passt. In diesem Abschnitt gehen wir auf die Vorteile ein, die das Rebasing in verschiedenen Phasen der Feature-Entwicklung bietet.
Der erste Schritt bei jedem Workflow, der den Befehl git rebase
nutzt, ist die Erstellung eines dedizierten Branch für jedes Feature. Dadurch erhältst du die für eine sichere Nutzung des Rebasing notwendige Branch-Struktur:
Local cleanup
Die Bereinigung von lokalen Features, die gerade bearbeitet werden, ist eine der besten Möglichkeiten, um Rebasing in deinen Workflow zu implementieren. Indem du regelmäßig ein interaktives Rebasing durchführst, kannst du sicherstellen, dass jeder Commit deines Features fokussiert und sinnvoll ist. So kannst du Code schreiben, ohne dir darüber Gedanken zu machen, ob du ihn in einzelne Commits zerteilen musst – Du kannst dich einfach anschließend darum kümmern.
Wenn du git rebase
aufrufst, hast du für die neue Base zwei Optionen: den übergeordneten Branch des Features (z. B. main
) oder einen früheren Commit in deinem Feature. Ein Beispiel der ersten Option haben wir im Abschnitt Interaktives Rebasing angeschaut. Die letztere Option ist gut, wenn du nur die letzten Commits korrigieren musst. Der nächste Befehl startet beispielsweise ein interaktives Rebasing nur der letzten 3 Commits.
git checkout feature git rebase -i HEAD~3
Indem du HEAD~3
als neue Base spezifizierst, verschiebst du den Branch nicht tatsächlich – du schreibst nur die 3 folgenden Commits interaktiv neu. Beachte, dass dabei keine Upstream-Änderungen in den feature-
Branch integriert werden.
Wenn du mithilfe dieser Methode das gesamte Feature neu schreiben willst, kann sich der Befehl git merge-base
als nützlich erweisen, um die Original-Base des feature-
Branch zu finden. Der folgende Befehl gibt die Commit-ID der Original-Base aus, die du dann an git rebase
weitergeben kannst:
git merge-base feature main
Diese Nutzung des interaktiven Rebasing ist eine gute Möglichkeit, um git rebase
in deinen Workflow einzubinden, da es nur lokale Branches betrifft. Andere Entwickler sehen später nur das Endprodukt in Form eines sauberen, einfach nachvollziehbaren Feature-Branch-Verlaufs.
Ich wiederhole aber, dass die nur bei privaten feature-Branches funktioniert. Wenn du mit anderen Entwicklern am selben feature-Branch arbeitest, ist dieser öffentlich und du darfst seinen Verlauf nicht neu schreiben.
Es gibt keine Alternative für git merge
, um lokale Commits mit einem interaktiven Rebasing zu bereinigen.
Incorporating upstream changes into a feature
Im Abschnitt Übersicht zum Konzept haben wir gesehen, wie ein Feature Branch Upstream-Änderungen vom main
-Branch über die Befehle git rebase
oder git merge
integrieren kann. Merging ist eine sichere Option, die den gesamten Verlauf deines Repositorys bewahrt, während beim Rebasing durch das Verschieben deines Feature-Branches an die Spitze des main
-Branches ein linearer Verlauf erstellt wird.
Die Verwendung von git rebase
ähnelt einem lokalen Cleanup (und kann gleichzeitig durchgeführt werden), bei dem Vorgang werden aber diese Upstream-Commits vom main
-Branch integriert.
Denke daran, dass es völlig legal ist, auf einen Remote-Branch zu rebasen, anstatt auf den main
-Branch. Dies kann passieren, wenn du mit anderen Entwicklern am selben Feature arbeitest und du deren Änderungen in dein Repository integrieren musst.
Wenn beispielsweise du und ein anderer Entwickler namens John dem Feature
Branch Commits hinzugefügt habt, könnte dein Repository wie das folgende aussehen, nachdem du den Remote-Feature
Branch von Johns Repository abgerufen hast:
Diesen Fork kannst du auf die exakt gleiche Weise auflösen wie bei der Integration von Upstream-Änderungen vom main
-Branch: Entweder mergst du deinen lokalen feature
-Branch mit john/feature
oder rebast dein lokales Feature
an die Spitze von john/feature
.
Beachte, dass dieses Rebasing nicht gegen die goldene Regel verstößt, denn nur deine lokalen feature-
Commits werden verschoben, alles andere davor bleibt unberührt. Das käme der Aufforderung "füge meine Änderungen zu dem hinzu, was John bereits gemacht hat" gleich. In den meisten Fällen ist der Vorgang intuitiver als die Synchronisation mit dem Remote-Branch über ein Merge-Commit.
Normalerweise führt der Befehl git pull
einen Merge aus. Du kannst damit aber erzwingen, dass der Remote-Branch in einen Rebasing-Vorgang integriert wird, indem du die Option --rebase
angibst.
Reviewing a feature with a pull request
Wenn du im Code-Review-Prozess Pull Requests nutzt, musst du den Befehl git rebase
nach Erstellung des Pull Request vermeiden. Sobald ein Pull Request erstellt wurde, sehen sich andere Entwickler deine Commits an; es handelt sich also um einen öffentlichen Branch. Das Neuschreiben des Verlaufs würde es Git und deinen Teamkollegen unmöglich machen, jedwede nachfolgenden Commits nachzuverfolgen, die dem Feature hinzugefügt wurden.
Änderungen von anderen Entwicklern müssen anstelle von git rebase
mit git merge
integriert werden.
Aus diesem Grund ist es immer eine gute Idee, deinen Code mit einem interaktiven Rebasing zu bereinigen, bevor du deinen Pull Request erstellst.
Integrating an approved feature
Nachdem ein Feature von deinem Team freigegeben wurde, hast du die Option, das Feature an die Spitze des main
-Branch zu rebasen, bevor du git merge
nutzt, um das Feature in die Haupt-Codebasis zu integrieren.
Das ist eine ähnliche Situation wie beim Integrieren von Upstream-Änderungen in den Feature-Branch. Da du aber im main
-Branch keine Commits neu schreiben darfst, musst du git merge
nutzen, um das Feature zu integrieren. Beim Durchführen von "git merge" vor dem Rebase stellst du sicher, dass ein Fast-Forward-Merge ausgeführt wird und damit ein perfekter linearer Verlauf entsteht. Dies gibt dir auch die Möglichkeit, jedwede nachfolgenden Commits zu vermeiden, die während einem Pull Request hinzugefügt werden.
Wenn du im Umgang mit git rebase
noch nicht ganz sicher bist, kannst du immer noch ein Rebasing in einem temporären Branch durchführen. Auf diese Weise kannst du den Original-Branch auschecken und einen neuen Versuch starten, wenn du den Verlauf deines Features versehentlich durcheinander bringst. Ein Beispiel:
git checkout feature
git checkout -b temporary-branch
git rebase -i main
# [Clean up the history]
git checkout main
git merge temporary-branch
Zusammenfassung
Das ist im Grunde alles, was du zum Rebasing deiner Branches und dem Unterschied zwischen "git merge" vs. "git rebase" wissen musst. Wenn dir ein sauberer, linearer Verlauf ohne unnötige Merge-Commits lieber ist, solltest du git rebase
statt git merge
nutzen, um Änderungen aus einem anderen Branch zu integrieren.
Wenn du andererseits den kompletten Projektverlauf erhalten und das Risiko vermeiden willst, öffentliche Commits neu zu schreiben, kannst du, statt "git rebase" zu nutzen, bei git merge
bleiben. Jede Option ist zulässig, aber du hast jetzt zumindest die Möglichkeit, die Vorteile von git rebase
gegenüber "git merge" zu nutzen.
Diesen Artikel teilen
Nächstes Thema
Lesenswert
Füge diese Ressourcen deinen Lesezeichen hinzu, um mehr über DevOps-Teams und fortlaufende Updates zu DevOps bei Atlassian zu erfahren.