Git
Chapters ▾ 2nd Edition

3.1 Клонове в Git - Накратко за разклоненията

Почти всички VCS системи разполагат с поддръжка на разклонения на версиите на кода под някаква форма. Разклоняването на кода означава, че вие се отделяте от основната линия на разработка (във ваш собствен клон, branch) и продължавате да работите без да се намесвате в тази основна линия. В множеството VCS системи това е процес, който изисква много ресурси и често сте принудени да копирате цялата си директория със сорс код, което може да е бавно при големи проекти.

Някои хора определят branching модела на Git като неговата най-силна черта и в действителност това е едно от нещата, които помагат на Git да изпъква сред другите VCS системи. Кое е толкова специално? Начинът, по който Git имплементира клоновете код е изключително олекотен, което прави branching операциите почти мигновени - това важи със същата сила и за превключването напред и назад по различните клонове код без оглед на мащаба на проекта. За разлика от другите VCS системи, Git окуражава работните процеси съдържащи чести разклонявания и сливания - дори по няколко пъти на ден. Ако успеете да овладеете тази страна на Git, ще разполагате с един мощен и уникален инструмент, който значително ще подобри и улесни методите ви на разработка.

Накратко за разклоненията

(Под клон, разклонение и branch ще имаме предвид едно и също нещо.) За да разберем как в действителност Git реализира разклоненията, трябва да се върнем стъпка назад и да си припомним как Git съхранява своите данни.

Както може би помните от Начало, Git не съхранява информацията си като серии от промени или разлики, а вместо това пази серии от моментното състояние на проекта - snapshots.

Когато правите къмит, Git съхранява един commit обект, който съдържа указател към snapshot-а на индексираното съдържание (това, което е в индексната област). Този обект също така съдържа името и имейла на автора, къмит съобщението и също така - указатели към къмита или къмитите, които са правени директно преди текущия къмит (тоест, неговите родител/родители): първоначалният къмит няма родители, нормалният къмит има един родител, а къмитът създаден в резултат от сливане на няколко клона има множество родители.

За да илюстрираме това, нека допуснем, че имате директория с три файла и сте ги индексирали и къмитнали. Процесът по индексирането на файловете (staging) изчислява чексума за всеки от файловете (това е SHA-1 хеш стрингът за който говорихме по-рано в Начало), записва версията на всеки файл в хранилището (Git третира файловете като blob-обекти) и добавя чексумите в индексната област (staging area):

$ git add README test.rb LICENSE
$ git commit -m 'The initial commit of my project'

След като изпълните git commit, Git изчислява чексума за всяка поддиректория (в този случай само основната директория на проекта) и ги съхранява като дървовиден обект в Git хранилището. След това Git създава commit-обект, който съдържа метаданните и указател към root-дървото на проекта, така че да може да пресъздаде snapshot-а (тоест йерархията от файлове и директории) по-късно, когато е необходимо.

Вашето Git хранилище сега съдържа 5 обекта: по един blob за всеки от трите файла, едно дърво описващо съдържанието на директорията и указващо кой файл под формата на кой blob се съхранява, и един къмит с указател към това основно дърво и всички метаданни за къмита.

Един къмит и неговото дърво.
Figure 9. Един къмит и неговото дърво

Ако направите някакви промени и къмитнете отново, следващият къмит ще съхранява указател към къмита направен веднага преди него.

Къмити и родителски къмити.
Figure 10. Къмити и родителски къмити

Разклонението код (branch) в Git е просто олекотен, променлив указател към един от тези къмити. Името на разклонението по подразбиране за Git е master. Когато започнете да правите къмити, вие разполагате с master branch, който сочи към последния къмит, който сте направили. Всеки път, когато къмитвате, той автоматично се премества и сочи към последния къмит.

Note

“master” клонът в Git не бива да се разглежда като специален такъв. Той е подобен на всички останали клонове. Единствената причина почти всяко хранилище да има master клон е, че командата git init го създава по подразбиране и повечето хора не си правят труда да му сменят името.

branch и неговата commit история.
Figure 11. branch и неговата commit история

Създаване на ново разклонение

Какво се случва, когато създадете нов клон? Git просто създава нов указател за вас, който да може да се премества. Да кажем, че създавате клон с име testing. Това се прави с командата git branch:

$ git branch testing

Това създава нов указател към същия къмит, на който сте в момента.

Два клона сочещи към една и съща серия къмити.
Figure 12. Два клона сочещи към една и съща серия къмити

Как Git знае в кой клон сте в даден момент? Системата си пази специален указател, който се нарича HEAD. Отбележете, че това е съвсем различно от HEAD концепциите в други VCS от рода на Subversion или CVS. В Git, това е указател към текущия локален клон от хранилището ви. В този случай, вие още сте в master клона. Това е така, защото git branch командата само създаде новия клон, но не превключи към него.

HEAD указател сочещ към текущия branch.
Figure 13. HEAD указател сочещ към текущия branch

Можете лесно да видите това изпълнявайки командата git log --decorate, която отпечатва накъде сочат указателите на разклоненията.

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

Виждате “master” и “testing” клоновете веднага до f30ab-къмита.

Превключване на разклонения

За да превключите към съществуващ клон, изпълнете командата git checkout. Нека превключим към testing клона:

$ git checkout testing

Това премества HEAD указателя и сега той сочи към testing клона.

HEAD сочи към текущия клон.
Figure 14. HEAD сочи към текущия клон

Какво означава това? Нека направим още един къмит:

$ vim test.rb
$ git commit -a -m 'made a change'
HEAD клонът се премества напред при направен къмит.
Figure 15. HEAD клонът се премества напред при направен къмит

Това е интересно, защото сега вашият testing клон се премести напред, но master клонът все още сочи към къмита, в който бяхте когато изпълнихте git checkout за да превключите разклоненията. Нека се върнем отново на master клона:

$ git checkout master
HEAD се премества когато превключвате.
Figure 16. HEAD се премества когато превключвате

Тази команда направи две неща. Тя премести HEAD указателя обратно към точката на master клона - и също така върна обратно статуса на всички файлове в работната ви директория така че те сега съдържат това, което са съдържали в момента на последния къмит в master клона. Това също означава, че промените които предстои да правите занапред от тази точка на проекта, ще произлизат от по-стара негова версия. Практически - връщането в master клона заличи всички промени от testing клона в работната директория и сега можете да тръгнете в различна посока.

Note
Превключването между клоновете променя файловете в работната директория

Важно е да се посочи, че когато превключвате клонове в Git, файловете в работната директория ще се променят. Ако превключите към по-ранен клон, вашата работна директория ще се превърти назад във времето и ще съдържа това, което е имала последния път когато сте направили къмит в този клон. Ако Git не може да направи това безпроблемно - то превключването няма да бъде позволено въобще.

Нека направим няколко промени и да къмитнем отново:

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

Сега историята на проекта се отклони (виж Разклонена история). Вие създадохте и превключихте към нов клон, направихте промени по кода в него, превключихте към основния клон и направихте други промени. И двата вида промени са изолирани в отделни разклонения: можете да превключвате между тези разклонения и да ги слеете в едно, когато сте готови. Направихте всичко това с прости команди като branch, checkout, и commit.

Разклонена история.
Figure 17. Разклонена история

Можете лесно да видите това и с командата git log. Ако изпълните git log --oneline --decorate --graph --all, това ще отпечата историята на вашите къмити, показвайки къде са вашите branch указатели и как се е разклонила историята на проекта.

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

Понеже клонът в Git на практика е обикновен файл съдържащ 40-символна SHA-1 чексума на къмита, към който клонът сочи, създаването и изтриването на разклонения в Git почти не изисква ресурси. Създаването на нов клон е толкова бързо и просто колкото е записа на 41 байта във файл (40-те символа от чексумата и символ за нов ред).

Това рязко контрастира с начина, по който повечето стари VCS системи управляват разклоненията, защото те копират всички файлове на проекта ви в отделна директория. При тях това може да отнеме много секунди и дори минути, според размера на проекта, докато при Git се прави почти мигновено. Също така, понеже записваме родителите когато къмитваме, намирането на правилната базова точка за сливане се осъществява автоматично за нас и е много лесно. Тези функции окуражават разработчиците да създават и използват разклонения колкото може по-често.

Нека видим защо е добре да го правим.

Note
Създаване на нов клон и превключване към него автоматично

Често се случва да искате да превключите веднага към новосъздаден клон — това може да стане на една стъпка с командата git checkout -b <newbranchname>.