Git: Git合并与冲突解决详解
合并是将不同分支的修改整合到一起的操作。通过合并,团队可以并行开发不同功能,最后统一整合。理解合并原理和冲突解决方法对团队协作至关重要。
1. 合并基础概念
(1) 什么是合并
合并是将一个分支的修改应用到另一个分支的操作。Git会尝试自动合并修改,当两个分支修改同一位置时可能产生冲突。
graph TB
A[main分支] --> B[合并feature分支]
B --> C{是否冲突?}
C -->|无冲突| D[自动合并成功]
C -->|有冲突| E[手动解决冲突]
E --> F[标记冲突已解决]
F --> G[完成合并]
style D fill:#d4edda
style E fill:#fff3cd
(2) 合并的类型
Git支持两种合并方式:
- 快进合并(Fast-forward):目标分支没有新提交,直接移动指针
- 三方合并(Three-way merge):两个分支都有新提交,创建合并提交
(3) 合并的方向
TEXT
📖 仅展示
git merge feature # 将feature合并到当前分支
合并总是将指定分支合并到当前分支,所以合并前要确认当前分支。
2. 快进合并
(1) 快进合并原理
当目标分支(如main)没有新提交,而要合并的分支(如feature)有新提交时,Git可以直接将main指针向前移动,这称为快进合并。
graph LR
subgraph 合并前
C1[提交C1] --> C2[提交C2]
C2 --> C3[提交C3]
main1[main] --> C2
feature1[feature] --> C3
end
subgraph 合并后
C4[提交C1] --> C5[提交C2]
C5 --> C6[提交C3]
main2[main] --> C6
feature2[feature] --> C6
end
style main1 fill:#fff3cd
style main2 fill:#d4edda
(2) 执行快进合并
▶ 示例:快进合并
BASH
# 查看当前状态
git log --oneline --graph --all
# 输出:
# * d4e5f6g (feature) Add feature
# * a1b2c3d (HEAD -> main) Initial commit
# 切换到main分支
git switch main
# 合并feature分支
git merge feature
# 输出:
# Updating a1b2c3d..d4e5f6g
# Fast-forward
# feature.js | 1 +
# 1 file changed, 1 insertion(+)
# create mode 100644 feature.js
# 查看合并结果
git log --oneline --graph --all
# 输出:
# * d4e5f6g (HEAD -> main, feature) Add feature
# * a1b2c3d Initial commit
(3) 禁用快进合并
有时希望即使可以快进合并,也创建合并提交,保留分支历史。
▶ 示例:禁用快进合并
BASH
# 使用--no-ff禁用快进合并
git merge --no-ff feature
# 输出:
# Merge made by the 'ort' recursive strategy.
# feature.js | 1 +
# 1 file changed, 1 insertion(+)
# create mode 100644 feature.js
# 查看历史
git log --oneline --graph --all
# 输出:
# * a1b2c3d (HEAD -> main) Merge branch 'feature'
# |\
# | * d4e5f6g (feature) Add feature
# |/
# * h7i8j9k Initial commit
3. 三方合并
(1) 三方合并原理
当两个分支都有新提交时,Git需要找到共同祖先(合并基),然后进行三方合并。
graph TB
A[共同祖先<br/>合并基] --> B[main分支提交]
A --> C[feature分支提交]
B --> D[三方合并]
C --> D
D --> E[合并提交]
style A fill:#fff3cd
style E fill:#d4edda
(2) 执行三方合并
▶ 示例:三方合并
BASH
# 查看分支状态
git log --oneline --graph --all
# 输出:
# * d4e5f6g (feature) Feature work
# | * a1b2c3d (HEAD -> main) Main work
# |/
# * h7i8j9k Initial commit
# 合并feature分支
git merge feature
# 输出:
# Merge made by the 'ort' recursive strategy.
# feature.js | 1 +
# 1 file changed, 1 insertion(+)
# 查看合并结果
git log --oneline --graph --all
# 输出:
# * k9l0m1n (HEAD -> main) Merge branch 'feature'
# |\
# | * d4e5f6g (feature) Feature work
# * | a1b2c3d Main work
# |/
# * h7i8j9k Initial commit
(3) 查看合并基
▶ 示例:查找共同祖先
BASH
# 查找两个分支的共同祖先
git merge-base main feature
# 输出:
# h7i8j9k
# 查看将要合并的内容
git diff $(git merge-base main feature) feature
# 或者使用三点语法
git diff main...feature
4. 合并冲突解决
(1) 冲突的产生
当两个分支修改同一文件的同一位置时,Git无法自动合并,会产生冲突。
▶ 示例:产生冲突
BASH
# 在main分支修改文件
echo "main content" > file.js
git add file.js
git commit -m "Main modification"
# 切换到feature分支
git switch feature
# 修改同一文件的同一位置
echo "feature content" > file.js
git add file.js
git commit -m "Feature modification"
# 切换回main并合并
git switch main
git merge feature
# 输出:
# Auto-merging file.js
# CONFLICT (content): Merge conflict in file.js
# Automatic merge failed; fix conflicts and then commit the result.
(2) 查看冲突状态
▶ 示例:分析冲突
BASH
# 查看状态
git status
# 输出:
# On branch main
# You have unmerged paths.
# (fix conflicts and run "git commit")
#
# Unmerged paths:
# (use "git add <file>..." to mark resolution)
# both modified: file.js
# 查看冲突文件
cat file.js
# 输出:
# <<<<<<< HEAD
# main content
# =======
# feature content
# >>>>>>> feature
# 查看冲突详情
git diff
# 输出:
# <<<<<<< HEAD
# -main content
# =======
# -feature content
# >>>>>>> feature
(3) 解决冲突
▶ 示例:手动解决冲突
BASH
# 编辑冲突文件,选择保留的内容
cat file.js
# 方式1:保留main的修改
# main content
# 方式2:保留feature的修改
# feature content
# 方式3:合并两者的修改
# main content
# feature content
# 标记冲突已解决
git add file.js
# 完成合并
git commit
# 输出:
# [main d4e5f6g] Merge branch 'feature'
(4) 使用工具解决冲突
▶ 示例:使用合并工具
BASH
# 使用图形化合并工具
git mergetool
# 使用特定工具
git mergetool --tool=vimdiff
# 查看可用的合并工具
git mergetool --tool-help
# 使用checkout选择版本
git checkout --ours file.js # 选择当前分支版本
git checkout --theirs file.js # 选择合并分支版本
(5) 冲突标记详解
TEXT
📖 仅展示
<<<<<<< HEAD
当前分支的内容(HEAD)
=======
要合并分支的内容
>>>>>>> feature
<<<<<<< HEAD:当前分支内容的开始=======:分隔线,上方是当前分支,下方是要合并的分支>>>>>>> feature:要合并分支内容的结束
5. 合并策略
(1) 合并策略选项
Git支持多种合并策略:
| 策略 | 说明 | 使用场景 |
|---|---|---|
| recursive(默认) | 递归三方合并 | 大多数情况 |
| octopus | 合并多个分支 | 合并多个分支时自动使用 |
| ours | 忽略其他分支 | 保留当前分支 |
| theirs | 忽略当前分支 | 保留要合并的分支 |
| resolve | 简单三方合并 | 特殊情况 |
▶ 示例:使用合并策略
BASH
# 使用recursive策略(默认)
git merge feature
# 使用ours策略(忽略feature的修改)
git merge -s ours feature
# 使用recursive策略的选项
git merge -X ours feature # 冲突时优先选择当前分支
git merge -X theirs feature # 冲突时优先选择要合并的分支
git merge -X ignore-space-change feature # 忽略空白变化
(2) 压缩合并
将多个提交压缩为一个提交再合并。
▶ 示例:压缩合并
BASH
# 压缩合并
git merge --squash feature
# 查看状态
git status
# 输出:
# Changes to be committed:
# new file: feature1.js
# new file: feature2.js
# 提交压缩后的修改
git commit -m "Merge feature branch (squashed)"
# 优点:保持主分支历史简洁
# 缺点:丢失feature分支的详细历史
(3) 仅合并但不提交
▶ 示例:预览合并
BASH
# 合并但不自动提交
git merge --no-commit feature
# 查看合并结果
git status
git diff --staged
# 如果满意,手动提交
git commit -m "Merge feature branch"
# 如果不满意,取消合并
git merge --abort
6. 合并相关操作
(1) 中止合并
▶ 示例:取消正在进行的合并
BASH
# 合并产生冲突
git merge feature
# CONFLICT (content): Merge conflict in file.js
# 取消合并,恢复到合并前状态
git merge --abort
# 或者使用reset
git reset --merge
(2) 查看合并历史
▶ 示例:查看合并提交
BASH
# 查看所有合并提交
git log --merges --oneline
# 输出:
# d4e5f6g Merge branch 'feature'
# a1b2c3d Merge branch 'develop'
# 查看非合并提交
git log --no-merges --oneline
# 查看合并的详细信息
git show --stat d4e5f6g
(3) 撤销合并
▶ 示例:撤销已完成的合并
BASH
# 撤销最后一次合并(合并提交还在)
git reset --hard HEAD~1
# 使用revert撤销合并(创建新提交)
git revert -m 1 HEAD
# -m 1 表示保留第一个父提交(main分支)
# 查看结果
git log --oneline --graph
❓ 常见问题
Q 快进合并和三方合并有什么区别?
A 快进合并是目标分支没有新提交时,直接移动指针,不创建合并提交。三方合并是两个分支都有新提交时,找到共同祖先进行合并,创建合并提交。
Q 如何解决合并冲突?
A 编辑冲突文件,删除冲突标记(<<<<<<<、=======、>>>>>>>),保留需要的内容,然后
git add 标记冲突已解决,最后 git commit 完成合并。Q 什么时候使用--no-ff选项?
A 当希望保留分支历史,明确显示这是一次合并操作时使用。通常在合并功能分支到主分支时使用,以便在历史中看到分支的合并点。
Q 如何取消正在进行的合并?
A 使用
git merge --abort 取消合并并恢复到合并前的状态。如果已经解决了部分冲突,也可以使用 git reset --merge 重置。Q squash合并和普通合并有什么区别?
A squash合并将分支的所有提交压缩为一个提交,不保留分支的详细历史。优点是主分支历史简洁,缺点是丢失了分支的提交历史和上下文信息。
📖 小节
- 合并是将一个分支的修改整合到另一个分支的操作
- 快进合并:目标分支无新提交,直接移动指针
- 三方合并:两个分支都有新提交,创建合并提交
- 冲突解决:编辑冲突文件,删除标记,保留需要的内容
- 合并策略:recursive(默认)、ours、theirs、squash等
- 合并操作:--abort取消、--no-commit预览、revert撤销
📝 作业
-
基础题:创建两个分支,在不同分支上修改不同文件,然后合并,体验快进合并和三方合并的区别。
-
进阶题:模拟冲突场景:在两个分支上修改同一文件的同一位置,合并产生冲突,手动解决冲突并完成合并。
-
挑战题:使用不同的合并策略(--no-ff、--squash、-X ours等)合并分支,比较不同策略的效果和适用场景。