likes
comments
collection
share

Git:Cherry-Pick 桃色陷阱

作者站长头像
站长
· 阅读数 5

以下内容为本人的学习笔记,如需要转载,请声明原文链接 微信公众号「ENG八戒」mp.weixin.qq.com/s/J2b130UVF…

Git:Cherry-Pick 桃色陷阱

Cherry-Pick 咋一看这名字就很时髦,用来干啥呢?

Cherry-Pick 是什么?

假设在开发某些功能或者修复 bug 的时候把代码 commit 到了错误的分支 A,并且分支 A 最新版本已经覆盖了该 commit。看到这样你不会抓狂?被逼无奈之下,你可能会这样补救一番:

首先,切换到分支 A,找到需要备份的 commit 内容,逐个备份出来,并且保存 commit 描述,然后再切换回将要接收合并的目标工作分支,接着把备份的内容逐个更新到当前分支中,最后再提交新的 commit 并且填上前面保存下来的 commit 描述。

如果更新内容庞大,没有相关指令可以自动化实现上面的操作,全程靠手动操作,那么估计 Ctrl + C/V 都会罢工。

幸运的是,Git 其实提供了这样一个命令 cherry-pick 帮助我们一步实现上面的补救措施。其命令格式是

git cherry-pick <commit-hash>

上面的命令格式中,commit-hash 是在提交 commit 时 git 自动生成的 hash 串,代表每个独一无二的 commit。

Cherry-Pick 用于从其它分支提取某些 commit,并且合并到当前工作分支,同时还会把之前提交的描述也拷贝进来。这样大大简化了拉取其他分支某个 commit 的难度。

为何要用 Cherry-Pick

在多人合作的团队项目中,管理员通常会创建一个主线分支,比如命名为 develop,然后允许各个开发者基于主线分支分叉出特性分支,并在各自特性分支上进行特性功能的开发。在特性分支上,功能开发验证完毕后,开发人员再提交 merge 合并请求,将特性分支合并到主线分支,合并请求由管理员审核通过后才执行具体合并动作。

这里边,在合并分支内容时,有些情况需要分开讨论。

如果,特性分支最终开发完毕,并得到完整的认可,那么可以采用 merge 操作将特性分支新增的所有 commits 都合并到主线分支。

可是,如果在特性分支没有开发完毕又被遗忘很久远的时候,后来想想又需要用到其中的部分更新内容,比如,bug 补丁等,那么这个特性分支里的部分 commit 才是值得被拉取的内容,用 merge 明显不行,应该用 cherry-pick。

既然 cherry-pick 和 merge 都是用于合并 commit,那么区别在哪?

Cherry-Pick 只针对某个分支的某些指定 commit,而不是全部,而 merge 会把被选中分支的所有不同 commit 都拉取过来,这正是 cherry-pick 和 merge 的核心区别所在。

此外,类似合并功能的命令还有 rebase,后边有机会再撩她。

怎么使用 Cherry-Pick

多人开发的团队项目在代码管理上,很多麻烦的操作(例如合并分支代码等)都在类似 Gitlab 的平台上进行,平台能够尽最大限度规范化推行代码的合并。关于 Gitlab 的介绍,可以查看八戒以前写的文章《在局域网搭建一个带 web 操作页面的 git 版本服务器 - Gitlab》。

当然,如果接收合并内容的分支不受管控,完全可以自己在本地通过 git 命令合并或者用集成 git 功能的客户端可视化操作。带有 git 功能的可视化客户端也比较多,例如 TortoiseGit、SourceTree 和各大热门 IDE 等。

接下来选择使用 git 命令来简单做个示例。

准备一个本地代码仓库

$ cd ~
$ mkdir sample && cd sample
$ git init
Initialized empty Git repository in ~/sample/.git/

添加第一个改动,这里创建一个文本文件 index.txt 并写入字符串 index,然后提交到默认分支 master

$ echo index>index.txt
$ git add .
$ git commit -m "add index file"
[master (root-commit) e423b8c] add index file
 1 file changed, 1 insertion(+)
 create mode 100644 index.txt
$ git status
On branch master
nothing to commit, working tree clean

再准备两个特性分支 A 和 B

$ git branch A
$ git branch B
$ git branch
  A
  B
* master

git branch 命令只会创建新分支,但不会切换。

切换到特性分支 B 开始特性开发,这里为演示起见单纯创建一个文件 feature.txt 并写入字符串 feature,然后将工程更新提交到当前分支 B

$ git switch B
Switched to branch 'B'
$ echo feature>feature.txt
$ ll
total 2
-rw-r--r-- 1 Administrator 197121 8 Nov  8 19:22 feature.txt
-rw-r--r-- 1 Administrator 197121 6 Nov  8 19:10 index.txt
$ git add .
$ git commit -m "create feature"
[B a827145] create feature
 1 file changed, 1 insertion(+)
 create mode 100644 feature.txt
$ git log --oneline
a827145 (HEAD -> B) create feature
e423b8c (master, A) add index file

可以看到本地工作目录,在只有 index.txt 文件的基础上,多了个 feature.txt 文件。

除了 git switch 可以切换分支,还有 git checkout 也可,两者作用基本一样,但是建议统一使用 switch,避免记忆负担。

使用 git log 查看当前分支的提交日志,后边的选项 --oneline 会将日志信息简化到一行,方便查看提交记录比较多的情况。

当特性开发到这个阶段时,突然发现工程代码其实有个 bug,自己发现---高兴坏了。既然分支 B 和 分支 A 都是从主线分支分叉而来,那么分支 A 的代码中也会存在同样的 bug。

着手修复分支 B 的 bug,这里简单起见只是创建一个文件 fix.txt 并写入字符串 fix,然后提交更新到仓库

$ echo fix>fix.txt
$ git add .
$ git commit -m "fix bug"
[B 797dfa4] fix bug
 1 file changed, 1 insertion(+)
 create mode 100644 fix.txt
$ git log --oneline
797dfa4 (HEAD -> B) fix bug
a827145 create feature
e423b8c (master, A) add index file

那么怎么把分支 B 里刚刚修复的 bug 更新应用到分支 A 中呢?如果使用 merge 合并,那么分支 B 中还未开发完成的特性功能也会被一起同步到分支 A 中,这样不是我们想要的结果,于是可以针对某些已提交的 commit 执行 git cherry-pick。

在执行 cherry-pick 时需要已提交 commit 的 hash。从上面分支 B 的提交日志可以找到这串 hash

...
797dfa4 (HEAD -> B) fix bug
...

797dfa4 就是我们需要的 commit hash,虽然它不是一串完整的 hash,但是 git 只对比前面一部分就够了。

切换到分支 A 中,然后 cherry-pick

$ git switch A
Switched to branch 'A'
$ git log --oneline
e423b8c (HEAD -> A, master) add index file
$ git cherry-pick 797dfa4
[A e27c778] fix bug
 Date: Wed Nov 8 19:48:36 2023 +0800
 1 file changed, 1 insertion(+)
 create mode 100644 fix.txt
 $ ll
total 2
-rw-r--r-- 1 Administrator 197121 4 Nov  8 20:01 fix.txt
-rw-r--r-- 1 Administrator 197121 6 Nov  8 19:10 index.txt
$ git log --oneline
e27c778 (HEAD -> A) fix bug
e423b8c (master) add index file

可以看到执行 git cherry-pick 后,本地目录下也有了 fix.txt 文件,也即是分支 B 中做了修复 bug 的更新内容已经同步到分支 A 中,合并结果成功。

陷阱

上面的合并过程是很理想化的顺利,但是现实往往有些棘手。

比如,在分支 B 中,如果当时开发特性功能时刚好修改了有几行主线分支的原有代码,而修复 bug 时同样改动了这几行代码,那么在 cherry-pick 时有极大可能会出现合并冲突,导致合并失败,这时需要手动再处理冲突的代码段,保留符合修复 bug 意图的代码。

这种冲突,在 merge 操作中也很常见,原因基本类似。

所以说,cherry-pick 虽然很强大,但不是万能的,不要过度使用,使用时应该谨慎。

一般在 merge 合并操作能满足使用需求的情况下,优先建议使用 Merge,而不是 cherry-pick,只有在两个分支不是可以完全合并的前提下,再考虑使用 cherry-pick。因为 cherry-pick 在设计时就是为了解决某些指定 commit 的合并,而非全部。

这就是 cherry-pick 的桃色陷阱,你说呢?