介绍

遗留项目使用的版本控制系统是 Mercurial,为了与他人协作方便,需要将其转换为 Git。

因为 Mercurial 与 Git 有着非常相似的模型,同时 Git 拥有强大的灵活性,将一个 Mercurial 仓库转换为 Git 仓库是相当直接的。

方法

使用 hg-fast-export 工具:

1
$ git clone https://github.com/frej/fast-export.git /tmp/fast-export

转换的第一步就是要先得到想要转换的 Mercurial 仓库的完整克隆:

1
$ hg clone <remote repo URL> /tmp/hg-repo

下一步就是创建一个作者映射文件。 Mercurial 对放入到变更集作者字段的内容比 Git 更宽容一些,所以这是一个清理的好机会。 只需要用到 bash 终端下的一行命令:

1
2
$ cd /tmp/hg-repo
$ hg log | grep user: | sort | uniq | sed 's/user: *//' > ../authors

这会花费几秒钟,具体要看项目提交历史有多少,最终 /tmp/authors 文件看起来会像这样:

1
Old Name <old@example.com>

在这个例子中,同一个人(Bob)使用不同的名字创建变更集,其中一个实际上是正确的,另一个完全不符合 Git 提交的规范。Hg-fast-export 通过向我们想要修改的行尾添加 ={new name and email address} 来修正这个问题,移除任何我们想要保留的用户名所在的行。如果所有的用户名看起来都是正确的,那我们根本就不需要这个文件。在本例中,我们会使文件看起来像这样:

1
"Old Name <old@example.com>"="New Name <new@example.com>"

新版本 fast-export 需要的是有双引号括起的原始字段与目标字段,可以使用 vim 替换下:

1
:%s/\(.*\)=\(.*\)/"\1"="\2"/

下一步是创建一个新的 Git 仓库,然后运行导出脚本:

1
2
3
$ git init /tmp/converted
$ cd /tmp/converted
$ /tmp/fast-export/hg-fast-export.sh -r /tmp/hg-repo -A /tmp/authors

-r 选项告诉 hg-fast-export 去哪里寻找我们想要转换的 Mercurial 仓库,-A 标记告诉它在哪找到作者映射文件。 这个脚本会分析 Mercurial 变更集然后将它们转换成 Git“fast-import”功能(我们将在之后详细讨论)需要的脚本。 这会花一点时间(尽管它比通过网格 更 快),输出相当的冗长:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
$ /tmp/fast-export/hg-fast-export.sh -r /tmp/hg-repo -A /tmp/authors
Loaded 4 authors
master: Exporting full revision 1/22208 with 13/0/0 added/changed/removed files
master: Exporting simple delta revision 2/22208 with 1/1/0 added/changed/removed files
master: Exporting simple delta revision 3/22208 with 0/1/0 added/changed/removed files
[…]
master: Exporting simple delta revision 22206/22208 with 0/4/0 added/changed/removed files
master: Exporting simple delta revision 22207/22208 with 0/2/0 added/changed/removed files
master: Exporting thorough delta revision 22208/22208 with 3/213/0 added/changed/removed files
Exporting tag [0.4c] at [hg r9] [git :10]
Exporting tag [0.4d] at [hg r16] [git :17]
[…]
Exporting tag [3.1-rc] at [hg r21926] [git :21927]
Exporting tag [3.1] at [hg r21973] [git :21974]
Issued 22315 commands
git-fast-import statistics:
---------------------------------------------------------------------
Alloc'd objects:     120000
Total objects:       115032 (    208171 duplicates                  )
      blobs  :        40504 (    205320 duplicates      26117 deltas of      39602 attempts)
      trees  :        52320 (      2851 duplicates      47467 deltas of      47599 attempts)
      commits:        22208 (         0 duplicates          0 deltas of          0 attempts)
      tags   :            0 (         0 duplicates          0 deltas of          0 attempts)
Total branches:         109 (         2 loads     )
      marks:        1048576 (     22208 unique    )
      atoms:           1952
Memory total:          7860 KiB
       pools:          2235 KiB
     objects:          5625 KiB
---------------------------------------------------------------------
pack_report: getpagesize()            =       4096
pack_report: core.packedGitWindowSize = 1073741824
pack_report: core.packedGitLimit      = 8589934592
pack_report: pack_used_ctr            =      90430
pack_report: pack_mmap_calls          =      46771
pack_report: pack_open_windows        =          1 /          1
pack_report: pack_mapped              =  340852700 /  340852700
---------------------------------------------------------------------
1
2
3
$ git shortlog -sn
   369  Bob Jones
   365  Joe Smith

那看起来非常好。 所有 Mercurial 标签都已被转换成 Git 标签,Mercurial 分支与书签都被转换成 Git 分支。 现在已经准备好将仓库推送到新的服务器那边:

1
2
$ git remote add origin git@my-git-server:myrepository.git
$ git push origin --all

额外处理

无法推送到服务器

服务器端仓库在推送时发生问题:

1
2
3
4
5
6
7
8
$ git push -u origin master
Counting objects: 10302, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (3488/3488), done.
remote: error: object 4d6b0a35ecbd348ed42c464fe12125754b994497: hasDotgit: contains '.git'
remote: fatal: Error in object
error: pack-objects died of signal 13
error: failed to push some refs to 'git@github.com:xxxxx/xxxxx.git'

解决方法

前往 BFG Repo-Cleaner by rtyley 下载 BFG Repo-Cleaner

运行以下命令来清理错误提交的 .git 目录文件:

1
2
$ cd converted_git_repository
$ java -jar ~/bfg-1.13.0.jar --delete-folders .git --delete-files .git --no-blob-protection .git

补充,清理完 .git 目录及文件后,需要执行以下命令,防止添加 .git 目录下的其他文件:

1
$ git reset --hard head

修改忽略文件

.hgignore 改为 .gitignore

需要手动修改 regex 语法为 glob 语法。

1
2
$ git mv .hgignore .gitignore
$ git commit -m "Git: Rename .hgignore to .gitignore"

本地设置

设置文件模式,以解决非 master 分支中文件模式的问题

1
2
$ cd repository
$ git config core.filemode false

git gui - How do I remove files saying “old mode 100755 new mode 100644” from unstaged changes in Git? - Stack Overflow

设置本地的忽略文件,以解决非 master 分支中忽略文件不存在的问题

1
2
3
4
$ cd repository
$ git checkout master .gitignore
$ git reset .gitignore
$ cp .gitignore .git/info/exclude

导入裸仓库到 GitLab 实例中

参考资料