26. srpna 2014

Mercurial, strategie branch-by-feature


Mercurial je skvělý, distribuovaný Version Control System (VCS, či DVCS), který nabízí velkou míru volnosti, jak s nakládat s verzováním zdrojových kódů. Svobodu většinou chápeme jako pozitivní věc, někdy je ale přílišná nespoutanost na škodu. A tak definování nějaké verzovací strategie prospěje týmu i projektu.

Proč mít verzovací strategii?

Verzovací strategii branch-by-feature jsme s úspěchem použili na stávajícím projektu. Důvody, proč jsme si něco takového definovali byly dva:
  • Když jsem se mihnul na předcházejícím projektu (taky Mercurial), žádná strategie, či konvence definovaná nebyla . Člověk pak slýchal řeči jako: "Proč to furt merguješ?", "Vznikaj ti tam anonymní branche." a "Tam by's měl používat rebase.". Prostě klasický, tohle-tady-všichni-ví a takhle-to-děláme-ale-tobě-jsme-to-neřekli.
  • Chtěli jsme mít na nadcházejícím projektu formalizované code review.

Takže jsme se zamysleli, chvilku nad tím špekulovali a pak jsme, spolu s podporou dalších nástrojů, došli k následující strategii.

Strategie branch-by-feature

Princip téhle strategie je jednoduchý a dá se popsat jednou větou: Pro každou novou feature (nebo bug) se založí nový branch. Hotovo, vymalováno.

A teď trochu obšírněji. Na počátku je v repozitory pouze jeden permanentní branch. Zpravidla se mu říká development branch. Z něj se odlamují další branche pro vývoj - feature branche. Co přesně tyto branche představují, záleží na granularitě položek, do kterých jsme si práci rozdrobili. Může to být user story, feature, requirement, task. Ideálně je to něco, co evidujeme v issue tracking systému (ITS).

Probíhá běžný vývoj a všechny komity jdou do (odpovídajícího) feature branche. Když je vývoj hotový, proběhne code review a pokud jsou změny akceptovány, zamergují se do development branche a feature branch se zavře. Pokud jsou změny zamítnuty, provede se náprava, komitne se do feature branche, následuje code review atd.

Pokud jsou v této fázi nalezeny nějaké bugy, přistupujeme k nim stejně jako k feature. To jest, bug branch -> code review -> merge do development branche.

Verzovací strategie branch by feature

Po nějakém čase vznikne release branch. Pokud jsou v něm nalezeny chyby, vzniká bug branch a po code review přichází merge jak do release, tak do development branche.

Celý postup se dá shrnout do následujících bodů:
  1. Vytvoření nového feature/bug branche.
  2. Development.
  3. Změny projdou code review.
  4. Uzavření feature/bug branche.
  5. (Bugfixy jsou zamergovány do release branche.)
  6. Změny jsou zamergovány do development branche.

Z pohledu příkazové řádky vypadá proces takto:
$ hg branche fb-logging
$ hg commit -m 'Logging feat. has been developed.'
$ hg commit -m 'Close branch fb-logging.' --close-branch
$ hg update default
$ hg merge fb-logging
$ hg commit -m 'Merge branch fb-logging -> default.'

Zcela záměrně se vyhýbám popisu code review. To proto, abych nekradl materiál svému kolegovi Banterovi, který již jistě brzy napíše článek, o tom, jak to děláme (je to fakt cool, tak se těšte). Nicméně, ve vztahu k Mercurialu si nemůžu odpustit, jak to vypadá procesně. Přepokládám, že všichni čtete plynně BPMN ;-)  takže dalších slov netřeba. Mercurial aktivity jsou v oranžovém rámečku.

Code Review proces (oranžové jsou aktivity v Mercurialu, modré v RhodeCode)

Drobná a příjemná vylepšení

Což o to, proces, jako takový, je pěkný. Na někoho je ale možná trochu komplexní. Přece jenom, když mastíte celý život všechno jenom do trunku, může to být trochu moc věcí najednou. Tady je pár věcí, které nám to o něco usnadnili.

Konvence

Konvence jsme měli nastavené jednak pro názvy branchů a jednak pro komitovací komentáře. Název nového branche se skládal z prefixu a čísla issue v ITS. Prefix byl buď fb- (feature branch), nebo bb- (bug branch). Např. fb-42, bb-1024.

Konvence pro komentáře by se daly rozdělit do tří skupin: běžné komentáře, při zavírání branche a při vytváření releasu. Konvence samotná by se v Mercurialu dala pohlídat pomocí hooku, ale nedokopal jsem se k tomu, ho napsat. Pro "běžné", vývojové komentáře jsme měli konvenci WIP #nnn; some comment. WIP je zkratka pro Work In Progress, nnn je id issue v ITS. Např. WIP #1561; Payment button has been removed.

Zavření branche se skládá ze čtyř Mercurial příkazů (commit, update, merge, commit, viz výše) a obsahuje dva komentáře ve formátu:
  • Close branch <branch-name>.
  • Merge branch <branch-name> -> default.

Za zmínku stojí mergovací komentář, ze kterého by mělo být jasné, odkud kam merge proběhnul.

Komentáře při zavírání branche

Releasovací komentáře byly celkem čtyři a obsahovaly klíčové slovo [Release].

Komentáře při vytváření releasu

V předešlém popisu vás možná zarazilo, že jak pro zavření branche, tak pro vytvoření releasu je potřeba spustit čtyři Mercurial příkazy. Dělat to ručně by byla značná otrava a hlavně by celá konvence brzo erodovala do chaosu. Šťastni kdož používají automatizaci, neboť jen ti vejdou do křemíkového nebe.

Mercurial alias

S používáním aliasu přišel kolega Banter, takže mu za to patří dík a rád ho zde zvěčním. Alias samotný vypadá dost ošklivě, zato jeho použití je elegantní. Jedinou vadou na kráse je, že jsme nepřišli, jak alias spustit z grafických nástojů, jako je SourceTree, nebo TortoiseHg, takže ještě budeme muset zapracovat na tom, aby tuto fičurku mohli používat i kolegové, kteří ještě nepřišli na to, jak cool je používat command line ;-)

Alias stačí přidat do souboru ~/.hgrc a spouští se jednoduchým příkazem hg close-feature nnn, kde nnn je (v našem případě) čístlo issue v ITS, tj. to, co je za prefixem fb-.
[alias]
close-feature = ![ -z "$1" ] && echo "You didn't specify a branch!" && exit 1; \
                if hg branches | grep -q "fb-$1"; \
                    then $HG up fb-$1; \
                         $HG commit -m 'Close branch fb-$1.' --close-branch; \
                         $HG pull; \
                         $HG up default; \
                         $HG merge fb-$1; \
                         $HG commit -m 'Merge branch fb-$1 -> default.'; \
                    else echo "The branch fb-$1 does NOT exist!"; \
                fi

Gradle release task

Náš projekt buildujema Mavenem. Chvilku jsem se snažil napasovat Maven Release plugin na naši branchovací strategii, ale pořád to nějak nebylo ono - je to prostě moc šitý na SVN. Pak jsem ztratil trpělivost a odpovídající chování si napsal jako Gradle task. Časem se z toho snad vyklube Gradle plugin.

Task samotný tady uvádět nebudu, protože je asi tak na tři stránky. A taky potřebuje ještě trochu poladit. Zkrátka ještě není zralý na to, abych ho dal z ruky. V podstatě ale dělá pouze to, že přepíná branche, šolichá s Mercurialem a manipuluje s verzí v pom.xml. A dělá to ty hezké komentáře, co jsem uváděl výše.

Pros & Cons

Celý výše popsaný koncept jsme vymysleli ještě před projektem. Pak přišel projekt a emotivně musím říct, že nám to krásně fungovalo. Jako hlavní benefit vidím, že jsme si formalizovali proces code review a s výše uvedenou branchovací strategií to bylo velice efektivní. Dalším plusem byla, "úhledná" práce s Mercurialem, kdy byla radost se prohrabávat verzovanou historií.

K negativům bych zařadil komplexnost. Pokud by nám nešlo o code review, asi by tato strategie byla zbytečná. Zatím mi taky chybí nějaká podpora v grafických a automatizačních nástrojích. Jako milovníkovi CLI mi to osobně nijak nevadí, ale musím myslet taky na ostatní členy týmu (bývalý kolega jim říkal "makáči" :-)  pro které by mělo být používání nástrojů co nejsnadnější.

Je to všechno?

Abyste dostali plný obrázek, jak to celé fungovalo, chybí nám jeden velký dílek skládanky - jo, code review. Takže pokud vám něco nedává smysl, tak je to možná proto, že jsem v tomto článku celkem důsledně odpáral použití dalších dvou nástrojů, které byly s branchovací strategii organicky provázané - issue tracking system a RhodeCode, nástroj, ve kterém jsme, pomocí pull requestů, řešili code review.

Popis code review je na samostatný článek. Tak snad ho Banter brzo napíše a já sem pak lísknu odkaz. A jinak doufám, že vám zmiňovaná strategie branch-by-feature přijde inspirativní.

Happy branching! :-)

Mind Map



Související články

13 komentářů:

  1. Už toho na mě moc nezbylo :) O tom, co je to code review a proč ho dělat už jsem psal. Ještě zbývá vysvětlit, jak se používá RhodeCode a proč jsme si ho vůbec vybrali. Rovněž bude stát za to popsat, co přináší code review do programátorova běžného pracovního dne.

    OdpovědětVymazat
  2. Nechapu proc kazdy si timhle musi projit - vymyslet jakou strategii pouzivat - kdyz uz je dlouho k dispozici github flow, ktery je pro vetsinou projektu naprosto dostacujici.

    OdpovědětVymazat
    Odpovědi
    1. Trochu mi to zní jako one-size-fits-all. Relevantních důvodů, proč vymýšlet, nebo adaptovat verzovací strategii může být mnoho. Článek popisuje konkrétní implementaci, která vycházela z daných nástrojů a prostředí. V jiném kontextu by to mohlo dopadnout jinak.

      Vymazat
  3. (Mam zkusenosti jen s gitem, ale predpokladam, ze nize uvedeny problem je stejny i v mercurialu.)

    Moc by se mi libilo, kdyby se slo zeptat "git branch --contains bb-1234" a dostal bych vzdy spravnou odpoved. Vidim tam ale jeden problem.

    Mozna mi neco unika, ale stale hledam odpoved na otazku "z ktere branch vytvorit bug/feature branch". Ma prozatimni odpoved je "z nejstarsi release branch, ve ktere bude bug opraven". To proto, aby hezky fungoval merge.

    Problem ale nastava v okamziku, kdy:
    1) Bug je fixly ve verzi 2.0 (tzn bb-1234 vznikla z 2.0 a byla do ni zpet mergnuta)
    2) Az pote se rozhodne, ze je potreba fix dat i do 1.0 (zakaznik, nas pan)

    Co ted? Pokud bych se pokusil o merge bug branche do 1.0, tak mi s sebou vezme i vse z 2.0, coz nechci. Takze zbyva cherry-pick, ale pak uz nikdy nebude fungovat "git branch --contains" tak, jak bych chtel.

    Jak tento "problem" resite vy popripade mercurial?

    OdpovědětVymazat
  4. Ahoj. Řešili jste nějak a pokud ano tak jak změny v branchy?

    Vytvořím branch z defaultu. Vyvíjím, a mezitím se mi default posune o další commity dopředu. Pokud nepushnu, tak mohu rebasovat. Pokud pushnu, tak nemohu, a musím mergnout.

    Pokud mi někdo bude dělat review, tak buď mi ho bude dělat v mojí kopii u mého fyzického počítače, nebo bude dělat review na pushnuté branchi. V takovém případě ale pokud bude mět nějaké připomínky, a já je fixnu, tak je musím přidat jako nové comity na konec. Protože rebase nejde.

    Jak jste to prosím řešili vy?

    OdpovědětVymazat
    Odpovědi
    1. Nevím, jestli té otázce rozumím. Je podstatné, jaký smysl má default branch, nebo branch, do kterého merguji. Myslím, že dělat code review vůči branchi, který se mezi tím změnil (přimergoval do něj někdo jiný) je v pořádku.

      Pokud bych opravdu chtěl dělat code review oproti původnímu stavu branche, tak většina code review nástrojů založených na pull/merge requestech umožňují dělat diff mezi branchema a tagama a někdy i jen changesety. Takže bych si udělal na potřebném místě dočasný tag, vytvořil pull request a tag pak smazal.

      Pokud není code review/pull request akceptovaný, tak přidání následujících commitů je přirozené. Pokud bych chtěl reviewovat jenom nově přidané změny, můžu odlomit z feature branche nový branch a pak porovnat jen tyto dva.

      A jinak obecně, vím že jsou lidé, kteří inklinují k rebasování. Bohužel, nadstavbové nástroje (GitHub, GitLab, RhodeCode, Bitbucket atd.) většinou pracují s mergovací strategií a rebase nepodporují buď vůbec, nebo jen částečně. A protože rebase mě osobně nijak zvlášť neučaroval, zas tolik jsem tuto oblast nezkoumal.

      Vymazat
    2. Poslední rok jsem si zvykl na to, že si svoji feature branch neustále rebasuju na develop, abych měl aktuální stav repository. Nakonec ještě udělám squash, abych měl ve feature branch jediný commit. BitBucket dokonce u pull-requestu varuje, že nejde zamergovat, že je tam konflikt.

      Vymazat
    3. Tak já mám hlavně problém v tom, že v mercurialu rebasovat pushnutou branch nejde (nebo jsem nepřišel jak na to, všechny moje pokusy na něčem selhaly).

      Můj ideál, ke kterému bych chtěl mercurial přinutit je takovej, že:

      default branch bude jakože hlavní větev, kam se mergnou schválený, reviewnutý feature branche.

      Na každou featuru si vytvořím branch. Když proběhne nějaká změna na defaultu, tak si ji rebasnu.

      Když mi kolega udělá review, a bude tam chyba třeba s překlepem, nebo tak něco, tak udělám změnu přímo do tého comitu, a opět rebasnu. Slovníkem gitu, budu přepisovat historii. Případně squashnu, jak píše @banter.

      Problém je v tom, že mi to Mercurial nechce dovolit.

      Zatím jsem schopen tohoto dosáhnout velice složitě tak, že:
      - na lokále si starou branch zavřu se speciálním příznakem a pushnu
      - server si všimne dotyčného příznaku a branch smaže
      - nový rebasnutý kód bude v dočasné branchi
      - dám pull
      - přejmenuju na starou branch a pushnu

      Což je strašná opičárna a spousta okamžiků, když se to může rozbít.

      Proč vůbec chci rebasovat?

      Mám tu tým lidí, kteří mercurial používají tak nějak. Třeba teď jeden kluk napíše SQL patch který vůbe neprojde, a tak to v některém následujícím commitu zase upraví. Nebo opraví jeden překlep, commitne, druhý překlep, commitne. Tak nechci ho tu hanit, uděláme review, a on to vylepší. Ale tím, jak se to nedá opravit, tak to proteče až do produkčního kódu.

      Vymazat
    4. S hg už nějakou dobu nepracuju, ale vypadá to, že opravdu force push nepodporuje https://stackoverflow.com/a/38796353/204950

      Vymazat
    5. Force push jde, ale výsledkem je, že na serveru se vytvoří dvě hlavy, což ve výsledku znamená, že co na lokále smažu (a je pushnuté), tak při nejbližším pull zase přiteče.

      Vymazat
    6. @banter to, že si rebasuješ a squashuješ je jen způsob tvé lokální práce. Stejně pak vytvoříš pull request, resp. spíše merge request, který se v repository merguje a ne rebasuje, ne?

      Zobrazit a vyřešit merge conflict umí i GitLab (jestli i GitHub to netuším).

      Vymazat
    7. @taco a není problém jen v "mindsetu"? rebase vs. merge. Pokud je code review založené na merge requestech, tak je rebase stejně jen zpříjemnění lokální práce vývojáře (pokud mu vyhovuje).

      Vymazat
    8. @vít-kotačka Mindset, jak říkáš máme založené na tom, že se vyvíjí na feature branchi, a merguje do produkční branche. Feature branch, aby se na ní dal udělat code review musí být pushnutá, tudíž to není jen lokální práce vývojáře.

      Plus si uvědom, uvažujem tak, že vytvořím branch, kolega udělá review, já zohledním jeho připomínky a upravím tu branch, ale rebasnutím - protože to, jak jsem to napsal předtím nechci aby zůstalo zachováno.

      Tedy pointa je v tom, že nechci, aby v historii repozitáře bylo zachováno, že jsem se v pondělí špatně vyspal, a splodil co jsem splodil.

      Cíl je, že ve vzdáleném repozitáři se nemerguje ani nerebasuje. Původní větev se zahodí, a nahradí se novou, správnou.

      Vymazat