Git 🌙
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