使用 Workspaces::GitMachine 将非标准 SVN 存储库迁移到 Git






4.64/5 (6投票s)
将 CodeProject 的源代码存储库从 SVN 迁移到 Workspaces::GitMachine。尽管 SVN 存储库的布局非标准,但迁移代码和历史记录是一个简单的过程。
引言
在 CodeProject Command and Control,我们一直使用 SVN 作为源代码存储库。虽然它一直很好地为我们服务,但集中式代码存储库存在其自身的问题。
最值得注意的是,存储库容易出现服务器错误和故障。从备份和本地副本重建存储库对胆小的人来说可不是什么好差事。此外,合并分支可能会导致一些棘手的问题和冲突,解决起来可能需要数小时,如果可能的话。有时这是不可能的,需要手动合并,通过复制文件来实现,这通常会导致文件丢失、更改被覆盖以及在尝试确保一切都已纠正时出现构建失败。
另一方面,Git 是一个分布式源代码控制系统。这意味着“中心”副本只是另一个副本。它可以从任何克隆或多个克隆重建,以获取所有更改。此外,您无需访问“中心”存储库即可查看历史记录。您的本地克隆中包含完整的历史记录。
我们已经讨论了转向 Git 一段时间了,随着我们发布 Workspaces,现在是时候进行迁移了。Kamil 和他的团队的工作创建了一个平台,我们有信心在该平台上开展业务、管理代码以及管理 Bug、功能和待办事项列表。
正如标题所示,我们的 SVN 存储库的组织方式,并非出于本意,而是以非标准的方式构建的。这意味着各种关于此过程的指南、博客和文档仅仅是起点。在这篇文章中,我将引导您完成我从 SVN 迁移到 Workspace Git 存储库的过程,并指出由于我们的 SVN 存储库结构,我必须更改或添加哪些步骤。
背景
我将为此练习使用的工具是(假设您已安装 SVN):
- Git Gui 和 Git Bash,它们随 GitExtensions 一起提供。我选择这个是因为大多数示例/指南都使用 *NIX shell 脚本。
- git 在线文档。
- Visual Studio 2013,因为这是我们在这里使用的,并且它内置了 Git 支持。
我这次旅程的起点是 John Albin 的博客 Converting a Subversion repository to Git。这是一个简单的分步指南,用于执行迁移。不幸的是,它假定 SVN 存储库的标准布局。标准的 SVN 存储库有一个根目录,其中包含 Trunk、Branches 和 Tags 子目录,每个存储库有一个项目,如下图所示。
不幸的是,我们没有这样设置。我们最终得到的是一个包含多个项目的存储库。下面显示了这种结构,其中突出了我们希望迁移的两个主要“存储库”。
执行迁移
步骤 1:创建 SVN 提交者列表
将 SVN 存储库克隆到 Git 时,Git 需要一个文件来将 SVN 标识符映射到 Git 格式的作者。此文件中的每一行都将如下所示:matthew = matthew <matthew@codeproject.com>
。
John Albin 建议运行脚本(为便于阅读而添加了换行符)
svn log -q | awk -F '|' '/^r/ {sub("^ ", "", $2); sub(" $", "", $2);
print $2" = "$2" <"$2">"}' | sort -u > authors-transform.txt
由于我有一段时间没用过 shell 脚本了,所以我做了些不同的处理。我使用了一个 Windows 控制台,并在我的临时目录中运行了命令 svn log -q > svnlog.txt
。svnlog.txt
文件包含以下格式的信息:
------------------------------------------------------------------------
r22779 | chris | 2014-01-18 21:52:05 -0400 (Sun, 18 Jan 2014)
------------------------------------------------------------------------
r22778 | matthew | 2014-01-16 17:41:09 -0400 (Fri, 16 Jan 2014)
------------------------------------------------------------------------
r22777 | chris | 2014-01-16 16:39:13 -0400 (Fri, 16 Jan 2014)
------------------------------------------------------------------------
r22776 | chris | 2014-01-16 15:42:53 -0400 (Fri, 16 Jan 2014)
------------------------------------------------------------------------
r22775 | chris | 2014-01-16 15:41:58 -0400 (Fri, 16 Jan 2014)
然后我编写了一个小的 C# 控制台应用程序来创建 Git 名称文件。它通过命令行 SvnUsers2Git svnlog.txt names.txt
执行。源代码如下所示。请注意,名称区分大小写,因此您可能会为同一个作者生成多个条目,仅大小写不同。这是必需的,因此请不要删除明显重复的条目。相信我,否则您的迁移将失败。
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace SvnUsers2Git
{
// This file takes a svn log -q file and converts it to for a user file for git migration.
class Program
{
static HashSet<string> names = new HashSet();
static void Main(string[] args)
{
if (args.Length != 2)
{
Console.WriteLine("Usage: SvnUser2Git inputfile outputfile");
return;
}
ReadNames(args[0]);
WriteNames(args[1]);
}
private static void ReadNames(string filename)
{
try
{
using (StreamReader inputFile = File.OpenText(filename))
{
string line;
string[] parts;
while ((line = inputFile.ReadLine()) != null)
{
if (line.StartsWith("-"))
continue;
parts = line.Split("|".ToCharArray());
if (parts.Length == 3)
names.Add(parts[1].Trim());
}
}
}
catch (Exception ex)
{
throw new Exception("Error opening input file " + filename, ex);
}
}
private static void WriteNames(string filename)
{
try
{
using (FileStream outputFile = File.Open(filename, FileMode.Create))
{
using (StreamWriter writer = new StreamWriter(outputFile))
{
foreach (string name in names.OrderBy(n => n))
writer.WriteLine("{0} = {1} <{1}@codeproject.com>", name, name.ToLower());
}
}
}
catch(Exception ex)
{
throw new Exception("Error opening output file " + filename, ex);
}
}
}
}
步骤 2:克隆 SVN 存储库
John Albin 使用 bash 命令行:
git svn clone [SVN repo URL] --no-metadata -A authors-transform.txt --stdlayout ~/temp/newrepo
由于我希望能够使用 SVN 中的新更改更新 Git 存储库,因此我没有使用 --no-metadata
选项。此外,由于我们的 SVN 布局非标准,我没有使用 --stdlayout
选项,而是使用了 -T Trunk/Src
选项来指定 SVN 存储库中的 Trunk 目录。我在我的临时目录中执行了以下 bash 命令,创建了一个可以从 SVN 更新的 Git 存储库 d:\temp\SvnGitRepo
。
git svn clone https://cp-######/svn/codeproject -T Trunk/Src/ --authors-file=names.txt /d/temp/SvnGitRepo >> repo.log
对于我们的存储库,此过程需要一天多的时间才能执行,因此需要能够刷新存储库。然后我将一个副本备份到 d:\temp\SvnGitRepo.bak
,以防我在下一步中出错。
步骤 3:创建 .gitignore 文件
在这里,我遵循了 John Albin 的建议,并对我的布局进行了少量修改。我创建了一个批处理文件 MakeGitIgnore.bat
,在 Git Bash 窗口中运行,其中包含:
cd /d/temp/SvnGitRepo
git svn show-ignore > .gitignore
git add .gitignore
git commit -m 'Convert svn:ignore properties to .gitignore.'
cd /d/temp
将 .gitignore
文件复制到安全的地方。如果您需要使用存储库的备份副本(我将这样做),则无需重新运行批处理文件,这可能需要一些时间。
更新:当我尝试执行完整迁移时,我发现此 .gitignore
文件未能按预期工作。它非常大,而且难以调试。我使用另一个 VS 创建的项目中的 .gitignore
文件作为模板,创建了一个更小、更易于理解的文件。您可能不会尝试转换包含 100 个项目的解决方案,因此您可能会有更好的运气。
步骤 4:创建仅本地 Git 存储库
为了创建仅包含感兴趣项目的 Git 存储库,我需要创建一个克隆存储库,对其进行修剪,然后将其推送到 Workspaces。第一步是创建一个本地的 Bare Git 存储库,并将 Git-SVN 存储库推送到其中。这样我就可以操作一个不需要很长时间才能重新创建的东西。
在此之前,我需要确保我的 Git-SVN 存储库是最新的。距离我进行初始 SVN 克隆已经过去了 5 天,并且自那时以来已经进行了许多错误修复和新功能的签入。由于我在确定如何进行迁移时弄乱了我的存储库,所以我首先将我的备份复制到一个新的 SvnGitRepo 文件夹,如果需要,删除旧的文件夹。通过以下 bash 命令获取最新更改:
cd /d/temp/SvnGitRepo
git svn fetch
获取之前
获取之后
然后,使用 Pro Git book 中的导入后指南 Git and other systems - migrating to git 部分,在 git 网站上,执行了以下命令。注意:我使用了 PDF 文件,它与在线版本略有不同。
$ cp -Rf .git/refs/remotes/tags/* .git/refs/tags/
$ rm -Rf .git/refs/remotes/tags
$ cp -Rf .git/refs/remotes/* .git/refs/heads/
$ rm -Rf .git/refs/remotes
这使得所有远程标签都变为本地(尽管我没有任何),并且本地分支也变为本地。
接下来我将运行一个批处理文件 InitBareAndClone.bat
。顾名思义,它将创建一个 Bare 存储库,将 SvnGitRepo 推送到其中,然后将其克隆到一个本地存储库 d:\temp\theRepo
。批处理文件的内容如下所示。
git init --bare /d/temp/bareRepo.git
cd /d/temp/bareRepo.git
git symbolic-ref HEAD refs/heads/trunk
cd /d/temp/SvnGitRepo
git remote add bareRepo /d/temp/bareRepo.git
git config remote.bareRepo.push 'refs/remotes/*:refs/heads/*'
git push bareRepo --all
cd /d/temp/
git clone bareRepo.git theRepo
cd theRepo
git branch -m trunk master
cd theRepo
几分钟后,这将完成,存储库将包含所有项目的文件,并且位于“master”分支上,已将“trunk”分支重命名为“masters”。
现在我们可以修剪此存储库以仅保留我们感兴趣的项目。
步骤 5:修剪存储库
我们在上一步中创建的存储库包含我们非标准 SVN 存储库中的所有项目。我们希望创建一个仅包含 CodeProject-2.7
项目的存储库。
快速搜索 Google 后,我找到了一篇由 Dalibor Nasevic 撰写的博客,标题为 Permanently remove files and folders from a git repository 。虽然这并没有完全达到我的目的,但它确实为我指明了我需要的 Git 命令 git-filter-branch
,可以在 在线文档中找到。
此命令有一个选项 --subdirectory-filter <directory>
,它会删除除指定目录外的所有根目录,然后将该目录的内容移至根目录。正是我需要的 :)。它还需要一个临时目录,通过 -d <directory>
选项指定,因为它需要签出存储库才能进行工作。建议临时目录位于另一个驱动器上以提高性能。我在 theRepo
存储库的 Bash 窗口中运行了以下命令。
git filter-branch --subdirectory-filter CodeProject-2.7 -d /c/TempGitRepo
几分钟后,我现在拥有一个仅包含我想要的项目的存储库,以及它的所有历史记录。
步骤 6:添加 .gitignore 文件
在这里我很高兴我保存了 .gitignore
文件的副本,因为根目录的内容已被选定项目的目录内容替换。我的 .gitignore
文件不见了。我将保存的副本复制到存储库目录。
然后我在 Visual Studio 2013 中打开了解决方案以验证项目是否加载。解决方案成功加载。因此,我将 .gitignore
文件添加到了我的 Solution Items
文件夹中,以便于参考。
下一步是将这些更改提交到本地存储库。为此,您需要使用 Git Source Code Provider
。可以在以下位置找到它:
Tools->Options->Source Control->Plug-in Selection
选项设置,如下图所示。更改后可能需要重新打开解决方案。我通常不会这样做,但在撰写本节时做了一次。
从 View
/ 菜单打开 Team Explorer
。
单击 Changes
进入 Git 的提交更改支持。如果需要,将 .gitignore
文件从 Untracked Files 拖到 Included Changes 部分。另外,如果需要,右键单击 CodeProject.suo
文件并使其忽略 *.suo 文件,因为它们是用户特定的。我的 .gitignore
文件中有此条目,因此不需要。
输入提交消息,例如“Added .gitignore file”,然后单击 Commit
按钮。关闭解决方案,因为我们需要在下一步将 Git 原始地址更改为 Workspaces。
步骤 7:推送到 Workspaces
第一步是获取 Workspaces 中 Git 存储库的 URL。我们已经有一个 Workspace,因此将使用它附带的 ::GitMachine 实例。您也可以创建一个新的 Workspace 或将 ::GitMachine 的新实例添加到现有 Workspace。这需要是一个空的存储库。您可以从 ::GitMachine 实例获取存储库的 URL,如下所示。页面上还提供了更详细的说明。
我创建了一个批处理文件 PushToWorkspaces.bat
来完成工作。此文件包含:
cd /d/temp/theRepo
git remote remove origin
git remote add origin https://git.codeproject.com/[a-user]/[a-workspace]/cp-code
git push -u origin master
系统会要求您输入电子邮件地址和密码。将 URL 替换为您的 Git 存储库 URL 中的 URL,您可以通过 Workspaces Git 存储库获取该 URL,如下图所示。
完成后,您就可以克隆存储库并开始工作了。我需要对我的解决方案做一些更改,但这些是为了修复 NuGet 包还原问题。否则,一切都能成功构建和运行。您正在阅读本文的 CodeProject 网站版本很可能就是从 Workspaces Git 存储库构建的。
关注点
Visual Studio 2013 Update 2,凭借其改进的内置 Git 支持和 Workspaces,为管理您的代码和您的任务、Bug 和待办事项列表提供了一个绝佳的环境。