Как читать вывод из git diff?

Страница руководства для git-diff довольно длинная и объясняет многие случаи, которые, как представляется, не нужны новичкам. Например:

git diff origin/master

Ответ 1

Давайте рассмотрим пример расширенного diff из git history (в зафиксировать 1088261f в репозитории git.git):

diff --git a/builtin-http-fetch.c b/http-fetch.c
similarity index 95%
rename from builtin-http-fetch.c
rename to http-fetch.c
index f3e63d7..e8f44ba 100644
--- a/builtin-http-fetch.c
+++ b/http-fetch.c
@@ -1,8 +1,9 @@
 #include "cache.h"
 #include "walker.h"

-int cmd_http_fetch(int argc, const char **argv, const char *prefix)
+int main(int argc, const char **argv)
 {
+       const char *prefix;
        struct walker *walker;
        int commits_on_stdin = 0;
        int commits;
@@ -18,6 +19,8 @@ int cmd_http_fetch(int argc, const char **argv, const char *prefix)
        int get_verbosely = 0;
        int get_recover = 0;

+       prefix = setup_git_directory();
+
        git_config(git_default_config, NULL);

        while (arg < argc && argv[arg][0] == '-') {

Давайте проанализируем это исправление по строкам.

  • Первая строка

    diff --git a/builtin-http-fetch.c b/http-fetch.c
    представляет собой заголовок "git diff" в форме diff --git a/file1 b/file2. Имена файлов a/ и b/ одинаковы, если не используется переименование/копирование (как в нашем случае). --git означает, что diff находится в формате "git".
  • Далее следуют одна или несколько строк расширенного заголовка. Первые три

    similarity index 95%
    rename from builtin-http-fetch.c
    rename to http-fetch.c
    говорят нам, что файл был переименован из builtin-http-fetch.c в http-fetch.c и что эти два файла идентичны на 95% (что было использовано для обнаружения этого переименования).

    Последняя строка в расширенный заголовок diff, который
    index f3e63d7..e8f44ba 100644
    сообщает нам о режиме заданного файла (100644 означает, что он является обычным файлом, а не, например, символической ссылкой, и что он не имеет исполняемого бита разрешения) и об укороченном хеше прообраза ( версия файла перед данным изменением) и postimage (версия файла после изменения). Эта строка используется git am --3way, чтобы попытаться выполнить трехстороннее слияние, если патч не может быть применен сам.

  • Следующий двухстрочный унифицированный заголовок diff

    --- a/builtin-http-fetch.c
    +++ b/http-fetch.c
    По сравнению с результатом diff -U у него нет времени на модификацию файла или время изменения файла после источника (прообраза) и адресата (postimage). Если файл был создан, источник /dev/null; если файл был удален, цель равна /dev/null.
    Если вы установили переменную конфигурации diff.mnemonicPrefix в значение true, вместо префиксов a/ и b/ в этом двухстрочном заголовке вместо c/, i/, w/ и o/ в качестве префиксов, соответственно тому, что вы сравниваете; см. git -config (1)
  • Далее следуют один или несколько разностей различий; каждый кусок показывает одну область, где файлы отличаются. Унифицированные форматы hunks начинаются с строки типа

    @@ -1,8 +1,9 @@
    или
    @@ -18,6 +19,8 @@ int cmd_http_fetch(int argc, const char **argv, ...
    Это в формате @@ from-file-range to-file-range @@ [header]. Диапазон from-file находится в форме -<start line>,<number of lines>, а to-file-range - +<start line>,<number of lines>. Как начальная строка, так и количество строк указывают на положение и длину куска в прообразе и постимаже соответственно. Если число строк не показано, это означает, что оно равно 0.

    Дополнительный заголовок показывает функцию C, где происходит каждое изменение, если это файл C (например, -p в GNU diff) или эквивалент, если таковой имеется, для других типов файлов.

  • Далее следует описание того, где файлы отличаются. Линии, общие для обоих файлов, начинаются с символа пробела. Строки, которые фактически отличаются между двумя файлами, имеют один из следующих индикаторных символов в левой колонке печати:

    • '+' - В первый файл добавлена ​​строка.
    • '-' - Строка была удалена из первого файла.


    Так, например, первый фрагмент

     #include "cache.h"
     #include "walker.h"
    
    -int cmd_http_fetch(int argc, const char **argv, const char *prefix)
    +int main(int argc, const char **argv)
     {
    +       const char *prefix;
            struct walker *walker;
            int commits_on_stdin = 0;
            int commits;
    

    означает, что cmd_http_fetch был заменен на main и что была добавлена ​​строка const char *prefix;.

    Другими словами, перед изменением соответствующий фрагмент файла builtin-http-fetch.c выглядел следующим образом:

    #include "cache.h"
    #include "walker.h"
    
    int cmd_http_fetch(int argc, const char **argv, const char *prefix)
    {
           struct walker *walker;
           int commits_on_stdin = 0;
           int commits;
    

    После изменения этот фрагмент теперь выглядит следующим образом: http/fetch.c:

    #include "cache.h"
    #include "walker.h"
    
    int main(int argc, const char **argv)
    {
           const char *prefix;
           struct walker *walker;
           int commits_on_stdin = 0;
           int commits;
    
  • Может присутствовать строка

    \ No newline at end of file
    (это не пример diff).

Как Donal Fellows сказал, лучше всего практиковать чтение разностей на реальных примерах, где вы знаете, что изменили.

Литература:

Ответ 2

@@ -1,2 +3,4 @@ часть различий

Эта часть заняла у меня некоторое время, чтобы понять, поэтому я создал минимальный пример.

Формат в основном совпадает с унифицированным diff diff -u.

Например:

diff -u <(seq 16) <(seq 16 | grep -Ev '^(2|3|14|15)$')

Здесь мы удалили строки 2, 3, 14 и 15. Вывод:

@@ -1,6 +1,4 @@
 1
-2
-3
 4
 5
 6
@@ -11,6 +9,4 @@
 11
 12
 13
-14
-15
 16

@@ -1,6 +1,4 @@ означает:

  • -1,6 означает, что этот фрагмент первого файла начинается со строки 1 и показывает всего 6 строк. Поэтому он показывает строки с 1 по 6.

    1
    2
    3
    4
    5
    6
    

    - означает "старый", так как мы обычно называем его diff -u old new.

  • +1,4 означает, что этот фрагмент второго файла начинается со строки 1 и показывает всего 4 строки. Поэтому он показывает строки с 1 по 4.

    + означает "новый".

    У нас только 4 строки вместо 6, потому что 2 строки были удалены! Новый кусок просто:

    1
    4
    5
    6
    

@@ -11,6 +9,4 @@ для второго блока аналогичен:

  • в старом файле у нас есть 6 строк, начиная со строки 11 старого файла:

    11
    12
    13
    14
    15
    16
    
  • в новом файле у нас есть 4 строки, начиная с 9 строки нового файла:

    11
    12
    13
    16
    

    Обратите внимание, что строка 11 является 9-й строкой нового файла, поскольку мы уже удалили 2 строки в предыдущем блоке: 2 и 3.

Заголовок куска

В зависимости от вашей версии и конфигурации git вы также можете получить строку кода рядом со строкой @@, например, func1() { в:

@@ -4,7 +4,6 @@ func1() {

Это также можно получить с флагом -p обычного diff.

Пример: старый файл:

func1() {
    1;
    2;
    3;
    4;
    5;
    6;
    7;
    8;
    9;
}

Если мы удалим строку 6, разница покажет:

@@ -4,7 +4,6 @@ func1() {
     3;
     4;
     5;
-    6;
     7;
     8;
     9;

Обратите внимание, что это неправильная строка для func1: пропущены строки 1 и 2.

Эта удивительная функция часто говорит точно, к какой функции или классу относится каждый кусок, что очень полезно для интерпретации различий.

Как именно работает алгоритм выбора заголовка, обсуждается на: Откуда взята выдержка из заголовка git diff?

Ответ 3

Вот простой пример.

diff --git a/file b/file 
index 10ff2df..84d4fa2 100644
--- a/file
+++ b/file
@@ -1,5 +1,5 @@
 line1
 line2
-this line will be deleted
 line4
 line5
+this line is added

Здесь объяснение (см. подробности здесь).

  • --git не является командой, это означает, что git версия diff (не unix)
  • a/ b/ - это каталоги, они не являются реальными. это просто удобство, когда мы имеем дело с одним и тем же файлом (в моем случае a/находится в индексе и b/находится в рабочем каталоге)
  • 10ff2df..84d4fa2 - это идентификаторы blob этих двух файлов.
  • 100644 - это "бит режима", указывающий, что это обычный файл (не исполняемый, а не символический).
  • --- a/file +++ b/file знаки минус показывают строки в версии a/, но отсутствуют в версии b/; и плюс показывает строки, отсутствующие в /, но присутствующие в b/(в моем случае --- означает, что удаленные строки и +++ означают добавленные строки в b/, и этот файл в рабочем каталоге)
  • @@ -1,5 +1,5 @@, чтобы понять это, лучше работать с большим файлом; если у вас есть два изменения в разных местах, вы получите две записи, такие как @@ -1,5 +1,5 @@; предположим, что у вас есть строка строки1... line100 и удалена строка10 и добавлена ​​новая строка100 - вы получите:
@@ -7,7 +7,6 @@ line6
 line7
 line8
 line9
-this line10 to be deleted
 line11
 line12
 line13
@@ -98,3 +97,4 @@ line97
 line98
 line99
 line100
+this is new line100

Ответ 4

Формат вывода по умолчанию (который первоначально поступает из программы, известной как diff, если вы хотите найти дополнительную информацию) известен как "унифицированный diff". Он содержит по существу 4 разных типа линий:

  • контекстные строки, начинающиеся с одного пробела,
  • в которых показана строка, которая была вставлена, начиная с +,
  • начинающиеся с - и
  • линии метаданных, которые описывают вещи более высокого уровня, такие как файл, о котором идет речь, какие параметры были использованы для генерации diff, независимо от того, изменил ли файл свои разрешения и т.д.

Я советую вам практиковать чтение разностей между двумя версиями файла, где вы точно знаете, что изменили. Таким образом вы узнаете, что происходит, когда вы это видите.

Ответ 5

На моем mac:

info diff затем выберите: Output formatsContextUnified formatDetailed Unified:

Или онлайн man diff на gnu по тому же пути в том же разделе:

Файл: diff.info, Node: Подробный Unified, Next: Пример Unified, Up: Унифицированный формат

Подробное описание унифицированного формата......................................

Запускается унифицированный формат вывода с двухстрочным заголовком, который выглядит например:

 --- FROM-FILE FROM-FILE-MODIFICATION-TIME
 +++ TO-FILE TO-FILE-MODIFICATION-TIME

Временная метка выглядит как `2002-02-21 23: 30: 39.942229878 -0800 'для обозначения дата, время с дробным секунд и часовой пояс.

Вы можете изменить содержимое заголовка с опцией `--label = LABEL '; видеть * Примечание Альтернативные имена::.

Далее следуют один или несколько различия; каждый кусок показывает одну область где файлы отличаются. унифицированный формат hunks выглядит следующим образом:

 @@ FROM-FILE-RANGE TO-FILE-RANGE @@
  LINE-FROM-EITHER-FILE
  LINE-FROM-EITHER-FILE...

Линии, общие для обоих файлов начните с символа пробела. линии, которые фактически различаются между два файла имеют одно из следующих индикаторы в левом отпечатке колонка:

`+ "     В первый файл была добавлена ​​строка.

`-"      Строка была удалена здесь из первого файла.

Ответ 6

В управлении версиями различия между двумя версиями представлены в том, что называется "diff" (или, как синоним, "патч"). Давайте подробно рассмотрим такую разницу - и научимся ее читать.

Посмотрите на вывод diff. Основываясь на этом выводе, мы будем понимать вывод git diff.

enter image description here

Сравненные файлы a/b

Наш diff сравнивает два элемента друг с другом: элемент A и элемент B. В большинстве случаев A и B будут одним и тем же файлом, но в разных версиях. Хотя не очень часто, diff может также сравнить два совершенно не связанных между собой файла, чтобы показать, как они отличаются. Чтобы понять, что на самом деле сравнивается, вывод diff всегда начинается с объявления, какие файлы представлены буквами "A" и "B".

Метаданные файла

Метаданные файла, показанные здесь, являются очень технической информацией, которая вам, вероятно, никогда не понадобится на практике. Первые два числа представляют хэши (или, проще говоря: "идентификаторы") наших двух файлов: Git сохраняет каждую версию не только проекта, но и каждого файла как объект. Такой хеш идентифицирует объект файла в определенной ревизии. Последнее число является внутренним идентификатором режима файла (100644 - это просто "обычный файл", в то время как 100755 указывает на исполняемый файл, а 120000 представляет символическую ссылку).

Маркеры для а/б

Далее в выходных данных фактические изменения будут помечены как поступающие от A или B. Чтобы отделить их друг от друга, каждому из A и B назначается символ: для версии A это знак минус ("-") и для версии B используется знак плюс ("+").

ломоть

Разница не показывает полный файл от начала до конца: вы не захотите видеть все в файле с 10 000 строк, когда изменились только 2 строки. Вместо этого он показывает только те части, которые были фактически изменены. Такая часть называется "кусок" (или "кусок"). В дополнение к фактически измененным строкам в чанке также есть немного "контекста": некоторые (неизменные) строки до и после модификации, чтобы вы могли лучше понять, в каком контексте произошло это изменение.

Заголовок куска

Каждому из этих кусков предшествует заголовок, заключенный между двумя маркерами @@. Git использует заголовок, чтобы сказать вам, какие строки были затронуты. В нашем случае следующие строки представлены в первом фрагменте:

  • Из файла A (представленного "-") извлекаются 6 строк, начиная со строки №. 34

  • Из файла B (обозначенного "+") отображаются 8 строк, также начиная со строки №. 34

Текст после закрывающей пары "@@" направлен на уточнение контекста, опять же: Git пытается отобразить имя метода или другую контекстную информацию о том, откуда этот фрагмент был взят в файле. Однако это сильно зависит от языка программирования и работает не во всех сценариях.

изменения

К каждой измененной строке добавляется символ "+" или "-". Как объяснено, эти символы помогают понять, как именно выглядят версии A и B: строка, начинающаяся со знака "-", идет от A, а строка со знаком "+" - от B. В большинстве случаев Git выбирает A и B таким образом, что вы можете думать о A / - как о "старом" контенте, а B/+ как о "новом" контенте.

Давайте посмотрим на наш пример:

  • Изменение № 1 содержит две строки с добавлением "+". Поскольку для этих строк не было аналога в (строк с "-" нет), это означает, что эти строки были добавлены.

  • Изменение № 2 как раз наоборот: в у нас есть две строки, помеченные знаком "-". Однако B не имеет эквивалента (без "+" строк), что означает, что они были удалены.

  • В изменении №3, наконец, некоторые строки были фактически изменены: две строки "-" были изменены, чтобы выглядеть как две строки "+" ниже.

Источник

Ответ 7

Неясно из вашего вопроса, какая часть различий вы считаете запутанной: фактический diff или дополнительная информация заголовка git печатает. На всякий случай, здесь краткий обзор заголовка.

Первая строка - это что-то вроде diff --git a/path/to/file b/path/to/file - очевидно, это просто говорит вам, для какого файла используется этот раздел diff. Если вы установите логическую конфигурационную переменную diff.mnemonic prefix, теги a и b будут изменены на более дескриптивные буквы, такие как c и w (commit and work tree).

Далее появятся строки "линии режима" - строки, дающие вам описание любых изменений, которые не связаны с изменением содержимого файла. Это включает в себя новые/удаленные файлы, переименованные/скопированные файлы и изменения прав доступа.

Наконец, есть строка типа index 789bd4..0afb621 100644. Вы, вероятно, никогда не будете заботиться об этом, но эти шестизначные шестнадцатеричные числа являются сокращенными хэшами SHA1 старых и новых блоков для этого файла (blob - это объект git, хранящий необработанные данные, такие как содержимое файла). И, конечно, 100644 - это режим файла - последние три цифры, очевидно, являются разрешениями; первые три дают дополнительную информацию о метаданных файла (сообщение SO, описывающее это).

После этого вы подключаетесь к стандарту унифицированного вывода diff (как и классический diff -U). Он разбивается на ханки - кусок - это раздел файла, содержащий изменения и их контекст. Каждому столбцу предшествует пара строк --- и +++, обозначающая данный файл, тогда фактический diff (по умолчанию) представляет собой три строки контекста по обе стороны строк - и +, показывающие удаленные/добавленные строки.