Chapters ▾ 2nd Edition

3.1 Git-grenar - Grenar i korthet

Nästan alla versionshanteringssystem har någon form av stöd för grenar. Att arbeta med grenar innebär att du lämnar huvudlinjen för utveckling och fortsätter att arbeta utan att störa den. I många VCS-verktyg är detta en ganska dyr process som ofta kräver att du skapar en ny kopia av källkodskatalogen, vilket kan ta lång tid för stora projekt.

En del kallar Gits grenmodell för dess "unika signum", och den gör verkligen att Git sticker ut i VCS-världen. Vad är det som är så speciellt? Gits sätt att hantera grenar på är extremt lättviktigt, vilket gör att grenåtgärder går nästan omedelbart, och att byta fram och tillbaka mellan grenar ofta går snabbt. Till skillnad från många andra VCS:er uppmuntrar Git arbetsflöden där man ofta skapar grenar och slår ihop dem, till och med flera gånger om dagen. Att förstå och behärska den här funktionen ger dig ett kraftfullt och unikt verktyg och kan helt förändra hur du utvecklar.

Grenar i korthet

För att verkligen förstå hur Git hanterar grenar behöver vi ta ett steg tillbaka och titta på hur Git lagrar data.

Som du kanske minns från Vad är Git? lagrar Git inte data som en serie ändringar eller ändringsmängder, utan som en serie ögonblicksbilder.

När du gör en incheckning sparar Git ett incheckningsobjekt som innehåller en pekare till ögonblicksbilden av innehållet du köade. Objektet innehåller också författarens namn och e-postadress, meddelandet du skrev och pekare till de incheckningar som kom direkt före (dess föräldrar): inga föräldrar för den första incheckningen, en förälder för en vanlig incheckning och flera föräldrar för en incheckning som är resultatet av en sammanslagning av två eller fler grenar.

För att visualisera detta kan vi anta att du har en katalog med tre filer, att du köar dem och gör en incheckning. När du köar filerna beräknas en kontrollsumma för varje fil (SHA-1-hashen som nämndes i Vad är Git?), Git lagrar filversionen i kodförrådet (Git kallar dem blobbar) och lägger kontrollsumman i köytan:

$ git add README test.rb LICENSE
$ git commit -m 'Initial commit'

När du skapar incheckningen med git commit beräknar Git en kontrollsumma för varje underkatalog (i det här fallet bara projektets rotkatalog) och lagrar dem som ett trädobjekt i kodförrådet. Git skapar sedan ett incheckningsobjekt med metadata och en pekare till projektets rotträd så att ögonblicksbilden kan återskapas vid behov.

Ditt Git-kodförråd innehåller nu fem objekt: tre blobbar (var och en representerar innehållet i en av de tre filerna), ett träd som listar katalogens innehåll och pekar ut vilka filnamn som motsvarar vilka blobbar, och en incheckning med pekare till rotträdet och dess metadata.

En incheckning och dess träd
Figur 9. En incheckning och dess träd

Om du gör ändringar och checkar in igen sparar nästa incheckning en pekare till den som kom direkt före.

Incheckningar och deras föräldrar
Figur 10. Incheckningar och deras föräldrar

En gren i Git är helt enkelt en lätt flyttbar pekare till en av dessa incheckningar. Standardgrenens namn i Git är master. När du börjar göra incheckningar får du en master-gren som pekar på den senaste incheckningen. Varje gång du checkar in flyttas master-pekaren framåt automatiskt.

Notera

“master”-grenen i Git är inte något särskilt. Den är precis som vilken annan gren som helst. Det enda skälet till att nästan alla kodförråd har den är att git init skapar den som standard och att de flesta inte ändrar det.

En gren och dess incheckningshistorik
Figur 11. En gren och dess incheckningshistorik

Skapa en ny gren

Vad händer när du skapar en ny gren? Du skapar helt enkelt en ny pekare som du kan flytta runt. Säg att du vill skapa en gren som heter testing. Det gör du med git branch:

$ git branch testing

Det här skapar en ny pekare till samma incheckning som du står på just nu.

Två grenar pekar på samma serie incheckningar
Figur 12. Två grenar pekar på samma serie incheckningar

Hur vet Git vilken gren du står på? Den håller en särskild pekare som heter HEAD. Observera att detta skiljer sig från HEAD i andra VCS:er som Subversion eller CVS. I Git är det en pekare till den lokala gren du för närvarande står på. I det här fallet är du fortfarande på master. git branch skapade bara en ny gren – den bytte inte till den.

HEAD pekar på en gren
Figur 13. HEAD pekar på en gren

Du kan lätt se detta genom att köra git log med flaggan --decorate, som visar var grenpekare ligger.

$ git log --oneline --decorate
f30ab (HEAD -> master, testing) Add feature #32 - ability to add new formats to the central interface
34ac2 Fix bug #1328 - stack overflow under certain conditions
98ca9 Initial commit

Du ser att master och testing pekar på samma incheckning f30ab.

Byta gren

För att byta till en befintlig gren kör du git checkout. Låt oss byta till den nya grenen testing:

$ git checkout testing

Det flyttar HEAD så att den pekar på testing.

HEAD pekar på aktuell gren
Figur 14. HEAD pekar på aktuell gren

Vad betyder det? Låt oss göra en incheckning:

$ vim test.rb
$ git commit -a -m 'Make a change'
HEAD-grenen flyttas fram när en incheckning görs
Figur 15. HEAD-grenen flyttas fram när du checkar in

Det intressanta är att din testing-gren har flyttat fram, medan master fortfarande pekar på incheckningen du stod på när du bytte gren. Låt oss byta tillbaka till master:

$ git checkout master
Notera
git log visar inte alltid alla grenar

Om du kör git log just nu kan du undra vart testing tog vägen, eftersom den inte syns i utskriften.

Grenen finns kvar; Git visar bara den historik den tror att du är intresserad av. Som standard visar git log endast historiken under den gren du har växlat till.

För att visa historiken för den gren du vill se behöver du ange den explicit: git log testing. För att visa alla grenar lägger du till --all.

HEAD flyttas när du växlar till
Figur 16. HEAD flyttar när du växlar till

Kommandot gjorde två saker. Det flyttade HEAD-pekaren tillbaka till master och återställde filerna i arbetskatalogen till ögonblicksbilden som master pekar på. Det betyder också att ändringar du gör från och med nu kommer att divergera från en äldre version av projektet. I praktiken spolar det tillbaka arbetet du gjorde i testing så att du kan gå i en annan riktning.

Notera
Byta gren ändrar filer i arbetskatalogen

Det är viktigt att veta att när du byter gren i Git ändras filerna i arbetskatalogen. Om du byter till en äldre gren återställs arbetskatalogen till hur den såg ut när du senast checkade in på den grenen. Om Git inte kan göra detta utan konflikter får du inte byta gren.

Låt oss göra några ändringar och checka in igen:

$ vim test.rb
$ git commit -a -m 'Make other changes'

Nu har projektets historik divergerat (se Divergerad historik). Du skapade och bytte till en gren, gjorde arbete där, och bytte sedan tillbaka till huvudgrenen och gjorde annat arbete. Båda ändringarna är isolerade i separata grenar: du kan byta fram och tillbaka mellan dem och sammanfoga dem när du är redo. Och allt detta gjorde du med branch, checkout och commit.

Divergerad historik
Figur 17. Divergerad historik

Du kan också se detta med git log. Om du kör git log --oneline --decorate --graph --all skrivs hela historiken ut, samt var grenpekare finns och hur historiken har divergerat.

$ git log --oneline --decorate --graph --all
* c2b9e (HEAD, master) Make other changes
| * 87ab2 (testing) Make a change
|/
* f30ab Add feature #32 - ability to add new formats to the central interface
* 34ac2 Fix bug #1328 - stack overflow under certain conditions
* 98ca9 Initial commit of my project

Eftersom en gren i Git i grunden är en fil som innehåller den 40 tecken långa SHA-1-kontrollsumman för incheckningen den pekar på, är grenar billiga att skapa och ta bort. Att skapa en ny gren är i praktiken lika enkelt som att skriva 41 byte till en fil (40 tecken plus radbrytning).

Detta står i skarp kontrast till äldre VCS-verktyg där grenar innebär att hela projektets filer kopieras till en ny katalog. Det kan ta sekunder eller minuter beroende på projektets storlek, medan Git alltid är omedelbart. Eftersom vi lagrar föräldrarna till varje incheckning hittar Git automatiskt en lämplig bas för sammanslagningar, vilket gör dem enkla att genomföra. Detta uppmuntrar utvecklare att skapa och använda grenar ofta.

Låt oss se varför.

Notera
Skapa och byta gren samtidigt

Det är vanligt att vilja skapa en ny gren och byta till den direkt. Det kan göras i ett steg med git checkout -b <nyttgrennamn>.