Git
Chapters ▾ 2nd Edition

3.6 Větve v systému Git - Přeskládání

Přeskládání

V Gitu existují dva základní způsoby, jak integrovat změny z jedné větve do druhé: merge (sloučení, začlenění) a rebase (přeskládání). V této podkapitole se dozvíte, co to přeskládání je, jak se provádí, proč je to docela úžasný nástroj a v jakých případech ho raději nepoužívat.

Základní přeskládání

Pokud se vrátíte k dřívějšímu příkladu v Základní slučování, vidíte, že jste se při práci odchýlili a vznikly objekty revize ve dvou různých větvích.

Jednoduché rozvětvení historie.
Figure 35. Jednoduché rozvětvení historie

Jak jsme si již ukázali, nejjednodušší způsob spojení větví spočívá v použití příkazu merge. Ten provede třícestné sloučení mezi dvěma posledními snímky (C3 a C4) a jejich nejmladším společným předkem (C2), přičemž vytvoří nový snímek (a novou revizi).

Spojení rozvětvené historie sloučením.
Figure 36. Spojení rozvětvené historie sloučením

Existuje však ještě jiný způsob. Můžete vzít záplatu se změnou, která vznikla v revizi C4, a aplikovat ji na revizi C3. V Gitu se to nazývá přeskládáním (rebasing). Příkazem rebase vezmete všechny změny, které byly zapsány na jedné větvi, a necháte je přehrát na jinou větev.

U tohoto příkladu byste provedli následující:

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

Funguje to tak, že se přejde na společného předka obou větví (větve, na níž se nacházíte, a větve, na kterou přeskládáváte), zjistí se rozdíly pro každý objekt revize ve větvi, na které se nacházíte, zjištěné rozdíly se uloží do dočasných souborů, aktuální větev se nastaví na stejný bod jako větev, na kterou přeskládáváte, a nakonec postupně aplikují všechny změny.

Přeskládání změn vzniklých v `C4` na `C3`.
Figure 37. Přeskládání změn vzniklých v C4 na C3

V tomto okamžiku můžete přejít zpět na větev master a provést sloučení typu „rychle vpřed“.

$ git checkout master
$ git merge experiment
Posunutí větve `master` rychle vpřed.
Figure 38. Posunutí větve master rychle vpřed

Snímek, na který odkazuje C4', je v tomto okamžiku přesně stejný, jako ten, na který u příkladu využívajícího slučování odkazoval C5. Ve výsledcích integrace není žádný rozdíl, výsledkem přeskládání je však čistší historie. Pokud si prohlížíte log přeskládané větve, vypadá jako lineární historie. Zdá se, jako kdyby veškeré práce probíhaly postupně, i když původně proběhly paralelně.

Tuto metodu budete často používat v situaci, kdy chcete mít jistotu, že byly na vzdálenou větev vaše změny aplikovány čistě — například u projektu, do kterého chcete přispět, ale který nespravujete. V takovém případě budete pracovat ve své větvi, a až budete mít připraveny záplaty k odeslání do hlavního projektu, přeskládáte svou práci na větev origin/master. Správce v tomto případě nemusí provádět žádnou integraci, provede pouze posun „rychle vpřed“ nebo čistou aplikaci.

Všimněte si, že snímek, na který ukazuje poslední objekt revize — ať už se jedná o poslední revizi vzniklou přeskládáním, nebo poslední bod sloučení po provedení merge --, je shodný. Liší se pouze historie. Přeskládání provede změny z jedné řady prací na jinou, a to v pořadí, v jakém byly provedeny. Naproti tomu sloučení vezme koncové body větví a sloučí je dohromady.

Zajímavější možnosti přeskládání

Posloupnost změn můžete aplikovat i na něco jiného než na cílovou větev pro přeskládání. Vezměme si například historii na obrázku Historie s tématickou větví vytvořenou z jiné tématické větve. Vytvořili jste novou tématickou větev (server), abyste v ní do projektu přidali novou funkčnost na straně serveru, a zapsali jste revizi. Poté jste se odvětvili, abyste učinili změny na straně klienta (client), a provedli jste několik zápisů. Nakonec jste se vrátili na větev server a zapsali dalších pár objektů revize.

Historie s tématickou větví vytvořenou z jiné tématické větve.
Figure 39. Historie s tématickou větví vytvořenou z jiné tématické větve

Předpokládejme, že nyní chcete změny straně klienta začlenit do své hlavní linie k vydání, ale prozatím chcete počkat se změnami na straně serveru, dokud nebudou víc otestovány. Můžete vzít změny na větvi client, které nejsou na větvi server (C8 a C9) a aplikovat je na větev master příkazem git rebase s volbou --onto:

$ git rebase --onto master server client

V podstatě tím říkáte: „Přepni se (checkout) na větev client, zjisti záplaty od společného předka větví client a server a znovu je aplikuj na větev master.“ Je to možná trochu složitější, ale výsledek stojí za to.

Přeskládání tématické větve vzniklé odvětvením z jiné tématické větve.
Figure 40. Přeskládání tématické větve vzniklé odvětvením z jiné tématické větve

Teď můžete větev master posunout „rychle vpřed“ (viz obrázek):

$ git checkout master
$ git merge client
Přidání změn z větve `client` posunutím větve `master` „rychle vpřed“.
Figure 41. Přidání změn z větve client posunutím větve master „rychle vpřed“

Řekněme, že se později rozhodnete vtáhnout i větev server. Větev server můžete přeskládat na větev master — aniž byste se na ni nejdříve museli nejdříve přepnout — tak, že provedete git rebase [základna] [tématická větev] — provede se tím přepnutí na tématickou větev (checkout, v tomto případě na větev server) a přeskládá její změny na základnu (angl. base branch, v tomto případě master):

$ git rebase master server

Příkaz přehraje práci z větve server k práci ve větvi master — viz obrázek.

Přeskládání větve `server` na vrchol větve `master`.
Figure 42. Přeskládání větve server na vrchol větve master

Poté můžete bázovou větev (master) přesunout „rychle vpřed“:

$ git checkout master
$ git merge server

Větve client a server můžete smazat, protože všechna práce z nich je integrována a tyto větve už nebudete potřebovat. Historie celého procesu bude vypadat jako na obrázku Historie poslední revize:

$ git branch -d client
$ git branch -d server
Historie poslední revize.
Figure 43. Historie poslední revize

Rizika spojená s přeskládáním

Ale slastné přeskládání má i stinné stránky, které se dají shrnout do jednoho řádku:

Neprovádějte přeskládání pro revize, které existují mimo váš repozitář.

Pokud se budete touto zásadou řídit, budete v pohodě. Pokud ne, lidi vás budou nenávidět, přátelé a rodina vámi budou opovrhovat.

Pokud provedete přeskládání, zahazujete existující objekty revize (commits) a vytváříte nové, které jsou podobné, ale přesto jiné. Pokud objekty revize odešlete (push) a ostatní si je stáhnou (pull) a založí na nich svou práci a vy potom tyto revize přepíšete příkazem git rebase a znovu je odešlete, vaši spolupracovníci budou muset znovu znovu začleňovat svou práci (merge) a ve věcech zavládne chaos v okamžiku, kdy se pokusíte vtáhnout jejich práci zpět do své.

Podívejme se na příklad toho, jaké problémy může přeskládání již zveřejněných dat způsobit. Předpokládejme, že jste naklonovali z centrálního serveru a provedli jste několik změn. Historie revizí bude vypadat nějak takto:

Naklonování repozitáře a provedení změn.
Figure 44. Naklonování repozitáře a provedení změn

Někdo jiný teď provede další úpravy, jejichž součástí bude i sloučení (merge), a odešle svou práci na centrální server. Vy tyto změny vyzvednete a začleníte novou vzdálenou větev do své práce — vaše historie teď vypadá nějak takto:

Vyzvednutí (fetch) bodů zápisu a jejich začlenění (merge) do vaší práce.
Figure 45. Vyzvednutí (fetch) bodů zápisu a jejich začlenění (merge) do vaší práce

Jenže osoba, která odeslala výsledek sloučení, se rozhodne vrátit zpět a svou práci raději přeskládat. Provede příkaz git push --force a přepíše historii na serveru. Vy poté znovu vyzvednete data ze serveru (fetch), čímž získáte nové objekty revizí.

Někdo odešle přeskládané objekty revize a zahodí ty, z kterých jste při své práci vycházeli.
Figure 46. Někdo odešle přeskládané objekty revize a zahodí ty, z kterých jste při své práci vycházeli

Teď jste oba v loji. Pokud provedete git pull, vytvoříte bod sloučení (merge commit), který zahrnuje obě historické linie. Váš repozitář bude vypadat takto:

Opakované začlenění stejné práce do nového bodu sloučení.
Figure 47. Opakované začlenění stejné práce do nového bodu sloučení

Pokud s takovouto historií spustíte příkaz git log, nastane zmatečná situace, kdy se zobrazí dvě revize se stejným autorem, datem a zprávou k revizi. Když navíc odešlete takovou historii zpět na server, znovu na centrálním serveru vzniknou všechny objekty revizí, které vznikly přeskládáním, což vede k dalšímu zmatení lidí okolo. Dá se předpokládat, že druhý vývojář nechtěl, aby byly C4 a C6 součástí historie. To je důvodem, proč přeskládání vůbec prováděl.

Přeskládejte po přeskládání

Když už se do takové situace dostanete, má Git v zásobě trochu magie, která vám z toho může pomoci ven. Pokud někdo ve vašem týmu vynutí odeslání změn, které přepíší práci z které vycházíte, pak potřebujete zjistit, co zůstalo vaše a co bylo přepsáno.

Věc se má tak, že Git pro objekt revize kromě kontrolního součtu (SHA-1) počítá také kontrolní součet, který vychází pouze ze záplat, které s revizí vznikají. Jmenuje se to „patch-id“ (doslova „identifikace záplaty“).

Pokud si stáhnete práci, která byla přepsána a nad těmito novými objekty revize od spolupracovníka provedete přeskládání, umí často Git úspěšně zjistit, co pochází určitě od vás a aplikovat to na vrchol nové větve.

Tak například, pokud se při předchozím scénáři nacházíme v situaci na obrázku Někdo odešle přeskládané objekty revize a zahodí ty, z kterých jste při své práci vycházeli a místo provedení sloučení (merge) spustíme git rebase teamone/master, provede Git následující:

  • Určí, jaká práce je pro naši větev jedinečná (C2, C3, C4, C6, C7).

  • Určí, co z toho nejsou body sloučení (C2, C3, C4).

  • Určí, které z nich nebyly do cílové větve zapsány znovu (jsou to jen C2 a C3, protože C4 představuje stejnou záplatu jako C4').

  • Na teamone/master aplikuje jen určené revize.

Takže místo výsledku z obrázku Opakované začlenění stejné práce do nového bodu sloučení, skončíme s něčím, co odpovídá spíše obrázku Přeskládání nad vynuceně odeslaným výsledkem přeskládání.

Přeskládání nad vynuceně odeslaným výsledkem přeskládání.
Figure 48. Přeskládání nad vynuceně odeslaným výsledkem přeskládání

Funguje to jen tehdy, když C4 a C4', které vytvořil váš partner, představují téměř shodné záplaty. V opačném případě nebude příkaz rebase schopen zjistit, že se jedná o duplikát, a přidá další záplatu, která se podobá té z C4 (kterou se ale pravděpodobně nepodaří čistě aplikovat, protože tam budou alespoň nějaké změny).

Můžete si to trochu zjednodušit spuštěním git pull --rebase místo běžného git pull. Nebo to v tomto případě můžete provést ručně příkazem git fetch, následovaným příkazem git rebase teamone/master.

Pokud používáte git pull a chcete, aby se z --rebase stala výchozí volba, můžete nastavit konfigurační hodnotu pull.rebase příkazem jako je git config --global pull.rebase true.

Pokud přeskládání chápete jako způsob úklidu a práce s revizemi před tím, než je odešlete, a pokud přeskládáváte jen revize, které nikdy nebyly veřejně dostupné, pak budete v pohodě. Pokud přeskládáte revize, které již byly odeslány do veřejného prostoru a ostatní na těchto revizích (commit) mohli založit další práce, pak se můžete dostat do nepříjemných potíží a stát se předmětem opovržení týmových kolegů.

Pokud to vy nebo váš partner v určité chvíli považujete za nezbytnost, ujistěte se, že všichni umí spustit git pull --rebase, aby poté své utrpení učinili o něco snesitelnějším.

Přeskládání vs. slučování

Když jste teď přeskládání a slučování viděli v akci, možná by vás zajímalo, které z nich je lepší. Než na to budeme moci odpovědět, podívejme se na to s odstupem a proberme si, co rozumíme historií.

Jeden pohled na věc považuje historii zápisů do repozitáře za záznam toho, co se skutečně stalo. Je to historický dokument, který je cenný sám o sobě, a neměli bychom s ním manipulovat. Z tohoto pohledu jsou úpravy historie zápisů revizí téměř rouhačstvím; lžete o tom, co se skutečně stalo. Jenže co když se vyskytly nějaké zamuchlané posloupnosti bodů sloučení? Takhle se to stalo a repozitář by to měl zachovat pro další generace.

Z opačného pohledu je historie zápisu revizí příběhem o tom, jak váš projek vznikal. U knihy byste také nepublikovali pracovní verzi a příručka o tom, jak udržovat udržovat váš vlastní software vyžaduje pečlivé úpravy. Zastánci tohoto tábora používají nástroje jako přeskládání a filtrování větví k tomu, aby příběh sdělili způsobem, který je nejlepší z hlediska budoucích čtenářů.

A teď zpět k otázce, zda je lepší slučování nebo přeskládání. Vidíte, doufám, že to není tak jednoduché. Git je mocný nástroj a dovoluje vám s historií provádět mnoho věcí. Ale každý tým a každý projekt se liší. Když teď víte, jak oba přístupy fungují, je jen na vás, který z nich ve vaší konkrétní situaci zvolíte jako nejlepší.

Obecně můžete nejlepší z obou světů získat tím, že použijete přeskládání vašich lokálních změn, které jste dosud nezveřejnili, abyste příběh vyčistili, než jej zveřejníte. Ale nikdy neprovádějte přeskládání toho, co už bylo někam odesláno.