如何从 git diff 读取输出?

Posted

技术标签:

【中文标题】如何从 git diff 读取输出?【英文标题】:How to read the output from git diff? 【发布时间】:2011-02-01 12:49:17 【问题描述】:

git-diff 的手册页相当长,并且解释了许多对初学者来说似乎不需要的情况。例如:

git diff origin/master

【问题讨论】:

通过使用不同的文本编辑器,行号的 @ ... @ 范围符号变得显而易见。 【参考方案1】:

让我们看一下 git 历史中的高级差异示例(在 commit 1088261f in git.git repository 中):

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
diff --git a/file1 b/file2 形式的“git diff”标头。 a/b/ 文件名是相同的,除非涉及重命名/复制(如我们的例子)。 --git 表示差异是“git”差异格式。

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

相似指数 95%
从 builtin-http-fetch.c 重命名
重命名为 http-fetch.c
告诉我们文件已从 builtin-http-fetch.c 重命名为 http-fetch.c 并且这两个文件 95% 相同(用于检测此重命名)。 扩展差异标头中的最后一行,即
index f3e63d7..e8f44ba 100644
告诉我们给定文件的模式(100644 表示它是普通文件而不是符号链接,并且它不'没有可执行权限位),以及关于 preimage(给定更改之前的文件版本)和 postimage(更改后的文件版本)的缩短哈希。 git am --3way 使用此行来尝试在无法自行应用补丁时进行 3 路合并。

接下来是两行统一的diff header

--- a/builtin-http-fetch.c
+++ b/http-fetch.c
diff -U 结果相比,它在源(原像)和目标(后像)之后没有 from-file-modification-time 和 to-file-modification-time文件名。如果文件已创建,则源为/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-range为-&lt;start line&gt;,&lt;number of lines&gt;,to-file-range为+&lt;start line&gt;,&lt;number of lines&gt;,start-line和number-of-lines均参考大块在原像和后像中的位置和长度,如果没有显示行数,则表示它是1。

如果是 C 文件(如 GNU diff 中的 -p 选项)或其他类型文件的等效文件(如果有),则可选标头显示每次更改发生的 C 函数。

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

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

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

例如,第一个块

     #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_fetchmain 替换,并添加了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;
可能有
\文件末尾没有换行符
存在行(它不在示例差异中)。

作为Donal Fellows said,最好练习阅读现实生活中的例子的差异,在那里你知道你已经改变了什么。

参考资料:

git-diff(1) manpage,“使用 -p 生成补丁”部分 (diff.info)Detailed Unified 节点,“统一格式详细说明”。

【讨论】:

@Geremia:Git 使用基于相似性的启发式算法进行重命名检测......以及git blame -C -C 中的代码移动和复制检测,这就是它的工作原理;这是 Git 设计决策。 git diff 格式只是向用户显示相似(或不相似)索引。 @Geremia:更准确地说,[header] 是最接近的前面,就像在大块之前的函数开头一样。在大多数情况下,这一行包含 diff 块所在的函数的名称。这可以通过将diff gitattribute 设置为差异驱动程序进行配置,并且差异驱动程序包括xfuncname 配置变量。 这是一个出色而全面的答案。几个月前我投了赞成票,但我一直在重新阅读它以巩固我的理解。我想查询一句:“如果不显示行数,则表示为0。”如果更改的行数为零,我会认为只是不会是大块头。使用GNU diff’s unified format,“如果一个块仅包含一行,则仅显示其起始行号”。我想 git 的差异也是如此。 @AnthonyGeoghegan:可能会删除行(则后映像中的行数为 0),或添加行(则前映像中的行数为 0)。 @KasunSiyambalapitiya:Git 使用的统一差异格式(相对于上下文差异格式^[1])不区分修改的行、删除的行和添加的行。 [1]:gnu.org/software/diffutils/manual/html_node/Context-Format.html【参考方案2】:

@@ -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。

大块头

根据您的 git 版本和配置,您还可以在 @@ 行旁边获取代码行,例如func1() 在:

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

这也可以通过普通diff-p 标志获得。

示例:旧文件:

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

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

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

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

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

选择标头的算法如何准确地工作在:Where does the excerpt in the git diff hunk header come from?

【讨论】:

这是为那些还不太明白的人准备的。在@@ -1,6 +1,4 @@ 中,请不要将-1 读为minus one+1 作为plus one,而是在旧(第一个)文件中将其读为line 1 to 6。注意这里- implies "old" 不是减号。顺便说一句,感谢您的澄清……哈哈。 从这个@@ -1,8 +1,9 @@ 是否有可能解释实际发生的事情。例如 1) 添加了一行 2) 修改了一行并添加了一行,以此类推。或者是从另一种方式,因为应该有办法得到它们 git diff correclty 识别代码中哪些行已被修改。请帮助我,因为我真的需要解决这个问题 请注意,这是不正确且非常具有误导性的,上面的答案中的这句话:“+1,4 说这一段对应于第二个文件的第 1 到第 4 行 ”。这是因为+1,4 可能指的是非条件上下文行。相反,“+1,4”实际上意味着“在该文件的“版本”中有4 行(即上下文行)”。理解这些行开头的+-&lt;whitespace&gt; 的含义很重要,因为它适用于对帅哥的解释。一个更直观的例子:youtube.com/watch?v=1tqMjJeyKpw【参考方案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 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 不特定于 git。右边的 3 位数字 (644) 将以八进制读取(值:1、2、4 分别为执行、写入和读取权限),并按顺序对应于所有者(用户),然后是组,然后是其他权限。所以简而言之,644 意味着如果象征性地写成u=rw,og=r,那么每个人都可以读取,但只能由所有者写入。左边的其他数字编码其他信息,比如是否是符号链接等。值可以看到github.com/git/git/blob/…,该位置的第一个1是“常规文件”。【参考方案4】:

默认输出格式(如果您想查找更多信息,它最初来自称为diff 的程序)称为“统一差异”。它基本上包含 4 种不同类型的线条:

上下文行,以单个空格开头, 插入行显示已插入的行,以+ 开头, 删除行,以- 开头,并且 描述更高级别内容的元数据行,例如正在讨论的文件、用于生成差异的选项、文件是否更改了权限等。

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

【讨论】:

+1:关于练习的建议非常好 - 可能比试图痴迷阅读文档要快得多。【参考方案5】:

在我的 Mac 上:

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

或online man diff 在 gnu 上遵循相同路径到相同部分:

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

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

统一输出格式开始 带有两行标题,看起来 像这样:

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

时间戳看起来像`2002-02-21 23:30:39.942229878 -0800' 表示 带小数的日期、时间 秒和时区。

您可以更改标题的内容 使用 `--label=LABEL' 选项;看 *注意别名::.

接下来是一大群人 差异;每个大块显示一个区域 文件不同的地方。统一 格式帅哥是这样的:

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

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

`+' 此处向第一个文件添加了一行。

`-' 此处从第一个文件中删除了一行。

【讨论】:

请注意,git 不会打印 'XXX-FILE-MODIFICATION-TIME' 部分,因为它对版本控制系统没有意义。为了比较文件系统时间戳上的文件,可以起到“穷人”版本控制的作用。【参考方案6】:

从您的问题中不清楚您对差异的哪一部分感到困惑:实际差异或 git 打印的额外标题信息。以防万一,这里是标题的快速概述。

第一行类似于diff --git a/path/to/file b/path/to/file - 显然它只是告诉您这部分差异用于哪个文件。如果您设置布尔配置变量diff.mnemonic prefixab 将更改为更具描述性的字母,例如cw(提交和工作树)。

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

最后,有一行像index 789bd4..0afb621 100644。您可能永远不会关心它,但那些 6 位十六进制数字是该文件的旧 blob 和新 blob 的缩写 SHA1 哈希(blob 是一个 git 对象,用于存储文件内容等原始数据)。当然,100644 是文件的模式——最后三位显然是权限;前三个提供额外的文件元数据信息 (SO post describing that)。

之后,您将使用标准的统一差异输出(就像经典的 diff -U 一样)。它被分成大块 - 大块是包含更改及其上下文的文件的一部分。每个块前面都有一对 ---+++ 行,表示有问题的文件,然后实际差异是(默认情况下)-+ 行两侧的三行上下文,显示删除/添加行。

【讨论】:

++ index 行。确认git hash-object ./file

以上是关于如何从 git diff 读取输出?的主要内容,如果未能解决你的问题,请参考以下文章

如何让 git diff 写入标准输出?

如何使用我喜欢的差异工具/查看器查看“git diff”输出?

获取合并提交的git diff输出结果

如何让 diff 像 git-diff 一样工作?

如何改进 git 的差异突出显示?

在 git-diff 的输出中着色空白