【问题标题】:How do you keep branches sharing the same history through rebases?你如何通过变基保持分支共享相同的历史记录?
【发布时间】:2017-12-16 23:58:41
【问题描述】:

如果我从这段历史开始:

* 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 提交。我如何让他们有相同的历史?

【问题讨论】:

  • stackoverflow.com/a/45844847/6309 看起来很有希望。
  • 请注意,通常不可能保留结构:例如,如果将 3ca2 压缩为 5ce5 会怎样?之后stage0 应该指向哪里?
  • @DavisHerring:我认为逻辑上正确的答案是像git filter-branch 一样处理这个问题:制作旧=>新提交哈希的映射,并根据映射条目替换标签。压缩两个提交意味着两个旧提交都映射到一个新提交。拆分提交会将旧提交映射到新提交中的第一个。不过,现有的 rebase 代码无助于制作这些地图。

标签: git git-rebase


【解决方案1】:

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

这里的根本问题是git rebase 通过复制 提交工作,就像通过git cherry-pick 一样(通过交互式变基,您可以在此过程中进行一些额外的更改)。2 新副本具有新的不同哈希 ID。3 复制完成后,Git 会重新指向 一个 分支名称 - 当前 分支,无论你何时开始 git rebase ——到最后一次这样的复制提交。

换句话说,在这种情况下,你有 Git 副本:

  • 0c8487b - foo
  • 5ce596d - (stage0) go to stage 0
  • 3ca2d7d - do something
  • ...
  • 505421e - (HEAD -> stage4) go to stage 4

按此顺序(从最早到最新),一次一个。在第一步中,您进行 一些 更改——实际上并不重要,只要你改变一些东西——以便新的提交获得一些新的、不同的哈希 ID,例如 cccccc1 (实际上可能不是这样,但这让我们将其称为“新提交 1”)。这个新提交的父提交是ccfb7c9,也就是标记为bar 的提交,所以新的历史在那个时候重新加入了旧的历史。

然后,Git 复制第二个提交,5ce596d - (stage0) go to stage 0,例如,变成cccccc2cccccc2 的父级是 cccccc1,仅它就与 5ce596d 有很大的不同,以迫使这是一个不同的提交。 Git 继续复制所有八个提交,最后一个可能变为cccccc8;然后 Git 将名称更改为 stage4,以便将其命名为 commit cccccc8

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

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

简单而暴力的解决方案是手动强制您想要更改的任何名称,以指向新的提交。运行 git log --all --decorate --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 都严格由其内容决定,因此,如果“副本”与原始对象 100% 相同——一点一点相同——你最终会得到相同的哈希 ID,即您只需重新使用原始 ID。但是,一旦您进行任何更改,序列中某些提交的哈希 ID 就会更改,这会强制每个“下游”(子)提交具有不同的 哈希 ID,这会改变每个下游提交也是如此。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-14
    • 2010-11-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多