git-diff的手册页相当长,解释了许多对于初学者来说似乎不必要的情况。例如:
git-diff
git diff origin/master
cig3rfwq1#
让我们来看看git history中的高级diff示例(在git.git仓库中的commit 1088261 f中):
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意味着diff是“git”diff格式。
diff --git a/file1 b/file2
a/
b/
--git
similarity index 95% rename from builtin-http-fetch.c rename to http-fetch.c
告诉我们文件从builtin-http-fetch.c重命名为http-fetch.c,并且这两个文件95%相同(用于检测此重命名)。扩展diff头中的最后一行,即
builtin-http-fetch.c
http-fetch.c
index f3e63d7..e8f44ba 100644
,告诉我们给定文件的模式(100644意味着它是普通文件而不是例如符号链接,并且它没有可执行许可位),关于原像的缩短散列(给定更改之前的文件版本)和postimage(修改后的文件版本)。如果补丁不能自己应用,git am --3way将使用这一行来尝试进行三向合并。
100644
git am --3way
--- 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,则可以将c/,i/,w/和o/分别作为前缀来代替此两行标题中的a/和b/前缀;参见git-config(1)
diff -U
/dev/null
diff.mnemonicPrefix
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分别表示前像和后像中块的位置和长度。如果number of lines未显示,则表示它为1。可选的头文件显示了每次更改发生的C函数,如果它是一个C文件(如GNU diff中的-p选项),或者等效的,如果有的话,对于其他类型的文件。
@@ from-file-range to-file-range @@ [header]
-<start line>,<number of lines>
+<start line>,<number of lines>
-p
比如说,第一块
#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'文件的适当片段看起来像这样:
cmd_http_fetch
main
const char *prefix;
#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中)。
正如多纳尔·费洛斯所说,最好是在现实生活中的例子中练习阅读差异,在那里你知道你改变了什么。
参考文献:
huus2vyu2#
@@ -1,2 +3,4 @@部分差异
@@ -1,2 +3,4 @@
这一部分花了我一段时间来理解,所以我创建了一个最小的示例。格式基本上是相同的diff -u统一的差异。例如:
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,4 @@
-1,6
1 2 3 4 5 6
-的意思是“旧”,因为我们通常将其称为diff -u old new。
-
diff -u old new
+1,4
+表示“新”。我们只有4行而不是6行,因为删除了2行!新的大块只是:
+
1 4 5 6
@@ -11,6 +9,4 @@对于第二个块是类似的:
@@ -11,6 +9,4 @@
11 12 13 14 15 16
11 12 13 16
请注意,11行是新文件的第9行,因为我们已经删除了前一个块上的2行:2和3。
11
Hunk header
根据您的git版本和配置,您还可以在@@行旁边获得一个代码行,例如func1() {:
@@
func1() {
@@ -4,7 +4,6 @@ func1() {
这也可以通过普通diff的-p标志来获得。示例:旧文件:
diff
func1() { 1; 2; 3; 4; 5; 6; 7; 8; 9; }
如果我们删除行6,差异显示:
6
@@ -4,7 +4,6 @@ func1() { 3; 4; 5; - 6; 7; 8; 9;
请注意,这不是func1的正确行:它跳过了1和2行。这个令人敬畏的特性通常会准确地告诉每个块属于哪个函数或类,这对于解释差异非常有用。选择报头的算法如何准确地工作在以下讨论中:Where does the excerpt in the git diff hunk header come from?
func1
1
2
一行块汇总表示法
这是非常罕见的,但考虑:
diff -U0 <(seq -w 16) <(seq -w 16 | sed 's/10/hack/')
其中:
-U0
10
hack
这种情况下的diff输出是:
@@ -10 +10 @@ -10 +hack
因此,我们理解,当有一行变化时,符号被总结为只显示一个数字,而不是m,n对。此行为记录在Todd's answer引用的文档中:如果一个数据块只包含一行,则只显示其起始行号。否则,其行号看起来像start,count。一个空数据块被认为是从该数据块后面的行开始。和单行块添加和删除看起来像这样,删除:
m,n
diff -U0 <(seq -w 16) <(seq -w 16 | grep -Ev '^(10)$')
输出:
@@ -10 +9,0 @@ -10
添加:
$ diff -U0 <(seq -w 16 | grep -Ev '^(10)$') <(seq -w 16)
@@ -9,0 +10 @@ +10
在diff 3.8,Ubuntu 22.10上测试。
rggaifut3#
这里有一个简单的例子。
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
这里有一个解释:
a/ b/
10ff2df..84d4fa2
--- a/file +++ b/file
@@ -1,5 +1,5 @@
@@ -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
gdx19jrr4#
默认的输出格式(如果你想查找更多信息,它最初来自一个名为diff的程序)被称为“统一差异”。它基本上包含4种不同类型的行:
我建议你练习阅读一个文件的两个版本之间的差异,这样你就能准确地知道你修改了什么,这样你就能在看到它的时候识别出发生了什么。
fiei3ece5#
在我的Mac上:info diff然后选择:Output formats -〉Context -〉Unified format -〉Detailed Unified:或者gnu上的在线man diff,遵循相同的路径到相同的部分:文件:diff.info,节点:详细统一,下一页:示例统一,向上:统一格式统一格式详细说明......................................统一的输出格式以两行标题开始,看起来像这样:
info diff
Output formats
Context
Unified format
Detailed Unified
--- 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...
这两个文件共有的行以空格字符开始。两个文件之间实际不同的行在左打印列中具有以下指示符之一:“+”在第一个文件中添加了一行。“-”从第一个文件中删除了一行。
nqwrtyyt6#
从你的问题中,你不清楚你对哪部分差异感到困惑:实际的diff,或者git打印的额外的头信息。以防万一,这里有一个头的快速概述。第一行类似于diff --git a/path/to/file b/path/to/file--显然它只是告诉你这一部分的差异是针对哪个文件的。如果你设置了布尔配置变量diff.mnemonic prefix,a和b将被更改为更具描述性的字母,如c和w(提交和工作树)。接下来是“模式行”--给你描述任何不涉及改变文件内容的更改,包括新建/删除文件,重命名/复制文件,以及权限更改。最后,有一行代码是index 789bd4..0afb621 100644。你可能不会在意它,但这些6位十六进制数字是这个文件的新旧blob的缩写SHA1哈希值(blob是一个git对象,存储原始数据,如文件的内容)。当然,100644是文件的模式-最后三位显然是权限;前三个给予额外的文件元数据信息(SO post describing that)。在此之后,您就可以使用标准的统一diff输出了(就像经典的diff -U一样)。它被分成块-块是文件中包含更改及其上下文的部分。每个块前面都有一对---和+++行,表示正在讨论的文件,则实际的diff是(默认情况下)在-和+行的任一侧上的三行上下文,其示出了移除/添加的行。
diff --git a/path/to/file b/path/to/file
diff.mnemonic prefix
a
b
c
w
index 789bd4..0afb621 100644
---
+++
6条答案
按热度按时间cig3rfwq1#
让我们来看看git history中的高级diff示例(在git.git仓库中的commit 1088261 f中):
让我们逐行分析这个补丁。
是一个格式为
diff --git a/file1 b/file2
的“git diff”头文件。a/
和b/
的文件名是相同的,除非涉及重命名/复制(就像我们的例子)。--git
意味着diff是“git”diff格式。告诉我们文件从
builtin-http-fetch.c
重命名为http-fetch.c
,并且这两个文件95%相同(用于检测此重命名)。扩展diff头中的最后一行,即
,告诉我们给定文件的模式(
100644
意味着它是普通文件而不是例如符号链接,并且它没有可执行许可位),关于原像的缩短散列(给定更改之前的文件版本)和postimage(修改后的文件版本)。如果补丁不能自己应用,git am --3way
将使用这一行来尝试进行三向合并。与
diff -U
结果相比,它在源(前映像)和目标(后映像)文件名之后没有from-file-modification-time和to-file-modification-time。如果文件已创建,则源为/dev/null
;如果文件被删除,目标是/dev/null
。如果您将
diff.mnemonicPrefix
配置变量设置为true,则可以将c/
,i/
,w/
和o/
分别作为前缀来代替此两行标题中的a/
和b/
前缀;参见git-config(1)或
为
@@ 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分别表示前像和后像中块的位置和长度。如果number of lines未显示,则表示它为1。可选的头文件显示了每次更改发生的C函数,如果它是一个C文件(如GNU diff中的
-p
选项),或者等效的,如果有的话,对于其他类型的文件。比如说,第一块
表示
cmd_http_fetch
被main
替换,并添加了const char *prefix;
行。换句话说,在更改之前,'builtin-http-fetch.c'文件的适当片段看起来像这样:
更改后,现在'http-fetch.c'文件的此片段看起来像这样:
行(它不在示例diff中)。
正如多纳尔·费洛斯所说,最好是在现实生活中的例子中练习阅读差异,在那里你知道你改变了什么。
参考文献:
huus2vyu2#
@@ -1,2 +3,4 @@
部分差异这一部分花了我一段时间来理解,所以我创建了一个最小的示例。
格式基本上是相同的
diff -u
统一的差异。例如:
这里我们删除了第2、3、14和15行。输出:
@@ -1,6 +1,4 @@
表示:-1,6
意味着第一个文件的这一部分从第1行开始,总共显示了6行。因此,它显示了第1行到第6行。-
的意思是“旧”,因为我们通常将其称为diff -u old new
。+1,4
意味着第二个文件的这一部分从第1行开始,总共显示了4行。因此,它显示了第1行到第4行。+
表示“新”。我们只有4行而不是6行,因为删除了2行!新的大块只是:
@@ -11,6 +9,4 @@
对于第二个块是类似的:请注意,
11
行是新文件的第9行,因为我们已经删除了前一个块上的2行:2和3。Hunk header
根据您的git版本和配置,您还可以在
@@
行旁边获得一个代码行,例如func1() {
:这也可以通过普通
diff
的-p
标志来获得。示例:旧文件:
如果我们删除行
6
,差异显示:请注意,这不是
func1
的正确行:它跳过了1
和2
行。这个令人敬畏的特性通常会准确地告诉每个块属于哪个函数或类,这对于解释差异非常有用。
选择报头的算法如何准确地工作在以下讨论中:Where does the excerpt in the git diff hunk header come from?
一行块汇总表示法
这是非常罕见的,但考虑:
其中:
-U0
:使用0行上下文10
替换为hack
这种情况下的diff输出是:
因此,我们理解,当有一行变化时,符号被总结为只显示一个数字,而不是
m,n
对。此行为记录在Todd's answer引用的文档中:
如果一个数据块只包含一行,则只显示其起始行号。否则,其行号看起来像start,count。一个空数据块被认为是从该数据块后面的行开始。
和单行块添加和删除看起来像这样,删除:
输出:
添加:
输出:
在diff 3.8,Ubuntu 22.10上测试。
rggaifut3#
这里有一个简单的例子。
这里有一个解释:
--git
不是命令,这意味着它是diff的git版本(不是unix)a/ b/
是目录,它们不是真实的。当我们处理同一个文件时,这只是一种方便(在我的例子中,a/在索引中,B/在工作目录中)10ff2df..84d4fa2
是这两个文件的blob ID100644
是“模式位”,表示这是一个常规文件(不可执行,也不是符号链接)--- a/file +++ b/file
减号表示a/版本中的行,但b/版本中没有;和加号表示a/中缺少但在b/中存在的行(在我的例子中---表示删除的行,+++表示在b/中添加的行,这是工作目录中的文件)@@ -1,5 +1,5 @@
为了理解这一点,最好使用一个大文件;如果你在不同的地方有两个变化,你会得到两个条目,如@@ -1,5 +1,5 @@
;假设你有文件line 1... line 100和删除的line 10,并添加新的line 100-你会得到:gdx19jrr4#
默认的输出格式(如果你想查找更多信息,它最初来自一个名为
diff
的程序)被称为“统一差异”。它基本上包含4种不同类型的行:+
开始,-
开头,以及我建议你练习阅读一个文件的两个版本之间的差异,这样你就能准确地知道你修改了什么,这样你就能在看到它的时候识别出发生了什么。
fiei3ece5#
在我的Mac上:
info diff
然后选择:Output formats
-〉Context
-〉Unified format
-〉Detailed Unified
:或者gnu上的在线man diff,遵循相同的路径到相同的部分:
文件:diff.info,节点:详细统一,下一页:示例统一,向上:统一格式
统一格式详细说明......................................
统一的输出格式以两行标题开始,看起来像这样:
时间戳类似于“2002-02-21 23:30:39.942229878 -0800”,以指示日期、时间(带小数秒)和时区。
您可以使用`--label=LABEL'选项更改标题的内容;请参见 * 注意备用名称::。
接下来是一个或多个大块的差异;每个块显示文件不同的一个区域。统一格式的块看起来像这样:
这两个文件共有的行以空格字符开始。两个文件之间实际不同的行在左打印列中具有以下指示符之一:
“+”在第一个文件中添加了一行。
“-”从第一个文件中删除了一行。
nqwrtyyt6#
从你的问题中,你不清楚你对哪部分差异感到困惑:实际的diff,或者git打印的额外的头信息。以防万一,这里有一个头的快速概述。
第一行类似于
diff --git a/path/to/file b/path/to/file
--显然它只是告诉你这一部分的差异是针对哪个文件的。如果你设置了布尔配置变量diff.mnemonic prefix
,a
和b
将被更改为更具描述性的字母,如c
和w
(提交和工作树)。接下来是“模式行”--给你描述任何不涉及改变文件内容的更改,包括新建/删除文件,重命名/复制文件,以及权限更改。
最后,有一行代码是
index 789bd4..0afb621 100644
。你可能不会在意它,但这些6位十六进制数字是这个文件的新旧blob的缩写SHA1哈希值(blob是一个git对象,存储原始数据,如文件的内容)。当然,100644
是文件的模式-最后三位显然是权限;前三个给予额外的文件元数据信息(SO post describing that)。在此之后,您就可以使用标准的统一diff输出了(就像经典的
diff -U
一样)。它被分成块-块是文件中包含更改及其上下文的部分。每个块前面都有一对---
和+++
行,表示正在讨论的文件,则实际的diff是(默认情况下)在-
和+
行的任一侧上的三行上下文,其示出了移除/添加的行。