告别发整个文件!用 Git Patch 优雅地传递代码变更

19 阅读7分钟

适用人群:有 Git 基础,希望系统掌握 Patch 工作流的开发者
阅读时长:约 12 分钟


一、什么是 Patch 文件?

Patch 文件(.patch)是一种纯文本格式的差异描述文件,记录了代码从旧状态到新状态的所有变更。它本质上是 diff 命令的结构化输出,精确描述了哪些行被增加、删除或保留。

一个典型的 patch 片段如下:

--- a/src/auth/login.py
+++ b/src/auth/login.py
@@ -10,6 +10,7 @@ def login(user, password):
 def login(user, password):
-    if user.name == password:
+    if user and user.name == password:
+        logger.info("Login attempt for: %s", user.name)
     return True

每一行的前缀含义:

前缀含义
空格上下文行,未变更(用于定位)
-被删除的旧内容
+新增的内容

二、为什么需要 Patch 文件?

在很多真实的工程场景中,直接 push 代码并不可行:

  • 没有写权限:你发现了某个开源库的 Bug,但无法直接提交到主仓库
  • 离线/内网环境:机器无法访问外网,无法执行 git pushgit pull
  • 内核/邮件列表式协作:Linux 内核社区长期通过邮件传递 patch,而非 Pull Request
  • 精准的热修复:生产环境只需应用某一个改动,不想引入整个分支的其他变更

Patch 文件在上述场景中都是理想的解决方案:轻量、自描述、可审查、可回滚


三、Patch 文件的完整结构

使用 git format-patch 生成的 patch 文件携带完整的 commit 元信息:

From a1b2c3d4e5f6a1b2c3d4 Mon Sep 17 00:00:00 2001
From: foo <foo@example.com>
Date: Thu, 17 Apr 2026 10:00:00 +0800
Subject: [PATCH] fix: 修复用户登录时的空指针异常

 user 对象为 None 时,直接访问 user.name 会触发 AttributeError。
增加非空判断以修复该问题。

---
 src/auth/login.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/auth/login.py b/src/auth/login.py
index 1234567..abcdefg 100644
--- a/src/auth/login.py
+++ b/src/auth/login.py
@@ -10,5 +10,6 @@ def login(user, password):
-    if user.name == password:
+    if user and user.name == password:
+        logger.info("Login attempt")
     return True

各区块说明:

  • 邮件头区域:包含 commit hash、作者、日期、标题,供 git am 解析还原
  • 正文区域:commit message 的详细描述
  • diffstat:变更文件统计,一眼看出影响范围
  • diff 正文:实际的代码差异,核心内容

四、创建 Patch 文件

Patch 的创建有三种常见方式,适用于不同场景。

4.1 git format-patch(推荐)

该命令将 commit 导出为 patch,完整保留作者、时间、提交信息,是团队协作时的首选。

# 导出最近 1 个 commit
git format-patch HEAD~1

# 导出最近 3 个 commit,每个 commit 单独一个文件
git format-patch HEAD~3
# 输出:
#   0001-feat-add-login-module.patch
#   0002-fix-null-pointer.patch
#   0003-docs-update-readme.patch

# 将多个 commit 合并为单个 patch 文件
git format-patch HEAD~3 --stdout > all-changes.patch

# 导出两个 tag 之间的所有变更
git format-patch v1.0..v1.1

# 导出某个特定 commit
git format-patch -1 a1b2c3d4

# 导出到指定目录
git format-patch HEAD~3 -o ./patches/

4.2 git diff(轻量导出)

该命令只记录代码差异,不包含 commit 元信息,适合快速分享未提交的改动。

# 导出工作区中尚未暂存的改动
git diff > working-changes.patch

# 导出已暂存(git add 后)的改动
git diff --cached > staged-changes.patch

# 导出两个分支之间的差异
git diff main..feature/foo > branch-diff.patch

# 只导出某个文件的差异
git diff HEAD~1 -- src/login.py > login-fix.patch

4.3 diff 命令(不依赖 Git)

当项目没有使用 Git 版本控制时,可以用系统自带的 diff 工具:

# 对比两个文件
diff -u old_login.py new_login.py > login.patch

# 递归对比两个目录(适合整个项目对比)
diff -ruN old_project/ new_project/ > full-project.patch

参数说明:

参数含义
-u统一差异格式(Unified),显示上下文,兼容性最好
-r递归处理子目录
-N将不存在的文件视为空,支持新增文件的 diff

五、应用 Patch 文件

5.1 git am(对应 format-patch,保留 commit 信息)

# 应用单个 patch,完整还原 commit 信息
git am 0001-fix-login.patch

# 按顺序应用目录下所有 patch
git am patches/*.patch

# 使用三路合并(遇到冲突时更智能,强烈推荐加上)
git am -3 0001-fix.patch

# 冲突解决后,继续应用
git am --continue

# 放弃本次 am 操作,回到应用前状态
git am --abort

# 跳过当前冲突的 patch,继续下一个
git am --skip

5.2 git apply(对应 git diff,只改文件不创建 commit)

# 应用 patch
git apply my-changes.patch

# 干运行:检查是否能成功应用,不修改任何文件
git apply --check my-changes.patch

# 忽略空白字符差异(Tab 与空格混用场景很有用)
git apply --ignore-whitespace my-changes.patch

# 反向应用:撤销某个 patch 的改动
git apply -R my-changes.patch

# 应用到暂存区(不修改工作区文件,用于脚本化场景)
git apply --cached my-changes.patch

# 允许部分失败,失败的 hunk 保存为 .rej 文件
git apply --reject my-changes.patch

5.3 patch 命令(Unix 原始工具)

# 应用 patch(-p1 去掉路径的第一层 a/ 或 b/ 前缀)
patch -p1 < my.patch

# 干运行验证
patch --dry-run -p1 < my.patch

# 反向应用
patch -R -p1 < my.patch

-p 参数说明:diff 头中的路径通常是 a/src/foo.py-p1 去掉第一层(a/),使路径变为 src/foo.py,与实际文件位置匹配。如果路径不对会报 can't find file 错误,调整 -p 值即可。


六、从 GitHub 直接获取 Patch

GitHub 支持一个简洁的隐藏技巧:在 commit 或 PR URL 后追加 .patch,即可直接下载对应的 patch 文件。

# 下载某个 commit 的 patch
curl -O https://github.com/foo/bar/commit/a1b2c3d4.patch

# 下载某个 PR 的 patch
curl -O https://github.com/foo/bar/pull/42.patch

# 直接通过管道应用,无需落盘
curl https://github.com/foo/bar/commit/a1b2c3d4.patch | git apply

这在快速测试社区补丁、紧急回滚时非常实用。


七、处理冲突

当 patch 与当前代码有出入时,会产生冲突。处理流程如下:

# 1. 使用 -3 模式应用,触发冲突提示
git am -3 0001-fix.patch
# error: patch failed: src/login.py:25
# Patch failed at 0001 fix: 修复登录问题

# 2. 查看冲突文件
git status

# 3. 手动编辑冲突(与 git merge 冲突处理方式完全一致)
vim src/login.py
# 找到 <<<<<<<、=======、>>>>>>> 标记,保留正确内容

# 4. 标记已解决
git add src/login.py

# 5. 继续应用剩余 patch
git am --continue

如果使用了 --reject 模式,无法自动应用的部分会保存为 .rej 文件:

git apply --reject my.patch
# 检查生成的 .rej 文件
cat src/login.py.rej
# 手动将 .rej 中的内容合并到对应文件后,删除 .rej
rm src/login.py.rej

八、完整实战流程

场景 A:为开源项目提交 Bug Fix

# 1. 克隆项目(假设你已 fork)
git clone https://github.com/foo/bar.git && cd bar

# 2. 创建修复分支
git checkout -b fix/null-pointer-login

# 3. 修改代码并提交
vim src/auth/login.py
git add src/auth/login.py
git commit -m "fix: 修复登录时 user 为 None 导致的 AttributeError"

# 4. 生成 patch 文件
git format-patch main --stdout > fix-null-pointer-login.patch

# 5. 将 patch 发给项目维护者(邮件附件 / Issue 评论)

场景 B:在离线/内网环境同步变更

# ── 在源机器(有代码更新)──
git format-patch origin/main --stdout > changes.patch
# 通过 U 盘 / 内网文件共享传输 changes.patch 到目标机器

# ── 在目标机器(需要同步)──
# 先检查是否能应用
git apply --check changes.patch

# 应用并还原完整 commit
git am changes.patch

场景 C:接收并审核他人的 Patch

# 1. 查看 patch 内容
cat fix-null-pointer-login.patch | less

# 2. 查看变更统计
git apply --stat fix-null-pointer-login.patch

# 3. 干运行确认可应用
git apply --check fix-null-pointer-login.patch

# 4. 应用并保留原始作者信息
git am fix-null-pointer-login.patch

# 5. 验证结果
git log --oneline -3
git show HEAD

九、命令速查表

场景命令
导出最近 1 个 commitgit format-patch HEAD~1
导出多个 commit 为单文件git format-patch HEAD~3 --stdout > all.patch
导出未提交的工作区改动git diff > changes.patch
对比两个文件生成 patchdiff -u old.py new.py > fix.patch
检查能否应用(干运行)git apply --check fix.patch
应用并保留作者/commit 信息git am fix.patch
应用但不创建 commitgit apply fix.patch
使用原始工具应用patch -p1 < fix.patch
撤销一个已应用的 patchgit apply -R fix.patch
冲突解决后继续git am --continue
放弃本次 amgit am --abort
从 GitHub 获取 patchcurl URL/commit/HASH.patch | git apply

十、小结

Patch 文件是 Git 工作流中常被忽视、实则极为强大的工具。掌握它的核心价值在于:

  1. format-patch + am:完整保留 commit 信息,团队协作首选
  2. diff + apply:轻量灵活,适合快速分享未提交的改动
  3. diff + patch:无 Git 依赖,适合运维和跨系统场景
  4. 冲突处理:与 git merge 的冲突解决逻辑一致,学习成本极低

下次当你在贡献开源、传递热修复,或需要跨越网络壁垒同步代码时,不妨让 patch 文件代替你完成这趟旅程。