Skip to content

冲突处理

当 Git 无法自动合并两个分支的改动时,就会产生冲突(Conflict)。冲突需要手动解决,是协作开发中必须掌握的技能。

冲突产生的原因

冲突发生在以下情况:

  • 两个分支修改了同一文件的同一区域
  • 一个分支删除了某文件,另一个分支修改了该文件
  • 两个分支都修改了同一个二进制文件

不会产生冲突的情况:

  • 修改了不同文件
  • 修改了同一文件的不同区域(Git 能自动合并)

冲突标记的含义

发生冲突时,Git 在文件中插入冲突标记:

javascript
<<<<<<< HEAD
// 当前分支(HEAD)的代码
function login(username, password) {
  return authenticate(username, password);
}
=======
// 被合并分支的代码
function login(email, password) {
  const user = findUser(email);
  return authenticate(user, password);
}
>>>>>>> feature/email-login
标记含义
<<<<<<< HEAD当前分支(你所在分支)的内容开始
=======两个版本的分隔线
>>>>>>> feature/email-login被合并分支的内容结束

手动解决冲突

步骤 1:查看冲突文件

bash
git status
# Both modified:  src/auth.js
# Both modified:  config/settings.json

步骤 2:编辑冲突文件

打开文件,根据业务逻辑决定保留哪个版本(或合并两者):

javascript
// 解决方案1:保留当前分支版本
function login(username, password) {
  return authenticate(username, password);
}

// 解决方案2:保留被合并分支版本
function login(email, password) {
  const user = findUser(email);
  return authenticate(user, password);
}

// 解决方案3:融合两者
function login(emailOrUsername, password) {
  const user = findUser(emailOrUsername);
  return authenticate(user, password);
}

必须删除所有的冲突标记(<<<<<<<=======>>>>>>>)。

步骤 3:标记冲突已解决

bash
git add src/auth.js
git add config/settings.json

步骤 4:完成合并

bash
# 合并冲突
git commit
# (系统会生成合并提交信息,可以编辑)

# rebase 冲突
git rebase --continue

git mergetool 使用图形化工具

配置并使用图形化工具来解决冲突,比手动编辑更直观:

bash
# 配置 merge 工具
git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait $MERGED'

# 其他工具配置
git config --global merge.tool vimdiff
git config --global merge.tool kdiff3

# 启动 merge 工具
git mergetool

# 为特定文件使用特定工具
git mergetool --tool=vscode src/auth.js

常用图形化 merge 工具:

  • VS Code:内置三栏视图,直观易用
  • IntelliJ IDEA / WebStorm:功能强大的 merge 工具
  • KDiff3:开源免费
  • Meld:Linux 上常用
  • Beyond Compare:付费,功能强大

查看冲突的三个版本(:1 :2 :3)

在暂存区中,冲突文件同时存在三个版本:

bash
# :1 - 共同祖先版本(base)
git show :1:src/auth.js

# :2 - 当前分支版本(ours)
git show :2:src/auth.js

# :3 - 被合并分支版本(theirs)
git show :3:src/auth.js

# 查看三个版本的差异
git diff :1:src/auth.js :2:src/auth.js  # base vs ours
git diff :1:src/auth.js :3:src/auth.js  # base vs theirs

git checkout --ours / --theirs

快速选择其中一方的版本,不用手动编辑:

bash
# 保留当前分支(HEAD)的版本,放弃对方的改动
git checkout --ours src/config.json
git restore --ours src/config.json     # 新语法

# 保留被合并分支的版本,放弃当前分支的改动
git checkout --theirs src/config.json
git restore --theirs src/config.json   # 新语法

# 批量操作:对某目录下所有冲突文件选择 ours 版本
git checkout --ours -- .
git add .

注意:在 rebase 过程中,--ours 和 --theirs 的含义与 merge 时相反

  • merge 时:--ours = 当前分支,--theirs = 被合并分支
  • rebase 时:--ours = 正在 rebase 到的目标分支,--theirs = 正在被 rebase 的提交

rerere(Reuse Recorded Resolution)

rerere("Reuse Recorded Resolution")是 Git 的一个功能,它会记录冲突的解决方案,下次遇到相同冲突时自动应用。

启用 rerere

bash
git config --global rerere.enabled true

工作原理

  1. 发生冲突时,Git 记录冲突的"模式"(两侧的代码)
  2. 你手动解决冲突
  3. Git 记录你的解决方案
  4. 下次遇到相同模式的冲突,自动应用已记录的方案
bash
# 发生冲突后,git 提示:
# Recorded preimage for 'src/auth.js'   ← rerere 记录了冲突

# 解决冲突后,执行 git add:
# Recorded resolution for 'src/auth.js' ← rerere 记录了解决方案

# 下次遇到相同冲突:
# Resolved 'src/auth.js' using previous resolution.  ← 自动解决!

rerere 常用命令

bash
# 查看 rerere 记录的解决方案
git rerere status
git rerere diff         # 查看记录的差异

# 手动触发 rerere 重新应用已记录的解决方案
git rerere

# 忘记某个冲突的解决方案(如果解决方案有误)
git rerere forget src/auth.js

# 清理所有 rerere 记录
rm -rf .git/rr-cache/

rerere 的强大场景:

  • 长期功能分支反复与主线 rebase 时
  • 多人同时修改同一区域,冲突反复出现时

复杂冲突处理技巧

查看是谁最后修改了冲突行

bash
git log -p -S "conflicting_line" -- src/auth.js
git blame src/auth.js

查看分叉点(共同祖先)的文件状态

bash
# 找到共同祖先
git merge-base HEAD feature/login

# 查看共同祖先时的文件内容
git show $(git merge-base HEAD feature/login):src/auth.js

中止并重新开始

bash
# 合并时遇到复杂冲突,想重新来过
git merge --abort

# rebase 时中止
git rebase --abort

预防冲突的最佳实践

  1. 频繁同步主线:定期将主线合并到功能分支(或 rebase),减少分叉程度
bash
# 每天开始工作前同步主线
git switch feature
git fetch origin
git rebase origin/main
  1. 小批量提交:避免长时间在功能分支上堆积大量改动

  2. 模块化代码:合理拆分模块,减少多人修改同一文件的机会

  3. 沟通协调:团队成员提前沟通,避免同时修改相同区域

  4. 使用 CODEOWNERS:通过代码所有权机制,让修改某模块前必须通知相关负责人

常见冲突场景示例

场景1:同行被不同方式修改

bash
# 解决:分析哪种修改是正确的,或综合两者
# 关键是理解业务意图,不要机械地选择一方

场景2:一方删除了文件,另一方修改了文件

bash
git status
# deleted by us:  src/old-module.js  ← 我们删除了,对方修改了

# 选项1:确认删除,忽略对方的修改
git rm src/old-module.js

# 选项2:保留对方的修改
git add src/old-module.js

场景3:二进制文件冲突

bash
# 二进制文件无法合并,只能选择其中一方
git checkout --ours -- assets/logo.png
git checkout --theirs -- assets/logo.png

总结

操作命令
查看冲突文件git status
选择当前分支版本git checkout --ours <file>
选择对方分支版本git checkout --theirs <file>
使用图形化工具git mergetool
标记冲突已解决git add <file>
完成合并git commit
继续 rebasegit rebase --continue
放弃合并/rebasegit merge --abort / git rebase --abort
启用 rereregit config rerere.enabled true