Git代码冲突原理与三路合并算法

Git代码冲突原理

Git合并文件是以行为单位进行一行一行合并的,但是有些时候并不是两行内容不一样Git就会报冲突,这是因为Git会帮助我们进行分析得出哪个结果是我们所期望的最终结果。而这个分析依据就是三路合并算法。当然,三路合并算法并不能帮助我们绝对的避免冲突,当三路合并算法也不能帮助我们合并结果时,这个时候Git会将冲突交由开发者,由开发者进行人工干预得出最终合并结果。

1.1 两路合并算法

学习三路合并时我们先了解一下"两路合并"。两路合并算法就是将两个文件进行逐行对别,如果行的内容不同就报冲突,两路合并示意图如下图所示。

两路合并的弊端是非常大的,他几乎没有任何作用。因为在两路合并中缺少了一个比较基准,在两个分支进行合并时,只要两个文件有某一行不一样,那么合并时必定出现冲突,这显然是不友好的。

假设对于同一个文件,其中有一个人在分支上修改了内容,但是我们并没有修改文件内容,此时我们想要合并其他人刚刚修改的内容,我们当前版本的内容(Ours)和其他人当前版本的(Theirs)Git都认为正确的,最终Git只能让我们自己来处理这种冲突了,这种情况非常多且没有必要出现冲突。而这种情况产生的核心就是缺少比较基准,即不知道Ours和Theirs上一个版本是什么,无法得出Ours和Theirs有没有对上一个版本进行改动。

1.2 三路合并算法

三路合并是Git中用于解决分支间差异和冲突的核心算法。在Git进行分支合并时,它会寻找三个提交点:两个分支的HEAD(即当前提交)以及它们共同的最近祖先提交。这被称为“三路”:

  1. 共同祖先(Common Ancestor):这是两个分支合并前的最近共享提交
  2. 当前分支(Ours):即将合并到的分支,通常是你正在操作并想要合并其他分支到的分支
  3. 待合并分支(Theirs):你想要合并进当前分支的那个分支

三路合并算法的工作原理如下:

  • 对于每个文件,Git会对比这三个提交点(三路)中的内容。
  • 如果在共同祖先之后,两个分支对同一文件做出了不同的修改,那么就会出现冲突,Git会在合并过程中标记出这些冲突,并暂停合并,等待用户手动解决。
  • 如果双方对某个文件的修改不冲突(修改的内容是一致的),Git则能自动将这些更改合并在一起。

如下图,我们的代码(Ours)需要合并其他人的代码(Theris)的时候,Git会尝试找到这两次提交的共同祖先(Base),以共同祖先作为比较基准,如果一方相对于Base进行了修改,另一方相当于Base没有修改,那么此时合并成功,如果双方都相对于Base进行了修改,那么此时合并就会出现冲突。

如下图所示

代码演示如下:

rm -rf ./* .git		# 重新初始化仓库
git init
echo "Hello" >> aaa.txt
git add ./
git commit -m 'Hello' ./git checkout -b test	# 创建并切换到一个新分支
vi aaa.txt				# 编辑为Hello World
cat aaa.txt
Hello Worldgit commit -m 'Hello World' ./	# 提交
git log --oneline		# 此时还是同轴开发路线
* 594456e (HEAD -> test) Hello World
* 2bd777a (master) Hellogit checkout master		
git merge test			# 属于快进合并(不会出现代码冲突)
cat aaa.txt
Hello World

如下图,在Ours合并Theirs时,双方都相对于比较基准Base进行了修改,那么此时合并就会出现冲突。我们不难发现,下图描述的其实是一个典型合并的场景。

代码演示如下:

rm -rf ./* .git					# 重新初始化仓库
git init		
echo "Hello" >> aaa.txt			
git add ./
git commit -m 'Hello' ./
git branch testvi aaa.txt
cat aaa.txt
Hello Gitgit commit -m 'Hello Git' ./
git log --oneline --all --graph	
* 1317c49 (HEAD -> master) Hello Git
* 75b8528 (test) Hellogit checkout test		# 切换到test分支开发
vi aaa.txt
cat aaa.txt
Hello Worldgit commit -m 'Hello World' ./
git log --oneline --all --graph			
* c7aefff (HEAD -> test) Hello World	# 产生分叉开发路线
| * 1317c49 (master) Hello Git
|/
* 75b8528 Hellogit checkout master			# 切换回master分支
git merge test				# 合并test分支(出现代码冲突)
Auto-merging aaa.txt
CONFLICT (content): Merge conflict in aaa.txt
Automatic merge failed; fix conflicts and then commit the result.cat aaa.txt					# 查看冲突内容
<<<<<<< HEAD
Hello Git
=======
Hello World
>>>>>>> test

了解完上面的案例,我们可以把测试变为更加复杂,如下图。

通过上图我们可以得出如下规则。

  • 只有一方修改了同一个文件的同一行内容,则最终合并结果为修改过的内容
  • 双方都修改了同一文件的同一行内容:
    • 如果双方修改的内容一致,则最终合并结果为修改过的内容
    • 如果双方修改的内容不一致,则出现冲突

代码演示如下:

rm -rf ./* .git 
git init
echo "A1" >> aaa.txt
echo "B2" >> aaa.txt
echo "C3" >> aaa.txt
echo "C3" >> aaa.txt
echo "D4" >> aaa.txt
echo "D4" >> aaa.txt
echo "E5" >> aaa.txt
git add ./ 
git commit -m 'a' ./git checkout -b test
echo "A1" > aaa.txt		# 注意 ">" 会清空文件
echo "B2" >> aaa.txt
echo "C3" >> aaa.txt
echo "C0" >> aaa.txt
echo "D4" >> aaa.txt
echo "D1" >> aaa.txt
echo "E0" >> aaa.txt
git commit -m 'b' ./git checkout master
echo "A1" > aaa.txt		# 注意 ">" 会清空文件
echo "B0" >> aaa.txt
echo "C3" >> aaa.txt
echo "C0" >> aaa.txt
echo "D4" >> aaa.txt
echo "D0" >> aaa.txt
echo "E0" >> aaa.txt
git commit -m 'c' ./# 合并test分支(产生冲突)
git merge test
Auto-merging aaa.txt
CONFLICT (content): Merge conflict in aaa.txt
Automatic merge failed; fix conflicts and then commit the result.# 查看冲突文件
cat aaa.txt
A1
B0
C3
C0
D4
<<<<<<< HEAD		# 只有这一行出现了冲突
D0
=======
D1
>>>>>>> test
E0

通过这种三路合并策略,Git能够高效地处理大部分情况下的代码合并,同时确保开发者可以准确无误地解决任何出现的合并冲突,以维护项目历史的一致性和可追溯性。

通过三路合并算法,Git能够很灵活的帮助我们在一些情况下进行自动的代码合并,以及识别出代码是否冲突、冲突的部分等。但是Git底层判断文件差异的变更却是依赖于diff文件差异算法。也就是说,只有通过diff算法得出文件差异之后,才能够根据三路合并来进行下一步操作,例如是应该合并代码还是出现冲突以及冲突代码的识别等操作。这在某些情况下可能会出现一些细小的问题,例如我们分析下面案例。

通过我们之前分析的案例可以得出,冲突的只有第四行。

代码演示如下:

rm -rf ./* .git 
git init
echo "A1" >> aaa.txt
echo "B2" >> aaa.txt
echo "C3" >> aaa.txt
echo "D4" >> aaa.txt
echo "E5" >> aaa.txt
git add ./ 
git commit -m 'a' ./git checkout -b test
echo "A1" > aaa.txt		# 注意 ">" 会清空文件
echo "B2" >> aaa.txt
echo "C0" >> aaa.txt
echo "D1" >> aaa.txt
echo "E0" >> aaa.txt
git commit -m 'b' ./git checkout master
echo "A1" > aaa.txt		# 注意 ">" 会清空文件
echo "B0" >> aaa.txt
echo "C3" >> aaa.txt
echo "D0" >> aaa.txt
echo "E0" >> aaa.txt
git commit -m 'c' ./# 合并test分支(产生冲突)
git merge test
Auto-merging aaa.txt
CONFLICT (content): Merge conflict in aaa.txt
Automatic merge failed; fix conflicts and then commit the result.cat aaa.txt
A1
<<<<<<< HEAD
B0
C3
D0
=======
B2
C0
D1
>>>>>>> test
E0

但是我们实际测试得出,出现冲突的不仅仅是第四行,如下图所示。

为什么②和③也会出现冲突呢?这中间就存在了diff算法的影响,diff算法计算从①之后的代码大部分都发生了变更,并没有逐行去对比内容,而是抛出了一整块的代码冲突。这可能是Git出于性能的考虑,虽然这样的做法在某些情况下并不明智,但这并不会对我们的开发造成很大的影响。在绝大多数情况下,我们并不会对代码那几行出现了冲突很敏感,我们只要灵活的掌握如何处理代码冲突就能应对实际开发过程中的实际问题。

1.3 递归三路合并

三路合并为我们在合并分支时提供了基准(Base),这个基准就是要合并分支的共同祖先,但有时候两个分支之间的共同祖先存在多个,这个时候Git就会将这两个分支的共同祖先做一次虚拟合并,当做这两个分支的共同祖先。这种情况常见于交叉合并,如下图所示。

B、C先合并一次成为D,然后B、C再合并一次成为E,此时E、D存在多个共同祖先为B和C。此时E和D如果要进行合并,需要找到一个唯一的共同祖先,Git的做法是先将B和C这两个共同祖先做一次虚拟合并为X,以X节点作为E和D合并时的唯一共同祖先。然而在合并B和C时又需要找到B和C的共同祖先(A),如果此时B和C也存在多个共同祖先,那么同样先把B和C的共同祖先做一次虚拟合并成为一个唯一的共同祖先。这个过程就是递归三路合并。

下面我们通过代码来完成上述图中表示。

(1)初始化仓库。

rm -rf .git ./*
git init
echo 'A' >> aaa.txt
git add ./
git commit -m 'A' ./

(2)开发B版本。

echo 'B' >> aaa.txt
git commit -m 'B' ./git log --oneline --all --graph
* 4bdf139 (HEAD -> master) B
* 18e222f A

(3)在A版本处建立分支,开发C版本。

git checkout -b test 18e222f		# 在A版本处建立分支
echo "C" >> aaa.txt					
git commit -m 'C' ./git log --oneline --all --graph
* 940e119 (HEAD -> test) C
| * 4bdf139 (master) B
|/
* 18e222f A

(4)切换到master分支,合并test分支。相当于B合并C。

git checkout master			# 切换回master分支
git merge test				# 合并test分支,相当与B合并C,出现冲突
cat aaa.txt					# 查看冲突内容
A
<<<<<<< HEAD
B
=======
C
>>>>>>> testvi aaa.txt					# 编辑文件(解决冲突)
cat aaa.txt
A
B
Cgit add ./					
git commit -m 'D'
git log --oneline --all --graph
*   1262b32 (HEAD -> master) D
|\
| * 940e119 (test) C
* | 4bdf139 B
|/
* 18e222f A

(5)在B版本处建立一个新的分支,然后切换到该分支合并test分支。相当与B再合并一次C。

git checkout -b test-B 4bdf139		# 在B节点处建立一个新的分支
git log --oneline --all --graph
*   1262b32 (master) D
|\
| * 940e119 (test) C				# test分支的位置
* | 4bdf139 (HEAD -> test-B) B		# 新分支的位置
|/
* 18e222f Agit merge test						# 合并test分支,相当于B合并C,出现冲突
cat aaa.txt							# 查看冲突内容
A
<<<<<<< HEAD
B
=======
C
>>>>>>> testvi aaa.txt							# 编辑文件(解决冲突)
cat aaa.txt							# 查看内容
A
C
Bgit add./
git commit -m 'E'
git log --oneline --all --graph
*   9e610d9 (HEAD -> test-B) E			# E的祖先有B和C
|\
| | * 1262b32 (master) D				# D的祖先有B和C
| |/|
|/|/
| * 940e119 (test) C
* | 4bdf139 B
|/
* 18e222f A

(6)切换回master分支,合并test-B分支。相当于D合并E。

git checkout master		# 切换到master分支
git merge test-B		# 合并test-B分支,相当于D合并E,出现冲突
cat aaa.txt				# 查看冲突内容
A
<<<<<<< HEAD
B
C
=======
C
B
>>>>>>> test-B

我们结合代码和文件内容等一起来分析一下Git递归三路合并算法,如图所示。

E和D合并时寻找共同祖先,找到了B和C,接着B和C做一次虚拟合并为X,其结果如下:

A
<<<<< B
B
===== 
C
>>>>> C

本次X就是E和D合并时的共同祖先;Git将X节点冲突部分忽略,将剩余部分作为共同祖先的基准内容;因此,在D合并E时,出现如下内容:

A
<<<<<<< D
B
C
=======
C
B
>>>>>>> E

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/349571.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用Python和Matplotlib绘制复杂数学函数图像

本文介绍了如何使用Python编程语言和Matplotlib库来绘制复杂的数学函数图像。通过引入NumPy库的数学函数,我们可以处理包括指数函数在内的各种复杂表达式。本文详细讲解了如何设置中文字体以确保在图像中正确显示中文标题和标签,并提供了一个完整的代码示例,用户可以通过输入…

Python 越来越火爆

Python 越来越火爆 Python 在诞生之初&#xff0c;因为其功能不好&#xff0c;运转功率低&#xff0c;不支持多核&#xff0c;根本没有并发性可言&#xff0c;在计算功能不那么好的年代&#xff0c;一直没有火爆起来&#xff0c;甚至很多人根本不知道有这门语言。 随着时代的…

纯C实现的ymodem库,无额外依赖

本文目录 1、引言2、理论2.1 YMODEM协议的主要特点2.2 YMODEM的工作原理 3、代码3.1 main.cpp3.2 ymodem.c 3.3 ymodem.h 4、验证4.1 ymodem发送4.2 ymodem接收 5、移植说明 文章对应视频教程&#xff1a; 暂无&#xff0c;可以关注我的B站账号等待更新。 点击图片或链接访问我…

vue 渲染函数 h jsx

h 是什么 vue 提供的创建虚拟 DOM 节点 (vnode)的函数。 https://cn.vuejs.org/api/render-function.html#h jsx 是什么 JSX是 JavaScript XML&#xff08;HTML&#xff09;的缩写&#xff0c;表示在 JS 代码中书写 HTML 结构。简单理解就是&#xff1a; JSXjavascript xml&am…

带头+双向+循环链表的实现

目录 1. 链表1.1 带头双向循环链表 2. 链表的实现2.1 结构体2.2 初始化2.3 打印2.4 判断空不能删2.5 尾插2.6 头插2.7 尾删2.8 头删2.9 查找2.10 在pos之前插入2.11 删除pos位置的值2. 12 销毁2.13 创建节点 3. test主函数4. List.c文件5. List.h文件 1. 链表 1.1 带头双向循环…

AI大模型探索之路-实战篇:智能化IT领域搜索引擎之知乎网站数据获取(初步实践)

系列篇章&#x1f4a5; No.文章1AI大模型探索之路-实战篇&#xff1a;智能化IT领域搜索引擎的构建与初步实践2AI大模型探索之路-实战篇&#xff1a;智能化IT领域搜索引擎之GLM-4大模型技术的实践探索3AI大模型探索之路-实战篇&#xff1a;智能化IT领域搜索引擎之知乎网站数据获…

Unity:Text-TextMeshPro 不显示中文

共计四步&#xff1a; 一、去C盘复制一份字体&#xff1a; C:\Windows\Fonts二、粘贴到你的项目里&#xff08;任意文件位置&#xff09;&#xff0c;得到“MSYH”&#xff1a; 三、右键字体文件&#xff0c;依次点击create–>TextMeshPro–>FontAsset&#xff1a; …

数据预处理——调整方差、标准化、归一化(Matlab、python)

对数据的预处理&#xff1a; (a)、调整数据的方差&#xff1b; (b)、标准化&#xff1a;将数据标准化为具有零均值和单位方差&#xff1b;&#xff08;均值方差归一化(Standardization)&#xff09; (c)、最值归一化&#xff0c;也称为离差标准化&#xff0c;是对原始数据的…

0. 云原生之基于乌班图远程开发

云原生专栏大纲 文章目录 安装乌班图配置静态IP重置root密码开启root远程登录开启远程SSH访问安装docker安装docker-compose安装Edge浏览器安装搜狗输入法安装TeamViewer安装虚拟显示器安装JDK安装maven安装vscodevscode插件安装VSCode配置maven、git、jdk、自动报错vscode快捷…

C++面向对象:多态性

多态性 1.概念 多态性是面向对象的程序设计的一个重要特征。在面向对象的方法中一般是这样表述多态的&#xff1a;向不同的对象发送同一个信息&#xff0c;不同的对象在接收时会产生不同的行为。也就是说&#xff0c;每个对象用自己的方式去响应共同的消息。 2.典例 下面这…

MPLS提高网络服务质量的原理

MPLS&#xff08;Multiprotocol Label Switching&#xff0c;多协议标签交换&#xff09;是一种网络技术&#xff0c;它能够提高网络的服务质量&#xff08;Quality of Service&#xff0c;QoS&#xff09;以及整体性能。MPLS通过以下几种方式来提升网络服务质量&#xff1a;标…

Unity引擎在UI上渲染粒子播放

大家好&#xff0c;我是阿赵。   在UI上面显示粒子特效&#xff0c;如果把粒子系统直接拖到Canvas里面&#xff0c;会存在很多问题&#xff0c;比如层级问题、裁剪问题等。这里分享一种用MaskableGraphic和UIVertex来显示粒子特效的方法。 一、 MaskableGraphic和UIVertex简…

r语言数据分析案例25-基于向量自回归模型的标准普尔 500 指数长期预测与机制分析

一、背景介绍 2007 年的全球经济危机深刻改变了世界经济格局&#xff0c;引发了一系列连锁反应&#xff0c;波及各大洲。经济增长停滞不前&#xff0c;甚至在某些情况下出现负增长&#xff0c;给出口导向型发展中国家带来了不确定性。实体经济受到的冲击尤为严重&#xff0c;生…

[qt] qt程序打包以及docker镜像打包

目录 一 环境准备: 1.1 qt环境 1.2 linuxdeplouqt打包工具 二 qt包发布: 2.1 搜索链接库 2.2 应用程序APP打包 2.3 发布 三 docker镜像包发布 3.1 环境准备 3.2 镜像生产脚本 3.3 加载镜像并运行docker容器 四 补充 4.1 时间不同步问题解决 一 环境准备: qt环境l…

2024-06-07 Unity 编辑器开发之编辑器拓展8 —— Scene 窗口拓展

文章目录 1 Handles 类1.1 Scene 响应函数1.2 自定义窗口中监听 Scene1.3 Handles 常用 API2.2.1 颜色控制2.2.2 文本2.2.3 线段2.2.4 虚线2.2.5 圆弧2.2.6 圆2.2.7 立方体2.2.8 几何体2.2.9 移动、旋转、缩放2.2.10 自由移动 / 旋转 2 Scene 窗口中显示 GUI3 HandleUtility4 G…

用Python代码锁定Excel单元格以及行和列

Excel能够帮助用户高效地组织数据&#xff0c;还支持复杂的公式计算和数据分析。而随着团队协作的日益频繁&#xff0c;保护数据的准确性和完整性变得尤为重要。在Excel表格中&#xff0c;我们可以通过锁定特定的单元格或区域&#xff0c;防止对单元格内容进行随意修改&#xf…

C++面向对象程序设计 - 命名空间

命名空间是ANSI C引入的可以由用户命名的作用域&#xff0c;用来处理程序中常见的同名冲突。 在C语言中定义了三个层次的作用域&#xff0c;即文件&#xff08;编译单元&#xff09;、函数和复合语句。C又引入了类作用域&#xff0c;类是出现在文件内的。在不同的作用域中可以定…

14.shell awk数组

awk数组 awk数组awk数组示例Nginx日志分析 awk数组 1.什么是awk数组 数组其实也算是变量,传统的变量只能存储一个值,但数组可以存储多个值 2.awk数组应用场景 通常用来统计、比如:统计网站访问TOP10、网站url访问TOP10等等 3.awk数组统计技巧 1.在awk中,使用数组时,不仅可以…

ceisum只聚合效果展示

忙于开发三维引擎的扩展功能&#xff0c;实在时间太少了&#xff0c;仓促截几张图&#xff0c;看一下聚合效果。 1.聚合又文字标签 四种效果&#xff1a;如下 2.聚合无文字标签

Docker(一)-认识Docker

1.docker理念 Docker是基于Go语言实现的云开源项目。 Docker的主要目标是“Build,Ship and Run Any App,Anywhere”&#xff0c;也就是通过对应用组件的封装&#xff0c;分发&#xff0c;部署&#xff0c;运行等生命周期的管理&#xff0c;使用户的应用及其运行环境能够做到”…