Git冲突了我赌你会这3种方法
凌晨两点,你正得意地准备下班。Git pull,merge,"冲突了"。三个人改的同一个文件,`<<<<<<<< HEAD` 和 `>>>>>>>>` 满天飞,心态当场爆炸。👊 别慌,这篇给你讲透。
上周五晚上 11 点,小李在赶一个功能分支:
git checkout -b feature/user-profile
git pull origin main
Merge conflict in src/components/UserForm.vue
CONFLICT (content): Merge conflict in src/components/UserForm.vue
Automatic merge failed; fix conflicts and then commit.
打开文件,三处冲突标记等着他:
<<<<<<<< HEAD
const theme = 'dark';
=======
const theme = 'light';
>>>>>>>> feature/user-profile
小李当时的内心:我是谁?我在哪?为什么小王和小张都改了同一个变量?
你是不是也经历过?或者正在经历?好,继续看,这篇就是为你写的。
在说怎么解决之前,先搞清楚原理
很多人一看到冲突就慌,其实冲突的本质非常简单:Git 不知道你想留哪一行,它把这个决定权交给你了。
正常情况下,Git 会自动合并:
- 你改了你的文件,小王改了另一个文件 → 自动合并不需要你介入
- 你和小王都改了同一行 → 冲突,Git 不敢替你选
- 你删了文件,小王修改了它 → 冲突,Git 不知道该咋办
冲突标记里的三个部分
<<<<<<<< HEAD
// 这是你当前分支的内容(你本地 HEAD 指向的)
const name = 'Alice';
=======
// 这是要合并进来的内容(别人改的)
const name = 'Bob';
>>>>>>>> feature-branch
<<<<<<<< HEAD 到 ======= 之间是你当前的代码,======= 到 >>>>>>>> 之间是别人改的代码。解决冲突就是:删掉标记,保留你想要的。
HEAD 永远在「当前分支」这一侧,顺着
<<<<<<<< 往上看,就能分清哪边是哪边。
为什么冲突文件状态是"unstaged"
冲突文件不会自动 staged,因为 Git 认为:你自己改的东西,你自己来确认。解决完冲突后:
git add <conflict-file>
git commit -m "resolve: 合并 feature/user-profile"
这才算真正解决。
---方法一:VSCode —— 最适合新手的图形化方案
VSCode 自带 Git 冲突解决界面,不用记命令,点几下鼠标就搞定。
第一步:识别冲突文件
Git pull 或 merge 之后,VSCode 的 Source Control 面板会显示有冲突的文件,文件名前面有个 ⚠ 标记:
☠ src/components/UserForm.vue — Merge Conflict
点开文件,冲突的地方会被高亮标记,你一眼就能看到哪几处冲突。
第二步:使用冲突编辑界面
VSCode 在编辑器顶部给了你三个选项:
每个冲突块的上方,VSCode 还提供:
- Accept Current Change — 保留你的(HEAD 那侧)
- Accept Incoming Change — 保留别人的(要合进来的分支)
- Accept Both — 两边都保留(变成两个相邻的行)
- Compare Changes — 并排对比两边差异
第三步:手动编辑(复杂冲突用这个)
如果两边改动都需要,Accept Both 留不住你的上下文,可以手动编辑:
// 删掉标记,自己写一个合并后的版本
const theme = 'dark'; // 保留你的,但改成了 'dark'(综合两边意见)
手动编辑后,这个冲突块就解决了,VSCode 会自动清除冲突标记。
第四步:标记已解决 + 提交
所有冲突都处理完,点 Source Control 面板的 "Resolve in Merge"(或类似按钮),然后:
git add src/components/UserForm.vue
git commit -m "resolve: 合并 main 到 feature/user-profile"
方法二:命令行 —— 速度最快,适合老司机
命令行解决冲突更快,因为你不需要切换窗口,而且可以配合 git mergetool 用可视化工具(后面会讲)。
标准流程
# 1. 先看看哪些文件冲突了
git status
# 输出里会写:Unmerged paths: (both modified): src/app.js
# 2. 打开冲突文件,手动编辑(vim/nano/你喜欢的编辑器)
# 找到 <<<<<<<< HEAD ... ======= ... >>>>>>>> 这整块,删掉标记,保留你需要的
# 3. 标记冲突已解决
git add src/app.js
# 4. 提交(如果 merge 还在进行中,commit 不需要额外消息)
git commit -m "resolve conflict in src/app.js"
查看冲突内容的命令
# 看某个文件里冲突的具体内容
git diff --name-only --diff-filter=U
# 只看冲突部分的差异(很实用!)
git diff --merge
# 对比两边改动(需要安装 git difftools)
git mergetool
保留某一边的两种方式
有时候你很清楚哪边是对的,不需要比较:
# 保留 HEAD(当前分支),丢弃对方的改动
git checkout --ours <file>
git add <file>
# 保留对方分支的改动,丢弃自己的
git checkout --theirs <file>
git add <file>
--ours 和 --theirs 在 rebase 场景下语义是反的!rebase 时 --ours 代表的是「要 rebase 上去的分支」,--theirs 才是「当前分支」。这个坑很多人踩过,记住。
放弃合并,直接重来
# abort 掉整个 merge,重新来
git merge --abort
# rebase 过程中放弃
git rebase --abort
不知道自己在干嘛?别乱用 --ours 或 --theirs,先 git merge --abort 退回原点,搞清楚再重来。
方法三:meld 可视化工具 —— 分屏对比,一目了然
当你面对的是一个复杂文件,三五个冲突根本不够看,或者你需要同时对照两边和原始版本做决策,meld 就是神器。
安装配置
# macOS
brew install meld
# Ubuntu/Debian
sudo apt install meld
# 配置 git 使用 meld 作为冲突解决工具
git config --global merge.tool meld
git config --global mergetool.meld.cmd 'meld "$LOCAL" "$MERGED" "$REMOTE" --output "$MERGED"'
git config --global mergetool.meld.trustExitCode false
启动并解决
# 直接启动三栏视图的 meld
git mergetool
meld 会打开一个三栏窗口:
- 左边(LOCAL):你当前的分支版本
- 中间(MERGED):合并后的目标文件(你要保存结果的)
- 右边(REMOTE):要合并进来的分支版本
解决方式很简单:把你需要的改动从左边或右边拖到中间,或者直接在中间手动编辑。最终保存中间那个文件就行。
配置 meld 的小技巧
# 如果你想在启动 mergetool 时同时看到基准版本(BASE),可以这样配置
git config --global mergetool.meld.cmd 'meld "$LOCAL" "$BASE" "$REMOTE" --output "$MERGED"'
# 但这需要手动创建 BASE(Git 默认不会生成):
git mergetool --no-mergetool
加了 BASE 中间那栏后,三栏变成四栏(LOCAL / BASE / REMOTE / MERGED),能同时看到三个版本,改动来源一目了然。
常见错误 & 避坑指南
错误一:冲突没解决完就 push
! [rejected] main -> main (non-fast-forward)
error: failed to push some refs
你在解决完一部分冲突后,Git 仍然认为文件处于冲突状态。全都 git add 完再 commit,别漏掉任何一个冲突文件。
错误二:解决了冲突但没 commit
You have not concluded your merge (MERGE_HEAD exists).
这是最常见的错误之一。冲突解决后必须 commit,merge 才能算完成:
git status # 会告诉你 MERGE_HEAD still exists
git commit # 直接 commit,不需要 -m(Git 已经帮你准备了 merge 信息)
错误三:rm 掉冲突标记假装解决了
# 错误的做法:只删掉标记,但没有真正决定保留什么
# 合并后功能坏了,runtime 报错
ReferenceError: theme is not defined
冲突标记不是"脏数据",删掉标记不等于解决问题。要么手动合并逻辑,要么明确选择保留哪一边。
错误四:在冲突状态做 rebase
fatal: cannot rebase with unresolved conflicts.
有冲突时不能 rebase,先把 merge 的冲突解决完,再 rebase。
---怎么预防冲突?(这才是最重要的)
解决冲突是技能,但预防冲突是工程习惯。以下是真正能减少冲突的做法:
1. 小步提交,频繁 push/pull
一次改 50 个文件,冲突了你就哭吧。每天下班前 git pull origin main 一次,改动越小,冲突越少,就算有冲突也容易解决。
git add -p # 交互式提交,只 add 你真正要提交的部分(减少同一文件内多个不相关的改动)
2. Rebase vs Merge:搞清楚用哪个
Merge(合并)的特点:
- 保留完整的分支历史,不会重写提交
- 会产生一个 merge commit,记录"某天合并了哪个分支"
- 分支图会显示交叉,适合多人在同一分支协作
git checkout feature/user-profile
git merge main
Rebase(变基)的特点:
- 把你的分支「重放」到目标分支的最新提交之上
- 历史是线性的,没有分叉,看起来干净
- 会重写提交历史,不要对已推送的提交做 rebase!
git checkout feature/user-profile
git rebase main
团队协作时的建议:
- 长期分支(如 develop)用 merge,保护协作历史
- 短期功能分支用 rebase,保持线性历史
- 绝对不要 rebase 公共分支(已经 push 的分支),否则团队其他人的分支会乱套
3. 冲突高发区的工程习惯
- 不要多人在同一文件同一区域写代码 — 事前沟通比事后撕冲突爽多了
- 共用配置(config.yaml, .env)改成单例或往上抽,减少横向冲突
- 公共组件不要多人同时改 — 先通知,再动手
- 用
git blame追查是谁改了某行 —git blame src/app.js | grep xxx,冲突前先问问
4. 配置 git pull 的默认行为(避免意外的 merge commits)
# 设置 pull 时默认用 rebase 而不是 merge(让 pull 历史更干净)
git config --global pull.rebase true
# 如果你就在本地分支上开发,用这个流程:
git fetch origin
git rebase origin/main
git push --force),因为历史被重写了。如果有人跟你同时在这个分支上工作,先确认他们已经 pull 过。
总结:3种方法一张图
冲突来了,先别慌,选择你的解决方式:
| 场景 | 推荐方式 | 工具 |
|---|---|---|
| 简单冲突,两边改得很清楚 | VSCode Accept | VSCode 内置 |
| 命令行老手,追求效率 | git checkout --ours/theirs + add | 终端 |
| 复杂冲突,需要对照原始版本 | git mergetool + meld | Meld 可视化 |
记住,冲突不可怕,可怕的是不知道怎么解决和怎么预防。多踩几次坑,你就是团队里 Git 最稳的那个仔。
☘️ 想系统掌握 Git 命令?
这边文章只是 Git 的冰山一角。CloverTools 为你整理了 Git 常用命令速查表,覆盖从日常操作到高级技巧的全部常用命令。
👉 点击查看 Git 命令速查表本文由 CloverTools 整理发布 · 更多开发工具,尽在 CloverTools
常见问题
A: 这类工具一般有明确的输入框和输出框,按提示输入内容,点击对应按钮即可得到结果。建议先用简单示例测试功能是否正常,再处理实际数据。
A: 根据具体工具类型决定。格式转换工具适合处理第三方数据,编码工具适合加密传输,压缩工具适合文件上传前处理。多积累工具使用经验,遇到问题时能快速判断用哪个工具解决。
A: 不同工具有不同侧重,重点是理解原理。可以同时安装多个类似工具,实际使用中对比效果,选择最顺手的一个。随着使用经验增加,你也能判断工具的好坏。