git rebase
Dit document bevat een diepgaande bespreking van de opdracht git rebase
. De rebase-opdracht wordt ook behandeld bij het instellen van een repository en op de pagina over het herschrijven van geschiedenis. Deze pagina bevat een gedetailleerdere uiteenzetting van de configuratie en uitvoering van git rebase
. Hier komen veelvoorkomende rebase-toepassingsscenario's en valkuilen aan bod.
Rebase is een van de twee Git-hulpprogramma's die zijn gespecialiseerd in het integreren van wijzigingen uit de ene branch in de andere. Het andere hulpprogramma voor het integreren van wijzigingen is git merge
. Samenvoegen is altijd een wijzigingsrecord die vooruit werkt. Rebase heeft krachtige functies voor het herschrijven van geschiedenis. Ga voor een gedetailleerde kijk op merge t.o.v. rebase naar onze gids over merge t.o.v. rebasing. Rebase zelf heeft 2 hoofdmodi: een 'handmatige' en" 'interactieve' modus. We zullen de verschillende Rebase-modi hieronder in meer detail behandelen.
Wat is git rebase?
Rebasing is het proces waarbij een reeks commits wordt verplaatst of gecombineerd in een nieuwe basiscommit. Rebasing is het nuttigst en wordt het gemakkelijkst gevisualiseerd in de context van een workflow voor het vertakken van functies. Dit hele proces kan als volgt worden gevisualiseerd:
Vanuit inhoudsperspectief verandert rebasing de basis van je branch van de ene commit naar de andere, waardoor het lijkt alsof je je branch hebt gemaakt vanuit een andere commit. Intern realiseert Git dit door nieuwe commits te maken en deze toe te passen op de opgegeven basis. Het is heel belangrijk om te begrijpen dat de branch is samengesteld uit geheel nieuwe commits, ook al ziet hij er hetzelfde uit.
gerelateerd materiaal
Git cheat sheet
Oplossing bekijken
Git leren met Bitbucket Cloud
Gebruik
De belangrijkste reden voor rebasing is het behoud van een lineaire projectgeschiedenis. Denk bijvoorbeeld aan een situatie waarin de hoofd-branch is doorontwikkeld sinds je aan een functie-branch begon te werken. Je wilt de laatste updates van de hoofd-branch in je functie-branch, maar je wilt de geschiedenis van je branch schoon houden, zodat het lijkt alsof je aan de nieuwste hoofd-branch hebt gewerkt. Dit heeft op een later moment een schone samenvoeging van je functie-branch in de hoofd-branch als voordeel. Waarom willen we een 'schone geschiedenis' behouden? De voordelen van een schone geschiedenis worden tastbaar bij het uitvoeren van git-bewerkingen om de introductie van een regressie te onderzoeken. Een meer praktisch scenario zou zijn:
1. Er wordt een bug geïdentificeerd in de hoofd-branch. Een functie die eerst succesvol werkte, is nu kapot;
2. A developer examines the history of the main branch using git log
because of the "clean history" the developer is quickly able to reason about the history of the project.
3. The developer can not identify when the bug was introduced using git log
so the developer executes a git bisect
.
4. Because the git history is clean, git bisect
has a refined set of commits to compare when looking for the regression. The developer quickly finds the commit that introduced the bug and is able to act accordingly.
Lees meer over git log en git bisect op de specifieke gebruikspagina's.
Je hebt twee opties om je functie in de hoofd-branch te integreren: direct samenvoegen of rebase, gevolgd door samenvoegen. De eerste optie resulteert in een '3-weg merge' en een merge-commit; de laatste resulteert in een 'fast forward-merge' en een perfect lineaire geschiedenis. Het volgende diagram laat zien hoe een rebase van de hoofd-branch een 'fast forward-merge' mogelijk maakt.
Rebasing is een gebruikelijke manier om upstream wijzigingen te integreren in je lokale repository. Het doorvoeren van upstream wijzigingen met Git merge resulteert in een overbodige merge-commit zodra je wilt zien hoe het project is verlopen. Aan de andere kant is er rebase – alsof je zegt: 'Ik wil mijn wijzigingen baseren op wat iedereen al heeft gedaan'.”
Geen rebase voor openbare geschiedenis
Zoals we eerder in De geschiedenis herschrijven hebben besproken, moet je commits nooit rebasen zodra ze naar een openbare repository zijn gepusht. De rebase zou de oude commits vervangen door nieuwe, waardoor dat deel van je projectgeschiedenis abrupt lijkt te zijn verdwenen.
Git rebase standard vs git rebase interactive
Bij Git Rebase Interactive accepteert git rebase een -- i-argument
. Deze staat voor 'Interactive'. Zonder argumenten wordt de opdracht uitgevoerd in de standaardmodus. Laten we in beide gevallen aannemen dat we een aparte functie-branch hebben gemaakt.
# Create a feature branch based off of main
git checkout -b feature_branch main
# Edit files
git commit -a -m "Adds new feature"
Git rebase zal in de standaardmodus automatisch de commits in je huidige werk-branch opnemen en deze toepassen op de head van de doorgegeven branch.
git rebase <base>
Dit voert automatisch een rebase uit voor de huidige branch op <basis>
, wat elke vorm van commit-referentie kan zijn (bijvoorbeeld een ID, een branch-naam, een tag of een relatieve referentie naar HEAD
).
Als je git rebase
uitvoert met de markering -i
, wordt er een interactieve rebasing-sessie gestart. In plaats van alle commits blind naar de nieuwe basis te verplaatsen, geeft interactieve rebasing je de mogelijkheid om individuele commits in het proces te wijzigen. Zo kun je de geschiedenis opschonen door een bestaande reeks commits te verwijderen, te splitsen en te wijzigen. Het is net als Git commit --amend
, met vleugels.
git rebase --interactive <base>
Dit voert een rebase uit voor de huidige branch op <basis>
, maar maakt gebruik van een interactieve rebasing-sessie. Dit opent een editor waarin je opdrachten (hieronder beschreven) kunt invoeren voor elke commit waarvoor een rebase moet worden uitgevoerd. Deze opdrachten bepalen hoe individuele commits worden overgedragen naar de nieuwe basis. Je kunt de commit-lijst ook opnieuw ordenen om de volgorde van de commits zelf te wijzigen. Zodra je opdrachten voor elke commit in de rebase hebt opgegeven, begint Git commits af te spelen door de rebase-opdrachten toe te passen. De bewerkingsopdrachten voor rebasing zijn als volgt:
pick 2231360 some old commit
pick ee2adc2 Adds new feature
# Rebase 2cf755d..ee2adc2 onto 2cf755d (9 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
Extra rebase-opdrachten
Zoals op de pagina De geschiedenis herschrijven is beschreven, kan rebasing worden gebruikt om oudere en meerdere commits, gecommite bestanden en meerdere berichten te wijzigen. Hoewel dit de meest voorkomende toepassingen zijn, heeft git rebase
ook extra opdrachtopties die nuttig kunnen zijn in complexere toepassingen.
git rebase -- d
houdt in dat tijdens het afspelen de commit wordt weggegooid uit het laatste gecombineerde commit-blok;git rebase — p
laat de commit ongewijzigd. De opdracht zal het bericht of de inhoud van de commit niet wijzigen en zal nog steeds een individuele commit zijn in de geschiedenis van de branches;git rebase -- x
voert tijdens het afspelen een opdrachtregel-shellscript uit op elke gemarkeerde commit. Om een nuttig voorbeeld te noemen: je kunt de testsuite van je codebasis uitvoeren op specifieke commits, wat kan helpen bij het identificeren van regressies tijdens een rebase.
Samenvatting
Interactieve rebasing geeft je volledige controle over hoe je projectgeschiedenis eruit ziet. Dit biedt ontwikkelaars veel vrijheid, omdat ze daardoor een 'rommelige' geschiedenis kunnen vastleggen terwijl ze zich richten op het schrijven van code. Vervolgens kunnen ze terugkeren en alles daarna 'opruimen'.
De meeste ontwikkelaars gebruiken graag een interactieve rebase om een functie-branch op te poetsen voordat ze deze samenvoegen in de hoofdcode. Dit biedt hen de mogelijkheid om onbeduidende commits te vernietigen, verouderde te verwijderen en ervoor te zorgen dat al het andere in orde is voordat ze commits maken aan de 'officiële' projectgeschiedenis. Het ziet er voor iedereen uit alsof de hele functie is ontwikkeld in een enkele reeks goed geplande commits.
De echte kracht van interactief rebasing is te zien in de geschiedenis van de resulterende hoofd-branch. Het lijkt er voor iedereen op dat je een briljante ontwikkelaar bent die de nieuwe functie de eerste keer met de perfecte hoeveelheid commits heeft geïmplementeerd. Zo kan interactief rebasen ervoor zorgen dat de geschiedenis van een project schoon en zinvol blijft.
Configuratie-opties
Er zijn een paar rebase-eigenschappen die kunnen worden ingesteld met behulp van git config
. Deze opties zullen de look en feel van de git rebase
-uitvoer veranderen.
rebase.stat
: Een booleaanse waarde die standaard is ingesteld op 'false'. De optie wisselt de weergave van visuele diffstat-inhoud die laat zien wat er is veranderd sinds de laatste rebase.rebase.autoSquash:
Een booleaanse waarde die het gedrag--autosquash
in- of uitschakelt.rebase.missingCommitsCheck:
Kan worden ingesteld op meerdere waarden die het gedrag van rebase rond ontbrekende commits wijzigen.
| Geeft een waarschuwing weer in de interactieve modus die waarschuwt voor verwijderde commits |
| Stopt de rebase en geeft verwijderde waarschuwingsberichten voor commits weer |
| Standaard ingesteld; alle ontbrekende commit-waarschuwingen worden genegeerd. |
rebase.instructionFormat:
Eengit log
-opmaaktekenreeks die zal worden gebruikt voor het opmaken van interactieve rebase-weergave
Geavanceerde toepassing van rebase
Het opdrachtregelargument --onto
kan worden doorgegeven aan git rebase
. In de git rebase --onto
-modus wordt de opdracht uitgebreid naar:
git rebase --onto <newbase> <oldbase>
The --onto
command enables a more powerful form or rebase that allows passing specific refs to be the tips of a rebase. Let’s say we have an example repo with branches like:
o---o---o---o---o main
\
o---o---o---o---o featureA
\
o---o---o featureB
featureB is gebaseerd op featureA, maar we realiseren ons dat featureB niet afhankelijk is van een van de wijzigingen in featureA en gewoon kan worden vertakt van de hoofd-branch.
git rebase --onto main featureA featureB
featureA is de <oldbase>
. main
wordt de <newbase>
en featureB is referentie voor het doel waarnaar HEAD
van de <newbase>
zal verwijzen. De resultaten zijn dan:
o---o---o featureB
/
o---o---o---o---o main
\
o---o---o---o---o featureA
De gevaren van rebase begrijpen
Eén nadeel waarmee rekening moet worden gehouden wanneer met Git Rebase wordt gewerkt, is dat samenvoegingsconflicten vaker voorkomen tijdens een rebase-workflow. Dit gebeurt als je een lang bestaande branch hebt die is afgeleid van de hoofd-branch. Uiteindelijk wil je een rebase uitvoeren voor de hoofd-branch en deze kan op dat moment veel nieuwe commits bevatten waarmee je branch-wijzigingen in conflict kunnen komen. Dit kan eenvoudig worden verholpen door regelmatig een rebase uit te voeren voor je branch tegen de hoofd-branch en door vaker commits te maken. De opdrachtregelargumenten --continue
en --abort
kunnen worden doorgegeven aan git rebase
om de rebase uit te voeren of opnieuw in te stellen in het geval van conflicten.
Een ernstiger nadeel van rebase zijn de kwijtgeraakte commits als gevolg van het interactief herschrijven van de geschiedenis. Als je een rebase uitvoert in de interactieve modus en subopdrachten zoals 'squash' of 'drop' uitvoert, worden commits verwijderd uit het directe logboek van je branche. Op het eerste gezicht kan het lijken alsof de commits permanent zijn verdwenen. Met behulp van git reflog
kunnen deze commits worden hersteld en kan de volledige rebase ongedaan worden gemaakt. Ga voor meer informatie over het gebruik van git reflog
om verloren commits te vinden naar onze Git reflog-documentatiepagina.
Git Rebase is op zichzelf niet heel riskant. De echte risico's doen zich voor bij het herschrijven van de geschiedenis en het geforceerd pushen van de resultaten naar een externe branch die door andere gebruikers wordt gedeeld. Dit een patroon moet worden vermeden omdat het het werk van andere externe gebruikers kan overschrijven wanneer ze een pull uitvoeren.
Herstellen van upstream rebase
Als een andere gebruiker een rebase heeft uitgevoerd en een geforceerde push heeft uitgevoerd naar de branch waarin je commits maakt, zal een git pull
alle commits die je op die vorige branch hebt gebaseerd overschrijven met de tip die geforceerd is gepusht. Gelukkig kun je met git reflog
de reflog van de remote branch opvragen. Je vindt in de reflog van de externe branch een ref vinden van voordat een rebase werd uitgevoerd. Je kunt vervolgens een rebase uitvoeren voor je branch op die remote ref met behulp van de optie --onto
, zoals hierboven besproken onder het kopje 'Geavanceerde toepassing van rebase'.
Samenvatting
In dit artikel hebben we het gebruik van git rebase
behandeld. We bespraken basisscenario's, geavanceerde gebruiksscenario's en meer geavanceerde voorbeelden. Enkele belangrijke discussiepunten zijn:
- Git Rebase Standard t.o.v. Git Rebase Interactive
- Configuratie-opties voor git rebase
- git rebase --onto
- Kwijtgeraakte commits na git rebase
We looked at git rebase
usage with other tools like git reflog, git fetch, and git push. Visit their corresponding pages for further information.
Deel dit artikel
Volgend onderwerp
Aanbevolen artikelen
Bookmark deze resources voor meer informatie over soorten DevOps-teams of voor voortdurende updates over DevOps bij Atlassian.