SVN(版本管理)教程

正月十六
2020 年的冠状病毒至今未过去,憋在家就研究了下如何在 Kali-linux 里利用 docker 来部署subversion server

Apache Subversion(简称SVN),一个开放源代码的版本控制系统,相较于RCS、CVS,它采用了分支管理系统,它的设计目标就是取代CVS。互联网上很多版本控制服务已从CVS转移到了Subversion。

Docker

这里使用最新版本的 Docker-ce,至于如何安装请看 Install Docker-Ce and Images
安装时需要注意一下 Docker-Ce 的系统版本,因为 kali-linux Rolling 是建立在最新版的 Debian 下,所以运行 get-docker.sh时会报错,提示没有kali-rolling版本,这时我们就需要对 docker.list 进行一下修改,。

1
2
:~$ sed -i 's|kali-rolling|buster|' /etc/apt/sources.list.d/docker.list
:~$ sudo apt-get update && sudo apt-get install docker-ce

svn-server 镜像

这里我们使用 garethflowers 的 svn-server 镜像。因为它的镜像开源且体积最小。

安装

  • hub.Docker.com 进行安装
    1
    :~$ sudo docker pull garethflowers/svn-server
  • 从源码安装
    1
    2
    :~$ git clone https://github.com/garethflowers/docker-svn-server.git
    :~$ cd docker-svn-server.git && sudo docker build -t garethflowers/svn-server .

打开SVN

打开一个数据存储在 /home/svn 的容器:

1
:~$ sudo docker run --name my-svn-server --detach --volume /home/svn:/var/opt/svn --publish 3690:3690  garethflowers/svn-server
  • --volume:将镜像里的 /var/opt/svn 映射至实体机上的 /home/svn 下。
  • --publish:将 Docker 的 3690 端口映射至实体机的 3690 端口。

创建一个新的库

在 Docker 容器里使用svnadmin来创建与管理库。
我们在容器 my-svn-server 里新建一个名为 test 的库:

1
2
3
4
5
# Method 1
:~$ sudo docker exec -it my-svn-server svnadmin create test
# Method 2
:~$ sudo docker exec -it my-svn-server env TERM=xterm sh -l
262982ca78f2:/var/opt/svn$ svnadmin create test

配置

启动方式

  • 方式一:多库 svnserve 模式(指定到版本库的上级目录),默认启动模式。
    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
    :~$ sudo docker exec -it my-svn-server env TERM=xterm sh -l
    262982ca78f2:/var/opt/svn$ cd test
    262982ca78f2:/var/opt/svn/test$ vi conf/svnserve.conf
    [general]
    anon-access = none
    auth-access = write
    password-db = passwd
    authz-db = authz
    262982ca78f2:/var/opt/svn/test$ vi conf/passwd
    [users]
    admin = password
    test1 = test1_password
    test2 = test2_password
    262982ca78f2:/var/opt/svn/test$ vi conf/authz
    [groups]
    g_admin = admin,test1
    g_dev = test2

    [/]
    test1 = r
    * =

    [test:/]
    @g_admin = rw
    * =

    [test_2:/]
    @g_admin = rw
    * =
    客户端使用svn ls svn://192.168.0.1/test来访问 test 版本库。
  • 方式二:单库模式(-r 只为一个库进行服务)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    :~$ sudo docker exec -it my-svn-server env TERM=xterm sh -l
    262982ca78f2:/var/opt/svn$ killall /usr/bin/svnserve
    262982ca78f2:/var/opt/svn$ /usr/bin/svnserve --daemon --foreground --root /var/opt/svn/test
    262982ca78f2:/var/opt/svn$ cd test
    262982ca78f2:/var/opt/svn/test$ vi conf/svnserve.conf
    [general]
    anon-access = none
    auth-access = write
    password-db = passwd
    authz-db = authz
    262982ca78f2:/var/opt/svn/test$ vi conf/authz
    [groups]
    g_admin = admin,test1
    g_dev = test2

    [/]
    test1 = r
    @g_admin = rw
    * =
    262982ca78f2:/var/opt/svn/test$ vi conf/passwd
    [users]
    admin = password
    test1 = test1_password
    test2 = test2_password
    客户端直接使用svn ls svn://192.168.0.1/即可访问 test 版本库。

svnserve.conf

1
2
3
4
5
6
[general]
anon-access = none
auth-access = write
password-db = /var/opt/svn/passwd
authz-db = /var/opt/svn/authz
# realm =
  • anon-access: 控制非鉴权用户访问版本库的权限,取值范围为write/read/none。 即write为可读可写,read为只读,none表示无访问权限。
    默认值:read
  • auth-access: 控制鉴权用户访问版本库的权限。取值范围为write/read/none/。 即write为可读可写,read为只读,none表示无访问权限。
    默认值:write
  • password-db:指定用户口令文件名,通过它实现增加或删除 svn 的用户。除非指定绝对路径,否则文件位置为相对 conf 目录的相对路径。
    默认值:passwd
    例如:/home/svn/passwd
  • authz-db: 指定权限配置文件名,通过该文件可以实现以路径为基础的访问控制。 除非指定绝对路径,否则文件位置为相对 conf 目录的相对路径。
    默认值:authz
  • realm: 指定版本库的认证域,即在登录时提示的认证域名称。若两个版本库的 认证域相同,建议使用相同的用户名口令数据文件。
    默认值:一个UUID(Universal Unique IDentifier,全局唯一标示)。

passwd

1
2
3
4
5
[users]
# <user name> = <password>
admin = 12346789
admin02 = 987654321
test = test

用户名口令文件由svnserve.conf的配置项password-db指定,默认为 conf 目录中的passwd。该文件仅由一个[users]配置段组成。

authz

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[groups]
# <group name> = <group users>
g_admin = admin,admin02
g_test = test

[test:/]
# [Repository:Path]
admin = rw
* =
# rw --- read and write
# r --- read
# --- null, couldn't read and write
[/]
# All Repository
@g_admin = rw

权限配置文件由svnserve.conf的配置项authz-db指定,默认为 conf 目录中的authz。该配置文件由一个[groups]配置用户组和若干个版本库路径权限段组成。

SVN Client

  1. 拉取库
    1
    2
    3
    4
    5
    6
    7
    :~$ svn checkout svn://192.168.0.1/test --username=admin --password=123456789
    Checked out revision 0.
    # 因为远程库为空,所以这里的 revision 值为 0。检出成功后在当前目录下生成 test 副本目录。查看拉取的内容
    :~$ ls -lah test
    drwxr-xr-x 2 root root 4.0K Feb 9 11:52 .
    drwxr-xr-x 6 root root 4.0K Feb 9 11:50 ..
    drwxr-xr-x 4 root root 4.0k Feb 9 11:53 .svn/
  2. 添加新文件
    1
    2
    3
    :~$ cd test && echo "Test 01" > test.md
    :~/test$ svn add ./*
    A test.md
  3. 提交库
    1
    2
    3
    4
    5
    :~/test$ svn commit -m "add first file"
    Adding test.md
    Transmitting file data .done
    Committing transaction...
    Committed revision 1.
  4. 查看文件的更改
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    :~/test$ echo "Test 02" >> test.md
    :~/test$ svn diff
    Index: test.md
    ===================================================================
    --- test.md (revision 1)
    +++ test.md (working copy)
    @@ -1 +1,2 @@
    Test 01
    +Test 02
    :~/test$

客户配置

配置文件位于~/.subversion

1
2
3
4
5
:~$ ls -lah ~/.subversion
drwx------ 6 root root 4.0K Feb 20 2018 auth
-rw-r--r-- 1 root root 8.5K Feb 20 2018 config
-rw-r--r-- 1 root root 4.2K Feb 20 2018 README.txt
-rw-r--r-- 1 root root 8.2K Feb 20 2018 servers

config

此配置文件里有个重要的参数password-stores,即在本地缓存已经验证的用户名与密码,这样就不用每次提交修改都需要输入用户名与密码了。
但有一个缺点,就是我们的用户密码在服务器端修改后,我们在本地再次提交文件时会报出密码不正确的错误。所以这时就需要我们去手动删除~/.subversion/auth/下的缓存,重新进行用户密码登录。
为此,我们也可以将此值设置为空,阻止 subversion 缓存用户登录凭证。

解决冲突

假设 A、B 两个用户都在版本号为 100 的时候,更新了 HelloWorld.html 这个文件,A 用户在修改完成之后提交 HelloWorld.html 到服务器, 提交成功后 HelloWorld.html 文件的版本号已经变成 101 了。B 用户还在版本号为 100 的 HelloWorld.html 文件上作修改, 修改完成之后提交到服务器时,由于不是在当前最新的 101 版本上做的修改,所以导致提交失败。

此时我们需要 svn update 来更新冲突文件的版本,然后再提交更新。

1
2
3
4
5
6
7
8
9
10
11
:~/test$ svn update
Updating '.':
C HelloWorld.html
Updated to revision 6.
Conflict discovered in file 'HelloWorld.html'.
Select: (p) postpone, (df) show diff, (e) edit file, (m) merge,
(mc) my side of conflict, (tc) their side of conflict,
(s) show all options: mc
Resolved conflicted state of 'HelloWorld.html'
Summary of conflicts:
Text conflicts: 0 remaining (and 1 already resolved)

这边输入mc,以本地的文件为主。你也可以使用其选项对冲突的文件进行不同的操作。

默认是更新到最新的版本,我们也可以指定更新到哪个版本 svn update -r6

此时工作副本是和仓库已经同步,可以安全地提交更改了

1
2
3
4
:~/test$ svn commit -m "change HelloWorld.html second"
Sending HelloWorld.html
Transmitting file data .
Committed revision 7.

版本回退

当我们想放弃对文件的修改,可以使用SVN revert命令。

svn revert操作将撤销任何文件或目录里的局部更改。

我们对文件 readme 进行修改,查看文件状态。

1
2
:~$ svn status
M readme

这时我们发现修改错误,要撤销修改,通过 svn revert 文件 readme 回归到未修改状态。

1
2
:~$ svn revert readme
Reverted 'readme'

再查看状态。

1
2
:~$ svn status
:~$

进行 revert 操作之后,readme 文件恢复了原始的状态。 revert 操作不单单可以使单个文件恢复原状, 而且可以使整个目录恢复原状。恢复目录用 -R 命令,如下。

1
svn revert -R trunk

但是,假如我们想恢复一个已经提交的版本怎么办。

为了消除一个旧版本,我们必须撤销旧版本里的所有更改然后提交一个新版本。这种操作叫做 reverse merge。

首先,找到仓库的当前版本,现在是版本 22,我们要撤销回之前的版本,比如版本 21。

1
svn merge -r 22:21 readme

历史信息

  • svn log: 用来展示svn 的版本作者、日期、路径等等。
    1
    2
    3
    4
    # 查看版本6至版本8之间的变化
    :~$ svn log -r 6:8
    # 查看指定文件的历史信息
    :~$ svn log HelloWorld.html
  • svn diff: 用来显示特定修改的行级详细信息。
    1
    2
    3
    4
    # 查看工作区与版本库中的变化
    :~$ svn diff
    # 查看工作区文件与版本库号为3的变化
    :~$ svn diff -r 3 rule.txt
  • svn cat: 取得在特定版本的某文件显示在当前屏幕。
    1
    2
    # 查看过去版本的文件内容
    :~$ svn cat -r 版本号 rule.txt
  • svn list: 显示一个目录或某一版本存在的文件。
    1
    2
    3
    # 查看远程目录中的文件:
    :~$ svn list svn://192.168.0.1/test
    test.md

SVN 分支

Branch 选项会给开发者创建出另外一条线路。当有人希望开发进程分开成两条不同的线路时,这个选项会非常有用。

比如项目 demo 下有两个小组,svn 下有一个 trunk 版。

由于客户需求突然变化,导致项目需要做较大改动,此时项目组决定由小组 1 继续完成原来正进行到一半的工作(某个模块),小组 2 进行新需求的开发。

那么此时,我们就可以为小组2建立一个分支,分支其实就是 trunk 版(主干线)的一个copy版,不过分支也是具有版本控制功能的,而且是和主干线相互独立的,当然,到最后我们可以通过(合并)功能,将分支合并到 trunk 上来,从而最后合并为一个项目。

1
2
3
4
5
6
:~$ svn copy src/ branches/my_src
:~$ svn commit -m "add my_branch"
:~$ cd branches/my_src
:~/branches/my_src$ svn add ./*
:~/branches/my_src$ svn commit -m "add ....."
:~/branches/my_src$ svn merge branches/my_src

References:
wikipedia
runoob
docker
Install Docker and Images