Chapters ▾ 2nd Edition

3.6 Git Branching - Herbasering (Rebasing)

Herbasering (Rebasing)

In Git is daar twee hoofmaniere om veranderings van een tak (branch) in 'n ander te integreer: die merge (saamsmelting) en die rebase (herbasering). In hierdie afdeling sal jy leer wat rebasing is, hoe om dit te doen, hoekom dit so 'n wonderlike instrument is, en in watter gevalle jy dit nie wil gebruik nie.

Die Eenvoudige Rebase (The Basic Rebase)

As jy teruggaan na 'n vroeëre voorbeeld uit Eenvoudige saamsmelting (Basic Merging), kan jy sien dat jou werk afgewyk het (diverged) en dat jy vasleggings (commits) op twee verskillende takke gemaak het.

Simple divergent history
Figure 35. Eenvoudige afgewykte geskiedenis (Simple divergent history)

Die maklikste manier om die takke te integreer, soos ons reeds gedek het, is die merge opdrag. Dit voer 'n drierigting-saamsmelting (three-way merge) uit tussen die twee laaste tak-momentopnames (branch snapshots) (C3 en C4) en die mees onlangse gemeenskaplike voorouer (common ancestor) van die twee (C2), en skep 'n nuwe momentopname (en vaslegging).

Merging to integrate diverged work history
Figure 36. Saamsmelting (merging) om afgewykte werkgeskiedenis te integreer

Daar is egter 'n ander manier: jy kan die pleister (patch) van die verandering wat in C4 ingestel is, neem en dit weer bo-op C3 toepas (reapply). In Git word dit rebasing (herbasering) genoem. Met die rebase opdrag kan jy al die veranderings wat op een tak vasgelê (committed) is, neem en hulle op 'n ander tak herspeel (replay).

Vir hierdie voorbeeld sal jy die experiment tak uitcheck (checkout), en dit dan soos volg op die master tak rebase:

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

Hierdie operasie werk deur na die gemeenskaplike voorouer van die twee takke te gaan (die een waarop jy is en die een waarop jy rebase), die verskil (diff) te kry wat deur elke vaslegging (commit) van die huidige tak ingestel is, daardie diffs in tydelike lêers te stoor, die huidige tak terug te stel (reset) na dieselfde vaslegging as die tak waarop jy rebase, en uiteindelik elke verandering een vir een toe te pas.

Rebasing the change introduced in `C4` onto `C3`
Figure 37. Rebase van die verandering ingestel in C4 bo-op C3

Op hierdie punt kan jy teruggaan na die master tak en 'n vinnig-vorentoe saamsmelting (fast-forward merge) doen.

$ git checkout master
$ git merge experiment
Fast-forwarding the `master` branch
Figure 38. Vinnig-vorentoe (fast-forwarding) van die master tak

Nou is die momentopname (snapshot) waarna C4' wys, presies dieselfde as dié waarna C5 in die merge-voorbeeld gewys het. Daar is geen verskil in die eindproduk van die integrasie nie, maar rebasing sorg vir 'n skoner geskiedenis. As jy die log van 'n gerebasede tak ondersoek, lyk dit soos 'n lineêre geskiedenis: dit blyk asof al die werk in 'n reeks gebeur het, selfs al het dit oorspronklik in parallel gebeur.

Jy sal dit dikwels doen om seker te maak jou vasleggings (commits) pas skoon toe op 'n afgeleë tak (remote branch) — miskien in 'n projek waartoe jy probeer bydra, maar wat jy nie self bestuur (maintain) nie. In hierdie geval sal jy jou werk in 'n tak doen en dan jou werk op origin/master rebase wanneer jy gereed is om jou pleisters (patches) aan die hoofprojek voor te lê. Op dié manier hoef die instandhouer (maintainer) geen integrasiewerk te doen nie — net 'n fast-forward of 'n skoon toepassing (clean apply).

Let daarop dat die momentopname (snapshot) waarna die finale vaslegging wys waarmee jy eindig, hetsy dit die laaste van die gerebasede vasleggings is na 'n rebase, of die finale saamsmeltingsvaslegging (merge commit) na 'n merge, presies dieselfde momentopname is — dit is slegs die geskiedenis wat verskil. Rebasing speel veranderings van een werklyn op 'n ander oor in die volgorde waarin hulle ingestel is, terwyl merging die eindpunte neem en hulle saamsmelt.

Meer Interessante Rebases (More Interesting Rebases)

Jy kan ook jou rebase op iets anders as die rebase-teikentak laat afspeel. Neem 'n geskiedenis soos 'n Geskiedenis met 'n onderwerp-tak wat van 'n ander onderwerp-tak af vertak, byvoorbeeld. Jy het 'n onderwerp-tak (topic branch) genaamd server vertak om bietjie bedienerkant-funksionaliteit (server-side functionality) by jou projek te voeg, en 'n vaslegging gemaak. Toe het jy daarvan af vertak om die kliëntkant-veranderings (client) te maak en 'n paar keer vasgelê. Ten slotte het jy teruggegaan na jou server tak en nog 'n paar vasleggings gemaak.

A history with a topic branch off another topic branch
Figure 39. 'n Geskiedenis met 'n onderwerp-tak wat van 'n ander onderwerp-tak af vertak

Veronderstel jy besluit dat jy jou kliëntkant-veranderings in jou hooflyn (mainline) wil saamsmelt vir 'n vrystelling (release), maar jy wil nog wag met die bedienerkant-veranderings totdat dit verder getoets is. Jy kan die veranderings op client neem wat nie op server is nie (C8 en C9) en hulle op jou master tak herspeel deur die --onto opsie van git rebase te gebruik:

$ git rebase --onto master server client

Dit sê basies: “Neem die client tak, vind uit wat die pleisters (patches) is vandat dit van die server tak afgewyk het, en herspeel hierdie pleisters in die client tak asof dit direk op die master tak gebaseer was.” Dit is 'n bietjie kompleks, maar die resultaat is nogal gaaf.

Rebasing a topic branch off another topic branch
Figure 40. Rebasing van 'n onderwerp-tak vanaf 'n ander onderwerp-tak

Nou kan jy jou master tak vinnig-vorentoe (fast-forward) stuur (sien Vinnig-vorentoe (fast-forwarding) van jou master tak om die client tak se veranderings in te sluit):

$ git checkout master
$ git merge client
Fast-forwarding your `master` branch to include the `client` branch changes
Figure 41. Vinnig-vorentoe (fast-forwarding) van jou master tak om die client tak se veranderings in te sluit

Kom ons sê jy besluit om jou server tak ook in te trek (pull in). Jy kan die server tak op die master tak rebase sonder om dit eers uit te check, deur git rebase <basebranch> <topicbranch> uit te voer — wat die onderwerp-tak (in hierdie geval, server) vir jou uitcheck en dit op die basistak (master) herspeel:

$ git rebase master server

Dit herspeel jou server werk bo-op jou master werk, soos getoon in Rebasing van jou server tak bo-op jou master tak.

Rebasing your `server` branch on top of your `master` branch
Figure 42. Rebasing van jou server tak bo-op jou master tak

Dan kan jy die basistak (master) vinnig-vorentoe stuur:

$ git checkout master
$ git merge server

Jy kan die client en server takke verwyder omdat al die werk geïntegreer is en jy hulle nie meer nodig het nie, wat jou geskiedenis vir hierdie hele proses soos Finale vasleggingsgeskiedenis (Final commit history) laat lyk:

$ git branch -d client
$ git branch -d server
Final commit history
Figure 43. Finale vasleggingsgeskiedenis (Final commit history)

Die Gevare van Rebasing (The Perils of Rebasing)

Aai, maar die vreugde van rebasing is nie sonder sy nadele nie, wat in 'n enkele reël opgesom kan word:

Moenie vasleggings (commits) rebase wat buite jou bewaarplek (repository) bestaan en waarop mense dalk hul werk gebaseer het nie.

As jy daardie riglyn volg, sal jy oukei wees. As jy dit nie doen nie, sal mense jou haat, en jy sal deur vriende en familie verag word.

Wanneer jy dinge rebase, laat vaar jy bestaande vasleggings en skep nuwes wat soortgelyk, maar tog verskillend is. As jy vasleggings êrens heen stuur (push) en ander trek (pull) hulle af en baseer hul werk daarop, en jy herskryf dan daardie vasleggings met git rebase en push hulle weer op, sal jou medewerkers hul werk weer moet saamsmelt (re-merge) en dinge sal morsig raak wanneer jy hul werk na joune probeer terugtrek.

Kom ons kyk na 'n voorbeeld van hoe rebasing van werk wat jy reeds publiek gemaak het, probleme kan veroorsaak. Veronderstel jy kloon vanaf 'n sentrale bediener en doen dan 'n bietjie werk daarvan af. Jou vasleggingsgeskiedenis lyk so:

Clone a repository, and base some work on it
Figure 44. Kloon 'n bewaarplek, en baseer bietjie werk daarop

Nou doen iemand anders nog werk wat 'n merge insluit, en stuur (push) daardie werk na die sentrale bediener. Jy haal dit af (fetch) en smelt die nuwe afgeleë tak (remote branch) in jou werk in, wat jou geskiedenis so iets laat lyk:

Fetch more commits, and merge them into your work
Figure 45. Haal meer vasleggings af (fetch), en smelt hulle by jou werk in (merge)

Vervolgens besluit die persoon wat die saamgesmelte werk gepush het, om terug te gaan en eerder hul werk te rebase; hulle doen 'n git push --force om die geskiedenis op die bediener te oorskryf. Jy haal (fetch) dan van daardie bediener af, wat die nuwe vasleggings aftrek.

Someone pushes rebased commits, abandoning commits you’ve based your work on
Figure 46. Iemand push gerebasede vasleggings, en laat vaar vasleggings waarop jy jou werk gebaseer het

Nou is julle albei in die pekel. As jy 'n git pull doen, sal jy 'n saamsmeltingsvaslegging (merge commit) skep wat albei lyne van die geskiedenis insluit, en jou bewaarplek sal so lyk:

You merge in the same work again into a new merge commit
Figure 47. Jy smelt dieselfde werk weer in 'n nuwe saamsmeltingsvaslegging (merge commit) in

As jy 'n git log uitvoer wanneer jou geskiedenis so lyk, sal jy twee vasleggings sien met dieselfde outeur, datum en boodskap, wat verwarrend sal wees. Verder, as jy hierdie geskiedenis weer na die bediener opstuur (push), sal jy al daardie gerebasede vasleggings weer aan die sentrale bediener bekendstel, wat mense nog verder kan verwar. Dit is redelik veilig om te aanvaar dat die ander ontwikkelaar nie C4 en C6 in die geskiedenis wil hê nie; dis hoekom hulle in die eerste plek gerebase het.

Rebase Wanneer Jy Rebase (Rebase When You Rebase)

As jy jou wel in 'n situasie soos hierdie bevind, het Git 'n bietjie verdere toorkuns wat jou dalk kan help. As iemand op jou span veranderings afdwing (force push) wat die werk oorskryf waarop jy jou werk gebaseer het, is jou uitdaging om uit te vind wat joune is en wat hulle herskryf het.

Dit blyk dat benewens die vaslegging SHA-1 kontrolesom, bereken Git ook 'n kontrolesom wat slegs gebaseer is op die pleister (patch) wat met die vaslegging ingestel is. Dit word 'n “patch-id” genoem.

As jy werk aftrek (pull) wat herskryf is en dit bo-op die nuwe vasleggings van jou vennoot rebase, kan Git dikwels suksesvol uitvind wat uniek joune is en dit weer bo-op die nuwe tak toepas.

Byvoorbeeld, in die vorige scenario, as ons in plaas van 'n merge doen wanneer ons by Iemand push gerebasede vasleggings, en laat vaar vasleggings waarop jy jou werk gebaseer het is, git rebase teamone/master uitvoer, sal Git die volgende doen:

  • Bepaal watter werk uniek is aan ons tak (C2, C3, C4, C6, C7)

  • Bepaal watter daarvan nie saamsmeltingsvasleggings (merge commits) is nie (C2, C3, C4)

  • Bepaal watter nie in die teikentak herskryf is nie (slegs C2 en C3, aangesien C4 dieselfde pleister is as C4')

  • Pas daardie vasleggings bo-op teamone/master toe

Rebase on top of force-pushed rebase work
Figure 48. Rebase bo-op afgedwonge-opgestuurde (force-pushed) rebase werk

Hierdie werk slegs as C4 en C4' wat jou vennoot gemaak het, byna presies dieselfde pleister (patch) is. Andersins sal die rebase nie kan vasstel dat dit 'n duplikaat is nie en sal nog 'n C4-agtige pleister byvoeg (wat waarschijnlijk sal misluk om skoon toe te pas, aangesien die veranderings reeds ten minste gedeeltelik daar sal wees).

Jy kan dit ook vereenvoudig deur 'n git pull --rebase in plaas van 'n normale git pull uit te voer. Of jy kan dit in hierdie geval handmatig doen met 'n git fetch gevolg deur 'n git rebase teamone/master.

As jy git pull gebruik en --rebase die verstek (default) wil maak, kan jy die pull.rebase konfigurasie-waarde stel met iets soos git config --global pull.rebase true.

As jy altyd net vasleggings rebase wat nog nooit jou eie rekenaar verlaat het nie, sal jy heel oukei wees. As jy vasleggings rebase wat gepush is, maar waarop niemand anders vasleggings gebaseer het nie, sal jy ook oukei wees. As jy vasleggings rebase wat reeds publiek gepush is, en mense dalk hul werk op daardie vasleggings gebaseer het, dan is jy dalk op pad na 'n mate van frustrerende moeilikheid, asook die veragting van jou spangenote.

As jy of 'n vennoot dit wel op een of ander stadium nodig vind, maak seker almal weet om git pull --rebase uit te voer om te probeer om die pyn nadat dit gebeur het, 'n bietjie eenvoudiger te maak.

Rebase vs. Merge

Noudat jy rebasing en merging in aksie gesien het, wonder jy dalk watter een beter is. Voordat ons dit kan beantwoord, kom ons tree 'n bietjie terug en praat oor wat geskiedenis beteken.

Een standpunt hieroor is dat jou bewaarplek (repository) se vasleggingsgeskiedenis 'n rekord is van wat werklik gebeur het. Dit is 'n historiese dokument, waardevol op sy eie, en daar behoort nie mee gepeuter te word nie. Uit hierdie hoek gesien, is die verandering van die vasleggingsgeskiedenis byna godslasterlik; jy lieg oor wat regtig gebeur het. Wat daarvan as daar 'n morsige reeks saamsmeltingsvasleggings (merge commits) was? Dit is hoe dit gebeur het, en die bewaarplek moet dit vir die nageslag bewaar.

Die teenoorgestelde standpunt is dat die vasleggingsgeskiedenis die storie is van hoe jou projek gemaak is. Jy sou nie die eerste konsep van 'n boek publiseer nie, so hoekom jou morsige werk wys? Wanneer jy aan 'n projek werk, mag jy dalk 'n rekord nodig hê van al jou misstappe en doodloopstrate, maar wanneer dit tyd is om jou werk aan die wêreld te wys, sal jy dalk 'n meer samehangende storie wil vertel van hoe jy van A na B gekom het. Mense in hierdie kamp gebruik instrumente soos rebase en filter-branch om hul vasleggings (commits) te herskryf voordat hulle in die hooftak (mainline branch) saamgesmelt (merged) word. Hulle gebruik gereedskap soos rebase en filter-branch, om die storie te vertel op 'n manier wat die beste is vir toekomstige lesers.

Nou, tot die vraag of saamsmelting (merging) of herbasering (rebasing) beter is: hopelik sal jy sien dat dit nie so eenvoudig is nie. Git is 'n kragtige instrument, en laat jou toe om baie dinge met en aan jou geskiedenis te doen, maar elke span en elke projek is verskillend. Noudat jy weet hoe albei hierdie dinge werk, is dit aan jou om te besluit watter een die beste vir jou spesifieke situasie is.

Jy kan die beste van albei wêrelde kry: rebase plaaslike (local) veranderings voordat jy push om jou werk skoon te maak, maar moenie ooit enigiets rebase wat jy iewers heen gepush het nie.