Git
Chapters ▾ 2nd Edition

3.6 Grananje u Gitu - Rebaziranje

Rebaziranje

U Gitu, postoje dva glavna načina za integraciju promena iz jedne grane u drugu: merge i rebase. U ovom odeljku ćete naučiti šta je rebaziranje, kako se radi, zašto je to prilično dobra stvarčica, i kada treba a kada ne treba da je koristite.

Osnovno rebaziranje

Ako se vratite na raniji primer iz Osnove spajanja, videćete da ste divergirali svoj rad i napravili komitove na različitim granama.

Jednostavna divergentna istorija.
Figure 35. Jednostavna divergentna istorija

Najlakši način da integrišete grane, što smo već pokrili, jeste pomoću komande merge. Ona će uraditi trostruki spoj između dva poslednja snimka sa grana (C3 i C4) i njihovog najskorašnjijeg zajedničkog pretka (C2), stvarajući novi snimak (i komit).

Spajanje radi integracije divergentne istorije rada.
Figure 36. Spajanje radi integracije divergentne istorije rada.

Međutim, postoji još jedan način: možete da uzmete zakrpu promene koja je predstavljena u C4 i da je ponovo primenite preko C3. U Gitu se ovo zove rebaziranje. Pomoću rebase komande, možete da uzmete sve promene koje su komitovane na jednu granu i da ih ponovite na drugoj.

U ovom primeru, pokrenuli biste sledeće:

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

Radi tako što ode na zajedničkog pretka dveju grana (onih na kojoj ste sad i one sa kojom rebazirate), uzima razliku koja je stvorena komitovima na grani na kojoj se nalazite, pamti te razlike u fajlovima koje čuva samo trenutno, resetuje trenutnu granu prema istom komitu na kome je grana ka kojoj rebazirate, i konačno primenjuje svaku promenu.

Rebaziranje promena koje su predstavljene u `C4` i `C3`.
Figure 37. Rebaziranje promena koje su predstavljene u C4 i C3.

U ovom trenutku, možete da se vratite nazad na master granu i da uradite spoj tehnikom motanja unapred.

$ git checkout master
$ git merge experiment
Motanje `master` grane unapred.
Figure 38. Motanje master grane unapred.

Sada, snimak na koji pokazuje C4' je potpuno isti kao i onaj na koji je pokazivao C5 u primeru spoja. Nema razlike u krajnjem proizvodu integracije, ali rebaziranjem se postiže čistija istorija. Ako istražite log rebazirane grane, izgleda kao linearna istorija: izgleda kao da se sav rad odvijao serijski, iako su se stvari zapravo odvijale paralelno.

Ovo ćete često raditi da biste se postarali da se komitovi primene na udaljenu granu kako valja — možda u projektu kojem želite da kontribuirate ali ne želite da ga održavate. U ovom slučaju, radili biste svoj posao na jednoj grani i onda biste rebazirali svoj rad u origin/master kada ste spremni da pošaljete svoje zakrpe glavnom projektu. Na ovaj način, održavalac ne mora da radi nikakvu integraciju — samo treba da premota unapred.

Obratite pažnju na to da je snimak na koji pokazuje konačni komit, bilo da je to poslednji od rebaziranih komitova za rebaziranje ili konačni spojni komit nakon spoja, jedan te isti — samo se istorija razlikuje. Rebaziranje ponavlja promene iz jedne linije rada u drugu i to redom kojim su pravljene, dok spajanje uzima krajnje tačke i meša ih u jedno.

Interesantniji slučajevi rebaziranja

Možete da rebazirate nad nečime što nije odredišna grana za rebaziranje. Uzmite istoriju kao Istorija sa tematskom granom izvedenom iz druge tematske grane., na primer. Odgranali ste se tematskom granom (server) da biste dodali neku funkcionalost u vezi sa serverom u projekat, i napravili komit. Onda se odgranali od toga da biste napravili neke promene sa strane klijenta (client) i komitovali nekoliko puta. Konačno, vratili ste se na server granu i napravili još nekoliko komitova.

Istorija sa tematskom granom izvedenom iz druge tematske grane.
Figure 39. Istorija sa tematskom granom izvedenom iz druge tematske grane.

Pretpostavimo da ste odlučili da želite da spojite promene na grani client sa glavnom granom, ali želite da odložite promene na server grani dok ih bolje ne testirate. Možete da uzmete promene sa client grane koje nisu na serveru (C8 i C9) i da ih ponovite na master grani koristeći opciju --onto komande git rebase:

$ git rebase --onto master server client

Ovo u suštini kaže, "Proveri granu client, shvati koje zakrpe postoje u odnosu na zajedničkog pretka grana client i server, i onda ih ponovo primeni na master". Malo je složeno, ali rezultat je odličan.

Rebaziranje tematske grane izvedene iz druge tematske grane.
Figure 40. Rebaziranje tematske grane izvedene iz druge tematske grane.

Sada možete da premotate unapred granu master (vidite Motanje master grane unapred da biste obuhvatili promene sa grane client.):

$ git checkout master
$ git merge client
Motanje `master` grane unapred da biste obuhvatili promene sa grane `client`.
Figure 41. Motanje master grane unapred da biste obuhvatili promene sa grane client.

Recimo da ste odlučili da povučete i server granu. Možete da rebazirate server granu u master granu a da ne morate da je prvo čekautujete sa git rebase [osnovna-grana] [tematska-grana] — što čekautuje tematsku granu (u ovom slučaju server) i primenjuje pronađene promene na osnovnu granu (master).

$ git rebase master server

Ovo ponavlja promene nađene na grani server na grani master, kao što se vidi na Rebaziranje server grane na master grani..

Rebaziranje `server` grane na `master` grani.
Figure 42. Rebaziranje server grane na master grani.

Onda premotate unapred osnovnu granu (master):

$ git checkout master
$ git merge server

Možete da obrišete grane client i server jer je sav rad obavljen na njima sada integrisan i više vam neće biti potreban, a istorija celokupnog rada će izgledati kao na Konačna istorija komitova:

$ git branch -d client
$ git branch -d server
Konana istorija komitova.
Figure 43. Konačna istorija komitova

Opasnosti prilikom rebaziranja

Ah, ali blaženstvo rebaziranja nije bez mana, što se može sumirati samo jednom rečenicom:

Ne rebazirajte komitove koji postoje van vašeg repozitorijuma.

Ako se držite te smernice, sve će biti u redu. Ako ne, ljudi će vas mrzeti, a porodica i prijatelji će vas prezirati.

Kada rebazirate stvari, napuštate postojeće komitove i stvarate nove koji su slični ali drugačiji. Ako gurnete komitove negde i ostali povuku s njih i baziraju svoj rad nad njima, a onda pišete preko tih komitova sa git rebase i gurnete ih ponovo, vaši kolaboratori će morati da ponovo spoje sav svoj rad i onda će nastati haos kada probate da povučete njihov rad nazad ka sebi.

Pogledajmo primer koji pokazuje kako rebaziranje koje ste napravili javnim može da izazove probleme. Pretpostavimo da ste napravili klon sa centralnog servera i onda radili nešto nad time. Istorija komitova izgleda ovako:

Kloniran repozitorijum nad kojim ste obavili neki posao.
Figure 44. Kloniran repozitorijum nad kojim ste obavili neki posao.

Sada, neko drugi uradi još nekoliko stvari i uključi to u spoj, a zatim gurne sve na centralni server. Vi to preuzmete i spojite novu udaljenu granu na ono što ste uradili, pa istorija izgleda nekako ovako:

Preuzimanje drugih komitova i spajanje sa ličnim radom.
Figure 45. Preuzimanje drugih komitova i spajanje sa ličnim radom.

Osoba koja je gurnula spojen rad zatim odluči da se vrati nazad i rebazira ono što je odradila; on ili ona uradi git push --force da bi se pisalo preko istorije na serveru. Vi onda preuzmete sa tog servera, dovlačeći nove komitove.

Neko gurne rebazirane komitove, napuštajući komitove nad kojima ste bazirali vaš rad.
Figure 46. Neko gurne rebazirane komitove, napuštajući komitove nad kojima ste bazirali vaš rad.

Sada ste oboje u neprilici. Ako uradite git pull, napravićete spojni komit koji uključuje obe linije istorije, i repozitorijum će izgledati ovako:

Ponovno spajanje istog rada u novi spojni komit.
Figure 47. Ponovno spajanje istog rada u novi spojni komit.

Ako pokrenete git log dok vam istorija izgleda ovako, videćete dva komita koji imaju istog autora, vreme i poruku, što zna da zbunjuje. Štaviše, ako gurnete ovu istoriju nazad na server, ponovo ćete uvesti sve te rebazirane komitove na centralni server, što će dalje zbunivati ljude. Pretpostavlja se da drugi programer ne želi da se C4 i C6 nađu u istoriji; zato su uopšte i radi rebaziranje.

Rebaziranje za rebaziranje

Ako se ipak nađete u ovakvoj situaciji, Git ima još neke čarolije koje vam mogu pomoći. Ako neko iz tima nasilno gurne promene koje preklope ono nad čime ste vi bazirali svoj rad, izazov koji vam se nameće je da provalite šta je vaše a šta je on dodao.

Ispostavlja se da pored SHA-1 čeksume vezane za komit, Git računa i čeksumu koja je bazirana samo na zakrpi koja je uvedena sa komitom. Ovo se zove "identifikacioni broj zakrpe".

Ako povučete rad koji je prepisan i rebazirate navrh toga sa novim komitovima vašeg partnera, Git često ume sam da provali šta je jedinstveno vaše i da primeni to nazad na vrh nove grane.

Na primer, u prethodnom scenariju, ako umesto spoja kada smo bili kod Neko gurne rebazirane komitove, napuštajući komitove nad kojima ste bazirali vaš rad. pokrenemo git rebase teamone/master, Git će:

  • odrediti koji rad je jedinstven za našu granu (C2, C3, C4, C6 i C7),

  • odrediti šta nisu spojni komitovi (C2, C3 i C4),

  • odrediti šta nije prepisano u odredišnu granu (samo C2 i C3, pošto je C4 ista zakrpa kao i C4') i

  • primeniti te komitove na teamone/master granu.

Zato ćemo, umesto rezultata koji vidimo na Ponovno spajanje istog rada u novi spojni komit., dobiti nešto što više podseća na Rebaziranje na nasilno gurnuto rebaziranje..

Rebaziranje na nasilno gurnuto rebaziranje.
Figure 48. Rebaziranje na nasilno gurnuto rebaziranje.

Ovo će da upali samo ako C4 i C4' koje je vaš partner napravio čine skoro identičnu zakrpu. U suprotnom, rebaziranjem neće moći da se ustanovi da je to duplikat i biće dodata još jedna zakrpa koja podseća na C4 (koja verovatno neće uspeti da se čisto primeni, jer će promene već bar delimično biti tamo).

Ovo možete da uprostite i pokretanjem git pull --rebase umesto običnog git pull. Ili možete da uradite to ručno sa git fetch za kojim sledi git rebase teamone/master u ovom slučaju.

Ako koristite git pull i želite da --rebase bude podrazumevana opcija, možete da podesite pull.rebase vrednost iz konfiguracionog fajla na true sa git config --global pull.rebase true.

Ako rebaziranje posmatrate kao način da pospremite i radite sa komitovima pre nego što ih gurnete, i ako samo rebazirate komitove koji nikad nisu bili dostupni javno, sve će biti u redu. Ako rebazirate komitove koji su već gurnuti javno, i ljudi baziraju svoj rad nad tim komitovima, onda ćete se naći u frustrirajućim situacijama i bićete meta prezira svojih saradnika.

Ako vi ili partner u nekom trenutku shvatite da je ovakav sled događaja neophodan, postarajte se da svi znaju da treba da pokrenu git pull --rebase da probaju da učine stvar barem malo jednostavnijom.

Rebaziranje protiv spajanja

Sada kada ste videli kako deluje rebaziranje a kako spajanje, možda se pitate šta je bolje. Pre nego što damo odgovor na ovo, hajde da načinimo korak unazad i popričamo malo o tome šta je zapravo istorija.

Jedna tačka gledišta na ovo je to da je istorija komitova vašeg repozitoijuma zapis onoga što se zapravo dogodilo. To je istorijski dokument, vredan na svoj način, i ne bi trebalo da se čačka. Iz ovog ugla, menjanje istorije komita je skoro pa bogohuljenje; vi lažete o onome što se zapravo dogodilo. Šta onda raditi kada se dogodi serija zbrkanih komitova? Tako su se stvari dogodile, i repozitorijum treba da sačuva to za potomstvo.

Suprotna tačka gledišta je da je istorija komitova priča o tome kako je projekat napravljen. Ne biste objavili prvu skicu knjige, a upustvo za održavanje softvera zaslužuje brižljivo redigovanje. Ovaj tabor koristi alate kao što je rebaziranje i filter grane da bi ispričao priču onako kako je najbolje da je pročita budući čitalac.

Sada, što se tiče pitanja da li je spajanje ili rebaziranje bolje: nadamo se da ćete videti da stvari nisu tako jednostavne. Git je moćan alat, dopušta vam da uradite mnoge stvari sa istorijom, ali svaki tim i svaki projekat je drugačiji. Sada kada znate kako obe ove stvari rade, na vama je da odlučite šta je bolje za vašu konkretnu situaciju.

U opštem slučaju, najbolji način da iskoristite prednost oba sveta je da rebazirate lokalne promene koje ste napravili ali još niste podelili pre nego što ih gurnete kako biste očistili istoriju, ali da nikad ne rebazirate ništa što ste negde gurnuli.