Git
Chapters ▾ 2nd Edition

3.2 Клонове в Git - Основи на клоновете код и сливането

Основи на клоновете код и сливането

Нека илюстрираме разклоняването и сливането с малък пример, какъвто може да срещнете в реалния живот. Ще следваме следните стъпки:

  1. Работите по уеб сайт.

  2. Създавате нов клон за нова статия, по която работите.

  3. Извършвате някакви дейности по този клон.

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

  1. Превключвате към работния (production) клон.

  2. Създавате нов клон и решавате проблема в него.

  3. След тест, че всичко в поправката е наред, сливате hotfix клона обратно в работния клон.

  4. Превключвате отново към клона с новата статия и продължавате работа.

Основи на разклоняването

Първо, нека приемем, че работите по проекта си и вече имате няколко къмита в клона master.

Проста история на къмитите.
Figure 18. Проста история на къмитите

Решили сте, че трябва да работите по проблем #53 в issue-tracking системата, която ползва вашата компания. За да създадете клон и превключите към него в същия момент, изпълнете командата git checkout с параметър -b:

$ git checkout -b iss53
Switched to a new branch "iss53"

Това е съкратена версия на командите:

$ git branch iss53
$ git checkout iss53
Създаване на нов указател към branch.
Figure 19. Създаване на нов указател към branch

Вие си работите по сайта и правите няколко къмита. По време на този процес, клонът iss53 се премества напред, защото е текущ (това означава, че HEAD указателят сочи към него):

$ vim index.html
$ git commit -a -m 'added a new footer [issue 53]'
Клонът `iss53` се е преместил напред в процеса на работа.
Figure 20. Клонът iss53 се е преместил напред в процеса на работа

Сега получавате обаждане, че нещо не е наред с уебсайта и трябва да го оправите незабавно. С Git, не е нужно да прилагате поправката заедно с промените, които се съдържат в iss53 клона и не е нужно да влагате усилия по отмяната на тези промени преди да можете да приложите спешната поправка в production версията на сайта. Всичко, което трябва да направите е да превключите обратно към master клона.

Обаче преди да направите това, отбележете, че ако работната ви директория или индексната област съдържат некъмитнати промени, които влизат в конфликт с клона, към който превключвате, Git няма да позволи превключването на клоновете. Най-добре е да имате чист работен статус преди превключването. Съществуват начини да заобиколите това (известно като stashing и commit amending), които ще разгледаме по-късно в Stashing и Cleaning. Засега, нека приемем, че сте къмитнали промените си, така че може да се върнете в master клона:

$ git checkout master
Switched to branch 'master'

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

След това, имате да правите спешната поправка. Нека създадем един hotfix клон, по който да работим докато тя стане готова:

$ git checkout -b hotfix
Switched to a new branch 'hotfix'
$ vim index.html
$ git commit -a -m 'fixed the broken email address'
[hotfix 1fb7853] fixed the broken email address
 1 file changed, 2 insertions(+)
Hotfix клон произлизащ от `master` клона.
Figure 21. Hotfix клон произлизащ от master клона

Можете да пускате тестовете си, да се уверите, че поправката работи както се очаква и да слеете обратно вашия hotfix клон в master клона за да го пуснете в работния вариант. Това се прави с командата git merge:

$ git checkout master
$ git merge hotfix
Updating f42c576..3a0874c
Fast-forward
 index.html | 2 ++
 1 file changed, 2 insertions(+)

В съобщението от сливането ще забележите фразата “fast-forward”. Понеже къмитът C4, към който сочи клона hotfix, който сляхте, беше директно след къмита C2, Git просто премества указателя напред. Казано по друг начин, когато се опитвате да слеете един къмит с друг такъв, който може да бъде достигнат следвайки историята на първия, Git опростява нещата премествайки указателя напред, защото не се налага да се върши работа по сливане на разклонен код. Това се нарича “fast-forward.”

Сега промяната ви е в snapshot-а на къмита сочен от master клона и можете да пуснете промяната в реалния сайт.

`master` е превъртян (fast-forwarded) към `hotfix`.
Figure 22. master е превъртян (fast-forwarded) към hotfix

След като суперважната промяна е въведена, можете да се върнете обратно към работата, която вършехте преди обаждането. Обаче, първо ще изтриете клона hotfix, понеже вече не ви е нужен — master клонът сочи към същото място. Изтриването се прави с параметъра -d на командата git branch:

$ git branch -d hotfix
Deleted branch hotfix (3a0874c).

Сега можете да се прехвърлите към клона, в който си вършите обичайната работа по проблем 53 и да си я продължите.

$ git checkout iss53
Switched to branch "iss53"
$ vim index.html
$ git commit -a -m 'finished the new footer [issue 53]'
[iss53 ad82d7a] finished the new footer [issue 53]
1 file changed, 1 insertion(+)
Работата продължава в клона `iss53`.
Figure 23. Работата продължава в клона iss53

Тук си струва да отбележим, че промените, които направихте в клона hotfix не се съдържат във файловете на клона iss53. Ако искате да ги имате, можете да слеете master клона в iss53 изпълнявайки git merge master, или пък можете да изчакате с интегрирането на тези промени докато дойде момента, в който решите че е време да слеете iss53 клона обратно в master.

Сливане

Да кажем, че сте решили, че работата по проблем 53 е свършена и сте готови да я слеете в master клона. За да направите това, ще действате по същия начин, по който го направихте с клона hotfix по-рано. Всичко, което трябва да направите е да превключите към клона, в който искате да сливате и да изпълните git merge:

$ git checkout master
Switched to branch 'master'
$ git merge iss53
Merge made by the 'recursive' strategy.
index.html |    1 +
1 file changed, 1 insertion(+)

Това обаче изглежда по-различно от hotfix сливането. В този случай, историята на разработката се е отклонила от по-ранна точка. Понеже къмитът на клона, в който сте (С4), не е директен предшественик на клона, който сливате, Git ще има малко работа за вършене. В този случай, Git прави просто трипосочно сливане използвайки двата snapshot-а на клоновете и общия им предшественик (С2).

Три snapshot-а използвани в типично сливане.
Figure 24. Три snapshot-а използвани в типично сливане

Това се нарича сливащ къмит (merge commit) и е специален заради това, че има повече от един родител. Вместо просто да премести указателя на клона напред, Git създава нов snapshot, който е резултат от това трипосочно сливане и автоматично създава нов къмит, който да сочи към него.

Сливащ commit.
Figure 25. Сливащ commit

Сега, когато работата ви е слята, вече не се нуждаете от клона iss53. Можете да затворите тикета в ticket-tracking системата и да изтриете клона:

$ git branch -d iss53

Конфликти при сливане

Понякога обаче, сливането не минава гладко. Ако сте променили една и съща част от един и същи файл в различни клонове, които искате да слеете, Git няма да може да направи това чисто. Ако промените ви по проблем #53 са модифицирали една и съща част от файл с тези от клона hotfix, ще се получи конфликт при сливането, съобщението за който може да изглежда по подобен начин:

$ git merge iss53
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.

Git не е извършил merge къмита. Процесът е прекъснат докато не разрешите конфликта Ако искате да видите кои файлове не са слети вследствие на конфликта, можете да изпълните git status:

$ git status
On branch master
You have unmerged paths.
  (fix conflicts and run "git commit")

Unmerged paths:
  (use "git add <file>..." to mark resolution)

    both modified:      index.html

no changes added to commit (use "git add" and/or "git commit -a")

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

<<<<<<< HEAD:index.html
<div id="footer">contact : email.support@github.com</div>
=======
<div id="footer">
 please contact us at support@github.com
</div>
>>>>>>> iss53:index.html

Това означава, че версията в HEAD (вашият master клон, понеже той е текущия, в който сте изпълнили командата по сливането) е горната част на този блок (всичко преди =======), докато версията в клона iss53 е в долната част. За да решите конфликта, трябва да изберете една от двете или да слеете съдържанието сами. Например, можете да замените целия блок с това:

<div id="footer">
please contact us at email.support@github.com
</div>

Това решение съдържа по малко от всяка секция и редовете <<<<<<<, =======, >>>>>>> се изтриват напълно. След като направите това за всяка секция и всеки файл в който има конфликти, изпълнете git add за всеки файл, за да го маркирате като коригиран. Индексирането на файл в Git го маркира като коректен, без конфликти.

Ако желаете да използвате графичен инструмент за решаването на конфликти, можете да изпълните git mergetool, което ще стартира подходящия визуален инструмент и ще ви води през конфликтите подред:

$ git mergetool

This message is displayed because 'merge.tool' is not configured.
See 'git mergetool --tool-help' or 'git help config' for more details.
'git mergetool' will now attempt to use one of the following tools:
opendiff kdiff3 tkdiff xxdiff meld tortoisemerge gvimdiff diffuse diffmerge ecmerge p4merge araxis bc3 codecompare vimdiff emerge
Merging:
index.html

Normal merge conflict for 'index.html':
  {local}: modified file
  {remote}: modified file
Hit return to start merge resolution tool (opendiff):

Ако желаете да използвате инструмент различен от подразбиращия се (в случая Git е избрал opendiff, защото командата е пусната на Mac), можете да видите всички поддържани такива в горната част на изхода от командата след надписа “one of the following tools.” Просто напишете името на инструмента, който предпочитате.

Note

Ако се нуждаете от по-модерни инструменти за разрешаване на заплетени конфликти, обръщаме повече внимание на това в Сливане за напреднали.

След като затворите инструмента, Git ви пита дали сливането е успешно. Ако кажете това на скрипта, системата ще индексира файла и ще го маркира като коректен. Можете да пуснете git status отново за да проверите дали всички конфликти са разрешени:

$ git status
On branch master
All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

Changes to be committed:

    modified:   index.html

Ако това ви устройва и сте проверили, че всичко по което е имало конфликти е в индекса, можете да изпълните git commit за да завършите сливащия къмит. Къмит съобщението по подразбиране изглежда подобно на това:

Merge branch 'iss53'

Conflicts:
    index.html
#
# It looks like you may be committing a merge.
# If this is not correct, please remove the file
#	.git/MERGE_HEAD
# and try again.


# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# All conflicts fixed but you are still merging.
#
# Changes to be committed:
#	modified:   index.html
#

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