将多个svn版本库迁移到一个git版本库中

我们希望永远从svn迁移git,以便能够在分支和协作方面使用git的更好function。

我们目前的svn仓库看起来像这样

svnrepo/ frontend/ trunk branches/ ng/ ... tags/ 1.x ... backend/ trunk branches/ ng/ ... tags/ 1.x ... 

工作布局是我们签出前端项目,在这里,我们创build一个后端文件夹,并签出后端项目。

我们现在想要迁移到git,并放弃前端和后端之间的分裂(就分开的项目而言),因为这给我们带来了更多的问题而不是优点。 我们希望他们都在一个单一的仓库。

我想使用svn2git进行转换。 不幸的是最新的发展发生在一个分支,而不是在树干,但我认为这不应该是svn2git的问题。 所以新的git仓库布局应该如下所示:

 / => svnrepo/frontend/branches/ng /backend => svnrepo/backend/branches/ng 

其中=>表示“从…迁移/转换”。

对于转换,我们没有必要将所有标签和分支从svn仓库转换到git。 这对我们并不重要。 然而重要的是,我们拥有所有提交到分支/ ng目录中的所有文件的完整历史logging,并返回到主干中的分支以及在主干中发生的所有提交。 我们希望所有这些提交都是在单个git存储库中提到的布局。 这甚至有可能吗? 我们将如何做到这一点?

我已经search谷歌,也在1,2 ,但无法find我们的问题的确切解决scheme。

一个解决scheme是用svn2git或者git svn (这是一个已经embeddedgit的漂亮的小工具)分别生成每个版本库,然后用git filter-branch它们连接在一起。

  1. 分别克隆每个svn仓库。
  2. 在你想成为root的仓库中,将其他仓库添加为远程仓库,并将你想要合并到仓库的分支(你会得到警告,因为分支没有共同的历史;这是预期的)。
  3. 在这些新git filter-branch上执行git filter-branch ,使用索引filter为它们生成一个新的子目录。
  4. 将过滤的分支合并到根存储库的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 在每次提交时过滤索引并用'newsubdir'前置所有blobpath(即改变工作树的文件path)。 您可能需要尝试四处获取path。 从以前走过这条路的人那里学到了一些教训:

  • 一切都恢复起来。 git filter-branch是具有破坏性的历史。 一旦你改变它,你不能轻易地改回它。 请务必备份您使用的所有存储库副本。 没有什么比这更糟糕的是,完成一个复杂的操作,发现你错过了path。
  • 脚本的一切。 除非你有一些认真的技巧, 你第一次得不到这个权利。 在完成每个步骤的脚本时进行脚本编写,以便重新运行任何一个步骤都很简单。 另外如果你一个星期之后发现你搞砸了一面旗帜,你可以在瞬间复制。
  • 在EC2中的集群计算实例上花费20美元。 git filter-branch非常耗费CPU资源。 深层历史logging上的索引filter可能需要几个小时才能在您的本地环境上运行,但在AWS 群集计算实例上只需要几分之一的时间。 当然,他们每小时花费超过2美元,但你只需要几个小时。 节省自己的痛苦,并使用在硬件上编写的脚本,使操作变得微不足道。 这是一个不错的午餐的代价。

其中一个解决scheme是将两个SVN项目库转换为2个Git仓库,然后将一个Git仓库作为另一个仓库的Git子模块 。

要将您的SVN仓库转换为Git仓库,您可以使用任何基于git-svn的脚本或SubGit 。 用最新的工具运行一个命令

 $ subgit install path/to/svn/repository 

转换的git仓库将在path / to / svn / repository / git。

然后你build立一个对Git仓库的访问权限,并添加一个作为另一个子仓库的子模块:

 $ git clone <frontend_GitURL> frontend $ git co $ cd frontend $ git submodule add -b ng <backend_GitURL> backend 

我能想到的是,除非svn2git (我不是专家)本地支持这个,否则这将需要一些极端的svn2git

问题是frontend的提交完全独立于backend的提交。 没有真正的方法来告诉哪个提交将映射到单个存储库中的哪个提交。 这使我们只有一个真正的select:历史将由两个分支合并在一起,代表了原来的项目的历史,然后一旦合并,新的分支是“更好的模式”。

从现在开始,我将假定你已经在svn-frontend分支中导入了svn-frontend ,在svn-backend导入了svn-backend分支,并且都包含了它们自己的历史logging。

第一个问题是修复svn-backendbackend/目录中:

 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创build一些预定义的基本提交或…),我们必须做一个。 你应该从哪个分支开始。

 git symbolic-ref HEAD refs/heads/svn-base rm .git/index git clean -dxf 

Git不能跟踪空目录。 我从来没有testing过,看看这是否适用于根目录,但我的假设不是,所以less创build一个空的git忽略文件并提交:

 touch .gitignore git add .gitignore git commit -m "Base for SVN branches" 

让我们改写历史:

 git rebase svn-base svn-frontend git rebase svn-base svn-backend 

我们差不多完成了。 让我们现在创build主分支。 如果它已经存在:

 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