mancuoj / TIL

今天学了什么,记录在 issue 中
1 stars 0 forks source link

GitHub 合并 PR 的三种策略 #1

Open mancuoj opened 3 months ago

mancuoj commented 3 months ago

一般来说,我们创建 PR 会有一个通用的工作流程:

  1. 创建一个分支,比如 feature
  2. 提交 PR
  3. 让别人 review
  4. 根据 review 意见修改
  5. 合并 PR 到主分支

但这个 feature 分支可能会有很多次提交,如何合并到主分支就成为了一个问题,GitHub 会给你三个选项:

image

mancuoj commented 3 months ago

Create a merge commit

创建一个连接两个分支历史记录的“合并提交”

image

我们可以在本地模拟图中这个过程,首先在主分支创建三个提交,使用 git log --oneline --graph 可以看到提交记录如下:

* b8472e4 (HEAD -> main) add c.txt
* 2db7159 add b.txt
* b5b52d8 add a.txt

然后在新的分支 git checkout -b feature 创建两个提交:

* 9770b82 (HEAD -> feature) add e.txt
* 4e65cc4 add d.txt
* b8472e4 (main) add c.txt
* 2db7159 add b.txt
* b5b52d8 add a.txt

切回主分支,使用命令合并:

git checkout main
git merge --no-ff feature

再次查看记录:

*   7da24fd (HEAD -> main) Merge branch 'feature' into main
|\  
| * 9770b82 (feature) add e.txt
| * 4e65cc4 add d.txt
|/  
* b8472e4 add c.txt
* 2db7159 add b.txt
* b5b52d8 add a.txt

如上,GitHub 在幕后使用 git merge --no-ff 的默认选项会创建一个连接两个分支历史记录的合并提交。

这种方法的优点就是保留了所有的历史记录以及各个时间节点,缺点就是多分支或者长期分支会一团乱麻。

mancuoj commented 3 months ago

Squash and merge

将分支中的所有提交压缩替换为单个提交后合并

image

依旧来尝试本地模拟,首先 git reset --hard b8472e4 回到合并前:

* b8472e4 (HEAD -> main) add c.txt
* 2db7159 add b.txt
* b5b52d8 add a.txt

使用 git merge --squash feature 压缩合并,然后编写适当的提交消息。

最后主分支的提交记录如下:

* 52c548d (HEAD -> main) add d.txt and e.txt
* b8472e4 add c.txt
* 2db7159 add b.txt
* b5b52d8 add a.txt

可以看出这种方法的优点就是主分支历史清晰且线性,一个功能用一个提交解决,但是缺点就是丢失了合并以及各个独立提交的上下文信息,甚至没法知道这个分支到底合并没有。

另一种 Squash 的方法

使用交互式 rebase 命令实现更细致的操作:

git checkout feature
git rebase -i main

选择 squash 将后面的提交融合到前一个提交:

pick 4e65cc4 add d.txt
squash 9770b82 add e.txt

编写适当的提交消息,此时 feature 分支提交记录变为:

* f37e60f (HEAD -> feature) add d.txt and e.txt
* b8472e4 (main) add c.txt
* 2db7159 add b.txt
* b5b52d8 add a.txt

最后回到主分支合并即可(当然你也可以用 cherry-pick 合并单独或组合的提交):

git checkout main
git merge feature

Bonus

这种搞坏提交记录的操作怎么恢复?

git reflog
# 你会看到你做的所有 git 操作,找到搞坏的前一步 reset 即可
git reset HEAD@{index}
mancuoj commented 3 months ago

Rebase and merge

所有提交都会被添加合并到主分支的“顶部”

没图,但是可以从描述中了解到主分支最后的结果应该是:

image

恢复分支状态,然后执行以下操作:

git checkout feature
git rebase main

git checkout main
git merge feature

完成后主分支提交记录如下:

* 9770b82 (HEAD -> main, feature) add e.txt
* 4e65cc4 add d.txt
* b8472e4 add c.txt
* 2db7159 add b.txt
* b5b52d8 add a.txt

优点是主分支历史清晰线性,没有多余的合并提交,其他分支的提交历史也会直接保存在主分支记录里。

缺点与 squash 合并类似,并且更多小提交可能会影响对大局的注意力。

mancuoj commented 3 months ago

建议

所有的选择都是 trade-off 的结果,应该权衡这几个关键的因素:

我只有一个建议:多提交,多写 PR,不要堆积代码,更小的代码片段意味着更容易的 code review,更简单的单元测试以及更符合单一职责原则的代码。

mancuoj commented 3 months ago

参考

Heqile666 commented 2 months ago

very good article, let me rotate