Git:Cherry-Pick 桃色陷阱

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

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

Cherry-Pick 是什么?

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

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

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

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

python 复制代码
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 命令来简单做个示例。

准备一个本地代码仓库

shell 复制代码
$ cd ~
$ mkdir sample && cd sample
$ git init
Initialized empty Git repository in ~/sample/.git/

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

sql 复制代码
$ 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

css 复制代码
$ git branch A
$ git branch B
$ git branch
  A
  B
* master

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

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

sql 复制代码
$ 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,然后提交更新到仓库

shell 复制代码
$ 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

erlang 复制代码
...
797dfa4 (HEAD -> B) fix bug
...

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

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

sql 复制代码
$ 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 的桃色陷阱,你说呢?

相关推荐
但老师7 小时前
Git遇到“fatal: bad object refs/heads/master - 副本”问题的解决办法
git
秃头女孩y7 小时前
git创建分支
git
研究是为了理解12 小时前
Git Bash 常用命令
git·elasticsearch·bash
DKPT12 小时前
Git 的基本概念和使用方式
git
Winston Wood15 小时前
一文了解git TAG
git·版本控制
喵喵先森16 小时前
Git 的基本概念和使用方式
git·源代码管理
xianwu54317 小时前
反向代理模块
linux·开发语言·网络·git
binishuaio19 小时前
Java 第11天 (git版本控制器基础用法)
java·开发语言·git
会发光的猪。20 小时前
如何在vscode中安装git详细新手教程
前端·ide·git·vscode
stewie61 天前
在IDEA中使用Git
java·git