Git教程 · 外包长历史记录
- [1️⃣ 概述](#1️⃣ 概述)
- [2️⃣ 使用要求](#2️⃣ 使用要求)
- [3️⃣ 执行过程及其实现](#3️⃣ 执行过程及其实现)
-
-
- [3.1 外包项目历史](#3.1 外包项目历史)
- [3.2 链接到当前活动版本库](#3.2 链接到当前活动版本库)
-
Git 版本库会随着时间积累越来越大,会影响它的内存管理效率。通常在版本库中只有源 代码文件情况下,这点效率影响可以忽略不计。在现有的磁盘效率和网络带宽条件下,这样的版本库并不显得过大。
但是,如果有大型的二进制文件(库文件、发布产物、测试数据库、图像文件)也在版本库中被管理,那么这个版本库的过大真的会带来负面影响。
和中央集中式的版本控制系统相比,分布式的版本库会消耗更多的计算传送资源。在克隆一个版本库时,所有的历史版本文件都会被复制。
本章工作流会演示如何外包过长的版本库历史记录,实现:
- 新的项目版本库会占用较少的资源;
- 依然可以使用
log
、blame
和annotate
命令搜索历史版本提交。
1️⃣ 概述
这段工作流包括三部分核心操作。
grafts
文件配置:通过grafts
移植文件配置,删除本地版本库的父节点提交。filter-branch
操作:使用filter-branch
命令可以复制版本库中所有的提交并批量修改他们,修改后可以将原父节点关系可以被永远删除。alternates
操作:通过alternates
代替文件配置,链接不同版本库的提交。
如图上部所示本章示例项目版本库结构概况,有3个提交 A 、B 、C 。 其中 C 的历史将被移除作为演示。
首先,借助grafts
命令,我们对提交C做修改,将其父节点提交删除。创建一个新的版本库,再使用 filter-branch
命令只得到修改后不含父节点的提交C。外包历史的操作就完成了。这些部署都只发生在新的项目中,所以之前的版本库可以作为档案留存。
为了搜索整个项目历史,档案版本库应被alternates
命令链接到新的项目中,再使用grafts
命令,将提交C连接到正确的父节点(见图底部)。
2️⃣ 使用要求
- 一致切断时:要求所有的项目成员一致同意并同步进行版本库历史切断操作,并在新的克隆上开展后续工作。
- 项目历史罕有需要:当项目历史频繁被许多人查询使用时,应该接受它占用较多的资源,而不需要去执行外包操作。
- 提交的散列值不被在意:Git提交的散列值可以被用来监测对旧版本未授权的更改。但是这样外包操作会切断历史,创建新的提交,使散列值改变。
3️⃣ 执行过程及其实现
这段工作流的目的是精简有过长历史,过多文件的版本库大小。旧的版本提交将被外包成一个独立的档案版本库,搜索历史操作依然可以执行。
3.1 外包项目历史
这段操作过程详细地描述了一个版本库历史如何被外包。更具体地来说,是建立一个新的版本库只含截断的版本历史。
注意! 原版本库的克隆在新的版本库中将不能使用。因此所有的开发修改工作都必须合并入中央版本库之后才进行外包操作,也应通知所有的开发者不再在原版本库的克隆上继续提交修改。
操作开始时项目结构如上图顶部所示。这个示例是一个简单的版本库,在主分支上有3次版本提交。新的项目版本库将从提交C开始创建。
通过下面这条log
命令,我们得到了提交B和提交C的散列值。
bash
> cd project.git
> git log --pretty=oneline
166a7e047a85b318720dc6e857a5321f9a3df7b4 C
dcbddd5cd590de3d30elecca1882c9187e7eab95 B
577b8e2cf613c43ed969453477fadc189482c1fb A
-pretty=oneline
参数指定输出格式是同一条日志输出为一行,与-oneline
参数指定的效果不同的是其结果中的散列值不会被截断。
-
第1步:创建移植标签
这是一步为集成档案版本库的准备工作。为了后续集成档案版本库的操作,需要预先知道历史前序节点的散列值。在上述示例中,就是提交B。较好的解决方案是在即将成为新项目第一个提交的C处创建一个标签(grafts/master),将这个散列值信息储存在标签注释中。
这个标签会被带到新的项目版本库中。不应该将标签创建在提交B处,因为这个提交节点会被新项目版本库排除在外。
创建命令
tag
可以标识提交C的散列值,也可以将提交B的散列值储存在标签描述中。bash> git tag -a grafts/master 166a7e047a85b318720dc6e857a5321f9a3df7b4 -m "Predecessor:dcbddd5cd590de3d30elecca1882c9187e7eab95"
这里,
grafts/master
是新建的标签名。在Git中创建标签名和分支名时是允许使用斜线符号"/"来表示层次关系的。如果现实中项目有多个分支,那么需要在每个分支重复以上操作。这就是说需要为每一个分支分别决定在何处截断历史,并创建一个新的标签
grafts/<branch-name>
来储存前序提交节点信息。 -
第2步:创建一个克隆
接下来的操作会永久的更改版本库内容。因为我们仍需要保持原版本库不变以作为档案版本库,所以必须创建一个克隆版本库。另外这样会使待操作版本库成为没有工作空间的裸版本库,方可使用
push
命令。bash> cd .. > git clone --bare project.git temp-project.git
-
第3步:通过grafts文件来转换历史
现在开始在克隆版本库上删减版本历史。
这步操作需要创建一个
info/grafts
文件并编辑它,文件info/grafts
的格式十分简单。每一行标识一条提交的前序提交关系。因此只会依次记下当前提交的散列值、空格、前须提交的散列值。如果这一行的第二个散列值为空,那说明这次提交没有前序提交。在我们的示例中,提交C将没有前序提交。所以这一操作将创建一个新的grafts文件,并将提交C的散列值写入:
bash> cd temp-project.git > echo 166a7e047a85b318720dc6e857a5321f9a3df7b4 >info/grafts
如果正在操作的项目有多个分支,那么需要为那个分支增加一行。检查标识成功,可以用
log
命令来查看。在我们的示例中,只会显示出提交C的信息。bash> git log --pretty=oneline 166a7e047a85b318720dc6e857a5321f9a3df7b4 C
-
第4步:永久性的改变版本库
在通过 grafts 文件调整过后,可以用
filter-branch
命令创建一条新的永久提交。这条命令可以取得指定分支的所有提交,并按照规则过滤选取部分提交来重新提交。在本文示例中, 不需要设置特定的过滤器,因为这次提交的唯一目的是根据 grafts 文件修改提交历史。这里需要用到
--tag-name-filter
参数,将已有的标签带到新的提交中。bash> git filter-branch --tag-name-filter cat ---all Rewrite 166a7e047a85b318720dc6e857a5321f9a3df7b4(2/2) Ref 'refs/heads/master' was rewritten Ref 'refs/tags/grafts/master' was rewritten WARNING: Ref 'refs/tags/release-1' is unchanged Ref 'refs/tags/release-2' was rewritten grafts/master -> grafts/master (166a7e047a85b318720dc6e857a5321f9a3df7b4 -> 259ee224ac1f2d73898ec2ed25ad4dccd3c40f70) release-1 -> release-1 (577b8e2cf613c43ed969453477fadc189482clfb -> 577b8e2cf613c43ed969453477fadc189482clfb) release-2 ->release-2 (166a7e047a85b318720dc6e857a5321f9a3df7b4 -> 259ee224ac1f2d73898ec2ed25ad4dccd3c40f70)
参数配置如下所示。
--tag-name-filter cat
: 表示所有的标签都讲重新创建并指向新的提交。-all
: 表示作用于版本库中所有的分支。
可以看到
filter-branch
的输出结果显示散列值为 166a7 的提交C 被选取并重新提交为散列值 259ee。在输出中可以看到一条警告 (warning), 名为 release-1 的标签不符合新的提交历史。因为在调整前的版本库中该标签绑定到了提交A。现在提交A 已经不存在与新的提交历史中了。
这些标签需要手动删除,否则他们最终会影响Git 删除相应的版本提交操作。
bash> git tag -d release-1
-
第5步:缩小版本库
在这个阶段,版本库已经完成转换新历史记录的整理。但是
filter-branch
命令尚未删除不再需要的旧版本提交,原因是他们还被引用着。因此版本库并没有比整理前缩小。再次克隆版本库,会得到一个只包含新历史的版本库。之后,可以删除上述操作中产生的中间临时版本库。
bash> git clone --bare temp-project.git new-project.git > rm -rf temp-project.git
新的版本库可以使用
gc
命令压缩。这一操作会执行各种清理删除工作,其中包括压缩新文件,删除它指向不可用对象的引用。bash> cd new-project.git > git gc --prune
参数
--prune
表明所有所有文件不再需要的旧版本都必须被清除。至此,新的版本库开始对所有开发者可用,供克隆和使用。
3.2 链接到当前活动版本库
如果仍需要接触历史版本信息,当前版本库必须链接到档案版本库。这只是一条本地链接,可以被每个开发者独立使用激活。
以下步骤中,假设一个开发者已有对新版本库的克隆,并有了一条新的版本提交 D 。
- 第1步:克隆档案版本库
为了得到历史信息,需要克隆档案版本库。因为档案版本库不再有开发工作,裸 (bare)克隆即可。
bash
> git clone --bare project.git archive-project.git
-
第2步:链接档案版本库
档案版本库的提交需要在开发者本地版本库中设置为可用。
为了在一个版本库中读取其他版本库,需要在
.git/objects/info/alternates
文件中设定候选路径。该文件中每行都指向其他版本库中对象的绝对路径。请注意,必须指向该对象的文件目录,仅仅指向该对象所在的版本库根目录是不够的。
使用
echo
命令在该文件中添加新的一行候选路径。bash> cd new-project > echo /gitrepos/archive-project.git/objects >> .git/objects/info/alternates
-
第3步:连接到历史版本
最后,使用上文已经熟悉使用过的
.git/info/grafts
文件,将提交C '链接到档案版本库的提交B。这种情况下, grafts 文件中准备好的标签将有效的提供链接必须的信息。(参见外包项目 历史第一步操作)。
bash> git show grafts/master --pretty=oneline > tag grafts/master Predecessor: dcbddd5cd590de3d30elecca1882c9187e7eab95 259ee224ac1f2d73898ec2ed25ad4dccd3c40f70 C diff --git a/foo.txt b/foo.txt
可以看到两个提交的散列值,第一个 dcbdd 指向了正确的历史前序提交 B, 第二个 259ee 指向当前版本库最新的提交C。
在 grafts 文件中散列值应该是倒序。第一个是提交 C', 接下来空格,和前序提交 B。
bash> echo 259ee224aclf2d73898ec2ed25ad4dccd3c40f70 \ dcbddd5cd590de3d30eleccal882c9187e7eab95 \ > .git/info/grafts
为了验证以上操作效果,可以用
log
命令,查看输出的记录将包括提交A 和提交B。bash> git log --pretty=oneline da8ba94d6bd9ec293f22a558756a91927f8b3525 D 259ee224ac1f2d73898ec2ed25ad4dccd3c40f70 C dcbddd5cd590de3d30elecca1882c9187e7eab95 B 577b8e2cf613c43ed969453477fadc189482clfb A
至此,所有的历史信息都可以在当前开发版本库使用。
为什么不获取档案版本库(而是采用链接) ?
本章工作流步骤描述了使用
objects/info/alternates
文件来配置链接到版本提交。另外一种解决方案是通过fetch
命令来获取导入这些版本提交,然后同样可以通过grafts文件配置创建提交之间的父子关系。尽管如此,本章工作流更适用于较少的频率临时性的使用历史信息,在这种情况下,使用 alternates 文件来配置更有效率,因为本方案不会增加提交数,扩大当前版本库。
⏪ 温习回顾上一篇(点击跳转) :
《【Git教程】(十九)合并小型项目 --- 概述及使用要求,执行过程及其实现,替代解决方案 ~》