Files
it-know-how/git/Gestapelten Feature-Branch nach einem Squash-Merge aktualisieren.md
2026-09-07 08:39:53 +02:00

10 KiB

Spickzettel: Gestapelten Feature-Branch nach einem Squash-Merge aktualisieren

Ausgangssituation:

main:       A----------------S
             \              /
feature1:     B------------C
                              \
feature2:                       D---E
  • feature1 wurde von main abgezweigt.
  • feature2 wurde von feature1 abgezweigt.
  • feature1 wurde anschließend per Squash-Merge in main integriert.
  • feature2 enthält sowohl die Änderungen von feature1 als auch eigene Änderungen.
  • Jetzt soll feature2 auf dem aktuellen main basieren und nur noch seine eigenen Änderungen zusätzlich enthalten.

Ziel:

main:       A----------------S
                              \
feature2:                       D'---E'

Die Commits D und E werden dabei als neue Commits D' und E' auf die neue Basis gesetzt.


1. Arbeitsstand prüfen

Zuerst sicherstellen, dass keine ungesicherten Änderungen vorhanden sind:

git status

Idealerweise lautet die Ausgabe sinngemäß:

nothing to commit, working tree clean

Falls noch Änderungen vorhanden sind, entweder committen oder vorübergehend weglegen:

git stash push -m "temporäre Änderungen vor Rebase"

2. Remote-Branches aktualisieren

git fetch origin

Damit werden beispielsweise diese Referenzen aktualisiert:

origin/main
origin/feature1
origin/feature2

Wichtig: origin/main ist der aktuelle Stand des Remote-Branches. Er enthält jetzt den Squash-Commit von feature1.


3. Prüfen, auf welchem Branch man arbeitet

git switch feature2

Anschließend:

git status

Es sollte erkennbar sein, dass man sich auf feature2 befindet.

Optional kann man sich die Historie grafisch ansehen:

git log --oneline --graph --decorate --all

4. Den alten Basis-Commit identifizieren

Für den Rebase muss Git wissen:

Ab welchem Commit beginnen die Änderungen, die ausschließlich zu feature2 gehören?

Wenn der Branch feature1 noch existiert und unverändert auf seinem ursprünglichen Stand steht, ist der Standardfall:

git rebase --onto origin/main origin/feature1 feature2

Das bedeutet:

git rebase --onto <neue-Basis> <alte-Basis> <Branch>

Konkret:

  • origin/main: neue Basis
  • origin/feature1: alte Basis, die nicht erneut übernommen werden soll
  • feature2: Branch, dessen eigene Commits neu aufgesetzt werden

Git nimmt also alle Commits, die in feature2, aber nicht in feature1 enthalten sind, und wendet sie auf origin/main an.

Das ist in der Regel genau das gewünschte Verhalten.


5. Vorher ein Sicherheitsbackup anlegen

Ein Rebase schreibt die Branch-Historie um. Deshalb ist ein Backup empfehlenswert:

git switch feature2
git branch backup/feature2-vor-rebase

Falls etwas schiefgeht, kann man jederzeit auf diesen Stand zurückgreifen:

git switch backup/feature2-vor-rebase

Oder den ursprünglichen Stand wiederherstellen:

git switch feature2
git reset --hard backup/feature2-vor-rebase

6. Rebase durchführen

Mit weiterhin ausgechecktem feature2:

git rebase --onto origin/main origin/feature1

Die Kurzform funktioniert hier, weil Git den aktuell ausgecheckten Branch verwendet.

Alternativ explizit:

git rebase --onto origin/main origin/feature1 feature2

Nach dem erfolgreichen Rebase sieht die Historie ungefähr so aus:

main:       A----------------S
                              \
feature2:                       D'---E'

Die Änderungen aus feature1 werden nicht noch einmal als eigene Commits übernommen. Sie sind bereits im Squash-Commit auf main enthalten.


Falls Konflikte auftreten

Konflikte sind bei diesem Vorgang möglich, insbesondere wenn:

  • der Squash-Merge in GitLab manuell angepasst wurde,
  • feature2 dieselben Dateien wie feature1 weiter verändert,
  • sich main seit dem Merge weiterentwickelt hat.

Git zeigt die betroffenen Dateien an:

git status

Die Konfliktmarkierungen sehen beispielsweise so aus:

<<<<<<< HEAD
Stand von main
=======
Stand aus feature2
>>>>>>> <commit>

Die Datei muss manuell in den gewünschten Endzustand gebracht werden. Danach:

git add pfad/zur/datei

Wenn alle Konflikte des aktuellen Rebase-Schritts gelöst sind:

git rebase --continue

Git kann anschließend weitere Konflikte melden. Dann wiederholt man:

git status
# Dateien bearbeiten
git add <dateien>
git rebase --continue

Einen Rebase abbrechen

Wenn der Rebase nicht sinnvoll fortgesetzt werden kann:

git rebase --abort

Damit wird feature2 auf den Zustand vor Beginn des Rebases zurückgesetzt.


7. Ergebnis überprüfen

Nach dem Rebase zunächst die Historie ansehen:

git log --oneline --graph --decorate -20

Außerdem prüfen, welche Änderungen feature2 gegenüber main enthält:

git diff origin/main...feature2

Das sollte im Wesentlichen nur die Änderungen des zweiten Features zeigen.

Auch die Commit-Liste kann geprüft werden:

git log --oneline origin/main..feature2

Dort sollten nur die eigenen Commits von feature2 auftauchen, nicht mehr die ursprünglichen Commits von feature1.

Zusätzlich:

git status

8. Den umgeschriebenen Branch pushen

Da ein Rebase die Commit-IDs ändert, ist ein normaler Push meist nicht möglich. Verwenden Sie:

git push --force-with-lease origin feature2

Nicht bevorzugt:

git push --force origin feature2

--force-with-lease ist sicherer, weil Git den Push ablehnt, wenn zwischenzeitlich jemand anderes neue Änderungen auf origin/feature2 veröffentlicht hat.

Danach sollte der Merge Request von feature2 nach main aktualisiert sein. Im Diff sollten nun normalerweise nur noch die Änderungen des zweiten Features erscheinen.


Der wichtigste Standardbefehl

Wenn feature1 noch existiert und auf dem alten Stand steht:

git fetch origin
git switch feature2
git branch backup/feature2-vor-rebase
git rebase --onto origin/main origin/feature1
git push --force-with-lease origin feature2

Dabei sollte man den Rebase-Schritt nicht blind überspringen, sondern Konflikte und den anschließenden Diff prüfen.


Wenn feature1 bereits gelöscht wurde

Wenn der Remote-Branch feature1 nach dem Merge gelöscht wurde, funktioniert dieser Befehl nicht mehr:

git rebase --onto origin/main origin/feature1

Dann braucht man den alten Stand von feature1, also den Commit, auf dem feature2 ursprünglich basierte.

Möglichkeit A: Lokale Referenz vorhanden

Prüfen:

git branch -a

Vielleicht existiert noch ein lokaler Branch:

feature1

Dann kann man verwenden:

git rebase --onto origin/main feature1 feature2

Möglichkeit B: Commit aus der Historie verwenden

Mit der Historie:

git log --oneline --graph --all

den letzten Commit von feature1 vor den eigenen feature2-Commits suchen.

Angenommen, dieser Commit heißt:

abc1234

Dann:

git rebase --onto origin/main abc1234 feature2

Bedeutung:

Alle Commits nach abc1234, die zu feature2 gehören, auf origin/main setzen.

Möglichkeit C: Reflog verwenden

Wenn der Commit nicht mehr über die normale Historie sichtbar ist:

git reflog

Dort kann man nach dem ursprünglichen Abzweigpunkt oder dem früheren Stand von feature1 suchen.


Variante ohne rebase --onto: eigene Commits per Cherry-Pick übernehmen

Wenn die Branch-Historie unklar ist, kann man auch einen neuen Branch auf Basis von main erstellen und nur die eigenen feature2-Commits übernehmen.

Beispiel:

git fetch origin
git switch -c feature2-neu origin/main
git cherry-pick <commit-D>^..<commit-E>

Dabei werden die eigenen Commits von feature2 einzeln übernommen.

Danach kann der neue Branch entweder als neuer Merge Request verwendet oder nach sorgfältiger Prüfung an die Stelle von feature2 gepusht werden.

Diese Variante ist nützlich, wenn:

  • feature1 gelöscht wurde,
  • die ursprüngliche Branch-Basis nicht mehr eindeutig ist,
  • feature2 nur wenige eigene Commits enthält.

Was man nicht tun sollte

Nach einem Squash-Merge von feature1 ist dieser Ablauf problematisch:

git switch feature2
git merge origin/main

Das kann funktionieren, muss aber nicht. Git sieht die ursprünglichen Commits von feature1 weiterhin in der Historie von feature2, während main einen neuen Squash-Commit enthält. Dadurch können Änderungen doppelt betrachtet oder Konflikte erzeugt werden.

Für diesen speziellen Fall ist besser:

git rebase --onto origin/main origin/feature1

Alternative: feature1 nicht squashen

Wenn häufig gestapelte Branches verwendet werden, kann es sinnvoll sein, den übergeordneten Branch feature1 nicht zu squashen, sondern per normalem Merge zu integrieren.

Dann bleibt die Commit-Abstammung erhalten:

main:       A---------M
             \       /
feature1:     B-----C
                    \
feature2:             D---E

Git kann in diesem Fall besser erkennen, dass B und C bereits in main enthalten sind.

Eine mögliche Teamregel wäre daher:

  • unabhängige Feature-Branches: Squash-Merge erlaubt
  • voneinander abhängige oder gestapelte Feature-Branches: normaler Merge ohne Squash oder anschließend gezielter Rebase

Zusammenfassung

Für Ihren Fall ist der typische Ablauf:

git status
git fetch origin
git switch feature2
git branch backup/feature2-vor-rebase
git rebase --onto origin/main origin/feature1

Bei Konflikten:

# Dateien bearbeiten
git add <dateien>
git rebase --continue

Bei Abbruch:

git rebase --abort

Nach erfolgreichem Rebase prüfen:

git diff origin/main...feature2
git log --oneline origin/main..feature2

Dann pushen:

git push --force-with-lease origin feature2

Der entscheidende Punkt ist: Nach einem Squash-Merge muss ein davon abgeleiteter Branch auf die neue main-Basis neu aufgebaut werden. Ein einfacher Merge von main in den alten gestapelten Branch ist dafür nicht die zuverlässigsten Variante.