Zum Inhalt springen

Git Merge vs Rebase

Eine FAQ, die erklärt, wann man was verwenden sollte – und warum.

Git Merge vs Rebase

Unter Entwicklern gibt es eine häufige Diskussion darüber, wie Teams Git verwenden sollten, um sicherzustellen, dass alle immer auf dem neuesten Stand mit den letzten Änderungen im main-Branch sind. Die typische Situation, in der diese Frage aufkommt, ist wenn jemand auf einem neuen Branch gearbeitet hat und dann, sobald die Arbeit fertig und bereit zum Mergen ist, sich der Main-Branch zwischenzeitlich so verändert hat, dass der Arbeits-Branch veraltet ist und nun Merge-Konflikte hat.

Natürlich müssen diese gelöst werden, bevor der Arbeits-Branch gemergt werden kann. Aber die Frage ist: Wie sollte diese Situation gelöst werden? Sollten wir den Main-Branch in den Arbeits-Branch mergen? Oder sollten wir den Arbeits-Branch auf den neuesten Main-Branch rebasen?

Meiner Meinung nach gibt es nur eine richtige Antwort auf diese Frage. Aus meiner Erfahrung ist der Hauptgrund, warum so viele Diskussionen rund um dieses Thema entstehen, dass es viele Missverständnisse darüber gibt, wie sich Merge und Rebase in diesem Kontext unterscheiden, und ein generelles Verständnisdefizit, was ein Rebase überhaupt ist.

Deshalb habe ich eine FAQ für mein Team erstellt, die versucht, die Dinge klarzustellen. Die möchte ich hier teilen:

Was ist ein Merge?

Ein Commit, der alle Änderungen eines anderen Branches in den aktuellen kombiniert.

Was ist ein Rebase?

Alle Commits des aktuellen Branches auf einen anderen Basis-Commit neu anzuwenden.

Was sind die Hauptunterschiede zwischen Merge und Rebase?

  1. merge führt nur einen neuen Commit aus. rebase führt typischerweise mehrere aus (Anzahl der Commits im aktuellen Branch).

  2. merge erzeugt einen neuen generierten Commit (den sogenannten Merge-Commit). rebase verschiebt nur bestehende Commits.

In welchen Situationen sollte man merge verwenden?

Verwende merge, wenn du Änderungen eines abgezweigten Branches zurück in den Basis-Branch übernehmen willst.

Typischerweise macht man das über den “Merge”-Button bei Pull/Merge Requests, z.B. auf GitHub.

In welchen Situationen sollte man rebase verwenden?

Verwende rebase, wenn du Änderungen eines Basis-Branches in einen abgezweigten Branch übernehmen willst.

Typischerweise macht man das in work-Branches, wenn es eine Änderung im main-Branch gibt.

Warum nicht merge verwenden, um Änderungen vom Basis-Branch in einen Arbeits-Branch zu mergen?

  1. Die Git-History wird viele unnötige Merge-Commits enthalten. Wenn mehrere Merges in einem Arbeits-Branch nötig waren, könnte der Arbeits-Branch sogar mehr Merge-Commits als eigentliche Commits enthalten!

  2. Das erzeugt eine Schleife, die das mentale Modell zerstört, für das Git entworfen wurde, was zu Problemen bei jeder Visualisierung der Git-History führt.

Stell dir vor, es gibt einen Fluss (z.B. den “Nil”). Wasser fließt in eine Richtung (Zeitrichtung in der Git-History). Hin und wieder gibt es Abzweigungen von diesem Fluss und die meisten davon fließen irgendwann wieder in den Fluss zurück. So könnte der natürliche Flussverlauf aussehen. Das ergibt Sinn.

Aber dann stell dir mal vor, es gibt eine kleine Abzweigung dieses Flusses. Dann, aus irgendeinem Grund, fließt der Fluss in die Abzweigung und die Abzweigung geht von dort aus weiter. Der Fluss ist nun technisch gesehen verschwunden, er befindet sich jetzt in der Abzweigung. Aber dann, auf magische Weise, wird diese Abzweigung wieder zurück in den Fluss geführt. In welchen Fluss fragst du? Keine Ahnung. Der Fluss sollte eigentlich in der Abzweigung sein, aber irgendwie existiert er noch weiter und ich kann die Abzweigung zurück in den Fluss führen. Also ist der Fluss im Fluss. Irgendwie ergibt das keinen Sinn.

Genau das passiert, wenn du den Basis-Branch in einen work-Branch mergest und dann, wenn der work-Branch fertig ist, diesen wieder zurück in den Basis-Branch mergst. Das mentale Modell ist kaputt. Und deswegen hat man am Ende eine Branch-Visualisierung, die nicht besonders hilfreich ist.

Beispiel einer Git-History bei Verwendung von merge:

Beispiel einer Git-History bei Verwendung von Merge

Beispiel einer Git-History bei Verwendung von merge

Beachte die vielen Commits, die mit Merge branch 'main' into … beginnen (mit gelben Kästchen markiert). Die existieren bei einem Rebase gar nicht (dort hat man nur Pull-Request-Merge-Commits). Beachte auch die vielen visuellen Branch-Merge-Schleifen (main in work in main).

Beispiel einer Git-History bei Verwendung von rebase:

Beispiel einer Git-History bei Verwendung von Rebase

Beispiel einer Git-History bei Verwendung von rebase

Viel sauberere Git-History mit deutlich weniger Merge-Commits und keinerlei unübersichtlichen visuellen Branch-Merge-Schleifen.

Gibt es Nachteile / Fallstricke bei rebase?

Ja:

  1. Da ein rebase Commits verschiebt (technisch gesehen neu ausführt), wird das Commit-Datum aller verschobenen Commits auf den Zeitpunkt des Rebase gesetzt und die Git-History verliert den ursprünglichen Commit-Zeitpunkt. Wenn also das genaue Datum eines Commits aus irgendeinem Grund wichtig ist, dann ist merge die bessere Option. Aber typischerweise ist eine saubere Git-History viel nützlicher als exakte Commit-Daten.

  2. Wenn der gerebaste Branch mehrere Commits hat, die dieselbe Zeile ändern, und diese Zeile auch im Basis-Branch geändert wurde, musst du möglicherweise Merge-Konflikte für dieselbe Zeile mehrmals lösen, was beim Mergen nie nötig ist. Im Durchschnitt gibt es also mehr Merge-Konflikte zu lösen.

Tipps zur Reduzierung von Merge-Konflikten bei Verwendung von rebase:

  1. Häufig rebasen. Ich empfehle, es mindestens einmal täglich zu machen.

  2. Versuche, Änderungen an derselben Zeile so weit wie möglich in einen Commit zusammenzufassen (squashen).

Ich hoffe, diese FAQ hilft dem einen oder anderen Team da draußen.

Hat dir dieser Beitrag gefallen? Folge mir auf Bluesky und Mastodon für mehr Swift-Tipps und Indie-Dev-Updates.