

Mastering Git Cherry-pick
source link: https://blog.triplez.cn/posts/mastering-git-cherry-pick/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Mastering Git Cherry-pick
April 8, 2022 · 7 分钟 · TripleZ
本文希望教你如何成为一个 git cherry-pick
的 “master”!通过使用 git cherry-pick
来轻松地维护多个分支版本,再也不会让 multi-version maintaining 成为你心头上的那把令你屡次痛心的剑了!
本文所有内容都会基于以下(精心构造的)示例,该例子涵盖了大部分工程上容易出现的 Git log pattern(如有其他 corner-case ,欢迎联系我,一起努力让该文变得对大家更有帮助)。
贯穿本文的示例场景,这是该示例基于时间序的 Git 提交历史。
$ git --no-pager log --oneline --graph --date-order
* f2c1619 (HEAD -> red) R6
* e6899ea R5 merge branch 'blue' into 'red'
|\
* \ 0979d45 R4 merge branch 'green' into 'red'
|\ \
| | * 186da41 (blue) B3
| * | c950910 (green) G3
* | | 17e2629 R3
| | * 69edfc9 B2
| * | 059425a G2
| * | 05719c8 G1
| | * ebb218d B1
| |/
* / 8c6595b R2
|/
* 6581ff8 R1
* 2787f8f (master) init commit
快速创建该示例。
当前 Git 提交历史示意图如下。
Git cherry-pick 的命令的基本原理是根据用户所选择的提交,根据提交中的差异信息(diff)将这些提交移植至用户目标版本中。如将 hotfix 应用至其他 LTS 版本中是该功能的一个典型应用。
git cherry-pick
的大致用法为:
git cherry-pick [options] <commit>...
此处的 <commit>...
即为用户希望移植的提交(集合),这是本文讨论的要点。
<commit>
可以为单一提交(commit),也可以为一个版本区间(revision range)。若为 revision range,则该命令会将该 revision range 中的所有 commit 都解析出来,最终成为一连串的单一 commit 1。cherry-pick
可以同时接受多个 <commit>
,此时表现类似于 git rev-list
中的 --no-walk
行为2。
那我们依次来讨论 <commit>...
为单一 commit 以及 revision range 的情况。
Single commit#
Normal commit#
回到上文的例子,如果仅需要将 G2
选取出来,我们可以这样操作。
# 回到 master 新建一条分支用于测试
$ git checkout master
$ git checkout -b cp-single-normal-commit
# G2 的提交 SHA 值为 059425a
$ git cherry-pick 059425a
此时会出现合并冲突(merge conflict),输出如下所示。
CONFLICT (modify/delete): green deleted in HEAD and modified in 059425a (G2). Version 059425a (G2) of green left in tree.
error: could not apply 059425a... G2
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add <paths>' or 'git rm <paths>'
hint: and commit the result with 'git commit'
这段内容告知我们这些信息:green
文件在当前(暂存)版本 HEAD
中并不存在,但在选取的 G2
提交中存在。如果需要该文件,则使用 git add
将其提交至暂存区,若希望保留当前暂存版本的状态,即删除该文件,则使用 git rm
将 green
文件舍弃。
我们希望在选取 G2 之后能够保留 green
文件,故采取如下操作。
# 将 green 提交至暂存区
$ git add green
# 已修复所有合并冲突,继续进行 cherry-pick
$ git cherry-pick --continue
此时 cherry-pick 操作已经完成,如果继续执行 git cherry-pick --continue
,则此时会显示 error: no cherry-pick or revert in progress
,即当前没有进行任何 cherry-pick 任务。
查看一下当前的提交记录,则会发现 G2
已经在我们当前的分支 cp-single-normal-commit
上了。
$ git --no-pager log --oneline --graph --date-order
* 0457362 (HEAD -> cp-single-normal-commit) G2
* 2787f8f (master) init commit
Merge commit#
那如果我们想选取一个 merge commit 呢,比如将 R4
选取出来。
# 回到 master 新建一条分支用于测试
$ git checkout master
$ git checkout -b cp-single-merge-commit
# R4 的提交 SHA 值为 0979d45
$ git cherry-pick 0979d45
当执行完这条 cherry-pick
命令之后,你会得到以下输出。
error: commit 0979d45f1b46f72730188c5c01b3f2c7f41b18e6 is a merge but no -m option was given.
fatal: cherry-pick failed
默认情况下,cherry-pick
不处理 merge commit 并直接报错。因为在 merge commit 中,会有多个 parent 信息,但此时 Git 并不知道该使用哪个 parent 作为 mainline。在错误信息中,也同时提示了我们,如果要选取 merge commit ,则需要使用 -m
(亦为 --mainline
)选项来指定哪个 parent 是主线3。
通过 git show
命令可以获得 merge commit 的多个 parent,且从 1 开始编号。由于该例中我们需要选取的 mainline parent 是 R3(17e2629)
,因此在 cherry-pick
中选择的是 -m 1
。
$ git --no-pager show 0979d45
commit 0979d45f1b46f72730188c5c01b3f2c7f41b18e6
Merge: 17e2629 c950910
Author: Triple-Z <[email protected]>
Date: Thu Mar 31 01:29:31 2022 +0800
R4 merge branch 'green' into 'red'
让我们再来试一次。
$ git cherry-pick -m 1 0979d45
cherry-pick
圆满完成!此时再看一下当前的提交记录,则发现在 cp-single-merge-commit
分支上产生了一个新的 R4
提交。
$ git --no-pager log --oneline --graph --date-order
* 987aba7 (HEAD -> cp-single-merge-commit) R4 merge branch 'green' into 'red'
* 2787f8f (master) init commit
现在我们再来讲讲刚刚的 -m 1
发生了什么。如果现在去看 cp-single-merge-commit
这个测试分支上的文件,则会发现有 green
,而没有 red
。
$ ls -lh
total 16
-rw-r--r-- 1 triplez staff 18B 4 7 19:06 green
-rw-r--r-- 1 triplez staff 5B 3 31 01:29 init
这是因为我们在选取 merge commit 时,使用的是 mainline 1 ,即 red
分支。因此 cherry-pick
事实是以 red
为基础,寻找 mainline 2 green
分支与 red
的差异,选取的就是 green
分支上所做的修改了。
Revision range#
Git 中可用多种方法来表示 revision (版本,或修订快照)4,这里我们主要讨论 revision range(版本区间)5。
对于 revision range,有以下六种表示法:
-
^<rev>
:(脱字符-表示法)表示排除<rev>
以及它所有可到达的父辈 commit。 -
<r1>..<r2>
(两点-范围表示法):等同于^r1 r2
,即 包含<r2>
以及其可到达的父辈 commit ,并排除<r1>
以及其可到达的父辈 commit。如果需要包括
<r1>
,可使用这种写法:<r1>^..<r2>
。 -
<r1>...<r2>
(三点-对称差分表示法):包含所有<r1>
或<r2>
及其可到达的父辈 commit,并排除<r1>
和<r2>
两者可到达的共同父辈 commit。 -
<rev>^@
:包含<rev>
的所有父辈,但排除<rev>
本身。 -
<rev>^!
:包含<rev>
本身,但排除<rev>
所有父辈。即表示单个<rev>
commit。注意:
<rev>
(表示<rev>
及其所有父辈)在 revision range 的语境中不同于<rev>^!
。仅有指定--no-walk
参数时,两者才可以认为是相同的(都仅表示<rev>
本身)。 -
<rev>^-[<n>]
:包含<rev>
及其所有父辈,但排除<rev>
的第<n>
个 parent 及其可到达的所有父辈。<n>
的缺省值为 1。
看起来很复杂,我们来用文中的场景来举两个范围表示法的例子。
首先,考虑 <r1>
和 <r2>
都在同一分支上的情况,如 G1 (05719c8)
和 G3 (c950910)
。
# 回到 master 新建一条分支用于测试
$ git checkout master
$ git checkout -b cp-range-same-branch
# G1 的提交 SHA 值为 05719c8,G3 的 SHA 值为 c950910
$ git cherry-pick 05719c8^..c950910
05719c8(G1)^..c950910(G3)
的含义应当是:
- 包含
G3
及其所有父辈。 - 并排除
G1
的所有父辈(不排除G1
)。
因此结果应当是选出从 G1
到 G3
的所有提交,示意图如下,黄色为被包含的节点,灰色则代表被排除的节点。
让我们再看看当前的提交记录。Bingo! G1
,G2
,G3
这三个提交已经被选取出来了。
$ git --no-pager log --oneline --graph --date-order
* 32eac39 (HEAD -> cp-range-same-branch) G3
* d3b1130 G2
* c82c4c7 G1
* 2787f8f (master) init commit
那 <r1>
和 <r2>
在不同分支上,是什么情况呢?
我们以 G1 (05719c8)
和 B2 (69edfc9)
作为用例。
# 回到 master 新建一条分支用于测试
$ git checkout master
$ git checkout -b cp-range-diff-branch
# G1 的提交 SHA 值为 05719c8,B2 的 SHA 值为 69edfc9
$ git cherry-pick 05719c8^..69edfc9
05719c8(G1)^..69edfc9(B2)
的含义应当是:
- 包含
B2
及其所有父辈。 - 并排除
G1
的所有父辈(不排除G1
)。
由于 B2
及其所有父辈中,并不包括 G1
。因此我们可以将 G1^..B2
理解为包含 B2
及其所有父辈,且排除 B2
和 G1
的共同父辈后的结果。自然就只剩下 B1
和 B2
两个 commit 了。示意图如下,黄色为被包含的节点,灰色则代表被排除的节点。
让我们再看看当前的提交记录,确实是只选择了 B1
和 B2
两个提交。
$ git --no-pager log --oneline --graph --date-order
* e63f214 (HEAD -> cp-range-diff-branch) B2
* aed6717 B1
* 2787f8f (master) init commit
Rerere#
Rerere 是“重用已记录的冲突解决方案(reuse recorded resolution)”,它是一种简化冲突解决的方法6 7。
如果你经常进行大量的 merge, rebase 或 cherry-pick,或在维护一个长期不同于主干的分支8,那么非常建议开启 rerere
功能。
开启 rerere
非常简单,仅需要进行一次全局配置即可。
$ git config --global rerere.enabled true
在本地仓库中直接创建
.git/rr-cache
文件夹,也可以为该仓库开启rerere
。
What’s next#
在笔者撰写该文的过程中,也看到了 Microsoft 的 Raymond Chen 写的 Stop cherry-picking, start merging 系列文章,他在其中提及了许多工程实践中 cherry-pick 可能导致的 pitfall。接下来的时间里,笔者将会逐一阅读该系列文章,并根据文中案例去分析 cherry-pick 是否能够在常用软件开发工作流给我们带来足够的收益,以及,是否应该 stop cherry-picking, start merging。
只有在深入了解工具后,我们才能更好地运用工具,真正实现效率提升。
-
git-cherry-pick <commit>…
https://git-scm.com/docs/git-cherry-pick#Documentation/git-cherry-pick.txt-ltcommitgt82308203 ↩︎ -
git-rev-list –no-walk
https://git-scm.com/docs/git-rev-list#Documentation/git-rev-list.txt---no-walksortedunsorted ↩︎ -
git-cherry-pick -m, –mainline
https://git-scm.com/docs/git-cherry-pick#Documentation/git-cherry-pick.txt--mltparent-numbergt ↩︎ -
gitrevisions: Specifying Revisions
https://git-scm.com/docs/gitrevisions/#_specifying_revisions ↩︎ -
gitrevisions: Specifying Ranges
https://git-scm.com/docs/gitrevisions/#_specifying_ranges ↩︎ -
Pro Git (zh): Git 工具 - Rerere
https://git-scm.com/book/zh/v2/Git-%E5%B7%A5%E5%85%B7-Rerere#ef_rerere ↩︎ -
Pro Git (en): Git Tools - Rerere
https://git-scm.com/book/en/v2/Git-Tools-Rerere ↩︎ -
Pro Git (zh): 分布式 Git - 维护项目 - Rerere
https://git-scm.com/book/zh/v2/%E5%88%86%E5%B8%83%E5%BC%8F-Git-%E7%BB%B4%E6%8A%A4%E9%A1%B9%E7%9B%AE#_rerere ↩︎

本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。
Recommend
-
10
对于多分支的代码库,将代码从一个分支转移到另一个分支是常见需求。 这时分两种情况。一种情况是,你需要另一个分支的所有代码变动,那么就采用合并( git merge )。另一种情况是,你只需要部分代码变动(某几个...
-
36
How to use git cherry-pick effectively Useful tricks of git cherry-pick
-
10
git cherry-pick的使用 2021年1月3日 / 1次阅读Git git cherry-pick是一个浪漫的名称,把branch想象成树枝,上面的每个commit都是一个cherry,cherry-pick就是摘取一个commit的意思。 ...
-
9
3 reasons I use the Git cherry-pick command Cherry-picking solves a lot of problems in Git repositories. Here are three ways...
-
6
Table of Contents Intro Without much ado, let's get right into it... So, the official git docs describe cherry-pick as the following:
-
6
-
10
Not FoundYou just hit a route that doesn't exist... the sadness.LoginRadius empowers businesses to deliver a delightful customer experience and win customer trust. Using the LoginRadius Identity...
-
10
git cherry-pick 2016年9月12日 | 字数 390 |
-
6
Git Cherry Pick 的後遺症 2020-10-15 10:26 PM 9 10,...
-
5
How git cherry-pick and revert use 3-way merge • git • Hello! I was trying to explain to someone how
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK