V předchozí kapitole jsme používali záložky ke sledování souběžných (tematických) větví. Nyní si ukážeme postup práce, založený na pojmenovaných větvích.
Pojmenované větve se liší od záložek v několika ohledech:
Neměnitelnost: Pojmenované větve jsou neměnitelné: je-li changeset komitován ve větvi foo, nelze jej později “přestěhovat” do jiné větve. Nelze je také přejmenovat nebo smazat.
Prověřitelnost: Protože pojmenované větve nelze smazat, je možné prozkoumat historii a zjistit, ve které větvi byl který changeset komitován.
Kvůli těmto rozdílům by mělo být v projektu uvedeno, jakým způsobem jsou pojmenované větve v projektu používány. Zatímco záložky lze vytvářet a mazat bez jakýchkoli důsledků, pojmenované větve přetrvávají a sdílejí globální jmenný prostor. To znamená, že se větve stávají integrální částí celkového průběhu prací na projektu a členové skupiny by se měli shodnout na jejich použití.
V následujících příkladech bude skupina používat podnikový bug tracker jako zdroj dlouhodobých odkazů. Zatímco toto je běžná praxe, některé projekty používají pojmenované větve jenom pro sledování dlouhodobých edičních větví a pro sledování chyb a témat v menších větvích používají klony nebo záložky. Tím se vyhnou nadbytečné “byrokracii”, spojené s používáním nemotornějších pojmenovaných větví.
Obsah
Představme si malý tým s několika mladými vývojáři. Alenka a Bob budou pracovat na jednotlivých úlohách a zkušenější vývojář – Karla, bude jejich práci posuzovat a integrovat jejich změny.
Pouze zkušenější Karla bude mít oprávnění zapisovat do hlavního repozitáře. Karla si stahuje změny od Alenky a Boba a po přezkoumání je posílá do hlavního repozitáře. Alena s Bobem si změny stahují z hlavního repozitáře:
Tímto způsobem může Karla kontrolovat vstupy do hlavního repozitáře, z něhož se vytvářejí konečné sestavy (builds).
V reálné situaci budou repozitáře s největší pravděpodobností umístěny na různých počítačích. V našich příkladech však umisťujeme repozitáře jeden vedle druhého.
$ ls alice bob carla main
Činíme tak kvůli zjednodušení příkladů.
Alenka začne tím, že si stáhne změny z hlavního repozitáře:
alice$ hg pull ../main pulling from ../main requesting all changes adding changesets adding manifests adding file changes added 1 changesets with 1 changes to 1 files (run 'hg update' to get a working copy) alice$ hg update 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
Alenka má nyní aktuální klon hlavního repozitáře. Projekt se skládá z jediného souboru hello.txt:
alice$ cat hello.txt Goodbye, World!
V textu je zřejmá chyba a Karla ji už zanesla do projektového bestiáře (bug tracker) jako Issue 1. Požádala Alenu, aby chybu napravila změnou textu na Hello, World!.
Alenka si pro svou práci vytváří novou větev:
alice$ hg branch issue-1 marked working directory as branch issue-1 (branches are permanent and global, did you want a bookmark?) alice$ hg commit -m "Starting branch for Issue 1."
Příkaz hg branch sám o sobě nemění historii nebo pracovní kopii, pouze zajišťuje, že následný komit je “orazítkován” poznámkou issue-1.
Předchozí sdělení chtělo varovat, že název větve nelze později smazat (uvidíme ale, že lze nepotřebnou větev skrýt). Jak bylo uvedeno v úvodu, pojmenované větve jsou persistentní a jejich používání se doporučuje jen v případech, kdy chceme trvale sledovat, kde byl který changeset vytvořen. V našem projektu používá skupina podnikový bug tracker a větve pojmenovává podle tematu, na kterém pracují — tím způsobem je snadné vyhledat později všechny komity, které se k dané chybě vztahují.
Prázdný komit normálně provést nelze, ale změna názvu větve se jako změna počítá, takže Alenka mohla komit provést. Repozitář má jenom dva changesety:
Všimněte si názvu větví ve sloupci /”Popis/” ve Verpánku. Tyto názvy se zobrazují jenom u těch changesetů, které tvoří čela větví. Changeset ve větvi default je zobrazen se světle modrým kolečkem a changeset ve větvi issue-1 je zobrazden se zeleným kolečkem.
Počne pinožit (hacking):
alice$ echo "Hey, World!" > hello.txt alice$ hg diff diff -r eb6db6528ec2 hello.txt --- a/hello.txt Mon Mar 15 10:25:00 2010 +0000 +++ b/hello.txt Mon Mar 15 10:28:00 2010 +0000 @@ -1,1 +1,1 @@ -Goodbye, World! +Hey, World! alice$ hg commit -m "Fixed Issue 1."
Svět nyní vypadá takto:
alice$ hg glog @ changeset: 2:2cdcbb0a5bfd | branch: issue-1 | tag: tip | user: Alice <alice@example.net> | date: Mon Mar 15 10:30:00 2010 +0000 | summary: Fixed Issue 1. | o changeset: 1:eb6db6528ec2 | branch: issue-1 | user: Alice <alice@example.net> | date: Mon Mar 15 10:25:00 2010 +0000 | summary: Starting branch for Issue 1. | o changeset: 0:e9eb044d45e0 user: Carla <carla@example.net> date: Mon Mar 01 10:20:30 2010 +0000 summary: Initial import.
Dva nejnovější changesety jsou ve větvi issue-1, kořenový changeset je ve větvi default, kterýžto název není z úsporných důvodů zobrazován. Všechny changesety v Mercurialu jsou v nějaké větvi, jejich název se však nezobrazí, pokud jím je default. Na čela větví se lze odkazovat jejich názvy: místo 97205992f938 lze psát issue-1 a místo e9eb044d45e0 můžeme napsat default. V tomto smyslu působí větve jako “plovoucí tagy”, které vždy ukazují k nejnovějšímu changesetu větve. Tyto čelní changesety jsou v thg-log zvýrazněny světlezeleným kolečkem:
Alenka poznamená do bestiáře, že chybu opravila a že si Karla může změny stáhnout.
Zatímco Alena opravovala chybu, byla Karla také pilná a přidala soubor README:
carla$ hg glog @ changeset: 1:550bad1893cf | tag: tip | user: Carla <carla@example.net> | date: Tue Mar 02 14:30:00 2010 +0000 | summary: Added README. | o changeset: 0:e9eb044d45e0 user: Carla <carla@example.net> date: Mon Mar 01 10:20:30 2010 +0000 summary: Initial import.
Stáhne si změny od Aleny:
carla$ hg pull ../alice pulling from ../alice searching for changes adding changesets adding manifests adding file changes added 2 changesets with 1 changes to 1 files (+1 heads) (run 'hg heads' to see heads)
Mercurial jí říká, že má nové čelo v repozitáři. To lze zobrazit příkazem hg heads:
carla$ hg heads changeset: 3:2cdcbb0a5bfd branch: issue-1 tag: tip user: Alice <alice@example.net> date: Mon Mar 15 10:30:00 2010 +0000 summary: Fixed Issue 1. changeset: 1:550bad1893cf user: Carla <carla@example.net> date: Tue Mar 02 14:30:00 2010 +0000 summary: Added README.
V repozitáři jsou nyní dvě větve:
carla$ hg branches issue-1 3:2cdcbb0a5bfd default 1:550bad1893cf
Připojení nové větve k větvi default si Karla prohlédne v thg log:
Obě větve jsou přehledně zobrazeny a Karla provede aktualizaci k větvi issue-1:
carla$ hg update issue-1 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
Její pracovní adresář nyní vypadá jako Alenin a Karla může soubor zkontrolovat. Není zcela spokojená s “moderním” pozdravem, který Alenka zvolila, dala by přednost starému, dobrému, klasickému pozdravu “Hello”. Probere to s Alenou v bestiáři a přesvědčí ji, aby pozdrav změnila. Alenka si nejprv zkontroluje, že je stále ve větvi issue-1 a sporný tvar opraví:
alice$ hg branch issue-1 alice$ echo "Hello, World!" > hello.txt alice$ hg commit -m "Really fix Issue 1."
Karla si novou změmu stáhne:
carla$ hg pull ../alice pulling from ../alice searching for changes adding changesets adding manifests adding file changes added 1 changesets with 1 changes to 1 files (run 'hg update' to get a working copy)
Pro přechod k vrcholu (tip) své aktuální větve (issue-1) může použít buď hg update nebo explicitně hg update issue-1:
carla$ hg update 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
S úpravou chyby je spokojená.
Větve již déle není zapotřebí, proto ji Karla označí jako uzavřenou:
carla$ hg commit --close-branch -m "Close branch, Issue 1 is fixed."
Tím se stane, že větev issue-1 zmizí ze seznamu hg branches:
carla$ hg branches default 1:550bad1893cf
Kdyby Karla větev neuzavřela, stal by se seznam hg-branches zbytečně objemný. Dále provede Karla aktualizaci k větvi default a sloučení s issue-1:
carla$ hg update default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved carla$ hg merge issue-1 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) carla$ hg commit -m "Merged fix for Issue 1."
Tím, že nejprve provede aktualizaci k default Karla zajistí, že se sloučený changeset vytvoří v default nikoliv v issue-1, kam nepatří. Povšimněte si, že se sloučení větví neliší od sloučení jakýchkoli dvou changesetů.
Nyní to všechno pošle (push) do hlavního repozitáře. Musí použít opci --new-branch, protože posílá novou pojmenovanou větev. Bez praporku se jí dostane přátelského upozornění:
carla$ hg push ../main pushing to ../main searching for changes abort: push creates new remote branches: issue-1! (use 'hg push --new-branch' to create new remote branches)
Poznámka
Opce (také praporek neboli flag) --new-branch byla zavedena ve verzi Mercurial 1.6. Starší verze požadovaly použití opce --force. Použití --force bylo nebezpečné z toho důvodu, že vyřazovalo veškerou kontrolu, zatímco --new-branch vám nedovolí věci, jako je posílání dvojích čel (heads).
Zajisté, připojení praporku učiní “push” průchodným:
carla$ hg push --new-branch ../main pushing to ../main searching for changes adding changesets adding manifests adding file changes added 6 changesets with 3 changes to 2 files
Hlavní repozitář je nyní v tomto stavu:
Zatímco Alena pracovala na ošetření Issue 1, Karla požádala Boba aby zajistil překlad souboru hello.txt do dánštiny, němčiny a francouzštiny. Pro toto téma vytvořili Issue 2. Stejně jako Alenka, bude Bob pracovat ve své vlastní větvi a začne stažením změn z hlavního repozitáře:
bob$ hg pull ../main pulling from ../main adding changesets adding manifests adding file changes added 2 changesets with 2 changes to 2 files (run 'hg update' to get a working copy) bob$ hg update 2 files updated, 0 files merged, 0 files removed, 0 files unresolved bob$ hg glog @ changeset: 1:550bad1893cf | tag: tip | user: Carla <carla@example.net> | date: Tue Mar 02 14:30:00 2010 +0000 | summary: Added README. | o changeset: 0:e9eb044d45e0 user: Carla <carla@example.net> date: Mon Mar 01 10:20:30 2010 +0000 summary: Initial import.
V této chvíli nebyla ještě Alenčina větev připojena do hlavního repozitáře, takže Bob vidí jenom první dva changesety, vytvořené Karlou. Bob vytvoří větev a přejmenuje hello.txt podle příslušného jazyka:
bob$ hg branch issue-2 marked working directory as branch issue-2 (branches are permanent and global, did you want a bookmark?) bob$ hg commit -m "Began Issue 2 branch." bob$ hg rename hello.txt hello.en.txt bob$ hg commit -m "English translation."
Rychle vytvoří dánskou a německou verzi a rovněž je komituje:
bob$ echo "Hej, verden!" > hello.da.txt bob$ echo "Hallo, Welt!" > hello.de.txt bob$ hg add adding hello.da.txt adding hello.de.txt bob$ hg commit -m "Danish and German translations."
Na překlad do francouzštiny potřebuje více času, proto tuto změnu odloží. Grafický průběh změn vidíme dole a všimněte si, že jeho větev issue-2 je označená červeně:
Mezitím včlenila Karla Aleninu nápravu (bugfix) do větve default:
Tato náprava je také potřebná pro Bobovu větev, proto si ji stáhne z hlavního repozitáře:
bob$ hg pull ../main pulling from ../main searching for changes adding changesets adding manifests adding file changes added 5 changesets with 2 changes to 1 files (+1 heads) (run 'hg heads' to see heads)
Nyní včlení default do issue-2 aby přenesl nápravu do své větve:
bob$ hg merge default merging hello.en.txt and hello.txt to hello.en.txt 0 files updated, 1 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) bob$ hg commit -m "Merge in bugfix from default."
Všimněte si jak hladce se Alenina změna hello.txt včlenila do přejmenovaného souboru hello.en.txt. Stejně jako dobré ozvučení filmu se dobré sloučení vyznačuje tím, že si ho nevšimnete. Porovnáme to s řešením v Subversion – viz dále. Výsledný přehled changestů vypadá takto:
S použitím barevných větví hned vidíme, že se zelená větev issue-1 přičlenila k modroé větvi default a to celé se sloučilo s červenou větví issue-2. Barvy nám velmi pomáhají při prohlížení složitých historií.
Bob konečně přišel na to, jak přeložit text do francouzštiny, takže provádí finální komit před tím než požádá Karlu o kontrolu větve.
bob$ echo "Bonjour tout le monde !" > hello.fr.txt bob$ hg add hello.fr.txt bob$ hg commit -m "French translation."
Spokojen se svým dílem, postupuje je Karle, která si změnu stáhne do svého repozitáře:
carla$ hg pull ../bob pulling from ../bob searching for changes adding changesets adding manifests adding file changes added 5 changesets with 5 changes to 4 files (run 'hg update' to get a working copy)
Její repozitář nyní vypadá takto:
Po náležité kontrole Bobových souborů se Karla rozhodne, že jeho práci začlení. Aktualizuje se k jeho větvi aby ji uzavřela a vrátí se k default aby provedla vlastní sloučení:
carla$ hg update issue-2 4 files updated, 0 files merged, 1 files removed, 0 files unresolved carla$ hg commit --close-branch -m "Close branch, Issue 2 is fixed." carla$ hg update default 1 files updated, 0 files merged, 4 files removed, 0 files unresolved carla$ hg merge issue-2 4 files updated, 0 files merged, 1 files removed, 0 files unresolved (branch merge, don't forget to commit) carla$ hg commit -m "Merged fix for Issue 2."
Závěrečné zobrazení changesetů krásně ukazuje, jak byla větev default (modrá) sloučena s větví issue-2 (červená) předtím, než byla větev issue-2` opět sloučena s větví default:
Toto je velmi typická konfigurace grafu pro dlouhodobější větve: větev default se opakovaně slučuje s experimentálnější větví aby přebrala nejnovější nápravy a změny. Udržování větví v synchronizovaném stavu napomáhá snadnějšímu slučování. Při každém sloučení nalezne Mercurial nejbližšího společného předka a aplikuje mezilehlé změny. Při pravidelném slučování není tento předek příliš zasunut v minulosti a větve se od sebe příliš neliší.
Při slučování dvou větví je důležité najít jejich společného předka. Jsou to změny provedené po vytvoření tohoto předka, které mají být sloučeny do cílové větve. Mercurial provádí sledování předků automaticky; je to integrální součást modelu historie, který si kdykoliv můžeme prohlédnout ve Verpánku ( thg log).
Historie v Subversion není založena na grafu jako v Mercurialu a po dlouhou dobu neměl Subversion žádný nástroj pro sledování sloučení. To znamenalo, že jste musel ručně vypátrat rozsahy revizí, které jste chtěl sloučit zadáním svn merge. Subversion 1.5 a pozdější již umí sledovat sloučení ale tato podpora je neúplná. V publikaci Version Control with Subversion (the “SVN Book”) je sledování sloučení označeno jako mimořádně složité. Zejména nedostatečně je ošetřeno slučování přejmenovaných souborů.
Nahoře jsme sloučili větev issue-2 s větví default. Ve větvi default byl změněn soubor“hello.txt“ a ve větvi issue-2 byl hello.txt přejmenován na hello.en.txt. Mercurial provedl správnou věc a při sloučení aplikoval změnu na hello.en.txt. Subversion se v takové situaci vykupuje zprávou o konfliktu:
$ svn merge --reintegrate file://$HOME/hello/branches/issue-2 --- Merging differences between repository URLs into '.': A hello.en.txt C hello.txt Summary of conflicts: Tree conflicts: 1
Pamatujte si, že v Subversion je nutné použít speciální praporek (flag), když se větev slučuje zpátky do hlavního kmenu (trunk). Žádný takový flag v Mercurialu nepotřebujeme, protože na větvi default není nic vnitřně speciálního (komě toho, že to je větev implicitní - default).
Někdy se stane se, některé changesety potřebujeme ve více než jedné větvi. Nahoře mohl Bob jednoduše stáhnout všechny změny z hlavního repozitáře - ale to často není žádoucí, protože se smísí požadovaná změna s jinými změnami.
Typickým příkladem je opravenka (bugfix) komitovaná do větve default, i když by měla být vlastně komitovaná do větve s posledním stabilním vydáním (release). Vezměme si repozitář s tímto grafem:
Zde jsou použity dvě větve: default (modrá) pro probíhající vývoj a stable (zelená) pro opravenky (bugfixes) a vydání (releases). Vidíme jak je stable sloučena s default aby se opravenka přenesla do aktuálního vývoje. Když je připraveno vydání (release), provede se sloučenív jiném směru — od default ke stable a je přidán tag. Výsledkem jsou dvě paralelní stopy vývoje, kde zelená větev stable vždy obsahuje podmnožinu změn v modré větvi default.
Závěrečný komit napravuje starou chybu a o chvilku později si Karla uvědomuje, že tato chyba je také přítomná ve starším vydání 2.0. Changeset by tedy vlastně měl být proveden ve větvi stable. Jednoduše připojit default k stable už nejde, protože by se sloučila také experimentální změna.
Řešením je použití příkazu hg graft pro kopírování changesetu. Setrvávaje ve větvi stable, Alenka provede:
carla$ hg graft tip grafting revision 12
Poznámka
Příkaz graft byl zaveden v Mercurialu 2.0. V předchozích verzích šlo ke kopírování changesetu použít extenzi transplant. Je to standardní extenze a povoluje se vložením:
[extensions] transplant =
do konfiguračního souboru. Extenze transplant pracuje tak, že exportuje changeset jako oprávku (patch) a potom aplikuje tuto oprávku na cíl (target). Pokud tato akce selže, musíte řešit konflikt sám vycházeje ze souboru (.rej).
To překopíruje changeset do její větve:
Protože duplikát má odlišnou minulost od originálu, dostane nový hash changesetu. Kopírování se uskuteční provedením interního trojného sloučení, které Karla provede svým oblíbeným slučovacím programem. Většinu malých opravenek (bugfixes) lze ovšem roubovat čistě. Karla nyní může sloučit větev stable zpět do default:
carla$ hg update default 1 files updated, 0 files merged, 0 files removed, 0 files unresolved carla$ hg merge stable 0 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) carla$ hg commit -m "Merge with stable."
Všimněte si, že se changesety při této technice ve skutečnosti nepřesunují. V necentralizovaném prostředí je téměř nemožné zničit historii. Implicitně nám Mercurial povoluje k historii pouze přidávat. To znamená, že náprava chyb zahrnuje vytváření nových changesetů, které chyby napravují. V našem připadě jsme chybu napravili zdvojením changesetu. Při sloučení se tyto duplikáty automaticky srovnají.
Nyní se rozdělíme do skupin po pěti. Každá skupina si zvolí jednoho, který bude mít roli Karly, to jest - bude stahovat práci ostatních a rozhodovat, co sloučit. Tato role bude označena písmenem K.
Proveďte tyto kroky:
K začíná klonováním příkladového repozitáře z:
https://bitbucket.org/aragost/cantons/
Když je klon proveden, spustí K vestavěný webový server v Mercurialu pomocí hg serve.
Ostatní členové skupiny si vytvoří klony od K. V této chvíli si budete potřebovat vyměnit IP adresy. Můžete také povolit extenzi zeroconf a po chvilce zadat hg paths. Měl byste být schopni vidět do repozitářů ostatních.
Ve Windows je nutné si uvědomit, že příkaz hg serve zpřístupní repozitář implicitně na portu 8000, takže se Mercurial pokusí pojmenovat klon nějak jako 172.16.0.8000, což je pro Windows neplatný název. Měli byste tedy zadat další argument příkazu hg clone ~, aby dostal klon vhodné jméno.
Ve vytvořeném klonu naleznete soubor cantons.txt. Obsahuje seznam zkratek švýcarských kantonů. Tyto zkratky se mají doplnit - například zkratka:
* FR
by měla být rozšířena na:
* FR: Fribourg
K přidělí každému členu skupiny rozsah písmen.
Pro svůj rozsah si vytvořte větev, např. hg branch range-a-g. Potom postupně doplňujte zkratky. Doplnění pro každé písmeno komitujte zvlášť.
Zatímco se lidé baví doplňováním zkratek, doplní K další kapitolu do souboru; asi takto:
Podle jazyků ============ Oficiálními jazyky Švýcarska jsou francouzština, němčina, italština a románština: Francouzština ------------- Francouzsky se mluví v: VD, ... Němčina ------- Německy se mluví v: FR, ... Italština --------- Italsky se mluví v: GR, ... Románština ---------- Románsky se mluví v: GR.
Doplňte chybějící kantony pro francouzštinu, němčinu, italštinu/n a komituje změnu. Počkekte, až ostatní dokončí doplňování svých zkratek.
Když někdo svojí práci dokončí, měl by použít hg serve a dát K vědět, že je hotov.
K si nyní postupně stáhne práci od všech členů skupiny a sloučí jejich změny do default. Pokud všichni dávali pozor a editovali pouze své rozsahy, neměly by vzniknout žádné konflikty.
Když si s doplňováním nebudete vědět rady, vzpomeňte si, že tu je Wikipedia aby vám pomohla :-)
Dále budeme experimentovat s řešením konfliktů:
Nechť dva členové skupiny editují tentýž řádek - jeden může přidat řádek s názvem francouzského kantonu, druhý s názvem italského kantonu.
Konflikt se zjistí, když K stáhne změny a zadá hg merge./n V závislosti na aktuální konfiguraci Mercurialu se spustí slučovací procedura. Implicitním nástrojem v Linuxu a ve Windows je KDiff3. Po otevření vypadá asi takto:
Spodní panel obsahuje výsledky slučování; na obrázku vidíme deklaraci tří konfliktů. Pravým poklepem na <Merge Conflict> vyberete konflikt a řešíte jej přidáním řádků ze složky B (vaše lokální verze) nebo C (jiná verze). My budeme chtít přidat obě změny:
Uložte výsledek a zavřete KDiff3. Mercurial zaznamená, že byl slučovací nástroj ukončen úspěšně a označí soubory jako vyřešené. Ověříme si to příkazem hg resolve --list.
Není-li Mercurial nakofigurován aby spustil slučovací proceduru, potom musíte editovat soubor sami, aby se odstranily značky, vložené Mercurialem. Po provedení úprav zadejte hg resolve --mark cantons.txt, aby se soubor označil jako vyřešený.
Výsledek slučování ukončíte komitem.
Ukázali jsme si jak lze použít pojmenovaných větví ke členění repozitáře Mercurialu. Větvení umožňuje pracovat samostatně ve více větvích současně.