使用git apply打补丁
如果收到一个通过电邮发来的补丁,你应该先把它应用到特性分支上进行评估。有两种应用补丁的方法:git apply 或者 git am。使用 apply 命令应用补丁如果收到的补丁文件是用 git diff 或由其它 Unix 的 diff 命令生成,就该用 gitapply 命令来应用补丁。假设补丁文件存在 /tmp/patch-ruby-client.patch,可以这样运行:
$ git apply /tmp/patch-ruby-client.patch
这会修改当前工作目录下的文件,效果基本与运行 patch -p1 打补丁一样,但它更为严格,且不会出现混乱。如果是 git diff 格式描述的补丁,此命令还会相应地添加,删除,重命名文件。当然,普通的 patch 命令是不会这么做的。另外请注意,git apply 是一个事务性操作的命令,也就是说,要么所有补丁都打上去,要么全部放弃。所以不会出现patch 命令那样,一部分文件打上了补丁而另一部分却没有,这样一种不上不下的修订状态。所以总的来说,git apply 要比 patch 严谨许多。因为仅仅是更新当前的文件,所以此命令不会自动生成提交对象,你得手工缓存相应文件的更新状态并执行提交命令。
在实际打补丁之前,可以先用 git apply --check 查看补丁是否能够干净顺利地应用到当前分支中:
$ git apply --check 0001-seeing-if-this-helps-the-gem.patch
error: patch failed: ticgit.gemspec:1
error: ticgit.gemspec: patch does not apply
如果没有任何输出,表示我们可以顺利采纳该补丁。如果有问题,除了报告错误信息之外,该命令还会返回一个非零的状态,所以在 shell 脚本里可用于检测状态。
进阶——使用 am 命令应用补丁
如果贡献者也用 Git,且擅于制作 format-patch 补丁,那你的合并工作将会非常轻松。因为这些补丁中除了文件内容差异外,还包含了作者信息和提交消息。所以请鼓励贡献者用
format-patch 生成补丁。对于传统的 diff 命令生成的补丁,则只能用 git apply 处理。对于 format-patch 制作的新式补丁,应当使用 git am 命令。从技术上来说,git am 能
够读取 mbox 格式的文件。这是种简单的纯文本文件,可以包含多封电邮,格式上用 From加空格以及随便什么辅助信息所组成的行作为分隔行,以区分每封邮件,就像这样:
From 330090432754092d704da8e76ca5c05c198e71a8 Mon Sep 17 00:00:00 2001
From: Jessica Smith <jessica@example.com>
Date: Sun, 6 Apr 2008 10:17:23 -0700
Subject: [PATCH 1/2] add limit to log function
Limit log functionality to the first 20
这是 format-patch 命令输出的开头几行,也是一个有效的 mbox 文件格式。如果有人用git send-email 给你发了一个补丁,你可以将此邮件下载到本地,然后运行 git am 命令来应用这个补丁。如果你的邮件客户端能将多封电邮导出为 mbox 格式的文件,就可以用git am 一次性应用所有导出的补丁。
如果贡献者将 format-patch 生成的补丁文件上传到类似 Request Ticket 一样的任务处理系统,那么可以先下载到本地,继而使用 git am 应用该补丁:
$ git am 0001-limit-log-function.patch
Applying: add limit to log function
你会看到它被干净地应用到本地分支,并自动创建了新的提交对象。作者信息取自邮件头From 和 Date,提交消息则取自 Subject 以及正文中补丁之前的内容。来看具体实例,采
纳之前展示的那个 mbox 电邮补丁后,最新的提交对象为:
$ git log --pretty=fuller -1
commit 6c5e70b984a60b3cecd395edd5b48a7575bf58e0
Author:
Jessica Smith <jessica@example.com>
AuthorDate: Sun Apr 6 10:17:23 2008 -0700
Commit:
Scott Chacon <schacon@gmail.com>
CommitDate: Thu Apr 9 09:19:06 2009 -0700
add limit to log function
Limit log functionality to the first 20
Commit 部分显示的是采纳补丁的人,以及采纳的时间。而 Author 部分则显示的是原作者,以及创建补丁的时间。
有时,我们也会遇到打不上补丁的情况。这多半是因为主干分支和补丁的基础分支相差太远,但也可能是因为某些依赖补丁还未应用。这种情况下,git am 会报错并询问该怎么
做
$ git am 0001-seeing-if-this-helps-the-gem.patch
Applying: seeing if this helps the gem
error: patch failed: ticgit.gemspec:1
error: ticgit.gemspec: patch does not apply
Patch failed at 0001.
When you have resolved this problem run "git am --resolved".
If you would prefer to skip this patch, instead run "git am --skip".
To restore the original branch and stop patching run "git am --abort".
Git 会在有冲突的文件里加入冲突解决标记,这同合并或衍合操作一样。解决的办法也一样,先编辑文件消除冲突,然后暂存文件,最后运行 git am --resolved 提交修正结果:
$ (fix the file)
$ git add ticgit.gemspec
$ git am --resolved
Applying: seeing if this helps the gem
如果想让 Git 更智能地处理冲突,可以用 -3 选项进行三方合并。如果当前分支未包含该补丁的基础代码或其祖先,那么三方合并就会失败,所以该选项默认为关闭状态。一般来
说,如果该补丁是基于某个公开的提交制作而成的话,你总是可以通过同步来获取这个共同祖先,所以用三方合并选项可以解决很多麻烦:
$ git am -3 0001-seeing-if-this-helps-the-gem.patch
Applying: seeing if this helps the gem
error: patch failed: ticgit.gemspec:1
error: ticgit.gemspec: patch does not apply
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...
No changes -- Patch already applied.
像上面的例子,对于打过的补丁我又再打一遍,自然会产生冲突,但因为加上了 -3 选项,所以它很聪明地告诉我,无需更新,原有的补丁已经应用。
对于一次应用多个补丁时所用的 mbox 格式文件,可以用 am 命令的交互模式选项 -i,这样就会在打每个补丁前停住,询问该如何操作:
$ git am -3 -i mbox
Commit Body is:
--------------------------
seeing if this helps the gem
--------------------------
Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all
在多个补丁要打的情况下,这是个非常好的办法,一方面可以预览下补丁内容,同时也可以有选择性的接纳或跳过某些补丁。
打完所有补丁后,如果测试下来新特性可以正常工作,那就可以安心地将当前特性分支合并到长期分支中去了。