Merge —— 这是 Git 中合并分支的操作,它将来自两条不同开发线的更改合并到一个目标分支中。与 rebase 不同,merge 会保留完整的分支历史,创建一个带有两个父提交的特殊 merge 提交。根据 Git 官方文档(2026),merge 是合并分支最安全的方式,因为它不会重写历史,并且可以跟踪何时合并了哪些分支。这是公共分支(如 main、develop 和 release)合并的标准选择。
要点
Merge —— 这是 git merge 命令,它将指定分支的更改合并到当前分支中。Git 找到共同祖先(共同的基础提交),计算每个分支相对于祖先的差异,并创建一个包含合并更改集的 merge 提交。结果 —— 目标分支将包含来自被合并分支的所有更改。
语法:在目标分支(例如 main)中,执行 git merge feature。如果没有冲突,Git 会自动创建一个 merge 提交。在 merge 提交的默认消息中显示:“Merge branch ’feature’ into main”。可以通过 -m 标志更改消息,或在打开的编辑器中编辑。
Merge 是一种非破坏性操作。与 rebase 不同,merge 不会触及现有的提交:它们会保留相同的哈希、作者和日期。这使得 merge 成为多个开发人员同时工作的分支的唯一安全合并方式。如果出现问题,可以使用 git merge --abort 命令取消 merge。
# 切换到目标分支
git checkout main
# 合并功能分支
git merge feature
# 结果 —— 带有两个父提交的 merge 提交
git log --oneline --graph
# 使用自定义消息合并
git merge feature -m "feat: integrate authentication module"
Git 支持三种合并模式,根据期望的结果进行选择。Regular merge(默认)会创建 merge 提交。Squash merge 将 feature 分支的所有提交合并为一个。Fast-forward —— 如果可能,直接向前移动分支指针而不创建提交。模式的选择取决于团队的工作流程和历史记录规则。
Regular merge (--no-ff) —— 即使合并可以以 fast-forward 方式执行,也会创建 merge 提交。建议用于 main 分支:merge 提交明确标记了功能集成的时刻,并允许通过一次 revert merge 提交轻松回滚 feature 分支的所有更改。GitHub 在通过 Merge 按钮合并 PR 时默认使用此模式。
Squash merge (--squash) —— 将 feature 分支的所有提交收集到目标分支中的一个提交中。当 feature 分支的草稿历史不应进入 main 时很有用。缺点:与原始提交的连接丢失 —— 无法看到功能逐步开发的过程。GitHub 在 PR 中选择 “Squash and merge” 时使用此模式。
Fast-forward (--ff) —— 如果目标分支在 feature 分支之后没有新的提交,Git 只是向前移动指针,而不创建 merge 提交。历史保持线性。--no-ff 标志强制创建 merge 提交,如果 fast-forward 不可行,--ff-only 将出错。
# 强制创建 merge 提交(建议用于 main)
git merge --no-ff feature
# Squash merge —— 将所有提交合并为一个
git merge --squash feature
git commit -m "feat: add authentication"
# 仅在可能时进行 fast-forward
git merge --ff-only feature
# 中止有冲突的 merge
git merge --abort
合并策略决定了 Git 用于组合更改的算法。每种策略适用于不同的场景。Git 会自动选择合适的策略,但开发人员可以通过 --strategy 标志显式指定它。理解这些策略有助于预测 Git 在复杂合并中的行为。
Recursive —— 合并两个分支的默认策略。Git 找到共同祖先,计算每个分支的更改并合并它们。如果找到了共同祖先,recursive 可以正确处理文件重命名和添加新文件。在冲突时,recursive 可以使用其他选项:ours(自动选择我们的版本)和 theirs(选择他们的版本)。
Octopus —— 用于同时合并两个以上的分支:git merge feature1 feature2 feature3。Octopus 不支持冲突解决 —— 所有冲突必须在调用命令前解决。很少使用,主要用于合并几个保证不冲突的独立分支(例如不同的模块)。
| 策略 | 分支数量 | 冲突解决 |
|---|---|---|
| Recursive | 2 | 自动 + ours/theirs 选项 |
| Octopus | 3+ | 否 —— 所有冲突必须事先解决 |
| Ours | 任意 | 始终选择我们的版本,忽略外部更改 |
| Subtree | 2 | 用于子树合并(subtree merge) |
Ours —— 一种特殊策略,完全忽略被合并分支的更改,保留目标分支的当前内容。Merge 提交被创建,但内容保持不变。当需要在历史中记录合并事实但实际拒绝来自其他分支的所有更改时很有用。
Merge 冲突发生在同一个文件的相同行在两个分支中被以不同方式修改时。Git 无法自动确定哪个版本是正确的,并暂停合并。冲突也可能发生在当一个分支中的文件被重命名而另一个分支中被修改时,或者当同一个文件被同时删除和修改时。
解决过程:Git 用标记标记冲突文件。在文件中出现带有 <<<<<<< HEAD(我们的版本)、=======(分隔符)和 >>>>>>> feature(他们的版本)的部分。开发人员手动编辑冲突部分,从两个版本中选择所需的行,删除标记,保存文件,并通过 git add 将其添加到索引中。
为了可视化解决冲突,Git 支持 mergetool —— 一种外部比较工具。流行的 mergetool 工具:Meld、KDiff3、Beyond Compare、VS Code(内置冲突编辑器)。Mergetool 显示三个面板:我们的版本、他们的版本和结果。开发人员直观地选择要包含在最终文件中的代码块。
# 开始合并并检测冲突
git merge feature
# 冲突(内容):在 src/main.swift 中的合并冲突
# 检查冲突文件
git status
# 打开可视化 mergetool
git mergetool
# 解决后 —— 添加并提交
git add src/main.swift
git commit
# 中止合并
git merge --abort
Merge 优先于 rebase 在几个关键情况下。第一:在处理其他开发人员可访问的公共分支时。Merge 不会重写历史,同事可以安全地同步。在公共分支上 rebase 将创建分歧的历史,并为所有已经收到旧提交的人带来冲突。
第二种情况:在完成 feature 分支时。大多数团队更倾向于使用 merge(带 --no-ff 标志)到 main,以记录功能集成的时刻。这简化了历史导航,并允许通过一次 git revert merge 提交轻松回滚整个功能。GitHub Flow 默认提供三种 merge 选项:简单 merge、squash merge 和 rebase merge。
第三种情况:在使用经过审查的 pull request 时。GitHub 和 GitLab 提供带有不同选项的 merge 按钮。Merge (Create a merge commit) —— 带有 merge 提交的完整历史。Squash and merge —— 没有开发细节的干净历史。Rebase and merge —— 没有 merge 提交的线性历史,但会重写提交。选择取决于团队的规则。
第一条规则:在 merge 之前始终处于目标分支的最新版本。在合并功能之前执行 git checkout main && git pull。这可以最大限度地减少冲突,并保证 merge 提交将包含所有最新的更改。如果目标分支已经超前很多,首先在 feature 分支内执行 git merge main,以便在其上下文中解决冲突。
第二条规则:在 merge 后测试代码。即使没有冲突,merge 也可能改变行为。CI/CD 管道应在发送到生产环境之前在 merge 提交上运行测试。一些团队使用 merge gates —— 强制检查,在通过之前阻止合并。
第三条规则:记录 merge 提交。标准的 “Merge branch ’feature’ into main” 消息用处不大。建议添加关于合并内容的描述:“Merge authentication module: login, registration, password recovery”。这简化了历史分析和回归查找。在大型项目中,merge 提交会自动从 PR 名称生成。
常见问题
合并(merge) —— 执行 git merge 将一个分支的更改合并到另一个分支。结果是一个 merge 提交,它记录了合并事实并包含来自两个分支的更改。这是在 Git Flow 中将功能分支集成到 main、develop 或 release 的主要方式。
Squash merge 将功能分支的所有提交合并到目标分支中的一个提交,丢失了中间的开发历史。普通 merge 创建一个 merge 提交,保留功能分支的所有提交。Squash merge 提供干净的历史,但不允许跟踪功能的逐步开发。
打开冲突文件,找到带有 <<<<<<< HEAD 和 >>>>>>> 标记的部分。编辑内容,保留两个版本中需要的行,删除标记。保存文件,执行 git add 和 git commit。你可以使用 git mergetool 进行可视化解决。
Merge 始终用于公共分支(main、develop、release),因为它不会重写历史。Rebase 在个人功能分支中在其发布之前使用。一旦分支成为共享仓库的一部分,同事引用了它,只允许 merge。
在 merge 完成之前(冲突期间)—— git merge --abort 完全取消合并。完成后 —— git revert <merge-commit-hash> -m 1 创建一个回滚提交。-m 1 标志指示保留哪个父分支(目标)。对于已发布的分支,git revert 比 git reset 更安全。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。