Git 如何通过回扣保持分支机构共享相同的历史记录?

Git 如何通过回扣保持分支机构共享相同的历史记录?,git,git-rebase,Git,Git Rebase,如果我从这段历史开始: * 505421e - (HEAD -> stage4) go to stage 4 * 307978f - (stage3) go to stage 3 * d8ab213 - steb b * 49cbef9 - (stage2) step a * 6e4a2ed - (stage1) go to stage 1 * 3ca2d7d - do something * 5ce596d - (stage0) go to stage 0 * 0c8487b - foo

如果我从这段历史开始:

* 505421e - (HEAD -> stage4) go to stage 4
* 307978f - (stage3) go to stage 3
* d8ab213 - steb b
* 49cbef9 - (stage2) step a
* 6e4a2ed - (stage1) go to stage 1
* 3ca2d7d - do something
* 5ce596d - (stage0) go to stage 0
* 0c8487b - foo
* ccfb7c9 - bar

我做了
git-rebase-I ccfb7c9
来更改
foo
提交,然后分支
stage0–stage3
将不再具有与
stage4
相同的提交历史,并且不会有更新的
foo
提交。我如何让他们有相同的历史

你说得对。不幸的是,Git中没有任何内置的东西能够以“正确的方式”做到这一点。在这里定义“正确”有点困难,尽管我在mind1中有一个特定的定义,并开始编写一个程序来完成它。这对所有奇怪的角落案件来说太难了,我放弃了

这里的基本问题是,
git-rebase
通过复制提交来工作,就像通过
git-cherry-pick
(通过交互式rebase,您可以在此过程中进行一些额外的更改)。2新副本具有新的、不同的散列ID。3一旦创建了副本,git将一个分支名称重新指向当前分支,无论是什么时候开始的
git-rebase
-到最后一次这样的复制提交

换句话说,在本例中,您拥有Git copy:

  • 0c8487b-foo
  • 5ce596d-(阶段0)进入阶段0
  • 3ca2d7d-做点什么
  • 505421e-(HEAD->stage4)进入第4阶段
按照这个顺序(从早到晚),一次一个。在第一步中,您进行一些更改实际上并不重要,只要您更改一些内容,以便新提交获得一些新的、不同的哈希ID,例如
ccccccc1
(实际上可能不是这样,但这让我们将其称为“新提交1”)。此新提交的父提交是
ccfb7c9
,它是标记为
bar
的提交,因此新历史在该点重新加入旧历史

然后Git复制第二次提交,
5ce596d-(stage0)转到stage 0
,也就是说,
cccc 2
CCCCCC 2
的父级是
CCCCCC 1
,它与
5ce596d
完全不同,从而强制将其作为不同的提交。Git继续复制所有八个提交,最后一个可能成为
ccccc8
;然后Git更改名称
stage4
,从而命名commit
ccccccc8

因此,当您现在从Git通过名称
stage4
找到的提交开始查看历史时,您会看到新的历史。但是Git没有改变任何其他名称:
stage3
,例如,仍然标识commit
307978f
,而
stage0
仍然标识commit
5ce596d
。因此,如果您从这些名称中的任何一个开始查看历史记录,就会看到原始的提交序列

您需要的是让Git将每个标签从其原始哈希移动到其新哈希。问题在于识别所有这些东西:哪些标签应该移动?(您可能希望一些人故意保留旧的提交,而另一些人移动。)因此,哪个提交是正确的新提交?如果在交互重基期间,我选择拆分一些提交并合并其他提交,该怎么办

简单而暴力的解决方案是手动强制您想要更改的任何名称指向新的提交。运行
git log--all--decoration--oneline--graph
并记下每个名称的新旧提交ID,然后运行:

git branch -f stage0 newhash0
git branch -f stage1 newhash1
git branch -f stage2 newhash2
git branch -f stage3 newhash3
你完成了。或者,只需完全删除其中一些分支名称,因为您可以通过从
stage4
(自动移动)开始并向后操作来查找提交


1我喜欢的定义包括对分支名称进行结构化,这样名称的语义就不仅仅是“提交的原始指针”。这种结构的形式也很困难:它应该使用名称层次结构,还是应该使用
git config
条目将分支名称分组到“超级分支”中

2有些
git-rebase
命令实际运行
git-cherry-pick
,有些则不运行。交互式rebase是真正运行
git cherry pick
以及
git commit--amend
和其他棘手项目的案例之一


3任何Git对象的散列ID都严格由其内容决定,因此,如果“副本”与原始比特完全相同,那么最终会得到相同的散列ID,也就是说,您只需重复使用原始。但是,只要您进行任何更改,序列中某些提交的哈希ID就会更改,这迫使每个“下游”(子)提交都有一个不同的父哈希ID,这也会更改每个下游提交。

看起来很有希望。请注意,一般来说,保存结构是不可能的:如果将3ca2压缩为5ce5,例如
stage0
之后应该指向何处?@DavisHerring:我认为逻辑上正确的答案是用与
git filter branch
相同的方法处理这个问题:为旧的=>新的提交散列创建一个映射,并根据映射条目替换标签。挤压两个提交意味着两个旧提交映射到一个新提交。拆分提交将旧提交映射到新提交的第一个提交。但是,现有的重基代码无助于生成这些映射。