Merge(合并)——是Git中的一项操作,它将一个分支的更改合并到另一个分支,创建一个合并提交(merge commit)。Git支持多种策略:fast-forward(线性历史)、three-way merge(创建合并提交)和squash merge(将所有提交压缩为一个)。根据git-scm.com, 2025的数据,merge仍然是Git团队开发中使用最广泛的代码集成机制。
要点
Merge(合并)——是Git中的一项基本操作,它将更改从一个分支(source)合并到另一个分支(target)。合并的结果是,目标分支接收源分支中尚未存在于目标分支的所有提交。根据情况,Git可以以三种不同的方式执行合并。
Merge的主要价值在于保留历史:合并提交记录了分支合并的事实,保存了何时以及哪些分支被合并的信息。这有助于审计更改、查找回归以及理解开发时间线。在大型项目中,合并提交是代码集成的标准方式。
根据GitLab Flow的数据,73%使用Git的团队使用合并提交。替代方法(rebase、squash)则被倾向于线性历史的团队所青睐。策略的选择取决于团队规模、发布频率以及项目中达成的约定。
Merge在开发者完成某个功能的工作并希望将其集成到develop或main时是必需的。典型场景:开发者从develop创建了一个功能分支,工作了几天,在此期间develop中出现了来自其他参与者的新提交。在合并之前,需要组合更改——而merge就是用来做这个的。
没有merge,就无法在Git中协作处理同一代码。每当两名开发者同时向同一代码库进行更改时,他们的分支就会产生分歧。Merge——是将这些更改重新合并而不会丢失数据的唯一方法。
Git支持三种类型的merge,每种都适用于不同的场景。合并类型的选择会影响提交历史、回滚的便利性以及日志的可读性。
Fast-forward发生在目标分支自源分支创建以来没有新提交的情况下。在这种情况下,Git只需将目标分支的指针向前移动到源分支的最新提交。历史保持线性,没有合并提交。
# Fast-forward合并:自feature创建以来develop未发生变化
git checkout develop
git merge feature/new-login
# 结果:develop指针移动到feature的末端
# 未创建任何合并提交
Fast-forward适用于短生命周期的分支,开发者独自工作。但这种方法的缺点是:会丢失分支存在的信息——所有提交看起来就像直接在develop中进行的。
Three-way merge在两个分支在分歧点之后都有新提交时执行。Git创建一个单独的带有两个父提交的合并提交,记录分支合并的事实。这种方法在团队开发中推荐用于功能分支。
# 使用--no-ff标志强制进行three-way合并
git checkout develop
git merge --no-ff feature/new-login
# 已创建带有默认消息的合并提交
# 可以通过-m设置自定义消息
git merge --no-ff feature/new-login -m "Merge feature/new-login into develop"
--no-ff标志即使在fast-forward可能的情况下也保证创建合并提交。这是在项目中保留分支信息的最佳实践。
Squash merge将源分支的所有提交压缩为一个并应用到目标分支。功能历史丢失——一个包含所有更改的提交进入分支。这在功能分支中的详细提交对整体历史没有价值时非常方便。
# Squash合并:feature的所有提交被压缩为一个
git checkout develop
git merge --squash feature/experimental
git commit -m "feat: experimental login flow (squashed)"
Squash适用于草稿、实验性分支以及保持历史清洁非常重要的情况。缺点——与原始提交的联系丢失,使得回滚个别更改变得困难。
Ours和Theirs——Git中的两种特殊合并策略。Ours完全忽略来自源分支的更改,只保留目标分支中的内容。Theirs则相反,在每次冲突时接受源分支的版本。这些策略在合并大量代码时很有用,当预先知道哪个版本应该获胜时。
Git中的合并机制基于三个点的比较:共同祖先(merge base)、源分支的状态和目标分支的状态。Git找到merge base——两个分支共有的最新提交——并计算每个分支在分歧之后发生了哪些更改。
Git使用三方合并算法,它不仅考虑文件的两个比较版本,还考虑它们的共同祖先。因此,Git可以自动解决当一个分支的更改不影响另一个分支的已修改部分的情况——即使两个文件都已被修改。
考虑这样一个场景:两个开发者在同一个功能分支中处理不同的文件。第一个修改了LoginActivity.kt,第二个修改了ProfileFragment.kt。当他们合并更改时,Git看到更改涉及不同的文件,并自动执行合并,无需人工干预。
如果两个开发者都修改了LoginActivity.kt,但在不同的方法中——Git也会自动处理,逐行合并更改。只有在两人修改了相同的行,或者一人删除了另一人修改的代码时,才会发生冲突。
合并冲突在Git无法自动合并更改时发生,因为两个分支以不同方式修改了相同的行。在这种情况下,Git在文件中标记冲突区域,并等待开发者手动解决。
冲突区域用特殊标记标示:<<<<<<< HEAD显示来自目标分支的代码,=======——分隔符,>>>>>>> source-branch——来自源分支的代码。开发者必须手动选择保留哪个版本或将它们合并。
# 1. 运行merge并查看冲突
git merge feature/new-login
# 输出:CONFLICT (content): Merge conflict in LoginActivity.kt
# 2. 查看有冲突的文件列表
git status
# both modified: src/ui/login/LoginActivity.kt
# 3. 解决冲突:编辑文件,删除标记
# 4. 添加已解决的文件并完成merge
git add src/ui/login/LoginActivity.kt
git merge --continue
# 或:git commit(不带--continue)
解决冲突有工具:git mergetool打开可视化的合并工具(Meld、Beyond Compare、VS Code)。许多开发者更喜欢在IDE中解决冲突——IntelliJ IDEA和Android Studio提供了内置的三面板比较工具,大大简化了这一过程。
解决冲突的建议:始终理解冲突每一方的作用,不要在不理解其逻辑的情况下删除他人的代码,如果冲突过于复杂——邀请两个分支的作者共同解决。
在Merge和Rebase之间选择——是Git中最常见的架构决策之一。两种方法都合并更改,但方式不同:merge保留分支历史,rebase重写历史使其线性化。
许多团队使用混合方法:使用rebase将功能分支更新到develop的当前状态(git rebase develop),然后使用带--no-ff标志的merge来记录合并。这样可以在功能内部提供干净的历史,并在develop级别提供信息丰富的合并点。
常见问题
不带--no-ff时,Git在可能的情况下执行fast-forward合并——只需移动分支指针。带--no-ff时,Git总是创建一个合并提交,保留分支信息。推荐在团队开发中用于功能分支。
使用git mergetool或IDE的内置工具。如果冲突涉及数十个文件——可能分支分歧过于严重。在这种情况下,应该与团队讨论合并计划,或许将其分成多个阶段。
可以:git merge --abort取消合并,如果尚未完成(冲突)。如果合并已经完成——使用git reset --hard HEAD~1或git revert -m 1 <merge-commit>进行安全回滚。
建议用于团队协作。合并提交记录合并的事实,包含对两个分支的引用,并有助于理解历史。对于个人或实验性分支,squash merge或fast-forward是可以接受的。
Git无法自动合并二进制文件——它整体选择其中一个版本。对于二进制文件(图片、.aab、.apk),建议最小化并行更改,并对大文件使用Git LFS。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。