10 KiB
Spickzettel: Gestapelten Feature-Branch nach einem Squash-Merge aktualisieren
Ausgangssituation:
main: A----------------S
\ /
feature1: B------------C
\
feature2: D---E
feature1wurde vonmainabgezweigt.feature2wurde vonfeature1abgezweigt.feature1wurde anschließend per Squash-Merge inmainintegriert.feature2enthält sowohl die Änderungen vonfeature1als auch eigene Änderungen.- Jetzt soll
feature2auf dem aktuellenmainbasieren 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
feature2gehö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 Basisorigin/feature1: alte Basis, die nicht erneut übernommen werden sollfeature2: 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,
feature2dieselben Dateien wiefeature1weiter verändert,- sich
mainseit 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 zufeature2gehören, auforigin/mainsetzen.
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:
feature1gelöscht wurde,- die ursprüngliche Branch-Basis nicht mehr eindeutig ist,
feature2nur 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.