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

272

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

git diff origin/master
poseid
источник
1
с помощью другого текстового редактора обозначения диапазона @ ... @ для номеров строк стали очевидными.
Posid
Какой текстовый редактор?
Jus12

Ответы:

489

Давайте рассмотрим пример расширенного diff из истории git (в коммите 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.cb / http-fetch.c
    это заголовок "git diff" в форме diff --git a/file1 b/file2. a/И b/имена файлов являются одинаковыми , если переименовать / копировать не участвует (как в нашем случае). Это --gitозначает, что diff находится в формате «git» diff.

  • Далее идет одна или несколько расширенных строк заголовка. Первые три

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

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

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

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

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

    @@ -1,8 +1,9 @@
    или
    @@ -18,6 +19,8 @@ int cmd_http_fetch (int argc, const char ** argv, ...
    Это в формате @@ from-file-range to-file-range @@ [header]. Диапазон от файла находится в форме -<start line>,<number of lines>, и диапазон файла - +<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;
    
  • Там может быть

    \ Нет новой строки в конце файла
    строка присутствует (это не в примере diff).

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

Ссылки:

Якуб Наребски
источник
1
@ Geremia: Git использует эвристику на основе сходства для обнаружения переименования ... а также для перемещения кода и обнаружения копирования git blame -C -C, вот как это работает; это Git дизайнерское решение. Формат git diff просто показывает индекс сходства (или различий) для пользователя.
Якуб Наренбский
1
@Geremia: Точнее, [header]ближайший предшествующий, как с началом функции, которая предшествует ломоть. В большинстве случаев эта строка содержит имя функции, в которой находится фрагмент diff. Это настраивается с помощью diffатрибута gitattribute для драйвера diff и драйвера diff, включая xfuncnameпеременную конфигурации.
Якуб Наребски
1
@AnthonyGeoghegan: строки могут быть удалены (тогда количество строк в изображении равно 0) или добавлено (тогда количество строк в изображении равно 0).
Якуб Наребски
1
@KasunSiyambalapitiya: унифицированный формат diff, который использует Git (в отличие от формата diff контекста ^ [1]), не различает измененную строку и удаленную и добавленную строку. [1]: gnu.org/software/diffutils/manual/html_node/Context-Format.html
Якуб Наребски,
1
@ JakubNarębski: Количество строк по умолчанию равно 1, а не 0. Это так просто. На практике он отображается только как «-1» и / или «+1» для однострочных файлов, поскольку отсутствует контекст для отображения.
Гвидо Флор
68

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

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

Формат в основном такой же, как diff -uунифицированный diff.

Например:

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.

Hunk header

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

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

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

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

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

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

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

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

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

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

Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
источник
11
Это для тех, кто еще не совсем понял. В @@ -1,6 +1,4 @@pls не читайте -1как minus oneили +1как plus oneвместо этого читайте это как line 1 to 6в старом (первом) файле. Обратите внимание, здесь - implies "old"не минус. Кстати, спасибо за разъяснения ... хааш.
dkjain
Исходя из этого, @@ -1,8 +1,9 @@ можно ли интерпретировать то, что на самом деле произошло. например, 1) добавлена ​​одна строка 2) изменена одна строка, добавлена ​​одна строка и т. д. Или это по-другому, так как должен быть способ получить их, поскольку git diff correclty определяет, какие строки были изменены в коде. Пожалуйста, помогите мне, так как мне действительно нужно разобраться с этим
Касун Сиямбалапития
Обратите внимание, что это неверно и очень вводит в заблуждение, это утверждение в ответе выше: « +1,4говорит, что этот фрагмент соответствует строки 1-4 второго файла ». Это связано с тем, что +1,4могут ссылаться на не зависящие от контекста строки. Скорее, на +1,4самом деле « » означает, что « в этой« версии »файла есть 4строки (т.е. строки контекста) . Важно понимать значение +, -и <whitespace>в начале этих строк, поскольку это относится к интерпретации фрагментов. Более наглядный пример: youtube.com/watch?v=1tqMjJeyKpw
Дамилола Оловуокер
23

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

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-объектов этих 2 файлов
  • 100644 это «биты режима», указывающие, что это обычный файл (не исполняемый и не символическая ссылка)
  • --- a/file +++ b/fileзнаки минус показывают строки в версии a /, но отсутствуют в версии b /; и знаки плюс показывают строки, отсутствующие в /, но присутствующие в b / (в моем случае --- означает удаленные строки, а +++ означает добавленные строки в b / и этот файл в рабочем каталоге)
  • @@ -1,5 +1,5 @@чтобы понять это, лучше работать с большим файлом; если у вас есть два изменения в разных местах, вы получите две записи, как @@ -1,5 +1,5 @@; Предположим, у вас есть файл line1 ... line100 и удаленный line10 и добавьте новый line100 - вы получите:
@@ -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
irudyak
источник
Спасибо. «100644 - это биты режима, указывающие, что это обычный файл (не исполняемый и не символическая ссылка)». Является ли «биты режима» концепцией в Linux или только в Git?
Тим
@Tim Не относится к мерзавцу. Правые 3 цифры ( 644) должны быть прочитаны в восьмеричном виде (значения: 1, 2, 4 соответственно разрешения eXecute, Write и Read) и соответствуют в этом порядке Владельцу (Пользователь), затем Группе, затем Другим разрешениям. Таким образом, короче говоря, 644это означает, что если оно написано символически u=rw,og=r, оно доступно для чтения всем, но доступно только для владельца. Другие цифры слева кодируют другую информацию, например, если это символическая ссылка и т. Д. Значения можно увидеть github.com/git/git/blob/… , первая 1 в этой позиции - «обычный файл».
Патрик Мевзек
15

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

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

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

Donal Fellows
источник
5
+1: предложение о практике очень хорошее - вероятно, гораздо быстрее, чем пытаться одержимо читать документацию.
Каскабель
6

На моем Mac:

info diffзатем выберите: Output formats-> Context-> Unified format-> Detailed Unified:

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

Файл: diff.info, узел: подробный унифицированный, следующий: пример унифицированный, вверх: унифицированный формат

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

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

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

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

Вы можете изменить содержимое заголовка с помощью опции --lala = LABEL; см. * Примечание к альтернативным именам ::.

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

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

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

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

`- 'Здесь удалена строка из первого файла.

stefanB
источник
1
Обратите внимание, что git не печатает часть «XXX-FILE-MODIFICATION-TIME», так как это не имеет смысла для системы контроля версий. Для сравнения файлов в файловой системе временные метки могут функционировать как контроль версий «бедняков».
Якуб Наребски
3

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

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

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

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

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

Cascabel
источник
++ для indexстроки. Подтвержденоgit hash-object ./file
Ciro Santilli 法轮功 冠状 病 六四 事件 法轮功