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:

Git-tutorial: Git rebase

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.

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. Een ontwikkelaar onderzoekt de geschiedenis van de hoofd-branch met behulp van git log. Dankzij de 'schone geschiedenis' kan de ontwikkelaar snel redeneren over de geschiedenis van het project;
  3. De ontwikkelaar kan met behulp van git log niet vaststellen wanneer de fout werd geïntroduceerd en voert daarom een git bisect uit;
  4. Doordat de git-geschiedenis schoon is, heeft git bisect een verfijnde set commits om te vergelijken bij het zoeken naar de regressie. De ontwikkelaar vindt snel de commit waarin de fout is geïntroduceerd en kan dienovereenkomstig handelen.

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.

Git rebase: Branch naar master

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 en 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 , wat elke vorm van commit-verwijzing kan zijn (bijvoorbeeld een ID, een branch-naam, een tag of een relatieve verwijzing 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 , 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.
warn Geeft een waarschuwing weer in de interactieve modus die waarschuwt voor verwijderde commits

fout

Stopt de rebase en geeft verwijderde waarschuwingsberichten voor commits weer

Negeren

Standaard ingesteld; alle ontbrekende commit-waarschuwingen worden genegeerd.
  • rebase.instructionFormat: Een git 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>

De opdracht --onto maakt een krachtigere vorm of rebase mogelijk waarmee specifieke refs de tips van een rebase kunnen worden doorgegeven.
Stel dat we een voorbeeldrepo hebben met branches zoals:


   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 . main wordt de en featureB is referentie voor het doel waarnaar HEAD van de 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 hebben gekeken naar het gebruik van git rebase met andere hulpprogramma's, zoals git reflog, git fetch en git push. Bezoek de bijbehorende pagina's voor meer informatie.

Klaar om Git te leren?

Probeer deze interactieve tutorial.

Nu aan de slag