24.2 子目录方式合并外部版本库

下面就用Git的底层命令git read-tree、git write-tree和git commit-tree子命令,实现将util-branch分支所包含的util.git版本库的目录树以子目录(lib/)的形式添加到master分支中。

先来看看util-branch分支当前的最新提交,记住最新提交所指向的目录树(tree),即tree id:0c743e4。


$git cat-file-p util-branch

tree 0c743e49e11019678c8b345e667504cb789431ae

parent f21f9c10cc248a4a28bf7790414baba483f1ec15

author Jiang Xin<jiangxin@ossxp.com>1288494998+0800

committer Jiang Xin<jiangxin@ossxp.com>1288494998+0800

util v2.0->v3.0


查看tree 0c743e4所包含的内容,会看到两个文件:Makefile和version。


$git cat-file-p 0c743e4

100644 blob 07263ff95b4c94275f4b4735e26ea63b57b3c9e3 Makefile

100644 blob bebe6b10eb9622597dd2b641efe8365c3638004e version


切换到master分支,以如下方式调用git read-tree将util-branch分支的目录树读取到当前分支lib目录下,具体操作过程如下。

(1)切换到master分支。


$git checkout master


(2)执行git read-tree命令,将分支util-branch读取到当前分支的一个子目录下。


$git read-tree—prefix=lib util-branch


(3)调用git read-tree只是更新了暂存区,所以查看工作区状态会看到工作区中还不存在lib目录下的两个文件。


$git status

On branch master

Changes to be committed:

(use "git reset HEAD<file>…" to unstage)

#

new file:lib/Makefile

new file:lib/version

#

Changed but not updated:

(use "git add/rm<file>…" to update what will be committed)

(use "git checkout—<file>…" to discard changes in working directory)

#

deleted:lib/Makefile

deleted:lib/version

#


(4)执行检出命令,将lib目录下的文件更新出来。


$git checkout—lib


(5)再次查看状态,会看到前面执行的git read-tree命令添加到了暂存区的文件中。


$git status

On branch master

Changes to be committed:

(use "git reset HEAD<file>…" to unstage)

#

new file:lib/Makefile

new file:lib/version

#


现在还不能忙着提交,因为如果现在进行提交就体现不出两个分支的合并关系。需要使用Git底层的命令进行数据提交,具体操作过程如下。

(1)调用git write-tree将暂存区的目录树保存下来。

要记住调用git write-tree后形成的新的tree-id:2153518。


$git write-tree

2153518409d218609af40babededec6e8ef51616


(2)执行git cat-file命令显示这棵树的内容,会注意到其中lib目录的tree-id和之前查看过的util-branch分支最新的提交对应的tree-id一样都是0c743e4。


$git cat-file-p 2153518409d218609af40babededec6e8ef51616

100644 blob 07263ff95b4c94275f4b4735e26ea63b57b3c9e3 Makefile

040000 tree 0c743e49e11019678c8b345e667504cb789431ae lib

100644 blob 638c7b7c6bdbde1d29e0b55b165f755c8c4332b5

version


(3)要手工创建一个合并提交,即新的提交要有两个父提交。这两个父提交分别是master分支和util-branch分支的最新提交。用下面的命令显示两个提交的提交ID,并记下这两个提交ID。


$git rev-parse HEAD

911b1af2e0c95a2fc1306b8dea707064d5386c2e

$git rev-parse util-branch

12408a149bfa78a4c2d4011f884aa2adb04f0934


(4)执行git commit-tree命令手动创建提交。新提交的目录树来自上面的git write-tree产生的目录树(tree-id为2153518),而新提交(合并提交)的两个父提交直接用上面git rev-parse显示的两个提交ID表示。


$echo "subtree merge"|\

git commit-tree 2153518409d218609af40babededec6e8ef51616\

-p 911b1af2e0c95a2fc1306b8dea707064d5386c2e\

-p 12408a149bfa78a4c2d4011f884aa2adb04f0934

62ae6cc3f9280418bdb0fcf6c1e678905b1fe690


(5)执行git commit-tree命令的输出是提交之后产生的新提交的提交ID。需要把当前的master分支重置到此提交ID。


$git reset 62ae6cc3f9280418bdb0fcf6c1e678905b1fe690


(6)查看一下提交日志及分支图,可以看到通过复杂的git read-tree、git write-tree和git commit-tree命令制造的合并提交,的确将两个不同的版本库合并到一起了。


$git log—graph—pretty=oneline

*62ae6cc3f9280418bdb0fcf6c1e678905b1fe690 subtree merge

|\

|*12408a149bfa78a4c2d4011f884aa2adb04f0934 util v2.0->v3.0

|*f21f9c10cc248a4a28bf7790414baba483f1ec15 util v1.0->v2.0

|*76db0ad729db9fdc5be043f3b4ed94ddc945cd7f util v1.0

*911b1af2e0c95a2fc1306b8dea707064d5386c2e main v2010.1


(7)看看现在的master分支。


$git cat-file-p HEAD

tree 2153518409d218609af40babededec6e8ef51616

parent 911b1af2e0c95a2fc1306b8dea707064d5386c2e

parent 12408a149bfa78a4c2d4011f884aa2adb04f0934

author Jiang Xin<jiangxin@ossxp.com>1288498186+0800

committer Jiang Xin<jiangxin@ossxp.com>1288498186+0800

subtree merge


(8)看看目录树。


$git cat-file-p 2153518409d218609af40babededec6e8ef51616

100644 blob 07263ff95b4c94275f4b4735e26ea63b57b3c9e3 Makefile

040000 tree 0c743e49e11019678c8b345e667504cb789431ae lib

100644 blob 638c7b7c6bdbde1d29e0b55b165f755c8c4332b5 version


整个过程非常繁琐,但是不要太过担心,只需要对原理了解清楚就可以了,因为在后面会介绍一个Git插件,它封装了复杂的子树合并操作。