如何从git diff读取输出?


270

的手册页git-diff相当长,其中介绍了许多初学者似乎不需要的情况。例如:

git diff origin/master

1
通过使用其他文本编辑器,行号的@ ... @范围符号变得显而易见。
poseid

哪个文字编辑器?
Jus12

Answers:


488

让我们看一下git历史记录的高级差异示例(在git.git仓库中的提交1088261f中):

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] == '-') {

让我们逐行分析此补丁。

  • 第一行

    差异--git a / builtin-http-fetch.cb / http-fetch.c
    是形式中的“ git diff”标头diff --git a/file1 b/file2。该a/b/,除非重命名/复制参与(如在我们的例子中)的文件名是相同的。该--git是意味着DIFF处于“混帐”的diff格式。

  • 接下来是一个或多个扩展标题行。前三个

    相似指数95%
    从Builtin-http-fetch.c重命名
    重命名为http-fetch.c
    告诉我们,文件已从builtin-http-fetch.c更改为http-fetch.c,并且这两个文件95%相同(用于检测此重命名)。

    扩展的diff标头中的最后一行是
    索引f3e63d7..e8f44ba 100644
    告诉我们给定文件的模式(100644意味着它是普通文件,而不是例如symlink,并且它没有可执行权限位),以及关于preimage(给定更改之前文件的版本)和postimage(更改后的文件版本)。git am --3way如果补丁本身无法应用,则使用此行尝试进行三向合并。

  • 接下来是两行统一的diff头

    --- a / builtin-http-fetch.c
    +++ b / http-fetch.c
    相较于diff -U结果,它在源(原映像)和目标(后映像)文件名之后没有从文件修改时间或到文件修改时间。如果创建文件,则源为/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文件)(例如-pGNU 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中不是)。

正如Donal Fellows所说的,最好是在真实示例中练习阅读差异,在这些示例中您知道自己已更改了什么。

参考文献:


1
@Geremia:Git使用基于相似性的启发式方法进行重命名检测...,还用于中的代码移动和复制检测git blame -C -C,这就是它的工作方式;这是Git的设计决策。git diff格式仅向用户显示相似性(或不相似性)索引。
雅库布·纳伦斯基(JakubNarębski)2016年

1
@Geremia:更确切地说,[header]是最接近的前导,例如在大块之前的函数的开头。在大多数情况下,此行包括diff块所在的函数的名称。这是可以配置的,其中diffgitattribute设置为diff驱动程序,并且diff驱动程序包括xfuncname配置变量。
JakubNarębski16年

1
@AnthonyGeoghegan:可以删除行(然后,后映像中的行数为0)或添加行(然后,前映像中的行数为0)。
JakubNarębski'16

1
@KasunSiyambalapitiya:Git使用的统一的diff格式(与上下文diff格式^ [1]相反)不能区分修改的行,删除的行和添加的行。[1]:gnu.org/software/diffutils/manual/html_node/Context-Format.html
JakubNarębski16年

1
@JakubNarębski:行数默认为1,而不是0。就这么简单。实际上,对于单行文件,它仅显示为“ -1”和/或“ +1”,因为没有上下文可显示。
Guido Flohr

68

@@ -1,2 +3,4 @@ 差异的一部分

这部分花了我一些时间来理解,因此我创建了一个最小的示例。

格式与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() {in:

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

也可以使用-pplain标志获得diff

示例:旧文件:

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

如果我们删除line 6,则差异显示:

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

请注意,这不是正确的行func1:它跳过了行12

这个很棒的功能通常会准确告诉每个块属于哪个函数或类,这对于解释差异非常有用。

有关选择标头的算法的确切工作方式,请参见:git diff hunk标头中的摘录来自何处?


11
这适用于仍然不太了解的任何人。在 @@ -1,6 +1,4 @@请不要读-1minus one+1作为plus one,而不是看这是line 1 to 6在旧的(第一个)文件。注意这里 - implies "old"不要减。顺便说一句,感谢您的澄清...哈希。
dkjain '16

从这个@@ -1,8 +1,9 @@可以解释实际发生了什么。例如1)添加了一行2)修改了一行并添加了一行,依此类推。还是从另一种方式开始,因为应该有一种方法来获得它们,因为git diff correclty标识代码中已修改了哪些行。请帮助我,因为我真的需要解决这个问题
Kasun Siyambalapitiya

请注意,这是不正确的,而且非常容易引起误解,上面的答案中的这一说法:“ +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 不是命令,这意味着它是diff的git版本(不是unix)
  • a/ b/是目录,它们不是真实的。当我们处理相同的文件时,这只是一种方便(在我的情况下,a /在索引中,而b /在工作目录中)
  • 10ff2df..84d4fa2 是这2个文件的Blob ID
  • 100644 是“模式位”,表示这是一个常规文件(不是可执行文件,不是符号链接)
  • --- a/file +++ b/file减号显示a /版本中的行,但b /版本中缺少行;加号显示a /中缺少的行,但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

谢谢。“ 100644是模式位,指示这是一个常规文件(不是可执行文件,不是符号链接)”。“模式位”是Linux中还是Git中的一个概念?
蒂姆(Tim)

@Tim不特定于git。右边的3位数字(644)将以八进制读取(值:1、2、4分别为eXecute,Write和Read权限),并依次对应于所有者(用户),组,其他权限。简而言之,644这意味着如果写成符号u=rw,og=r,对所有人来说都是可读的,但只有所有者才能写。左边的其他数字编码其他信息,例如它是否是符号链接等。可以在github.com/git/git/blob/…中看到,该位置的前1个是“常规文件”。
Patrick Mevzek '19年

15

默认输出格式(最初来自于diff您希望查找更多信息的程序)称为“统一差异”。它实质上包含4种不同类型的线:

  • 上下文线,以单个空格开头,
  • 插入行,显示以开头的已插入行+
  • 缺失系,其与启动-,并
  • 元数据行描述了更高层次的内容,例如正在讨论的文件,用于生成差异的选项,文件是否更改了权限等。

我建议您练习读取文件的两个版本之间的差异,以了解确切的更改。这样,当您看到它时,您就会知道发生了什么。


5
+1:关于实践的建议是一个很好的建议-可能比痴迷于阅读文档的速度要快得多。
卡斯卡贝尔

6

在我的Mac上:

info diff然后选择:Output formats-> Context-> Unified format-> Detailed Unified

或在gnu上的在线man diff遵循相同的路径到达同一部分:

文件:diff.info,节点:详细统一,下一个:示例统一,向上:统一格式

统一格式的详细说明.....................................

统一输出格式以两行标题开头,如下所示:

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

时间戳类似于“ 2002-02-21 23:30:39.942229878 -0800”,用于指示日期,带小数秒的时间和时区。

您可以使用--label = LABEL选项更改标题的内容。请参阅* Note备用名称::。

接下来是一个或多个差异。每个块显示一个文件不同的区域。统一格式的块看起来像这样:

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

这两个文件共有的行以空格字符开头。这两个文件之间实际不同的行在左侧打印列中具有以下指示符之一:

`+'在第一个文件中添加了一行。

`-'这里从第一个文件中删除了一行。


1
请注意,git不会打印“ XXX-FILE-MODIFICATION-TIME”部分,因为它对于版本控制系统没有意义。为了比较文件系统上的文件,timestams可以用作“可怜的人”版本控制。
JakubNarębski2010年

3

从您的问题尚不清楚,您会发现diff的哪一部分令人困惑:实际上是diff,还是git打印出了额外的标头信息。以防万一,这里是标题的快速概述。

第一行是这样的diff --git a/path/to/file b/path/to/file-显然,它只是告诉您diff的这一部分用于什么文件。如果设置boolean config变量diff.mnemonic prefix,则ab将更改为更具描述性的字母,如cw(提交和工作树)。

接下来,有“模式行”-为您提供不涉及更改文件内容的任何更改的描述。这包括新/删除的文件,重命名/复制的文件以及权限更改。

最后,有一行类似index 789bd4..0afb621 100644。您可能永远不会在意它,但是这些6位十六进制数字是此文件的旧Blob和新Blob的缩写SHA1哈希值(Blob是一个git对象,用于存储原始数据(如文件内容))。当然,这100644是文件的模式-后三位显然是权限;前三个给出了额外的文件元数据信息(SO对此进行了描述)。

之后,您就可以进行标准的统一diff输出(就像classic一样diff -U)。它分为多个块-块是文件的一部分,其中包含更改及其上下文。每个大块前面都带有一对---+++线,它们代表所讨论的文件,然后,实际的差异(默认情况下)是(默认情况下)-+线两边的三行上下文,这些行表示已删除/添加的行。


++为该index行。确认git hash-object ./file
西罗Santilli郝海东冠状病六四事件法轮功2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.