Git 的 diff 和 apply 命令是代码审查、打补丁以及在分支或仓库间迁移变更的必备工具。git diff 生成显示提交或工作树之间差异的补丁文件,而 git apply 则将补丁应用到另一个代码库。本指南从基本的文件级 diff 到处理复杂场景,结合实际开发工作流,介绍实用用法。

理解 git diff 和补丁文件
git diff 生成统一 diff 格式,逐行描述变更。补丁文件(.patch 或 .diff)是 git diff 的输出,可应用到其他地方。与 git format-patch(每个提交生成一个文件,包含完整提交元数据)不同,git diff 生成单个无提交历史的补丁,非常适合临时变更。
基本用法
为特定文件生成补丁:
git diff Test.java > test.patch
为所有修改的文件生成补丁:
git diff > all-changes.patch
在两个提交之间生成补丁:
git diff <old-commit> <new-commit> > changes.patch
使用 git apply 应用补丁
获得补丁文件后,将其应用到目标仓库:
git apply test.patch
git apply 修改工作树但不创建提交。在干净的工作目录上使用是安全的;如果补丁失败,工作树保持不变。
选项
--check:测试补丁是否能干净应用,不实际应用。--reject:应用能应用的部分,拒绝的块留在.rej文件中。--whitespace=fix:自动修复空白错误。
完整示例:代码审查与打补丁
假设开发者 Alice 在功能分支上做了修改,希望 Bob 审查并应用。
- Alice 修改了两个文件:
src/app.js和src/utils.js。 - 她生成补丁:
git diff > feature.patch - Bob 收到
feature.patch并审查。他可以在不应用的情况下检查 diff:git apply --check feature.patch - 如果干净,Bob 应用:
git apply feature.patch
处理冲突
如果补丁失败,Bob 可以使用 --reject 应用非冲突部分:
git apply --reject feature.patch
这为拒绝的块创建 .rej 文件,Bob 可以手动解决。
对比:git diff vs git format-patch
| 特性 | git diff |
git format-patch |
|---|---|---|
| 包含提交元数据 | 否 | 是(作者、日期、消息) |
| 文件粒度 | 单个文件或全部变更 | 每个提交一个文件 |
| 使用场景 | 临时补丁、代码审查 | 基于邮件的工作流、保留历史 |
| 应用命令 | git apply |
git am |
常见陷阱
- 在脏工作树上应用补丁:始终在干净状态下应用补丁以避免混淆。
- 二进制文件:
git diff适用于文本文件;二进制文件需要--binary标志。 - 空白问题:补丁可能因尾部空白或缩进差异而失败。使用
--whitespace=fix。 - 跨仓库补丁:路径必须匹配;否则使用
-p<n>剥离前导目录组件。 - 大补丁:对于大变更,考虑拆分为更小的逻辑补丁。
性能考虑
git diff 和 git apply 对大多数用例高效。但对于非常大的仓库或深历史,考虑:
- 使用路径参数限制 diff 范围。
- 使用
git diff --cached比较暂存区与上次提交。 - 对于大型补丁,使用
git format-patch和git am,它们能更好地处理二进制文件和编码。
常见问题
.patch 和 .diff 文件有什么区别?
技术上格式相同。.patch 通常用于 git format-patch 的输出,.diff 用于 git diff。两者都是纯文本统一 diff。
可以应用来自另一个仓库的补丁吗?
可以,只要文件路径匹配。如果不匹配,使用 git apply -p1(剥离一个目录级别)或用 -p<n> 调整。
如何撤销已应用的补丁?
使用 git apply -R <patch-file> 反转补丁。或者 git checkout 受影响的文件。
git apply 会创建提交吗?
不会,它只修改工作树。要提交,之后运行 git commit。
如果补丁部分失败怎么办?
使用 --reject 应用非冲突部分。手动解决 .rej 文件中的拒绝块。
在应用补丁之前,试试我们的文本差异工具来直观比较变更。