Git
Chapters ▾ 2nd Edition

3.2 Větve v systému Git - Základy větvení a slučování

Základy větvení a slučování

Proberme si jednoduchý příklad větvení a slučování s pracovním postupem, který můžete využít i v reálném životě. Postupujete podle těchto kroků:

  1. Pracujete na webových stránkách.

  2. Vytvoříte větev pro nový případ, na kterém pracujete.

  3. V této větvi něco uděláte.

V tomto okamžiku vám zavolají, že se vyskytla jiná kritická chyba, která vyžaduje rychlou opravu (hotfix). Provedete následující:

  1. Přepnete se na produkční větev.

  2. Vytvoříte větev pro přidání opravy.

  3. Po otestování začleníte větev s opravou a odešlete ji do produkce.

  4. Přepnete zpět na svůj původní případ a pokračujete v práci.

Základní větvení

Řekněme, že pracujete na projektu a už jste vytvořili několik revizí.

Jednoduchá historie revizí.
Figure 18. Jednoduchá historie revizí

Rozhodli jste se, že budete pracovat na problému #53 (issue), zachyceném v jakémkoliv systému pro sledování chyb, který vaše společnost používá. Abyste vytvořili novou větev a rovnou na ni přepnuli, můžete spustit příkaz git checkout s přepínačem -b:

$ git checkout -b iss53
Switched to a new branch "iss53"

Jde o zkratku pro příkazy:

$ git branch iss53
$ git checkout iss53
Vytvoření nového ukazatele větve.
Figure 19. Vytvoření nového ukazatele větve

Pracujete na webových stránkách a zapíšete několik revizí. S každou novou revizí se větev iss53 posune vpřed, protože jste na ni přepnutí (provedli jste checkout a HEAD na ni ukazuje):

$ vim index.html
$ git commit -a -m 'added a new footer [issue 53]'
Větev iss53 se při práci posunula vpřed.
Figure 20. Větev iss53 se při práci posunula vpřed

V tomto okamžiku vám zavolají, že se na webových stránkách vyskytl problém, který musíte okamžitě vyřešit. Při používání Gitu nemusíte nasadit opravu společně se změnami, které jste provedli při řešení problému iss53. Před opravou produkční verze nemusíte ani vynakládat velké úsilí a změny zase rušit. Vše, co musíte udělat, je přepnout se zpět na větev master.

Ale než tak učiníte, všimněte si, že pokud máte v pracovním adresáři nebo v oblasti připravených změn nezapsané změny, které kolidují s větví, do které se přepínáte, Git vám přepnutí větví nedovolí. Nejlepší je, když máte při přepínání větví čistý pracovní stav. Existují způsoby, jak to obejít (konkrétně odložení (stashing) a úprava revize (commit amending)), těm se však budeme věnovat až později, v části Stashing and Cleaning. Teď předpokládejme, že jste všechny změny zapsali, takže se můžete přepnout zpět do větve master:

$ git checkout master
Switched to branch 'master'

V tomto okamžiku vypadá váš pracovní adresář přesně tak, jak vypadal, než jste začali pracovat na problému #53, a vy se nyní můžete soustředit na požadovanou opravu. Zapamatujte si následující důležitý bod: když přepínáte větve, Git nastavuje obsah pracovního adresáře tak, aby vypadal stejně jako v době, kdy jste v dané větvi prováděli poslední zápis. Automaticky přidá, odstraní a upraví soubory tak, aby zajistil, že vaše pracovní kopie bude vypadat stejně, jako poslední revize v dané větvi.

V dalším kroku máte provést opravu. Vytvořme si pro ni větev, na níž budeme pracovat, dokud nebude oprava hotová:

$ git checkout -b hotfix
Switched to a new branch 'hotfix'
$ vim index.html
$ git commit -a -m 'fixed the broken email address'
[hotfix 1fb7853] fixed the broken email address
 1 file changed, 2 insertions(+)
Větev pro opravu je odvozená od `master`.
Figure 21. Větev pro opravu je odvozená od master

Můžete spustit testy, abyste se ujistili, že oprava splňuje všechny požadavky, a pak můžete větev začlenit (merge) zpět do větve master, aby mohla být nasazena k používání. Učiníte tak příkazem git merge:

$ git checkout master
$ git merge hotfix
Updating f42c576..3a0874c
Fast-forward
 index.html | 2 ++
 1 file changed, 2 insertions(+)

Ve výstupu příkazu merge si všimněte fráze „fast-forward“. Protože byl objekt revize C4, na který odkazovala začleňovaná větev hotfix, přímým následníkem objektu C2, na kterém jste, Git jednoduše přesune ukazatel vpřed. Jinými slovy, pokud se snažíte sloučit jeden objekt revize s jiným, na který se můžete dostat přes historii prvního objektu revize, Git to vše zjednoduší tím, že přesune ukazatel vpřed, protože neexistuje žádná odchylující se práce, s kterou bychom to museli slučovat. Tomuto postupu se říká „rychle vpřed“ („fast-forward“).

Vaše změna je nyní obsažena ve snímku revize, na kterou ukazuje větev master, a vy můžete zveřejnit opravu.

Větev `master` se přesunula „rychle vpřed“ na `hotfix`.
Figure 22. Větev master se přesunula „rychle vpřed“ na hotfix

Po zveřejnění veledůležité opravy se můžete přepnout zpět na práci, které jste se věnovali před jejím přerušením. Nejprve však smažete větev hotfix, protože už ji nebudete potřebovat — větev master ukazuje na totéž místo. Větev smažete přidáním volby -d k příkazu git branch:

$ git branch -d hotfix
Deleted branch hotfix (3a0874c).

Teď se můžete přepnout zpět na větev s rozdělanou prací a pokračovat na problému #53.

$ git checkout iss53
Switched to branch "iss53"
$ vim index.html
$ git commit -a -m 'finished the new footer [issue 53]'
[iss53 ad82d7a] finished the new footer [issue 53]
1 file changed, 1 insertion(+)
Pokračuje práce na větvi `iss53`.
Figure 23. Pokračuje práce na větvi iss53

Za zmínku stojí, že práce, kterou jste udělali ve větvi hotfix, není obsažena v souborech ve větvi iss53. Pokud potřebujete tyto změny do větve vtáhnout, můžete začlenit větev master do větve iss53 provedením příkazu git merge master, nebo můžete s integrací změn vyčkat a provést ji až ve chvíli, kdy budete chtít větev iss53 vtáhnout zpět do větve master.

Základní slučování

Předpokládejme, že jste se rozhodli, že práce na problému #53 je hotová a lze ji začlenit do větve master. Abychom tak učinili, začleníme větev iss53 do větve master podobně, jako jsme to učinili dříve s větví hotfix. Jediné, co pro to musíte udělat, je přepnout na větev, do které chcete tuto větev začlenit, a potom spustit příkaz git merge.

$ git checkout master
Switched to branch 'master'
$ git merge iss53
Merge made by the 'recursive' strategy.
index.html |    1 +
1 file changed, 1 insertion(+)

Tady už se to od dřívějšího slučování s větví hotfix trochu liší. V tomto případě se historie vývoje od určitého bodu v minulosti rozbíhala. Protože objekt revize, na kterém se ve větvi nacházíte, není přímým předkem větve, kterou připojujete, musí s tím Git něco udělat. Git v tomto případě provádí jednoduché třícestné sloučení, při kterém používá dva snímky, na které ukazují vrcholy větví, a jejich společného předka.

Při typickém slučování se používají tři snímky.
Figure 24. Při typickém slučování se používají tři snímky

Git místo pouhého přesunutí ukazatele větve vpřed, tentokrát vytvoří nový snímek, který je výsledkem třícestného slučování, a automaticky vytvoří nový objekt revize, který jej zachycuje. Říká se mu bod sloučení (merge commit) a je zvláštní v tom, že má víc než jednoho rodiče.

Bod sloučení.
Figure 25. Bod sloučení

Za zmínku stojí, že Git určí nejlepšího společného předka, jako základ pro slučování. Tím se liší od starších nástrojů jako CVS nebo Subversion (před verzí 1.5), kde si musel nejlepší základ pro slučování určit sám vývojář, který sloučení prováděl. Tím se slučování větví v Gitu stává o dost jednodušší, než je tomu v ostatních systémech.

Nyní, když jste svou práci začlenili, nebudete už větev iss53 potřebovat. V systému sledování tiketů můžete tento tiket uzavřít a větev můžete smazat:

$ git branch -d iss53

Základní konflikty při slučování

Občas se stane, že tento proces neproběhne hladce. Pokud jste tutéž část stejného souboru změnili odlišně ve dvou větvích, které chcete sloučit, Git je nebude umět sloučit čistě. Pokud se oprava problému #53 týkala stejné části souboru jako větev hotfix, dojde ke konfliktu při slučování (merge conflict), který vypadá nějak takto:

$ git merge iss53
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.

Git automaticky nevytvořil nový bod sloučení. Prozatím pozastavil celý proces do doby, než konflikt vyřešíte. Chcete-li kdykoli po konfliktu zjistit, které soubory zůstaly nesloučeny, spusťte příkaz git status:

$ git status
On branch master
You have unmerged paths.
  (fix conflicts and run "git commit")

Unmerged paths:
  (use "git add <file>..." to mark resolution)

    both modified:      index.html

no changes added to commit (use "git add" and/or "git commit -a")

Vše, u čeho při slučování vznikl konflikt, který nebyl vyřešen, je označeno jako „unmerged“ (nesloučeno). Git přidává do souborů způsobujících konflikt standardní značky pro označení konfliktů (conflict-resolution markers), takže soubory můžete ručně otevřít a konflikty vyřešit. Soubor obsahuje část, která vypadá nějak takto:

<<<<<<< HEAD:index.html
<div id="footer">contact : email.support@github.com</div>
=======
<div id="footer">
 please contact us at support@github.com
</div>
>>>>>>> iss53:index.html

To znamená, že verze v HEAD (což je vaše větev master, protože jste na ni byli přepnutí, když jste spouštěli příkaz merge) je uvedena v horní části tohoto bloku (všechno nad oddělovačem =======), zatímco verze z větve iss53 je uvedena v dolní části. Chcete-li vzniklý konflikt vyřešit, musíte buď vybrat jednu z obou možností, nebo je ručně spojit dohromady. Tento konflikt můžete vyřešit například nahrazením celého bloku tímto textem:

<div id="footer">
please contact us at email.support@github.com
</div>

Toto řešení obsahuje trochu z každé části a řádky <<<<<<<, ======= a >>>>>>> byly úplně odstraněny. Po vyřešení všech označených částí ve všech souborech s konfliktem, spusťte pro každý soubor příkaz git add, kterým ho označíte jako vyřešený. Připravením k zápisu se v Gitu soubor označí jako vyřešený.

Chcete-li k vyřešení problémů použít grafický nástroj, můžete použít příkaz git mergetool, který spustí příslušný vizuální nástroj pro slučování, a ten vás všemi konflikty provede:

$ git mergetool

This message is displayed because 'merge.tool' is not configured.
See 'git mergetool --tool-help' or 'git help config' for more details.
'git mergetool' will now attempt to use one of the following tools:
opendiff kdiff3 tkdiff xxdiff meld tortoisemerge gvimdiff diffuse diffmerge ecmerge p4merge araxis bc3 codecompare vimdiff emerge
Merging:
index.html

Normal merge conflict for 'index.html':
  {local}: modified file
  {remote}: modified file
Hit return to start merge resolution tool (opendiff):

Česky: Tato zpráva se zobrazila, protože hodnota 'merge.tool' (nástroj pro slučování) není nakonfigurována…​ a následuje návod ke spuštění nápovědy. Dále…​ 'git mergetool' se nyní pokusí spustit jeden z následujících nástrojů: …​ Následuje informace o souboru s konfliktem a končí to…​ Stiskněte klávesu Return (Enter) pro spuštění opendiff.

Chcete-li pro slučování použít jiný než výchozí nástroj (Git v tomto případě vybral opendiff, protože byl příkaz spuštěn v systému Mac), naleznete všechny podporované nástroje na začátku výstupu v části „one of the following tools:“ („jeden z možných nástrojů“). Jednoduše napište jméno nástroje, který byste použili raději.

Note

Pokud pro řešení zapeklitých konfliktů potřebujete pokročilejší nástroje, podívejte se na podkapitolu Advanced Merging, která se slučováním zabývá podrobněji.

Po zavření nástroje pro slučování se vás Git zeptá, zda sloučení proběhlo úspěšně. Pokud skriptu oznámíte, že ano, připraví soubor k zapsání a tím ho označí jako vyřešený. Ještě jednou můžete spustit příkaz git status, abyste si ověřili, že byly všechny konflikty vyřešeny:

$ git status
On branch master
All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

Changes to be committed:

    modified:   index.html

Pokud jste s výsledkem spokojeni a ujistili jste se, že všechny soubory s konfliktem jsou připraveny k zapsání, můžete zadat příkaz git commit a dokončit vytvoření bodu sloučení (merge commit). Zpráva k revizi má v takovém případě přednastavenu tuto podobu:

Merge branch 'iss53'

Conflicts:
    index.html
#
# It looks like you may be committing a merge.
# If this is not correct, please remove the file
#	.git/MERGE_HEAD
# and try again.


# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# All conflicts fixed but you are still merging.
#
# Changes to be committed:
#	modified:   index.html
#

Není-li zcela zřejmé co a proč jste udělali, můžete zprávu doplnit o detaily, jak jste konflikt vyřešili — pokud si myslíte, že by to v budoucnu mohlo při zkoumání obsahu této revize vzniklé sloučením nějak pomoci.