Git之深入解析如何替换数据库中的Git对象
Posted Forever_wj
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Git之深入解析如何替换数据库中的Git对象相关的知识,希望对你有一定的参考价值。
- 了解了管理或者维护 Git 仓库、实现代码控制所需的大多数日常命令和工作流程,尝试跟了踪和提交文件的基本操作,并且掌握了暂存区和轻量级地分支及合并的威力。如果想进一步对 Git 深入学习,可以学习一些 Git 更加强大的功能,这些功能可能并不会在日常操作中使用,但在某些时候可能还是会起到一定的关键性作用。
- 如果还不清楚 Git 的基础使用流程、分支的管理、托管服务器的技术以及分布式工作流程等相关的技术和能力,请参考博客:
- Git 对象数据库中的对象是不可改变的,然而 Git 提供了一种有趣的方式来用其他对象“假装”替换数据库中的 Git 对象。replace 命令可以在 Git 中指定某个对象并告诉 Git:“每次遇到这个 Git 对象时,假装它是其它对象”。在用一个不同的提交替换历史中的一个提交而不想以 git filter-branch 之类的方式重建完整的历史时,这会非常有用。
- 例如,有一个大型的代码历史并想把自己的仓库分成一个短的历史和一个更大更长久的历史,短历史供新的开发者使用,后者给喜欢数据挖掘的人使用。可以通过用新仓库中最早的提交“替换”老仓库中最新的提交来连接历史,这种方式可以把一条历史移植到其他历史上。这意味着不用在新历史中真正替换每一个提交(因为历史来源会影响 SHA-1 值),就可以加入他们。
- 首先获取一个已经存在的仓库,并将其分成两个仓库,一个是最近的仓库,一个是历史版本的仓库,然后我们将看到如何在不更改仓库 SHA-1 值的情况下通过 replace 命令来合并他们。我们将使用一个拥有 5 个提交的简单仓库:
$ git log --oneline
ef989d8 fifth commit
c6e1e95 fourth commit
9c68fdc third commit
945704c second commit
c1822cf first commit
- 我们想将其分成拆分成两条历史,第一个到第四个提交的作为第一个历史版本,第四、第五个提交的作为最近的第二个历史版本:
- 创建历史版本的历史很容易,可以只将一个历史中的分支推送到一个新的远程仓库的 master 分支:
$ git branch history c6e1e95
$ git log --oneline --decorate
ef989d8 (HEAD, master) fifth commit
c6e1e95 (history) fourth commit
9c68fdc third commit
945704c second commit
c1822cf first commit
- 现在可以把这个新的 history 分支推送到我们新仓库的 master 分支:
$ git remote add project-history https://github.com/schacon/project-history
$ git push project-history history:master
Counting objects: 12, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (12/12), 907 bytes, done.
Total 12 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (12/12), done.
To git@github.com:schacon/project-history.git
* [new branch] history -> master
- 这样一来,我们的历史版本就发布了,稍难的部分则是删减最近的历史来让它变得更小,我们需要一个重叠以便于用一个相等的提交来替换另一个提交,这样一来,将截断到第四、五个提交:
$ git log --oneline --decorate
ef989d8 (HEAD, master) fifth commit
c6e1e95 (history) fourth commit
9c68fdc third commit
945704c second commit
c1822cf first commit
- 在这种情况下,创建一个能够指导扩展历史的基础提交是很有用的。这样一来,如果其他的开发者想要修改第一次提交或者其他操作时就知道要做些什么,因此,接下来我们要做的是用命令创建一个最初的提交对象,然后将剩下的提交(第四、第五个提交)变基到它的上面。
- 为了这么做,我们需要选择一个点去拆分,对于我们而言是第三个提交(SHA 是 9c68fdc)。因此我们的提交将基于此提交树,可以使用 commit-tree 命令来创建基础提交,这样就有了一个树,并返回一个全新的、无父节点的 SHA-1 提交对象:
$ echo 'get history from blah blah blah' | git commit-tree 9c68fdc^{tree}
622e88e9cbfbacfb75b5279245b9fb38dfea10cf
- commit-tree 命令属于底层指令,有许多指令并非直接使用,而是被其他的 Git 命令用来做更小一些的工作。有时当我们做一些像这样的奇怪事情时,它们允许我们做一些不适用于日常使用但真正底层的东西。
- 现在我们已经有一个基础提交了,可以通过 git rebase --onto 命令来将剩余的历史变基到基础提交之上。–onto 参数是刚才 commit-tree 命令返回的 SHA-1 值,变基点会成为第三个提交(想留下的第一个提交的父提交,9c68fdc):
$ git rebase --onto 622e88 9c68fdc
First, rewinding head to replay your work on top of it...
Applying: fourth commit
Applying: fifth commit
- 我们已经用基础提交重写了最近的历史,基础提交包括如何重新组成整个历史的说明。,可以将新历史推送到新项目中,当其他人克隆这个仓库时,他们仅能看到最近两次提交以及一个包含上述说明的基础提交。
- 现在将以想获得整个历史的人的身份来初次克隆这个项目,在克隆这个截断后的仓库后为了得到历史数据,需要添加第二个远程的历史版本库并对其做获取操作:
$ git clone https://github.com/schacon/project
$ cd project
$ git log --oneline master
e146b5f fifth commit
81a708d fourth commit
622e88e get history from blah blah blah
$ git remote add project-history https://github.com/schacon/project-history
$ git fetch project-history
From https://github.com/schacon/project-history
* [new branch] master -> project-history/master
- 现在,协作者在 master 分支中拥有他们最近的提交并且在 project-history/master 分支中拥有过去的提交:
$ git log --oneline master
e146b5f fifth commit
81a708d fourth commit
622e88e get history from blah blah blah
$ git log --oneline project-history/master
c6e1e95 fourth commit
9c68fdc third commit
945704c second commit
c1822cf first commit
- 为了合并它们,你可以使用 git replace 命令加上你想替换的提交信息来进行替换。这样一来,我们就可以将 master 分支中的第四个提交替换为 project-history/master 分支中的“第四个”提交:
$ git replace 81a708d c6e1e95
- 现在,查看 master 分支中的历史信息,显示如下:
$ git log --oneline master
e146b5f fifth commit
81a708d fourth commit
9c68fdc third commit
945704c second commit
c1822cf first commit
- 很酷,是不是?不用改变上游的 SHA-1 就能用一个提交来替换历史中的所有不同的提交,并且所有的工具(bisect,blame 等)也都奏效。
- 有趣的是,即使是使用了 c6e1e95 提交数据来进行替换,它的 SHA-1 仍显示为 81a708d。 即使你运行了 cat-file 命令,它仍会显示你替换的数据:
$ git cat-file -p 81a708d
tree 7bc544cf438903b65ca9104a1e30345eee6c083d
parent 9c68fdceee073230f19ebb8b5e7fc71b479c0252
author Scott Chacon <schacon@gmail.com> 1268712581 -0700
committer Scott Chacon <schacon@gmail.com> 1268712581 -0700
fourth commit
- 请记住,81a708d 真正的父提交是 622e882 占位提交,而非呈现的 9c68fdce 提交。另一个有趣的事情是数据将会以以下引用显示:
$ git for-each-ref
e146b5f14e79d4935160c0e83fb9ebe526b8da0d commit refs/heads/master
c6e1e95051d41771a649f3145423f8809d1a74d4 commit refs/remotes/history/master
e146b5f14e79d4935160c0e83fb9ebe526b8da0d commit refs/remotes/origin/HEAD
e146b5f14e79d4935160c0e83fb9ebe526b8da0d commit refs/remotes/origin/master
c6e1e95051d41771a649f3145423f8809d1a74d4 commit refs/replace/81a708dd0e167a3f691541c7a6463343bc457040
- 这意味着我们可以轻而易举的和其他人分享替换,因为我们可以将替换推送到服务器中并且其他人可以轻松地下载。也许在历史移植情况下不是很有用(既然每个人都乐意下载最新版本和历史版本,为何还要拆分他们呢?),但在其他情况下仍然很有用。
以上是关于Git之深入解析如何替换数据库中的Git对象的主要内容,如果未能解决你的问题,请参考以下文章