在迁移Git仓库时,确保提交历史、分支和标签的完整保留是首要目标。然而,错误的命令或操作顺序可能导致分支丢失、标签断裂,甚至LFS文件仅剩空指针。下图清晰地展示了不同场景下的核心操作路径与决策流程。

要实现无损的Git仓库迁移,关键在于深入理解每条命令的适用边界及其潜在的“陷阱”。
git clone --mirror:完整复制所有引用的唯一方法
标准的 git clone 命令仅会拉取默认分支(通常是 main 或 master),而远程分支、refs/pull/* 引用以及某些Git平台特有的合并请求引用则不会被包含。相比之下,git clone --mirror 是专为创建完整镜像设计的:它会生成一个裸仓库,将源仓库的所有 refs(包括 heads、tags、remotes/origin/*)原封不动地复制过来。
- 执行后生成的目录名会带有
.git后缀(例如my-project.git)。请注意,这是一个裸仓库,没有工作区,无法直接在其中修改代码文件。 - 它不支持
git status或git checkout等日常操作,其主要用途是作为迁移的中转站进行后续推送。 - 这里有一个关键注意事项:如果源仓库使用了 Git LFS,
--mirror参数不会自动获取LFS对象文件。迁移完成后,你需要在目标仓库手动执行git lfs fetch --all来补全这些大文件,避免出现空指针。
git push --mirror:要求目标仓库必须“绝对干净”
git push --mirror 的强大之处在于,它会将本地所有的引用强制覆盖到远程对应的位置,而不仅仅是推送几个分支。这意味着,目标仓库中任何不存在于源仓库的分支或标签,都会被此命令删除。因此,执行此操作的前提是目标仓库必须是一个全新创建的空白项目。
- 操作中常见的报错
! [remote rejected] master -> master (pre-receive hook declined),通常是因为目标GitLab等平台的master分支设置了保护规则,且未开启“允许开发者推送”的权限。 - 解决方法并非更换推送分支,而是需要临时关闭该分支的保护设置,或者由项目管理员使用更高权限(如维护者权限)来执行推送。
- 同样,如果目标仓库在创建时自动生成了README等初始提交,
--mirror推送也会失败。必须先清空仓库或直接重建一个完全空的项目。
git remote set-url + 分步推送:更灵活,但需警惕遗漏
对于仅需同步常规分支和标签,不关心PR引用或特定CI配置的轻量级迁移,可以采用更灵活的组合操作:先进行普通克隆,再修改远程地址,最后分步推送。
- 具体步骤为:
git clone源仓库,进入目录后执行git remote set-url origin <新仓库地址>,然后分两步推送:git push --all origin和git push --tags origin。 - 这种方法比
--mirror更安全,因为它不会误删目标仓库已有的其他分支。但其缺点是,--all参数仅推送本地已跟踪的远程分支(即执行过git fetch拉取到本地的)。如果源仓库有新增的分支但你本地从未获取过,它们就会被遗漏。 - 如何验证是否推送完整?一个实用的方法是对比两个仓库的引用列表:分别对源地址和新地址执行
git ls-remote --heads,检查输出的分支数量是否完全一致。
GitLab原生导出/导入:不等于底层的Git历史迁移
GitLab平台自带的项目导出功能(生成 .tar.gz 包)非常强大,它能包含议题、合并请求、Wiki等丰富的项目元数据。但需要注意的是,其内部的Git数据是重新打包的快照,并非原始的裸仓库引用流。导入后,虽然提交的哈希值保持一致,但一些底层引用(如 refs/notes/*)或自定义的钩子配置可能会丢失。
- 此方法最适合需要完整保留Issue和MR关联关系的团队协作场景。反之,如果你的工作流严重依赖Git底层引用(例如CI/CD中直接解析
refs/pull/123/head这样的路径),则不建议使用。 - 导入完成后,务必运行
git ls-remote --heads origin和git ls-remote --tags origin命令,仔细核对分支和标签的数量是否与源仓库完全一致。 - 另一个重要提示:LFS文件不会随导出包自动迁移。导入后,需要单独执行
git lfs migrate import等命令来处理大文件,并重新推送至目标仓库。
最后,无论选择哪种迁移方法,都有一个最容易被忽略的前提:源仓库在迁移窗口期内必须冻结写入。哪怕期间只多了一次 git push,新的提交也不会出现在你已克隆的镜像里。历史迁移的本质是获取一个静态快照,而非建立实时同步的管道。
