需求

SVN 存在着太多问题了,具体见:

使用 Git 作为版本控制可以享受很多好处:查找历史很快,容易新建分支等等。

是否使用 Git LFS?

  • 但是游戏项目有自身的特点,那就是仓库极大,大概有 75G,需要将大文件转换为 Git LFS。
  • 另外使用 Git LFS 也有额外的好处,就是在拉取更新时可以使用多线程同时下载,极大提升更新速度。
  • 使用 Git LFS 后,仓库本身会变大,因为数据都不再压缩;但是用户本地的仓库并不会将所有历史的大文件都下载下来,只会保存当前分支引用的大文件,因此会维持在一个可以接受的大小。

因此确定最终需要将 SVN 转换为 Git + Git LFS。

调研

资料

官方文档

回答是用 git-svn 克隆 SVN 仓库后再用 git lfs migrate 迁移到 Git LFS

记录了使用 Atlassian 工具生成作者列表、git gc 导致转换过程需人工介入

使用不同方法转换的文章:

其他工具

调研结果

对比了一下第三方方案,发现很多已经很久没有更新了,而 Git 社区本身是非常活跃的。

虽然有很多第三方工具,但是考虑到成熟度、稳定性与兼容性,还是决定完全使用 Git 与 Git LFS 官方工具自己执行转换。

最终确定方案为:

  • SVN 仓库转换为 Git 仓库
  • Git 仓库转换为 Git LFS 仓库

环境

  • svn 1.13.0
  • Git 2.28.0
  • Git LFS 2.11.0
  • macOS 10.14.6
  • Sourcetree 4.0.2 macOS
  • Sourcetree 3.3.9 Windows

准备

软件升级

建议将使用到的软件都升级到最新版本,以解决可能出现的兼容性问题,包括 SVN、Git、Git LFS、Sourcetree、GitLab。

GitLab 存储位置

需要将仓库与 Git LFS 存储路径放到最大剩余空间的磁盘上。

使用 df -h 找到最大的磁盘,然后将仓库移动到那儿

确定由 /data/gitlab/git-data 移动到 /home/gitlab/git-data

1
2
3
4
5
git_data_dirs({
  "default" => {
    "path" => "/data/gitlab/git-data"
   }
})
1
2
3
4
5
6
sudo gitlab-ctl stop
mv /data/gitlab /home
mv /var/opt/gitlab/gitlab-rails/shared/lfs-objects /home/gitlab/git-data
vi /etc/gitlab/gitlab.rb
gitlab-ctl reconfigure
sudo gitlab-ctl start

调整完成后一定要新建一个仓库验证是否成功,在新建的裸仓库中使用 /opt/gitlab/embedded/bin/git log 可以看到提交记录:

1
2
3
4
cd /home/gitlab/git-data/repositories/@hashed/xx/xx/xxxxxxx.git
/opt/gitlab/embedded/bin/git log

ls /home/gitlab/git-data/lfs-objects

SVN 转换为 Git

作者映射

转换仓库是一个清理作者信息非常好的机会。建议在获得仓库的所有权限后获取作者信息,以防遗漏。 不推荐在本地使用命令获取作者信息。

1
2
svn log --quiet http://svn.example.com/project/trunk/ | grep -E "r[0-9]+ \| .+ \|" | cut -d'|' -f2 | sed 's/ //g' | sort | uniq
8m 3s

需要将得到的列表添加映射名字并保存为 user.txt

执行转换

1
2
git svn clone http://svn.example.com/project \
      --authors-file=users.txt --prefix "" -s fltx

关闭 GC

在转换过程中可以进入转换中的 Git 目录,执行以下命令关闭 gc 功能:

1
2
3
cd /work/project
git config gc.auto 0
git config --global gc.auto 0

结果比较

SVN 仓库大小 79.5G

Git 仓库大小 67,005,297,747 字节(磁盘上的 69.78 GB),共 117,796 项

验证

提交

查看最后一次提交 git log

查看首次提交时间是否正确 git log --reverse

查看提交统计信息

1
2
git shortlog -sn
1m 34s

文件内容

使用比较软件比较转换完成的 Git 仓库与原始 SVN 仓库,查找文件差异。比较时使用二进制比较文件内容。虽然这是转换的中间步骤,但是依然建议检测,确保及时发现问题。

清理

按照 ProGit 2nd 使用的方法删除不需要的东西,但在实际使用时可能是由于 SVN 仓库结构不合理,并没有删除掉什么东西。

1
2
3
4
5
for t in $(git for-each-ref --format='%(refname:short)' refs/remotes/tags); do git tag ${t/tags\//} $t && git branch -D -r $t; done
git tag
for b in $(git for-each-ref --format='%(refname:short)' refs/remotes); do git branch $b refs/remotes/$b && git branch -D -r $b; done
for p in $(git for-each-ref --format='%(refname:short)' | grep @); do git branch -D $p; done
git branch -d trunk

备份

由于 SVN 转换为 Git 耗时太长,建议在执行后续操作时提前备份。备份时不用压缩,保存为固实文件即可:

1
2
3
4
cd /work
tar -cvf project.tar project
scp /work/project.tar remote:/home/gitlab/git-backup
10m 5s

转换为 Git LFS

查找大型二进制文件

find 方法

在开始查找前先将 git 目录移走,不然查找时会浪费大量时间,过滤的方法也会遍历这些文件,造成不必要的时间浪费

1
mv .git ..

find . -type f -not -path "./.git/*" 用这种方法过滤会浪费大量时间

查找当前工作目录的大文件数量

 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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
$ find . -type f -size +10k | grep -o -E "\.[^\.]+$" | sort | uniq -c | sort -rn
8966 .tga
6307 .prefab
5823 .png
4574 .FBX
1696 .anim
 543 .jpg
 506 .txt
 367 .cs
 302 .TGA
 263 .jar
 257 .xlsx
 252 .lua
 249 .cpp
 239 .fbx
 127 .h
 121 .wav
 104 .exr
  96 .unity
  96 .bin
  86 .psd
  85 .py
  84 .asset
  76 .dll
  72 .cog
  62 .mp3
  51 .xml
  36 .ogg
  30 .vcxproj
  29 .exe
  27 .lib
  26 .so
  25 .shader
  24 .aar
  22 .c
  21 .obj
  19 .cginc
  17 .PNG
  16 .meta
  16 .mat
  15 .pdf
  14 .vcproj
  14 .cubemap
  13 .html
  12 .ttf
  11 .md
   9 .tif
   8 .guiskin
   7 .hlsl
   6 .unitypackage
   6 .pdb
   6 .docx
   6 .doc
   6 .cube
3m 50s

Git LFS 方法

同时也可以使用 Git LFS 提供的命令查找文件的总大小,感觉比 find 命令找到的结果多了一层汇总功能,更好用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ git lfs migrate info --everything --above="5MB"
migrate: Sorting commits: ..., done.
migrate: Examining commits: 100% (60286/60286), done.
*.exe     85 GB      3289/5785 files(s)    57%
*.lua     10 GB     1189/16545 files(s)     7%
*.xlsx    2.4 GB     278/17169 files(s)     2%
*.bin     1.5 GB        32/401 files(s)     8%
*.zip     1.1 GB      69/15067 files(s)     0%

9m 39s

执行转换

将上一步得到的大文件扩展名整理成 git lfs migrate 需要的格式。建议整个命令都在文本编辑器下编写修改,然后复制到终端中执行。

转换过程其实相对于 SVN 迁移要快得多:

1
2
3
4
5
6
7
8
$ git lfs migrate import --everything --include="*.unity3D,*.exr,*.unitypackage,*.pdf,*.psd,*.ai,*.fla,*.gif,*.jpg,*.jpeg,*.tga,*.tif,*.tiff,*.bmp,*.png,*.ttf,*.TTF,*.otf,*.aif,*.ogg,*.wav,*.rns,*.mp3,*.flv,*.mov,*.wmv,*.mpg,*.mpeg,*.avi,*.mp4,*.FBX,*.fbx,*.blend,*.lxo,*.so,*.bundle,*.a,*.dll,*.aar,*.srcaar,*.bin,*.mdb,*.ipa,*.swf,*.jar,*.apk,*.exe,*.rar,*.zip,*.gz,*.7z,*.PNG,*.TGA,*.obj,*.pdb,*.pyd,*.doc,*.docx,*.xlsx"
migrate: Sorting commits: ..., done.
migrate: Rewriting commits: 100% (60286/60286), done.
  master    82ca815ca183062a543f77614d1409b5308a8c69 -> fbbf99a916662e8d94c2dbb78ebfaaf82b27f965
migrate: Updating refs: ..., done.
migrate: checkout: ..., done.

1h 35m 23s

统计空间占用

1
2
3
4
.git/objects
55,727,853,152 字节(磁盘上的 58.25 GB),共 0 项
.git/lfs/objects
128,682,453,395 字节(磁盘上的 128.88 GB),共 118,924 项

验证

确定文件为文本指针

打开原来的二进制文件,确定现在内容是 Git LFS 定义的文本指针。

检出

转换完成的仓库不能直接使用,需要将 LFS 文件检出

1
2
3
4
$ git lfs checkout
Checking out LFS objects: 100% (26057/26057), 7.7 GB | 20 MB/s, done.

7m 10s

文件内容

使用比较软件比较转换完成的 Git 仓库与原始 SVN 仓库,查找文件差异。比较时使用二进制比较文件内容。

推送

执行推送

使用以下命令向 GitLab 服务器推送

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ git remote add origin git@git.example.com:project/project.git

$ git lfs install
Updated git hooks.
Git LFS initialized.

$ git push -u origin --all
Locking support detected on remote "origin". Consider enabling it with:
  $ git config lfs.https://git.example.com/project/project.git/info/lfs.locksverify true
Uploading LFS objects: 100% (74284/74284), 129 GB | 10 MB/s, done.
Enumerating objects: 888166, done.
Counting objects: 100% (888166/888166), done.
Delta compression using up to 8 threads
Compressing objects: 100% (838220/838220), done.
Writing objects: 100% (888166/888166), 1.90 GiB | 4.34 MiB/s, done.
Total 888166 (delta 662312), reused 75521 (delta 45182), pack-reused 0
remote: Resolving deltas: 100% (662312/662312), done.
remote: Checking connectivity: 888166, done.
To git.example.com:project/project.git
 * [new branch]          master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.

4h 5m 13s

验证

注意这是最后的集成测试,要确保所有的内容在服务器与客户端都是正常的。

服务器验证 LFS 功能

推送的过程中需要检查 LFS 文件是否正确上传,SSH 进入 GitLab 服务器查看以下目录

1
ls /home/gitlab/git-data/lfs-objects

客户端验证 LFS 功能

在另一台机器上拉取验证

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
$ git lfs install --skip-smudge

$ git clone git@git.example.com:project/project.git
Cloning into 'fltx'...
remote: Enumerating objects: 888166, done.
remote: Counting objects: 100% (888166/888166), done.
remote: Compressing objects: 100% (221090/221090), done.
remote: Total 888166 (delta 662312), reused 888166 (delta 662312), pack-reused 0
Receiving objects: 100% (888166/888166), 1.90 GiB | 10.83 MiB/s, done.
Resolving deltas: 100% (662312/662312), done.
Updating files: 100% (112949/112949), done.

$ time git lfs pull
Downloading LFS objects: 100% (21707/21707), 6.8 GB | 7.1 MB/s
real    16m15.989s
user    0m0.000s
sys     0m0.015s

恢复 LFS 钩子

1
2
3
$ git lfs install
Updated git hooks.
Git LFS initialized.

使用

忽略文件

仓库正式使用前第一件事情是增加忽略文件:

1
curl -sL https://www.toptal.com/developers/gitignore/api/unity > .gitignore

Git 钩子

服务器钩子

GitLab 的推送检查需要付费,不想付费的话只能使用全局的钩子自行编写逻辑

需求:过滤非第一父提交、作者信息、LFS 大小检测、提交信息格式检测、关键文件提交检测、限制分支名字必须包含作者全拼

文件用户修改为 git:root,增加可执行权限

测试时使用 GitLab 内置 ruby git

/opt/gitlab/embedded/bin/ruby

但是 sh 脚本无需修改 ruby 路径,因为 GitLab 在执行 sh 钩子脚本时已正确设置 PATH 环境变量,可以找到 ruby。

ruby 文件头部增加

1
2
Encoding.default_external = Encoding::UTF_8
Encoding.default_internal = Encoding::UTF_8

LFS 大文件检测

需要将文件名改为 updatepre-receive

1
2
chown git:root update pre-receive
chmod +x update pre-receive

客户端钩子

需求:提交信息格式检测、关键文件提交检测

根据需要查找一些现成的钩子使用,如检查指定文件是否提交。考虑到客户端不能方便地安装不同软件,建议只使用 bash 脚本编写的钩子,没有额外依赖。

软件

准备好软件及其安装方法

GitLab 配置

注册账号、分配权限、配置 SSH

生成 SSH Key

1
2
ssh-keygen -t rsa -b 2048 -C "user@example.com"
cat ~/.ssh/id_rsa.pub | clip

传输

为了减少下载时间,使用 U 盘或移动硬盘工具传输已经初始化好的仓库

需要将仓库克隆到本地后打包成压缩包,传递给所有人后本地解压使用仓库

Git 中文显示

在 Git 安装路径下,找到 etc 目录的 bash.bashrc 文件,加入下面几行解决了

1
2
3
4
5
6
export LANG="zh_CN.UTF-8"
export LC_ALL="zh_CN.UTF-8"
## 设置为中文环境,使提示成为中文
export LANG="zh_CN"
## 输出为中文编码
export OUTPUT_CHARSET="utf-8"
1
2
3
4
5
git config --global core.quotepath false                 # 显示 status 编码
git config --global gui.encoding utf-8                        # 图形界面编码
git config --global i18n.commitencoding utf-8        # 提交信息编码
git config --global i18n.logoutputencoding utf-8      # 输出 log 编码
git config --global svn.pathnameencoding utf-8

使用

  • 编写安装软件文档
  • 编写用户配置文档
  • 手动拉取最新仓库与 LFS 对象压缩为一整个压缩包
  • 教会几个核心人员如何安装与配置
  • 所有人同时拉取仓库与安装配置 Git