git 交互式变基压缩到下一次提交

git interactive rebase squash into next commit

在 Git 中,我可以使用交互式 rebase 来重写历史,这很棒,因为在我的功能分支中,我在探索不同的重构和获取方式时使用部分工作代码进行了大量提交完成了。

我想在将分支变基或合并到 master 之前将很多提交压缩在一起。

有些人按照从第一个(顶部)到底部(最后一个)的顺序进行提交

1. Initial commit on feature branch "Automatic coffee maker UI"
2. Add hot chocolate as product
3. Add tea as product. Products are now generic
4. Create in memory data store for adapter tests
5. Cry because I can't get entity framework to create a composite key. Integration tests broken.
6. Implemented composite key!!
7. All tests green and feature done!

假设我想保留提交 3、4 和 7。

使用 rebase 我想 "squash" 提交

理想情况下我会在交互式变基中做

1. squash
2. squash
3. pick (contains the work of 1 & 2)
4. pick 
5. squash
6. squash
7. pick (contains the work of 5 & 6)

但这是倒退的,因为 squash 将提交与其之前的提交合并。我不知道如何让它向前挤压。

我是不是很难相处,我应该接受这行不通(我宁愿让它工作),还是有办法做到这一点?

我用

调用这个命令
git checkout My-feature-branch
git rebase master -i

然后我正在编辑出现的提交列表,并尝试通过保存文件和编辑编辑器来完成它,这通常对我有用。

如果可行,您还需要对提交进行重新排序,以便待保留的提交先于待压缩的提交。

如果这不可行,因为那样你会遇到不想解决的冲突,那就去做吧

1. pick
2. squash
3. squash
4. pick 
5. pick
6. squash
7. squash

压缩完成后,您可以编辑提交消息以包含您希望最终提交具有的消息。非常简单。 :-)

你甚至可以做到

1. pick
2. fixup
3. squash
4. pick 
5. pick
6. fixup
7. squash

然后我认为应该只启动一次提交消息编辑器,就像修复一样,只是在不启动编辑器的情况下获取上一个提交消息。

在提交消息编辑器触发时进行压缩时,您还会收到两条提交消息,即来自待压缩提交和待压缩提交的消息,因此您可以简单地删除提交消息你不想保留。

是对的,但我想提供一个不同的视角。我认为这是你让自己过度紧张的地方:你开始于:

Lets say I want to keep commits 3, 4 and 7.

然后添加:

  • 1 and 2 go into 3.
  • 4 stays
  • 5 and 6 go into 7

但这意味着您要保留 all 的 1、2、3、4、5、6 和 7 的(内容)...只是不像 单独提交。交互式变基 squash 并不意味着 "throw away",甚至 "throw away commit message",它只是意味着 "combine"。如果动词是 "combine" 或 "meld" 或 "mix-in" 或类似的动词可能会更好。所以序列(pick, squash, squash)意味着:保持所有三个,同时按顺序应用它们,然后从中做出一个大的提交。

如前所述,一旦 Git 从中做出一个大提交,Git 给你另一个机会将三个合并的提交消息编辑为一个大提交消息。

变基完成后,您没有保留 任何 原始提交。相反,您已经进行了 new 提交。新的第一次提交是如何从各个部分组装起来的并不重要,重要的是最终来源是什么,以及你在一个大提交消息中输入了什么。

你想要的是:

1. pick
2. squash
3. fixup
4. pick
5. squash
6. squash
7. fixup

然后当编辑器中出现新的组合提交时,删除所选提交的行并保留修复。生成的功能补丁应该具有该系列中最后一次提交的提交消息。

确实有可能在交互式变基期间将提交压缩到 后续 提交中,并完全保留其中第二个提交的身份(包括作者、日期等) .

然而,该方法有些复杂,因此仍然感谢 git rebase -i 的原生支持。

我将仅用三个提交 aaaaaaa Abbbbbbb Bccccccc C 进行演示,我们希望将 A 折叠成 B 并保留 B的身份:(该方法很容易推广到更多提交)

  • git rebase -i aaaaaaa^
  • 修改编辑脚本在提交后停止B并退出编辑器:
    pick aaaaaaa A
    edit bbbbbbb B
    pick ccccccc C
    
  • 当变基在 B 后停止时,恢复最新的提交 两次,然后继续:
    git revert HEAD
    git revert HEAD
    git rebase --continue
    
    因为还原 revert 会恢复以前的状态,所以所有后续提交都将干净利落地应用。 BRevert "B" 的顺序在一起没有效果,但这些提交中的第一个带有提交 B 的完整身份(事实上,在这个阶段它仍然是 提交 B)。
    BRevert "B" 可以被压缩在一起形成一个无操作提交,携带提交 B 的身份向前和一个单独的 rebase -i --keep-empty aaaaaaa^。这非常有用,推荐用于防止虚假合并冲突,尤其是在更复杂的情况下。但是,我们将在这里跳过该步骤,只将提交放在一起。
    现在重要的提交是 Revert "Revert "B"",它将包含最初由 B 所做的所有更改。
  • 现在 rebase -i aaaaaaa^ 再次移动 A 通过 BRevert "B",同时将 B 压入 Revert "B"A:
    pick   bbbbbbb B
    squash b111111 Revert "B"
    squash a222222 A
    squash b333333 Revert "Revert "B""
    pick   c444444 C
    
    因为 BRevert "B" 的顺序没有效果,所以您可以将任何提交移到它后面,同时只创建微不足道的可解决的合并冲突。
  • 当 rebase 因合并冲突而停止时,将 git mergetool 与可以自动解决琐碎冲突的工具一起使用,并让这些工具来解决。
  • Rebase 将再次停止,让您编辑已压缩提交的提交消息。根据您的喜好进行编辑。
  • git rebase --continue 大功告成。

我的 go-to 方法是 reset 加上 commit -C.

git rebase -i origin/main # or some other commit

这会调出您的编辑器,我将其设置为如下所示:

...
pick aafaa Merge me forward
edit bbfbb Into me
...

然后当变基停止在bbfbb时,做

git reset --soft HEAD^
git commit --amend -C HEAD@{1} # could put an explicit commit here
git rebase --continue

也可以放入单个rebase中todo-list:

...
pick aafaa Squash me forward
pick bbfbb Into me
exec git reset --soft HEAD^ && git commit --amend -C HEAD@{1}
...