Git用户的Subversion入门指南

有的时候就是会遇到这么奇葩的情况。虽然 git 在版本控制软件中独占鳌头,但学校的老师还是要求使用更加古老的工具,比如在 PhD 圈子中流传很广的 subversion (svn). 我花了几个小时简单学习了 svn, 如果有 git 的基础,掌握 svn 一点儿都不困难。


为什么要推出 git?

Pro Git 这本书的作者介绍,2005年 Linus 打造 Linux Kernel 的时候,和先前免费托管代码的公司关系破裂,不能再使用它们的产品,所以走上了自主研发 git 的道路。在构思 git 时 Linus 考虑到了 Linux Kernel 这种超大型工程的效率问题,因此采用了轻量级的设计。这种设计特别有助于非线性编辑和完全分布式开发。如果剥离这些特征,git 与 svn 就大同小易了。


集中控制与分布控制

svn 与 git 最大的区别在于,svn 是集中版本控制软件,而 git 是分布式版本控制软件。svn 有一个主服务器,上面存储了所有的信息,每个人要从服务器 checkout 代码库,获得本地的缓存,做相应的变动,并且直接 commit 到远程的服务器。因此,svn 在不联网的情况下是无法提交新版本的。而 git 的代码仓库不仅存在于远程服务器中,也存在于本地磁盘上,开发者要先 commit 到本地的仓库,再 push 到远程服务器。如果服务器的仓库损坏,任何一个最新的本地仓库都可以恢复整个系统。


svn 的仓库结构

与 git 不同,svn 缺乏项目的概念。对于 git 而言,可以任意指定一个文件夹,在终端输入

1
git init

就创建了一个新的项目,它拥有独立的仓库。而 svn 的仓库是保存在远程服务器中的,为了便于管理,所有的“项目”都存放在同一个地方,例如 /var/svn, 且约定“项目”具有下面的结构:

1
2
3
4
5
6
7
8
9
10
11
var
|- svn
|- repos
|- project1
|- trunk
|- branches
|- tags
|- project2
|- trunk
|- branches
|- tags

每个项目中的 trunk, branches, tags 文件夹不是必须的,但一般使用这三个文件夹达成不同的目的。trunk 用来存放主分支的代码,相当于 git 中的 master 分支。branches 用于存放其他分支的代码,类似于 git branch. tags 用于存放特定版本的代码,用于软件发布,类似于 git tag.

受到这些约定的限制,新建一个 svn 项目会略微复杂一些,因为我们需要自己建立这些文件夹,再将整个“项目” import 到服务器中

1
2
3
4
5
6
7
8
9
mkdir trunk branches tags
cd trunk
vim foo.c # do some jobs

# assume we are in /home/bar/svnproj/trunk
# and svn server is running on localhost
svn import /home/bar/svnproj file:///var/svn/repos/svnproj -m "initial import"
# if we want to import to a remote host
svn import /home/bar/svnproj https://svn.example.com/repos/svnproj -m "initial import"

而在 git 中,branches 和 tags 的都极度轻量化,新增一个 branch 或者 tag 仅仅会增加一个指针,而不会把代码从一个地方复制到另一个地方,因此也就没有必要设置额外的文件夹了。


svn 的基本操作

先来回顾一下 git 的基本操作



再对比 svn 的相关操作



他们的区别主要是 local repo 引起的。在 svn 中,没有 local repo 的概念,commit 操作直接将修改提交至 remote repo, 也就是说 svn commit = git commit + git push.

另外,由于没有 local repo, 也就不存在 fetch 和 pull 的问题,从 remote 取回的更新将直接反映在 working directory 上,也就是说 svn update 取代了 git fetch 和 git pull 的功能。

但是图中的 svn checkout 又是怎么回事呢?一般来说,checkout 用于取回只存在于 remote, 而在 working directory 上没有的文件。例如

1
svn checkout file:///var/svn/repos/svnproj/trunk svnproj

会在当前目录下新建一个 svnproj 文件夹,里面存放了 remote repo 中 svnproj/trunk 目录下的文件,以及一个 .svn 文件夹,说明该目录是一个 svn 目录。如果只想修改服务器里 trunk 下的文件,下次从服务器索取更新时就应使用 svn update. 这时如果需要修改 remote repo 中 svnproj/branches 或 svnproj/tags 的内容,就要先用 svn checkout 复制到本地的 working directory, 再 commit 到 remote repo. 注意在本地可以使用不同的目录对应 remote 的 trunk, branches 和 tags, 因此操作时请多加小心。


杂项

最后谈一谈 ignore files 的问题。在 git 中,每个项目的根目录下都可以配置一个 .gitignore 的文件,用于标记不应被 stage 的文件。在 svn 中可以使用类似的技巧,但并不够直观。

首先,创建一个 ignore.txt 文件(名字不重要),使用和 .gitignore 相同的方法标记不应被 stage 的文件。

随后,将文件内容应用于 svn 仓库

1
svn propset svn:ignore -F ignore.txt .

注意最后的 dot 会将此设置应用于当前目录。

别忘了删除或者移出 ignore.txt, 因为它不是我们想提交的文件。