我们希望永久地从 svn 迁移到 git,以便能够使用 git 在分支和协作方面的更好功能。
我们当前的 svn 存储库如下所示
svnrepo/
frontend/
trunk
branches/
ng/
...
tags/
1.x
...
backend/
trunk
branches/
ng/
...
tags/
1.x
...
工作布局是,我们检出前端项目,并在其中创建一个后端文件夹并检出后端项目。
我们现在想迁移到 git,并放弃前端和后端之间的分离(就作为独立项目而言),因为这给我们带来的问题多于好处。我们希望它们都位于一个 git 存储库中。
我想用svn2git进行转换。不幸的是,最新的开发都发生在分支中,而不是主干中,但我认为这对 svn2git 来说应该不是问题。因此,新的 git 存储库布局应如下所示:
/ => svnrepo/frontend/branches/ng
/backend => svnrepo/backend/branches/ng
其中 => 表示“从...迁移/转换”。
对于转换,我们不需要将 svn 存储库中的所有标签和分支都转换到 git。这对我们来说并不重要。但重要的是,我们拥有 branch/ng 目录中所有文件的所有提交的完整历史记录,可以追溯到 trunk 的分支以及在此之前在 trunk 中发生的所有提交。我们希望所有这些提交都采用上述布局,位于单个 git 存储库中。这可能吗?我们该怎么做?
答案1
一个解决方案是使用 svn2git 或仅仅git svn
(它是 git 中已经内置的一个很好的小工具)分别生成每个存储库,然后将它们连接在一起git filter-branch
。
- 单独克隆每个 svn 存储库。
- 在您想要成为 root 的存储库中,将其他存储库添加为远程存储库,并获取您想要合并到该存储库的分支(您会收到警告,因为分支没有共同的历史记录;这是预料之中的)。
- 在这些新分支上执行
git filter-branch
,使用索引过滤器为它们生成一个新的子目录。 - 将筛选后的分支合并到
master
根存储库(或您想要的任何分支)。完整的历史记录将被保留。
步骤 3 的命令如下所示:
git filter-branch --index-filter '
git ls-files -s |
perl -pe "s{\t\"?}{$&newsubdir/}" |
GIT_INDEX_FILE=$GIT_INDEX_FILE.new git update-index --index-info &&
mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE
' HEAD
魔法,每次我必须这样做的时候,感觉有点像魔法,就是语句perl
。git filter-branch
正在过滤索引每次提交时并在所有 blob 路径(即更改工作树的文件路径)前面添加“newsubdir”。您可能需要反复试验才能获得完全正确的路径。从某人那里学到的几个教训以前走过这条路:
- 备份所有内容。
git filter-branch
历史记录具有破坏性。一旦更改,就无法轻易改回。请务必备份您正在使用的所有存储库副本。没有什么比完成一项复杂的操作并发现您/
在路径中遗漏了一个更糟糕的了。 - 编写一切脚本。除非你拥有一些真正的技能,否则你不可能第一次就成功。在完成每个步骤时编写脚本,这样重新运行任何步骤都很容易。此外,如果你在一周后发现你搞砸了一个标志,你可以在几分钟内复制。
- 在 EC2 中花费 20 美元购买集群计算实例。
git filter-branch
非常耗费 CPU。深度历史记录上的索引筛选在本地环境中可能需要数小时才能运行,但在 AWS 上只需花费一小部分时间集群计算实例。当然,它们要花钱每小时2美元多一点,但你只需要几个小时。省点力气,使用你在硬件上编写的那些脚本,让操作变得简单。它的价格相当于一顿美味的午餐。
答案2
解决方案之一是将两个 SVN 项目存储库转换为 2 个 Git 存储库,然后添加一个 Git 存储库作为Git 子模块另一个。
要将 SVN 存储库转换为 Git 存储库,您可以使用任何基于 git-svn 的脚本或子Git. 使用最新工具,您只需运行一个命令
$ subgit install path/to/svn/repository
转换后的 git 存储库将位于 path/to/svn/repository/git。
然后设置对两个 Git 存储库的访问权限,并将其中一个存储库添加为另一个存储库的子模块:
$ git clone <frontend_GitURL> frontend
$ git co
$ cd frontend
$ git submodule add -b ng <backend_GitURL> backend
答案3
我所能想到的是,这将需要一些极端的黑客技术,除非svn2git
(我不是一位专家以某种方式原生地支持这一点。
问题在于,的提交frontend
完全独立于的提交backend
。没有真正的方法可以告诉哪个提交将映射到单个存储库中的哪个提交。这给我们留下了一个真正的选择:历史记录将由两个合并在一起的分支组成,这代表原始项目的历史记录,然后一旦它们合并,新分支就是“更好的模型”。
从现在开始,我将假设您已经frontend
在svn-frontend
导入的分支中并且在导入的分支backend
中svn-backend
,并且都包含自己的历史记录。
第一个问题需要修复svn-backend
在backend/
目录中:
git checkout svn-backend
git filter-branch --index-filter '
git ls-files -s |
perl -pe "s{\t\"?}{$&newsubdir/}" |
GIT_INDEX_FILE=$GIT_INDEX_FILE.new git update-index --index-info &&
mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE' HEAD
(看本文档,以及@Christopher 的回答)
现在,除非这些以某种方式包含与基础相同的提交(除非svn2git
创建一些预定义的基础提交或其他东西,否则不太可能...),否则我们必须创建一个。从哪个分支开始并不重要。
git symbolic-ref HEAD refs/heads/svn-base
rm .git/index
git clean -dxf
Git 无法跟踪空目录。我从未测试过这是否适用于根目录,但我的假设是不适用的,因此创建一个空的 git ignore 文件并提交:
touch .gitignore
git add .gitignore
git commit -m "Base for SVN branches"
让我们重写历史:
git rebase svn-base svn-frontend
git rebase svn-base svn-backend
我们快完成了。现在让我们创建主分支。如果它已经存在:
git update-ref master "$head"
否则:
git branch master
让我们来看看:
git checkout master
最后,合并:
git merge svn-backend
标记旧分支然后删除它们是一个好主意:
git checkout svn-frontend
git tag svn-frontend
git branch -d svn-frontend
git checkout svn-backend
git tag svn-backend
git branch -d svn-backend
git checkout master
git branch -d svn-base