替换

大约 4 分钟

我们之前强调过,Git 对象数据库中的对象是不可改变的, 然而 Git 提供了一种有趣的方式来用其他对象假装替换数据库中的 Git 对象。

replace命令可以让你在 Git 中指定某个对象并告诉 Git:“每次遇到这个 Git 对象时,假装它是其它对象”。在你用一个不同的提交替换历史中的一个提交而不想以 git filter-branch 之类的方式重建完整的历史时,这会非常有用。

让我们来试试吧。首先获取一个已经存在的仓库,并将其分成两个仓库,一个是最近的仓库,一个是历史版本的仓库, 然后我们将看到如何在不更改仓库 SHA-1 值的情况下通过 replace 命令来合并他们。

$ git log --oneline
0328eb8 (HEAD -> master, origin/master, origin/HEAD) update my_submodule.txt.
a7597da update my_submodule.txt.
a5cbd40 update my_submodule.txt.
0c7ce50 init submodule

创建历史版本的历史很容易,我们可以只将一个历史中的分支推送到一个新的远程仓库的 master 分支。

$ git branch history a5cbd40 
$ git log --oneline --decorate
0328eb8 (HEAD -> master, origin/master, origin/HEAD) update my_submodule.txt.
a7597da (history) update my_submodule.txt.
a5cbd40 update my_submodule.txt.
0c7ce50 init submodule

现在我们可以把这个新的 history 分支推送到我们新仓库的 master 分支:

$ git remote add p-h https://gitee.com/willcoder/p-h.git
$ git push p-h history:master
Enumerating objects: 9, done.
Counting objects: 100% (9/9), done.
Delta compression using up to 8 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (9/9), 773 bytes | 773.00 KiB/s, done.
Total 9 (delta 0), reused 9 (delta 0)
remote: Powered by GITEE.COM [GNK-5.0]
To https://gitee.com/willcoder/p-h.git
 * [new branch]      history -> master

这样一来,我们的历史版本就发布了。稍难的部分则是删减我们最近的历史来让它变得更小。我们需要一个重叠以便于用一个相等的提交来替换另一个提交,这样一来,我们将截断最后的提交。

在这种情况下,创建一个能够指导扩展历史的基础提交是很有用的。这样一来,如果其他的开发者想要修改第一次提交或者其他操作时就知道要做些什么,因此,接下来我们要做的是用命令创建一个最初的提交对象,然后将最后的提交变基到它的上面。

为了这么做,我们需要选择一个点去拆分,对于我们而言是第三个提交(SHA 是a5cbd40)。因此我们的提交将基于此提交树。我们可以使用 commit-tree 命令来创建基础提交,这样我们就有了一个树,并返回一个全新的、无父节点的 SHA-1 提交对象。

$ echo 'get history from blah blah blah' | git commit-tree a5cbd40^{tree}
b06ec96de666b38d61470a566634707b3033a87a

现在我们已经有一个基础提交了,我们可以通过 git rebase --onto 命令来将剩余的历史变基到基础提交之上。--onto 参数是刚才 commit-tree 命令返回的 SHA-1 值,变基点会成为第二个提交(我们想留下的第一个提交的父提交,a5cbd40):

$ git rebase --onto b06ec9 a5cbd40
First, rewinding head to replay your work on top of it...
Applying: update my_submodule.txt.
Applying: update my_submodule.txt.

我们已经用基础提交重写了最近的历史,基础提交包括如何重新组成整个历史的说明。我们可以将新历史推送到新项目中,当其他人克隆这个仓库时,他们仅能看到最近两次提交以及一个包含上述说明的基础提交。

现在我们将以想获得整个历史的人的身份来初次克隆这个项目。在克隆这个截断后的仓库后为了得到历史数据,需要添加第二个远程的历史版本库并对其做获取操作:

$ git clone https://gitee.com/willcoder/git_submodule.git
Cloning into 'git_submodule'...
remote: Enumerating objects: 21, done.
remote: Counting objects: 100% (21/21), done.
remote: Compressing objects: 100% (11/11), done.
remote: Total 21 (delta 3), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (21/21), done.
$ cd git_submodule/
$ git remote add p-h https://gitee.com/willcoder/p-h.git
$ git fetch p-h
From https://gitee.com/willcoder/p-h
 * [new branch]      master     -> p-h/master

现在,协作者在 master 分支中拥有他们最近的提交并且在 p-h/master 分支中拥有过去的提交。

$ git log --oneline master
eea6ff3 (master) update my_submodule.txt.
0f808ad update my_submodule.txt.
b06ec96 get history from blah blah blah
$ git log --oneline p-h/master
a7597da (p-h/master) update my_submodule.txt.
a5cbd40 update my_submodule.txt.
0c7ce50 init submodule

为了合并它们,你可以使用 git replace 命令加上你想替换的提交信息来进行替换。这样一来,我们就可以将 master 分支中的第四个提交替换为 p-/master 分支中的“第四个”提交。

$ git replace 0f808ad a7597da

现在,查看 master 分支中的历史信息,显示如下:

$ git log --oneline master
eea6ff3 (master) update my_submodule.txt.
0f808ad (replaced) update my_submodule.txt.
a5cbd40 update my_submodule.txt.
0c7ce50 init submodule
上次编辑于: 2024年5月11日 00:02