Git
Chapters ▾ 2nd Edition

3.6 Veje Git - Ponovno baziranje (rebasing)

Ponovno baziranje (rebasing)

V Git-u sta dva glavna načina za integracijo sprememb iz ene veje v drugo: merge in rebase. V tej sekciji se boste naučili, kaj je rebasing, kako ga narediti, zakaj je precej posebno orodje in v katerih primerih, ga ne boste uporabljali.

Osnovno ponovno baziranje

Če se vrnete na prejšnji primer iz Basic Merging, lahko vidite, da ste se oddaljili od vašega dela in naredili pošiljanja na dveh različnih vejah.

Simple divergent history.
Figure 35. Simple divergent history

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 najbolj zadnji skupen prednik obeh (C2), ustvarjanje novega posnetka (in pošiljanje).

Merging to integrate diverged work history.
Figure 36. Merging to integrate diverged work history

Vendar obstaja še drug način: lahko vzamete popravek spremembe, ki je bil predstavljen v C4 in ga ponovno uporabite na vrhu C3. V Git-u se to imenuje rebasing. Z ukazom rebase lahko vzamete vse spremembe, ki so bile poslane na eni veji in jih ponovite na drugi.

V tem primeru bi pognali sledeče:

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

Deluje, če greste do skupnega prednika obeh vej (ene na kateri ste in druge katero ponovno bazirate), pridobitev diff-a predstavljenega z vsakim pošiljanjem veje na kateri ste, shranjevanje teh diff-ov v začasne datoteke, ponastavljanje trenutne veje na istem pošiljanju kot je veja, na katero bazirate in končno uporaba vsake spremebe v zameno.

Rebasing the change introduced in `C4` onto `C3`.
Figure 37. Rebasing the change introduced in C4 onto C3

Na tej točki lahko greste nazaj k master veji in naredite združevanje s hitrim pomikanjem naprej (fast-forward).

$ git checkout master
$ git merge experiment
Fast-forwarding the master branch.
Figure 38. Fast-forwarding the master branch

Sedaj, ko posnetek kaže k C4 je točno enak kot tisti, ki je bil pokazan na s strani C5 v primeru združevanja. Ni razlike v končnem produktu integracije vendar ponovno baziranje naredi čistejšo zgodovino. Če primerjate dnevnik ponovno baziranje veje izgleda kot linearna zgodovina: izgleda, da se je vso delo zgodilo v serijah tudi ko se je prvotno zgodilo vzporedno.

Pogostokrat boste to naredili, da zagotovite, da se vaša pošiljanja uporabijo čisto 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 vaše popravke glavnemu projektu. Na ta način vzdrževalcu ni treba narediti nikakršnega integracijskega dela - samo fast-forward ali čisto uporabo.

Bodite pozorni, saj posnetek na katerega kaže končno pošiljanje, s katerim ste končali, bodisi je zadnje ponovno baziranega pošiljanja za rebase ali končno pošiljanje združevanja po združevanju, gre za isti posnetek - samo zgodovina je, kar je drugačno. Rebasing ponovno predvaja spremembe iz ene vrstice dela v drugo v vrstnem redu, ki so bile predstabljene, medtem ko združevanje vzame končne točke in jih združi skupaj.

Bolj zanimivi rebasing

Lahko tudi imate vaše ponovno predvajanje rebase-a na nečem drugem od ciljne rebase veje. Vzamimo zgodovino, kot je A history with a topic branch off another topic branch za primer. Naredili ste tematsko vejo (server), da ste dodali nekaj funkcionalnosti strežniške strani vašemu projektu in naredili ste pošiljanje. Nato ste od tam naredili razvejanje, da ste naredili spremembe strani klienta (client) in nekajkrat poslali. Končno ste šli nazaj na vašo vejo server in naredili nekaj več pošiljanj.

A history with a topic branch off another topic branch.
Figure 39. A history with a topic branch off another topic branch

Predpostavimo, da se odločite, da želite združiti vaše spremembe strani klienta v vašo glavno izdajo, vendar se želite držati stran od sprememb strežniške strani dokler ni nadaljnje testirano. Lahko vzamete spremembe na klientu, ki niso na strežniku (C8 and C9) in jih ponovno predvajate na vaši veji master z uporabo opcije --onto na git rebase:

$ git rebase --onto master server client

To v osnovi pove, “Izpišite vejo client, ugotovite popravke iz skupnih prednikov vej client in server in jih nato ponovno predvajajte na ``master.” Je nekoliko bolj kompleksno, vendar rezultat je precej cool.

Rebasing a topic branch off another topic branch.
Figure 40. Rebasing a topic branch off another topic branch

Sedaj lahko naredite fast-forward na vaši veji master (glejte Fast-forwarding your master branch to include the client branch changes):

$ git checkout master
$ git merge client
Fast-forwarding your master branch to include the client branch changes.
Figure 41. Fast-forwarding your master branch to include the client branch changes

Recimo, da se odločite potegniti tudi na vašo vejo server. Lahko naredite rebase na veji server glede na vejo master brez, da jo morate najprej izpisati s pogonom git rebase [basebranch] [topicbranch] - kar izpiše tematsko vejo (v tem primeru server) za vas in jo ponovno predvajate na osnovni veji (master):

$ git rebase master server

To ponovno predvaja vaše delo server na vrhu vašega dela master, kot je prikazano v Rebasing your server branch on top of your master branch.

Rebasing your server branch on top of your master branch.
Figure 42. Rebasing your server branch on top of your master branch

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

$ git checkout master
$ git merge server

Lahko odstranite veji client in server, ker je celotno delo integrirano in ju ne potrebujete več, kar pusti vašo zgodovino za ta celotenn proces izgledati kot Final commit history:

$ git branch -d client
$ git branch -d server
Final commit history.
Figure 43. Final commit history

Nevarnosti ponovnega baziranja

Ahh, vendar blagoslova ponovnega baziranja ni brez njegovih slabih strani, ki so lahko povzete v eni vrstici:

Pošiljanj, ki obstojajo izven vašega repozitorija ne ponovno bazirajte.

Če sledite tem smernicam, boste 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ča pošiljanja in ustvarjate nove, ki so podobna vendar drugačna. Če potisnete pošiljanja nekaj in jih ostali potegnejo in bazirajo svoje delo na njih in nato vi prepišete ta pošiljanja z git rebase in jih potisnete ponovno, bodo vaši sodelavci morali narediti ponovno združevanje njihovega dela in stvari bodo postale grde, ko poskušate potegniti njihovo delo nazaj v vaše.

Poglejmo primer, kako baziranje dela, ki ste naredili javno, lahko povzroča probleme. Predpostavimo, da klonirate iz centralnega strežnika in nato naredite nekaj dela iz tega. Vaša zgodovina pošiljanja izgleda takole:

Clone a repository, and base some work on it.
Figure 44. Clone a repository, and base some work on it

Sedaj, ko nekdo drug naredi delo, ki vključuje združitev in potiskanje, ki dela na centralnem strežniku. Ujamete ga in združite novo oddaljeno vejo v vaše delo, naredite, da vaša zgodovina izgleda nekakako takole:

Fetch more commits, and merge them into your work.
Figure 45. Fetch more commits, and merge them into your work

Naslednje, oseba, ki je potisnila združeno delo, se odloči iti nazaj in ponovno bazirati svoje delo namesto tega; naredi git push --force, da prepiše zgodovino na strežniku. Nato ujamete iz tega strežnika in prenesete nova pošiljanja.

Someone pushes rebased commits, abandoning commits you’ve based your work on.
Figure 46. Someone pushes rebased commits, abandoning commits you’ve based your work on

Sedaj ste oboji v škripcih. Če naredite git pull, boste ustvarili pošiljanje združitve, ki vključuje tako vrstico zgodovine in vaš repozitorij bo izgledal takole:

You merge in the same work again into a new merge commit.
Figure 47. You merge in the same work again into a new merge commit

Če poženete git log, ko vaša zgodovina izgleda takole, boste videli dve pošiljanji, ki imata istega avtorja, datum in sporočilo, kar bo zmedeno. Nadalje, če potisnete to zgodovino nazaj na strežnik, boste ponovno predstavili vsa ta ponovno bazirana pošiljanja na centralnem strežniku, kar je lahko dalje zmede ljudi. Je precej varno predpostavljati, da drug razvijalec ne želi C4 in C6 v zgodovini; to je razlog, zakaj sploh ponovno baziranje.

Ponovno bazirajte ko ponovno bazirate

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

Izkaže se, da kot dodatek k pošiljanju preverjene vsote SHA-1, Git tudi preračuna preverjeno vsoto, ki je osnovana samo kot popravek predstavljen v pošiljanju. To se imenuje “patch-id”.

Če potegnete delo, ki je bilo prepisano in osnovano na vrhu novega pošiljanja vašega partnerja lahko Git tudi pogostokrat uspešno ugotovi, kaj je unikatno vaše in jih uporabite nazaj na vrhu nove veje.

Na primer v prejšnjem scenariju če namesto, da delate združevanje, ko ste na Someone pushes rebased commits, abandoning commits you’ve based your work on, 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, katera niso pošiljanja združevanja (C2, C3, C4)

  • Določil, katera niso bila prepisana v ciljno vejo (samo C2 in C3, saj C4 je isti popravek kot C4')

  • Uporabil ta pošiljanja na vrhu teamone/master

Torej namesto rezultata, ki ga vidimo v You merge in the same work again into a new merge commit, bi končali z nečim bolj kot Rebase on top of force-pushed rebase work..

Rebase on top of force-pushed rebase work.
Figure 48. Rebase on top of force-pushed rebase work.

To samo deluje, če sta C4 in C4', ki ga je naredil vaš partner skoraj točno enak popravek. Drugače ponovno baziranje ne bo zmožno povedati, da je duplikat in bo dodal drug C4 podoben popravek (ki ga bo verjetno neuspel uporabiti čisto, saj bi spremembe će bile vsaj nekako tam).

To lahko tudi poenostavite s pogonom git pull --rebase namesto normalnega git pull. Ali pa bi lahko naredili to ročno z git fetch, ki mu sledi git rebase teamone/master v tem primeru.

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

Če obravnavate ponovno baziranje kot način čiščenja in dela s pošiljanju preden jih potisnete in če samo ponovno bazirate pošiljanja, ki še nikoli niso bila na voljo javno potem boste v redu. Če ponovno bazirate pošiljanja, ki so že bila potisnjena javno in so ljudje lahko bazirali delo na teh pošiljanjih, potem ste lahko v neki frustrirajoči težavi in prezirate vaše člane ekipe.

Če vi ali partner najde to potrebno na neki točki, zagotovite, da vsi znajo pognati git pull --rebase, da poskusijo narediti problem potem, ko se zgodi, nekoliko bolj enostaven.

Ponovno baziranje proti združevanju

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

Ena točka pogleda na to je, da je zgodovina pošiljanja 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 spreminjanje zgodovine pošiljanja je skoraj bogolketno; ležite, kaj se je dejansko zgodilo. Torej kaj če je grda serijca pošiljanj združevanja? Tako se je zgodilo in repozitorij bi moral ohraniti to za zaznamce.

Nasprotno stališče je, da je zgodovina pošiljanja zgodba, kako je bil vaš projekt narejen. Ne bi objavili prvega osnutka knjige in navodila, kako vzdrževati programsko opremo, zaslužijo previdno urejanje. To je tabor, ki uporablja orodja, kot je rebase in filter-branch, da povejo zgodbo na način, ki je najboljši za bodoče bralce.

Sedaj vprašanje ali je združevanje ali ponovno baziranje boljše: upajmo, da boste videli, da ni tako enostavno. Git je močno orodje in vam omogoča narediti mnogo stvari in z vašo zgodovino, vendar vsaka ekipa in vsak projekt je drugačen. Sedaj ko veste kako obe stvari delujeta, je odvisno od vas, da se odločite, katera je najboljša za vašo določeno situacijo.

V splošnem način za dobiti najboljše obeh svetov je ponovno baziranje lokalnih sprememb, ki ste jih naredili vendar niste delili preden ste jih potisnili, da počistite vašo zgodbo, vendar nikoli ničesar ponovno bazirali, ste potisnili nekam.