Close

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.

Verzweigter Commit-Verlauf

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".

Datenbanken
Zugehöriges Material

Verschieben eines vollständigen Git-Repositorys

Bitbucket-Logo
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 main into feature branch

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.

Rebasing feature branch into main

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:

Commit mit interaktivem Rebasing squashen

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:

Rebasing the main branch

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:

Feature in einem eigenen Branch entwickeln

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.

Rebasing zu Head~3

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:

Zusammenarbeit am selben Feature Branch

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.

Merging vs. Rebasing zu einem Remote-Branch

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.

Integrating a feature into main with and without a rebase

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

Lesenswert

Füge diese Ressourcen deinen Lesezeichen hinzu, um mehr über DevOps-Teams und fortlaufende Updates zu DevOps bei Atlassian zu erfahren.

Mitarbeiter arbeiten mit unzähligen Tools zusammen

Bitbucket-Blog

Abbildung: DevOps

DevOps-Lernpfad

Demo Den: Feature-Demos mit Atlassian-Experten

So funktioniert Bitbucket Cloud mit Atlassian Open DevOps

Melde dich für unseren DevOps-Newsletter an

Thank you for signing up