将 SVN 仓库迁移到 Git

将 SVN 仓库迁移到 Git

更新时间:

本文介绍如何将 SVN 仓库转换为 Git 作为版本控制系统。

背景说明

SVN(Subversion)是一个过时的版本控制工具。曾经风靡一时的 SVN 因为如下缺点让其在与 Git 的较量中败下阵来:

  • 在开源社区、企业内部代码开源等场景,期望人人皆可参与代码贡献,但是 SVN 的集中式版本控制模式导致 SVN 只能由小部分具有写权限的用户参与代码协同,无法满足开源和内部开源的诉求。

  • SVN 的提交操作直接发生在服务器端,除了依赖网络、速度慢之外,因为缺乏对于提交的代码评审流程,导致代码质量难以把控。

因此,Git 成为了当前的主流版本控制系统,在进行 SVN 转换 Git 之前,你需要确认迁移范围,以选择对应的迁移方案。

方案一:仅需要迁移SVN的最新数据

如果仅需要迁移SVN的最新数据,不需要迁移SVN的提交历史记录,那么迁移操作非常简单。

只需要 svn git 两个工具即可完成迁移。操作如下:

  • 使用 svn checkout 命令从 SVN 仓库检出最新文件到本地工作区。

  • 在本地工作区执行 git init 初始化,完成本地Git仓库的创建。

  • 编辑 .git/info/exclude 文件,在其中添加 .svn 等条目以便忽略工作区中的 SVN 管理目录。

  • 执行 git add -A git commit 命令,创建 Git 提交。

  • 在远程 Git 服务器上创建代码仓库,假设仓库地址的示例地址为 <URL>

  • 执行 git remote add origin <URL> 命令,将本地工作区和远程仓库地址相关联。

  • 执行 git push -u origin HEAD 将本地Git仓库的分支推送到远程仓库中。

该迁移方式和普通 Git 仓库推送服务端一致,不再赘述。

方案二:需要迁移 SVN 的全部历史

如果需要迁移 SVN 的历史提交,或者需要迁移多个分支和标签,则需要安装和使用 git-svn 实现 SVN 仓库到 Git 仓库的迁移。

image.png

作为 Git 的一条子命令, git-svn 可以桥接远端的 SVN 仓库和本地的 Git 仓库,允许用户使用 Git 操作远端的 SVN 仓库。 git-svn 支持如下操作:

  • 使用 git svn clone 命令将远程的 SVN 代码仓库克隆到本地 Git 仓库。

  • 使用 Git 命令在本地工作区中创建提交。

  • 使用 git svn dcommit 命令将本地的 Git 提交推送到 SVN 仓库上,创建 SVN 提交。

  • 使用 git svn fetch 命令持续从 SVN 仓库同步提交到本地仓库。

利用 git-svn 的能力,可以实现将 SVN 仓库全部历史保留并转换为 Git 仓库。

针对方案二,接下来进行详细操作说明。

针对方案二的迁移准备

安装 git-svn

Git 的内置子命令 git-svn 和其他的子命令不同,不是编译后的原生可执行程序,而是一个 Perl 脚本,在运行时依赖 svn 的 perl 模块。

以 Ubuntu 操作系统为例,首先安装 Git 和 svn。如下:

 $ apt-get install git subversion

再单独安装 git-svn 和 svn 的 perl 模块,如下:

 $ apt-get install git-svn libsvn-perl

执行 git svn ,如果输出正常的帮助信息,则安装成功。

反之,可能会输出如下的错误信息:

$ git svn
Can't locate SVN/Core.pm in @INC (you may need to install the SVN::Core module) (@INC contains: /opt/git/dev/share/perl5 /Library/Perl/5.30/darwin-thread-multi-2level ... ) at /opt/git/dev/share/perl5/Git/SVN/Utils.pm line 6.

这可能是因为用户手动编译安装的 svn、git,在运行 git-svn 时无法定位 svn 的 perl 模组。解决方案是:将 git 和 svn 安装目录中的 perl 模块目录添加到环境变量 GITPERLLIB , git-svn 即可正确运行。如下所示:

$ GITPERLLIB=/opt/git/dev/share/perl5:/usr/local/Cellar/subversion/1.14.2_1/lib/perl5/site_perl/5.30.3/darwin-thread-multi-2level
$ export GITPERLLIB
$ git svn

探测 SVN 仓库布局

SVN 使用目录拷贝操作来创建分支,随意性非常强,甚至可以对不同版本的文件、不完整的文件树拷贝为分支。这种将分支和仓库文件树混杂一起的管理方式,导致工具难以自动化识别 SVN 仓库的分支。

因此需要用户从 SVN 仓库的目录结构布局,猜测 SVN 仓库的分支、标签的设置:主干分支如何映射到目录,其他分支、标签如何映射到目录?

执行 svn info 命令查看给定地址的 SVN 仓库的根路径。如:

$ svn info https://example.com/svn/repo/trunk
路径: trunk
URL: https://example.com/svn/repo/trunk
Relative URL: ^/trunk
版本库根: https://example.com/svn/repo/
版本库 UUID: ff59cfcf-da3e-4e76-82c0-6e975afb2284
版本: 4
节点种类: 目录
最后修改的作者: gotgit
最后修改的版本: 2
最后修改的时间: 2023-07-12 09:44:00 +0800 (三, 2023-07-12)

执行 svn ls 命令查看仓库的根路径,如:

$ svn ls https://example.com/svn/repo/
branches/
tags/
trunk/

可以看到 SVN 仓库采用了官方推荐的单项目标准布局:主干为 trunk/ 目录,分支创建在 branches/ 目录下,标签创建在 tags/ 目录下。

在 SVN 转换 Git 仓库时,要通过参数将仓库布局提供给 git-svn ,以便将分支和标签正确导出。

其他的布局方式还有:

  • 无分支、无标签的主干模式:SVN 仓库的根目录即为项目主干。

  • 多项目模式:SVN 仓库的一级目录作为项目名,每个项目有自己特定的仓库布局。

建立 SVN 和 Git 之间的作者名称映射

SVN 采用服务端注册的用户登录ID作为仓库中提交的作者名称 ,而 Git 的提交是在客户端生成的,提交的作者名称是用户自己设置的,由两部分构成,即用户全名和邮箱。

git-svn 进行仓库格式转换过程中,推荐提供一个 SVN 用户到 Git 用户的映射文件,这个文件的每一行为一条记录,指定 SVN 和 Git 的用户名和邮箱的映射关系,格式为 SVN用户名 = Git用户名 <邮箱> 。示例如下:

loginname = Joe User <user@example.com>

执行 svn log 命令可以获取 SVN 仓库全部提交的用户名列表:

$ svn log -q https://example.com/svn/repo/

从中提取 SVN 用户名,请手动编辑生成如上所述的 SVN 到 Git 用户的映射文件备用。

迁移 SVN 仓库

初始化本地 Git 仓库

首先创建一个本地工作区目录,进入到目录中,如下:

$ mkdir workdir
$ cd workdir

然后执行 git svn init 命令将本地工作区初始化为一个 Git 仓库。

对于无分支的 SVN 仓库,初始化命令示例如下:

$ git svn init https://example.com/svn/repo

对于标准布局的 SVN 仓库,初始化命令示例如下:

$ git svn init -s https://example.com/svn/repo

对于非标准布局的 SVN 仓库(例如目录名大小写与标准布局有差异),初始化命令示例如下:

$ git svn init -t Trunk -b Branches -t Tags https://example.com/svn/repo

自远程 SVN 仓库同步

执行 git svn fetch 命令和远程 SVN 仓库同步,将远程仓库获取到本地并转换为 Git 提交。如果有 SVN/Git 作者映射文件,如文件 authors.map ,需在命令行中提供。

同步全部历史提交,命令示例如下:

$ git svn fetch -A /path/of/authors.map

同步某个范围的提交,命令示例如下:

$ git svn fetch -A /path/of/authors.map -r 0:100 

推送到远程仓库

在云效的服务端创建 Git 仓库,用于保存转换后的结果,创建仓库操作参见 步骤一:新建第一个代码库

接着在服务端页面获取 Git 库地址:

1-2.png

说明

请不要在新库上创建任何分支、标签以及文件,确保其为空仓库,否则可能因为强制推送问题导致迁移失败。

例如 Git 仓库地址为: https://codeup.aliyun.com/$group/repo.git 。使用 git remote 命令将远程 Git 仓库地址添加为新的源。为避免和 git-svn 设置的源的名称重复,可以设置为 target。示例如下:

$ git remote add target https://codeup.aliyun.com/$group/repo.git

迁移完成后,远程 SVN 仓库的分支和标签转换为本地 Git 仓库中的跟踪分支和标签。向目标Git仓库推送时,对于不同的 SVN 仓库布局,推送方式各不相同。

对于无分支和标签的 SVN 仓库,仓库的主干分支映射为 refs/remotes/git-svn ,将其推送到目标 Git 仓库的 master 分支(或 main 分支)。示例如下:

$ git push target refs/remotes/git-svn:refs/heads/master

对于标准布局的 SVN 仓库,其分支和标签混杂在一起,用如下命令重命名标签,以便本地跟踪分支和标签有不同前缀,便于区分:

$ git for-each-ref --format="%(objectname) %(refname:lstrip=4)" \
      "refs/remotes/origin/tags/*" |
  while read oid tag; do
      git update-ref "refs/tags/$tag" $oid &&
      git update-ref -d "refs/remotes/origin/tags/$tag"
  done

然后执行如下命令将本地仓库的分支和标签推送到目标Git仓库。

$ git push target \
      --tags \
      refs/remotes/origin/trunk:refs/heads/master \
      "refs/remotes/origin/*:refs/heads/*"

使用 Git 仓库

更多使用说明参见 新手指引

附录

Git 迁移相关材料参见: https://git-scm.com/book/en/v2/Git-and-Other-Systems-Migrating-to-Git