Git 合并冲突学习笔记
这篇笔记记录一次 git pull 后遇到冲突的学习过程。重点不是背命令,而是理解 Git 为什么会冲突、冲突文件里那些符号是什么意思,以及解决后为什么还要 add、commit、push。
一次典型的冲突提示
执行:
git pull如果本地和远程都改了同一个文件的同一块内容,Git 可能会提示:
Auto-merging docs/.vuepress/theme/styles/custom.css
CONFLICT (content): Merge conflict in docs/.vuepress/theme/styles/custom.css
Automatic merge failed; fix conflicts and then commit the result.这句话的意思是:
Git 已经尝试自动合并,但是 custom.css 里有内容冲突。
你需要手动解决冲突,然后提交这次合并结果。冲突标记是什么意思
冲突文件里通常会出现这样的内容:
<<<<<<< HEAD
right: -15px;
top: 0;
=======
top: 0;
right: -15px;
bottom: 0;
>>>>>>> 98460e30b8ec1c3dc65ec89d411e17a21088f8ee可以这样读:
<<<<<<< HEAD
本地当前分支的版本
=======
远程拉下来的版本
>>>>>>> 远程提交 ID其中:
HEAD表示当前本地分支的版本。=======是分隔线。>>>>>>> ...后面通常是远程提交的 ID。- 这些冲突标记不是代码本身,解决冲突后必须删除。
最终文件里不能保留:
<<<<<<<
=======
>>>>>>>diff 里的加号是什么意思
在 git diff 中,经常会看到行首有 +:
+<<<<<<< HEAD
+ right: -15px;
+ top: 0;
+=======
+ top: 0;
+ right: -15px;
+ bottom: 0;
+>>>>>>> 98460e30b8ec1c3dc65ec89d411e17a21088f8ee普通 diff 里:
+ 表示新增行
- 表示删除行但是在合并冲突状态下,Git 展示的是一种更特殊的合并 diff。它要同时比较本地版本、远程版本和当前这个带冲突标记的工作区文件。
所以这时看到的 + 不一定表示“这行就是你刚刚手写新增的”,更准确地说,它表示:
这一行出现在当前冲突文件里,并且相对于某个合并版本是新增或不同的。如果看到:
+<<<<<<< HEAD大致可以理解为:
<<<<<<< HEAD 这行是 Git 为了展示冲突临时插入的。
它既不属于本地原始版本,也不属于远程原始版本,所以在合并 diff 里可能显示成两个加号。判断时不要只看 +,更重要的是看:
<<<<<<< HEAD 到 ======= 之间是什么
======= 到 >>>>>>> 之间是什么顺序不同也可能冲突
Git 是按文本行合并的,它不理解 CSS 语义。
比如本地是:
right: -15px;
top: 0;远程是:
top: 0;
right: -15px;
bottom: 0;对 CSS 来说,top、right、bottom 这些不同属性的顺序通常不影响效果。但对 Git 来说,行顺序变了,附近又新增了内容,它就可能判断为同一块代码被两边同时修改,于是产生冲突。
需要注意,不是所有 CSS 顺序都无所谓。如果是同一个属性重复出现,后面的会覆盖前面的:
color: red;
color: blue;如果是简写属性和具体属性,顺序也可能影响结果:
margin: 0;
margin-top: 10px;和:
margin-top: 10px;
margin: 0;效果就不一样。
如何手动解决一个冲突
看到:
<<<<<<< HEAD
right: -15px;
top: 0;
=======
top: 0;
right: -15px;
bottom: 0;
>>>>>>> 98460e30b8ec1c3dc65ec89d411e17a21088f8ee可以选择本地版本:
right: -15px;
top: 0;也可以选择远程版本:
top: 0;
right: -15px;
bottom: 0;也可以手动合并成自己想要的最终结果:
right: -15px;
top: 0;
bottom: 0;重点是最终只留下正常代码,不留下冲突标记。
完全选择远程版本
如果某个冲突文件想完全采用远程版本,可以执行:
git checkout --theirs -- docs/.vuepress/theme/styles/custom.css
git add docs/.vuepress/theme/styles/custom.css
git commit在普通 git pull 或 git merge 冲突中:
--ours 表示选择本地版本
--theirs 表示选择远程合并进来的版本所以:
git checkout --theirs -- 文件路径意思是:
这个冲突文件直接采用远程那一方的版本。中间的 -- 是分隔符,表示后面的是文件路径,不是分支名。
如果不全部选择远程
如果不想整个文件都用远程,而是有些地方选本地、有些地方选远程、有些地方手动合并,就要一个冲突块一个冲突块处理。
流程是:
git status
# 打开冲突文件,逐个处理 <<<<<<< ======= >>>>>>> 标记
git add docs/.vuepress/theme/styles/custom.css
git commit可以用命令检查是否还有冲突标记:
Select-String -Path docs/.vuepress/theme/styles/custom.css -Pattern "<<<<<<<|=======|>>>>>>>"如果没有输出,通常说明冲突标记已经清理完了。
为什么解决后还要 commit
git pull 通常等价于:
git fetch
git merge也就是先下载远程更新,再把远程更新合并到本地。
如果合并没有冲突,Git 可以自动完成。
如果合并有冲突,Git 会停在中间,等待你手动处理。
这时:
git add docs/.vuepress/theme/styles/custom.css表示:
这个冲突文件我已经解决好了。然后:
git commit表示:
结束这次 merge,把最终合并结果记录成一个新的提交。所以可以这样记:
修改冲突文件 = 做决定
git add = 告诉 Git 这个文件解决好了
git commit = 完成这次合并A、B、C、M 分别是什么
假设最开始有一个共同祖先提交 A:
A远程后来提交了 B:
A -- B origin/main你本地也从 A 开始提交了 C:
A -- C main于是本地和远程分叉:
A -- B origin/main
\
C main你执行 git pull,Git 会尝试把远程的 B 合并进本地的 C。如果冲突了,解决后再 commit,就会生成一个 merge commit,记作 M:
A -- B
\ \
C -- M mainM 记录了:
B 和 C 已经合并过了;
当时冲突最终选择了哪些内容;
最终文件结果是什么。所以以后 Git 不会再重复拿 B 和 C 原样冲突一次。
如果远程从 B 更新到 D
如果你生成了 M 之后还没有 push,而远程又新增了提交 D:
A -- B -- D origin/main
\ \
C -- M main这时再 pull,本质上是把 D 合并进你的 M。
这次比较的是:
共同祖先:B
本地版本:M
远程版本:D它不会重复发生旧的 B 和 C 冲突。
但是如果 D 又改到了 M 里同一块内容,就可能出现新的冲突。
一句话:
M 会记住上一次 B 和 C 的合并结果;
下次合并 D 时,Git 看的是 M 和 D 是否还能自动合并。ahead 是什么意思
如果 Git 提示:
Your branch is ahead of 'origin/main' by 2 commits.意思是:
你本地 main 上有 2 个 commit,远程 origin/main 还没有。在前面的例子里:
A -- B origin/main
\ \
C -- M main远程停在 B,本地在 M。
本地有但远程没有的提交是:
C
M所以是 ahead 2 commits。
如何同步本地和远程
可以这样记:
ahead = 本地多,需要 push
behind = 远程多,需要 pull
ahead + behind = 两边都有新提交,先 pull,再 push把本地提交同步到远程:
git push把远程提交同步到本地:
git pull如果 push 时提示:
rejected
non-fast-forward
fetch first说明远程在你 push 前又有新提交了。此时一般需要:
git pull
# 如果有冲突,解决冲突
git add .
git commit
git push一个实用的冲突处理流程
进入项目:
cd D:\AHW\blog查看状态:
git status打开冲突文件:
code docs/.vuepress/theme/styles/custom.css手动处理所有冲突块,删除:
<<<<<<< HEAD
=======
>>>>>>> ...检查是否还有冲突标记:
Select-String -Path docs/.vuepress/theme/styles/custom.css -Pattern "<<<<<<<|=======|>>>>>>>"标记冲突已解决:
git add docs/.vuepress/theme/styles/custom.css完成合并提交:
git commit同步到远程:
git push最后总结
这次学习可以浓缩成几句话:
冲突不是 Git 出错,而是 Git 不知道该替你选哪边。
<<<<<<< HEAD 上面是本地,下面是远程。
diff 里的 + 是差异展示标记,不一定表示你刚刚手写新增。
顺序不同也可能冲突,因为 Git 按文本行合并。
解决冲突后要 git add,再 git commit,merge 才算完成。
commit 是保存到本地,push 才是同步到远程。
ahead 表示本地有远程没有的提交。