☰
gbe_fork 工程实战:把仓库内的本地分支添加为 Git 子模块(submodule)的完整操作指南
2026/10/4 1:58:45 网站建设 项目流程
  • 游戏开发
  • 逆向工程

【免费下载链接】gbe_fork

Fork of https://gitlab.com/Mr_Goldberg/goldberg_emulator

项目地址:https://gitcode.com/gh_mirrors/gbe/gbe_fork
点击查看免费下载

本文源自 gbe_fork(Goldberg Emulator 分支)仓库中的开发笔记 dev.notes/how to add a branch as a submodule.md,讲解如何把当前仓库内一个尚未推送到远端的本地分支,以file://文件协议的方式挂载为子模块。该手法正是本仓库third-party/目录下 Linux/Windows 预编译依赖管理方案的实际落地方式。读完本文,你将掌握「孤儿分支创建 → 文件暂存提交 → 本地协议添加子模块 → 修正.gitmodules相对路径 → 最终提交」的完整九步流程,并能直接迁移到任何需要"仓库内嵌子模块"的工程场景。

背景:为什么 gbe_fork 需要"把分支添加为子模块"

在 gbe_fork 中,third-party/目录下存放着构建所依赖的第三方预编译产物与工具链,它们被划分为build、common、deps三组,每组又按win、linux(以及deps额外包含的common)平台拆分。查看仓库根目录的 .gitmodules 可以看到,这些目录全部是以子模块形式挂载的,并且每个子模块的url都是./(即指向仓库自身),branch则指向对应的平台分支名:

[submodule "third-party/build/win"] path = third-party/build/win url = ./ branch = third-party/build/win [submodule "third-party/common/linux"] path = third-party/common/linux url = ./ branch = third-party/common/linux [submodule "third-party/deps/win"] path = third-party/deps/win url = ./ branch = third-party/deps/win

这意味着:子模块的"数据源"不是外部的 GitHub/GitLab,而是同一个仓库内的某个专用分支。例如构建脚本 premake5.lua 中引用了third-party/build/win/cert/sign_helper.bat,该文件就来自third-party/build/win这个子模块分支;若仓库在克隆后没有正确拉取这些子模块,构建流程就会缺文件。

问题在于:这种"本地分支作为子模块"的挂载方式,git submodule add默认并不支持——它要求子模块仓库有一个可解析的 URL,且通常会尝试访问该 URL 的origin。而本仓库的开发场景往往是分支先只在本地存在、尚未推送到远端,因此需要本文这套九步流程。

前置条件与适用前提

  • Git 版本较新(本文命令在 git 2.39.5 上验证可用;-c protocol.file.allow=always选项从 Git 2.29 左右开始稳定支持)。
  • 目标分支尚未推送到远端(已推送也可执行,但本地协议依然是最稳的方式)。
  • 全程在当前仓库工作目录内执行,不需要额外克隆任何远程仓库。

九步完整流程

第 1 步:创建一个孤儿分支

孤儿分支(orphan branch)与当前分支没有任何共享提交历史,适合用来专门承载"第三方依赖文件"这类与主代码历史无关的内容:

git checkout --orphan 'third-party/my-branch'

例如本仓库实际使用的分支名是third-party/build/win、third-party/common/linux等,命名约定即为third-party/<平台或用途>。

第 2 步:清空暂存区

git checkout --orphan会把当前工作目录里所有已跟踪文件保留在工作区中,但不会暂存。为了让新分支只包含你想放的依赖文件,需要把所有已跟踪文件从索引中移除(仅从索引移除,不删除磁盘文件):

git rm -r -f --cached .

--cached是关键:文件依然留在磁盘上,只是不再被 Git 跟踪。这样后续git add才能从"空索引"重新挑选文件。

第 3 步:拷入需要的新文件

把要放进该分支的文件复制到工作区(或直接使用工作区中已经存在的文件):

cp ~/myfile.txt ./

实际场景中通常是拷贝预编译好的.dll、.so、.lib、工具脚本(如sign_helper.bat)等到third-party/对应的平台目录结构里。

第 4 步:暂存所需文件

只暂存真正需要的文件:

git add myfile.txt

如果目录里就是一堆依赖文件,也可以直接全部暂存:

git add .

第 5 步:提交

git commit -m 'my commit msg'

此时仓库内就诞生了一个独立的、只包含依赖文件的历史分支。

第 6 步:以文件协议把分支添加为子模块(关键)

git -c protocol.file.allow=always submodule add -f -b 'third-party/my-branch' file://"$(pwd)" 'my-relative-dir/without/dot/at/beginning'

这条命令包含三层关键信息,逐一拆解:

  1. -c protocol.file.allow=always:Git 出于安全考虑,默认禁止使用本地文件协议(file://)拉取仓库。这个选项强制放行本地协议,是整条命令能跑通的前提(也可写成git config --global protocol.file.allow always永久放行,但用-c仅对单条命令生效更安全)。
  2. -f(force):强制添加。由于目标目录此前可能已在索引或工作区出现过(例如从主分支带过来的占位目录),不加-f会报错。
  3. file://"$(pwd)":这里刻意不使用./,因为如果写成./,Git 会去解析当前仓库的origin远端地址(GitHub/GitLab 等)。而该分支尚未推送到 origin,解析必然失败。file://加上$(pwd)展开出的绝对路径,则强制 Git 直接读取本地仓库文件作为子模块数据源。
  4. -b 'third-party/my-branch':指定子模块要跟踪的分支名。
  5. 目录名不带./前缀:目标子模块路径my-relative-dir/without/dot/at/beginning应写成不带点前缀的相对路径,这正是.gitmodules中path = third-party/build/win的写法。

当然,如果你先把分支推送到 origin 再执行本步,Git 也能正常从远端拉取——本地协议方案的价值就在于"分支还没推送也能先挂载",让子模块的建立不依赖远端发布节奏。

第 7 步:修正.gitmodules中的路径

第 6 步执行后,.gitmodules里url会被写成磁盘上的绝对路径(即$(pwd)的值)。绝对路径在其他机器、其他用户目录下都会失效,因此必须改回相对路径。此时再次执行子模块添加命令,改用./作为 url:

git -c protocol.file.allow=always submodule add -f -b 'third-party/my-branch' ./ 'my-relative-dir/without/dot/at/beginning'

这次不会再去 origin 拉数据,因为数据源已经是本仓库自己;Git 只是借这条命令把.gitmodules中的 url 重写为./。完成后的.gitmodules片段与仓库根目录现存的内容完全一致(url = ./+branch = ...)。

第 8 步:查看待提交的改动

git status

典型输出如下:

On branch third-party/my-branch Changes to be committed: (use "git restore --staged <file>..." to unstage) modified: .gitmodules new file: third-party/my-branch

可以看到只会暂存两个东西:被修改的.gitmodules配置,以及一个指向子模块提交的 gitlink 条目(new file: third-party/my-branch)。

第 9 步:提交这两个文件

git commit -m 'add branch third-party/my-branch as submodule'

至此"本地分支作为子模块"就正式固化进仓库历史了。之后其他协作者克隆本仓库时,执行git submodule update --init --recursive,Git 会依据.gitmodules中的url = ./在本仓库内查找third-party/my-branch分支对应的提交来填充目录内容。

常见问题与避坑要点

  • 为什么不能用./直接作为第 6 步的 url?因为./会触发 Git 走origin协议解析路径,而分支未推送时 origin 上查不到该分支,submodule add直接失败。必须先用file://"$(pwd)"让 Git 从本地读取。
  • -f不加会怎样?目标目录已在索引中存在时,Git 会拒绝添加,报"already exists in the index"一类的错误,所以-f几乎是必带的。
  • .gitmodules的绝对路径隐患:忘掉第 7 步直接提交,url会是类似file:///data/web/...的绝对路径,换台机器、换个用户目录就会失效;改成./才是可移植的写法。
  • 安全提示:protocol.file.allow=always全局放开会让 Git 接受来自任何仓库的本地文件协议请求(存在被恶意仓库读取本机路径的隐患),建议仅在需要时用-c临时放行,不要长期全局配置。

小结

gbe_fork 用"本地分支即子模块"的模式,把各平台预编译依赖与主代码历史彻底隔离在third-party/*子模块分支中,既避免了在主分支里混入二进制大文件、拖慢历史,又能让构建脚本(如 premake5.lua 引用third-party/build/win/cert/sign_helper.bat)按统一目录约定取到依赖。其核心方法论可以沉淀为三条:

  1. 用孤儿分支承载独立于主历史的依赖文件;
  2. 用file://"$(pwd)"+-c protocol.file.allow=always绕过未推送分支无法被./解析的限制;
  3. 用第二次submodule add(url 改回./)把.gitmodules修正为可移植的相对路径。

掌握这套流程,你就能在自己的多平台 C/C++ 工程里复刻同样的依赖管理方案。

  • 游戏开发
  • 逆向工程

【免费下载链接】gbe_fork

Fork of https://gitlab.com/Mr_Goldberg/goldberg_emulator

项目地址:https://gitcode.com/gh_mirrors/gbe/gbe_fork
点击查看免费下载
上一篇:Down的7种输出格式详解:HTML、XML、LaTeX、groff man、CommonMark、NSAttributedString和AST
下一篇:GO Feature Flag数据导出实战:从S3到Kafka的完整方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询