使用diff命令打补丁详解

对于开源源码修改过程中的必经阶段:对源码打补丁,总是不够精通,搜索了补丁的原理的详细过程,如下:


在移植或版本升级过程中,手动比对(用比对工具)转换是很费力的事情,特别是发生变化的文件非常多的情况下,“制作补丁、打补丁”可以简化这个过程。主要用到diff和patch。在这里不会把man在线文档上所有的选项都介绍一下,那样也没有必要。在99%的时间里,我们只会用到几个选项。

1、diff

--------------------

NAME

      diff - find differences between two files

SYNOPSIS

      diff [options] from-file to-file

      from_file to_file can be a directory.

--------------------

简单的说,diff的功能就是用来比较两个文件的不同,然后记录下来,也就是所谓的diff补丁。
语法格式:diff 【选项】 源文件(夹) 目的文件(夹),就是要给源文件(夹)打个补丁,使之变成目的文件(夹),术语也就是“升级”。
下面介绍三个最为常用选项:

-r 是一个递归选项,设置了这个选项,diff会将两个不同版本源代码目录中的所有对应文件全部都进行一次比较,包括子目录文件。

-N 选项确保补丁文件将正确地处理已经创建或删除文件的情况。

-u 选项以统一格式创建补丁文件,这种格式比缺省格式更紧凑些。

一般 -uN 是一直使用的参数,而 -r 如果是含子目录就使用,不含则不使用。

2、patch

------------------

NAME

       patch - apply a diff file to an original

SYNOPSIS

       patch [options] [originalfile[patchfile]]

       but usually just

       patch -pnum <patchfile

       带下划线的代表需要根据实际情况替换。比如 -pnum 实际使用时一般为 -p0, -p1。

------------------

简单的说,patch就是利用diff制作的补丁来实现源文件(夹)和目的文件(夹)的转换。这样说就意味着你可以由源文件(夹)――>目的文件(夹),也可以目的文件(夹)――>源文件(夹)。下面介绍几个最常用选项:

-pnum 是指查找patch文件中指定的文件时,忽略前num个目录,一个"/"为一层,详细内容下面解释。

-R 选项说明在补丁文件中的“新”文件和“旧”文件现在要调换过来了(实际上就是给新版本打补丁,让它变成老版本)

-E 选项说明如果发现了空文件,那么就删除它

3、patch文件的结构

(1)补丁头

补丁头是分别由---/+++开头的两行,用来表示要打补丁的文件。---开头表示旧文件,+++开头表示新文件。

一个补丁文件中可能包含以---/+++开头的很多节,每一节用来打一个补丁。所以在一个补丁文件中可以包含好多个补丁。

--- linux-2.6.25/arch/alpha/boot/misc.c 2010-05-06 01:56:42.565397700 -0700
+++ linux-2.6.29/arch/alpha/boot/misc.c 2010-05-06 00:51:06.000000000 -0700

(2)块

块是补丁中要修改的地方。它通常由一部分不用修改的东西开始和结束。他们只是用来表示要修改的位置。他们通常以@@开始,结束于另一个块的开始或者一个新的补丁头。

块会缩进一列,这一列是用来表示这一行是要增加还是要删除的。

+号表示这一行是要加上的。

-号表示这一行是要删除的。

没有加号也没有减号表示这里只是引用的而不需要修改。

4、-pnum

我们在生成补丁时,多是对目录进行操作,比如下面我对linux内核25和29两个版本arch目录下的文件做一个diff操作,生成的差异保存在arch.patch中:

diff -uNr linux-2.6.25/arch linux-2.6.29/arch > arch.patch

可以看到arch.patch开始位置的补丁头如下:

--- linux-2.6.25_android/arch/alpha/boot/misc.c 2010-05-06 01:56:42.565397700 -0700(黄色部分为打patch时命令会查找的文件名)
+++ linux-2.6.29_android/arch/alpha/boot/misc.c 2010-05-06 00:51:06.000000000 -0700

patch -p0 <arch.patch 代表忽略0层目录,即从当前目录中查找linux-2.6.25_android/arch/alpha/boot/misc.c,然后进行patch操作。
patch -p1 <arch.patch 代表忽略一层目录,即从从当前目录中查找arch/alpha/boot/misc.c,然后进行patch操作。为了能找到文件,当前目录应转到arch所属的目录下。
patch -p2 <arch.patch 代表忽略两层目录,即从从当前目录中查找alpha/boot/misc.c,然后进行patch操作。为了能找到文件,当前目录应转到alpha所属的目录下。
以此类推,patch的目录不限,可以指定patch的目录。

这个功能使用的情景是:打补丁时的目录结构/目录名跟现在要打补丁的目录结构/目录名不一样。
比如上面的例子,这个patch很可能是别人打的,打补丁时目录为 linux-2.6.25/arch,而本地的目录可能是linux_kernel_2625,
那打补丁时就可以进入linux_kernel_2625,用命令patch -p1 <arch.patch 忽略第一层目录即可打上补丁而无需修改自己的目录名或结构也不需要修改patch文件。


5、常用命令

(1)单个文件比较

diff –uN from-file to-file > to-file.patch //生成补丁 【因为单个文件,所以不需要-r选项。选项顺序没有关系,即可以是-uN,也可以是-Nu】

patch –p0 < to-file.patch //打补丁

patch –R –p0 < to-file.patch //去除补丁

(2)目录比较

diff –uNr from-dir to-dir > to-dir.patch //生成补丁

cd from-dir

patch –p1 < to-dir.patch //打补丁

patch –R –p1 < to-dir.patch //去除补丁

6、为内核打补丁

(1)首先是解压,因为发布的补丁文件都是使用gzip压缩的。

$gunzip ../setup-dir/ patch-2.4.21-rmk1.gz

(2)然后进入你的内核源代码目录

$cd linux-2.4.21

(3)打补丁

$patch –p1 < ../../setup-dir/patch-2.4.21-rmk1

打完补丁后,需要检查一下有没有拒绝执行的文件,即检查.rej文件的存在。使用命令:

$find -name *.rej

打补丁时候的常见错误
-------------------


当用patch命令来打一个补丁的时候,它试图以不同的方法来验证这个文件的完整性。

检查这个文件是一个有效的patch文件并且检查这些被改变周围的代码是不是和提供的
上下文相匹配。这些仅仅是patch所作的两个最基本的完整性检查。

如果patch遇到了一些看起来不正确的事情,那么它有两种选择。它可以拒绝应用这些改变并且
异常中断或者它试图找到一个方法来使patch命令仅仅做一些比较小的改变。


一个patch试图修正错误的例子就是:如果所有的上下文都匹配,被改变的行匹配,但是这些行的
行号不匹配。这是可能发生的,例如,如果patch在一个文件的中间做了一些改变,但是出于一些
原因在文件的开头处一些行被添加了进来或者被删除了。在这种情况下,一切看起来都很好,它只
是简单地上下移动一点,这时候patch通常会修正这些行号并且打上这个补丁。

任何时候,只要patch在打补丁的时候需要改动文件的一些内容,它就会告诉你说:
这个补丁打得有点儿混乱。你应该对这些改变保持一些警惕,因为即使补丁很可能被正确
地打上了,但是情况并不总是这样,有些情况下结果会是错误的。

当patch命令遇到一个变化而不能进行使用一种模糊的方法进行弥补的时候,它就会彻底地
放弃这个动作,并且留下来一个以.rej为扩展名的文件。你可以阅读这个文件来查看到底是
什么改变不能进行下去,从而在你愿意的情况下来手动修补它。


如果你的内核源代码上没有应用任何第三方的补丁,只是一些来自kernel.org的补丁,并且你打这些补丁
的顺序是正确的,而且你自己没有对这些源文件进行改动过,那么你应该就不会看到一个补丁
对这个文件的模糊的改变或者是一些拒绝消息。如果你确实看到了这些消息的话,那么将有非常高的
危险性,这说明或者是你的本地的源代码书或者是补丁文件在某些方面被玷污了。在这种情况下,你很可能
是应该重新下载这个补丁文件,如果事情仍然还是保持原样的话,那么我建议你去尝试从kernel.org上
下载一个完整的新的源代码树。

让我们来看一下补丁可能产生的更多信息。


如果patch命令停下来并且显示一个“File to patch”的提示符,那么这个时候patch命令找不到
要打补丁的文件。很可能的情况是你忘记指定-p1参数或者你处于一个错误的目录中了。更加不
常见的一种情况是,你会发现一些补丁需要使用-p0参数而不是-p1参数来打补丁(阅读这个补丁文件
应该能揭示出这些信息--如果是这样的话,这是一种创建补丁文件的人所犯的错误,但是不致命)

如果你得到信息“Hunk #2 succeeded at 1887 with fuzz 2 (offset 7 lines)”,或者是一个类似
的消息,那就意味着patch命令必须调整改变的位置(在这个例子中,它需要在它想打补丁的地方移动7行来
适应这个补丁)。结果得到的文件可能正确也可能不正确,这决定于这个文件与所期望的文件不相同的原因。
这常常发生于你所要打的patch产生于一个另外一个内核版本,这个内核版本和你要打的patch所基于的内核
版本不同。

如果你得到一个类似于“Hunk #3 FAILED at 2387”之类的消息,那么这就意味着不能正确地打上这个
补丁,并且patch程序也不能模糊地通过。这将产生一个导致patch失败的.rej文件并且产生一个.orig
文件把一些不能改变的原始内容显示给你。

如果你得到的信息是:“Reversed (or previously applied) patch detected!  Assume -R? [n]”,
那么patch检测到了这个补丁文件中包含的改变已经应用在了目标文件上。如果你确实已经在此之前打了
这个补丁并且重新打补丁遇到了错误,那么你可以简单地选择[n]o并且终止这次补丁动作。如果你之前
打了补丁并且想得到打补丁以前的版本,但是忘记了指定-R参数,那么你在这里可以回答[y]es来使用patch
为你恢复它。这也可能发生在补丁文件的创建者在创建补丁文件的时候倒置了源文件和目标目录的位置,
在这种情况下从patch中revert实际上是打上了这个补丁。

一个类似于“patch: **** unexpected end of file in patch”或者“patch
unexpectedly ends in middle of line”的消息意味着patch命令对你加入到它之中的
文件觉得没有意义。或者是你的下载被打断了,你试图打上一个没有压缩的patch文件,而事前
并没有解压缩它,或者是你使用的补丁文件在传输的某个地方被一个邮件客户端或者一个邮件
传输代理给损坏了。例如,通过把一个长行分成两行。通常情况下这些警告可以通过把这两个
被分开的行合并起来来解决。

就像我上面提到的那样,如果你打的是从kernel.org得来的补丁到一个正确的版本,
并且你没有修改过源代码树,这些错误将从来不会发生。因此如果你从kernel.org来的
补丁上得来了这些错误,那么你应该很可能认为或者是你的补丁文件或者是源代码树
损坏掉了。我建议你重新开始下载一个完整的内核树以及你想要打的补丁2.6.x内核
---------

这是Linus发布的基础稳定版本.发布的最高版本是最新的。


如果发现了冲突或者严重的瑕疵,那么在这个基础上,一个-stable的修正补丁就会被发布
出来(参见下面)。一旦一个新的2.6.x的基础内核发布出来,就可以得到一个测试版本的补丁
,这个补丁基于先前的2.6.x版本内核和这个新的内核。


为了应用一个从2.6.11到2.6.12的补丁,你最好按照下面来做(注意这些补丁不能应用于2.6.x.y的内核,
而是应用在2.6.x的基础内核---如果你需要从2.6.x.y到2.6.x+1,那么你首先需要卸载掉2.6.x.y的补丁)

下面是一些例子:


#从2.6.11到2.6.12
$ cd ~/linux-2.6.11            # 切换到内核源代码目录
$ patch -p1 < ../patch-2.6.12        # 应用2.6.12补丁
$ cd ..
$ mv linux-2.6.11 linux-2.6.12        # 重命名源代码目录

# moving from 2.6.11.1 to 2.6.12
$ cd ~/linux-2.6.11.1            # 切换到内核源代码目录
$ patch -p1 -R < ../patch-2.6.11.1    # 恢复出来2.6.11.1
                    # 源代码目录现在是2.6.11
$ patch -p1 < ../patch-2.6.12        # 应用新的2.6.12补丁
$ cd ..
$ mv linux-2.6.11.1 linux-2.6.12    # 重命名源代码目录


2.6.x.y内核
-----------

带有四位数字版本号的内核是-stable的内核。他们包含了对一个给定的2.6.x内核的一些安全
问题以及发现的重要的退化的修复。


对于那些想要最近的稳定内核并且对于测试开发中的试验性的版本没有兴趣的
用户来说,我们推荐这个分支。

如果没有可用的2.6.x.y内核,那么最高数字的2.6.x内核是目前的稳定内核。


注意:维护稳定内核的团队通常会做一些增量的补丁,就像是基于最近的主流版本发布
的补丁一样。但是在下面我仅仅说明了非增量的情况。那些增量式的版本可以在下面的ftp
处找到: ftp://ftp.kernel.org/pub/linux/kernel/v2.6/incr/.


这些补丁不是增量式的,意味着例如对于2.6.12.3补丁不能应用于2.6.12.2的内核源代码
上去,但是可以应用在2.6.12内核代码上。
因此,为了为了把2.6.12.3的补丁应用到你使用的2.6.12.2的内核源代码上,你不得不卸载掉
2.5.12.2补丁(因此你可以得到一个基础的2.6.12的内核源代码),并且应用新的2.6.12.3补丁。


下面是一个小例子:

$ cd ~/linux-2.6.12.2            # 切换到内核源代码目录
$ patch -p1 -R < ../patch-2.6.12.2    # 回归2.6.12.2补丁
$ patch -p1 < ../patch-2.6.12.3        # 应用新的2.6.12.3补丁
$ cd ..
$ mv linux-2.6.12.2 linux-2.6.12.3    # 重新命名内核源代码目录


-rc内核
-------

这些是候选的发布内核。当Linus认为目前的git(内核的源代码管理工具)内核树处于一个
健全的稳定状态足以用来测试的时候,而发布的开发内核。


这些内核是不稳定的,如果你试着运行他们应该会想到可能会不时地有问题出现。
但是这是主开发分支上的最稳定的内核,并且最终会变成下一个稳定的内核。因此
让尽可能多的人来测试它就显得格外重要。


对于那些想帮忙测试开发中的内核但是又不想跑那些试验性的东西的人来说,这将是
一个非常好的分支。(这样的人应该参照下面的关于-git和-mm内核的部分)

-rc补丁是非增量式的,他们应用于2.6.x内核上,就像上面描述的2.6.x.y内核一样。在-rcN
后缀之前的内核版本号代表了这个-rc的内核最终会变成的内核版本。

因此,2.6.13-rc5意思是这是2.6.13内核的第五个候选的发布版本,并且这个补丁应该打在
2.6.12的内核源代码上。


下面是3个关于怎样打这些补丁的例子:

# 首先是一个从2.6.12到2.6.13-rc3的例子

$ cd ~/linux-2.6.12            # 切换到2.6.12的源代码目录
$ patch -p1 < ../patch-2.6.13-rc3    # 打上2.6.13-rc3的补丁
$ cd ..
$ mv linux-2.6.12 linux-2.6.13-rc3    # 重新命名源代码目录

# 现在从2.6.13-rc3迁移到2.6.13-rc5

$ cd ~/linux-2.6.13-rc3            # 切换到2.6.12的源代码目录
$ patch -p1 -R < ../patch-2.6.13-rc3    # 卸载掉2.6.13-rc3补丁
$ patch -p1 < ../patch-2.6.13-rc5    # 应用新的2.6.13-rc5补丁
$ cd ..
$ mv linux-2.6.13-rc3 linux-2.6.13-rc5    # 重新命名源代码目录

# 最后让我们试着从2.6.12.3到2.6.13-rc5

$ cd ~/linux-2.6.12.3            # 切换到内核源代码目录
$ patch -p1 -R < ../patch-2.6.12.3    # 回返2.6.12.3补丁
$ patch -p1 < ../patch-2.6.13-rc5    # 应用新的2.6.13-rc5补丁
$ cd ..
$ mv linux-2.6.12.3 linux-2.6.13-rc5    # 重新命名源代码目录


-git内核
--------

这些是每天Linus的内核树的快照(在一个git仓库中管理着,因此得名)。

这些补丁通常每天都发布而且代表了的Linus的内核树的当前状态,由于它们是自动产生的
甚至没有任何一个光标的骚动来看它们是不是健全的,所以它们比-rc内核更具有试验性。


-git补丁不是增量的,它们或者是应用在2.6.x内核上或者是应用在一个基础的
2.6.x-rc内核上---这一点你可以从他们的名字上看出来。一个名字是2.6.12-git1的
补丁应用在2.6.12内核源代码上,一个名字为2.6.13-rc3-git2的补丁应用在2.6.13-rc3
的内核源代码上。


这里是一些怎样打这些补丁的例子:

# 从2.6.12迁移到2.6.12-git1

$ cd ~/linux-2.6.12            # 切换到内核源代码目录
$ patch -p1 < ../patch-2.6.12-git1    # 应用2.6.12-git1补丁
$ cd ..
$ mv linux-2.6.12 linux-2.6.12-git1    # 重新命名内核源代码目录

# 从2.6.12-git1迁移到2.6.13-rc2-git3

$ cd ~/linux-2.6.12-git1        # 切换到内核源代码目录
$ patch -p1 -R < ../patch-2.6.12-git1    # 回返2.6.12-git1补丁
                    # 我们现在有了一个2.6.12内核
$ patch -p1 < ../patch-2.6.13-rc2    # 打上2.6.13-rc2补丁
                    # 内核现在是2.6.13-rc2
$ patch -p1 < ../patch-2.6.13-rc2-git3    # 打上2.6.13-rc2-git3补丁
                    # 内核现在是2.6.13-rc2-git3
$ cd ..
$ mv linux-2.6.12-git1 linux-2.6.13-rc2-git3    # 重新命名内核源代码目录


-mm内核
-------

这是Andrew Morton发布的实验性的内核

-mm树作为一个新特性和实验性的补丁的实验场。一旦一个补丁在-mm中经过一段时间被证明
有价值,为了使它能包含在主流内核中,Andrew就会把它推给Linus。


尽管鼓励的方法是通过-mm树把补丁推给Linus,这个步骤并不是总被实行。子系统的维护者
(或者个人)有些时候直接把补丁推给Linus,尽管(或者之后)它们已经被它并到了-mm中并得
到了测试(或者有些时候并没有事前在-mm中得到测试)。


通常情况下你应该尽力使你的补丁通过-mm中最大程度测试后再到达主流内核中。


这个分支是一个持续的变化并且包含了一些实验性的特征,很多正在debug的补丁并不适合于
主流的内核等等。这个分支是这个文档中描述的最具有试验性的分支。


这些内核内核不适合于应用在要求稳定的系统上面,并且在运行中比其他任何的分支都可能承担
更大的风险(确信你有最新的备份---跟踪了任何试验性的内核但是甚至更多,于是达到-mm内核)。


这些内核除了包含所有的试验性的补丁以外,它们还包含了在主流-git内核发布的时候任何
可用的改变。


对-mm内核测试会得到极大的赏识,因为这个分支的总的目的就是为了在改变被加到更加稳定的
主流的Linus内核树之前,消除退化、死机、数据失败bug、build失败(以及任何通常意义上的bug)。

但是-mm的测试者应该清醒地认识到这个源代码树中的失败会比其他任何树中的都要普遍。


-mm内核并不会以一个固定的时间发布,但是通常一些-mm内核会在每一个-rc内核(通常1到3个)
发布的中间。-mm内核或者是应用于一个基础的2.6.x内核(当还没有-rc内核发布的时候)或者应用于
一个Linus -rc的内核。


这里有一个打-mm补丁的例子

# 从2.6.12到2.6.12-mm1

$ cd ~/linux-2.6.12            # 切换到2.6.12的源文件目录
$ patch -p1 < ../2.6.12-mm1        # 打一个2.6.12-mm1的补丁
$ cd ..
$ mv linux-2.6.12 linux-2.6.12-mm1    # 重新正确命名这个源文件


# 从2.6.12-mm1到2.6.13-rc3-mm3

$ cd ~/linux-2.6.12-mm1
$ patch -p1 -R < ../2.6.12-mm1        # 卸载掉2.6.12-mm1补丁
                    # 现在我们得到了一个2.6.12的源文件
$ patch -p1 < ../patch-2.6.13-rc3    # 打一个2.6.13-rc3的补丁
                    # 我们现在得到一个2.6.13-rc3的源文件
$ patch -p1 < ../2.6.13-rc3-mm3        # 打一个2.6.13-rc3-mm3的补丁
$ cd ..
$ mv linux-2.6.12-mm1 linux-2.6.13-rc3-mm3    # 重新命名源文件目录



上面总结了不同内核树的一些解释。我希望你已经明白了怎样打不同的补丁并且对你测
试内核有所帮助。


致谢列表:
Randy Dunlap, Rolf Eike Beer, Linus Torvalds, Bodo Eggert,
Johannes Stezenbach, Grant Coady, Pavel Machek,还有一些人我可能忘记了他们的名字,
但是他们对这篇文档进行了评论或者贡献。

你可能感兴趣的:(使用diff命令打补丁详解)