Obsah
1. Nástroje pro porovnání obsahu dvou textových souborů
2. Klasický unixový nástroj diff
3. Způsoby zobrazení rozdílů mezi soubory nástrojem diff
4. Formáty, v nichž není zobrazen kontext
5. Formáty s volitelným kontextem
5.1 Základní formát s kontextem
5.3 Zobrazení rozdílů ve dvou sloupcích
7. Filtr idiff aneb barevné zobrazení rozdílů
9. Použití filtru diff-so-fancy s GITem
10. Filtr diffh určený pro vygenerování HTML stránky se zobrazením rozdílů
11. Další utility pro porovnání souborů s textovým rozhraním
16. Utility s plnohodnotným GUI
19. Další užitečné nástroje popsané příště
1. Nástroje pro porovnání obsahu dvou textových souborů
Nedávno na Rootu vyšla zprávička o článku s tématem, jak v Linuxu hledat rozdíly v textových souborech. Zmíněný článek se zabýval základním nástrojem diff a taktéž aplikací Meld s plnohodnotným grafickým uživatelským rozhraním. Ovšem porovnání dvou souborů, popř. i jejich synchronizace je tak často používaná operace, že postupně vzniklo několik desítek dalších nástrojů, které buď doplňují možnosti diffu (skvělý diff-so-fancy), nebo je lze použít zcela samostatně. V tomto článku si některé z těchto nástrojů představíme.
Poznámka: samostatnou kapitolu tvoří utility pro třícestný merge. Těm se budeme podrobněji věnovat příště.
2. Klasický unixový nástroj diff
První nástroj, o kterém se v tomto článku zmíníme, možná ani není nutné čtenářům Rootu podrobně představovat, protože se s ním už pravděpodobně setkali. Tento nástroj se jmenuje diff a jedním z důvodů, proč se o něm zmiňujeme hned v úvodních kapitolách, je fakt, že první verze diffu vznikla již na začátku sedmdesátých let minulého století (v roce 1974 již například vyšla verze založená na stále používaném Hunt-McIlroyově algoritmu). Tento nástroj samozřejmě prošel poměrně dlouhým vývojem a různými rozšířeními, ať se to již týká vlastního interního algoritmu pro hledání rozdílů, tak i formátů výstupu, tj. způsobů, jakým diff zobrazuje rozdíly mezi porovnávanými soubory. V současné verzi dokáže diff porovnat dva soubory, obsah dvou adresářů, popř. rekurzivně procházet a navzájem porovnat zvolenou dvojicí adresářů. Pokud výstup produkovaný diffem uložíme do souboru, získáme tzv. patch, který je možné (například na jiném počítači) aplikovat na původní soubor a vlastně tak znovu provést historii editace (podle použitého formátu lze použít nástroje patch, ed, vi či Vim).
Již v předchozím odstavci jsme se zmínili o tom, že současné verze nástroje diff podporují několik způsobů zobrazení rozdílů mezi dvěma soubory. Vyžadovaný formát se volí pomocí přepínačů zadaných na příkazové řádce:
Přepínač | Dlouhá verze | Význam |
---|---|---|
--normal | výchozí formát | |
-n | --rcs | formát používaný v dnes již archaickém systému RCS |
-e | --ed | vytváří skript spustitelný v editorech ed, vi a samozřejmě i Vim |
-c, -C | --context | základní formát, v němž se kromě změn zobrazuje i kontext |
-u, -U | --unified | tzv. unifikovaný formát, v němž se také zobrazuje kontext |
-y | --side-by-side | zobrazení rozdílů mezi soubory ve dvou sloupcích |
3. Způsoby zobrazení rozdílů mezi soubory nástrojem diff
Podívejme se nyní na způsob zobrazení rozdílů mezi dvěma verzemi jednoho zdrojového souboru. Oba soubory, které se budou porovnávat, naleznete na adresách:
Nejprve si popíšeme ty formáty, v nichž není explicitně zobrazen kontext, tj. „okolí změn“ a posléze si ukážeme formáty, v nichž naopak kontext nalezneme. Kontext není důležitý pouze pro čtenáře vytvořených rozdílových souborů, ale například i pro nástroj patch, který dokáže změnu aplikovat i ve chvíli, kdy byl soubor mezitím editován a došlo tedy například k posunu řádků apod.
4. Formáty, v nichž není zobrazen kontext
4.1 Výchozí formát
Pokud nástroj diff spustíme jen se specifikací dvou souborů, popř. souboru a adresáře, v němž se nachází soubor stejného jména, dostaneme následující výstup:
105a106,109 > # three new lines > # three new lines > # three new lines > 118,120d121 < # TODO: delete these three lines < # TODO: delete these three lines < # TODO: delete these three lines 125d125 < @then('I should see 0 components') 127c127 < def check_components(context, num=0, components='', ecosystem=''): --- > def check_components(context, num, components='', ecosystem=''): 131a132 > assert json_data is not None 133c134 < search_results = json_data['result'] --- > search_results = json_data['analysis']
Vidíme, že diff zobrazil pouze ty části souborů, které se od sebe odlišují. Do těchto částí pak vložil přesné informace o tom, k jakým změnám došlo. To je důležité, protože výsledek musí být strojově (tudíž jednoznačně) zpracovatelný. Ve výstupu nalezneme tři typy příkazů, přičemž každý příkaz obsahuje číslo řádku popř. rozsah změněných řádků, dále jméno příkazu a potom číslo řádku (či rozsah řádků), ve druhém souboru. Všechna čísla řádků odpovídají původním souborům. Mezi tři podporované příkazy patří:
Příkaz | Mnemotechnická pomůcka | Význam |
---|---|---|
a | append | rozdíl spočívá v přidaných řádcích ve druhém souboru |
c | change | řádek či řádky byly změněny, následovat bude seznam rozdílů oddělený --- |
d | delete | rozdíl spočívá v řádcích, které ve druhém souboru chybí |
Příklady:
Celý příkaz | Význam |
---|---|
127c127 | došlo ke změně na řádku 127 |
105a106,109 | změna na řádku 105 (v prvním soubor), ve druhém souboru jsou nové čtyři řádky 106 až 109 |
125d125 | ve druhém souboru chybí (oproti souboru prvnímu) řádek číslo 125 |
118,120d121 | ve druhém souboru chybí (oproti souboru prvnímu) tři řádky 118 až 120 |
4.2 Formát pro RCS
Ještě stručnější (a velmi těžce čitelný) je formát používaný v RCS. Ten získáme jednoduše příkazem:
diff -n old.py new.py
Ve výstupu najdeme pouze dva typy příkazů, a to d (delete) a a (append). Změna je tedy představována vymazáním řádku a jeho nahrazením jiným řádkem. Každý příkaz navíc obsahuje i počitadlo opakování, tj. kolik řádků se má vložit nebo vymazat:
a105 4 # three new lines # three new lines # three new lines d118 3 d125 1 d127 1 a127 1 def check_components(context, num, components='', ecosystem=''): a131 1 assert json_data is not None d133 1 a133 1 search_results = json_data['analysis']
4.3 Formát pro ed, vi či Vim
Z technického hlediska je zajímavější přepínač -e, protože ten produkuje skripty, které je možné spustit jak v editoru ed (ten velmi pravděpodobně máte nainstalovaný, i když jste ho možná nikdy nepoužili), tak i ve Vimu. Na řádcích označených <Esc> se nachází jediný znak – escape (kód 27 v ASCII), protože právě tímto znakem se v edu i Vimu ukončují příkazy „append“, „change“ i „delete“:
133c search_results = json_data['analysis'] <Esc> 131a assert json_data is not None <Esc> 127c def check_components(context, num, components='', ecosystem=''): <Esc> 125d 118,120d 105a # three new lines # three new lines # three new lines <Esc>
Pokud si vytvoříte skript příkazem:
diff -e old.py new.py > patch.vim
Je možné ve Vimu otevřít původní soubor old.py a pomocí příkazu:
:source patch.vim
aplikovat jednotlivé editační příkazy uložené ve skriptu.
Popř. lze vše provést z příkazového řádku:
vim old.py -S patch.vim
Poznámka: ve skutečnosti je mnohem častější i praktičtější použití dále popsaných formátů, které jsou zpracovatelné utilitou patch.
5. Formáty s volitelným kontextem
Výše uvedené tři formáty zobrazují pouze rozdíly mezi soubory, ale nepřidávají do vytvořeného výstupu žádný kontext, tj. oblast kódu, v níž ke změně došlo. Proto nejsou takové rozdílové soubory příliš čitelné, což nám však ve skutečnosti nemusí příliš vadit, protože diff podporuje i další výstupní formáty. Velkou předností existence kontextu je fakt, že případné změny je možné aplikovat i ve chvíli, kdy se (další editací) změní počet řádků nebo dojde k dalším změnám v souboru, na který je patch aplikován (to se ovšem týká utility patch a nikoli nástroje diff).
5.1 Základní formát s kontextem
Jedním ze základních formátů, který zobrazuje i kontextové informace, je formát zapnutý přepínačem -c. Výstup při použití příkazu:
diff -c old.py new.py
vypadá značně odlišně, protože se namísto jednopísmenných příkazů do prvního sloupce zapisují značky, které označují přidané či naopak smazané řádky (z pohledu historie). Dále si povšimněte, že změny jsou (pokud je to možné) sloučené do skupin pojmenovaných hunk. Každý hunk začíná řádkem s hvězdičkami, za ním následují údaje o řádcích v prvním i druhém souboru, jichž se hunk týká a poté jsou již jednotlivé řádky z hunku vypsány. V prvním sloupci znak mezery znamená, že řádek nebyl změněn, ! značí změněný řádek, – řádek vymazaný (chybí ve druhém souboru) a + řádek přidaný (do druhého souboru oproti souboru prvnímu):
*** old.py 2018-01-23 17:19:26.424398470 +0100 --- new.py 2018-01-23 17:19:40.856322400 +0100 *************** *** 103,108 **** --- 103,112 ---- use_token = parse_token_clause(token) + # three new lines + # three new lines + # three new lines + url = component_analysis_url(context, ecosystem, component, version) for _ in range(timeout // sleep_amount): *************** *** 115,136 **** elif status_code != 404: raise Exception('Bad HTTP status code {c}'.format(c=status_code)) time.sleep(sleep_amount) - # TODO: delete these three lines - # TODO: delete these three lines - # TODO: delete these three lines else: raise Exception('Timeout waiting for the component analysis results') - @then('I should see 0 components') @then('I should see {num:d} components ({components}), all from {ecosystem} ecosystem') ! def check_components(context, num=0, components='', ecosystem=''): """Check that the specified number of components can be found.""" components = split_comma_separated_list(components) json_data = context.response.json() ! search_results = json_data['result'] assert len(search_results) == num for search_result in search_results: assert search_result['ecosystem'] == ecosystem --- 119,137 ---- elif status_code != 404: raise Exception('Bad HTTP status code {c}'.format(c=status_code)) time.sleep(sleep_amount) else: raise Exception('Timeout waiting for the component analysis results') @then('I should see {num:d} components ({components}), all from {ecosystem} ecosystem') ! def check_components(context, num, components='', ecosystem=''): """Check that the specified number of components can be found.""" components = split_comma_separated_list(components) json_data = context.response.json() + assert json_data is not None ! search_results = json_data['analysis'] assert len(search_results) == num for search_result in search_results: assert search_result['ecosystem'] == ecosystem
Namísto volby -c můžeme použít i volbu -C číslo, kterou se určuje, kolik nezměněných řádků se má zahrnout do jednotlivých hunků. Pokud budeme například vyžadovat pět řádků na začátku a na konci, použijeme příkaz:
diff -C 5 old.py new.py
s následujícím výsledkem:
*** old.py 2018-01-23 17:19:26.424398470 +0100 --- new.py 2018-01-23 17:19:40.856322400 +0100 *************** *** 101,110 **** --- 101,114 ---- timeout = context.component_analysis_timeout # in seconds sleep_amount = 10 # we don't have to overload the API with too many calls use_token = parse_token_clause(token) + # three new lines + # three new lines + # three new lines + url = component_analysis_url(context, ecosystem, component, version) for _ in range(timeout // sleep_amount): if use_token: status_code = requests.get(url, headers=authorization(context)).status_code *************** *** 113,138 **** if status_code == 200: break elif status_code != 404: raise Exception('Bad HTTP status code {c}'.format(c=status_code)) time.sleep(sleep_amount) - # TODO: delete these three lines - # TODO: delete these three lines - # TODO: delete these three lines else: raise Exception('Timeout waiting for the component analysis results') - @then('I should see 0 components') @then('I should see {num:d} components ({components}), all from {ecosystem} ecosystem') ! def check_components(context, num=0, components='', ecosystem=''): """Check that the specified number of components can be found.""" components = split_comma_separated_list(components) json_data = context.response.json() ! search_results = json_data['result'] assert len(search_results) == num for search_result in search_results: assert search_result['ecosystem'] == ecosystem assert search_result['name'] in components --- 117,139 ---- if status_code == 200: break elif status_code != 404: raise Exception('Bad HTTP status code {c}'.format(c=status_code)) time.sleep(sleep_amount) else: raise Exception('Timeout waiting for the component analysis results') @then('I should see {num:d} components ({components}), all from {ecosystem} ecosystem') ! def check_components(context, num, components='', ecosystem=''): """Check that the specified number of components can be found.""" components = split_comma_separated_list(components) json_data = context.response.json() + assert json_data is not None ! search_results = json_data['analysis'] assert len(search_results) == num for search_result in search_results: assert search_result['ecosystem'] == ecosystem assert search_result['name'] in components
5.2 Unifikovaný formát
Pravděpodobně nejpoužívanějším formátem rozdílových souborů, který podporují všechny moderní varianty diffu, je takzvaný unifikovaný formát. Tento formát se povoluje přepínačem -u, takže volání diffu může vypadat následovně:
diff -u old.py new.py
Výsledek vypadá odlišně od předchozího formátu. Především můžeme vidět, že se informace o místu, v němž ke změně došlo, zapisují na jediný řádek uvozený dvojicí znaků @@. Před čísly řádků je znak + či – podle toho, jakého souboru se číslo týká (zda souboru prvního či druhého), číslo za čárkou značí počet řádků v hunku (včetně kontextových řádků před a za změnou). Vložené řádky začínají znakem + v prvním sloupci, řádky smazané znakem -. I pokud na řádku došlo jen k nepatrné změně, je tato změna zaznamenána dvěma operacemi: vymazáním původního řádku a vložením řádku s novým obsahem. Díky tomu, že se kromě vlastních změněných řádků zobrazí i kontext (zde konkrétně tři řádky před a za změnou), může utilita patch aplikovat změny i ve chvíli, kdy byl soubor mezitím změněn:
--- old.py 2018-01-23 17:19:26.424398470 +0100 +++ new.py 2018-01-23 17:19:40.856322400 +0100 @@ -103,6 +103,10 @@ use_token = parse_token_clause(token) + # three new lines + # three new lines + # three new lines + url = component_analysis_url(context, ecosystem, component, version) for _ in range(timeout // sleep_amount): @@ -115,22 +119,19 @@ elif status_code != 404: raise Exception('Bad HTTP status code {c}'.format(c=status_code)) time.sleep(sleep_amount) - # TODO: delete these three lines - # TODO: delete these three lines - # TODO: delete these three lines else: raise Exception('Timeout waiting for the component analysis results') -@then('I should see 0 components') @then('I should see {num:d} components ({components}), all from {ecosystem} ecosystem') -def check_components(context, num=0, components='', ecosystem=''): +def check_components(context, num, components='', ecosystem=''): """Check that the specified number of components can be found.""" components = split_comma_separated_list(components) json_data = context.response.json() + assert json_data is not None - search_results = json_data['result'] + search_results = json_data['analysis'] assert len(search_results) == num for search_result in search_results: assert search_result['ecosystem'] == ecosystem
5.3 Zobrazení rozdílů ve dvou sloupcích
Posledním formátem zobrazení rozdílů nabízeným nástrojem diff je formát, v němž se zobrazí obsah obou porovnávaných souborů ve třech sloupcích, přičemž prostřední sloupec pouze zobrazuje speciální znaky >, < a | pro určení typu rozdílu. Tento formát se nastavuje přepínačem -y (s možností specifikace šířky sloupců):
diff -y old.py new.py
Podívejme se na výsledek, který však byl zkrácený o shodné řádky. Povšimněte si, jak se pomocí znaků > a < označují ty řádky, které byly přidány či naopak vymazány. Změněné řádky jsou označeny znakem |:
timeout = context.component_analysis_timeout # in second timeout = context.component_analysis_timeout # in second sleep_amount = 10 # we don't have to overload the API wi sleep_amount = 10 # we don't have to overload the API wi use_token = parse_token_clause(token) use_token = parse_token_clause(token) > # three new lines > # three new lines > # three new lines > url = component_analysis_url(context, ecosystem, componen url = component_analysis_url(context, ecosystem, componen for _ in range(timeout // sleep_amount): for _ in range(timeout // sleep_amount): if use_token: if use_token: status_code = requests.get(url, headers=authoriza status_code = requests.get(url, headers=authoriza else: else: status_code = requests.get(url).status_code status_code = requests.get(url).status_code if status_code == 200: if status_code == 200: break break elif status_code != 404: elif status_code != 404: raise Exception('Bad HTTP status code {c}'.format raise Exception('Bad HTTP status code {c}'.format time.sleep(sleep_amount) time.sleep(sleep_amount) # TODO: delete these three lines < # TODO: delete these three lines < # TODO: delete these three lines < else: else: raise Exception('Timeout waiting for the component an raise Exception('Timeout waiting for the component an @then('I should see 0 components') < @then('I should see {num:d} components ({components}), all fr @then('I should see {num:d} components ({components}), all fr def check_components(context, num=0, components='', ecosystem | def check_components(context, num, components='', ecosystem=' """Check that the specified number of components can be f """Check that the specified number of components can be f components = split_comma_separated_list(components) components = split_comma_separated_list(components) json_data = context.response.json() json_data = context.response.json() > assert json_data is not None search_results = json_data['result'] | search_results = json_data['analysis'] assert len(search_results) == num assert len(search_results) == num for search_result in search_results: for search_result in search_results: assert search_result['ecosystem'] == ecosystem assert search_result['ecosystem'] == ecosystem
6. Vylepšujeme výstup z diffu
Nástroj diff je již sám o sobě velmi užitečný a patří mezi základní utility využívané (nejenom) při vývoji na unixových systémech (dalšími velmi užitečnými nástroji jsou sed, grep, patch atd.). To však neznamená, že by diff nešel vylepšit. Existuje například poměrně velké množství utilit, jejichž cílem je zjednodušit čtení výstupů diffu. V dalších kapitolách se zaměříme na tři takové utility, které se jmenují idiff, diff-so-fancy a konečně diffh.
7. Filtr idiff aneb barevné zobrazení rozdílů
Velmi jednoduchým prográmkem, který do jisté míry dokáže zlepšit čitelnost rozdílových souborů, je skript pojmenovaný idiff, který naleznete na stránce http://www.pixelbeat.org/scripts/idiff. Překlad není zapotřebí provést, protože idiff je napsán v shellu a je založen na nástrojích less, sed a tput, které by již měly být nainstalovány. Tento skript pracuje jako běžný filtr, tj. lze ho použít například takto (pokud se idiff nachází v pracovním adresáři a má nastaveno právo „x“ pro uživatele):
diff -u old.py new.py | ./idiff
Možnosti skriptu jsou vidět na následující trojici screenshotů:
Obrázek 1: Obarvený výstup při použití standardního formátu diffu.
Obrázek 2: Obarvený výstup při použití formátu s kontextem (-c).
Obrázek 3: Obarvený výstup při použití unifikovaného formátu (-u). Pro zajímavost si porovnejte tento screenshot se screenshotem číslo 4.
Skript idiff si samozřejmě můžete zkopírovat do adresáře, který se nachází na PATH. Potom se k němu nemusí uvádět cesta.
8. Filtr diff-so-fancy
Druhým filtrem, tentokrát již mnohem zajímavějším, je skript nazvaný diff-so-fancy, který je naprogramovaný v Perlu a můžete ho získat z Git repositáře https://github.com/so-fancy/diff-so-fancy (opět není zapotřebí provést žádný překlad, pouze umístění skriptu do adresáře v PATH). Jméno tohoto skriptu je příhodné, protože diff-so-fancy nejenom že zvýrazní rozdíly provedené v rámci jednoho řádku (ty je jinak mnohdy složité najít), ale dokáže upravit a především zpřehlednit i výstup z příkazu git diff atd.
Obrázek 4: Povšimněte si, jak diff-so-fancy dokáže zvýraznit rozdíly provedené na jediném řádku (spodní polovina screenshotu).
Pokud potřebujeme pouze zpřehlednit výstup z klasického diffu, můžeme spojit možnosti obou filtrů, tj. provést tento příkaz:
diff -u old.py new.py | idiff | diff-so-fancy
Jednodušší je si napsat příslušnou funkci představující nový příkaz shellu do .bashrc nebo do .bash_profile:
function bestdiff() { diff -u "$1" "$2" | idiff | diff-so-fancy }
Obrázek 5: Ještě lépe jsou schopnosti skriptu v zobrazení změn provedených v rámci jednoho řádku patrné u zobrazení rozdílů v běžném textu (úvodní odstavec článku na Wikipedii o Linuxu).
9. Použití filtru diff-so-fancy s GITem
Filtr diff-so-fancy je určen pro spolupráci s GITem a dokonce GIT používá i ve chvíli, kdy pouze mění výstup z běžného diffu (čte informace o použitých barvách terminálu). Aby tento filtr pracoval správně, je nutné do souboru ~/.gitconfig vložit nové řádky s konfigurací barev. Výchozí hodnoty, které můžete připojit na konec GITovského konfiguračního souboru, vypadají následovně:
[color] ui = true [color "diff-highlight"] oldNormal = red bold oldHighlight = red bold 52 newNormal = green bold newHighlight = green bold 22 [color "diff"] meta = yellow frag = magenta bold commit = yellow bold old = red bold new = green bold whitespace = red reverse
Poté již můžeme použít například příkaz:
git diff --color | diff-so-fancy
Popř. změnit konfiguraci GITu tak, aby se diff-so-fancy používal vždy (změny se opět zapíšou do souboru ~./gitconfig):
git config --global core.pager "diff-so-fancy | less --tabs=4 -RFX"
Obrázek 7: Takto vypadá výstup příkazu git diff při použití skriptu diff-so-fancy.
Obrázek 8: Po vložení testovacího souboru do GIT repositáře se můžeme na změny podívat i přes git diff (porovnejte se screenshotem číslo 4).
10. Filtr diffh určený pro vygenerování HTML stránky se zobrazením rozdílů
Zatímco předchozí dva filtry upravovaly výstup diffu takovým způsobem, aby se vylepšilo zobrazení na běžném textovém terminálu, pracuje filtr nazvaný diffh odlišným způsobem, protože jeho výstupem je HTML stránka se zvýrazněním rozdílů:
Obrázek 9: Výstup generovaný nástrojem diffh.
Tento filtr je kupodivu naprogramovaný v jazyku C, takže pokud diffh chybí v repositáři vaší distribuce, musíte si provést překlad sami. Ve skutečnosti je to velmi jednoduché, protože má tento nástroj jen minimální závislosti:
- Stáhněte zdrojové kódy z adresy https://sourceforge.net/projects/diffh/
- Rozbalte tarball: tar xvfz diffh-0.3.2.tar.gz
- Ve vytvořeném adresáři spusťte ./configure
- Následuje klasické make
- Výsledný binární soubor objevíte v adresáři src
11. Další utility pro porovnání souborů s textovým rozhraním
Krátce se zmíním ještě o dalších utilitách, které slouží pro porovnání souborů a které taktéž používají textové rozhraní, ať již „pouze“ výstup na konzoli či plnohodnotné TUI.
12. Nástroj sdiff
Nástroj nazvaný sdiff (který pravděpodobně máte nainstalovaný) získal svoje jméno proto, že dokáže porovnávat dvě verze souborů „side-by-side“. Způsob zobrazení odpovídá příkazu diff -y, ovšem důvod, proč se o tomto nástroji dnes vůbec zmiňuji, souvisí s tím, že sdiff podporuje i interaktivní režim, v němž může uživatel postupně procházet jednotlivými změnami a ty aplikovat (schvaluje je) či přeskočit. Výstup produkovaný tímto nástrojem může vypadat následovně:
@then('I should see 0 components') < @then('I should see {num:d} components ({components}), all fr @then('I should see {num:d} components ({components}), all fr def check_components(context, num=0, components='', ecosystem | def check_components(context, num, components='', ecosystem=' """Check that the specified number of components can be f """Check that the specified number of components can be f components = split_comma_separated_list(components) components = split_comma_separated_list(components) json_data = context.response.json() json_data = context.response.json() > assert json_data is not None search_results = json_data['result'] | search_results = json_data['analysis'] assert len(search_results) == num assert len(search_results) == num for search_result in search_results: for search_result in search_results: assert search_result['ecosystem'] == ecosystem assert search_result['ecosystem'] == ecosystem assert search_result['name'] in components assert search_result['name'] in components
13. Nástroj wdiff
Jméno dalšího nástroje wdiff vzniklo ze slovního spojení „word diff“. A skutečně – wdiff zobrazuje rozdíly na úrovni jednotlivých slov a nikoli celých řádků. To nemá (alespoň podle mého názoru) větší význam pro zdrojové kódy, protože se informace o změnách ztratí ve změti dalších řádků; ostatně to můžete sami posoudit:
@then('I should see [-0 components') @then('I should see-] {num:d} components ({components}), all from {ecosystem} ecosystem') def check_components(context, [-num=0,-] {+num,+} components='', ecosystem=''): """Check that the specified number of components can be found.""" components = split_comma_separated_list(components) json_data = context.response.json() {+assert json_data is not None+} search_results = [-json_data['result']-] {+json_data['analysis']+} assert len(search_results) == num for search_result in search_results: assert search_result['ecosystem'] == ecosystem assert search_result['name'] in components
Ovšem pokud potřebujete porovnat například dvě verze souboru README, může být wdiff velmi užitečný. Podívejme se na ukázkový příklad s prvními dvěma odstavci o Linuxu, které jsem získal z Wikipedie a provedl v nich malé změny. Rozdíly mezi dvěma verzemi souboru se zobrazí formou značek [- … -] a {+ … +}, které jsem navíc zvýraznil tučně (větší změny by však již nebyly zobrazeny takto přehledně):
Linux is a name that broadly denotes a family of free and [-closed-source-] {+open-source+} software operating systems (OS) built around the Linux kernel. Typically, Linux is packaged in a form known as {+a+} Linux distribution (or distro for short) for both desktop and server use. The defining component of a Linux distribution is the Linux kernel,[11] an operating system kernel first released on September 17, 1991, by Linus Torvalds.[12][13][14] Many Linux distributions use the word "Linux" in their name. The Free Software Foundation uses the name GNU/Linux to refer to the operating system family, as well as specific distributions, to emphasize that most Linux distributions are not just the Linux kernel, and that they have in common not only {+the+} kernel, but also numerous utilities and libraries, a large proportion of which are from the GNU project. This has led to some controversy. Linux was originally developed for personal computers based on the Intel x86 architecture, but has since been ported to more platforms than any other operating system.[17] Because of the dominance of the Linux kernel-based Android OS on smartphones, Linux has the largest installed base of all general-purpose operating systems.[18] Linux is also the leading operating system on servers and other big iron systems such as mainframe computers, and the only OS used on TOP500 supercomputers (since November 2017, having before gradually eliminated all competitors).[19][20] It is used by around 2.3% of desktop computers.[21][22] The Chromebook, which runs the Linux kernel-based Chrome OS, dominates the US K–12 education market and represents nearly [-12%-] {+20%+} of the sub-$300 notebook sales in the US.[23] [-GNU/Linux-] {+Linux+} also runs on embedded systems—devices whose operating system is typically built into the firmware and is highly tailored to the system. This includes TiVo and similar {+DVR+} devices, network routers, facility automation controls, televisions,[24][25] video game consoles and smartwatches.[26] Many smartphones and tablet computers run Android and other Linux derivatives.[27]
14. Diff režim Vimu
Pro zobrazení rozdílů mezi dvěma verzemi souboru i pro jejich synchronizaci je možné využít i interní nástroj zabudovaný do Vimu. Pokud se tento textový editor spustí s parametrem -d, očekávají se za tímto parametrem jména dvou souborů pro porovnání, popř. jméno souboru a jméno adresáře, v němž se nachází soubor stejného jména:
vim -d old.py new.py vim -d old.py ../test-sources/
V obou případech textový editor Vim oba soubory skutečně načte do dvojice bufferů, ovšem režim zobrazení se změní takovým způsobem, že se pomocí různých barev zvýrazní ty části souborů, které jsou odlišné. Navíc je možné delší části, jež jsou v obou souborech stejné, „zabalit“ (klávesová zkratka zc).
Obrázek 10: Porovnávání dvou souborů při vertikálním rozdělení oken. Toto rozdělení je vhodné použít v případě, že se jedná o zdrojové soubory s krátkými řádky, popř. pokud máte širokoúhlý monitor.
Barevné zvýraznění změn je sice pěkná a užitečná vlastnost, ovšem to není zdaleka vše, co Vim v „režimu diff“ programátorům nabízí. V tomto režimu jsou totiž k dispozici některé nové příkazy určené pro řízenou synchronizaci mezi soubory. Mezi tyto příkazy patří především:
Příkaz | Význam |
---|---|
[c | skok na začátek předchozího bloku se změnami |
]c | skok na začátek následujícího bloku se změnami |
dp | pokud se kurzor nachází na řádcích, které se v obou souborech odlišují, je změna z aktuálního souboru přenesena do souboru druhého |
do | opak předchozího příkazu – získání změny z druhého souboru a přenesení této změny do souboru aktuálního (tj. do souboru, v němž se nachází textový kurzor) |
:diffupdate | tento příkaz provede nové vyhodnocení rozdílů mezi oběma soubory, vhodné v případech, kdy se oba buffery rozsynchronizují (to se stává poměrně často) |
Příkazy dp a do lze použít i ve chvíli, kdy je vybraný blok textu. V tomto případě se změna aplikuje pouze na vybrané řádky, nikoli na celý blok změn.
Obrázek 11: Porovnávání dvou souborů při horizontálním rozdělení oken, které je vhodné využít v případě, že porovnávané zdrojové kódy (nebo jiné texty) obsahují dlouhé řádky.
Obrázek 12: Zobrazení našeho testovacího souboru ve Vimu v režimu rozpoznávání změn.
15. Nástroj mcdiff
Pokud preferujete použití nástroje pro zobrazení změn, který je založen na textovém uživatelském rozhraní, může být jednou z dobrých voleb utilitka nazvaná mcdiff, která je, podobně jako editor mcedit nebo hexa prohlížeče, součástí správce souborů Midnight Commander. Použití nástroje mcdiff je extrémně jednoduché – pouze ho zavolejte a předejte mu dvojici souborů (resp. přesněji řečeno verzí téhož souboru). Zobrazí se dva panely s obsahy obou verzí a uživateli jsou dány k dispozici nástroje pro sloučení změn, vyhledávání v obou verzích souboru atd. Klávesa pro sloučení změn se vždy vztahuje k prvnímu viditelnému rozdílu.
Obrázek 13: Integrovaná nápověda k nástroji mcdiff.
Obrázek 14: Zobrazení rozdílů v textových souborech.
Obrázek 15: Nastavení vlastností porovnávače mcdiff.
Obrázek 16: Změna barvového palety a zobrazení našeho testovacího souboru.
16. Utility s plnohodnotným GUI
Následuje popis některých GUI aplikací pro zobrazení rozdílů mezi soubory a popř. i pro slučování těchto souborů. V této oblasti se pravděpodobně nejvíce používá Meld a KDiff3, ovšem i další utility mohou být zajímavé. To se týká jak postaršího TkDiffu, tak i méně známé utilitky xxdiff. O dalších nástrojích se zmíním ve druhé části článku.
17. TkDiff
Další nástroj, s nímž se v dnešním článku alespoň ve stručnosti seznámíme, se jmenuje TkDiff. Jedná se o nástroj s grafickým uživatelským rozhraním a jak již jeho název napovídá, používá pro svoje GUI knihovnu Tk. Samotný TkDiff je naprogramován v jazyku TCL, který již sice dnes v žádném případě nestojí na předních místech v žebříčku popularity jazyků, nicméně TkDiff je stále udržován a je ho možné provozovat prakticky na jakémkoli systému, v němž je dvojice Tcl/Tk nainstalována (to v Linuxu není problém, pro Windows existujeActiveTcl atd. atd.). Pokud samotný TkDiff nenaleznete v repositáři své distribuce, můžete si ho stáhnout z adresy https://sourceforge.net/projects/tkdiff/files/latest/download, rozbalit a přímo použít (žádná kompilace není nutná, protože TCL je čistý interpret).
Nástroj TkDiff zobrazuje dvě verze souboru, dokáže přeskakovat mezi jednotlivými částmi, které byly změněny (hunky), označovat tyto části a poté provést operaci „merge“. Pokud začnete tento nástroj používat, je nutné vědět, že se mezi změněnými částmi musíte přesouvat pomocí ikon se šipkami a poté každou změnu odsouhlasit příslušným příkazem (ikona se zelenou vlajkou). Jednotlivé schválené změny jsou reprezentovány novými tlačítky na nástrojovém pruhu, takže se k nim můžete kdykoli vrátit. Po označení všech změn, které chcete schválit, je možné provést operaci „merge“. Dále je možné otevřít soubor obsahující informace o konfliktech (je generován například Gitem, pokud dojde k problémům při pokusu o automatické sloučení změn), ovšem jednu funkcionalitu současná verze postrádá – třícestný merge. Zajímavé je, že předchozí verze TkDiffu tuto funkci měly implementovanou. Pokud tedy potřebujete použít třícestný merge, který je skutečně potřebný, je lepší se poohlédnout po jiném nástroji, například po utilitě Kdiff3, kterou si popíšeme příště.
Podívejme se, jak vypadá uživatelské rozhraní tohoto nástroje při otevření dvou verzí jednoho souboru:
Obrázek 17: Porovnání textových souborů (plaintext).
Obrázek 18: Porovnání našeho testovacího souboru (povšimněte si scrollbaru s náhledem změn).
Obrázek 19: Nastavení vlastností TkDiffu.
18. xxdiff
Poslední nástroj s grafickým uživatelským rozhraním, který si dnes představíme a který pravděpodobně naleznete i v repositářích své Linuxové distribuce, se jmenuje xxdiff. Tento relativně malý a na systémové prostředky nenáročný nástroj sice není tak známý jako Meld, ovšem nabízí podobné funkce a navíc je v něm možné velmi snadno provádět třícestný merge, tj. porovnání a aplikování změn mezi třemi verzemi souborů (typicky se jedná o společného předka, soubor změněný lokálně a soubor mezitím změněný někým jiným). Utilitu xxdiff lze pro základní operace volat takto:
xxdiff old.py new.py
Obrázek 20: Porovnání dvou verzí souboru (klasický dvoucestný merge).
Popř. pokud budeme potřebovat provést třícestný merge, spustíme utilitu xdiff následujícím způsobem:
xxdiff puvodni.py muj.py vzdalene_zmeneny.py
Obrázek 21: Zobrazení tří verzí souboru při provádění třícestného merge.
K této utilitě se ještě na chvíli vrátíme příště při popisu nástrojů, které podporují provádění třícestného merge.
Obrázek 22: Integrovaná nápověda k nástroji xxdiff.
19. Další užitečné nástroje popsané příště
Ve druhé části tohoto článku si popíšeme několik dalších nástrojů s plnohodnotným grafickým uživatelským rozhraním. Bude se jednat především o Meld (pravděpodobně nejznámější nástroj z této kategorie), dále pak o aplikaci Diffuse a samozřejmě nezapomeneme ani na KDiff3.
Obrázek 23: Dvoucestný merge našeho testovacího zdrojového kódu v nástroji Meld.
20. Odkazy na Internetu
- Comparing and Merging Files
https://www.gnu.org/software/diffutils/manual/diffutils.html - Three-way merge
https://en.wikipedia.org/wiki/Merge_(version_control)#Three-way_merge - diff (1) – Linux Man Pages
https://www.systutorials.com/docs/linux/man/1-diff/ - diff utility (Wikipedia)
https://en.wikipedia.org/wiki/Diff_utility - GNU Wdiff
https://www.gnu.org/software/wdiff/ - GNU wdiff Manual
https://www.gnu.org/software/wdiff/manual/ - wdiff (1) – Linux Man Pages
https://www.systutorials.com/docs/linux/man/1-wdiff/ - diff3 (1) – Linux Man Pages
https://www.systutorials.com/docs/linux/man/1-diff3/ - sdiff (1) – Linux Man Pages
https://www.systutorials.com/docs/linux/man/1-sdiff/ - Stránky nástroje Meld
http://meldmerge.org/ - Meld na stránkách GNOME
https://wiki.gnome.org/Apps/Meld - Stránky nástroje TkDiff
https://sourceforge.net/projects/tkdiff/ - Zdrojové kódy TkDiffu
https://sourceforge.net/projects/tkdiff/files/tkdiff/4.2/ - Poslední verze nástroje TkDiff
https://sourceforge.net/projects/tkdiff/files/latest/download - Manuálová stránka k nástroji TkDiff
http://linux.math.tifr.res.in/manuals/man/tkdiff.html - diffh: Make your diff easier to see
https://inconsolation.wordpress.com/2013/10/07/diffh-make-your-diff-easier-to-see/ - Stránky projektu diffh
https://sourceforge.net/projects/diffh/ - Pretty Diff (implementovaný v JavaScriptu)
http://prettydiff.com/ - Nástroje pro diff textů
https://en.wikipedia.org/wiki/Diff-Text - Pretty Diff (implementovaný v JavaScriptu)
https://en.wikipedia.org/wiki/Pretty_Diff - Stránky projektu colordiff
https://www.colordiff.org/ - Skript idiff
http://www.pixelbeat.org/scripts/idiff - Three way git merging with Meld
https://lukas.zapletalovi.com/2012/09/three-way-git-merging-with-meld.html - xxdiff na serveru SourceForge
https://sourceforge.net/projects/xxdiff/ - Stránka nástroje KDiff3
http://kdiff3.sourceforge.net/ - Seriál o programovacím jazyku TCL a GUI knihovně Tk
https://www.root.cz/serialy/programovaci-jazyk-tcl/ - ActiveTcl
https://www.activestate.com/activetcl - Tiobe: žebříček popularity programovacích jazyků
https://www.tiobe.com/tiobe-index/