diff & patch 製作及打補丁(轉)
生成patch過程:
1. Git branch a
2. git checkout a
3. modify
4. git commit -am
5. git diff master > patch(生成標準patch)/git format-patch -M master 01.patch(生成git專有patch)
應用patch過程:
1. git branch a
2. git checkout a
3. git apply patch(標準)/ git am 01.patch
git提供了兩種簡單的patch方案。一是用git diff生成的標準patch,二是git format-patch生成的Git專用Patch。
1.git diff生成的標準patch
我們可以首先用git diff製作一個patch。本文示例的工作目錄裡最初有一個檔案a,內容是“This is the file a.”,放置在master分支中。為了修改程式碼,我們一般的做法是建立一個新分支:
[email protected]:~/GitEx$ git branch Fix [email protected]:~/GitEx$ git checkout Fix Switched to branch 'Fix'
接下來我們在a檔案裡面追加一行,然後執行git diff。 [email protected]
我們看到了Git diff的輸出,這是一個非常典型的Patch式diff。這樣我們可以直接把這個輸出變為一個Patch:
[email protected]:~/GitEx$ git commit -a -m "Fix"
[Fix b88c46b] Fix
1 files changed, 1 insertions(+), 0 deletions(-)
我們現在有一個patch檔案,並且簽出了master,接下來我們可以使用git apply來應用這個patch。當然了,實際應用中,我們不會這樣在一個分支建patch,到另一個分支去應用,因為只有merge一下就好了。我們現 在權當沒有這個Fix分支。一般情況下,為了保護master,我們會建立一個專門處理新交來的patch的分支:
[email protected]:~/GitEx$ git branch PATCH [email protected]:~/GitEx$ git checkout PATCH Switched to branch 'PATCH' [email protected]:~/GitEx$ git apply patch [email protected]:~/GitEx$ git commit -a -m "Patch Apply" [PATCH 9740af8] Patch Apply 1 files changed, 1 insertions(+), 0 deletions(-)
看,現在我們在PATCH分支中應用了這個補丁,我們可以把PATCH分支和Fix比對一下,結果肯定是什麼也沒有,說明PATCH分支和Fix分支完全一樣。patch應用成功。即使有多個檔案git diff 也能生成一個patch。
2.git format-patch生成的git專用補丁。
我們同樣用上面那個例子的工作目錄,這次,我們在Fix分支中的a添加了新行之後,用git format-patch生成一個patch。 [email protected]:~/GitEx$ git checkout Fix Switched to branch 'Fix' [email protected]:~/GitEx$ echo 'Fix!!!'>>a [email protected]:~/GitEx$ git commit -a -m "Fix1" [Fix 6991743] Fix1 1 files changed, 1 insertions(+), 0 deletions(-) [email protected]:~/GitEx$ git format-patch -M master 0001-Fix1.patch
git format-patch的-M選項表示這個patch要和那個分支比對。現在它生成了一個patch檔案,我們看看那是什麼:
[email protected]:~/GitEx$ cat 0001-Fix1.patch From 6991743354857c9a6909a253e859e886165b0d90 Mon Sep 17 00:00:00 2001 From: Sweetdumplings <[email protected]> Date: Mon, 29 Aug 2011 14:06:12 +0800 Subject: [PATCH] Fix1
--- a | 1 + 1 files changed, 1 insertions(+), 0 deletions(-)
diff --git a/a b/a index 4add65f..0d295ac 100644 --- a/a +++ b/a @@ -1 +1,2 @@ This is the file a. +Fix!!! -- 1.7.4.1
看,這次多了好多東西,不僅有diff的資訊,還有提交者,時間等等,仔細一看你會發現,這是個E-mail的檔案,你可以直接傳送它!這種patch,我們要用git am來應用。
[email protected]:~/GitEx$ git checkout master Switched to branch 'master' [email protected]:~/GitEx$ git branch PATCH [email protected]:~/GitEx$ git checkout PATCH [email protected]:~/GitEx$ git am 0001-Fix1.patch Applying: Fix1 [email protected]:~/GitEx$ git commit -a -m "PATCH apply"
在提交了補丁之後,我們可以再看看目前檔案a的情況:
[email protected]:~/GitEx$ cat a This is the file a. Fix!!!
果然,多了一個Fix!!!
不過要注意的是,如果master與Fix分支中間有多次提交,它會針對每次提交生成一個patch。
3.兩種patch的比較:
- 相容性:很明顯,git diff生成的Patch相容性強。如果你在修改的程式碼的官方版本庫不是Git管理的版本庫,那麼你必須使用git diff生成的patch才能讓你的程式碼被專案的維護人接受。
- 除錯功能:對於git diff生成的patch,你可以用git apply --check 檢視補丁是否能夠乾淨順利地應用到當前分支中;如果git format-patch 生成的補丁不能打到當前分支,git am會給出提示,並協助你完成打補丁工作,你也可以使用git am -3進行三方合併,詳細的做法可以參考git手冊或者《Progit》。從這一點上看,兩者除錯功能都很強。
- 版本庫資訊:由於git format-patch生成的補丁中含有這個補丁開發者的名字,因此在應用補丁時,這個名字會被記錄進版本庫,顯然,這樣做是恰當的。因此,目前使用Git的開源社群往往建議大家使用format-patch生成補丁。
在移植或版本升級過程中,手動比對(用比對工具)轉換是很費力的事情,特別是發生變化的檔案非常多的情況下,“製作補丁、打補丁”可以簡化這個過程。主要用到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
7、patch命令裡面的層數(-p0?-p1?)
引數-p來指定從第幾層開始比較。比如有一個patch檔案的補丁頭是這樣的:
程式碼:<div dir="ltr" style="word-wrap: break-word;">--- old/modules/pcitableMon Sep 27 11:03:56 1999 +++ new/modules/pcitableTue Dec 19 20:05:41 2000</div>如果使用引數-p0,就表示從當前目錄,找一個叫作new的目錄,在它下面找一個叫modules的目錄,再在它下面找一個叫pcitableMon的目錄。如果使用引數-p1,就表示忽略第一層,從當前目錄找一個叫modules的目錄,在它下面找一個叫modules的目錄。這樣會忽略掉補丁頭提到的new目錄。依此類推。