将分支变基到另一个分支/变基并压缩提交历史

3
这是我经常遇到的情况,我正在寻找一个干净的解决方案。
1. 在git分支(`my-first-work`)上进行一些工作 2. 推送到GitHub 3. 基于`my-first-work`工作创建一个新的分支(`some-further-work`)
未指定时间过去了
4. 审批人在`master`上对`my-first-work`进行了变基或压缩 5. 这会创建新的提交,即使最终结果相同,git也不会将其视为等效于我所依赖的内容(即`master`的头与`my-first-work`的头相同) 6. 我运行`git rebase master`将`some-further-work`移动到`master` 7. 我所依赖的来自`my-first-work`的提交现在都与已经被压缩/合并的内容冲突
目前,我的解决方法是使用`git rebase -i master`,然后删除所有提交直到`my-first-work`的头。这样可以在`master`顶部重新播放附加的提交。
是否有更简洁的解决方案?还有没有办法让git自动识别变基/压缩(如4)已发生?
1个回答

3
Cleaner(整洁)是在观察者(或工作者)的眼中(或手中),但您可以使用git rebase --onto要复制的提交放置副本的位置分离出来。

请记住,git rebase的意思是:1我有一条线性的提交链,如图1所示,我想将其复制到一个新的线性的提交链,如图2所示。完成副本后,我希望我的分支名称指向最后一个复制的提交。原始的A-B-C链即使仍然存在也不再有用。

[drawing 1]
...--o--*--o   <-- upstream/master
         \
          A--B--C   <-- topic

[drawing 2]
...--o--*--o   <-- upstream/master
            \
             A'-B'-C'  <-- topic

最初的提交和副本之间的区别在于,原始提交基于提交*,它曾经是upstream/master的最新提交。而副本则基于(新的)upstream/master的最新提交。这里的“基于”一词实际上有两个含义:提交A的父提交是*,而提交A'的父提交是后来的提交;提交A中的快照是*+一些更改,而提交A'中的快照则将相同的更改添加到了后来的提交中。
由于我们使用了新的提交A'(具有不同的哈希值),而不是旧的A,因此我们需要将B复制为B',将C复制为C',并且完成后,我们需要使名称topic指向最后一个复制的提交,即C',而不是C
普通的git rebase正好可以做到这一点。我们说:
git checkout topic; git rebase upstream/master

这告诉Git:
  1. C开始向后枚举所有提交。 这是C,然后是B,然后是A,然后是*,最后是*之前的所有内容。
  2. upstream/master开始向后枚举所有提交。 这是第二个o,然后是*,最后是*之前的所有内容。
  3. 从第一个列表中删除第二个列表中的所有内容。 这样就删除了*和之前的所有提交。第二个o不在第一个列表中,但这没关系:如果它在其中,我们会将其删除,但它现在不在,所以我们什么也不做。现在我们的列表变成了CBA
  4. 反转列表以使其按正确顺序排列,然后逐个将每个提交复制到新位置。新位置从upstream/master指向的提交开始。 因此,这将A复制到A',将B复制到B',将C复制到C'
  5. 从其先前位置上分离当前分支名称topic,并像往常一样将其放在新的提交链的末尾。 这将使topic指向C'而不是C

但在您的新情况下,您有:

...--o--*   <-- upstream/master
         \
          A--B--C   <-- feature1
                 \
                  D--E--F--G   <-- feature2

他们在上游没有采用你的 A-B-C 链。相反,他们制作了自己不同的 ABC 压缩提交。你从上游存储库中获取了它,所以现在你有:

最初的回答已经过时,他们没有采用你的修改,而是创建了一个新的压缩提交。

...--o--*--ABC   <-- upstream/master
         \
          A--B--C   <-- feature1
                 \
                  D--E--F--G   <-- feature2

如果您只运行git checkout feature2; git rebase upstream/master,您的Git将枚举提交G-F-E-D-C-B-A-*-...,枚举ABC-*-...,从第一个中减去第二个,并留下复制G-F-E-D-C-B-A链的指令。
更高级的rebase命令是:
git checkout feature2
git rebase --onto upstream/master feature1

这样做的作用是将Git开始复制的目标参数(target)与限制参数(limit)分离。现在的targetupstream/master(Git文档称其为onto参数)。限制参数现在是feature1。如果您愿意,可以使用提交C的原始哈希ID。Git只需要知道:我从哪里开始枚举这些提交?(令人困惑的是,Git文档将此称为upstream参数。)
正如您所看到的,现在它会删掉C-B-A-*提交而不仅仅是*提交,因此复制后,您将得到:
               D'-E'-F'-G'  [in progress]
              /
...--o--*--ABC   <-- upstream/master
         \
          A--B--C   <-- feature1
                 \
                  D--E--F--G   <-- feature2

现在Git可以将标签feature2G上移除,并粘贴到G2上。


1技术上讲,git rebase还有很多更复杂的用法,特别是新加的--rebase-merges选项。但是,它主要用于进行线性提交链

额外的好处是,rebase通常可以判断出他们是否已经将你的A-B-C链复制到他们自己的A'-B'-C'链中。但这只是通常情况。对于他们将你的A-B-C压缩成他们自己的ABC的情况,rebase则无法判断,所以需要使用--onto选项。


太棒了。非常清晰的答案和出色的解释。 - deworde

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接