Chapters ā–¾ 2nd Edition

3.6 Git fƶrgreningar - Grenflytt

Grenflytt

I Git finns i huvusak tvƄ sƤtt att integrera Ƥndringar frƄn en gren in i en annan: sammanslagning (merge) eller grenflytt (rebase). I detta avsnitt kokker du fƄr lƤra dig vad en grenflytt Ƥr, hur man gƶr det och varfƶr det Ƥr ett ganska hƤpnadsvƤckande verktyg, samt i vilka fall du inte vill anvƤnda det.

Den grundlƤggande grenflytten

Om du gƄr tillbaks till ett tidigare exempel frƄn GrundlƤggande sammanslagning, kan du se att du divergerade ditt arbete och gjorde versioner pƄ tvƄ olika grenar.

Enkel divergent historik.
Figur 35. Enkel divergent historik

Det Ƥnklaste sƤttet att integrera grenar Ƥr, som vi redan gƄtt igenom, kommandot merge. Den genomfƶr en trevƤgssammanslagning mellan de tvƄ senaste ƶgonblicksbilderna (C3 och C4) och den senaste gemensamma versionen av de tvƄ grenarna (C2) och skapar en ny ƶgonblicksbild (och version).

Sammanslagning fƶr att integrera divergerad versionshistorik.
Figur 36. Sammanslagning fƶr att integrera divergerad versionshistorik

Det finns emellertid ett annat sƤtt: Du kan ta Ƥndringarna som introducerades i C4 och tillƤmpa den pƄ toppen av C3. I Git kallas detta fƶr grenflytt (eng. rebasing). Med kommandot rebase kan du ta alla Ƥndringar som sparats i en gren och spela upp dem pƄ en annan gren.

I detta exemplet kommer du checka ut experiment-grenen och sedan flytta grenen till master-grenen som fƶljer:

$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command

Denna operation fungerar genom att hitta den senaste gemensamma versionen fƶr de tvƄ grenarna (den du stƄr pƄ och den du skall flytta din gren till), ta reda pƄ skillnaderna som introducerats i varje version av den gren du stƄr pƄ, spara dessa i temporƤra filer, peka om den aktuella grenen till toppen av den gren som du skall flytta din aktuella gren till, och sedan applicera Ƥndringarna i turordning.

Flytta Ƥndringarna som introducerats i `C4` pƄ toppen av `C3`.
Figur 37. Flytta Ƥndringarna som introducerats i C4 pƄ toppen av C3

Nu kan du gƄ tillbaka till master-grenen och gƶra en sammanslagning via snabbspolning.

$ git checkout master
$ git merge experiment
Snabbspolning av master-grenen.
Figur 38. Snabbspolning av master-grenen

Ɩgonblicksbilden som C4' pekar pĆ„ Ƥr exakt samma som den som C5 pekade pĆ„ i the merge example. Det finns ingen skillnad i slutprodukten av integrationen, men att flytta grenen gƶr att historiken blir renare. Om du undersƶker historiken av en flyttad gren kommer den vara linjƤr: det verkar som att allt arbete har skett sekvensiellt, trots att det egentligen skedde parallellt.

Ofta vill du gƶra detta fƶr att sƤkerstƤlla att dina versioner kan lƤggas till rent pĆ„ en fjƤrrgren — kanske i ett projekt som du fƶrsƶker bidra till men som du inte underhĆ„ller. I detta fall gƶr du ditt arbete i en gren och sedan flyttar ditt arbet in i origin/master nƤr du Ƥr redo att publicera dina Ƥndringar till huvudprojektet. PĆ„ detta vis behƶver inte den som underhĆ„ller projektet gƶra nĆ„got integrationsarbete — endast snabbspola eller lƤgga till Ƥndringarna rent.

Notera att ƶgonblicksbilden som pekas pĆ„ av den slutliga versionen, oavsett om det Ƥr den senaste av de flyttade versionerna fƶr en grenflytt eller den slutliga versionen efter en sammanslagning Ƥr samma ƶgonblicksbild — det Ƥr bara historiken som skiljer. Grenflytt spelar upp Ƥndringarna frĆ„n en arbetshistorik pĆ„ toppen av en annan i samma ordning de introducerades, medan sammanslagning tar Ƥndpunkterna pĆ„ varje gren och slĆ„r ihop dem.

Mer intressanta grenflyttar

Du kan ocksƄ spela upp din historik pƄ nƄgot annat Ƥn den ursprungliga basgrenen. Ta en historik som Historik med en gren frƄn en annan gren, till exempel. Du gjorde en gren (server) fƶr att lƤgga till lite serverfunktionalitet till ditt projekt och skapade en ny version. DƤrefter gjorde du en gren frƄn denna fƶr att gƶra motsvarande Ƥndringar hos klienten (client) och gjorde nƄgra nya versioner. Slutligen gick du tillvaks till din servergren och gjorde nƄgra fler versioner.

Historik med en gren frƄn en annan gren.
Figur 39. Historik med en gren frƄn en annan gren

Antag att du beslutar att du vill slƄ samman din klientfunktionalitet till ditt huvudspƄr fƶr att frislƤppa dem, men att du vill avvakta serverƤndringarna tills dessa Ƥr testade. Du kan ta klietƤndringarna som inte Ƥr pƄ server (C8 och C9) och spela upp dem pƄ din master gren genom att anvƤnda flaggan --onto till git rebase:

$ git rebase --onto master server client

Detta betyder i praktiken ā€œTa client grenen, ta reda pĆ„ de patchar sedan den divergerade frĆ„n server grenen, och spela upp dem pĆ„ klient delen som om de vore baserade direkt frĆ„n master grenen istƤllet.ā€ Det Ƥr lite komplext, men resultatet Ƥr rƤtt hƤftigt.

Flytta en gren frƄn en annan gren.
Figur 40. Flytta en gren frƄn en annan gren
$ git checkout master
$ git merge client
Snabbspola din master-gren till att inkludera klientgrenens Ƥndringar.
Figur 41. Snabbspola din master-gren till att inkludera klientgrenens Ƥndringar

SƤg att du beslutar att dra in din servergren ocksĆ„. Du kan spela upp servergrenen pĆ„ master grenen utan att behƶva checka ut den fƶrst genom att kƶra git rebase <basgren> <stickspĆ„r> — vilket checkar ut stickspĆ„ret (server i detta fall) fƶr dig och spelar up den pĆ„ basgrenen (master):

$ git rebase master server

Detta spelar upp ditt arbete i server ovan pƄ ditt arbete i master, som synes i Flytta din servergren til toppen av din mastergren.

Flytta din servergren til toppen av din mastergren.
Figur 42. Flytta din servergren til toppen av din mastergren

DƤrefter kan du snabbspola din basgren (master):

$ git checkout master
$ git merge server

Du kan ta bort grenarna client och server eftersom allt arbete Ƥr integrerat, vilket ger dig en historik fƶr denna process likt Slutlig versionshistorik:

$ git branch -d client
$ git branch -d server
Slutlig versionshistorik.
Figur 43. Slutlig versionshistorik

Farorna med grenflyttar

Ahh, lyckan med grenflytt Ƥr inte helt utan nackdelar, vilka kan sammanfattas i en mening:

Flytta inte versioner som existerar utanfƶr ditt lokala repo som andra kan ha baserat sitt arbete pƄ.

Fƶljer du det tipset sƄ kommer allt gƄ bra. Om inte, kommer folk hata dig och du kommer att hƄnas av dina vƤnner och familj.

NƤr du flyttar om saker ƶverger du existerande versioner och skapar nya som Ƥr lika, men annorlunda. Om du publicerar versioner nƄgonstans och andra hƤmtar dem och baserar arbete pƄ dem och du sedan skriver om historiken med git rebase och publicerar dessa Ƥndringarna igen, kommer dina medarbetare att behƶva Ƅterintegrera sitt arbete och saker kommer bli krƄnligt nƤr du fƶrsƶker integrera deras Ƥndringar i dina.

LƄt oss ta ett exempel pƄ hur det kan uppstƄ problem om du skriver om arbete som du gjort publikt. Antag att du klonar frƄn en central server och sedan gƶr lite arbete pƄ det. Din versionshistorik ser ut sƄhƤr:

Klona ett repo och gƶr lite jobb pƄ det.
Figur 44. Klona ett repo och gƶr lite jobb pƄ det

Nu gƶr nƄgon annan mer arbete som inkluderar en sammanslagning och publicerar det arbetet till den centrala servern. Du hƤmtar det och slƄr ihop fjƤrrgrenen in i ditt arbete vilket gƶr att din versionshistorik ser ut ungefƤr sƄhƤr:

HƤmta fler versioner och slƄ ihop Ƥndringarna i ditt arbete.
Figur 45. HƤmta fler versioner och slƄ ihop Ƥndringarna i ditt arbete

Sedan bestƤmmer sig personen som publicerade Ƥndringarna att gƄ tillbaks och skriva om sin historik istƤllet; de gƶr git push --force fƶr att skriva ƶver den historik som finns pƄ servern. Du hƤmtar sedan frƄn den server, och fƄr hem de nya versionerna.

NƄgon publicerar omskriven historik, och ƶverger versioner pƄ vilka du baserat arbete.
Figur 46. NƄgon publicerar omskriven historik, och ƶverger versioner pƄ vilka du baserat arbete

Nu sitter ni bƄda i skiten. Om du gƶr git pull kommer du skapa en sammanslagningsversion som inkluderar bƄda versionstidslinjerna, och ditt repo kommer se ut sƄhƤr:

Du integrerar samma arbete igen i en ny sammanslagningsversion.
Figur 47. Du integrerar samma arbete igen i en ny sammanslagningsversion

Om du kƶr git log nƤr din historik ser ut sƄhƤr kommer du se tvƄ versioner som har samma fƶrfattare, datum och meddelande, vilket kommer vara fƶrvirrande. Vidare, om du publicerar denna historik tillbaks till servern, kommer du Ƅterintroducera alla de tidigare omskrivna versionerna vilket kan fƶrvirra andra ocksƄ. Man kan vara ganska sƤker pƄ att den andra utvecklaren inte vill att C4 och C6 skall vara i historiken; det Ƥr dƤrfƶr de skrev om historiken frƄn bƶrjan.

Flytta en gren nƤr du flyttar en gren

Om du dƤremot finner att du Ƥr i en liknande sitiation, sƄ har Git lite ytterligare magi som kan komma vƤl till pass. Om nƄgon i ditt team trycker ut en Ƥndring som skriver ƶver arbete som du baserar arbete pƄ, blir din utmaning att ta reda pƄ vad som Ƥr ditt och vad de har skrivit om.

Det faller sig sĆ„ att utƶver till versionens SHA-1 checksimma, berƤknar Git ocksĆ„ en checksumma baserat pĆ„ just den patch som introducerades med versionen. Denna kallas fƶr ā€œpatch-idā€.

Om du hƤmtar hem arbete som var omskrivet och gƶr en egen omskrivning pƄ toppen av versionerna frƄn din kollega, kan Git ofta lista ut vad som Ƥr unikt ditt och och applicera dina Ƥndringar ovanpƄ den nya grenen.

Till exempel, i fƶregƄende scenario, om du istƤllet fƶr att slƄ ihop Ƥndringarna vid NƄgon publicerar omskriven historik, och ƶverger versioner pƄ vilka du baserat arbete och kƶr git rebase teamone/master, kommer Git att:

  • Ta reda pĆ„ vilket arbete som Ƥr unikt fƶr vĆ„r gren (C2, C3, C4, C6, C7)

  • Ta reda pĆ„ vilka som inte Ƥr sammanslagningsversioner (C2, C3, C4)

  • Ta reda pĆ„ vad som inte har skrivits om i mĆ„lgrenen (bara C2 och C3, eftersom C4 Ƥr samma patch som C4')

  • Applicera de Ƥndringarna ovanpĆ„ teamone/master

SƄ istƤllet fƶr resultatet i Du integrerar samma arbete igen i en ny sammanslagningsversion kommer vi fƄ ett slutligt resultat liknande Grenflytt till toppen av en tvingande publicering av omskriven historik.

Grenflytt till toppen av en tvingande publicering av omskriven historik.
Figur 48. Grenflytt till toppen av en tvingande publicering av omskriven historik

Detta fungerar bara om C4 och C4' som din kollega gjort Ƥr nƤst intill samma patch. Annars kommer Git inte kunna avgƶra att de Ƥr duplikat och kommer lƤgga till ytterligare en C4-lik patch (som fƶrmodligen inte kommer gƄ att applicera rent, eftersom Ƥndringarna bitvis redan Ƥr pƄ plats).

Du kan ocksƄ fƶrenklad detta genom att kƶra git pull --rebase istƤllet fƶr en vanlig git pull. Eller sƄ kan du gƶra det manuellt genom git fetch fƶljt av git rebase teamone/master i detta fallet.

AnvƤnder du git pull och vill gƶra --rebase till normalfallet, kan du sƤtta konfigureringsparametern pull.rebase med nƄgot liknande git config --global pull.rebase true.

Om du nƤgonsin skriver om historik som bara finns lokalt pƄ din dator kommer du vara helt sƤker. Om du skriver om historik som Ƥr publicerade, men som ingen annan baserat versioner pƄ, kommer du ocksƄ vara helt sƤker. Om du skriver om versionshistorik som redan har publicerats publikt, och som folk har baserat arbete pƄ, kommer du hamna i frustrerande trubbel och hƄnas av dina teammedlemmar.

Om du eller en kollega anser det vara nƶdvƤndigt vid nƄgot tillfƤlle, se till att alla vet om att de skall kƶra git pull --rebase fƶr att gƶra den efterfƶljande pinan nƄgot lƤttare.

Omskrivning vs. Sammanslagning

Nu nƤr du sett hur omskrivning och sammanslagning fungerar, kanske du undrar vilken som Ƥr bƤst. Innan vi kan svara pƄ det, lƄt oss ta ett steg tillbaka och prata om vad historik betyder.

En infallsvinkel pƄ det Ƥr att dit repos versionshistorik Ƥr en beskrivning ƶver vad som faktiskt hƤnde. Det Ƥr ett historiskt dokument, vƤrdefull i sig sjƤlv, och skall inte manipuleras. Med denna vinkel Ƥr Ƥndring av versionshistoriken nƤrmast blasfemi; du ljuger om vad som faktisk skedde. Vad gƶr det om det Ƥr en stƶkig historik av sammanslagningsversioner? Det var sƄ det hƤnde, och repot skall bevara det fƶr eftervƤrlden.

En motstƄende infallsvinkel Ƥr att versionshistoriken Ƥr berƤttelsen av hur ditt projekt skapades. Du publicerar inte fƶrsta utkastet av en bok, och manualen fƶr hur du underhƄller din mjukvara fƶrtjƤnar noggrann redigering. Detta Ƥr lƤgret som anvƤnder verktyg som omskrivning och filter-grenar fƶr att berƤtta historien som bƤst lƤmpar sig fƶr framtida lƤsare.

Nu till frƄgan huruvida sammanslagning eller omskrivning Ƥr bƤttre: fƶrhoppningsvis inser du att det inte Ƥr sƄ enkelt. Git Ƥr ett kraftfullt verktyg och tillƄter dig att gƶra mƄnga saker med din historik, men alla team och alla projekt Ƥr olika. Nu nƤr du vet hur bƄda dessa verktyg fungerar, Ƥr det upp till dig att avgƶra vilken metod som Ƥr bƤst lƤmpad i din specifika situation.

I allmƤnhet, fƶr att fƄ det bƤsta frƄn tvƄ vƤrldar, Ƥr att skriva om lokala Ƥndringar du gjort men Ƥnnu inte delat innan du publicerar dem i syfte att rensa upp din historik, men att aldrig skriva om historik du publicerat nƄnstans.

scroll-to-top