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.
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?
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.
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.
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
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.