[轉]Linux之旅(1): diff, patch和quilt (上)

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和補丁文件,我們可以方便地保存工作快照。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章