掌握冲突处理技巧,保证团队协作顺畅
当两个分支对同一文件的同一部分进行了不同的修改,Git 无法自动合并时就会产生冲突。
<<<<<<< HEAD
// 当前分支的代码
console.log("Hello from main");
=======
// 要合并的分支的代码
console.log("Hello from feature");
>>>>>>> feature/new-feature<<<<<<< HEAD:当前分支的内容开始=======:分隔符>>>>>>> branch-name:要合并的分支内容结束# 查看冲突状态
git status
# 输出示例:
# Unmerged paths:
# both modified: src/main.js
# both modified: src/utils.js
# 查看冲突详情
git diff
# 只查看冲突部分
git diff --name-only --diff-filter=U
# 查看三方对比(base、ours、theirs)
git diff --base
git diff --ours
git diff --theirs # 使用 Git 自带的合并工具
git mergetool
# 配置 VS Code 作为合并工具
git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait $MERGED'
# 配置 Beyond Compare
git config --global merge.tool bc
git config --global mergetool.bc.path "C:/Program Files/Beyond Compare 4/bcomp.exe"# 查看导致冲突的提交
git log --merge
# 查看文件修改历史
git log --oneline --all --graph --
# 查看谁修改了这一行
git blame # 1. 尝试合并
git merge feature/new-feature
# 2. 查看冲突文件
git status
# 3. 打开冲突文件,手动编辑
# 删除冲突标记,保留需要的代码
# 4. 标记冲突已解决
git add
# 5. 完成合并
git commit -m "Merge feature/new-feature"
# 或者放弃合并
git merge --abort // 冲突前的文件
<<<<<<< HEAD
function calculate(a, b) {
return a + b;
}
=======
function calculate(x, y) {
return x * y;
}
>>>>>>> feature/new-feature
// 解决后的文件(保留两个功能)
function add(a, b) {
return a + b;
}
function multiply(x, y) {
return x * y;
}# 完全采用当前分支的版本
git checkout --ours
# 完全采用要合并分支的版本
git checkout --theirs
# 然后标记为已解决
git add rebase 是逐个应用提交,可能需要多次解决冲突。
# 1. 开始 rebase
git rebase main
# 2. 遇到冲突,Git 会停下来
# CONFLICT (content): Merge conflict in file.js
# 3. 查看冲突
git status
# 4. 手动解决冲突,编辑文件
# 5. 标记冲突已解决
git add
# 6. 继续 rebase
git rebase --continue
# 7. 如果还有冲突,重复步骤 3-6
# 放弃 rebase
git rebase --abort
# 跳过当前提交
git rebase --skip # 场景:feature 分支 rebase 到 main
git checkout feature/user-profile
git rebase main
# 第一个冲突
# 解决冲突...
git add .
git rebase --continue
# 第二个冲突
# 解决冲突...
git add .
git rebase --continue
# 完成 rebase
# Successfully rebased and updated refs/heads/feature/user-profile# rebase 时优先采用当前分支的版本
git rebase -X ours main
# rebase 时优先采用目标分支的版本
git rebase -X theirs mainrerere(reuse recorded resolution)记录冲突解决方案,自动应用到相同冲突。
# 全局启用
git config --global rerere.enabled true
# 查看配置
git config --get rerere.enabled
# 自动暂存 rerere 解决的文件
git config --global rerere.autoupdate true# 第一次遇到冲突
git merge feature/branch1
# 手动解决冲突
git add .
git commit -m "Merge branch1"
# 第二次遇到相同冲突
git merge feature/branch2
# rerere 自动应用之前的解决方案
# Resolved 'file.js' using previous resolution.
# 查看 rerere 记录
git rerere status
# 查看 rerere 差异
git rerere diff
# 清除 rerere 缓存
git rerere clear
git rerere forget Git 无法合并二进制文件(图片、视频、Office 文档等),只能选择一个版本。
# 查看冲突的二进制文件
git status
# 输出示例:
# both modified: images/logo.png
# both modified: docs/report.docx# 方案1:采用当前分支的版本
git checkout --ours images/logo.png
git add images/logo.png
# 方案2:采用要合并分支的版本
git checkout --theirs images/logo.png
git add images/logo.png
# 方案3:手动选择外部文件
# 1. 导出两个版本
git show :2:images/logo.png > logo_ours.png
git show :3:images/logo.png > logo_theirs.png
# 2. 手动选择或合并
# 3. 复制选择的版本
cp logo_final.png images/logo.png
git add images/logo.pnglogo_v1.png、logo_v2.png# 安装 Git LFS
git lfs install
# 跟踪大文件
git lfs track "*.psd"
git lfs track "*.mp4"
# 查看跟踪的文件
git lfs ls-files# main 分支:user.js 重命名为 user-model.js
git mv user.js user-model.js
git commit -m "refactor: rename user.js"
# feature 分支:修改了 user.js
# 修改内容...
git commit -m "feat: add user validation"
# 合并时 Git 通常能自动处理
git checkout main
git merge feature/user-validation
# 如果 Git 无法自动处理
# 1. 手动将修改应用到新文件名
git checkout --theirs user.js
git mv user.js user-model.js
# 2. 手动合并修改
# 3. 删除旧文件
git rm user.js
git add user-model.js
git commit# main: user.js → user-model.js
# feature: user.js → user-service.js
# 合并后需要手动决定
# 1. 选择一个名称
git mv user-model.js user-service.js
# 2. 删除另一个
git rm user-model.js
# 3. 提交
git add .
git commit -m "resolve: use user-service.js"# main: 删除了 old-api.js
# feature: 修改了 old-api.js
git merge feature/update-api
# CONFLICT (modify/delete): old-api.js
# 决策:
# 1. 如果确实要删除
git rm old-api.js
# 2. 如果要保留修改
git add old-api.js
git commit| 策略 | 说明 | 使用场景 |
|---|---|---|
| ours | 完全采用当前分支 | 废弃另一分支的修改 |
| theirs | 完全采用要合并的分支 | 完全接受新分支 |
| recursive | 默认策略,三方合并 | 大多数情况 |
| octopus | 合并多个分支 | 同时合并多个分支 |
# 冲突时优先采用当前分支
git merge -X ours feature/branch
# 冲突时优先采用要合并的分支
git merge -X theirs feature/branch
# 忽略空白字符差异
git merge -X ignore-space-change feature/branch
git merge -X ignore-all-space feature/branch
# 重命名检测阈值
git merge -X rename-threshold=50 feature/branch# 使用 ours 策略(完全忽略另一分支)
git merge -s ours feature/old-feature
# 使用 octopus 策略(合并多个分支)
git merge -s octopus feature/a feature/b feature/c
# 使用 subtree 策略
git merge -s subtree feature/library# 场景:合并时完全采用 main 的配置文件
git merge -X ours feature/new-config
# 场景:合并时完全采用 feature 的代码
git merge -X theirs feature/refactor
# 场景:废弃旧分支但保留合并记录
git merge -s ours feature/deprecated# 统计冲突文件数量
git diff --name-only --diff-filter=U | wc -l
# 列出所有冲突文件
git diff --name-only --diff-filter=U
# 按目录分组
git diff --name-only --diff-filter=U | cut -d/ -f1 | sort | uniq -c
# 查看冲突严重程度
git diff --stat# 1. 对于配置文件,统一采用一方
git checkout --ours config/
git add config/
# 2. 对于生成文件,重新生成
git checkout --theirs package-lock.json
npm install
git add package-lock.json
# 3. 对于代码文件,分批处理
# 先处理核心模块
git checkout --ours src/core/
git add src/core/
# 再处理其他模块
# 逐个文件手动解决...#!/bin/bash
# resolve-conflicts.sh
# 获取所有冲突文件
conflicts=$(git diff --name-only --diff-filter=U)
for file in $conflicts; do
echo "Processing $file..."
# 根据文件类型选择策略
if [[ $file == *.json ]]; then
# JSON 文件采用 theirs
git checkout --theirs "$file"
elif [[ $file == config/* ]]; then
# 配置文件采用 ours
git checkout --ours "$file"
else
# 其他文件需要手动处理
echo "Manual resolution needed for $file"
continue
fi
git add "$file"
done
echo "Batch resolution complete. Manual files remaining:"
git diff --name-only --diff-filter=U# 场景1:还未 commit,撤销单个文件
git checkout --conflict=merge
# 文件恢复到冲突状态
# 场景2:还未 commit,撤销所有文件
git merge --abort
git rebase --abort
# 场景3:已经 commit,回退到合并前
git reset --hard ORIG_HEAD
# 场景4:已经 push,创建反向提交
git revert -m 1
# -m 1 表示保留第一个父提交(通常是 main) # 1. 撤销错误的解决
git checkout --conflict=merge src/main.js
# 2. 重新编辑文件
vim src/main.js
# 3. 标记为已解决
git add src/main.js
# 4. 继续合并
git merge --continue
# 或
git rebase --continue# 查看合并前的 HEAD
git reflog
# 找到合并前的提交
# 查看 ORIG_HEAD(合并前的 HEAD)
git show ORIG_HEAD
# 对比合并前后
git diff ORIG_HEAD HEAD# 1. 开始工作前先同步
git checkout main
git pull origin main
git checkout feature/my-feature
git rebase main
# 2. 开发过程中定期同步
git fetch origin
git rebase origin/main
# 3. 提交前最后一次同步
git fetch origin
git rebase origin/main
git push origin feature/my-feature# 配置自动 rebase
git config --global pull.rebase true
# 配置 Prettier
npm install --save-dev prettier
echo "*.js" > .prettierignore
npx prettier --write "src/**/*.js"
# 配置 ESLint
npm install --save-dev eslint
npx eslint --init
npx eslint --fix "src/**/*.js"锁文件是自动生成的,手动解决容易出错,应该重新生成。
# 方法1:采用一方后重新安装
git checkout --theirs package-lock.json
npm install
git add package-lock.json
# 方法2:删除后重新生成
git checkout --ours package.json
rm package-lock.json
npm install
git add package-lock.json
# 方法3:合并 package.json 后重新生成
# 1. 手动解决 package.json 冲突
git add package.json
# 2. 删除锁文件
rm package-lock.json
# 3. 重新安装
npm install
# 4. 提交
git add package-lock.json
git commit# 采用一方后重新安装
git checkout --theirs yarn.lock
yarn install
git add yarn.lock
# 或删除后重新生成
rm yarn.lock
yarn install
git add yarn.lock# 重新生成锁文件
rm pnpm-lock.yaml
pnpm install
git add pnpm-lock.yaml# 配置 .gitattributes
echo "package-lock.json merge=npm" >> .gitattributes
echo "yarn.lock merge=yarn" >> .gitattributes
# 配置自定义合并驱动
git config merge.npm.driver "npm install"
git config merge.yarn.driver "yarn install"显示三个版本的对比:共同祖先(base)、当前分支(ours)、要合并的分支(theirs)。
# 全局启用
git config --global merge.conflictstyle diff3
# 仅当前仓库启用
git config merge.conflictstyle diff3
# 查看配置
git config --get merge.conflictstyle<<<<<<< HEAD (ours)
function calculate(a, b) {
return a + b;
}
||||||| base (共同祖先)
function calculate(a, b) {
return a - b;
}
=======
function calculate(x, y) {
return x * y;
}
>>>>>>> feature/new-feature (theirs)// 场景:两个分支都修改了同一函数
<<<<<<< HEAD
function getUserName(user) {
return user.firstName + ' ' + user.lastName;
}
||||||| base
function getUserName(user) {
return user.name;
}
=======
function getUserName(user) {
return user.fullName;
}
>>>>>>> feature/user-refactor
// 分析:
// base: 使用 user.name
// ours: 改为拼接 firstName + lastName
// theirs: 改为使用 fullName
// 解决:需要确认数据结构,选择正确的实现错误的合并已经推送到远程,其他人可能已经基于这个合并继续开发。
# 1. 找到错误的合并提交
git log --oneline --graph
# 2. revert 合并提交
git revert -m 1
# -m 1 表示保留第一个父提交(main)
# 3. 推送
git push origin main
# 4. 如果后续需要重新合并该分支
# 先 revert 这个 revert
git revert
# 然后再合并
git merge feature/branch # ⚠️ 仅在确保没有其他人基于此提交开发时使用
# 1. reset 到合并前
git reset --hard
# 2. 强制推送
git push --force origin main
# 3. 通知团队成员
# 其他人需要执行:
git fetch origin
git reset --hard origin/main # 1. 从错误合并前创建新分支
git checkout -b hotfix/fix-merge
# 2. 重新正确合并
git merge --no-ff feature/branch
# 手动解决冲突
# 3. 推送新分支
git push origin hotfix/fix-merge
# 4. 创建 PR 合并到 main
# 5. 通知团队切换到新分支 1. 理解冲突原因
2. 解决步骤
# 标准流程
1. git status 查看冲突文件
2. 打开文件,查看冲突标记
3. 分析冲突原因,理解双方修改意图
4. 手动编辑,保留正确的代码
5. 删除冲突标记
6. git add 标记为已解决
7. git commit 完成合并
8. 测试验证3. 工具和技巧
git mergetool 图形化工具--ours / --theirs 快速选择rerere 自动解决重复冲突diff3 查看三方对比4. 实战经验
面试示例回答:
"在我之前的项目中,遇到过一次大规模冲突。当时我们有两个
并行开发的功能分支,都对核心模块进行了重构。合并时产生了
50+ 个文件的冲突。
我的处理方式是:
1. 先分析冲突文件,按模块分类
2. 对于配置文件,统一采用主分支的版本
3. 对于核心业务代码,逐个文件仔细对比
4. 使用 VS Code 的三方对比工具辅助
5. 解决后进行完整的回归测试
6. 总结经验,制定了团队的冲突预防规范
这次经历让我深刻理解了频繁同步和模块化开发的重要性。"5. 预防措施