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 --edita 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 useTo 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ě:
- Začínáme v dolním "výsledkovém" bufferu.
- ]c (skočit na následující diff, který chceme mergovat)
- :diffget <identifikace-bufferu> získáme změnu z daného bufferu (viz dále)
- Opakujeme 2-3.
- :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.