Fork-Rebase-Rename
将一个已有 git 项目(含子模块或不含)原地转换为一个新项目:换远程源、重命名模块目录和项目名称、修改代码中的旧路径引用。全程避免不必要的重新克隆,最大化复用本地已有文件。
核心原则
- 不擅自决定:遇到不确定、有歧义的内容(如同名资源文件、项目名和资源名重合、多工作区边界等),必须向用户提问并获得明确答复后才行动。
- 对用户透明:remote name 等 git 内部概念不暴露给用户;自动检测 remote(若唯一则直接使用,若多个则询问用户用哪个)。
- 两次提交,不 push:换源一次 commit,重命名一次 commit,均不自动 push。
- commit message 遵循项目惯例:观察项目已有提交风格(中文/英文、前缀风格),保持一致。若无法判断则询问用户。
工作流程
Phase 0:信息收集
依次向用户收集以下信息(逐步提问,不一次性罗列):
- 主模块是否换源? 新 URL 是什么?
- 检测子模块情况:
- 若
.gitmodules存在且非空 → 列出所有子模块,询问用户"哪些需要换源?各自的新 URL?哪些保持不动?" - 若无子模块 → 跳过子模块相关步骤
- 若
- 新库与旧库的关系(对每个需换源的子模块):
- 若新库是旧库的 fork(历史相同/相近)→ 仅换源即可,无需重新克隆
- 若新库与旧库内容差异大 → 对该子模块执行 deinit + 重新克隆
Phase 1:换源(commit #1)
1.1 主模块换源
- 自动检测当前 remote name(
git remote输出唯一则用它,多个则问用户) git remote set-url <remote> <new-url>- 验证:
git remote -v
1.2 子模块处理(若有)
根据 Phase 0 中对每个子模块确认的关系,走不同路径:
路径 A:fork 关系(仅换源,不 re-clone)
- 检查子模块是否已初始化:
- 若
.git/modules/<name>存在且子模块目录内有.git文件 → 已初始化,进入子模块执行git remote set-url - 若未初始化 → 仅更新
.gitmodules中的 url,后续 sync 即可
- 若
- 更新
.gitmodules中对应子模块的 url(git config -f .gitmodules submodule.<name>.url <new-url>) git submodule sync <path>(同步到.git/config)- 若存在嵌套子模块,使用
git submodule sync --recursive
路径 B:内容差异大(deinit + 重新克隆)
git submodule deinit -f <path>
git rm -f <path>
rm -rf .git/modules/<path>
git submodule add <new-url> <path>
git submodule update --init <path>
1.3 提交
git add -A
git commit -m "<根据项目惯例生成的换源提交信息>"
Phase 2:信息收集(重命名)
向用户收集:
- 新项目名称(影响构建目标名、lib 名等)
- 子模块目录新名称(每个需改名的子模块)
- 构建中间文件/缓存目录:是否需要清理?(由用户决定)
Phase 3:自主探索旧名引用
在执行重命名前,自动搜索代码库中所有引用旧名称的位置。
搜索范围:仅当前项目目录。若工作区有多个项目目录,询问用户搜索范围。
排除目录(搜索时自动跳过):
.git/- 构建输出目录(
build/、out/、dist/、target/等) - 依赖缓存(
node_modules/、.build/、Pods/、vendor/等) - 用户指定的其他排除目录
搜索目标:
- 构建配置文件(CMakeLists.txt、Makefile、package.json、Cargo.toml、build.gradle 等)
- 资源打包/部署配置(JSON、YAML、CSV 等)
- 源代码中的硬编码路径字符串
- 文档中的引用
将结果汇总为列表,向用户展示并确认:
- 哪些是项目名引用 → 需要改
- 哪些是资源/音频/资产文件名(恰好与项目同名)→ 确认是否改
- 哪些在第三方库/不可控目录中 → 通常不改
对每一个有歧义的项,单独询问用户。
Phase 4:执行重命名(commit #2)
4.1 子模块目录重命名
根据 Phase 1 中各子模块走的路径:
对路径 A 子模块(已换源、本地文件保留)→ 本地 mv
# 1. 移动工作目录
mv <old-path> <new-path>
# 2. 移动 .git/modules 对应目录
mv .git/modules/<old-path> .git/modules/<new-path>
# 3. 修复子模块内 .git 文件的 gitdir 指向
echo "gitdir: <relative-path-to-new-modules-dir>" > <new-path>/.git
# 4. 修复 .git/modules/<new>/config 中的 worktree 指向
# worktree = <relative-path-to-new-workdir>
# 5. 从 git index 移除旧路径
git update-index --force-remove <old-path>
# 6. 重新注册新路径到 index(利用已有本地仓库,不触发 clone)
git submodule add -f <url> <new-path>
# 7. 确保 .gitmodules 中 submodule name 和 path 正确
git config -f .gitmodules --rename-section "submodule.<old-name>" "submodule.<new-name>"
git config -f .gitmodules submodule.<new-name>.path <new-path>
# 8. 更新 .git/config 中 section 名
git config --local --rename-section "submodule.<old-name>" "submodule.<new-name>"
对路径 B 子模块(已在 Phase 1 重新克隆)→ 仅需改目录名
路径 B 子模块在 Phase 1 中已通过 git submodule add <url> <path> 注册。若目标目录名与 Phase 1 时指定的不同,执行同样的本地 mv 流程。若 Phase 1 时已用新目录名注册,则无需额外操作。
4.2 更新项目名和 lib 名
根据 Phase 3 确认的列表,逐一替换:
- 构建系统中的项目名、目标名、库名
- 源代码中的路径字符串
- 资源配置中的目录引用
4.3 验证
git submodule status # 所有子模块正常显示,无 fatal 错误
# 在各子模块内执行 git status / git log --oneline -1 确认正常
4.4 提交
若子模块内部有文件改动(如 lib 名变更),先在子模块内提交:
cd <submodule-path>
git add -A
git commit -m "<子模块内部的改动描述>"
cd <project-root>
然后在主模块提交:
git add -A
git commit -m "<根据项目惯例生成的重命名提交信息>"
Phase 5:收尾提醒
告知用户:
- 两个 commit 已就绪,未 push
- 若子模块内部有提交,push 时需要同时推送子模块和主模块
- 构建缓存/中间文件需要用户自行清理后重新构建
- 可选:工作区根目录重命名(纯本地操作,不影响 git)
注意事项
- 不修改不属于当前项目的工作区内容(如工作区有多个项目目录时)
- 音频、图片等资源文件名默认不改(除非用户明确要求)
- remote name 对用户透明,不要求用户知道或操作 remote name
- git 操作安全:不 force push、不 reset --hard、不删除 .git 历史
- 嵌套子模块:若检测到子模块内还有子模块,提醒用户并确认是否递归处理
- 所有不确定的决策点 → 停下来问用户