I code it

Code and Life

Diff和patch的使用介绍

diff和patch是*nix上一对强大的工具,diff用以指出两个文件/目录之间的差异,而patch则用于将某个差异应用到一个文件上,从而消除差异(结果就是为老的文件打上新的补丁)。我们从最简单的场景来看:我们有一个文件的老版本load.js和新版本load-new.js。

   1: function load(file){
   2:     var text = null;
   3:     if(file){
   4:         var reader = new java.io.BufferedReader(new java.io.FileReader(file));
   5:         var line;
   6:         while ((line = reader.readLine()) != null)
   7:             text += line;
   8:             
   9:         try{
  10:             eval(text);
  11:         }catch(e){
  12:             throw new Error("error while load string");
  13:         }
  14:     }
  15: }

从load.js到load-new.js有一些修改:

   1: function loads(string){
   2:     try{
   3:         eval(string);
   4:     }catch(e){
   5:         throw new Error("error while load string");
   6:     }
   7: }
   8:  
   9: function load(file){
  10:     var text = null;
  11:     if(file){
  12:         var reader = new java.io.BufferedReader(new java.io.FileReader(file));
  13:         var line;
  14:         while ((line = reader.readLine()) != null)
  15:             text += line;
  16:         
  17:         loads(text);
  18:     }
  19: }

使用diff命令来查看这两个文件的不同之处:

   1: $ diff load.js load-new.js

得到以下输出:

   1: 0a1,8
   2: >; function loads(string){
   3: >;       try{
   4: >;               eval(string);
   5: >;       }catch(e){
   6: >;               throw new Error("error while load string");
   7: >;       }
   8: >; }
   9: >;
  10: 8,13c16,17
  11: <;
  12: <               try{
  13: <;                       eval(text);
  14: <;               }catch(e){
  15: <;                       throw new Error("error while load string");
  16: <;               }
  17: ---
  18: >;
  19: >               loads(text);

第一行0a1,8的意思是:从load.js的第0行开始,做添加操作,添加的内容为load-new.js的1到18行的内容。第10行8,13c16,17的意思是:从load.js的8到13行开始,做替换操作,替换内容为load-new.js的第16到17行。输出中,大于号开始的行表示load-new.js文件的内容,而小于号开始的行表示load.js中的内容。diff的输出有简单的语法:

(行号/区间)|(动作)|(行号/区间)

其中,动作包括添加(a),修改(c),删除(d)。行号是一个数字,而区间是由被逗号分隔的两个数字。

有了这些信息,patch程序就知道如何处理两个文件了,我们可以将diff的输出重定向到一个patch文件中,这个文件是文本格式,内容就是上面的那个。使用patch命令则更为简单,比如刚才的场景,我们曾经开发了一个load.js的脚本并发布,用户已经成功的在系统中使用了该脚本,但是我们对代码做了修改,并在本地保存为load-new.js。然后,我们可以用diff生成一个补丁,并发送给用户,这样用户就可以通过patch工具来完成升级了:

   1: $ patch load.js -i load.patch -o load-new.js
   2: patching file `load.js'
   3: $ 

 

如果打完补丁之后,发现这个补丁有问题,想要恢复到打补丁之前,可以使用patch命令的-R来恢复:

   1: $ patch load-new.js -R -i load.patch
   2: patching file `load-new.js'
   3: $ 

 

实际开发中,我们一般会操作整个目录结构,越复杂越能体现工具的好处。比如我们有两个目录:old和new。两者的结构分别如下:

old:

   1: D:\development\blog>;tree /a /f old
   2: D:\DEVELOPMENT\BLOG\OLD
   3: |   init.js
   4: |   load.js
   5: |   pretty-old.js
   6: |
   7: \---dir
   8:         deeper.js

new:

   1: D:\development\blog>;tree /a /f new
   2: D:\DEVELOPMENT\BLOG\NEW
   3: |   init.js
   4: |   load.js
   5: |   pretty-new.js
   6: |
   7: \---dir
   8:         deeper.js    

然后使用diff来比较两者的不同:

   1: $ diff old new –r

 

-r表示包含子目录的比较:

   1: diff -r old\dir\deeper.js new\dir\deeper.js
   2: 0a1
   3: >; print("with one line inside");
   4: diff -r old\init.js new\init.js
   5: 1a2,4
   6: >; (function(){
   7: >;     print("do the real init...");
   8: >; })();
   9: diff -r old\load.js new\load.js
  10: 0a1,8
  11: >; function loads(string){
  12: >;     try{
  13: >;         eval(string);
  14: >;     }catch(e){
  15: >;         throw new Error("error while load string");
  16: >;     }
  17: >; }
  18: >; 
  19: 8,13c16,17
  20: <;             
  21: <         try{
  22: <;             eval(text);
  23: <;         }catch(e){
  24: <;             throw new Error("error while load string");
  25: <;         }
  26: ---
  27: >;         
  28: >         loads(text);
  29: Only in new: pretty-new.js
  30: Only in old: pretty-old.js

 

在末尾有两行“奇特”的信息,表明pretty-new.js仅存在于new目录,而pretty-old.js仅存在于old目录。其余的输出与我们之前讨论的相同。但是这样的输出格式对于目录来讲,还不够详细,我们需要提供足够的上下文信息。因此需要采用diff命令的-c选项。

   1: $ diff old new -cr >; dirdiff.patch

得到的输出格式为:

   1: diff -cr old\dir\deeper.js new\dir\deeper.js
   2: *** old\dir\deeper.js    Mon Feb 06 14:35:05 2012
   3: --- new\dir\deeper.js    Mon Feb 06 14:37:43 2012
   4: ***************
   5: *** 0 ****
   6: --- 1 ----
   7: + print("with one line inside");
   8: diff -cr old\init.js new\init.js
   9: *** old\init.js    Mon Feb 06 14:29:03 2012
  10: --- new\init.js    Mon Feb 06 14:30:03 2012
  11: ***************
  12: *** 1,2 ****
  13: --- 1,5 ----
  14:   println("init...");
  15: + (function(){
  16: +     print("do the real init...");
  17: + })();
  18:   println("done.");
  19: diff -cr old\load.js new\load.js
  20: *** old\load.js    Mon Feb 06 11:23:50 2012
  21: --- new\load.js    Mon Feb 06 11:24:08 2012
  22: ***************
  23: *** 1,3 ****
  24: --- 1,11 ----
  25: + function loads(string){
  26: +     try{
  27: +         eval(string);
  28: +     }catch(e){
  29: +         throw new Error("error while load string");
  30: +     }
  31: + }
  32: + 
  33:   function load(file){
  34:       var text = null;
  35:       if(file){
  36: ***************
  37: *** 5,15 ****
  38:           var line;
  39:           while ((line = reader.readLine()) != null)
  40:               text += line;
  41: !             
  42: !         try{
  43: !             eval(text);
  44: !         }catch(e){
  45: !             throw new Error("error while load string");
  46: !         }
  47:       }
  48:   }
  49: --- 13,19 ----
  50:           var line;
  51:           while ((line = reader.readLine()) != null)
  52:               text += line;
  53: !         
  54: !         loads(text);
  55:       }
  56:   }
  57: Only in new: pretty-new.js
  58: Only in old: pretty-old.js

有了上下文信息之后,现在我们将新产生的补丁应用于old目录,使得其更新到最新版本:

   1: $ patch -p0 <; dirdiff.patch
   2: patching file `old\dir\deeper.js'
   3: patching file `old\init.js'
   4: Hunk #1 succeeded at 1 with fuzz 1.
   5: patching file `old\load.js'
   6: Hunk #2 succeeded at 13 with fuzz 2.        
   7: $ 

 

附:可能由于语法高亮插件的bug,大于号/小于号之后都会出现一个分号(;),在使用时请注意将其清除

Comments