如何将Git sub-module吸收回原仓库并保留子模块提交历史
操作前请确保主仓库
M、子模块仓库A/B的所有未提交内容均已提交或暂存,避免操作过程中文件丢失。
1. 清理主仓库原有子模块关联
进入主仓库M的根目录,依次执行以下命令移除旧的子模块配置:
- 注销子模块注册状态
git submodule deinit -f -- A B - 从git索引中移除子模块目录
git rm -f A B - 删除本地残留的子模块缓存文件
rm -rf .git/modules/A .git/modules/B - 提交清理改动
git commit -m "chore: remove old submodule config before history merge"
2. 关联子模块仓库为临时远程
如果只需要保留近2个月的提交历史,先分别进入A、B仓库根目录,执行以下命令拿到2个月前的截断提交ID,记为A_BASE、B_BASE:git log --before="2 months ago" --pretty=format:"%H" -1
回到主仓库M根目录,添加子模块仓库为临时远程,支持填本地仓库路径或远程仓库地址:
- 添加A仓库为临时远程
git remote add -f subA /path/to/your/local/A/repo - 添加B仓库为临时远程
git remote add -f subB /path/to/your/local/B/repo
上述路径替换为你本地A、B仓库的实际存储路径即可,本地路径拉取速度远快于远程地址。
3. 重写子模块路径并合并历史
因为子模块仓库的提交历史默认以自身根目录为基准,需要先把历史内的文件路径统一加上A/、B/前缀,再合并到主仓库,才能保证文件落到对应子目录且历史连续。
合并A仓库历史
- 基于A仓库的工作分支创建临时分支,如果你的分支名是master就把main替换成master
git checkout -b tempA subA/main - 重写临时分支的文件路径,把所有文件放到A目录下;如果要截断历史,把命令末尾的HEAD换成
${A_BASE}..HEAD即可git filter-branch --index-filter 'git ls-files -s | sed "s-\t\"*-&A/-" | GIT_INDEX_FILE=$GIT_INDEX_FILE.new git update-index --index-info && mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE' HEAD - 切回主仓库主分支
git checkout main - 合并重写后的A仓库历史
git merge tempA --allow-unrelated-histories
如果合并时提示冲突,直接解决冲突后执行
git add [冲突文件]再提交即可。
合并B仓库历史
操作逻辑和A完全一致,替换对应目录名和分支名即可:git checkout -b tempB subB/maingit filter-branch --index-filter 'git ls-files -s | sed "s-\t\"*-&B/-" | GIT_INDEX_FILE=$GIT_INDEX_FILE.new git update-index --index-info && mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE' HEADgit checkout maingit merge tempB --allow-unrelated-histories
4. 收尾清理
合并完成后进入A、B目录执行git log,确认提交历史、文件内容均符合预期后,清理临时资源:
- 删除临时分支
git branch -D tempA tempB - 删除临时远程关联
git remote remove subA subB
操作完成后,A、B会成为主仓库下的普通目录,所有原有提交历史(或你指定的近2个月历史)都会完整保留,git blame、git log等命令都可以正常追溯修改记录,不会生成单条压缩提交。
内容的提问来源于stack exchange,提问作者biosbob

