版本库控制Git 的子树合并和子树拆分.docx
《版本库控制Git 的子树合并和子树拆分.docx》由会员分享,可在线阅读,更多相关《版本库控制Git 的子树合并和子树拆分.docx(9页珍藏版)》请在冰点文库上搜索。
![版本库控制Git 的子树合并和子树拆分.docx](https://file1.bingdoc.com/fileroot1/2023-5/6/e971dcb6-a18d-4517-ad5a-071feabed6fd/e971dcb6-a18d-4517-ad5a-071feabed6fd1.gif)
版本库控制Git的子树合并和子树拆分
Git的子树合并和子树拆分
可以通过Git的subtree合并策略实现分支到目录的跟踪,之前我也是一知半解。
这次在准备书稿过程,总算是搞明白了,也发现我在写Gistore时可以使用更好的算法,有时间我再改进gistore吧。
下面是我书稿中的部分内容:
5.5 子树合并
使用子树合并,同样可以实现在一个项目中引用其它项目的数据。
但是和子模组方式不同的是,使用子树合并模式,外部的版本库整个复制到本版本库中并建立跟踪关联。
使用子树合并模型,使得对源自外部版本库的数据的访问和本版本库数据的访问没有区别,也可以对其进行本地修改,并且能够通过子树合并的方式将对源自外部版本库的改动和本地的修改相合并。
5.5.1 引入外部版本库
为演示子树合并,我们需要至少准备两个版本库,一个是将被作为子目录引入的版本库util.git,另外一个是主版本库main.git。
$git--git-dir=/path/to/util.gitinit--bare
$git--git-dir=/path/to/main.gitinit--bare
在本地检出这两个版本库:
$gitclone/path/to/util.git
$gitclone/path/to/main.git
我们需要为这两个空版本库添加些数据。
非常简单,每个版本库下我们只创建两个文件:
Makefile和version。
当执行make命令时显示version文件的内容。
我们对version文件多次提交以建立多个提交历史。
别忘记在最后使用gitpushoriginmaster 将版本库推送到远程版本库中。
Makefile文件示例如下:
all:
@catversion
我们之前尝试的gitfetch命令都是获取同一项目的版本库的内容。
其实命令gitfetch并不禁止你用它获取不同项目的版本库数据,因为Git的版本库不像Subversion那样用一个唯一的UUID标识让Subversion的版本库之间势同水火。
当然你也可以用gitpull,但是那样将把两个项目的文件彻底混杂在一起,对于我们这个示例来说,因为两个项目具有同样的文件Makefile和version,使用gitpull将导致冲突,是我们不愿意出现的。
所以为了将不同项目的版本库引入,我们需要用gitfetch 。
为了便于以后对外部版本库的跟踪,在使用gitfetch时,最好先在main版本库中注册远程版本库util.git。
$gitremoteaddutil/path/to/util.git
$gitremote-v
origin/path/to/main.git/(fetch)
origin/path/to/main.git/(push)
util/path/to/util.git(fetch)
util/path/to/util.git(push)
$gitfetchutil
$gitbranch-a
*master
remotes/origin/master
remotes/util/master
我们基于remotes/util/master创建一个本地分支,我们会看到根目录的Makefile和version属于原util.git版本库,而非main.git版本库。
$make
mainv2010.1
$gitcheckout-butil-branchutil/master
Branchutil-branchsetuptotrackremotebranchmasterfromutil.
Switchedtoanewbranch'util-branch'
$gitbranch-a
master
*util-branch
remotes/origin/master
remotes/util/master
$make
utilv3.0
像这样在main.git中引入util.git显然不能满足我们的需要,因为在main.git的本地克隆版本库中,master分支访问不到只有在util-branch分支中才出现的util版本库数据。
我们需要做进一步的工作,将两个版本库的内容合并到一个分支中。
即util-branch分支的数据作为子目录加入到master分支。
5.5.2 子目录方式合并外部版本库
下面我们就用git的低端命令gitread-tree,gitwrite-tree,gitcommit-tree 子命令实现将util-branch分支所包含的util.git版本库的目录树以子目录(lib/)型式添加到master分支。
我们先来看看util-branch分支当前最新提交所指向的目录树(tree)。
$gitcat-file-putil-branch
tree0c743e49e11019678c8b345e667504cb789431ae
parentf21f9c10cc248a4a28bf7790414baba483f1ec15
authorJiangXin1288494998+0800
committerJiangXin1288494998+0800
utilv2.0->v3.0
这是util-branch分支中最新提交所包含的信息。
我们记住该提交对应的treeid为:
0c743e4。
查看tree0c743e4所包含的内容。
$gitcat-file-p0c743e4
100644blob07263ff95b4c94275f4b4735e26ea63b57b3c9e3Makefile
100644blobbebe6b10eb9622597dd2b641efe8365c3638004eversion
我们切换到master分支,如下方式调用gitread-tree 将util-branch的目录树读取到当前分支lib目录下。
$gitcheckoutmaster
$gitread-tree--prefix=libutil-branch
$gitstatus
#Onbranchmaster
#Changestobecommitted:
#(use"gitresetHEAD..."tounstage)
#
#newfile:
lib/Makefile
#newfile:
lib/version
#
#Changedbutnotupdated:
#(use"gitadd/rm..."toupdatewhatwillbecommitted)
#(use"gitcheckout--..."todiscardchangesinworkingdirectory)
#
#deleted:
lib/Makefile
#deleted:
lib/version
#
$gitcheckout--lib
$gitstatus
#Onbranchmaster
#Changestobecommitted:
#(use"gitresetHEAD..."tounstage)
#
#newfile:
lib/Makefile
#newfile:
lib/version
#
调用gitread-tree只是更新了index,所以上面我们还用一条gitcheckout—lib 命令更新了工作区lib目录的内容。
现在我们还不能提交,因为现在提交体现不出来两个分支的合并关系。
我们调用gitwrite-tree 将index(暂存区)的目录树保存下来。
$gitwrite-tree
2153518409d218609af40babededec6e8ef51616
$gitcat-file-p2153518409d218609af40babededec6e8ef51616
100644blob07263ff95b4c94275f4b4735e26ea63b57b3c9e3Makefile
040000tree0c743e49e11019678c8b345e667504cb789431aelib
100644blob638c7b7c6bdbde1d29e0b55b165f755c8c4332b5version
我们要记住调用gitwrite-tree 后形成的新的tree-id:
2153518。
同时我们也注意到,该tree-id指向的目录树中包含的lib目录的treeid和之前我们查看过的util-branch分支最新提交对应的treeid一样是0c743e4。
然后要调用gitcommit-tree来产生新的提交。
之所以不用gitcommit 而使用底层命令,是因为我们要为此新的提交指定两个parents,让这个提交看起来是两棵树的合并。
这两棵树分别是master分支和util-branch分支。
$gitrev-parseHEAD
911b1af2e0c95a2fc1306b8dea707064d5386c2e
$gitrev-parseutil-branch
12408a149bfa78a4c2d4011f884aa2adb04f0934
就以上面两个revid为parents,我们对树2153518409d218609af40babededec6e8ef51616执行提交。
$echo"subtreemerge"|\
gitcommit-tree2153518409d218609af40babededec6e8ef51616\
-p911b1af2e0c95a2fc1306b8dea707064d5386c2e\
-p12408a149bfa78a4c2d4011f884aa2adb04f0934
62ae6cc3f9280418bdb0fcf6c1e678905b1fe690
提交之后产生一个新的commitid。
我们需要把当前的master分支重置到此commitid。
$gitreset62ae6cc3f9280418bdb0fcf6c1e678905b1fe690
我们查看一下提交日记及分支图,我们可以看到我们通过复杂的gitread-tree ,gitwrite-tree 和gitcommit-tree 制造的提交,的确将两个不同版本库合并到一起了。
$gitlog--graph--pretty=oneline
*62ae6cc3f9280418bdb0fcf6c1e678905b1fe690subtreemerge
|\
|*12408a149bfa78a4c2d4011f884aa2adb04f0934utilv2.0->v3.0
|*f21f9c10cc248a4a28bf7790414baba483f1ec15utilv1.0->v2.0
|*76db0ad729db9fdc5be043f3b4ed94ddc945cd7futilv1.0
*911b1af2e0c95a2fc1306b8dea707064d5386c2emainv2010.1
我们看看现在的master分支。
$gitcat-file-pHEAD
tree2153518409d218609af40babededec6e8ef51616
parent911b1af2e0c95a2fc1306b8dea707064d5386c2e
parent12408a149bfa78a4c2d4011f884aa2adb04f0934
authorJiangXin1288498186+0800
committerJiangXin1288498186+0800
subtreemerge
$gitcat-file-p2153518409d218609af40babededec6e8ef51616
100644blob07263ff95b4c94275f4b4735e26ea63b57b3c9e3Makefile
040000tree0c743e49e11019678c8b345e667504cb789431aelib
100644blob638c7b7c6bdbde1d29e0b55b165f755c8c4332b5version
整个过程非常繁琐,但是不要太过担心,我们只需要对原理了解清楚就可以了,因为在后面我会介绍一个Git插件封装了复杂的子树合并操作。
5.5.3 利用子树合并跟踪上游改动
如果合并子树(lib目录)的上游(即util.git)包含了新的提交,如何将util.git的新提交合并过来呢?
这就要用到名为subtree的合并策略。
参见前面关于合并的相关章节。
在执行子树合并之前,我们先切换到util-branch分支,获取远程版本库改动。
$gitcheckoututil-branch
$gitpull
remote:
Countingobjects:
8,done.
remote:
Compressingobjects:
100%(4/4),done.
remote:
Total6(delta0),reused0(delta0)
Unpackingobjects:
100%(6/6),done.
From/path/to/util
12408a1..5aba14fmaster->util/master
Updating12408a1..5aba14f
Fast-forward
version|2+-
1fileschanged,1insertions(+),1deletions(-)
$gitcheckoutmaster
在切换回master分支后,如果这时我们执行gitmergeutil-branch ,会将uitl-branch的数据直接合并到master分支的根目录下,而我们希望合并发生在lib目录中,这就需要如下方式进行调用。
如果git的版本小于1.7,直接使用subtree合并策略。
$gitmerge-ssubtreeutil-branch
如果git的版本是1.7之后(含1.7)的版本,则可以使用缺省的recursive合并策略,通过参数subtree=在合并时使用正确的子树进行匹配合并。
避免了使用subtree合并策略时的猜测。
$gitmerge-Xsubtree=libutil-branch
我们再来看看执行子树合并之后的分支图示。
$gitlog--graph--pretty=oneline
*f1a33e55eea04930a500c18a24a8bd009ecd9ac2Mergebranch'util-branch'
|\
|*5aba14fd347fc22cd8fbd086c9f26a53276f15c9utilv3.1->v3.2
|*a6d53dfcf78e8a874e9132def5ef87a2b2febfa5utilv3.0->v3.1
*|62ae6cc3f9280418bdb0fcf6c1e678905b1fe690subtreemerge
|\\
||/
|*12408a149bfa78a4c2d4011f884aa2adb04f0934utilv2.0->v3.0
|*f21f9c10cc248a4a28bf7790414baba483f1ec15utilv1.0->v2.0
|*76db0ad729db9fdc5be043f3b4ed94ddc945cd7futilv1.0
*911b1af2e0c95a2fc1306b8dea707064d5386c2emainv2010.1
5.5.4 子树拆分
既然可以将一个代码库通过子树合并方式作为子目录加入到另外一个版本库中,反之也可以将一个代码库的子目录独立出来转换为另外的版本库。
不过这个反向过程异常复制。
要将一个版本库的子目录作为顶级目录导出到另外的项目,潜藏的条件是要导出历史的,因为如果不关心历史,直接文件拷贝重建项目就可以了。
子树拆分的大致过程是:
1.找到要导出的目录的提交历史,并反向排序。
2.依次对每个提交执行下面的操作:
3.找出提交中导出目录对应的treeid。
4.对该treeid执行gitcommit-tree 。
5.执行gitcommit-tree 要保持提交信息还要重新设置提交的parents。
Git, gitbook, 版本库控制
分享文章
这篇文章由 蒋鑫 于2010年10月31日-17:
56发表于北京群英汇信息技术有限公司。