代码审查、问题报告以及通过邮件往来的补丁,大多采用 unified diff 格式。git diff、diff -u 和 GitHub 的“raw diff”用的都是这种格式。初看符号很多,其实规则很简单。
整体结构
@@ -1,5 +1,6 @@ def greet(name):- print("Hello " + name)+ message = f"Hello, {name}!"+ print(message) def main(): greet("world")
从上到下依次是 git 扩展头、文件头(---、+++)和 hunk(以 @@ 开头的块)。一个文件可以有多个 hunk,一个补丁也可以包含多个文件。
文件头:--- 和 +++
--- a/greet.py是修改前的文件,+++ b/greet.py是修改后的文件。- git 会在路径前加上
a/、b/。所以用patch命令应用补丁时,要用-p1去掉第一级路径。 - 新建的文件显示为
--- /dev/null,删除的文件显示为+++ /dev/null。 - GNU diff 有时会在文件名后附上修改时间。
hunk 头 @@ -a,b +c,d @@
@@ -1,5 +1,6 @@ 的意思是:显示“修改前文件从第 1 行起的 5 行,以及修改后文件从第 1 行起的 6 行”。
- 第一个数字是起始行号,逗号后面是该 hunk 所占的行数(包括上下文行)。
- 行数为 1 时,连同逗号一起省略。
@@ -7 +7 @@表示第 7 行这一行的 hunk。 - 行数为 0 表示该侧没有行。
@@ -3,0 +4,2 @@表示在原文件第 3 行之后新插入了两行。与空文件比较时会出现-0,0。 - 第二个
@@后面有时会带上函数名之类的文字(@@ -40,7 +40,8 @@ def main():)。这是提示该 hunk 位于哪个函数内的参考信息,应用补丁时会被忽略。
亲自数一下:上例中的上下文行(以空格开头的行)有 def greet(name):、一个空行、def main():、greet("world") 共 4 行。修改前的行数是 4 行上下文加 1 行 -,共 5 行;修改后的行数是 4 行上下文加 2 行 +,共 6 行,所以 hunk 头是 -1,5 +1,6。
正文中的三种行
| 首字符 | 含义 |
|---|---|
| 空格 | 两侧都有的上下文行 |
- | 只在修改前存在的行(删除) |
+ | 只在修改后存在的行(新增) |
默认上下文是变更前后各 3 行(可以像 git diff -U5 这样修改)。上下文行用于在应用补丁时找到正确的位置。即使行号稍有偏差,只要上下文吻合,patch 就会平移位置(offset)后应用。
修改了一行时,会表现为一行 - 紧接着一行 +。unified 格式没有专门表示“修改”的符号,所以阅读的诀窍是把连续的 - 块和其下的 + 块上下对照着看。
\ No newline at end of file
@@ -1,2 +1,2 @@ host=example.com-port=8080\ No newline at end of file+port=8080
这表示紧挨着的上一行是文件的最后一行,并且末尾没有换行符。在上例中内容相同,只是修改后的文件多了结尾换行符。详情请参阅 换行符与看不见的差异。
git 扩展头
diff --git a/路径 b/路径 之后的几行是 git 特有的附加信息。
index 3b18e51..a6f4c2d 100644—— 修改前后文件内容哈希的前几位,以及文件模式(100644 为普通文件,100755 为可执行文件)new file mode 100644/deleted file mode 100644—— 文件新建、删除old mode 100644/new mode 100755—— 只改变了执行权限(可能不带任何内容 hunk)similarity index 90%、rename from 旧路径、rename to 新路径—— 重命名(表示内容有 90% 相同)Binary files a/logo.png and b/logo.png differ—— 二进制文件只显示这一行,不显示内容
审查时的实用技巧
- 先看 hunk 头中行数的差值,就能估计变更的规模。
-10,7 +10,30表示增加了 23 行。 - 想过滤掉只改了空白的 hunk 时,使用
git diff -w。在查看缩进整理与实际修改混在一起的提交时尤其有用(忽略空白选项)。 - 想看行内具体改了什么,可以用
git diff --word-diff,或者把内容放进像本站这样支持行内高亮的工具。 - 应用补丁前,先用
git apply --check 文件.patch确认能否干净地应用。如果不在 git 仓库中,则用patch -p1 --dry-run < 文件.patch。
在本工具中试一试
文本对比工具 的合并视图按与此格式相同的顺序,先显示 - 块再显示 + 块,并再次高亮行内改动的词。点击结果栏中的 patch 按钮即可复制标准的 unified diff 文本,旁边的保存按钮会下载 diff.patch 文件。在关闭所有忽略选项的状态下生成的 patch,可以用 patch -p1 或 git apply 应用到原文件上。