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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[merge] | |
tool = vimdiff | |
conflictstyle = diff3 | |
[mergetool] | |
prompt = false | |
keepBackup = false |
Nastavení Mercurialu
Nastavení Mercurialu je podobně jednoduché. Otevřeme soubor ~/.hgrc příkazem$ hg config --edita vložíme následující řádky
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[ui] | |
merge = vimdiff | |
[merge-tools] | |
vimdiff.executable = vimdiff | |
vimdiff.args = -f -d $output -M $local $base $other -c "wincmd J" -c "set modifiable" -c "set write" | |
vimdiff.premerge = keep | |
[extensions] | |
hgext.extdiff = | |
[extdiff] | |
cmd.vimdiff = vimdiff |
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.