背景
在实际开发中,经常会遇到这样的场景:一个项目从主仓库拷贝出去,交给其他团队(或供应商)独立开发新功能,开发完成后需要将成果集成回主仓库。
本文总结了几种常见的合并方案,重点介绍最推荐的「直接合并远程分支」方案,帮助你快速、安全地完成多仓库代码集成。
适用场景
- 项目 B 是从项目 A 拷贝/fork 出去的,目录结构基本一致
- 项目 B 在原有基础上新增了页面、模块或子包
- 需要将项目 B 的成果合并回项目 A
- 后续项目 B 可能还会持续更新,需要多次同步
方案对比
| 方案 | 保留 Git 记录 | 后续同步 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| 直接合并分支 | ✅ 完整 | ✅ git merge | 低 | 两个仓库结构一致,推荐首选 |
| git subtree | ✅ 完整 | ✅ subtree pull | 中 | 需要将外部仓库映射到子目录 |
| git submodule | ✅ 完整 | ✅ submodule update | 高 | 外部仓库需要独立版本管理 |
| 手动拷贝文件 | ❌ 丢失 | ❌ 每次手动 | 低 | 一次性集成,不再更新 |
推荐方案:直接合并远程分支
为什么推荐
当两个仓库有相同的代码基础(从同一份代码拷贝出去),Git 能识别共同的文件基础,合并时只会引入增量变更。这和日常合并功能分支的体验完全一致:
- 零额外工具 — 纯 Git 操作,团队所有人都熟悉
- 完整保留提交记录 — 外部仓库的每一条 commit 都保留
- 后续同步最简单 —
git fetch+git merge一步到位 - 冲突处理自然 — Git 三路合并,和日常开发体验一致
操作步骤
第一步:添加远程仓库
# 将外部仓库添加为一个新的 remote
git remote add external <外部仓库地址>
# 拉取外部仓库的所有分支信息
git fetch external
external是自定义的远程名称,可以根据实际情况命名,比如vendor、partner等。
第二步:创建集成分支
# 从主分支创建一个独立的集成分支,避免直接影响主分支
git checkout main
git checkout -b feat/integrate-external
第三步:执行合并
# 合并外部仓库的主分支
git merge external/main
如果两个仓库没有共同的 Git 祖先(比如外部仓库是全新 git init 的,而不是 fork),需要加上 --allow-unrelated-histories 参数:
git merge external/main --allow-unrelated-histories
第四步:解决冲突
合并时大概率会在公共配置文件上产生冲突,比如:
- 路由配置文件
- package.json / 依赖管理文件
- 全局样式文件
- 入口文件
解决冲突的原则:
# 查看所有冲突文件
git diff --name-only --diff-filter=U
# 逐个解决冲突后标记为已解决
git add <冲突文件>
# 全部解决后提交
git commit
第五步:审查与合入
# 审查本次合并引入的所有变更
git diff main..feat/integrate-external --stat
# 确认没问题后,合入主分支(建议通过 MR/PR 流程)
git checkout main
git merge feat/integrate-external
后续同步
当外部仓库有新的更新时,同步非常简单:
# 拉取最新代码
git fetch external
# 创建同步分支
git checkout -b sync/external-$(date +%Y%m%d)
# 合并更新
git merge external/main
# 解决冲突(如果有),审查后合入主分支
Git 会基于上次合并点做增量 diff,只合入新的 commit,冲突会比首次少很多。
关键注意事项
1. 首次合并前做好准备
- 了解外部仓库改了什么:让对方提供变更清单,或者先 fetch 后用
git log external/main --oneline查看提交记录 - 备份当前分支:虽然 Git 操作都可以回退,但创建一个备份分支更安心
git branch backup/before-merge
2. 重点关注公共文件的冲突
外部团队在开发过程中,可能修改了一些不该改的公共文件。合并时要特别审查:
- 配置文件:路由配置、构建配置、环境变量等
- 依赖文件:package.json、lock 文件等
- 全局文件:入口文件、全局样式、公共工具函数等
对于不需要的改动,可以在合并后针对性 revert:
# 查看某个文件在合并中的变更
git diff main -- <文件路径>
# 如果某个文件的改动不需要,恢复为主分支的版本
git checkout main -- <文件路径>
git commit -m "revert: 还原不需要的改动"
3. 处理「不相关历史」的情况
如果外部仓库不是从主仓库 fork 出去的,而是:
- 把代码打包发过去,对方重新
git init - 或者通过其他方式创建的独立仓库
这时两个仓库没有共同祖先,Git 默认会拒绝合并。解决方法:
git merge external/main --allow-unrelated-histories
这种情况下首次合并的冲突会比较多(因为 Git 无法判断哪些文件是「相同的基础」),需要耐心逐个解决。但解决完一次后,后续同步就正常了。
4. 建议的长期工作流
main
│
├── feat/integrate-external ← 首次合并(通过 MR 审查)
│
├── sync/external-20260428 ← 第二次同步
│
└── sync/external-20260515 ← 第三次同步
每次同步都走独立分支 + MR/PR 审查流程,确保主分支始终可控。
5. 集成完成后的清理
如果外部仓库不再需要同步,可以移除远程引用:
git remote remove external
这不会影响已经合并进来的代码和提交记录。
其他方案简述
git subtree
适用于需要将外部仓库映射到主仓库的某个子目录的场景:
# 首次添加
git subtree add --prefix=path/to/subdir external main --squash
# 后续同步
git subtree pull --prefix=path/to/subdir external main --squash
优点是外部代码被隔离在指定目录,缺点是当两个仓库目录结构一致时反而不方便。
git submodule
适用于外部仓库需要保持完全独立、按版本引用的场景:
# 添加子模块
git submodule add <外部仓库地址> path/to/subdir
# 更新子模块
git submodule update --remote
优点是版本管理精确,缺点是团队成员需要额外学习 submodule 的操作流程,clone 时也需要 --recursive。
总结
对于「从主仓库拷贝出去开发,再合并回来」这种场景,直接合并远程分支是最简单、最自然的方案。它不引入额外的工具或概念,后续同步成本最低,团队协作也最顺畅。核心就三步:
git remote add— 添加远程仓库git fetch+git merge— 拉取并合并- 解决冲突,审查合入
掌握这个方法,多仓库合并就不再是难题。