曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 3880|回复: 0
打印 上一主题 下一主题

diff 和 patch

[复制链接]

4917

主题

5879

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34382
跳转到指定楼层
楼主
发表于 2015-9-6 21:01:03 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
diff 和 patch 是在 Linux 环境为源代码制作和应用补丁的标准工具。diff 可以比较文件或目录的差异,并将差异记录到补丁文件。patch 可以将补丁文件应用到源代码上。quilt 也是一个制作和应用补丁的工具,它适合于管理较多补丁。quilt 有自己的特有的工作方式。本文通过简单的例子介绍这三个常用的工具。


0 示例工程

我们先准备一个用来做实验的工程,它包含若干子目录和文件。可以用 find 命令列出文件清单:
$ find old-prj/ -type f
old-prj/inc/def1.h
old-prj/inc/def2.h
old-prj/src/sys/sys1.c
old-prj/src/sys/sys1.h
old-prj/src/app/app1.c
old-prj/src/app/app2.c
old-prj/src/app/app2.h
old-prj/src/app/app1.h
old-prj/src/drv/drv1.h
old-prj/src/drv/drv2.c
old-prj/src/drv/drv1.c
old-prj/src/drv/drv2.h
old-prj/build/Makefile

find 命令的 "-type f" 参数选择普通文件,可以省略掉目录。

1 diff 和 patch

1.1 比较一个文件

将 old-prj.tar.bz2 放到我们的工作目录,然后建立一个子目录,进入后解压示例工程:
$ mkdir test1
$ cd test1
$ tar xvjf ../old-prj.tar.bz2


将 old-prj 复制到 new-prj:
$ cp -a old-prj/ new-prj


让我们编辑一个文件。src/drv/drv1.h 的内容本来是:
$ cat -n old-prj/src/drv/drv1.h
     1  #ifndef DRV1_H
     2  #define DRV1_H
     3
     4  #include "def1.h"
     5
     6  typedef struct {
     7    int p1;
     8    int p2;
     9    int p3;
    10  } App1;
    11
    12  void do_app1(void);
    13
    14  #endif

cat命令的 "-n" 参数可以增加行号。我们用vi将它修改成:
$ cat -n new-prj/src/drv/drv1.h
     1  #ifndef DRV1_H
     2  #define DRV1_H
     3
     4  #include "def1.h"
     5
     6  typedef struct {
     7    int a;
     8    int b;
     9  } App1;
    10
    11  void do_app1(void);
    12
    13  #endif


现在可以用 diff 命令比较文件了:
$ diff -u old-prj/src/drv/drv1.h new-prj/src/drv/drv1.h
--- old-prj/src/drv/drv1.h      2008-03-01 12:59:46.000000000 +0800
+++ new-prj/src/drv/drv1.h      2008-03-01 13:07:14.000000000 +0800
@@ -4,9 +4,8 @@
#include "def1.h"

typedef struct {
-  int p1;
-  int p2;
-  int p3;
+  int a;
+  int b;
} App1;

void do_app1(void);


diff 程序按行比较文本文件。比较文件的 diff 命令格式是:
$ diff -u 旧文件 新文件


"-u" 参数指定 diff 命令使用 unified 格式,这是一种最常用的格式,我们来看看它的含义。

1.2 diff 的 unified 格式

以"---" 开头的行是旧文件信息,以 "+++" 开头的行是新文件信息:
--- old-prj/src/drv/drv1.h      2008-03-01 12:59:46.000000000 +0800
+++ new-prj/src/drv/drv1.h      2008-03-01 13:07:14.000000000 +0800


unified 格式默认在变化部分的前后各显示三行上下文。在上例中,旧文件的7、8、9 行被替换成新文件的 7、8 行。旧文件的变化部分是 7-9 行,前后多显示 3 行,因此显示 4-12 行。新文件的变化部分是 7-8 行,前后多显示 3 行,因此显示 4-11 行。以 "@@" 包围的行指示补丁的范围:
@@ -4,9 +4,8 @@


'-4,9' 中,'-' 表示旧文件,'4,9' 表示从第 4 行开始,显示 9 行,即显示 4-12 行。'+4,8' 中,'+' 表示新文件,'4,8' 表示从第4行开始,显示 8 行,即显示 4-11 行。"@@" 行之后是上下文和变化的文本,其中 '-' 开头的行是旧文件特有的,'+' 开头的行是新文件特有的,其它行是两个文件都有的,即补丁的上下文。例如:
#include "def1.h"

typedef struct {
-  int p1;
-  int p2;
-  int p3;
+  int a;
+  int b;
} App1;

void do_app1(void);


1.3 制作和应用补丁

所谓制作补丁就是diff的输出重定向到一个文件,这个文件就是补丁文件。例如:
$ diff -u old-prj/src/drv/drv1.h new-prj/src/drv/drv1.h>../drv1.diff


我们将 old-prj 解压到另一个目录,准备应用这个补丁:
$ cd ..
$ mkdir test2
$ cd test2
$ tar xvjf ../old-prj.tar.bz2
$ mv old-prj myprj
$cd myprj


在真实场景中,test2 目录通常是在用户2的电脑上。用户2可能不使用 old-prj 作为第一级目录的名字。例如:用户1的第一级目录名是 linux-2.6.23.14, 用户2的第一级目录名是linux。所以我们将 old-prj 改为 myprj 以模拟这种情况。

我们在 myprj 目录使用 patch 命令应用补丁:
$ patch -p1 < ../../drv1.diff
patching file src/drv/drv1.h


patch 命令行中为什么没有出现要打补丁的文件?这是因为 patch 命令可以使用补丁文件中的文件信息:
--- old-prj/src/drv/drv1.h      2008-03-01 12:59:46.000000000 +0800


"-pn" 参数(上例中 n=1)中的n表示要从补丁文件的文件路径中去掉几层目录,可以理解为去掉几个'/'。例如:p1 表示去掉一层目录,"old-prj/src/drv/drv1.h" 去掉一层就成为 "src/drv/drv1.h"。patch命令在 myprj 目录找到"src/drv/drv1.h" 后应用补丁。

我们通常都在代码树的上一层目录制作补丁,在代码树的根目录应用补丁。因此,最常用的patch命令格式是:
$ patch -p1 < 补丁文件


1.4 比较目录
我们回到 test1 目录,再对 new_prj  做一些改动。这次我们删除掉 src/sys 目录及其中的文件。再建立 src/usr 目录,并在该目录增加两个文件 usr1.h 和 usr1.c 。
$ cd ../../test1; rm -rf new-prj/src/sys; mkdir new-prj/src/usr
$ echo -e "#ifndef USR1_H/n#define USR1_H/n#include /"def1.h/"/n#endif">new-prj/src/usr/usr1.h
$ echo -e "#include /"usr1.h/"">new-prj/src/usr/usr1.c


echo命令的"-e"参数打开对转义符的支持,bash默认是不支持转义符的。

现在我们比较目录并制作补丁:
$ diff -Nur old-prj/ new-prj/ > ../prj.diff


读者可以 cat 这个补丁文件的内容。根据前面的介绍,读者应该能看懂补丁文件了吧。

比较目录的常用命令是:
$ diff -Nur 旧目录 新目录 > 补丁文件


$ diff -Naur 旧目录 新目录 > 补丁文件


"-u" 参数前面已经介绍过了。"-N" 参数将不存在的文件当作空文件。如果没有这个参数,补丁就不会包含孤儿文件(即另一方没有的文件)。"-r" 参数表示比较子目录。"-a" 参数表示将所有文件当作文本文件。

我们再准备一个目录来应用补丁:
$ cd ..; mkdir test3; cd test3; tar xvjf ../old-prj.tar.bz2; mv old-prj myprj; cd myprj



在源代码树的根目录应用补丁:
$ patch -p1 < ../../prj.diff
patching file src/drv/drv1.h
patching file src/sys/sys1.c
patching file src/sys/sys1.h
patching file src/usr/usr1.c
patching file src/usr/usr1.h


好了,读者可以用"diff -Nur"比较一下"test1/new_prj"和"test3/myprj",没有输出就表示完全相同。

$ cd ../..; diff -Nur test1/new-prj test3/myprj


1.5 很多的补丁...

一个大项目可能有不同开发者提供很多补丁。这些补丁可能还存在依赖关系,例如补丁B必须打在补丁A上。我们当然可以凭着程序员的“心细如发”去管理好这些补丁,不过有一个叫 quilt 的工具可以使我们轻松一些。当然,即使有工具的帮助,细心和认真也是必需的。

附录:
为了简单起见,前面只介绍了一个"diff -Nur 老目录 新目录"的用法。有时候,新目录里只放了修改过的文件。这时可以不使用 -N 参数以忽略孤儿文件,即 "diff -ur 老目录 新目录"。diff 会输出孤儿文件的提示,我们可以删除或保留这些提示,它们对patch没有影响。

使用diff时可以用 --exclude 排除文件和目录,例如:
diff -ur -exclude=.* --exclude=CVS prj_old prj_new


上例排除了源代码树中以'.'开头的文件和所有 CVS 目录。其实对于 CVS 项目,可以直接在源代码树根目录中执行:
cvs diff -u3 > 补丁文件名


u3 表示输出3行上下文的 unified 格式。打补丁时在源代码树根目录中执行:
patch -p0 < 补丁文件名


"cvs diff" 会自动忽略CVS项目外的文件。通过 CVS 的 tag 和补丁文件,我们可以方便地保存工作快照。

转自:http://blog.csdn.net/fmddlmyy/article/details/2140064
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2024-5-4 05:33 , Processed in 0.066952 second(s), 26 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表