Zobrazují se příspěvky se štítkemmercurial. Zobrazit všechny příspěvky
Zobrazují se příspěvky se štítkemmercurial. Zobrazit všechny příspěvky

13. listopadu 2017

vimdiff, nástroj drsňáků


Musím se vám k něčemu přiznat... Už patnáct let je Vim můj nejoblíbenější textový editor. A občas, čas od času, i hlavní nástroj na programování.

Umím si poeditovat vimrc, který po léta udržuju a vylepšuju. Dokonce jsem se i naučil trochu Vim script/VimL a napsal dva zanedbatelné a nedotažené pluginy (pro Gradle a WSDL).

Ale vždycky jsem se jako čert kříži vyhýbal jedné věci - používání vimdiff. Nicméně na každého jednou dojde. Z určitých (pro článek nepodstatných) důvodů jsem si nemohl pro nové vývojové prostředí nastavit P4Merge a tak jsem vstoupil do zapovězené komnaty.

Disclaimer: Tenhle článek píšu jako shrnutí toho, jak jsem práci s vimdiff pochopil. Pokud máte víc zkušeností, budu rád, když se podělíte v komentářích.

2-way merge

Nejjednodušší způsob, jak používat vimdiff - pokud pomineme, že umí dělat i "plain old" diff - je 2-way merge: máme vedle sebe dvě okna se zvýrazněnými rozdíly a chceme mezi nimi tyto změny propagovat.

vimdiff, 2-way merge

Stav na předešlém obrázku, který je výchozí pro merge, se dá dosáhnout několika způsoby:
  • Příkazem: vimdiff myFile.txt theirFile.txt
  • Příkazem: vim -d myFile.txt theirFile.txt
  • Kombinací příkazů:
    • vim myFile.txt
    • :diffsplit theirFile.txt
  • Kombinací příkazů
    • vim -O myFile.txt theirFile.txt (vsplit obou souborů)
    • :diffthis (zapne diff na aktuálním bufferu)
    • Ctrl-W Ctrl-W (skok do druhého bufferu)
    • :diffthis(zapne diff v druhém bufferu)

Základní příkazy

Tak, diff máme zobrazený, co s ním? První věc - je potřeba se v diffu umět pohybovat. Kromě toho, že můžete použít jakýkoli skok, který znáte z běžného Vimu, jsou tu dva příkazy, které umožňují skákat po jednotlivých rozdílech:
  • ]c skočí na následující diff
  • [c skočí na předcházející diff

Za druhé - chceme propagovat změny z/do aktuálního bufferu: skočíme na diff, který chceme upravit a:
  • do, nebo :diffget natáhne změny z "druhého" bufferu do toho aktuálního.
  • dp, nebo :diffput propaguje změny z aktuálního bufferu do "toho druhého".

Za třetí - změny uložíme. Kromě příkazů na standardní ukládání (:w, ZZ atd.) se může hodit:
  • :only zavře všechny ostatní buffery kromě toho aktuálního
  • :qall zavře všechny otevřené buffery
  • :only | wq zavře ostatní buffery + uloží stávající + ukončí Vim. Cool!

Eventuálně začtvrté - pokud věci nejdou hladce, může se šiknout:
  • :diffupdate znovu proskenuje a překreslí rozdíly (u komplikovanějších mergů nemusí Vim správně pochopit danou změnu)
  • :set wrap nastavení zalamování řádků (hodí se při velmi dlouhých řádcích, typicky některá XML)
  • zo/zc otevře/zavře skryté (folded) řádky

3-way merge

Nemusím vám říkat, že 2-way merge je pro školáky - profíci makaj v Gitu, či v Mercurialu a tam je dvoucestný merge nedostačující. Ke slovu přichází 3-way merge. Co to je?

Schéma 3-way merge

3-way merge není nic složitého. V podstatě jde o to, že máme dvě verze, které mají společného předka. V mergovacím nástroji pak vidíme všechny tři verze vedle sebe a většinou máme k dispozici ještě čtvrté okno s aktuálním výsledkem merge.

3-way merge v aplikaci


Nastavení Gitu

Nastavení spolupráce Gitu a vimdiff je jednoduché - stačí spustit z příkazové řádky následující sadu příkazů:
$ git config --global merge.tool vimdiff
$ git config --global merge.conflictstyle diff3
$ git config --global mergetool.prompt false
$ git config --global mergetool.keepBackup false

Pokud se podíváte do ~/.gitconfig, měli byste tam vidět:


Nastavení Mercurialu

Nastavení Mercurialu je podobně jednoduché. Otevřeme soubor ~/.hgrc příkazem
$ hg config --edit
a vložíme následující řádky


Sekce [extensions] a [extdiff] nejsou pro merge nutné, ale hodí se, pokud chceme vimdiff používat jako dodatečný externí diff nástroj. Sekundární diff spustíme příkazem hg vimdiff.

Základní příkazy

Základní příkazy jsou stejné jako v sekci 2-way merge, s výjimkou příkazů dp/do (:diffput/:diffget) - pokud bychom je nyní použili, vimdiff nám zahlásí chybu:
More than two buffers in diff mode, don't know which one to use
To je v pořádku: u 3-way merge se ve vimdiff otevřou 4 buffery, všechny v diff módu. Takže do té doby, než se vimdiff naučí komunikovat telepaticky, je potřeba mu říct, ze kterého bufferu chceme danou změnu natáhnout.

vimdiff, 3-way merge v Gitu

Klasické merge flow vypadá následovně:
  1. Začínáme v dolním "výsledkovém" bufferu.
  2. ]c (skočit na následující diff, který chceme mergovat)
  3. :diffget <identifikace-bufferu> získáme změnu z daného bufferu (viz dále)
  4. Opakujeme 2-3.
  5. :only | wq uložíme merge.

Obecně, identifikátor bufferu získáme příkazem :ls. To je ale dost nepraktické a nepřehledné. Další možnost je identifikovat buffer částečným názvem souboru. Tady přichází na pomoc jak Git, tak Mercurial, který přidávají k názvům souborů příhodný suffix.

Merge v Gitu

Git přidává do názvů mergovaných souborů následující suffixy, v pořadí zleva doprava: LOCAL (vlevo), BASE (uprostřed), REMOTE (vpravo). Pro natažení změny z (levého) bufferu LOCAL můžeme použít příkaz :diffg LO.

Výpis bufferů pro Git:
:ls
  1 #a   "./myFile_LOCAL_7424.txt"      line 1
  2  a   "./myFile_BASE_7424.txt"       line 0
  3  a   "./myFile_REMOTE_7424.txt"     line 0
  4 %a   "myFile.txt"                   line 12

Merge v Mercurialu


vimdiff, 3-way merge v Mercurialu

Mercurial přidává do názvů mergovaných souborů následující suffixy, v pořadí zleva doprava: orig (vlevo), base (uprostřed), other (vpravo). Pro natažení změny z (levého) bufferu orig můžeme použít příkaz :diffg orig.

Výpis bufferů pro Mercurial:
:ls
  1 %a   "myFile.txt"                   line 2
  2  a-  "myFile.txt.orig"              line 0
  3  a-  "/tmp/myFile.txt~base.iZwwuA"  line 0
  4  a-  "/tmp/myFile.txt~other.km9Itr" line 0

Co mi (zatím) schází?

Musím říct, že potom, co jsem si vimdiff osahal, pochopil jeho logiku a naučil se jeho příkazy, jsem si ho docela oblíbil.

Jediná výtka zatím jde za jeho neschopností skákat přímo po konfliktech - Git i Mercurial dělají výborně automatické merge a ty jsou samozřejmě vidět ve vimdiffu taky, jako pouhá změna bez konfliktu. Mít nějaký příkaz, který rozlišuje pouhý diff a konflikt, by bylo fajn.

Související články


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

20. května 2013

Mercurial, jak nastavit P4Merge jako nástroj pro vizuální merge a diff

Mercurial je výborný distribuovaný verzovací systém (DVCS). Je free a má spoustu zajímavých vlastností. Perforce (P4) je centralizovaný verzovací systém. Má převážně komerční licenci a výborné nástroje na mergování a branchování. Co můžou mít tyto dva systémy společného?

P4Merge

P4Merge je grafický nástroj pro merge a diff. Je jednou ze silných zbraní P4. Já jsem ho vždycky rád používal. Jeho výhodou je, že akceptuje parametry z příkazové řádky, takže jej lze použít i mimo rámec P4 a to buď úplně samostatně, nebo jako externí nástroj z jiné aplikace. Třeba Mercurialu :-)

Diff pomocí P4Merge

Podle toho, kolik parametrů se zadá na příkazové řádce, se spustí buď diff (2 parametry), nebo merge (3-4 parametry). Každý parametr je cesta k porovnávanému souboru.

Parametry příkazové řádky P4Merge

P4Merge sice není open source, ale je free, takže nic nebrání jeho zakomponování do jiného nástroje.

Konfigurace Mercurialu

Mercurial neobsahuje nástroj na vizuální merge. Pokud je potřeba zmergovat konfliktní změny a žádný nástroj není nakonfigurovaný, tak Mercurial vloží do sloučeného souboru mergovací značky (takový to <<<<<<<<<<< something). To asi nechceme :-)

Mergovacích nástrojů existuje spousta, každý si určitě vybere. Já jsem si oblíbil P4Merge - přece jenom, když něco s oblibou používáte dva roky, tak vám to trochu přiroste k srdci. Jak teda Mercurialu podstrčíme P4Merge?

Konfigurace merge nástroje je v souboru ~/.hgrc v sekci [merge-tools]:
[merge-tools]
p4.priority = 60
p4.premerge = True
p4.executable = p4merge
p4.gui = True
p4.args = $base $other $local $output
p4.binary = False

[extensions]
hgext.extdiff =

[extdiff]
cmd.p4diff = p4merge
cmd.meld = meld
Jak je vidět, nastavil jsem si kromě merge nástroje i externí diff. Takže když pustím příkaz hg p4diff file1 file2, spustí se mi také P4Merge, tentokrát jako diff nástroj.

Merge pomocí P4Merge

Je to opravdu taková idyla?

Není. Abych byl maximálně spokojený, chybí mi (zatím) dvě věci. Jednak bych rád, abych mohl provést hromadný merge - Mercurial totiž dělá to, že pro každý mergovaný soubor sekvenčně spustí P4Merge. A já bych chtěl např. hromadně přijmout příchozí, nebo své změny. Což je věc, kterou P4 normálně umí. Ale asi je to zajištěno přes P4V klienta.

Druhá vada na kráse je, že P4Merge neumí diff více souborů (např. příkaz hg p4diff -r 12:tip). Takže ho nemůžu použít pro vizuální diff mezi revizemi. To je důvod, proč mám v souboru .hgrc jako další externí diff nástroj Meld, který diff adresářů umí.

Meld, vizuální diff mezi revizemi

Máte nějaké tipy na používání P4Merge, nebo jiného vizuálního nástroje na merge v Mercurialu? Budu rád, když se podělíte o své zkušenosti v komentářích.

Související články