1. 场景背景与方案选择
在跨网络环境的代码管理场景中,我们经常会遇到这样的困境:主仓库位于公网(如Gitee),而子仓库(Demo代码)存储在内网GitLab服务器上。这种架构带来了两个核心挑战:
- 网络隔离问题:外部协作者无法直接访问内网GitLab,导致传统的git submodule方案失效
- 历史记录污染:直接将子仓库历史合并到主仓库会导致commit log混乱,特别是当子仓库有频繁提交时
1.1 为什么选择git subtree + squash方案
经过多次实践验证,我最终选择了git subtree配合--squash参数的方案,主要基于以下考量:
传统submodule的局限性:
- 仅存储子仓库的引用指针(commit hash)
- 需要额外执行
git submodule update --init才能获取实际代码 - 当子仓库不可达时(如内网环境),整个克隆过程会失败
subtree的核心优势:
- 将子仓库代码物理复制到主仓库指定目录
- 所有代码随主仓库一起提交,完全规避网络访问问题
- 通过
--squash参数可以将子仓库历史压缩为单条提交
实际案例:我们团队维护的文档仓库需要展示多个业务线的Demo代码。使用subtree后,外部合作伙伴只需克隆主仓库就能获得所有演示代码,而无需访问我们的内网GitLab。
2. 核心操作:引入子仓库代码
2.1 准备工作与常见误区
在开始操作前,有几个关键注意事项需要特别强调:
绝对不要提前手动clone子仓库:
git subtree add命令会自动处理子仓库的克隆- 如果目标目录已存在,Git会报错:
fatal: prefix 'xxx' already exists. - 遇到这种情况需要先删除已存在的目录
目录结构规划建议:
- 建议将第三方代码统一放在
thirdparty/目录下 - 按
<业务线>/<项目名>的层级组织 - 例如:
thirdparty/skysi/use_cases/kylin-v10-digitalme-docker
2.2 具体操作命令
在主仓库根目录执行以下命令(以两个Demo仓库为例):
bash复制# 引入第一个demo仓库
git subtree add \
--prefix=thirdparty/skysi/use_cases/kylin-v10-digitalme-docker \
git@192.168.1.18:penguido/kylin-v10-digitalme-docker.git \
master \
--squash
# 引入第二个demo仓库
git subtree add \
--prefix=thirdparty/skysi/use_cases/mt-docker \
git@192.168.1.18:penguido/mt-docker.git \
master \
--squash
命令解析:
--prefix:指定子仓库代码在主仓库中的存放路径- 远程仓库地址:使用SSH协议格式(确保有访问权限)
master:表示要引入的子仓库分支--squash:将子仓库历史压缩为单条提交
执行后,使用git log --oneline -5可以看到类似这样的提交记录:
code复制abcdef1 Merge commit 'xyz1234' into main
xyz1234 Squashed 'thirdparty/skysi/use_cases/kylin-v10-digitalme-docker/' content
3. 推送干净提交到Gerrit
3.1 为什么需要特殊处理
Gerrit代码审查系统对提交有以下特殊要求:
- 每个提交必须包含
Change-Id标识 - 提交历史应该清晰简洁,避免包含无关的合并提交
而git subtree add会自动产生两类提交:
- Squash提交:包含子仓库压缩后的代码
- Merge提交:将子仓库代码合并到主分支
这些自动生成的提交通常不符合Gerrit的要求,因此需要特殊处理。
3.2 三步打造完美提交
以下是创建符合Gerrit要求的干净提交的完整流程:
-
重置到添加subtree之前的状态:
bash复制git reset --soft <git subtree add前的commit id>这会保留所有文件变更,但撤销自动生成的提交
-
创建新的合规提交:
bash复制git commit -m "feat: add kylin and mt docker demos Add squashed subtree for: - thirdparty/skysi/use_cases/kylin-v10-digitalme-docker - thirdparty/skysi/use_cases/mt-docker Change-Id: Ixxx..."注意包含完整的变更描述和Change-Id
-
推送到Gerrit审查:
bash复制
git push origin HEAD:refs/for/skysi
4. 常见问题与解决方案
4.1 错误使用rebase导致的问题
问题现象:
尝试对git subtree add --squash产生的提交执行rebase后,出现复杂的合并冲突,甚至导致Gerrit推送失败。
根本原因:
- subtree生成的merge提交包含特殊父关系
- 直接rebase会破坏这种关系
解决方案:
- 立即中止错误操作:
bash复制
git rebase --abort - 使用正确的变基方法:
bash复制
git fetch origin skysi git rebase --onto origin/skysi HEAD~1
4.2 Gerrit推送后的补救措施
如果不慎将错误提交推送到Gerrit,即使abandon后重新推送,审核员合并时仍可能失败。
修复步骤:
- 确认当前提交历史:
bash复制git log --oneline -5 - 执行变基操作:
bash复制
git fetch origin skysi git rebase --onto origin/skysi HEAD~1 - 强制更新Gerrit上的变更:
bash复制
git push origin HEAD:refs/for/skysi
5. 高级技巧与最佳实践
5.1 后续更新子仓库代码
当子仓库有更新需要同步时,使用git subtree pull:
bash复制git subtree pull \
--prefix=thirdparty/skysi/use_cases/kylin-v10-digitalme-docker \
git@192.168.1.18:penguido/kylin-v10-digitalme-docker.git \
master \
--squash
5.2 保持Change-Id的一致性
Gerrit通过Change-Id追踪变更,建议:
- 在首次提交时生成Change-Id
- 后续更新时保持相同Change-Id
- 使用commit-msg钩子自动生成:
bash复制curl -Lo .git/hooks/commit-msg https://gerrit-review.googlesource.com/tools/hooks/commit-msg chmod +x .git/hooks/commit-msg
5.3 多环境协作建议
对于跨网络团队协作:
- 主仓库维护者负责subtree的更新
- 子仓库开发者通过MR/PR向子仓库提交变更
- 建立定期同步机制(如每周同步一次)