使用Git Subtree合并仓库并保留文件历史的问题
问题场景
需要将多个Git仓库逐步合并到一个主仓库,要求保留所有提交历史,且在旧仓库完全弃用前仍能拉取其变更。使用git subtree add命令完成合并后,主仓库的整体提交历史可正常查看,但单个文件的历史仅显示合并提交,看不到原仓库中该文件的完整修改记录。
期望的合并后主仓库结构:
. └─ src └── stores ├── storeA │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── models │ │ │ └── index.ts │ │ └── stores │ │ ├── FileA.ts │ │ ├── FileB.ts │ └── yarn.lock └── storeB ├── README.md ├── package.json ├── src │ ├── index.ts │ ├── models │ │ └── index.ts │ └── stores │ ├── FileA.ts │ ├── FileB.ts └── yarn.lock
执行操作与现象
执行的合并命令:
git subtree add --message="old repo - storeA" --prefix src/stores/storeA git@ssh.XXX/storeA main
合并后整体提交历史正常显示:
// git log --graph --oneline * 8e276e5 (HEAD -> main) old repo - storeA | | * 2b59054 fix 2 | * 70ede5a feat 2 | * 57041d5 fix 1 | * de39054 feat 1 | * 85d213b Added README.md * 49942fb init
但查看单个文件历史时,仅能看到合并提交:
// git log --graph --oneline src/stores/storeA/src/stores/FileA.ts * 8e276e5 (HEAD -> main) old repo - storeA
通过git blame能看到该文件的不同提交记录,git show 57041d5显示原仓库中该文件的路径为src/stores/FileA.ts,说明问题源于文件路径在合并后发生了变更,Git默认未追踪这种路径变化。
此前尝试使用git-filter-repo --path-rename src/stores/:src/stores/storeA/src/stores/FileA.ts重写路径,虽能解决单个仓库的问题,但会重写主仓库历史,且后续合并其他仓库时会破坏已调整的路径。
解决方案
方案1:查看历史时使用--follow参数
无需修改仓库,仅在查看单个文件历史时添加--follow参数,该参数会自动追踪文件的路径变更(包括重命名、移动):
git log --graph --oneline --follow src/stores/storeA/src/stores/FileA.ts
此方法简单直接,不影响仓库结构和后续的git subtree pull操作,适合仅需要查看历史的场景。
方案2:合并前先调整原仓库的文件路径
在合并前,先为原仓库添加一个路径调整的分支,将所有文件移动到主仓库对应的目标路径下,再合并该分支:
- 克隆原仓库storeA:
git clone git@ssh.XXX/storeA.git cd storeA - 创建并切换到新分支:
git checkout -b adjust-path - 移动所有文件到目标路径(对应主仓库的
src/stores/storeA):mkdir -p src/stores/storeA git mv ./* src/stores/storeA/ # 注意:如果有.gitignore等隐藏文件,需要单独处理 git mv .gitignore src/stores/storeA/ - 提交路径变更:
git commit -m "adjust path for subtree merge" - 推送该分支到原仓库:
git push origin adjust-path - 在主仓库中合并该分支:
git subtree add --message="old repo - storeA (adjusted path)" --prefix src/stores/storeA git@ssh.XXX/storeA adjust-path
后续需要拉取原仓库的变更时:
- 先在原仓库的main分支合并adjust-path分支的路径变更(或直接在adjust-path分支同步main的变更);
- 再在主仓库执行
git subtree pull:git subtree pull --prefix src/stores/storeA git@ssh.XXX/storeA adjust-path
此方法确保主仓库中文件路径与原仓库调整后的路径一致,无需使用--follow即可查看完整历史,且不影响原仓库的正常使用。
方案3:使用git subtree split同步路径(可选)
如果原仓库后续仍有大量变更,可使用git subtree split将主仓库中storeA的内容拆分回原仓库的分支,保持路径同步,但此操作相对复杂,适合需要双向同步的场景。
内容的提问来源于stack exchange,提问作者0x4950

