【idea结合git实现项目管理实战】四、git冲突篇_idea git实战-爱代码爱编程
【IDEA结合Git实现项目管理实战】四、git冲突篇
前言
本系列将结合我个人参与团队协作开发项目的经验来介绍如何使用IDEA结合Git实现项目管理,因此可能与真正的企业开发协作存在差异,且文章所涉及的解析可能存在个人理解与实际的偏差。
本系列主讲如何具体操作,因此对于Git内部的原理将不会过多深究。
本文严禁任何形式的转载、搬运!
在使用Git进行项目管理时,代码合并是一项常见而重要的操作。本文将重点探讨两种常用的代码合并操作:合并(merge)和变基(rebase)。在进行代码合并时,我们难免会遇到Git冲突的情况。本文也将通过举例详细介绍如何通过IDEA使用Git进行合并或变基操作时可能遇到的代码冲突情况,并提供解决方法。
什么是git冲突
在多分支并行处理时,每个分支可能基于不同版本的主干分支创建。如果每个分支都独立开发而没有进行代码合并,自然不会出现代码冲突。但是,当两个分支同时修改同一文件时,在代码合并时就会出现冲突。
下图为两个分支分别使用合并/变基操作解决冲突后的提交树。
解决git冲突
介绍完冲突出现的原因,那么如何解决冲突呢?在解决git冲突时,我们需要确定以哪个分支的文件版本为准,或者取两个分支的文件的部分片段进行整合。
IDEA提供了强大的冲突解决功能,供用户处理git冲突。下面将进行详细介绍。
当前分支dev1的代码:
package com.hmdp;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.redisson.api.RedissonClient;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
@Slf4j
@SpringBootTest
class JavaTest {
@Resource
private RedissonClient redissonClient;
@Test
void method1() {
// 打印hello world
System.out.println("hello world");
}
void method2() {
// 打印你好 世界
System.out.println("你好 世界");
}
}
目标分支dev的代码:
package com.hmdp;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.redisson.api.RedissonClient;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@Slf4j
@SpringBootTest
class JavaTest {
@Resource
private RedissonClient redissonClient;
@Test
void access() {
//打印Red Dead:redemption2
System.out.println("Red Dead:redemption2");
}
@Test
void access2() {
//打印荒野大镖客:救赎2
System.out.println("荒野大镖客:救赎2");
}
}
我们现在的目标是让两个分支合并后的代码中同时出现method1、method2、access和acess2这四个方法。
执行合并后,出现界面:
左侧为当前分支dev1的提交记录,中间为合并前的预览结果,右侧为目标分支dev的提交记录。
其中红色区域为代码存在差异的部分。
先来看第一块红色区域的中间部分的代码。大家一定会疑惑预览结果里出现这段代码是什么意思?为什么会出现报错呢?
<<<<<<< HEAD
=======
import java.util.concurrent.TimeUnit;
>>>>>>> dev
这里其实是git对于左右侧存在差异的代码的标记。符号<<<<<<< xxx
的下方是左侧存在差异的代码,符号>>>>>> xxx
的上方是右侧存在差异的代码,比如<<<<<<< HEAD
箭头所指方向也就是我们当前分支的方向(在左侧),在该箭头下面的部分是当前分支的与目标分支的差异代码,这里因为左侧比右侧少了一段代码,因此下面啥东西没有;=======
代表分割符号,该分割符号的下面就是目标分支的代码,即import java.util.concurrent.TimeUnit
;>>>>>>> dev
也就代表目标分支的方向(在右侧)
那么如何解决冲突呢?对于我们的目标来说,我们的输出语句自然不需要导入这个包,因此把语句import java.util.concurrent.TimeUnit;
给删除即可。
点击左侧的箭头符号,可以把中间区域被替换成左侧的红色区域(那根细线,也就是没有代码)。
点击后中间区域消失。
再来看第二个红色区域,根据我们的目标,我们要将这四个方法都添加进入中间区域。
先点击左侧的箭头。可以发现中间区域被替换为左侧代码,右侧向左箭头变成了向左下箭头。
这个向左下的箭头代表将右侧的代码添加到中间代码的下方。
点击后如图:
那么一切就大功告成了,冲突解决成功,点击右下角应用按钮。
git提示还有冲突未处理,这是为什么?
把界面翻到上面,发现这个红色区域还没有处理,我们点击那个叉号,作用是将冲突标记为已解决。
这时IDEA提示所有变更已被处理,那么我们就可以放心大胆的合并了。
合并成功!
合并/变基详解
合并(git merge)
当前分支和目标分支执行合并操作时,Git会将当前分支的最新提交记录与目标分支的最新提交记录合并,并在当前分支形成一个新的提交记录。
示例1
当前分支为dev1,目标分支为dev,目标分支dev中存在两条当前分支dev1分支没有的提交记录。
执行合并操作,dev中的提交记录添加到了分支dev1中。
示例2
当前分支为dev1,目标分支为dev,当前分支dev1中存在两条目标分支dev分支没有的提交记录。
执行合并操作,git给出提示(已是最新 删除dev),当前分支dev1没有变动。
示例3
当前分支为dev1,目标分支为dev,当前分支dev1中有新的提交记录添加测试类
,目标分子dev中有新提交记录添加新文件
(该示例由于都是添加新文件,没有对同一文件进行更改,因此不存在代码冲突)
dev1中添加了一个JavaTest文件。
dev分支中添加了一个test.lua文件。
执行合并操作,在目标分支dev1中生成一个新的提交记录Merge branch 'dev' into dev1
,该提交记录包含了这两个提交记录的变更,如图。
在提交树中,可以看到两个提交记录合并为一个记录。
变基(git rebase)
当前分支和目标分支执行变基操作时,Git会将目标分支的最新提交记录依次应用到当前分支的每个新的提交记录中。
示例1
当前分支为dev1,目标分支为dev,目标分支dev中存在两条当前分支dev1分支没有的提交记录。
执行变基操作,dev中的两条记录添加到了dev1中。
示例2
当前分支为dev1,目标分支为dev,当前分支dev1中存在两条目标分支dev分支没有的提交记录。
执行变基操作,没有发生变化。
示例3
当前分支为dev1,目标分支为dev,当前分支dev1中有新的提交记录添加测试类
,目标分子dev中有新提交记录添加新文件
(该示例由于都是添加新文件,没有对同一文件进行更改,因此不存在代码冲突)
dev1中添加了一个JavaTest文件。
dev分支中添加了一个test.lua文件。
执行变基操作,dev分支的提交记录添加到了dev1分支中。
总结
可以发现,无论是对于合并还是变基操作的示例1和示例2,最终执行操作后的结果都是一样的。对于合并操作,git将两个分支进行合并,最后生成一个新的提交记录,提交树存在交叉。对于变基操作,git将目标分支的提交记录应用到当前分支,提交树仍然是线性的。如图所示。
至于在实际开发中选择合并还是变基,还是看个人喜好了。
代码冲突示例
注意:本文为方便理解,所有示例均简单的修改项目中的md文件,实际开发中可能存在对多个文件的冲突,但万变不离其宗,只要你具备了解决单个文件代码冲突的能力,那么多个文件的冲突也能轻松应对。
合并/变基分支1
分支情况,当前dev1的两个提交记录
博文1
和博文2
都在dev的提交记录博文3
之前,其余分支一样时间顺序:博文1->博文2->博文3
合并
此时合并有代码冲突,解决这个冲突。
发现该冲突只针对博文2,也就是最后一个提交记录
变基
该冲突为博文1
和博文3
的冲突
该冲突为变基后的博文1
和博文2
的冲突
合并/变基分支2
分支情况:
dev中的
博文3
在dev1中的博文1
和博文2
之间时间顺序:博文1->博文3->博文2
合并
基于上述情况,合并分支存在代码冲突
在代码冲突中,存在博文2和博文3的冲突,
冲突解决后如图所示。
这里紫色因为博文3
是属于别的分支过来的,其父提交是add README.md.
。所以从add README.md.
出发,与dev1原本的提交记录博文2
结合形成一个新的提交记录Merge branch 'dev' into dev1
结论:分支以时间顺序进行排序,合并分支永远是两个分支的最后一个提交历史进行合并。
变基
博文1
和博文3
存在冲突
冲突解决后,选择提交消息不变
依然存在冲突
可以发现该冲突来自于已经变基的提交博文1
和之后的博文2
得到变基后的提交树
合并/变基分支3
分支情况:
dev1中的两个提交记录
博文1
和博文2
在dev中的博文3
提交之后时间顺序:博文3->博文1->博文2
合并
博文2
和博文3
存在代码冲突
变基
冲突来自于博文3
和博文1
冲突来自变基后的博文1
和博文2
总结
通过这三个代码冲突的示例,看到区别了吗?
在合并操作时,冲突通常发生在两个分支的最新提交记录上。这是因为合并是将两个不同的分支合并为一个,而最新的提交记录是两个分支的端点。如果两个分支都对同一文件进行了修改,Git 无法确定应该选择哪个更改,因此会产生冲突。
在变基操作时,冲突可能发生在当前分支的提交记录和目标分支的提交记录之间的每个提交记录上。这是因为变基是将一系列提交应用到另一个分支上,而不仅仅是最新的提交。如果两个分支都修改了相同的文件,冲突可能会在每个提交记录上发生,而不仅仅是最新的提交。
总的来说,冲突是由于两个分支都对同一文件进行了修改,而 Git 无法自动解决冲突的情况下发生的。在合并操作中,冲突通常发生在最新的提交记录上;在变基操作中,冲突可能发生在多个提交记录上。