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插件,它封装了复杂的子树合并操作。