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

    В былые времена, на 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) если откинуть предыдущий пункт, и просто привести копии упоминаемых файлов в какой-нибудь единый формат только для сравнивания, появляется другая задача - как спроецировать найденные различия на оригиналы?
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 2

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

    Самое читаемое