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.mzph.cn/diannao/27321.shtml

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

相关文章

Flowable-决策表设计器

✨✨✨ 最好用的Flowable决策表设计器 ✨✨✨ 最好用的Flowable流程设计器 本文中内容和案例出自贺波老师的书《深入Activiti流程引擎&#xff1a;核心原理与高阶实战》&#xff0c;书中的介绍更全面、详细&#xff0c;推荐给大家。 深入Activiti流程引擎

C++ 31 之 静态成员变量

#include <iostream> #include <string.h> using namespace std;// 特点: // 1.在编译阶段就分配了内存空间 // 2.类内声明&#xff0c;在类外进行初始化 // 3.所有对象共享一份静态成员数据 class Students01{ public:static int s_a; // 静态成员变量int s_b; };…

LSS 和 BEVDepth算法解读

前言 当前BEV的研究大都基于深度学习的方法&#xff0c;从组织BEV特征信息的方式来看&#xff0c;主流方法分属两类&#xff1a;自底向上方法和自顶向下方法。 自底向上方法比较早的代表工作是LSS&#xff0c;后来BEVDet、BEVDepth等也是基于LSS的框架来进行优化。自底向上方…

电子画册制作与传统画册相比,有哪些优势?

在当今数字化时代&#xff0c;电子画册作为一种新兴的媒体形式&#xff0c;其制作与传统画册相比具有显著的优势。以下是对这些优势的详细探讨。 首先&#xff0c;电子画册的制作过程通常更加便捷和经济。相较于传统画册需要经历的繁琐的印刷过程&#xff0c;电子画册的制作大多…

【C++课程学习】:类和对象(拷贝构造和运算符重载)

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;C课程学习 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 ✍拷贝构造&#xff1a; &#x1f349;特点一&#xff1a; &#x1f349;特点二&#xff1a; &…

(三十)Flask之wtforms库【剖析源码上篇】

每篇前言&#xff1a; &#x1f3c6;&#x1f3c6;作者介绍&#xff1a;【孤寒者】—CSDN全栈领域优质创作者、HDZ核心组成员、华为云享专家Python全栈领域博主、CSDN原力计划作者 &#x1f525;&#x1f525;本文已收录于Flask框架从入门到实战专栏&#xff1a;《Flask框架从入…

45.Python-web框架-Django - 开始建立第一个项目

目录 1.django是什么&#xff1f; 2.Pycharm 社区版&#xff0c;还是专业版&#xff1f; 3.开始django&#xff0c;Pycharm专业版 创建一个Django项目 运行一个Django项目 运行方法一&#xff0c;命令行的方式 运行方法二&#xff0c;配置Django Server的方式 4.django尊…

gpio-0设置成输出失败的原因

原因 spi驱动先设置gpio-0成了输出&#xff1b; 但是后面其他驱动&#xff0c;使用of_property_read_u32获取设备树节点的属性时&#xff0c;没去判断返回值&#xff1b;而直接使用参数返回的0&#xff0c;导致gpio-0又被设置成了输入 实例 正常的如下&#xff0c;gpio显示…

入职3年-我如何做一名AI产品经理(文末福利)

引言 从2021年校招加入京东开始&#xff0c;我一直从事AI产品经理的工作&#xff0c;有幸见证了AI行业的热情从一台台服务器烧到了全世界各个角落&#xff0c;也见证了京东AI中台团队的影响力如何一步步的扩大。从21年的迷茫到24年的坚定&#xff0c;很庆幸我正走在适合自己的…

【C++】类的默认成员函数

类的默认成员函数 类的六个默认成员函数构造函数构造函数的概念构造函数的特性 析构函数析构函数的概念析构函数的特性 构造函数与析构函数的调用顺序拷贝构造拷贝构造的概念拷贝构造的特性赋值运算符重载运算符重载赋值运算符重载前置与后置重载输入输出流重载 const修饰成员实…

高精度减法

高精度减法 两个高精度整数的减法题目思路实现代码实现 两个任意符号的高精度加减法 两个高精度整数的减法 高精度指的是数字的大小非常非常大&#xff0c;最多能有10的5次方 的 位数。 本次计算的两个数均为 正数&#xff0c;如何求负数会在最后提到。 题目 给定两个正整数…

解决浏览器缩放的时候,重新设置滚动条的位置,使页面滚动条固定悬浮在页面底部

项目场景&#xff1a; 浏览器调试页面兼容页面时&#xff0c;缩放页面宽度&#xff0c;整体超出时滚动条出现在页面最底部&#xff0c;不是悬浮在页面下面&#xff0c;只有滚动到最底部才出现&#xff0c;需要的是悬浮在页面底部&#xff0c;不是滚动到最下面才出现 解决方案…

java面试整合全套

什么是Java &#xff08;定义 优点&#xff09; java是一个平台&#xff0c;由jvm和Java应用编程接口构成的一门面向编程语言。 不仅吸收了C语言的各种优点&#xff0c;还摒弃了c语言里面的多继承,指针等概念&#xff0c;因此java的特征主要有功能强大和简单易用的特征。 jav…

FPGA Verilog模块化设计入门篇一

随着电子技术的快速发展&#xff0c;现场可编程门阵列&#xff08;FPGA&#xff09;已成为现代电子系统设计中不可或缺的一部分。FPGA的灵活性、可重构性和高性能使得它成为处理复杂算法、加速数据处理和实现特定功能的理想选择。然而&#xff0c;随着系统复杂性的增加&#xf…

go-zero整合Excelize并实现Excel导入导出

go-zero整合Excelize并实现Excel导入导出 本教程基于go-zero微服务入门教程&#xff0c;项目工程结构同上一个教程。 本教程主要实现go-zero框架整合Excelize&#xff0c;并暴露接口实现Excel模板下载、Excel导入、Excel导出。 go-zero微服务入门教程&#xff1a;https://blo…

Mysql学习(九)——存储引擎

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 七、存储引擎7.1 MySQL体系结构7.2 存储引擎简介7.3 存储引擎特点7.4 存储引擎选择7.5 总结 七、存储引擎 7.1 MySQL体系结构 连接层&#xff1a;最上层是一些客户…

自然语言处理领域的重大挑战:解码器 Transformer 的局限性

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

聚焦赛宁网安竞赛平台+赛事服务,引领网络安全竞赛新潮流

第八届XCTF总决赛将在2024年6月22日于中国成都震撼开启&#xff0c;本届总决赛分为个人Live Solo和团队KOH巅峰对决两个赛道&#xff0c;从个人和团队多角度全方位考察参赛人员的竞技水平。 巅峰对决 智慧的火花在此碰撞 个人Live Solo赛制 Live Solo赛分为晋级赛和Solo赛。…

贝壳APP渗透测试WP

前期配置 环境说明 使用PIXEL 4手机&#xff0c;为Android 12系统 APP名为贝壳找房&#xff0c;包名com.lianjia.beike&#xff0c;版本号3.01.10&#xff0c;截至2024/05/07为最新版&#xff0c;小米应用市场下载 绕过反Frida机制 可以参考往期推送&#xff0c;《绕过最新…

2分钟用手机开发一个ChatBot

前言&#xff1a; 在上一期&#xff0c;我们测评了CodeFlying&#xff0c;用它开发出了一个复杂推文管理系统&#xff0c;然后体验了一下它的热门应用&#xff1a;AI智能机器人。今天咱就继续用CodeFlying来开发一个属于我们自己的聊天机器人。 老规矩&#xff0c;我们先在手机…