Git
Chapters ▾ 2nd Edition

3.2 Гранање у програму Гит - Основе гранања и спајања

Основе гранања и спајања

Хајде да прођемо кроз једноставан пример гранања и спајања у процесу рада какав се често јавља у реалном свету. Пратићете ове инструкције:

  1. Одрадићете неки посао на веб сајту.

  2. Направите грану за нову корисничку причу на којој радите.

  3. Одрадићете нешто на тој грани.

У овом тренутку, добићете позив да постоји критичан проблем који морате да решите истог тренутка. Урадићете следеће:

  1. Пребацићете се на грану за продукцију.

  2. Направићете нову грану на којој ћете додати кôд који решава проблем.

  3. Када га тестирате, спојићете грану са решењем проблема, и гурнућете на продукцију.

  4. Вратићете се назад на корисничку причу на којој сте радили и наставити посао.

Основе гранања

Најпре, претпоставимо да радите на пројекту у коме на master грани већ имате неколико комитова.

Једноставна историја комитова
Слика 18. Једноставна историја комитова

Одлучили сте да ћете радити на проблему #53 који се налази на неком систему за праћење проблема који користи ваша компанија. Да бисте направили грану и истовремено скочили на њу, извршите команду git checkout са прекидачем -b:

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

Ово је скраћеница за:

$ git branch iss53
$ git checkout iss53
Креирање новог показивача на грану
Слика 19. Креирање новог показивача на грану

Радите неке ствари на свом веб сајту и вршите неке комитове. Док то радите, грана iss53 се креће унапред, јер сте је одјавили (односно, HEAD показује на њу):

$ vim index.html
$ git commit -a -m 'Create new footer [issue 53]'
Грана `iss53` се померила напред у складу с послом који сте обавили
Слика 20. Грана iss53 се померила унапред у складу с послом који сте обавили

Сада добијате позив да постоји проблем са веб сајтом, и морате одмах да га поправите. Са програмом Гит, не морате да решавате проблем заједно са iss53 променама које сте направили, и не морате да улажете много труда у то да опозовете те промене пре него што почнете рад на примени исправке за оно што је у продукцији. Потребно је само да се пребаците назад на master грану.

Ипак, пре него што то урадите, обратите пажњу на то да ако ваш радни директоријум или стејџ има некомитоване промене које су у конфликту са граном коју одјављујете, програм Гит вам неће дозволити да промените грану. Најбоље да радно стање буде чисто вршите скок између грана. Постоје начини да се ово заобиђе (наиме, скривање и исправљање комита) које ћемо обрадити касније, у Скривање и чишћење. Засад, претпоставимо да сте комитовали све промене, тако да се безбедно можете вратити на master грану:

$ git checkout master
Switched to branch 'master'

У овом тренутку, радни директоријум вашег пројекта изгледа исто онако како је изгледао пре него што сте почели да радите на проблему #53, па можете да се концентришете на хитни случај. Ово је битна ствар коју треба запамтити: када мењате гране, програм Гит ресетује радни директоријум тако да изгледа онако како је изгледао када сте последњи пут комитовали на тој грани. Аутоматски додаје, брише и мења фајлове тако да ваша радна копија изгледа тачно онако како је изгледала када сте на грани урадили последњи комит.

Следеће, треба да решите хитан проблем. Направићемо hotfix грану на којој ћемо радити све док не решимо проблем:

$ git checkout -b hotfix
Switched to a new branch 'hotfix'
$ vim index.html
$ git commit -a -m 'Fix broken email address'
[hotfix 1fb7853] Fix broken email address
 1 file changed, 2 insertions(+)
`Hotfix` грана базирана на `master` грани
Слика 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 на ком се налазите, програм Гит једноставно помера показивач унапред. Да то преформулишемо, када покушате да спојите један комит са комитом до ког се може стићи пратећи историју првог комита, програм Гит поједностављује ствари тако што само помери показивач унапред, јер нема раздвојеног рада којом би требало се спаја — ово зове премотавање унапред.

Промена се сада налази у снимку комита на који показује master грана, па можете да примените исправку.

`master` се премотала унапред до `hotfix`
Слика 22. master се премотала унапред до 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 'Finish the new footer [issue 53]'
[iss53 ad82d7a] Finish the new footer [issue 53]
1 file changed, 1 insertion(+)
Рад се наставља на `iss53`
Слика 23. Рад се наставља на iss53

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

Основе спајања

Претпоставимо да сте одлучили да је ваш рад на проблему #53 готов и да је кôд спреман за спајање са master граном. Да бисте то урадили, спојићете грану iss53 са граном 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 гране. У овом случају, историја вашег развоја се раздвојила од неке старије тачке. Пошто комит на грани на којој се налазите није директан предак гране у коју спајате, програм Гит мора одради неки посао. У овом случају, програм Гит ради једноставан троструки спој, користећи два снимка на које показују врхови грана и њихов заједнички предак.

Три снимка који се користе у типичном спајању
Слика 24. Три снимка који се користе у типичном спајању

Уместо да само помери показивач гране унапред, програм Гит прави нови снимак који је резултат овог троструког спајања и аутоматски прави нови комит који показује на њега. Ово се назива комит спајања (merge commit) и посебан је једино у том смислу што има више од једног родитеља.

Комит спајања
Слика 25. Комит спајања

Сада када је комплетан рад спојен, више нема потребе за iss53 граном. Можете да затворите тикет у вашем систему за праћење проблема и да обришете грану:

$ git branch -d iss53

Основни конфликти при спајању

Овај процес често неће тећи овако глатко. Ако сте у две различите гране које спајате променили исти део истог фајла на различит начин, програм Гит неће моћи лепо да их споји. Ако је ваша исправка за тикет #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 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). Програм Гит додаје стандардне маркере за решавање конфликта у фајлове са конфликтима, тако да ручно можете да их отворите и решите конфликте. Фајл садржи секцију која изгледа слично овоме:

<<<<<<< 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 грана, јер сте то одјавили када сте покренули команду merge) на врху тог блока (све изнад =======), док је верзија из гране iss53 приказана у доњем делу. Да бисте решили конфликт, морате или да изаберете једну или другу страну, или да ручно спојите садржину фајла. На пример, овај конфликт се може решити тако што ћете цео горњи блок заменити следећим:

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

Ово решење има помало текста из обе секције, а линије <<<<<<<, ======= и >>>>>>> су комплетно уклоњене. Након што решите сваку од оваквих секција у сваком фајлу са конфликтом, извршите git add за сваки фајл чиме означавате да је разрешен. У програму Гит, стејџовање фајла означава да је конфликт разрешен.

Ако желите да користите графички алат за решавање оваквих проблема, можете да извршите команду 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):

Ако хоћете да користите неки други алат за спајање који није подразумевани (у овом случају је програм Гит је изабрао opendiff јер је команда била покренута на Меку), можете видети све подржане алате наведене при врху након „one of the following tools”. Једноставно укуцајте име алата који бисте радије користили.

Белешка

Ако су вам потребни напреднији алати за решавање компликованих конфликта при спајању, погледаћемо неке од њих у Напредно спајање.

Када изађете из алата за решавање конфликта при спајању, програм Гит ће вас питати да ли је спајање било успешно. Ако скрипти одговорите да јесте, она ће стејџовати фајл и уместо вас га означити као разрешен. Можете да извршите 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
#

Можете да измените ову комит поруку додавањем детаља о томе како сте разрешили спој ако мислите да ће то бити корисно другима који буду гледали ово спајање у будућности — зашто сте урадили то што сте урадили, ако није очигледно.