Как стать автором
Обновить

diff для ленивых разработчиков или как сравнить несравнимое

Время на прочтение4 мин
Количество просмотров6K
В былые времена, на Windows, для сравнения oracle-схем (читай баз) я удовлетворялся встроенным в Quest Software TOAD сравнивателем. Он был неплох, и со своей задачей справлялся. Но пересев в linux, меня ждало разочарование. Ни один из [и так небольшого количества] инструментов не предоставляет даже половины былого комфорта. А именно, элементарное сравнение и мёржинг двух файлов (DDL, SQL, любых исходников — не суть) по несколько тысяч строк в каждом, где каждый отформатирован по-своему а реальных изменений всего-ничего, становится натуральной пыткой.



Поняв тщетность попыток найти необходимый функционал в одном инструменте, я решил поискать костыли в виде сторонних инструментов. А именно — самостоятельные сравниловки и мёржиловки ([visual] compare and merging tools). Перебрав практически все популярные инструменты, выяснилось, что очень немногие (читай почти никто) умели пропускать в сравнении двух текстовых файлов символы конца строки.

Для наглядности, поясню на примере. Допустим у нас есть 2 файла, типично старый и новый.

Старый (ora1.sql):

declare
 nfunctionresult NUMBER(9);
begin

 nFunctionResult :=
  orauser.pack_util.FGetNeededValue (
   in_nSomeParam => somevalue
  );

 dbms_output.put_line('Function result: '||
            nFunctionResult);
exception
 when SOME_EXCEPTION THEN
  orauser.pack_util.PReportError('SOME_EXCEPTION');
 when OTHERS THEN
  orauser.pack_util.PReportError('OTHER exception');
end;


Новый (ora2.sql ):

declare
 nFunctionResult NUMBER(9);
begin
 nFunctionResult := orauser.pack_util.FGetNeededValue (in_nSomeParam => somevalue);
 dbms_output.put_line('Function result: '||nFunctionResult);
exception
 when SOME_EXCEPTION THEN
  orauser.pack_util.PReportError('SOME_EXCEPTION');
 when OTHERS THEN
  orauser.pack_util.PReportError('OTHER');
end;



Как видно пытливым человеческим глазом, суть различий файлов ora1.sql и ora2.sql в одном лишь слове и разном форматировании, но их diff абсолютно нечитаем. Вот классический результат их сравнения:

[bugman@localhost 1]$ diff ora1.sql ora2.sql
2c2
< nfunctionresult NUMBER(9);
---
> nFunctionResult NUMBER(9);
4,11c4,5
<
< nFunctionResult :=
< orauser.pack_util.FGetNeededValue (
< in_nSomeParam => somevalue
< );
<
< dbms_output.put_line('Function result: '||
< nFunctionResult);
---
> nFunctionResult := orauser.pack_util.FGetNeededValue (in_nSomeParam => somevalue);
> dbms_output.put_line('Function result: '||nFunctionResult);
16c10
< orauser.pack_util.PReportError('OTHER exception');
---
> orauser.pack_util.PReportError('OTHER');


Не улучшает ситуацию и ключ «положить на все пробелы» -w:

[bugman@localhost 1]$ diff -wi ora1.sql ora2.sql
4,11c4,5
<
< nFunctionResult :=
< orauser.pack_util.FGetNeededValue (
< in_nSomeParam => somevalue
< );
<
< dbms_output.put_line('Function result: '||
< nFunctionResult);
---
> nFunctionResult := orauser.pack_util.FGetNeededValue (in_nSomeParam => somevalue);
> dbms_output.put_line('Function result: '||nFunctionResult);
16c10
< orauser.pack_util.PReportError('OTHER exception');
---
> orauser.pack_util.PReportError('OTHER');


Не смотря на то, что в оригинальной документации сказано, что пробелами являются и символы конца строки, заведённый мною баг был закрыт в redhat'ной багзилле с пометкой «НЕ БАГ», а сами ГНУшники отписались что мол да, это БАГ, но БАГ документации. А в последнем письме и вовсе пообещались исправить всё на свете.

Из всех переробованных утилит, наиболее удобная оказалась xxdiff (а конкретнее её опция ignore per-hunk whitespaces), но к сожалению, сама утилита не умела работать с юникод файлами, не имела встроенного редактора. чтобы «на лету» править фалы, не предоставляла CLI для возможной интеграции с другими средствами да и к тому же имела не самый приятный интерфейс.

Но ближе всех по смыслу подошла утилитка коммандной строки dwdiff. Эта небольшая обёрточка вокруг diff выдаёт ровно то, что мне нужно — различия на уровне СЛОВ. Но и без ложки дёгтя не обошлось — вывод этой утилиты своеобразен и стандартных опций для приведения разницы в diff формат не имеет:

[bugman@localhost 1]$ dwdiff -P -i ora1.sql ora2.sql
declare
nFunctionResult NUMBER(9);
begin
nFunctionResult := orauser.pack_util.FGetNeededValue (in_nSomeParam => somevalue);
dbms_output.put_line('Function result: '||nFunctionResult);
exception
when SOME_EXCEPTION THEN
orauser.pack_util.PReportError('SOME_EXCEPTION');
when OTHERS THEN
orauser.pack_util.PReportError('OTHER [-exception-]');
end;


С другой стороны, это и не мудрено, ибо стандарнтный patch имеет уровень строки, а в каком виде выводить разницу двух файлов, если они различаются по-дифовски в каждой строке? Этим вопросом я озадачился и списался с автором сей утилиты, коммрадом G.P. Halkes, результатом переписки стал небольшой костыль:

[bugman@localhost 1]$ ./diffwrap.sh ora1.sql ora2.sql diff -i
10c10
< orauser.pack_util.PReportError('OTHER exception');
---
> orauser.pack_util.PReportError('OTHER');


Сей враппер позволяет пользоваться в том числе и с визуальными дифферами, типа kdiff3. Сам скриптик, отточенная версия которого скоро войдёт в состав dwdiff и будет распространятся вместе с ней на радость ленивым кодерам, но кому не терпится уже сейчас опробовать его в работе, никакой магии тут нет:

[bugman@localhost 1]$ cat ~/diffwrap.sh
#!/bin/bash

if [ $# -lt 3 ] ; then
echo "Usage: script.sh "
exit 1
fi

OLD="$1"
NEW="$2"
shift 2

# First create a version of the old file that is formated like
# the new file...
TMP="`mktemp oldconvertXXXXXX`"
dwdiff -P -2 -w '' -x '' "$OLD" "$NEW" > "$TMP"

# ... and then call a diff program to show the changes (per line).
"$@" "$TMP" "$NEW"

# Finally, clean up the temporary file
rm "$TMP"


P. S. Безусловно у описываемой мною проблемы существуют и другие решения. Одно из них - натравить на сравниваемые исходники какой-нить автоформатер. Но мне пришлось отказатся от этого по следующим соображениям: 1) в коллективе выработался некий coding style guide, привести в соответствие с которым любой автоформатер дело не одного даже наверное дня, тратить на это время, чтобы потом разочароваться в выборе пока нет возможности; 2) если откинуть предыдущий пункт, и просто привести копии упоминаемых файлов в какой-нибудь единый формат только для сравнивания, появляется другая задача - как спроецировать найденные различия на оригиналы?
Теги:
Хабы:
Всего голосов 12: ↑10 и ↓2+8
Комментарии2

Публикации

Истории

Ближайшие события

15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань