Git
Chapters ▾ 2nd Edition

3.6 Гранање у програму Гит - Ребазирање

Ребазирање

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

Основно ребазирање

Ако погледате ранији пример из Основе спајања, видећете да сте разгранали свој рад и направили комитове на две различите гране.

Једноставна разграната историја
Слика 35. Једноставна разграната историја

Као што смо већ раније показали, најлакши начин да интегришете гране је помоћу команде merge. Она ће урадити троструко спајање између два последња снимка са грана (C3 и C4) и њиховог најсвежијег заједничког претка (C2), стварајући нови снимак (и комит).

Спајање ради интеграције разгранате историје рада
Слика 36. Спајање ради интеграције разгранате историје рада

Међутим, постоји још један начин: можете да узмете закрпу промене која је уведена у C4 и да је поново примените преко C3. У програму Гит се ово зове ребазирање. rebase командом можете да узмете све промене које су комитоване у једну грану и да их поновите у некој другој.

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

$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command

Ова операција функционише тако што оде на заједничког претка двеју грана (оне на којој се тренутно налазите и оне преко које ребазирате), узима разлику која је створена сваким комитом у грани на којој се налазите, чува те разлике у привремене фајлове, ресетује тренутну грану на исти комит на коме је и грана преко које ребазирате и коначно, редом примењује сваку промену.

Ребазирање промена које су уведене у `C4` преко `C3`
Слика 37. Ребазирање промена које су уведене у C4 преко C3

У овом тренутку можете да се вратите назад на master грану и да урадите спајање техником премотавања унапред.

$ git checkout master
$ git merge experiment
Премотавање `master` гране унапред
Слика 38. Премотавање master гране унапред

Сада је снимак на који показује C4' потпуно исти као и онај на који је показивао C5 у примеру спајања. Нема разлике у крајњем производу интеграције, али ребазирањем се постиже чистија историја. Ако истражите лог ребазиране гране, изгледа као линеарна историја: изгледа као да се сав рад одвијао серијски, иако су се ствари заправо одвијале паралелно.

Ово ћете често радити када желите се ваши комитови примене чисто на удаљену грану — можда у пројекту којем желите да дате допринос, али који не одржавате. У том случају, свој посао бисте радили у једној грани, па када будете спремни да пошаљете своје закрпе главном пројекту, ребазираћете свој рад преко origin/master. На овај начин, одржавалац не мора да ради никакав посебан посао око интеграције — само треба да премота унапред или одради чисто примењивање.

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

Интересантнији случајеви ребазирања

Ребазирање може да понови измене и над нечему другом, што није циљна грана ребазирања. На пример, узмите историју као што је приказана на Историја са тематском граном разгранатом од друге тематске гране. Разгранали сте тематску грану (server) да бисте у свој пројекат додали неку функционалност са серверске стране, па направили комит. Онда сте разгранали и од ње да бисте направили неке промене на клијентској страни (client), па комитовали неколико пута. Коначно, вратили сте се на server грану и направили још неколико комитова.

Историја са тематском граном разгранатом од друге тематске гране
Слика 39. Историја са тематском граном разгранатом од друге тематске гране

Претпоставимо да сте одлучили да желите спојити ваше промене на клијентској страни са главном граном како би се објавиле, али желите да одложите промене на серверској страни док их боље не тестирате. Можете да узмете промене са client гране које нису на server грани (C8 и C9) и да их поновите преко master гране користећи опцију --onto команде git rebase:

$ git rebase --onto master server client

Ово у суштини каже „Провери грану client, одреди закрпе које су настале након што се одвојила од server гране, па их онда поново примени у грани client као да је уместо од server била одвојена директно од master гране”. Мало је сложено, али резултат је одличан.

Ребазирање тематске гране разгранате из друге тематске гране
Слика 40. Ребазирање тематске гране разгранате из друге тематске гране

Сада можете да премотате унапред грану master (погледајте Премотавање master гране унапред тако да обухвати промене са гране client):

$ git checkout master
$ git merge client
Премотавање `master` гране унапред тако да обухвати промене са гране
Слика 41. Премотавање master гране унапред тако да обухвати промене са гране client

Рецимо да сте одлучили да повучете и промене из server гране. server грану можете да ребазирате преко master гране са git rebase <основна_грана> <тематска_грана> без потребе да је прво одјавите — ова команда прво одјави тематску грану (у овом случају server) и примењује пронађене промене на основну грану master):

$ git rebase master server

Ово понавља рад са server гране преко master гране, као што се види на Ребазирање server гране преко master гране.

Ребазирање `server` гране преко `master` гране
Слика 42. Ребазирање server гране преко master гране

Затим премотате унапред основну грану (master):

$ git checkout master
$ git merge server

Сада можете да обришете гране client и server јер је сав рад обављен на њима интегрисан и више вам неће бити потребне, а историја рада након овог процеса ће изгледати као на Коначна историја комитова:

$ git branch -d client
$ git branch -d server
Коначна историја комитова
Слика 43. Коначна историја комитова

Опасности ребазирања

Ах, али блаженство ребазирања није без мана, што се може сумирати само једном реченицом:

Не ребазирајте комитове који постоје ван вашег репозиторијума и на којима су људи можда засновали свој рад.

Ако се држите ове смернице, све ће бити у реду. У супротном ће вас људи мрзети, а породица и пријатељи ће вас презирати.

Када нешто ребазирате, ви напуштате постојеће комитове и стварате нове који су им слични, али су ипак другачији. Ако комитове гурнете негде и остали их повуку, па базирају свој рад над њима, а ви затим поново напишете те комитове са git rebase и гурнете их поново, ваши сарадници ће морати да поново споје сав свој рад и онда ће настати хаос када пробате да повучете њихов рад назад у свој.

Погледајмо пример који показује како ребазирани рад који сте учинили јавно доступним може изазвати проблеме. Претпоставимо да сте направили клон са централног сервера и онда радили нешто почевши од њега. Историја комитова изгледа овако:

Клонирани репозиторијум над којим сте обавили неки посао.
Слика 44. Клонирани репозиторијум над којим сте обавили неки посао

Сада, неко други уради још нешто што укључи и спајање, а затим гурне све на централни сервер. Ви то преузмете и спојите нову удаљену грану са оним што сте урадили, тако да историја изгледа некако овако:

Преузимање још комитова и спајање са личним радом.
Слика 45. Преузимање још комитова и спајање са личним радом

Затим, особа која је гурнула спојен рад одлучи да се врати назад и уместо спајања ребазира оно што је одрадила; изврши git push --force како би се преписала историја на серверу. Ви онда преузмете податке са тог сервера, довлачећи нове комитове.

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

Сада сте обоје у сосу. Ако извршите git pull, направићете комит спајања који укључује обе линије историје, и ваш репозиторијум ће изгледати овако:

Поновно спајање истог рада у нови комит спајања
Слика 47. Поновно спајање истог рада у нови комит спајања

Ако извршите git log када ваша историја изгледа овако, видећете два комита који имају истог аутора, време и поруку, што ће унети забуну. Штавише, ако гурнете ову историју назад на сервер, поново ћете увести све те ребазиране комитове на централни сервер, што ће још више збунити људе. Прилично је безбедно претпоставити се да други програмер не жели да се C4 и C6 нађу у историји; то је разлог зашто су и радили ребазирање.

Ребазирање када ребазирате

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

Испоставља се да поред SHA-1 контролне суме комита, програм Гит рачуна и контролну суму која је базирана само на закрпи која је уведена комитом. Ово се зове „идентификациони број закрпе” (patch-id).

Ако повучете рад који је преписан и ребазирате га преко нових комитова вашег партнера, програм Гит често сам може успешно да одреди шта је јединствено ваше и да примени то назад на врх нове гране.

На пример, ако у претходном сценарију када смо били код Неко гурне ребазиране комитове, напуштајући комитове над којима сте базирали ваш рад уместо спајања извршимо git rebase teamone/master, програм Гит ће:

  • одредити који рад је јединствен за нашу грану (C2, C3, C4, C6 и C7),

  • одредити шта нису комитови спајања (C2, C3 и C4),

  • одредити шта није било преписано у одредишну грану (само C2 и C3, пошто је C4 иста закрпа као и C4') и

  • применити те комитове на врх teamone/master гране.

Тако да ћемо, уместо резултата који видимо на Поновно спајање истог рада у нови комит спајања, добити нешто што више подсећа на Ребазирање преко насилно гурнутог ребазираног рада.

Ребазирање преко насилно гурнутог ребазираног рада
Слика 48. Ребазирање преко насилно гурнутог ребазираног рада

Ово ће функционисати само ако су C4 и C4' који је ваш партнер направио скоро идентична закрпа. У супротном, ребазирање неће моћи да установи да је то дупликат и додаће још једну закрпу која подсећа на C4 (и која вероватно неће моћи чисто да се примени, јер би промене бар донекле већ биле тамо).

Ово можете да упростите и извршавањем git pull --rebase уместо обичног git pull. Или можете то ручно да урадите са git fetch за којим у овом случају следи git rebase teamone/master`.

Ако користите git pull и желите да --rebase буде подразумевана опција, можете да подесите pull.rebase вредност из конфигурационог фајла на true са git config --global pull.rebase true.

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

Ако ви или партнер у неком тренутку схватите да је овакав след догађаја неопходан, постарајте се да сви остали знају да треба да изврше git pull --rebase и тако пробају да макар донекле упросте проблем који настаје након ребазирања.

Ребазирање против спајања

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

Једна тачка гледишта је да историја комитова вашег репозиторијума представља запис онога што се заправо догодило. То је историјски документ, вредан сам по себи, па не би требало да се преправља. Из овог угла, мењање историје комита је скоро па богохуљење; ви лажете о ономе што се заправо догодило. Шта онда радити када се догоди серија збрканих комитова спајања? Па, ствари су се тако догодиле и репозиторијум треба да сачува то за потомство.

Супротна тачка гледишта је да историја комитова представља причу о томе како је пројекат направљен. Не бисте објавили прву скицу књиге, па зашто да прикажете свој траљави посао? Када радите на пројекту, може вам бити потребан запис о свим погрешним корацима које се начинили и свим ћорсокацима у које сте ушли, али када дође време да објавите свету свој рад, пожелећете да прикажете прецизнију причу о томе како се долази од тачке А до тачке Б. Овај табор користи алате као што је ребазирање и филтер гране да поново испише комитове пре него што се споје у главну грану. Употребом алата rebase и filter-branch они причају причу на начин који је најбољи за будуће читаоце.

Сада, што се тиче питања да ли је боље спајање или ребазирање: надамо се да ћете увидети да ствари нису тако једноставне. Програм Гит је моћан алат, допушта вам да урадите многе ствари са својом историјом, али сваки тим и сваки пројекат је другачији. Сада када знате како обе ове ствари раде, на вама је да одлучите шта је боље за вашу конкретну ситуацију.

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