第38章 补丁中的二进制文件

有的时候,需要将代码的改动以补丁文件的方式进行传递,最终合并至版本库。例如直接在软件部署目录内进行改动,再将改动传送到开发平台。或者是因为在某个开源软件的官方版本库中没有提交权限,需要将自己的改动以补丁文件的方式提供给官方。

关于补丁文件的格式,补丁的生成和应用在第3篇的“第20章补丁文件交互”当中已经进行了详细介绍,使用的是git format-patch和git am命令,但这两个命令仅对Git库有效。如果没有使用Git对改动进行版本控制,而仅仅是两个目录:一个改动前的目录和一个改动后的目录,大部分人会选择使用GNU的diff命令及patch命令实现补丁文件的生成和补丁的应用。

但是GNU的diff命令(包括很多版本控制系统,如SVN的svn diff命令)生成的差异输出有一个非常大的不足或者说漏洞,就是差异输出不支持二进制文件。如果生成了新的二进制文件(如图片),或者二进制文件发生了变化,在差异输出中无法体现,当这样的差异文件被导出,应用到代码树中,会发现二进制文件或二进制文件的改动丢失了!

Git突破了传统差异格式的限制,通过引入新的差异格式,实现了对二进制文件的支持。并且更为神奇的是,不必使用Git版本库对数据进行维护,可以直接对两个普通目录进行Git方式的差异比较和输出。

38.1 Git版本库中二进制文件变更的支持

1.创建支持二进制文件的补丁

对Git工作区的修改进行差异比较(git diff—binary),可以输出二进制的补丁文件。包含二进制文件差异的补丁文件可以通过git apply命令应用到版本库中。可以通过下面的示例看看Git的补丁文件是如何对二进制文件提供支持的,具体操作过程如下。

(1)首先建立一个空的Git版本库。


$mkdir/tmp/test

$cd/tmp/test

$git init

initialized empty Git repository in/tmp/test/.git/

$git commit—allow-empty-m initialized

[master(root-commit)2ca650c]initialized


(2)然后在工作区创建一个文本文件readme.txt,以及一个二进制文件binary.data。二进制的数据是从系统中的二进制文件/bin/ls读取而来的,当然可以用任何其他的二进制文件代替。


$echo hello>readme.txt

$dd if=/bin/ls of=binary.data count=1 bs=32

记录了1+0的读入

记录了1+0的写出

32字节(32 B)已复制,0.0001062秒,301 kB/秒


注:拷贝/bin/ls可执行文件(二进制)的前32个字节作为binary.data文件。

(3)如果执行git diff—cached则看到的是未扩展的差异格式。


$git add.

$git diff—cached

diff—git a/binary.data b/binary.data

new file mode 100644

index 0000000..dc2e37f

Binary files/dev/null and b/binary.data differ

diff—git a/readme.txt b/readme.txt

new file mode 100644

index 0000000..ce01362

—-/dev/null

+++b/readme.txt

@@-0,0+1@@

+hello


可以看到对于binary.data,此差异文件没有给出差异内容,而只是一行"Binary files……and……differ"。

(4)再执行git diff—cached—binary试试看,即增加了参数—binary。


$git diff—cached—binary

diff—git a/binary.data b/binary.data

new file mode 100644

index

0000000000000000000000000000000000000000..dc2e37f81e0fa88308bec48cd5195b6

542e61a20

GIT binary patch

literal 32

bcmb<-^>JfjWMqH=CI&kO5HCR00W1UnGBE;C

literal 0

HcmV?d00001

diff—git a/readme.txt b/readme.txt

new file mode 100644

index 0000000..ce01362

—-/dev/null

+++b/readme.txt

@@-0,0+1@@

+hello


看到了吗,此差异文件给出了二进制文件binary.data差异的内容,并且差异内容经过base85[1]文本化了。

(5)提交后,用新的内容覆盖binary.data文件。


$git commit-m "new text file and binary file"

[master 7ab2d01]new text file and binary file

2 files changed,1 insertions(+),0 deletions(-)

create mode 100644 binary.data

create mode 100644 readme.txt

$dd if=/bin/ls of=binary.data count=1 bs=64

记录了1+0的读入

记录了1+0的写出

64字节(64 B)已复制,0.00011264秒,568 kB/秒

$git commit-a-m "change binary.data."

[master a79bcbe]change binary.data.

1 files changed,0 insertions(+),0 deletions(-)


(6)再来看看更改后的二进制文件的新差异格式。


$git show HEAD—binary

commit a79bcbe50c1d278db9c9db8e42d9bc5bc72bf031

Author:Jiang Xin<jiangxin@ossxp.com>

Date:Sun Oct 10 19:22:30 2010+0800

change binary.data.

diff—git a/binary.data b/binary.data

index

dc2e37f81e0fa88308bec48cd5195b6542e61a20..bf948689934caf2d874ff8168cb716f

bc2a127c3 100644

GIT binary patch

delta 37

hcmY#zn4qBGzyJX+<}pH93=9qo77QFfQiegA0RUZd1MdI;

delta 4

LcmZ=zn4kav0;B;E


(7)更简单的,使用git format-patch命令,直接将最近的两次提交导出为补丁文件。


$git format-patch HEAD^^

0001-new-text-file-and-binary-file.patch

0002-change-binary.data.patch


毫无疑问,这两个补丁文件都包含了对二进制文件的支持。


$cat 0002-change-binary.data.patch

From a79bcbe50c1d278db9c9db8e42d9bc5bc72bf031 Mon Sep 17 00:00:00 2001

From:Jiang Xin<jiangxin@ossxp.com>

Date:Sun,10 Oct 2010 19:22:30+0800

Subject:[PATCH 2/2]change binary.data.


binary.data|Bin 32->64 bytes

1 files changed,0 insertions(+),0 deletions(-)

diff—git a/binary.data b/binary.data

index

dc2e37f81e0fa88308bec48cd5195b6542e61a20..bf948689934caf2d874ff8168cb716f

bc2a127c3 100644

GIT binary patch

delta 37

hcmY#zn4qBGzyJX+<}pH93=9qo77QFfQiegA0RUZd1MdI;

delta 4

LcmZ=zn4kav0;B;E

1.7.1


2.应用包含二进制文件差异的补丁

应用包含二进制文件差异的补丁,不能使用GNU patch命令,因为前面曾经说过GNU的diff和patch不支持二进制文件的补丁,当然也不支持Git的新的补丁格式。将Git格式的补丁应用到代码树,只能使用Git命令,即git apply命令。

接着前面的例子。首先将版本库重置到最近两次提交之前的状态,即丢弃最近的两次提交,然后将两个补丁都合并到代码树中,具体操作步骤如下。

(1)重置版本库到两次提交之前的状态。


$git reset—hard HEAD^^

HEAD is now at 2ca650c initialized

$ls

0001-new-text-file-and-binary-file.patch 0002-change-binary.data.patch


(2)使用git apply应用补丁。


$git apply 0001-new-text-file-and-binary-file.patch

0002-change-binary.data.patch


(3)可以看到64字节长度的binary.data又回来了。


$ls-l

总用量16

-rw-r—r—1 jiangxin jiangxin 754 10月10 19:28

0001-new-text-file-and-binary-file.patch

-rw-r—r—1 jiangxin jiangxin 524 10月10 19:28

0002-change-binary.data.patch

-rw-r—r—1 jiangxin jiangxin 64 10月10 19:34 binary.data

-rw-r—r—1 jiangxin jiangxin 6 10月10 19:34 readme.txt


(4)最后不要忘了提交。


$git add readme.txt binary.data

$git commit-m "new text file and binary file from patch files."

[master 7c1389f]new text file and binary file from patch files.

2 files changed,1 insertions(+),0 deletions(-)

create mode 100644 binary.data

create mode 100644 readme.txt


Git对补丁文件的扩展,实际上不只是增加了二进制文件的支持,还提供了对文件重命名(rename from和rename to指令)、文件拷贝(copy from和copy to指令)、文件删除(deleted file指令)及文件权限(new file mode和new mode指令)的支持。

[1]Git源代码diff.c的emit_binary_diff_body函数。