代码合并在日常开发中是较为常见的场景,采用合适的合并方式,可以起到事半功倍的效果。对应在 Git 中合并的方式主要有三个,Merge,Rebase,Cherry-Pick. 开始部分会首先介绍一下这三个命令,并录制了一些动画,用于演示三个命令的不同合并过程。之后会实操这三个命令,演示如何解决冲突。

Git Merge

Merge 会将两个分支合并到一起,并生成一个新的 commit 记录。新生成的 commit 节点会有两个父节点。一般在开发某个新功能时,会选择新的分支,之后在合并回主线。

在 master 分支上,使用 git merge bugFix 将 bugFix 分支合并到 master 分支。

Git rebase

rebase 也能合并分支,它会取出一系列 commit 记录,复制它们。然后在目标分支逐个放下去。rebase 的好处是能保持线性的提交历史,从而使历史更加清晰。

在 bugFix 分支上,使用 git rebase master 分支,会将 bugFix 分支上的 commit 记录复制到 master 分支,并且保证了提交历史是线性的。

rebase 除了可以在合并时使用,还可用于整理 commit 记录。

使用 rebase 来整理记录

使用 git rebase -i 命令打开交互式的 rebase UI. 在打开的界面中会显示每个 commit 的哈希值以及提交说明,可以在其中调整 commit 的顺序、删除不想要的提交、以及合并提交。

通过 git rebase -i HEAD~4 后,我们看到了实际的交互式界面,但实际界面一般由 vim 打开并进行相应的操作。在这里,我们删除 c2,调整了 c5 和 c4 的位置。

这里需要注意的是:在真实的环境中,打开后的交互页面,commit 记录方向和 log 记录的 commit 是相反的。

cherry-pick

cherry-pick 而是将一些 commit 复制到当前的分支的 HEAD 上,和 rebase 相比,更加灵活,可以随意的选择 commit 进行复制。

通过 git cherry-pick c3 c4 c7 将其他分支上的 3 个 commit 复制到当前的 master.

三种情况下的冲突解决

场景模拟:团队开发时,不同的开发者需要经常会对公共类进行修改。当修改的位置重合或者删除公共的代码片段后,便会产生冲突。现在模拟这一场景。

首先,创建一个仓库用于演示,其中包含一个名叫 origin.txt 的文件,并在其中写入 "line1",在 master 分支提交。

git 仓库初始化:

mkdir merge-conflict-things
cd merge-conflict-things
git init
vim origin.txt
echo line1 > origin.txt

创建初始 commit 及三种情况 branch 用于演示:

git add .
git commit -m "add origin.txt"
git branch merge_branch
git branch rebase_branch
git branch cherrypick_branch

查看初始化提交信息

git log --oneline --all  -n4 --graph

准备工作:在 master 分支上,创建两个 commit.

第一个 commit : 对 origin.txt 增加一些内容,相当于增加一个 feature.

origin.txt 中添加 "line2" 后:

line1
line2

第二个 commit 用于对过去提交的内容进行修改,相当于 Bug Fix.

origin.txt 的内容如下:

line1 - fixed
line2

接下来,会在三个分支进行相同的操作,并演示如何解决冲突。

git merge

merge_branch 分支,模拟和 master 分支相同的操作,这里提交的过程就不演示了,只要和 master 分支提交的内容不同即可。

第一个 commit:用于增加 feature "line 2 in merge branch"。

第二个 commit:同于修改之前的 bug "line 1 fixed in merge branch"。

提交后的内容如下,merge 分支多了两个 commit :

下面将 merge 和 master 分支进行合并,通常的规范是在次分支去主动合并主分支的代码,然后再推送给主分支,这样做是由于所有分支都是基于主分支,万一在主分支发生错误,影响较为广泛。

进行 merge 操作

git merge master

由于 master 和 merge 分支修改了相同的内容,这时 git 并不知道哪个是需要的,所以提示 origin.txt 发生冲突,让我们手动解决。

<<< HEAD ===== 中间的内容是当前所在的分支的内容,下面的是 master 分支上的内容。

这里你需要注意的是,这里提示冲突的行是两个 commit 的内容。这就意味着,**merge 解决冲突时内容是基于两个分支的所有的 commit **

选择 merge_branch 的方案进行提交。

git add origin.txt
git commit -m "fixed conflict with master"

可以看到,merge 操作将分开的分支进行合并,并**形成一个新的 merge commit. **

git rebase

rebase_branch 分支上模拟 master 的提交操作。

第一个 commit 添加了新行:“line2 - rebase branch”

第二个 commit 用于修改bug: “line1 - rebase branch”

结果如下:

使用 rebase 合并操作

git rebase master

查看冲突的文件:

rebase 的合并过程不同于 merge,在合并时会选取 master 的所有 commit,和当前分支中一个出现冲突的 commit 进行合并。

如上图中所示,HEAD 指针停留于master 分支上最新的 commit 节点,而 rebase 指针指向添加 feature 的节点。

由于 rebase 和 merge 相比较为特殊,这里详细演示下,选取 master 分支内容,选取 rebase 分支内容 ,选择 master 和 rebase 分支的共同内容的处理方法。

1. 选择 rebase 分支内容作为合并后的结果

保存当前的修改:

git add origin.txt
git rebase --continue

修改后的内容直接就提交成功了,因为第二次需要合并的 commit 本就是基于当前内容的 commit,并不会产生冲突。

2. 选择 master 分支内容作为合并后的结果

保存当前的修改:

git add origin.txt
# 由于我们选择了 master 分支内容进行合并,而当前又基于 master,和 master 本身分支上的内容没有任何区别。
# 所以执行 skip 跳过这次冲突。 也就是意味着放弃当前分支提交的 commit,这个也能理解,既然要 master 的 commit,
# 当前分支的 commit 自然就不需要了,直接忽略掉,同时当前的 commit 也不会出现在 commit 的历史中。
git rebase --skip

接着会出现第二次冲突:

再次产生的冲突的原因是,之前解决冲突时,选择了 master 的内容,也就意味着在 rebase_branch 提交的 commit add feature for origin.txt in rebase 被放弃了,而当前的 commit fixed before commit in rebase_branch 是基于丢弃的那个 commit 。

了解到产生冲突的原因,任意选择内容,保存就可以了,这里我们选择了 rebase_merge 分支上的内容。

git add .
git rebase --continue

3. 选择将 master 和 rebase_branch 的内容合并后的结果

这里我们选择将 master 和 rebase_branch 的分支都保留下来:

保存当前的修改:

git add origin.txt
git rebase --continue

可以看到,第二次冲突产生,原因是 rebase_branch 分支中过去的 commit 出现变更,和 rebase_branch 原有 commit 的记录不一致。

同样的,我们也会遇到三种情况:

A 以第一次解决冲突后的内容为准:

git add .
# 这就意味着 rebase_branch 最新节点的添加的内容被舍弃,所以没有任何改变,可以直接跳过
git rebase --skip

B 以 rebase_branch 分支上的结果为准

C 保留 rebase_branch 和 之前修改后的内容:

B C 处理方法相同:

git add .
# 保存我们的更改就可以了
git rebase --continue

注意有时 git rebase --continute 操作无法生效时,原因在于 rebase 会逐一应用当前分支的 commit 到目标分支(一般是 master)。像例子中演示的那样,第一次解决冲突后的内容,和第二次解决冲突后的内容一样,这时用 git rebase --skip 跳过就好了。

git cheery-pick

cherrypick_branch 分支上模拟 master 的提交操作。

使用 cherry-pick 进行合并操作

git cherry-pick  eb1a8fb

出现了冲突,可以看到和 rebase 操作的合并操作刚好相反,这里是以 cherrypick_branch 的最新 commit 为准,去合并 master 的 commit.

选择 master 内容,进行合并:

git add origin.txt
git commit -m "fixed add feature conflict with master"

合并 master 分支上提交的第二个 commit:

git cherry-pick 23e106b

再次出现了冲突:

修改后,提交就可以了:

git add origin.txt
git commit -m "fixed add feature conflict with master"

注意有时 git commit 操作无法生效,提示你是否提交的一个空的 commit. 原因在于修改后的冲突内容和当前分支的原有内容一样。但这样有些矛盾,取过来别人的 commit,最后却不用。解决办法是,取消 cherry-pick 这个 commit 或者提交一个空的 commit 信息。

总结

git merge 是基于两个分支上的最新内容到有冲突的内容之间的所有 commit 进行合并,之后会形成一个新的 commit 记录,将两个分支重新关联起来。

git rebase 正如它的中文名字“变基操作”一样,会将所在分支新添加的内容,增加到目的分支,并保证了 commit 提交记录的串行性。简单来说就是,会以目的分支(一般是 master)为基础,逐一的将当前分支的 commit 记录应用。需要注意的是,在应用时并不是直接应用在 master 分支,而是将 master 分支整体拷贝,然后将当前 commit 应用在拷贝后的分支上。

git cherry-pick 和 rebase 操作正好相反,会以当前的分支为基础,然后将 commit 一个个的拿过来应用。形成的 commit 记录也是串行的。

最后附上一个 Git 练习网站,网站上设置了各种任务,可以用于检验自己的操作水平。

Merge,Rebase,Cherry-Pick 一文解惑的更多相关文章

  1. git之rebase、merge和cherry pick的区别(面试常问)

    git flow图例镇楼 merge 这个简单,初学者常用.比如主分支是Dev,最新版本是01.然后小明基于此,搞了个feature 分支A,业务:打酱油.然后在上面多次提交,完成功能迭代开发,如A1 ...

  2. git merge rebase的区别及应用场景

    前两天和同事交流发现他在日常开发中跟上游保持同步每次都是用git pull操作,而我一直习惯git fetch然后rebase,发现这两种操作后的log是有些区别的.他每次pull操作之后都会自动生成 ...

  3. git 开发merge rebase 记录

    git status git lg git add src/ git commit -m "restful api and portal" //先commit到自己的本地branc ...

  4. Git merge rebase cherry-pick 以及 游离commit 的测试

    Microsoft Windows [版本 10.0.17134.345] (c) Microsoft Corporation.保留所有权利. C:\Users\zhangyang\Desktop\b ...

  5. git merge / rebase 分支的新建与合并

    merge https://git-scm.com/book/zh/v2/Git-%E5%88%86%E6%94%AF-%E5%88%86%E6%94%AF%E7%9A%84%E6%96%B0%E5% ...

  6. git原理及如何选择分支模式

    一.git 原理介绍 1.git的四个工作区域 Git有四个工作区域:工作目录(Working Directory).暂存区(Stage/Index).资源库(Repository或Git Direc ...

  7. git merge 与 rebase 的区别

    http://gitbook.liuhui998.com/4_2.html merge rebase

  8. Git:pull --rebase 和 merge --no-ff

    首先是吐嘈 如果你正在 code review,看到上图(下文将称之为:提交线图)之后,特别是像我这样有某种洁癖的人,是否感觉特别难受?如果是的话,请看下文吧 :) 为什么 Git 作为分布式版本控制 ...

  9. git merge & git rebase

    git merge & git rebase bug error: You have not concluded your merge (MERGE_HEAD exists). hint: P ...

随机推荐

  1. [安卓基础] 004.运行app

    运行你的app 这篇课程会教你: 1.如何在设备上运行你的app. 2.如何在模拟器上运行你的app. 当然,在学习之前,你还需要知道: 1.如何使用设备. 2.如何使用模拟器. 3.管理你的项目. ...

  2. 第一次写js轮播图

    仿小米首页轮播图(注意事项) 布局部分 1.用ul包裹li再包裹a的形式来装图片,建立focus类: <div class="focus"> <ul> &l ...

  3. kubeadm实现k8s高可用集群环境部署与配置

    高可用架构 k8s集群的高可用实际是k8s各核心组件的高可用,这里使用主备模式,架构如下: 主备模式高可用架构说明: 核心组件 高可用模式 高可用实现方式 apiserver 主备 keepalive ...

  4. JavaScript 引用数据类型

    目录 1. 问题描述 2. 原因分析 3. React 中的引用数据类型 4. 业务场景 5. 参考资料 1. 问题描述 今天在写一个代码题时候, 有一个BUG 导致自己停滞好久, 该BUG 可以描述 ...

  5. Java实现 LeetCode 763 划分字母区间(暴力)

    763. 划分字母区间 字符串 S 由小写字母组成.我们要把这个字符串划分为尽可能多的片段,同一个字母只会出现在其中的一个片段.返回一个表示每个字符串片段的长度的列表. 示例 1: 输入: S = & ...

  6. Java实现 LeetCode 386 字典序排数

    386. 字典序排数 给定一个整数 n, 返回从 1 到 n 的字典顺序. 例如, 给定 n =1 3,返回 [1,10,11,12,13,2,3,4,5,6,7,8,9] . 请尽可能的优化算法的时 ...

  7. Java实现 LeetCode 75 颜色分类

    75. 颜色分类 给定一个包含红色.白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色.白色.蓝色顺序排列. 此题中,我们使用整数 0. 1 和 2 分别表示红 ...

  8. Java GUI 鼠标事件

    import java.awt.Button; import java.awt.FlowLayout; import java.awt.Frame; import java.awt.event.Mou ...

  9. Java实现找零问题

    1 问题描述 现需找零金额为n,则最少需要用多少面值为d1 < d2 < d3 < - < dm的硬币?(PS:假设这m种面值d1 < d2 < d3 < - ...

  10. TensorFlow开发者证书 中文手册

    经过一个月的准备,终于通过了TensorFlow的开发者认证,由于官方的中文文档较少,为了方便大家了解这个考试,同时分享自己的备考经验,让大家少踩坑,我整理并制作了这个中文手册,请大家多多指正,有任何 ...