CherryPick 和 Bisect

9/14/2022

# 一、cherry pick

cherry-pick 用于将指定的提交,应用到当前分支。

它支持pick单个提交、多个提交、甚至区间的提交:

# pick 单个提交
git cherry-pick <commitId>
# pick多个提交
git cherry-pick <commitId-A> <commitId-B>
# pick 区间用..连接
git cherry-pick <commitId-A>..<commitId-B>

但是pick过来的提交会生成新的commitId。

# 1.遇到冲突

如果遇到冲突,解决方式和rebase比较类似。

  • 可以放弃 git cherry-pick --abort

  • 可以解决完冲突后执行 git cherry-pick --continue

# 2.后续是否有冲突

  • 假如我们从分支 X pick了一个commitId为 A 的提交
  • pick过来后commitId 变成了A1
  • 如果我们后续将X分支merge了进来,是否会有冲突?

答案是可能有。

这取决于在A中提交的代码,后续是否有改动:

  • 后续没有任何改动,尽管commitId不同,也不会有冲突

  • 后续做了改动,那就会有冲突

# 3.适用场景

当需要从一堆提交里pick能用的提交的时候,就是用cherry-pick的时候。

cherry-pick适用于只需要部分提交的场景,比如需要其他分支的某个bug-fix,或者想要其他分支的某个公共类库。

而假如想要整个分支,应当优先用merge。

# 4.特殊场景

开发的多个功能只能上一部分” 这个场景不一定适合用 cherry-pick

一般我们不会在一个分支上开发多个功能。

如果出于某种考虑,确实在一个分支中开发了多个功能,那么一般提交会很多,而且其中很多提交都涉及多个功能。

如果能准确地挑选出目标commit,那么cherry-pick 确实为不二之选。

考虑到大多数人含糊的注释和频繁交错的提交,所以更多情况下无法做到准确挑选。

这种情况下靠cherry-pick 来选择目的提交,将会是一个繁琐且无法保证效果的体力劳动

那么这种情况怎么做?

我们不妨继续考虑一下这个开发中的分支的后续归宿:

  • 那部分要上的功能是否还需要补充开发?
  • 不上的那部分是永远不上,还是继续开发?

这样一考虑,理想的做法是把这个分支拆成两个部分,互不影响。具体做法如下:

  • 假如当前开发中的分支名 dev,我们从master新建一个分支 dev2
  • 然后使用 git merge --squash dev 将开发中的代码一次性合并到 dev2
  • 然后在 dev2 中把不需要的功能删掉,用dev2 上线

所以整个过程跟cherry-pick 无关,只是用到 merge squash.

而另一部分如果要继续开发可以在原来分支开发,也可以参照dev2再建一个分支。

# 二、git的二分搜索 bisect

我觉得这是一个:乍一听令人惊叹,细一想作用有限的命令。

这是git 提供的一套连续的命令,用来定位有问题的代码首次出现在哪次提交

我们要先知道有问题的代码是什么,然后通过bisect的一套组合拳定位出问题的提交。

但是定位这个提交一般情况下并没有什么用,毕竟找到bug直接改了就是了,追究谁提交了对于改bug帮助不大。

# 1.原理和操作

这是一个二分查找思想的应用。

假设我们要找到第一次出问题的提交X,操作步骤如下:

  1. 首先启用 bisect 并界定起止提交
  2. 然后git 会帮我们定位到中间节点(像二分查找一样),并且会将代码还原到该提交下的快照
  3. 判断代码是否异常:如果异常输入 git bisect bad, 如果正常输入 git bisect good
  4. 不断重复2,3步骤,直到git定位到首次有问题的提交
  5. 使用 git bisect reset 退出

# 伪代码

# 界定起止提交
# 其中 begin 是最近一次的提交(有问题的),end是更久之前的提交(没问题的)
git bisect start <commitId-begin> <commitId-end>
# 人工判断代码是否正确 正确的标记good 错误的标记bad
git bisect bad
git bisect good
#...重复上述步骤,直到git 给出结论
c5af8ddb7e44c06c897e is the first bad commit

# 退出
git bisect reset

其中 begin 是最近一次的提交(有问题的),end是更久之前的提交(没问题的)。

# 效果图

# 2.遇到merge的情况

假如遇到分支合并了,bisect 也能解决。

假设X是第一次有问题的提交,F是最近提交。提交树如下:

A->B->C->D->E->F
 \         /
   N->X->M 

而现在我们不知道X就是要找的目标提交,我们可能会将起止点设置为 F 和 C。

即便是如此,经过不断二分,git 也能定位到 X。

# 3. 误解

网上很多文章觉得这个命令可以帮我们找到bug。倒不是不行,只是正常情况下我们排查bug应该不会是上述步骤

问题出现在第2步的操作可行性:关于如何判断代码是否异常。

如果是前端或者Python这种脚本语言还好,毕竟执行和跑单测都很快,所以很容易判断功能是否异常。

但是如果是Java这种编译一次十来秒,甚至还要临时更新依赖的程序,这个过程简直漫长得不可忍受。

常规的debug操作,应该是使用异常用例,通过打断点来单步调试。而脚本语言的调试,也是通过异常用例打断点/日志调试更快一些。

只能说bisect功能虽然很强大,但作用有限。