312

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

git diff origin/master
4

6 回答 6

562

让我们看一下来自 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] == '-') {

让我们逐行分析这个补丁。

  • 第一行

    diff --git a/builtin-http-fetch.cb/http-fetch.c
    是表单中的“git diff”标题diff --git a/file1 b/file2a/和文件名是相同的b/,除非涉及重命名/复制(就像我们的例子一样)。这--git意味着差异是“git”差异格式。

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

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

    扩展差异标头中的最后一行,即
    索引 f3e63d7..e8f44ba 100644
    告诉我们给定文件的模式(100644意味着它是普通文件而不是符号链接,并且它没有可执行权限位),以及关于原像的缩短哈希(给定更改之前的文件版本)和后像(更改后的文件版本)。git am --3way如果无法自行应用补丁,则此行用于尝试进行 3 路合并。

  • 接下来是两行统一的差异标头

    --- 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/、和作为前缀,分别用于您比较的内容;见git-config(1)b/c/i/w/o/

  • 接下来是一大堆差异;每个大块显示文件不同的一个区域。统一格式的帅哥以行开头

    @@ -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 的格式为-<start line>,<number of lines>,而 to-file-range 的格式为+<start line>,<number of lines>。start-line 和 number-of-lines 都分别指原像和后像中大块的位置和长度。如果未显示行数,则表示它为 1。

可选标头显示每次更改发生的 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;
  • 可能有
    \ 文件末尾没有换行符
    行存在(它不在示例差异中)。

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

参考:

于 2010-03-27T16:28:28.307 回答
90

@@ -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() {

@@ -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 标头中的摘录来自哪里?

于 2015-07-24T16:25:38.790 回答
29

这是一个简单的例子。

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是这 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
于 2014-09-19T10:31:48.063 回答
17

The default output format (which originally comes from a program known as diff if you want to look for more info) is known as a “unified diff”. It contains essentially 4 different types of lines:

  • context lines, which start with a single space,
  • insertion lines that show a line that has been inserted, which start with a +,
  • deletion lines, which start with a -, and
  • metadata lines which describe higher level things like which file this is talking about, what options were used to generate the diff, whether the file changed its permissions, etc.

I advise that you practice reading diffs between two versions of a file where you know exactly what you changed. Like that you'll recognize just what is going on when you see it.

于 2010-03-27T14:33:23.957 回答
7

在我的 Mac 上:

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

或者在线 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...

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

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

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

于 2010-03-27T14:09:19.330 回答
4

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

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

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

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

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

于 2010-03-27T14:57:51.217 回答