搜索 K
主题
git merge 是 Git 中用于将一个分支的修改合并到另一个分支的核心命令。它是团队协作中最基本的操作之一,理解不同的合并策略和冲突解决方法是高效使用 Git 的关键。
merge 就像「汇流」——将两条支流(分支)的修改汇合到一起,形成完整的代码流。
git merge <branch>
git merge --no-ff <branch>
git merge --ff-only <branch>
git merge --squash <branch>
git merge --abort
git merge --continue
git merge --no-commit <branch>合并是将两个分支的修改历史整合在一起。Git 会找到两个分支的共同祖先(merge base),然后将两个分支相对于祖先的修改合并起来。
| 方式 | 命令 | 特点 | 历史结构 |
|---|---|---|---|
| Fast-forward | git merge <branch> | 直接移动 HEAD 指针,不创建合并提交 | 线性 |
| No fast-forward | git merge --no-ff <branch> | 强制创建合并提交,保留分支结构 | 分叉合并 |
| Squash merge | git merge --squash <branch> | 将所有提交压缩为一个,需手动提交 | 线性 |
当目标分支没有新的提交时(即 feature 分支是 main 的直接后代),Git 只需移动 HEAD 指针即可完成合并。
git checkout main
git merge feature
# Updating abc1234..def5678
# Fast-forward
# src/main.ts | 10 ++++++++++
# 1 file changed, 10 insertions(+)Fast-forward 的结果是线性历史,看不出分支曾经存在。如果你想保留分支合并的痕迹,使用 --no-ff。
# 只允许 fast-forward(如果不能 ff 则失败)
git merge --ff-only feature
# fatal: Not possible to fast-forward, aborting.
# 适用场景:确保 main 是 feature 的祖先(如 rebase 后合并)即使可以 fast-forward,也强制创建一个合并提交:
git checkout main
git merge --no-ff feature
# 打开编辑器编写合并提交信息
# Merge branch 'feature' into main--no-ffgit log --graph 中清晰看到功能分支的起止git revert -m 1 <merge-commit> 回滚整个功能很多团队在 .gitconfig 中设置:
[merge]
ff = false将 feature 分支的所有提交压缩为一个修改,放入暂存区但不自动创建提交:
git checkout main
git merge --squash feature
# Squash commit -- not updating HEAD
# 所有修改已暂存,但未提交
git commit -m "feat: 完整的支付模块 (#456)"| 对比项 | 普通 merge | --no-ff merge | Squash merge | Rebase + merge |
|---|---|---|---|---|
| 历史结构 | 线性或分叉 | 分叉 | 线性 | 线性 |
| 保留分支提交 | ✅ | ✅ | ❌(压缩为一个) | ✅(但 SHA 变了) |
| 合并提交 | 可能有 | 一定有 | ❌ | ❌ |
| 回滚整个功能 | revert -m 1 | revert -m 1 | revert <sha> | 需要多次 revert |
| 适用场景 | 默认方式 | 保留完整历史 | PR 提交太碎/太乱 | 追求线性历史 |
Squash merge 后,feature 分支的提交并没有真正合入 main 的历史。如果继续在 feature 上开发并再次合并,可能产生冲突或重复代码。
# ❌ squash merge 后继续在同一分支上开发
git merge --squash feature
git commit -m "feat: 功能 A"
# ... 在 feature 上继续开发 ...
git merge --squash feature # 可能冲突!
# ✅ squash merge 后删除原分支,从 main 重新拉
git merge --squash feature
git commit -m "feat: 功能 A"
git branch -D feature
git checkout -b feature-v2 # 新分支继续开发当两个分支修改了同一文件的同一区域时,Git 无法自动决定保留哪个版本,需要手动解决。
<<<<<<< HEAD
// 当前分支(main)的修改
const value = 2;
=======
// 被合并分支(feature)的修改
const value = 3;
>>>>>>> feature# 1. 执行 merge 遇到冲突
git merge feature
# CONFLICT (content): Merge conflict in src/config.ts
# Automatic merge failed; fix conflicts and then commit the result.
# 2. 查看冲突文件
git status
# both modified: src/config.ts
# 3. 编辑冲突文件,删除冲突标记,选择或合并代码
vim src/config.ts
# 4. 标记已解决
git add src/config.ts
# 5. 完成合并
git commit
# 或
git merge --continue
# 如果想放弃合并
git merge --abort# 使用 VS Code 解决冲突(推荐)
# VS Code 会显示 Accept Current / Accept Incoming / Accept Both 按钮
# 使用 Git 内置的 mergetool
git mergetool
# 会打开配置的合并工具(vimdiff、meld、kdiff3 等)
# 配置默认合并工具
git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait $MERGED'# 冲突时自动采用当前分支(ours)的版本
git merge -X ours feature
# 冲突时自动采用被合并分支(theirs)的版本
git merge -X theirs feature
# 完全使用 ours 策略(忽略 feature 的所有修改)
git merge -s ours feature
# ⚠️ 注意:-s ours 和 -X ours 不同!
# -s ours:完全忽略 feature 的修改,只保留 main
# -X ours:只在冲突时用 main 的版本,非冲突部分正常合并-s ours vs -X ours 的区别-s ours(策略级别):完全丢弃被合并分支的所有修改,只记录合并关系-X ours(选项级别):正常合并,只在冲突时优先使用当前分支的版本大多数情况下你想用的是 -X ours,而不是 -s ours。
Git 支持多种合并算法,大多数时候你不需要手动指定:
| 策略 | 命令 | 说明 |
|---|---|---|
recursive | 默认(两个分支合并) | 最常用的三路合并策略 |
ort | Git 2.33+ 默认 | recursive 的替代品,更快更准确 |
resolve | -s resolve | 简单的三路合并,不处理重命名 |
ours | -s ours | 完全忽略被合并分支的修改 |
octopus | 默认(多分支合并) | 同时合并多个分支 |
subtree | -s subtree | 将子项目合并到子目录 |
# 同时合并多个分支
git merge feature-a feature-b feature-c
# 创建一个有 4 个父提交的合并提交
# 注意:octopus 合并不处理冲突
# 如果有冲突,需要逐个合并最基础的工作流——功能开发完成后合入主分支:
# 1. 确保 main 是最新的
git checkout main
git pull origin main
# 2. 合并 feature 分支
git merge --no-ff feature/payment -m "Merge feature/payment: 支付模块 (#456)"
# 3. 推送
git push origin main
# 4. 删除已合并的 feature 分支
git branch -d feature/payment
git push origin --delete feature/payment大多数团队通过 PR/MR 合并,平台提供多种合并选项:
在 GitHub 仓库设置中可以限制允许的合并方式: Settings → General → Pull Requests → Allow merge commits / squash / rebase
长期开发的 feature 分支需要定期同步 main 的更新:
# 方式 1:merge main 到 feature(推荐给新手)
git checkout feature/long-running
git merge main
# 如果有冲突就解决
# 方式 2:rebase feature 到 main(更干净但需要 force push)
git checkout feature/long-running
git rebase main
git push --force-with-lease origin feature/long-running| 对比项 | merge main 到 feature | rebase feature 到 main |
|---|---|---|
| 操作复杂度 | 低 | 中 |
| 历史整洁度 | 有很多合并提交 | 线性,干净 |
| 是否需要 force | ❌ | ✅ --force-with-lease |
| 冲突解决 | 一次性解决所有 | 可能每个提交都要解决 |
| 推荐场景 | 多人协作分支 | 个人 feature 分支 |
线上 bug 需要紧急修复并合并到 main 和 develop:
# 1. 从 main 创建 hotfix 分支
git checkout -b hotfix/critical-bug main
# 2. 修复 bug
git commit -m "fix: 修复订单金额计算错误 (#789)"
# 3. 合并到 main
git checkout main
git merge --no-ff hotfix/critical-bug -m "Merge hotfix: 修复订单金额计算错误"
git tag -a v2.0.1 -m "v2.0.1: hotfix"
# 4. 同时合并到 develop(保持 develop 也有这个修复)
git checkout develop
git merge --no-ff hotfix/critical-bug -m "Merge hotfix into develop"
# 5. 清理
git branch -d hotfix/critical-bug发布流程中 release 分支的合并:
# 1. 从 develop 创建 release 分支
git checkout -b release/v3.0 develop
# 2. 在 release 上做发布前的修改(版本号、CHANGELOG)
git commit -m "chore: bump version to 3.0.0"
# 3. 合并到 main
git checkout main
git merge --no-ff release/v3.0 -m "Merge release/v3.0"
git tag -a v3.0.0 -m "Release v3.0.0"
# 4. 合并回 develop
git checkout develop
git merge --no-ff release/v3.0 -m "Merge release/v3.0 back to develop"
# 5. 清理
git branch -d release/v3.0当两个分支互相合并过,历史变得复杂时:
# 场景:A merge B,然后 B merge A,再合并时可能出现问题
# 解决方案:使用 recursive 策略(默认)
git merge -s recursive feature
# 如果递归策略也有问题,尝试指定更多选项
git merge -s recursive -X patience feature将两个没有共同历史的仓库合并(如合并 monorepo):
# 1. 添加另一个仓库作为 remote
git remote add other-repo https://github.com/org/other-repo.git
git fetch other-repo
# 2. 合并不相关的历史
git merge other-repo/main --allow-unrelated-histories
# 3. 解决可能的冲突
# 4. 提交--allow-unrelated-histories从 Git 2.9 开始,默认不允许合并没有共同祖先的分支。使用此选项可以强制合并。常见场景:
--no-commit:合并但不自动提交 # 合并但不提交,允许你在提交前做修改
git merge --no-commit feature
# 检查合并结果
git diff --cached
# 做额外修改
vim src/config.ts
# 手动提交
git commit -m "merge: 合并 feature 并调整配置"# 不是真正的 merge,但经常被误认为是 merge 的需求
# 从 feature 分支获取特定文件
git checkout feature -- src/utils.ts
git commit -m "feat: 从 feature 分支引入 utils"
# 真正的 merge 无法只合并特定文件
# 如果需要部分合并,考虑使用 cherry-pick# 模拟合并,查看是否有冲突
git merge --no-commit --no-ff feature
git diff --cached --stat
# 查看合并会影响哪些文件
# 放弃模拟合并
git merge --abort
# 查看合并后的 diff(不实际合并)
git diff main...feature
# 显示 feature 相对于 merge base 的差异# 使用 -s ours 策略:记录合并关系但不引入修改
# 常用于「标记某个分支已经被处理过」
git merge -s ours obsolete-branch -m "merge: 标记 obsolete-branch 已处理"# 1. 确保工作区干净
git status
# 2. 更新目标分支
git checkout main
git pull origin main
# 3. 预览要合并的提交
git log main..feature --oneline
# 4. 预览文件变更
git diff main...feature --stat
# 5. 执行合并
git merge --no-ff feature# 方式 1:合并后还没推送 → 用 reset
git reset --hard HEAD~1
# 或
git reset --hard ORIG_HEAD
# 方式 2:合并后已推送 → 用 revert
git revert -m 1 <merge-commit-sha>
# -m 1 表示回到第一个父提交(即 main 在合并前的状态)详见 Git reset 和 Git revert
# 查看合并提交的两个父提交
git log --oneline -1 <merge-sha>
git cat-file -p <merge-sha>
# 显示 parent 1 和 parent 2
# 查看合并引入了什么修改
git diff <merge-sha>^1 <merge-sha> # 相对于 main 的差异
git diff <merge-sha>^2 <merge-sha> # 相对于 feature 的差异
# 查看哪些文件在合并中被修改
git show <merge-sha> --stat# 在 ~/.gitconfig 中
[merge]
# 默认不允许 fast-forward(推荐团队使用)
ff = false
# 合并提交信息模板
log = true
# 合并时显示冲突的共同祖先版本
conflictstyle = diff3
# diff3 风格的冲突标记(多显示祖先版本)
# <<<<<<< HEAD
# value = 2
# ||||||| base
# value = 1 ← 祖先版本,帮助理解变更意图
# =======
# value = 3
# >>>>>>> featureconflictstyle = diff3diff3 多显示了冲突区域的祖先版本(原始代码),让你知道「之前是什么 → 两边分别改成了什么」,极大提高冲突解决的准确性。
[alias]
# 安全合并(no-ff + 显示 log)
mg = merge --no-ff
# 查看待合并的提交
mg-preview = "!f() { git log HEAD..$1 --oneline; }; f"
# 查看待合并的文件变更
mg-files = "!f() { git diff HEAD...$1 --stat; }; f"
# 快速放弃合并
mg-abort = merge --abort
# 检查分支是否已合并到当前分支
mg-check = branch --merged# 查看已合并到 main 的分支(可以安全删除)
git branch --merged main
# 查看未合并到 main 的分支
git branch --no-merged main
# 批量删除已合并的分支
git branch --merged main | grep -v "main\|develop" | xargs git branch -d在 Git Flow 模型中,不同类型的分支有不同的合并规则:
# Feature → develop(squash 或 no-ff)
git checkout develop
git merge --no-ff feature/payment
# Release → main + develop
git checkout main
git merge --no-ff release/v3.0
git tag -a v3.0.0 -m "Release v3.0.0"
git checkout develop
git merge --no-ff release/v3.0
# Hotfix → main + develop
git checkout main
git merge --no-ff hotfix/critical
git tag -a v3.0.1 -m "Hotfix v3.0.1"
git checkout develop
git merge --no-ff hotfix/critical长期未同步的分支合并时可能有几十个冲突文件:
# 1. 先预览冲突量
git merge --no-commit --no-ff feature
git diff --name-only --diff-filter=U
# 列出所有冲突文件
git diff --name-only --diff-filter=U | wc -l
# 42 个冲突文件
# 2. 如果太多,考虑放弃并分批合并
git merge --abort
# 3. 分批策略:先 merge main 到 feature(在 feature 上解决冲突)
git checkout feature
git merge main
# 在 feature 上解决冲突(不影响 main 的稳定性)
# 解决完毕后
git checkout main
git merge --no-ff feature # 此时应该没有冲突了--ours / --theirs 快速处理明确的文件——如锁文件、自动生成的文件# 对于 package-lock.json 等锁文件,直接用 theirs
git checkout --theirs package-lock.json
npm install # 重新生成正确的锁文件
git add package-lock.json# 1. 合并到 staging 分支先测试
git checkout staging
git merge --no-ff feature/payment
# 2. 运行完整测试
npm run test:all
npm run test:e2e
# 3. 测试通过后,再合并到 main
git checkout main
git merge --no-ff feature/payment
# 4. 测试失败?在 staging 上 revert
git checkout staging
git revert -m 1 HEAD# 子模块冲突的表现
git merge feature
# CONFLICT (submodule): Merge conflict in lib/shared
# 解决方式 1:使用 feature 分支的子模块版本
git checkout --theirs lib/shared
git add lib/shared
# 解决方式 2:手动选择子模块版本
cd lib/shared
git checkout <desired-commit>
cd ../..
git add lib/shared
# 解决方式 3:更新到子模块的最新版本
git submodule update --remote lib/shared
git add lib/shared
git merge --continue在团队中配置分支保护规则,限制合并方式:
# GitHub 分支保护规则(Settings → Branches → Branch protection rules)
# ✅ Require pull request reviews before merging(需要审查)
# ✅ Require status checks to pass before merging(需要 CI 通过)
# ✅ Require linear history(只允许 squash 或 rebase merge)
# ✅ Include administrators(管理员也受限制)
# 本地配置:禁止直接推送到 main
# 在 .git/hooks/pre-push 中
#!/bin/bash
BRANCH=$(git rev-parse --abbrev-ref HEAD)
if [ "$BRANCH" = "main" ]; then
echo "❌ 禁止直接推送到 main 分支!请使用 Pull Request。"
exit 1
firerere 自动记忆冲突解决方案 rerere(REuse REcorded REsolution)让 Git 记住你解决过的冲突,下次遇到相同的冲突时自动应用:
# 启用 rerere
git config --global rerere.enabled true
# 工作流程:
# 1. 第一次合并遇到冲突
git merge feature
# CONFLICT!
# 2. 手动解决冲突
vim src/config.ts
git add src/config.ts
git commit
# 3. rerere 自动记录了解决方案
# 4. 下次遇到相同的冲突(如 rebase 时),rerere 自动应用
git rebase main
# rerere: Resolved 'src/config.ts' using previous resolution
# 管理 rerere 缓存
git rerere status # 查看当前记录
git rerere diff # 查看记录的解决方案
git rerere forget <file> # 忘记特定文件的记录# 合并冲突解决完整流程
# 1. 查看冲突文件列表
git diff --name-only --diff-filter=U
# 2. 逐个解决冲突(推荐使用 IDE 的可视化工具)
# VS Code: 点击 "Accept Current" / "Accept Incoming" / "Accept Both"
# 3. 对特定类型文件使用自动策略
git checkout --ours .eslintrc.json # 保留我们的配置
git checkout --theirs package-lock.json # 使用他们的锁文件
npm install # 重新生成锁文件
# 4. 验证没有残留冲突标记
grep -rn "<<<<<<\|>>>>>>" src/
# 5. 构建和测试
npm run build
npm test
# 6. 提交
git add .
git merge --continue# ❌ 不更新就合并
git checkout main
git merge feature # main 可能不是最新的!
# ✅ 先拉取最新代码
git checkout main
git pull origin main
git merge feature-s ours 和 -X ours# ❌ 想保留 main 的冲突版本,但用了 -s ours
git merge -s ours feature
# 结果:feature 的所有修改都被丢弃了!
# ✅ 应该用 -X ours
git merge -X ours feature
# 只在冲突时保留 main 的版本Squash merge 不会创建合并关系,Git 不知道这些提交已经被合并了。在原分支上继续开发再合并时可能出问题。
解决方案:squash merge 后删除原分支,从 main 重新拉新分支。
git addgit merge feature
# 冲突!手动编辑文件解决冲突...
# ❌ 直接 commit(冲突标记还在!)
git commit -m "merge"
# ✅ 先 add,再 commit
git add src/config.ts
git commit -m "merge"git merge --abort 丢失工作merge --abort 会丢弃所有合并过程中的修改(包括你已经解决的冲突)。确认不再需要这些修改后再使用。
# 解决冲突后检查是否有遗漏
git diff --check # 检查冲突标记残留
grep -rn "<<<<<<" src/ # 搜索冲突标记
grep -rn ">>>>>>" src/没有绝对答案,取决于团队约定:
| 场景 | 推荐方式 |
|---|---|
| 合入 main | merge |
| 同步上游到个人分支 | rebase |
| 需要保留完整历史 | merge |
| 追求线性历史 | rebase |
| 共享的长期分支 | merge |
| 个人的短期 feature 分支 | rebase |
详见 Git rebase
推荐默认使用 --no-ff,在 .gitconfig 中设置 merge.ff = false。
git reset --hard ORIG_HEADgit revert -m 1 <merge-sha>详见 Git reset 和 Git revert
# 检查 feature 是否已合并到 main
git branch --merged main | grep feature
# 或查看 feature 的最新提交是否在 main 中
git merge-base --is-ancestor feature main && echo "已合并" || echo "未合并"merge 不支持只合并部分文件。替代方案:
git cherry-pick --no-commit + git checkout HEAD -- <files> 选择性保留git checkout <branch> -- <file> 获取特定文件# PowerShell 中 ^ 是转义字符,需要用引号
git diff "HEAD^1" "HEAD" # 查看合并的第一个父提交差异
# 或使用 ~ 代替
git diff HEAD~1 HEAD| 目标 | 命令 |
|---|---|
| 基本合并 | git merge <branch> |
| 强制创建合并提交 | git merge --no-ff <branch> |
| 只允许 fast-forward | git merge --ff-only <branch> |
| Squash 合并 | git merge --squash <branch> |
| 合并不提交 | git merge --no-commit <branch> |
| 冲突时用当前分支版本 | git merge -X ours <branch> |
| 冲突时用被合并分支版本 | git merge -X theirs <branch> |
| 完全忽略被合并分支 | git merge -s ours <branch> |
| 同时合并多个分支 | git merge <branch1> <branch2> |
| 合并不相关的历史 | git merge --allow-unrelated-histories |
| 放弃合并 | git merge --abort |
| 冲突解决后继续 | git merge --continue |
| 撤销已完成的合并(未推送) | git reset --hard ORIG_HEAD |
| 撤销已推送的合并 | git revert -m 1 <merge-sha> |
| 查看已合并分支 | git branch --merged |
| 查看未合并分支 | git branch --no-merged |