Git
Chapters ▾ 2nd Edition

3.6 Pag-branch ng Git - Pag-rebase

Pag-rebase

Sa Git, mayroong dalawang pangunahing mga paraan upang pagsamahin ang mga pagbabago mula sa isang branch patungo sa iba: ang merge at ang rebase. Sa seksyong ito matututo ka kung ano ang pag-rebase, paano ito gawin, bakit ito ay isang medyo kahanga-hangang kasangkapan, at sa anong kaso mo hindi gugustuhing gamitin ito.

Ang Pangunahing Rebase

Kung ikaw ay pupunta pabalik sa kamakailang halimbawa mula sa Batayan ng Pag-merge, maaari mong tingnan na nahiwalay mo ang iyong trabaho at gumawa ng mga commit sa dalawang magkaibang mga branch.

Simpleng divergent na kasaysayan.
Figure 35. Simpleng divergent na kasaysayan

Ang pinakamadaling paraan upang mapagsama ang mga branch, bilang nasakop na natin, ay ang merge na utos. Ito ay gumagawa ng isang three-way na merge sa pagitan ng dalawang pinakabagong mga snapshot ng branch (C3 at C4) at ang pinakakamakailang karaniwang ninuno ng dalawa (C2), kaya gumagawa ng isang panibagong snapshot (at commit).

Pag-merge upang mapagsama ang nahiwalay na kasaysayan ng trabaho.
Figure 36. Pag-merge upang mapagsama ang nahiwalay na kasaysayan ng trabaho

Subalit, mayroong ibang paraan: maaari mong kunin ang patch ng pagbabago na napakilala sa C4 at ilapat muli ito sa itaas ng C3. Sa Git, ito ay tinatawag na rebasing. Gamit ang rebase na utos, maaari mong kunin ang lahat ng mga pagbabago na na-commit sa isang branch at i-replay ang mga ito sa iba pa.

Sa halimbawang ito, papatakbuhin mo ang sumusunod:

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

Gumagana ito sa pamamagitan ng pagpunta sa karaniwang ninuno ng dalawang mga branch (yung isa ay kung saan nandoon ka at yung isa ay kung saan ka magre-rebase), kukunin ang diff na napakilala sa bawat commit ng branch kung saan nandoon ka, isi-save ang mga diff na iyon sa pansamantalang mga file, iri-reset ang kasalukuyang branch sa parehong commit bilang branch na iyong iri-rebase, at sa huli ay ilalapat ang bawat pagbabago.

Pag-rebase ng pagbabago na napakilala sa `C4` patungo sa `C3`.
Figure 37. Pag-rebase ng pagbabago na napakilala sa C4 patungo sa C3

Sa puntong ito, maaari kang pumunta pabalik sa master na branch at gumawa ng fast-forward na merge.

$ git checkout master
$ git merge experiment
Pag-fast-forward ng master na branch.
Figure 38. Pag-fast-forward ng master na branch

Ngayon, ang snapshot na itinuro ng C4' ay eksaktong kapareho sa isa na itinuro ng C5 sa merge na halimbawa. Walang pagkakaiba sa resulta ng integrasyon, ngunit ang pag-rebase ay ginagawang mas malinis ang kasaysayan. Kung iyong susuriin ang log ng isang na-rebase na branch, magmumukha itong isang linear na kasaysayan: lumalabas na ang lahat ng trabaho ay nangyari na nakaserye, kahit ito ay orihinal na nangyari na nakahilera.

Kadalasan, gagawin mo ito upang siguraduhing ang iyong mga commit ay nalapat nang malinis sa isang remote na branch — marahil sa isang proyekto kung saan sinusubukan mong mag-ambag ngunit iyong hindi mo na pinapanatili. Sa kasong ito, gagawin mo ang iyong trabaho sa isang branch at pagkatapos ay iri-rebase ang iyong trabaho sa origin/master kapag ikaw ay handa nang isumite ang iyong mga patch sa pangunahing proyekto. Sa paraan iyon, ang tagapanatili ay hindi na kailangang gumawa ng anumang pagsasama na trabaho — isang fast-forward o isang malinis na paglapat lamang.

Tandaan na ang snapshot na itinuro ng huling commit na nagtapos sa iyo, kung ito ay ang huli sa na-rebase na mga commit para sa isang rebase o ang huling merge na commit pagkatapos ng isang merge, ay ang parehong snapshot - ang kasaysayan lamang ang naiiba. Ang pag-rebase ay iri-replay ang mga pagbabago mula sa isang linya ng trabaho patungo sa iba sa pagkakaayos base sa kanilang pagpakilala, samantalang ang pag-merge ay kinukuha ang mga endpoint at sama-samang imi-merge ang mga ito.

Maraming Kagiliw-giliw na mga Rebase

Maaari ka ring magkaroon ng iyong rebase na replay sa anuman maliban sa rebase na target na branch. Gamitin ang isang kasaysayan katulad ng Isang kasaysayan na may isang paksa na naka-branch off sa ibang paksa na branch, bilang halimbawa. Ikaw ay nag-branch ng isang paksa na branch (server) upang magdagdag ng ilang server-side na functionality sa iyong proyekto, at gumawa ng isang commit. Pagkatapos, ikaw ay nag-branch off nito upang gawin ang mga pagbabago sa client-side (client) at nag-commit ng ilang beses. Sa wakas, ikaw ay bumalik sa iyong server na branch at gumawa ng ilang mga commit.

Isang kasaysayan na may isang paksa na naka-branch off sa ibang paksa na branch.
Figure 39. Isang kasaysayan na may isang paksa na naka-branch off sa ibang paksa na branch

Ipagpalagay na ikaw na nakapagpasya na gusto mong i-merge ang iyong client-side na mga pagbabago sa iyong mainline para sa isang release, ngunit gusto mong pigilan ang mga pagbabago ng server-side hanggang ito ay mas lalo pang nasubukan. Maaari mong kunin ang mga pagbabago na wala sa server (C8 at C9) at i-replay ang mga ito sa iyong master na branch gamit ang --onto na opsyon ng git rebase:

$ git rebase --onto master server client

Ang ibig talagang sabihin nito ay, “Kunin ang client na branch, alamin ang mga patch mula nung humiwalay ito mula sa server na branch, at i-replay ang mga patch na ito sa client na branch animo ito ay direktang nakabase sa master na branch.” Ito ay medyo kumplikado, ngunit ang resulta ay talagang kamangha-mangha.

Pag-rebase ng isang paksa na naka-branch off sa ibang paksa na branch.
Figure 40. Pag-rebase ng isang paksa na naka-branch off sa ibang paksa na branch

Ngayon maaari ka nang mag-fast-forward sa iyong master na branch (tingnan ang Pag-fast-forward ng iyong master na branch upang isama ang mga pagbabago sa kliyente na branch):

$ git checkout master
$ git merge client
Pag-fast-forward ng iyong master na branch upang isama ang mga pagbabago sa kliyente na branch.
Figure 41. Pag-fast-forward ng iyong master na branch upang isama ang mga pagbabago sa kliyente na branch

Sabihin nating ikaw ay nakapagpasyang mag-pull din sa iyong server na branch. Maaari mong i-rebase ang server na branch sa master na branch nang hindi kailangang unang mag-check out nito sa pamamagitan ng pagpapatakbo ng git rebase <basebranch> <topicbranch> — na nagchi-check out ng paksa na branch (sa kasong ito, server) para sa iyo at iri-replay ito sa base na branch (master):

$ git rebase master server

Ito ay iri-replay ang iyong server na trabaho sa itaas ng iyong master na trabaho, na ipinapakita sa Pag-rebase ng iyong server na branch sa itaas ng iyong master na branch.

Pag-rebase ng iyong server na branch sa itaas ng iyong master na branch.
Figure 42. Pag-rebase ng iyong server na branch sa itaas ng iyong master na branch

Pagkatapos, maaari mong i-fast-forward ang base na branch (master): Then, you can fast-forward the base branch (master):

$ git checkout master
$ git merge server

Maaari mong tanggalin ang client at server na mga branch dahil lahat ang trabaho ay napagsama-sama na at hindi mo na kailangan ang mga ito, iniiwan ang iyong kasaysayan para sa buong proseso na nagmumukhang katulad ng Huling kasaysayan ng commit:

$ git branch -d client
$ git branch -d server
Huling kasaysayan ng commit.
Figure 43. Huling kasaysayan ng commit

Ang mga Panganib ng Pag-rebase

Ahh, ngunit ang kaligayahan ng pag-rebase ay mayroon ding kakulangan, na maaaring mabuo sa isang linya:

Huwag mag-rebase ng mga commit na umiiral sa labas ng iyong repositoryo.

Kung susundin mo ang patnubay na iyon, magiging maayos ka. Kung hindi, kasusuklaman ka ng mga tao, at kakamuhian ka ng iyong mga kaibigan at pamilya.

Kapag ikaw ay nag-rebase ng mga bagay, ikaw ay lumilisan sa umiiral na mga commit at gumagawa ng mga panibago na kahawig ngunit naiiba. Kung magpu-push ka ng mga commit saanman at ang iba ay magpu-pull down ng mga ito at magbabase sa trabaho nito, at pagkatapos ay isusulat mo muli ang mga commit na iyon gamit ang git rebase at i-push muli ang mga ito, ang iyong mga katulong ay kailangang mag merge muli ng kanilang trabaho at ang mga bagay ay magugulo kapag sinubukan mong mag-pull sa kanilang trabaho patungo pabalik sa iyo.

Tumingin tayo sa isang halimbawa kung papaano ang pag-rebase ng trabaho na iyong ginawang publiko ay maaaring magsanhi ng mga problema. Ipagpalagay na ikaw ay nag-clone mula sa isang sentral na server at pagkatapos ay gumawa ng ilang trabaho sa iyon. Ang iyong kasaysayan ng commit ay magmumukhang katulad nito:

Mag-clone ng isang repositoryo, at magbase ng ilang trabaho nito.
Figure 44. Mag-clone ng isang repositoryo, at magbase ng ilang trabaho nito

Ngayon, may iba pang gumagawa ng higit pang trabaho na nagsasama ng isang merge, at nagpu-push ng trabahong iyon sa sentral na server. Kinuha mo ito at nag-merge ng bagong remote na branch sa iyong trabaho, ginagawang magkamukha nito ang iyong kasaysayan:

Kumuha ng maraming mga commit, at i-merge ang mga ito sa iyong trabaho.
Figure 45. Kumuha ng maraming mga commit, at i-merge ang mga ito sa iyong trabaho

Susunod, ang tao na nag-push ng na-merge na trabaho ay nagpasyang bumalik at sa halip ay mag-rebase ng kanilang trabaho; gumawa sila ng isang git push --force upang sapawan ang kasaysayan sa server. Ikaw ngayon ay nag-fetch mula sa server na iyon, hinihila pababa ang mga bagong commit.

May isang tao na nagpu-push ng na-rebase na mga commit, iniiwanan ang mga commit kung saan mo binase ang iyong trabaho.
Figure 46. May isang tao na nagpu-push ng na-rebase na mga commit, iniiwanan ang mga commit kung saan mo binase ang iyong trabaho

Ngayon kayong dalawa ay nasa mahirap na kalagayan. Kung gagawa ka ng isang git pull, ikaw ay lilikha ng isang merge na commit na naglalaman ng parehong mga linya ng kasaysayan, at ang iyong repositoryo ay magmumukhang katulad nito:

Ikaw ay muling nag-merge sa parehong trabaho patungo sa isang panibagong merge na commmit.
Figure 47. Ikaw ay muling nag-merge sa parehong trabaho patungo sa isang panibagong merge na commmit

Kung magpapatakbo ka ng isang git log kapag ang iyong kasaysayan ay magmumukhang katulad nito, makikita mo ang dalawang mga commit na mayroong parehong may-akda, petsa, at mensahe, na kung saan ay nakalilito. At saka, kung ipu-push mo ang kasaysayang ito pabalik sa server, ipapakilala mo ulit ang lahat ng mga na-rebase na commit na iyon sa sentral na server, na kung saan ay tuluyan pang nakakalito sa mga tao. Medyo ligtas magpalagay na ang ibang developer ay hindi gusto ang C4 at C6 sa kasaysayan; iyan ang dahilan kung bakit sila nag-rebase sa simula pa lang.

Mag-rebase Kung Ikaw ay Mag-rebase

Kung talagang natagpuan mo iyong sarili sa sitwasyong katulad nito, ang Git ay may ilang higit pang salamangka na maaaring makatulong sa iyo. Kung may tao sa iyong team na sapilitang nagpu-push ng mga pagbabago na nag-o-overwrite ng trabaho kung saan nakabase ang iyong mga ginawa, ang iyong hamon ay ang malaman kung ano ang sa iyo at kung ano ang nasulat muli nila.

Lumilitaw na sa karagdagan sa checksum ng commit SHA-1, ang Git ay nagkakalkula rin ng isang checksum na nakabase lamang sa patch na napakilala sa commit. Ito ay tinatawag na isang “patch-id”.

Kung ikaw ay magpu-pull down ng trabaho na muling isinulat at iri-rebase ito sa itaas ng bagong mga commit mula sa iyong kasosyo, ang Git ay kadalasang matagumpay na nalalaman kung ano ang katangi-tanging sa iyo at ilalapat ang mga ito pabalik sa itaas ng bagong branch.

Halimbawa, sa nakaraang sitwasyon, kung sa halip na gumawa ng isang merge kapag tayo ay nasa May isang tao na nagpu-push ng na-rebase na mga commit, iniiwanan ang mga commit kung saan mo binase ang iyong trabaho papatakbuhin natin ang git rebase teamone/master, ang Git ay:

  • Tutukuyin kung anong trabaho ang katangi-tangi sa ating branch (C2, C3, C4, C6, C7)

  • Tutukuyin kung ano ang hindi merge na mga commit (C2, C3, C4)

  • Tutukuyin kung ano ang hindi naisulat muli sa target na branch (C2 at C3 lamang, dahil ang C4 ay kaparehong patch sa C4')

  • Ilalapat ang mga commit na iyon sa itaas ng teamone/master

Kaya sa halip sa resulta na nakikita natin sa Ikaw ay muling nag-merge sa parehong trabaho patungo sa isang panibagong merge na commmit, tayo ay magtatapos na may bagay na mas katulad sa Pag-rebase sa itaas ng na-force-push na rebase ng trabaho.

Pag-rebase sa itaas ng na-force-push na rebase ng trabaho.
Figure 48. Pag-rebase sa itaas ng na-force-push na rebase ng trabaho

Ito ay gumagana lamang kung ang C4 at C4' na ginawa ng iyong kasosyo ay halos eksaktong magkatugma na patch. Kung hindi ay ang rebase ay hindi makakapagsabi na ito ay isang kopya at magdaragdag ng ibang katulad ng C4 na patch (na marahil ay mabibigong malinis na maglapat, dahil ang mga pagbabago ay marahil nandoon na).

Maaari mo ring pasimplihin ito sa pamamagitan ng isang git pull --rebase sa halip na isang normal na git pull. O maaari mong manu-manong gawin ito gamit ang isang git fetch na sinusundan ng isang git rebase teamone/master sa kasong ito.

Kung ikaw ay gumagamit ng git pull at gustong i---rebase ang default, maaari mong itakda ang halaga ng pull.rebase na config gamit ang kagaya ng git config --global pull.rebase true.

Kung ituturing mo ang pag-rebase bilang isang paraan upang maglinis at magtrabaho sa mga commit bago mo i-push ang mga ito, at kung ikaw ay magri-rebase lamang ng mga commit na hindi pampublikong magagamit, ikaw ay magiging maayos lamang. Kung ikaw ay magri-rebase ng mga commit na pampublikong nai-push na, at ang mga tao ay bumabase ng kanilang trabaho sa mga commit na iyon, ikaw ay maaaring mapasabak sa isang nakakabigong panganib, at suklam ng iyong mga kasamahan sa koponan.

Kung ikaw o isang kasosyo ay nakatuklas na kinakailangan ito sa isang punto, siguraduhing ang lahat ay nakakaalam kung paano patakbuhin ang git pull --rebase upang subukang mas pasimplehin pa ang sakit nito pagkatapos.

Rebase vs. Merge

Ngayong nakita mo na ang pag-rebase at pag-merge na kumikilos, baka ikaw ay nagtataka kung ano ang mas mabuti. Bago natin maaaring sagutin ito, umatras muna tayo at pag-usapan ang tungkol sa ibig sabihin ng kasaysayan.

Isang pananaw nito ay ang iyong kasaysayan ng commit sa repositoryo ay isang rekord kung ano ang tunay na nangyari. Ito ay isang makasaysayang dokumento, mahalaga sa sarili nitong karapatan, at hindi dapat mabago. Mula sa anggulong ito, ang pagbabago sa kasaysayan ng commit ay halos lapastangan sa diyos; ikaw ay nagsisinungaling tungkol sa kung ano ang tunay na naganap. Kaya ano kung mayroong isang magulong serye ng merge na mga commit? Iyon ang nangyari, at ang repositoryo ay kailangang i-preserba iyon para sa angkan.

Ang humahadlang na pananaw ay ang kasaysayan ng commit ay ang istorya kung papaano nagawa ang iyong proyekto. Hindi mo iaambag ang unang draft ng isang aklat, at ang manwal para sa kung papaano panatilihin ang iyong software ay nararapat na ingatan ang pag-edit. Ito ang kampo na gumagamit ng mga kasangkapan katulad ng rebase at filter-branch upang magtalakay sa istorya sa paraan na pinakamainam para sa mga mambabasa sa hinaharap.

Ngayon, sa tanong kung alin sa pag-merge o pag-rebase ang mas mabuti: sana makita mo na ito ay hindi ganoon ka simple. Ang Git ay isang makapangyarihan na kasangkapan, at nagpapahintulot sa iyo upang gumawa ng maraming mga bagay at gamit ang iyong kasaysayan, ngunit bawat koponan at bawat proyekto ay magkakaiba. Ngayon na alam mo na kung paano gumagana ang dalawang bagay na ito, nakasalalay sa iyo na magpasya kung ano ang pinakamainam para sa iyong partikular na sitwasyon.

Sa karaniwan ang paraan upang makakuha ng pinakamabuti sa dalawang mundo ay ang pag-rebase ng mga lokal na pagbabago na ginawa mo ngunit hindi pa naibahagi bago mo i-push ang mga ito upang linisin ang iyong istorya, ngunit huwag mag-rebase ng kahit ano na na-push mo kahit saan.