Git 🌙
Chapters ▾ 2nd Edition

3.6 Veje Git - Ponovno baziranje

Ponovno baziranje

V Gitu obstajata dva glavna načina za integracijo sprememb iz ene veje v drugo: merge in rebase. V tem razdelku se boste naučili, kaj je ponovno baziranje, kako ga narediti, zakaj je precej posebno orodje in v katerih primerih, ga ne boste želeli uporabiti.

Osnovno ponovno baziranje

Če se vrnete na prejšnji primer iz razdelka Osnovno združevanje, lahko vidite, da ste se oddaljili od svojega dela in naredili potrditve na dveh različnih vejah.

Enostavna različna zgodovina
Slika 35. Enostavna različna zgodovina

Najenostavnejši način za integracijo vej, kot smo to že pokrili, je ukaz merge. Izvede tri-načinsko združevanje med dvema zadnjima posnetkoma vej (C3 in C4) in najnovejšim zadnjim skupnim prednikom obeh (C2), kar ustvari nov posnetek (in potrditev).

Združitev za integracijo zgodovine različnega dela
Slika 36. Združitev za integracijo zgodovine različnega dela

Vendar obstaja še drug način: vzamete programski popravek spremembe, ki je bil uveden v C4 in ga ponovno uporabite na vrhu C3. V Gitu se to imenuje ponovno baziranje. Z ukazom rebase lahko vzamete vse spremembe, ki so bile potrjene na eni veji, in jih ponovite na drugi veji.

V tem primeru bi izvlekli vejo experiment in jo nato ponovno bazirali na osnovi veje master na naslednji način:

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

Ta operacije deluje tako, da gre do skupnega prednika obeh vej (tista na kateri ste in druga, katero ponovno bazirate), pridobi razliko uvedeno z vsako potrditvijo veje, na kateri ste, shrani te razlike v začasno datoteko, ponastavi trenutno vejo na isto potrditev, kot je veja, na katero bazirate, in končno v zameno uporabi vsako spremembo.

Ponovno baziranje spremembe uvedene v `C4` na `C3`
Slika 37. Ponovno baziranje spremembe uvedene v C4 na C3

Na tej točki se lahko vrnete na vejo master in naredite združevanje s hitrim previjanjem naprej (angl. fast-forward merge).

$ git checkout master
$ git merge experiment
Hitro previjanje naprej veje `master`
Slika 38. Hitro previjanje naprej veje master

Sedaj je posnetek, na katerega kaže C4, točno tak kot tisti, ki je bil pokazan na C5 v primeru združitve potrjevanja. Ni razlike v končnem produktu integracije, vendar ponovno baziranje naredi zgodovino čistejšo. Če primerjate dnevnik ponovno bazirane veje, je videti kot linearna zgodovina: videti je, kot da se je vse delo zgodilo v serijah, tudi ko se je prvotno zgodilo vzporedno.

Pogostokrat boste to naredili, da zagotovite, da se vaše potrditve uporabijo gladko na oddaljeni veji — mogoče v projektu kateremu poskušate prispevati, vendar ga ne vzdržujete. V tem primeru bi naredili vaše delo na veji in nato osnovali vaše delo glede na to origin/master, ko ste pripravljeni poslati svoje popravke glavnemu projektu. Na ta način vzdrževalcu ni treba narediti nikakršnega integracijskega dela — samo fast-forward ali pa čista uporaba.

Bodite pozorni, saj gre za isti posnetek, na katerega kaže končna potrditev, s katero ste končali, bodisi je ta zadnja od ponovno bazirane potrditve za ponovno baziranje ali pa končna potrditev združevanja po združevanju — samo zgodovina je drugačna. Ponovno baziranje ponovno predvaja spremembe iz ene vrstice dela v drugo v vrstnem redu, kakor so bile uvedene, medtem ko združevanje vzame končne točke in jih združi skupaj.

Bolj zanimivo ponovno baziranje

Svoje ponovno baziranje lahko ponovno predvajate tudi na nečem drugem od ciljne veje ponovnega baziranja. Za primer vzemimo zgodovino, kot je na sliki Zgodovina s tematsko vejo na osnovi druge tematske veje. Naredili ste tematsko vejo (server), da ste svojemu projektu dodali nekaj funkcionalnosti strežniške strani, in naredili ste potrditev. Nato ste od tam naredili razvejanje (client), da ste naredili spremembe na strani odjemalca in naredili nekaj potrditev. Na koncu ste šli nazaj na vašo vejo server in naredili še nekaj potrditev.

Zgodovina s tematsko vejo na osnovi druge tematske veje
Slika 39. Zgodovina s tematsko vejo na osnovi druge tematske veje

Predpostavimo, da se odločite, da želite združiti vaše spremembe strani odjemalca v vašo glavno izdajo, vendar želite še malo počakati s spremembami strežniške strani, dokler niso nadaljnje testirane. Vzamete lahko spremembe na veji client, ki niso na veji server (C8 in C9) in jih ponovno predvajate na vaši veji master z uporabo možnosti --onto ukaza git rebase:

$ git rebase --onto master server client

To v osnovi navede, »Vzemi vejo client, ugotovi popravke, odkar se je veja ločila od veje server, in jih nato ponovno predvajaj na veji client, kakor da je le-ta neposredno osnovana na veji master«. Je nekoliko bolj kompleksno, vendar rezultat je precej dober.

Ponovno baziranje tematske veje na osnovi druge tematske veje
Slika 40. Ponovno baziranje tematske veje na osnovi druge tematske veje

Sedaj lahko naredite hitro previjanje naprej (angl. fast-forward) na vaši veji master (glejte sliko Hitro previjanje naprej vaše veje master, da vključuje spremembe veje client):

$ git checkout master
$ git merge client
Hitro previjanje naprej vaše veje `master`, da vključuje spremembe veje `client`
Slika 41. Hitro previjanje naprej vaše veje master, da vključuje spremembe veje client

Recimo, da se odločite povleči to tudi v vašo vejo server. Ponovno baziranje lahko naredite na veji server glede na vejo master, brez da jo morate najprej izvleči s pogonom git rebase <basebranch> <topicbranch> — kar vam izvleče tematsko vejo (v tem primeru server) in jo ponovno predvaja na osnovni veji (master):

$ git rebase master server

To ponovno predvaja vaše delo server na vrhu vašega dela master, kot je prikazano na sliki Ponovno baziranje vaše veje server na vrhu vaše veje master.

Ponovno baziranje vaše veje `server` na vrhu vaše veje `master`
Slika 42. Ponovno baziranje vaše veje server na vrhu vaše veje master

Nato lahko naredite fast-forward na osnovni veji (master):

$ git checkout master
$ git merge server

Veji client in server lahko odstranite, ker je celotno delo integrirano in ju ne potrebujete več, kar pusti vašo zgodovino za ta celoten proces, da je videti kot na sliki Končna zgodovina potrditev:

$ git branch -d client
$ git branch -d server
Končna zgodovina potrditev
Slika 43. Končna zgodovina potrditev

Nevarnosti ponovnega baziranja

Ah, vendar blagoslova ponovnega baziranja ni brez njegovih slabih strani, ki jih lahko povzamemo v eni vrstici:

Ne bazirajte ponovno tistih potrditev, ki obstajajo izven vašega repozitorija in na katerih so ljudje morda osnovali delo.

Če sledite tem smernicam, bo v redu. Če ne, vas bodo ljudje sovražili in zaničevani boste s strani prijateljev in družine.

Ko ponovno bazirate, opuščate obstoječe potrditve in ustvarjate nove, ki so podobne vendar drugačne. Če nekam potisnete potrditve in jih drugi povlečejo ter bazirajo svoje delo na njih, nato pa vi prepišete te potrditve z git rebase in jih ponovno potisnete, bodo vaši sodelavci morali narediti ponovno združevanje njihovega dela in nastala bo zmešnjava, ko boste poskusili povleči njihovo delo nazaj v vaše.

Poglejmo primer, kako lahko baziranje dela, ki ste ga naredili javno, povzroča probleme. Predpostavimo, da klonirate iz osrednjega strežnika in nato iz tega naredite nekaj dela. Vaša zgodovina potrditev je videti takole:

Klonirajte repozitorij in bazirajte nekaj dela na njem
Slika 44. Klonirajte repozitorij in bazirajte nekaj dela na njem

Sedaj, nekdo drug naredi delo, ki vključuje združitev, in potisne to delo na osrednji strežnik. Prenesete in združite novo oddaljeno vejo v svoje delo, kar naredi, da vaša zgodovina izgleda nekakako takole:

Prenesite več potrditev in jih združite v svoje delo
Slika 45. Prenesite več potrditev in jih združite v svoje delo

V nadaljevanju se oseba, ki je potisnila združeno delo, odloči iti nazaj in namesto tega ponovno bazira svoje delo; naredi git push --force, da prepiše zgodovino na strežniku. Nato prenesete iz tega strežnika in dobite nove potrditve.

Nekdo potisne ponovno bazirane potrditve in opusti potrditve, na katerih ste vi osnovali delo
Slika 46. Nekdo potisne ponovno bazirane potrditve in opusti potrditve, na katerih ste vi osnovali delo

Sedaj ste oboji v škripcih. Če naredite git pull, boste ustvarili potrditve združitve, ki vključujejo obe vrstici zgodovine, in vaš repozitorij bo videti takole:

Ponovno združite isto delo v novo potrditev združevanja
Slika 47. Ponovno združite isto delo v novo potrditev združevanja

Če poženete git log, ko je vaša zgodovina videti takole, boste videli dve potrditvi, ki imata istega avtorja, datum in sporočilo, kar bo zmedeno. Nadalje, če potisnete to zgodovino nazaj na strežnik, boste ponovno uvedli vse te ponovno bazirane potrditve na osrednjem strežniku, kar lahko dalje zmede ljudi. Precej prepričano je domnevati, da drug razvijalec ne želi imeti C4 in C6 v zgodovini; to je razlog, zakaj sploh ponovno bazirati.

Ponovno bazirajte, ko ponovno bazirate

Če se najdete v položaju, kot je ta, ima Git nekaj dodatne čarobnosti, ki vam lahko pomaga. Če nekdo v vaši ekipi potisne spremembe, ki prepišejo delo, na katerem ste osnovali svoje delo, je vaš izziv ugotoviti, kaj je vaše in kaj so prepisali drugi.

Izkaže se, da poleg kontrolne vsote SHA-1 potrditve, Git preračuna tudi kontrolno vsoto, ki je osnovana samo kot programski popravek uveden v potrditvi. To se imenuje »patch-id«.

Če povlečete delo, ki je bilo prepisano in osnovano na vrhu nove potrditve vašega partnerja, lahko Git tudi pogostokrat uspešno ugotovi, kaj je unikatno vaše, in to uporabi nazaj na vrhu nove veje.

Na primer, če v prejšnjem scenariju, namesto da delamo združevanje, ko smo na primeru iz slike Nekdo potisne ponovno bazirane potrditve in opusti potrditve, na katerih ste vi osnovali delo, poženemo git rebase teamone/master, bo Git:

  • Določil, katero delo je unikatno za vašo vejo (C2, C3, C4, C6, C7)

  • Določil, katere niso potrditve združevanja (C2, C3, C4)

  • Določil, katere niso bile prepisane v ciljni veji (samo C2 in C3, saj je C4 enak programski popravek kot C4')

  • Uporabil te potrditve na vrhu teamone/master

Torej namesto rezultata, ki ga vidimo na sliki Ponovno združite isto delo v novo potrditev združevanja, bi dobili nekaj bolj takega, kot je na sliki Ponovno baziranje na osnovi prisilno potisnjenega ponovno baziranega dela.

Ponovno baziranje na osnovi prisilno potisnjenega ponovno baziranega dela
Slika 48. Ponovno baziranje na osnovi prisilno potisnjenega ponovno baziranega dela

To deluje zgolj, če sta C4 in C4', ki ju je naredil vaš partner skoraj točno enaka popravka. Drugače ponovno baziranje ne bo zmožno vedeti, da je duplikat in bo dodalo drug C4 podoben programski popravek (ki ga verjetno ne bo uspelo uporabiti na gladek način, saj bi spremembe že bile vsaj nekako tam).

To lahko tudi poenostavite s pogonom git pull --rebase namesto običajnega git pull. Lahko pa to naredite ročno z git fetch, kateremu v tem primeru sledi git rebase teamone/master.

Če uporabljate git pull in želite privzeto narediti --rebase, lahko nastavite pull.rebase nastavitveno vrednost z nečim, kot je git config --global pull.rebase true.

Če ponovno bazirate samo potrditve, ki niso nikoli zapustile vaše naprave, bo v redu. Če ponovno bazirate potrditve, ki so bile potisnjene, vendar ni nihče osnoval dela na njih, bo tudi v redu. Če ponovno bazirate potrditve, ki so bile že javno potisnjene in so na teh potrditvah ljudje lahko osnovali delo, potem ste lahko v frustrirajočih težavah in prezirate člane svoje ekipe.

Če se vam ali partnerju zdi na neki točki to potrebno, zagotovite, da vsi vejo, da morajo pognati git pull --rebase, tako da poskusijo nekoliko bolj poenostaviti problem, ko se ta zgodi.

Ponovno baziranje v primerjavi z združevanjem

Sedaj, ko ste videli ponovno baziranje in združevanje v delovanju, se lahko sprašujete, kaj je boljše. Preden lahko to odgovorimo, pojdimo korak nazaj in spregovorimo o tem, kaj pomeni zgodovina.

Eno stališče tega je, da je zgodovina potrditev vašega repozitorija posnetek, kaj se je dejansko zgodilo. Je zgodovinski dokument, vreden svojega lastnega prav in ne bi smel biti ponarejen. Iz tega zornega kota je spreminjanje zgodovine potrditev skoraj bogokletno; namreč lažete, kaj se je dejansko zgodilo. Torej, kaj če obstaja grda serija potrditev združevanj? Tako se je zgodilo in repozitorij bi moral to ohraniti zanamcem.

Nasprotno stališče je, da je zgodovina potrditev zgodba, kako je bil vaš projekt narejen. Prvega osnutka knjige tudi ne bi objavili, torej zakaj bi pokazali svoje nepopolno delo? Ko delate na projektu, morda potrebujete zapis vseh svojih napačnih korakov in slepih ulic, vendar ko je čas, da pokažete svoje delo svetu, morda želite povedati bolj koherentno zgodbo o tem, kako priti od A do B. Ljudje v tej skupini uporabljajo orodja, kot sta rebase in filter-branch, da prepišejo svoje potrditve, preden se združijo v glavno vejo. Uporabljajo orodja, kot sta rebase in filter-branch, da povedo zgodbo na način, ki je najboljši za prihodnje bralce.

Zdaj, glede vprašanja, ali je boljše združevanje ali ponovno baziranje: upajmo, da boste spoznali, da to ni tako preprosto. Git je zmogljivo orodje in omogoča veliko stvari v zvezi z vašo zgodovino, vendar je vsaka ekipa in vsak projekt drugačen. Ko zdaj veste, kako delujeta oba načina, je odvisno od vas, da se odločite, kateri je najboljši za vaš poseben položaj.

Lahko dobite najboljše iz obeh svetov: pred objavo spremenite lokalne spremembe, da očistite svoje delo, vendar nikoli ne prepišite ničesar, kar ste že objavili drugje.

scroll-to-top