本节目标
这一节学习 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 的区别。