Git 仓库迁移

本文最后更新于:2026年3月28日 晚上

Git 仓库迁移

最近碰到一个需求,要把 gitee 上的代码仓库迁移到内网 GitLab,而且不是只迁默认分支,而是要把历史记录、所有分支、tag 一起带过去。

这种场景最省事的做法,其实就是直接用 mirror 做完整迁移。

一、仓库 mirror 迁移

1
2
3
4
5
6
7
8
9
10
11
# 1. 镜像克隆原仓库
git clone --mirror <old-repo-url>

# 2. 进入镜像仓库目录
cd <repo-name>.git

# 3. 把 origin 改成新仓库地址
git remote set-url origin <new-repo-url>

# 4. 把整个镜像推到新仓库
git push --mirror

--mirror 的关键点在于:它不只是推代码本身,而是把 refs 一起带过去,所以提交历史、分支、tag 通常都能完整保留下来。

如果你的目标只是“把旧仓库原样搬到新平台”,那上面这几步基本就够了。

二、注意事项

1. 最好在一个全新的目录里操作

第一步是把原始仓库镜像拉到本地。为了保险起见,最好找一个新的空文件夹来做,不要直接在你平时开发用的本地仓库上折腾,免得把已有的 remote 配置改乱了。

2. 目标仓库最好是一个新的空仓库

这一点很重要。

git push --mirror 不是“补充推送”,而是按镜像去覆盖目标仓库里的 refs。所以如果目标仓库已经存在分支,它有可能把已有分支删掉,再按你当前镜像里的内容重建。

所以更稳妥的做法是:迁移时直接推到一个新的空仓库。

三、拓展:迁移过程中顺手学到的新东西

前面两部分已经够完成迁移了,下面这些是我在实际操作里顺手验证出来的补充知识点。

1. 本地仓库也可以作为迁移源

我一开始还在想:既然可以 clone sshhttp 地址,那本地仓库行不行?后来试了一下,完全可以。

比如我当时就是这样拉本地仓库的:

1
git clone --mirror D:/workspace/java/backend2/.git

如果源仓库本来就在你电脑上,这种方式反而更快。

2. git remote set-url--push

这里顺手也学到一个点:

1
git remote set-url --push origin <new-repo-url>

这里的 --push 代表只改推送地址。

git remote set-url ,默认是拉取(fetch)地址和推送(push)地址一样的;如果加了 --push,那就只改推送地址,拉取地址保持不变。

如果不单独区分拉取和推送地址,那直接写成下面这样就够了:

1
git remote set-url origin <new-repo-url>

设置完之后,可以用这个命令确认:

1
git remote -v

如果拉取(fetch)和推送(push)地址不一样,输出的就像这样:

1
2
origin  D:/workspace/java/backend2/.git (fetch)
origin ssh://[email protected]:20222/backenddevelopergroup/xiao.git (push)

3. clone--bare--mirror 到底有什么区别

先说定义:
“工作区” 是 Git 仓库里一个重要的概念,指的是那些被检出出来、可以直接编辑的源码文件所在的目录。
“裸仓库”(bare repository)则是没有工作区的 Git 仓库,通常用来作为远程仓库或者备份仓库。

比如正常 clone 一个项目后,你会看到 srcREADME.mdpackage.json 这些文件,这一层就是工作区。

然后再看这三个命令:

1
2
3
git clone <url>
git clone --bare <url>
git clone --mirror <url>

可以直接理解成这样:

  • git clone:最普通的克隆方式,有工作区,适合日常开发。
  • git clone --bare:没有工作区,目录里不会直接出现你平时写代码的那些文件,保留的是 Git 仓库本身,常用在服务端仓库或备份场景。
  • git clone --mirror:可以理解成“更完整的 bare clone”。我也专门查了本机 git clone -h,里面明确写着 --mirror ... (implies --bare),所以它同样没有工作区。但它会把 refs 和远程配置也尽量按镜像方式带下来,所以更适合做迁移。

这里还有一个很容易误会的点:git clone --mirror 不是“没有仓库内容”,而是“没有被直接展开出来的源码目录”。

也就是说:

  • 你不会像普通 clone 那样,一进目录就看到 srcREADME.mdpackage.json 这些已检出的文件。
  • 但仓库内容、提交历史、分支、tag 这些东西其实都还在,只是它们保存在 Git 仓库数据里,没有以工作区文件的形式直接铺出来。

如果只是记一个结论,那就是:

  • 日常开发:git clone
  • 裸仓库:git clone --bare
  • 完整迁移:git clone --mirror

4. deny updating hidden ref 这个坑

我当时还遇到过一个报错:

1
deny updating hidden ref

这个问题出现在我拿“本地仓库”当迁移源的时候。

原因大概是这样:

  • 本地仓库里除了正常分支,还可能带着一些 remote-tracking refs。
  • git push --mirror 会尝试把这些 refs 也一起推过去。
  • GitLab 对这类 hidden refs 不一定允许直接更新,于是就报错了。

我当时的处理方式比较直接:先在源仓库里把这些 remote 分支显式转成正常的本地分支,再重新做镜像迁移。

比如原来只有 origin/b,那就先补一个本地分支:

1
git checkout -b b origin/b

这样做完之后,再重新走一遍镜像迁移,就不会再报错了。


Git 仓库迁移
http://bestkele.com/2022/09/07/investigation/git-move/
作者
kele
发布于
2022年9月7日
许可协议