Git Rebase vs. Merge: Wann man was verwendet
git merge und git rebase bringen beide die Änderungen deines Feature-Branches auf main, und genau diese oberflächliche Ähnlichkeit ist der Grund, warum man sie durcheinanderbringt und entweder mit einer verworrenen Historie endet oder mit einer umgeschriebenen, die man gar nicht umschreiben wollte. Sie lösen dasselbe Problem auf grundlegend verschiedene Weise, und die falsche Wahl für die Situation führt zu Force-Push-Desastern oder unlesbaren Commit-Graphen.
Was jeder von beiden tatsächlich tut
Angenommen, main ist seit dem Abzweigen weitergelaufen, und dein Feature-Branch hat eigene Commits:
A---B---C feature
/
D---E---F---G main
Merge erzeugt einen neuen Commit mit zwei Eltern, der die beiden Historien zusammenführt:
git checkout feature
git merge main
A---B---C-------H feature
/ /
D---E---F---G-------- main
An A, B oder C ändert sich nichts. H ist ein neuer Merge-Commit, der festhält: „diese beiden Historienlinien sind jetzt vereint". Die vollständige Historie – einschließlich der Tatsache, dass du abgezweigt bist, bevor F und G landeten – bleibt intakt.
Rebase nimmt deine Commits und spielt sie nacheinander auf der neuen Basis ab:
git checkout feature
git rebase main
D---E---F---G---A'---B'---C' feature
A', B', C' sind neue Commits – dieselben Änderungen, dieselben Nachrichten, aber ein anderer Elternteil, was andere Commit-Hashes bedeutet. Der Abzweigpunkt, an dem ihr auseinandergelaufen seid, ist weg; es sieht nun so aus, als hättest du von G abgezweigt und deine drei Commits von dort in einer geraden Linie geschrieben.
Warum diese Unterscheidung wichtig ist
Der zentrale Kompromiss: Merge bewahrt, was tatsächlich geschah; Rebase erzeugt eine sauberer aussehende Historie, die so nicht buchstäblich stattgefunden hat. Beides ist nützlich – für unterschiedliche Zielgruppen.
- Merge ist ehrlich über die parallele Arbeit. Haben zwei Personen beide auf einem alten
mainaufgebaut und sind ihre Branches auseinandergelaufen und wieder zusammengekommen, hält ein Merge-Commit das wahrheitsgemäß fest. Er ist nicht destruktiv – kein bestehender Commit wird verändert, also ist er auf geteilten Branches immer sicher. - Rebase lässt die Historie so lesen, als wäre sie seriell geschehen, was später leichter nachzuvollziehen ist –
git logzeigt eine saubere Linie statt eines Gitters aus Merge-Commits –, schreibt aber Commit-Hashes um, und da liegt die Gefahr.
Die Regel, die wirklich zählt: Niemals geteilte Historie rebasen
git checkout feature
git rebase main
git push --force-with-lease
Ist feature ein Branch, an dem nur du arbeitest, ist das in Ordnung – du schreibst Commits um, auf denen niemand sonst aufgebaut hat. Ist feature (oder schlimmer: main) ein Branch, den andere schon gepullt und auf dem sie aufgebaut haben, schreibt ein Rebase die Historie unter ihnen weg: Die Historie ihres lokalen Branches und die des Remotes laufen auseinander, und ihr nächster Pull erzeugt ein verwirrendes Knäuel doppelter Commits, oder ihre lokalen Commits scheinen zu verschwinden (sie sind nicht weg – nur nicht mehr von dem Branch erreichbar, dem sie zu folgen glaubten).
Die Faustregel: Rebase frei auf Branches, auf die nur du pushst; merge (oder nutze Rebase-und-Fast-Forward über ein PR-Tool) für alles, was bereits geteilt ist.
Interaktiver Rebase: aufräumen, bevor man teilt
Die andere übliche Nutzung von Rebase hat gar nichts mit einer anderen Basis zu tun – es geht darum, die eigenen, noch nicht gepushten Commits aufzuräumen, bevor man einen PR öffnet.
git rebase -i HEAD~4
Das öffnet einen Editor mit deinen letzten vier Commits, in dem du sie umsortieren, "fix typo" und "actually fix typo" in den Commit squashen kannst, zu dem sie gehören, oder Nachrichten umformulieren – alles, bevor irgendjemand sonst sie gesehen hat. Das ist die sicherste und in der Praxis häufigste Nutzung von Rebase: eine chaotische Work-in-progress-Historie in eine kleine Zahl stimmiger Commits zu verwandeln, bevor sie zu geteilter Historie wird.
Merge-Konflikte fühlen sich jeweils anders an
Bei einem Merge löst man Konflikte einmal, im einzelnen Merge-Commit. Bei einem Rebase kann man, wenn mehrere Commits dieselben Zeilen berühren, denselben Konflikt wiederholt treffen – einmal pro abgespieltem Commit –, was mühsamer, aber auch präziser ist: Man löst jede historische Änderung in dem Kontext, in dem sie tatsächlich gemacht wurde, nicht einen riesigen Diff am Ende.
git rebase main
# CONFLICT in commit B
# ...resolve...
git add .
git rebase --continue
# CONFLICT in commit C, same lines
# ...resolve again...
git add .
git rebase --continue
Wird ein Rebase zur Konflikt-Schlepperei, bringt dich git rebase --abort genau dorthin zurück, wo du angefangen hast – gut zu wissen, bevor man drei Konflikte tief steckt und unsicher ist, ob man es schlimmer macht.
Ein praktischer Standard
- Auf dem eigenen Feature-Branch, bevor man weitermacht, zu
mainaufschließen:rebase. - Commits aufräumen, bevor man einen PR öffnet:
rebase -i. - Einen fertigen, geprüften Feature-Branch nach
mainbringen: die Konvention deines Teams – beides ist vertretbar, und Konsistenz zählt mehr als die Wahl. - Alles, was bereits gepusht und womöglich von jemand anderem gepullt wurde:
merge, oder Rebase nur mit ausdrücklicher Absprache im Team und Vorwarnung.
Fazit
Merge sagt die Wahrheit über parallele Historie, um den Preis eines verworreneren Graphen; Rebase erzeugt einen sauberen linearen Graphen, um den Preis, die Commit-Identität umzuschreiben. Die Linie, die wirklich zählt, ist nicht „was ist besser" – sondern ob die Commits, die du gleich umschreibst, noch ausschließlich deine sind.