Git 仓库迁移
本文最后更新于:2026年3月28日 晚上
Git 仓库迁移
最近碰到一个需求,要把 gitee 上的代码仓库迁移到内网 GitLab,而且不是只迁默认分支,而是要把历史记录、所有分支、tag 一起带过去。
这种场景最省事的做法,其实就是直接用 mirror 做完整迁移。
一、仓库 mirror 迁移
1 | |
--mirror 的关键点在于:它不只是推代码本身,而是把 refs 一起带过去,所以提交历史、分支、tag 通常都能完整保留下来。
如果你的目标只是“把旧仓库原样搬到新平台”,那上面这几步基本就够了。
二、注意事项
1. 最好在一个全新的目录里操作
第一步是把原始仓库镜像拉到本地。为了保险起见,最好找一个新的空文件夹来做,不要直接在你平时开发用的本地仓库上折腾,免得把已有的 remote 配置改乱了。
2. 目标仓库最好是一个新的空仓库
这一点很重要。
git push --mirror 不是“补充推送”,而是按镜像去覆盖目标仓库里的 refs。所以如果目标仓库已经存在分支,它有可能把已有分支删掉,再按你当前镜像里的内容重建。
所以更稳妥的做法是:迁移时直接推到一个新的空仓库。
三、拓展:迁移过程中顺手学到的新东西
前面两部分已经够完成迁移了,下面这些是我在实际操作里顺手验证出来的补充知识点。
1. 本地仓库也可以作为迁移源
我一开始还在想:既然可以 clone ssh 和 http 地址,那本地仓库行不行?后来试了一下,完全可以。
比如我当时就是这样拉本地仓库的:
1 | |
如果源仓库本来就在你电脑上,这种方式反而更快。
2. git remote set-url 和 --push
这里顺手也学到一个点:
1 | |
这里的 --push 代表只改推送地址。
git remote set-url ,默认是拉取(fetch)地址和推送(push)地址一样的;如果加了 --push,那就只改推送地址,拉取地址保持不变。
如果不单独区分拉取和推送地址,那直接写成下面这样就够了:
1 | |
设置完之后,可以用这个命令确认:
1 | |
如果拉取(fetch)和推送(push)地址不一样,输出的就像这样:
1 | |
3. clone、--bare、--mirror 到底有什么区别
先说定义:
“工作区” 是 Git 仓库里一个重要的概念,指的是那些被检出出来、可以直接编辑的源码文件所在的目录。
“裸仓库”(bare repository)则是没有工作区的 Git 仓库,通常用来作为远程仓库或者备份仓库。
比如正常 clone 一个项目后,你会看到 src、README.md、package.json 这些文件,这一层就是工作区。
然后再看这三个命令:
1 | |
可以直接理解成这样:
git clone:最普通的克隆方式,有工作区,适合日常开发。git clone --bare:没有工作区,目录里不会直接出现你平时写代码的那些文件,保留的是 Git 仓库本身,常用在服务端仓库或备份场景。git clone --mirror:可以理解成“更完整的 bare clone”。我也专门查了本机git clone -h,里面明确写着--mirror ... (implies --bare),所以它同样没有工作区。但它会把 refs 和远程配置也尽量按镜像方式带下来,所以更适合做迁移。
这里还有一个很容易误会的点:git clone --mirror 不是“没有仓库内容”,而是“没有被直接展开出来的源码目录”。
也就是说:
- 你不会像普通
clone那样,一进目录就看到src、README.md、package.json这些已检出的文件。 - 但仓库内容、提交历史、分支、tag 这些东西其实都还在,只是它们保存在 Git 仓库数据里,没有以工作区文件的形式直接铺出来。
如果只是记一个结论,那就是:
- 日常开发:
git clone - 裸仓库:
git clone --bare - 完整迁移:
git clone --mirror
4. deny updating hidden ref 这个坑
我当时还遇到过一个报错:
1 | |
这个问题出现在我拿“本地仓库”当迁移源的时候。
原因大概是这样:
- 本地仓库里除了正常分支,还可能带着一些 remote-tracking refs。
git push --mirror会尝试把这些 refs 也一起推过去。- GitLab 对这类 hidden refs 不一定允许直接更新,于是就报错了。
我当时的处理方式比较直接:先在源仓库里把这些 remote 分支显式转成正常的本地分支,再重新做镜像迁移。
比如原来只有 origin/b,那就先补一个本地分支:
1 | |
这样做完之后,再重新走一遍镜像迁移,就不会再报错了。