欢迎光临
我们一直在努力

Git + Gerrit 第四课:合并冲突解决

本节目标

这一节学习 Git 中非常重要、也非常常见的问题:冲突。

你要理解这些问题:

什么是冲突?
为什么会产生冲突?
冲突文件里的 <<<<<<<、=======、>>>>>>> 是什么意思?
如何正确解决冲突?
解决冲突后为什么还要 git add?
Gerrit 工作流中遇到冲突应该怎么办?

本节重点命令:

git status
git merge
git diff
git add
git commit
git merge –abort
git log –oneline –graph –all

1. 什么是 Git 冲突

冲突的本质是:

两个分支修改了同一个文件的同一处内容,Git 无法自动判断应该保留哪一份。

例如:

main 分支把一行代码改成:

title = main version

feature/login 分支把同一行代码改成:

title = login version

当你把 feature/login 合并到 main 时,Git 不知道最终应该使用哪一行,于是就会产生冲突。

注意:

冲突不是 Git 出错。
冲突是 Git 需要你这个开发者做判断。

2. 为什么会产生冲突

常见原因有这些:

  • 两个人修改了同一个文件的同一行

  • 两个分支都修改了同一个配置

  • 一个分支删除了文件,另一个分支修改了这个文件

  • 一个分支重命名文件,另一个分支继续修改旧文件

  • 长时间不更新主分支,导致自己的分支落后太多

最常见的是:

你在功能分支开发时,主分支也被别人改了。
你们刚好改到了同一个位置。

3. 冲突示意图

一开始主分支是:

A — B — C main

你创建功能分支:

A — B — C main
\\
D feature/login

后来别人也修改了 main:

A — B — C — E main
\\
D feature/login

如果 D 和 E 修改了同一个地方,合并时就可能冲突:

git switch main
git merge feature/login

Git 会提示类似:

CONFLICT (content): Merge conflict in readme.md
Automatic merge failed; fix conflicts and then commit the result.

意思是:

readme.md 出现内容冲突。
自动合并失败。
请手动解决冲突,然后提交结果。

4. 冲突文件长什么样

假设 readme.md 出现冲突,文件内容可能变成:

<<<<<<< HEAD
hello main
=======
hello feature
>>>>>>> feature/login

这几个标记非常重要。

含义如下:

<<<<<<< HEAD
当前分支的内容
=======
被合并进来的分支内容
>>>>>>> feature/login

如果你当前在 main 分支执行:

git merge feature/login

那么:

HEAD

通常表示当前分支 main 的内容。

feature/login

表示你正在合并进来的分支内容。

5. 解决冲突的核心原则

解决冲突不是简单删除某一边。

你需要判断:

最终正确的代码应该是什么样。

例如冲突内容是:

<<<<<<< HEAD
标题:主分支版本
=======
标题:登录功能版本
>>>>>>> feature/login

你可以选择保留当前分支:

标题:主分支版本

也可以选择保留功能分支:

标题:登录功能版本

也可以合并两边含义:

标题:登录功能说明

关键是:

最终文件里不能再保留冲突标记。

也就是说,解决后文件里不能还有:

<<<<<<<
=======
>>>>>>>

6. 标准冲突解决流程

当你执行:

git merge feature/login

遇到冲突后,标准处理流程是:

git status

查看哪些文件冲突。

然后打开冲突文件,手动编辑成最终正确内容。

解决完成后:

git add 冲突文件

最后提交:

git commit

完整流程:

git merge feature/login
git status

# 手动修改冲突文件

git add readme.md
git commit

注意:

解决冲突后必须 git add。

因为 git add 的作用是告诉 Git:

这个冲突文件我已经处理好了。

7. 为什么解决冲突后还要 git add

很多新手会疑惑:

我已经手动改好文件了,为什么还要 git add?

因为 Git 不会自动知道你是否已经处理完冲突。

你执行:

git add readme.md

是在告诉 Git:

readme.md 的冲突已经解决,可以进入下一次提交。

如果还有冲突文件没有 git add,Git 不会让你完成合并提交。

8. 如何查看冲突状态

冲突发生后,先执行:

git status

你可能看到:

Unmerged paths:
both modified: readme.md

意思是:

readme.md 在两个分支都被修改了,现在处于未合并状态。

解决后执行:

git add readme.md
git status

如果所有冲突都解决了,会看到类似:

All conflicts fixed but you are still merging.

意思是:

所有冲突已经解决,但合并流程还没有完成。

这时继续:

git commit

完成合并。

9. 放弃本次合并

如果你合并后发现情况很复杂,暂时不想继续,可以放弃本次合并:

git merge –abort

它的作用是:

取消当前 merge,回到 merge 之前的状态。

适合这些场景:

  • 冲突太多,想重新整理思路

  • 合并错了分支

  • 发现自己当前分支不对

  • 想先备份或更新代码再重新合并

注意:

git merge –abort 只用于正在合并且出现冲突的情况。

如果没有正在进行的 merge,它通常不能执行。

10. 实战练习:制造一个冲突

下面我们手动制造一个冲突,帮助你理解完整过程。

创建练习目录:

mkdir git-conflict-demo
cd git-conflict-demo
git init

创建初始文件:

echo "version: base" > config.txt
git add config.txt
git commit -m "添加基础配置"

创建功能分支:

git switch -c feature/login

在功能分支修改同一行:

echo "version: login feature" > config.txt
git add config.txt
git commit -m "修改登录功能配置"

切回主分支:

git switch main

如果你的主分支叫 master,使用:

git switch master

在主分支也修改同一行:

echo "version: main update" > config.txt
git add config.txt
git commit -m "修改主分支配置"

现在合并功能分支:

git merge feature/login

你应该会看到冲突提示。

11. 实战练习:解决冲突

查看状态:

git status

打开 config.txt,你会看到类似:

<<<<<<< HEAD
version: main update
=======
version: login feature
>>>>>>> feature/login

现在你决定最终内容为:

version: main update with login feature

把整个文件改成:

version: main update with login feature

然后执行:

git add config.txt
git status
git commit

如果 Git 打开编辑器让你确认 merge commit message,保存并关闭即可。

如果你想直接写提交信息,也可以:

git commit -m "合并登录功能配置"

查看提交图:

git log –oneline –graph –all

你会看到合并历史。

12. 冲突解决时的三种选择

遇到冲突时,通常有三种处理方式。

12.1 保留当前分支内容

冲突内容:

<<<<<<< HEAD
version: main update
=======
version: login feature
>>>>>>> feature/login

解决为:

version: main update

适合:

当前分支内容是正确的,被合并分支的修改不需要。

12.2 保留被合并分支内容

解决为:

version: login feature

适合:

功能分支内容是正确的,当前分支旧内容应该被替换。

12.3 手动合并两边内容

解决为:

version: main update with login feature

适合:

两边都有价值,需要人工整理成最终正确结果。

真实工作中最常见的是第三种。

13. 冲突解决完成后的检查

解决冲突后,不要急着提交。

建议检查:

git status
git diff

如果已经 git add,可以看:

git diff –cached

重点确认:

冲突标记是否已经全部删除
最终代码逻辑是否正确
有没有误删别人代码
有没有把临时代码放进去

可以搜索冲突标记:

git diff –check

它可以帮助发现一些空白符问题,但不能替代人工检查。

实际项目中,也可以在编辑器里搜索:

<<<<<<<
=======
>>>>>>>

确保没有遗留冲突标记。

14. 常见冲突类型

14.1 内容冲突

最常见。

两个分支修改了同一行。

Git 提示:

CONFLICT (content)

14.2 删除与修改冲突

一个分支删除了文件,另一个分支修改了文件。

Git 可能提示:

CONFLICT (modify/delete)

你需要判断:

这个文件最终应该保留还是删除。

如果保留:

git add 文件名

如果删除:

git rm 文件名

14.3 重命名冲突

一个分支重命名了文件,另一个分支也改了相关文件。

这种情况需要特别小心,因为可能涉及项目结构变化。

建议做法:

先理解双方修改目的,再决定最终文件名和内容。

15. Gerrit 中的冲突

在 Gerrit 中,冲突通常发生在两种情况。

第一种:

你本地更新目标分支时产生冲突。

例如:

git fetch origin
git merge origin/master

或者:

git pull

第二种:

你的 Change 在 Gerrit 上显示无法自动合入。

这通常是因为目标分支已经有了新提交,而你的提交基于旧版本。

16. Gerrit 中处理冲突的一般思路

如果你的 Change 和目标分支冲突,一般流程是:

git fetch origin
git switch feature/login
git merge origin/master

# 解决冲突

git add 冲突文件
git commit
git push origin HEAD:refs/for/master

有些团队更推荐使用 rebase:

git fetch origin
git switch feature/login
git rebase origin/master

# 解决冲突

git add 冲突文件
git rebase –continue
git push origin HEAD:refs/for/master

rebase 后面会单独讲。

现在先记住:

merge 是把目标分支合进你的分支。
rebase 是把你的提交重新放到目标分支最新位置之后。

具体团队用 merge 还是 rebase,要看项目规范。

17. Gerrit 中 amend 和冲突的关系

如果你的 Gerrit Change 已经存在,评审者让你修改,通常使用:

git add .
git commit –amend
git push origin HEAD:refs/for/master

这样 Gerrit 会生成新的 Patch Set。

但如果你为了解决冲突引入了新的 merge commit,可能会让历史变复杂。

所以很多 Gerrit 团队更喜欢:

rebase + amend

常见目标是:

一个 Change 对应一个清晰的 commit。

不过新手阶段先不要急,先把冲突解决的基本流程掌握。

18. 冲突解决的工程习惯

解决冲突时,成熟工程师会这样做:

先看 git status,确认哪些文件冲突
逐个文件解决,不要乱改无关内容
理解两边修改目的,而不是机械保留某一边
解决后搜索冲突标记
运行必要的编译或测试
最后再 git add 和 commit

不要这样做:

看到冲突就全部保留自己的
看到冲突就全部保留别人的
不理解代码就随便删
冲突标记没删干净就提交
把无关格式化混进冲突解决

19. 常见错误

错误一:把冲突标记提交了

错误结果:

<<<<<<< HEAD
some code
=======
other code
>>>>>>> branch

这种代码提交上去后,通常会导致编译失败。

提交前一定搜索:

<<<<<<<
>>>>>>>

错误二:解决冲突后忘记 git add

如果忘记:

git add 冲突文件

Git 会认为冲突还没有解决。

错误三:合并错分支

如果你发现合并错了,且还没完成合并:

git merge –abort

错误四:不知道 HEAD 是谁

冲突中:

<<<<<<< HEAD

表示当前分支内容。

所以合并前一定知道自己在哪个分支:

git branch
git status

错误五:解决冲突时顺手重构

冲突解决应该尽量只处理冲突。

不要顺手做大量重构,否则评审者很难看懂:

哪些是冲突解决
哪些是你新增的修改

20. 本节必须记住的命令

git status
git merge 分支名
git merge –abort
git add 冲突文件
git commit
git diff
git diff –cached
git log –oneline –graph –all

对应含义:

git status 查看冲突状态
git merge 分支名 合并指定分支
git merge –abort 放弃当前合并
git add 冲突文件 标记冲突已解决
git commit 完成合并提交
git diff 查看未暂存修改
git diff –cached 查看已暂存修改
git log –oneline –graph –all 查看分支合并历史

21. 本节总结

这一节你学习了 Git 冲突解决:

  • 冲突是两个分支改到同一处,Git 无法自动判断

  • 冲突文件中 HEAD 表示当前分支

  • ======= 上下分别是两个版本的内容

  • 解决冲突要编辑成最终正确内容

  • 解决后必须执行 git add

  • 如果不想继续合并,可以用 git merge –abort

  • Gerrit 中冲突通常意味着你的 Change 落后目标分支

  • 不要机械保留某一边,要理解代码含义

冲突解决是开发工程师的基本功。

真正重要的不是命令,而是判断:

最终代码应该是什么样。

下一节建议学习:

Git rebase:为什么 Gerrit 中经常使用 rebase,以及 rebase 和 merge 的区别。

赞(0)
未经允许不得转载:171主机测评 » Git + Gerrit 第四课:合并冲突解决
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址