SVN 版本控制入门:集中式协作与冲突处理
📝 笔记摘要:SVN 版本控制入门
SVN 是集中式版本控制系统,所有历史都放在中心服务器上,本地只有一份工作副本。这篇先把它和 Git 的模型差异讲清楚,再走一遍完整流程:服务端怎么装怎么配、客户端怎么检出提交、冲突怎么解、分支标签怎么打,再说说从 SVN 迁到 Git 要注意什么。
原笔记里只有概念、工作流图和两个下载页面,客户端与服务端的配置细节是缺的。补上的部分里有两个坑值得单独提:svnadmin create 从 1.8 起不再接受 --username 和 --password,权限要改配置文件;svn commit 之前必须先 svn update,不然冲突全堆到提交那一步。
1. 先说清楚:SVN 和 Git 差在哪
两者的差别不在命令,而在仓库放在谁手里。
Git 是分布式的
每个克隆都是完整仓库,带着全部历史
本地提交不联网也行,断网照样干活
分支是纯本地指针,建分支几乎不花时间
SVN 是集中式的
历史只在服务器上,本地是工作副本,没网就干不了活
更新才能拿到别人的改动,提交前必须先 update
分支是服务器上的目录副本,建分支占服务器空间
共同点
两者都有 commit、都有版本历史、都能打 tag
两者都会冲突,也都得手工解决
历史里都能追到某个文件是被谁在什么时候改的
所以 SVN 的人离开服务器就寸步难行,Git 断网照样能提交;也所以 SVN 的目录版本控制天然统一,Git 的分支管理更灵活。用惯 Git 的人上手 SVN 最大的不适应是「提交前得先更新」,这在 Git 时代基本不用管。
2. 核心概念
Repository 版本库,源代码存放的地方
在服务器上,里面是全部历史和当前版本
Working Copy 工作副本,本地检出的那份
SVN 1.7 之后整个工作副本只有一个.svn目录,放在根目录,不是每个子目录一份
1.6 及更早是每个子目录一个.svn,从一个老仓库升级上来会看到一堆
Checkout 从服务器把项目检出到本地
本地还没有代码时的第一步
Update 把服务器上的最新改动同步到本地
对应 Git 的 fetch 加 merge,但 SVN 的 update 会直接合
Commit 把本地改动提交回服务器
服务器版本号加一,变成全局的新版本
Revert 撤销本地改动,恢复成服务器上的样子
注意这个 Revert 是 SVN 的,Git 里没有同名命令,对应的是git restore
几个 SVN 特有的说法:
版本号 revision 服务器上的全局递增序号,整个仓库共用一条
所以看某个目录的日志,版本号可能是 18、25、31 这样跳着走的,不是连续递增
想看连续的记录得用svn log -r 18:31指定区间
URL 上的 ^ 符号
svn log ^/trunk当前工作副本所对应的仓库根下的 trunk
svn log ^/branches/feature
这是 SVN 的 peg revision 语法,1.5 引入的,记分支路径时很好用
trunk / branches / tags SVN 仓库的经典目录布局
trunk 放主线,branches 放开发分支,tags 放发布版本
这三个只是约定,SVN 本身不强制
3. 集中式工作流
日常循环就三步:
1.
svn update把服务器上的改动拉下来
2. 改代码
3.svn commit -m "说明"提交回服务器
update 放在最前面
趁改动还少的时候同步,冲突范围小,手动解的成本低
攒一周再同步,同一个文件被改过的地方全撞在一起,解到想哭
提交之前如果本地有新增的文件或目录,还得先 svn add 登记一遍。SVN 不跟踪没 add 过的文件,这是 SVN 和 Git 最容易让人卡住的一处差异——Git 里新文件只要 add 过就行,SVN 里新目录还得把里面的内容一起 add。
4. 安装
SVN 分服务端和客户端两套东西,各有官网,装之前先分清自己要哪一边。
4.1 服务端
Windows 上用 VisualSVN Server,带图形界面,能创建仓库、配用户、开权限、跑成 Windows 服务,比敲命令方便。
下载地址
https://www.visualsvn.com/server/download
64-bit 还是 32-bit
现在的 Windows 都是 64 位,选 64-bit 那个
服务器内存不够 32 位进程会直接崩,这是硬限制
版本
图里是 5.1.1,现在官网已经到 5.4.x 了,用官网当前版本
5.3.x 及更早的分支已经停止维护
Linux 上一般用 Apache 官方发布的 svnadmin 加 svnserve,或者直接上 Docker 镜像。命令行的配置方法在后面单独讲。
4.2 客户端
Windows 上主流是 TortoiseSVN,装完在资源管理器右键多出一整套 SVN 菜单,操作方式跟 Git 客户端类似。
下载地址
https://tortoisesvn.net/download.html
版本
图里是 1.14.5,现在官网已经是 1.14.9 了
1.14.9 链接的是 Subversion 1.14.5 库
三个架构怎么选
普通 Intel/AMD 的 Windows 机器选 64-bit
ARM 设备(比如骁龙笔记本)才选 ARM64
选错了装不上,或者装上右键菜单不出现
中文界面
安装程序里单独有「简体中文语言包」选项,装完重启资源管理器生效
x64 上的一个老问题
在 64 位 Windows 上,32 位程序打开的文件对话框里右键菜单不显示
装一下vc_redist.x86.exe就好了,资源管理器本身不受影响
命令行客户端是 Apache SVN 的 svn.exe,TortoiseSVN 安装时可以选择一并装上。用它做 CI 或者在服务器上跑脚本更方便。
5. 服务端配置
不管用 VisualSVN 还是命令行,SVN 服务端的结构都一样:一个仓库目录,里面放数据、配置和钩子脚本。
# 建一个仓库
svnadmin create /opt/svn/repo
# 目录结构长这样
/opt/svn/repo/
├── conf/ 配置文件,最常改的就是这里
│ ├── svnserve.conf 服务总配置
│ ├── passwd 用户名和密码
│ ├── authz 权限规则
│ ├── hooks/ 钩子脚本目录
│ └── ...
├── db/ 版本库数据本体
├── hooks/ 仓库级钩子脚本
├── locks/ 锁定信息
├── format 仓库格式版本
└── README.txt
提示:
svnadmin create从 SVN 1.8 起不再接受--username和--password参数。
网上很多老教程还在写这两个参数,照着敲会直接报 unrecognized option。1.8 之后建仓库不带权限,权限统一在 conf/ 里的三个文件里配。
5.1 三个配置文件
conf/svnserve.conf 是服务的总开关:
[general]
# 匿名用户的权限:none / read / write
anon-access = none
# 认证通过用户的权限
auth-access = write
# 用户账号文件
password-db = passwd
# 权限规则文件
authz-db = authz
# 认证域,客户端弹窗里显示的名字
realm = myrepo
conf/passwd 放账号:
[users]
user1 = 密码1
user2 = 密码2
conf/authz 放权限规则,这一个是最需要想清楚的:
[groups]
dev = user1, user2
qa = user3
[/]
* = # 其他人对根目录没有任何权限,这行必须写
user1 = rw
[/trunk]
@dev = rw
[/branches]
@dev = rw
@qa = r # 测试只读
[/tags]
* = r
authz 是按路径逐级细化的
根目录给了rw,某个子目录没单独配,就跟着根目录的权限走
想让 QA 在 trunk 上只读,得在[/trunk]段里显式写@qa = r
* =这一行别漏
漏了的话前面没匹配到的用户会继承到意外权限
权限是白名单式的
没写到的用户默认什么权限都没有,不会自动继承
密码在 passwd 里是明文存的,这个文件权限要收紧:
chmod 600 /opt/svn/repo/conf/passwd
5.2 启动服务
# 前台跑,方便看日志
svnserve -r /opt/svn/repo --listen-port 3690
# 后台跑
svnserve -d -r /opt/svn/repo --listen-port 3690
# 用 https 协议需要套一层 Apache
mod_dav_svn + mod_ssl
3690 是默认端口
用默认端口的话--listen-port可以省掉
防火墙要放行
CentOS / RHEL 上firewall-cmd --add-port=3690/tcp --permanent然后--reload
云服务器还要在安全组里放行,光开系统防火墙没用
两种访问协议
svn://服务器IP:3690/仓库名svnserve 自己的协议,轻量
http://服务器IP/svn/仓库名走 Apache,可以挂 Web 界面和 HTTPS
5.3 钩子脚本
仓库的 hooks/ 目录里放着一堆 .tmpl 模板,把模板改个名去掉后缀就是生效的钩子。常用的有两个:
pre-commit 提交前执行,退出码非 0 就拒绝这次提交
常用来做格式检查、代码风格检查、禁止提交大文件
post-commit 提交后执行,不影响提交结果
常用来发通知、触发 CI、打标签
pre-revprop-change 允许修改已提交版本的属性,主要是提交说明
默认 SVN 禁止改 commit message,报错说「此版本库不允许修改版本属性」
就是缺这个钩子,放一个返回 0 的脚本进去就能改
VisualSVN Server 的图形界面把这些都做成了表单,Windows 上一般不用手工改这些文件。
6. 客户端基本操作
TortoiseSVN 的操作都在右键菜单里,命令行和图形界面是一一对应的。
SVN Checkout 从服务器把项目拉到本地
填仓库地址,选一个本地目录,revision 留 HEAD 就是最新
同一个项目可以在本地放多份工作副本,目录互相独立
SVN Update 同步服务器上的改动
SVN Commit 提交本地改动
提交说明是必填的,不填不给提交
提交前会列出待提交的文件,新增的没 add 过的不在列表里
SVN Revert 撤销本地改动
单文件可以选中右键 revert,整个目录 revert 会把下面所有改动一起撤掉
SVN Add / Delete 登记新增和删除
删除分两步:先在资源管理器里删掉,再在 SVN 菜单里选 Delete 登记
Show Log 看提交历史
选中某个版本能看详细信息:谁提交的、什么时候、改了哪些文件、每个文件的具体改动
Update to Revision 把工作副本退回到某个历史版本
这是 SVN 独有的能力,Git 里没有直接对应的命令
命令行的对应写法:
# 检出
svn checkout https://服务器IP/svn/repo/trunk myproject
# 更新
svn update
# 看状态:M 改了,A 新增,? 没纳入版本控制,! 丢失,冲突是 C
svn status
# 提交
svn commit -m "修复订单超时未释放库存"
# 撤销
svn revert src/main/java/OrderService.java
# 看历史
svn log -v
svn log -r 20:35
# 看某个文件某次改动的内容
svn cat -r 25 ^/trunk/src/Order.java
7. 冲突
两个人改了同一个文件的同一部分,SVN 就会在文件里插标记让你处理。冲突文件在 svn status 里显示为 C,TortoiseSVN 的图标会变成红色感叹号。
<<<<<<< r25
这是服务器上的原始内容
=======
这是你本地改的内容
>>>>>>> r31
处理流程:
1. 打开文件,把三段之间的内容改成最终该有的样子
2. 删掉<<<<<<<、=======、>>>>>>>三行标记
3. 在 TortoiseSVN 里右键 → SVN Resolve → Mark Resolved
命令行是svn resolve 文件 --accept working
4.svn commit提交
accept 的几个取值
--accept working用你自己改好的内容,默认就是这个
--accept mine-full冲突部分全用自己这版,不用自己那版覆盖
--accept theirs-full冲突部分全用服务器那版
--accept base用合并前的原始内容,一般不用
提示:TortoiseSVN 自带 TortoiseMerge 合并工具,右键 → Edit Conflict 能开三栏对比,比纯文本清楚得多。
能少冲突就少冲突
提交前先 update,这是成本最低的一条
改动的文件分散开,别几个人同时动同一个文件
大文件、生成代码这类别人也会改的,提前说一声
SVN 没有 Git 那种本地分支随便试的机制,冲突只能硬解
8. 分支与标签
SVN 没有专门的分支命令,分支和标签都是用 svn copy 在服务器上复制一份目录出来的。
# 从主线拉一个分支(服务器端复制,不下载文件,很快)
svn copy ^/trunk ^/branches/feature-login -m "拉出登录模块开发分支"
# 从已有分支再拉一个
svn copy ^/branches/feature-login ^/branches/feature-login-v2 -m ""
# 打标签
svn copy ^/trunk ^/tags/v1.0 -m "1.0 发布版本"
# 把工作副本切到分支
svn switch ^/branches/feature-login
# 切回主线
svn switch ^/trunk
# 看有哪些分支和标签
svn list ^/branches
svn list ^/tags
copy 不是真的复制文件
服务器端是写一条「这个路径就和那个路径共享同一份历史」的记录
所以建分支几乎不花时间也不占空间,这是 SVN 的分支比 Git 还轻的原因
tag 和 branch 用的是同一个命令
区别只在于约定:放在tags/下面的是标签,理论上不再改动
SVN 不阻止你改 tag,纪律得自己守
合并走 merge,不是 copy
svn merge ^/branches/feature-login ^/trunk .
把分支上相对某个基点的改动合进当前工作副本
合完照常 commit
9. 常用命令速查
svn cleanup 清理中断的操作
操作中途断网或者进程被杀,会留下一个锁,下次操作直接报错说工作副本被锁住了
跑一下svn cleanup就解开
svn info 看工作副本的基本信息
仓库地址、当前版本号、上次 update 的时间都在里面
svn diff 看本地改动
svn diff -c 25看某个版本具体改了什么
svn export 导出干净副本,不带 .svn 目录
发版打包、给不装 SVN 的人看代码,都用它
10. 从 SVN 迁到 Git
SVN 项目转 Git,一般用 git svn clone 或专门的转换工具。
# 从 SVN 克隆到 Git
git svn clone https://服务器IP/svn/repo/trunk myproject
# 只拉最近若干个版本,快很多
git svn clone https://服务器IP/svn/repo/trunk myproject -r 1:HEAD
# 拉完之后把主干变成 master
git branch -m master
# 检出 tags 和 branches
git config svn-remote.origin.fetch refs/remotes/origin/trunk:refs/remotes/origin/master
git config svn-remote.origin.fetch refs/remotes/origin/tags/*:refs/tags/*
git config svn-remote.origin.fetch refs/remotes/origin/branches/*:refs/heads/*
git svn fetch
历史对应不上是正常的
SVN 是全局版本号,Git 是每次提交一个哈希,转换时只能按逻辑关系近似映射
SVN 一次提交涉及多个分支的情况,映射不完美
作者信息要手工配
SVN 里存的是显示名,Git 要的是名字和邮箱,转换后作者可能全是同一个人
用git svn的authors-file参数做映射表
大仓库要有耐心
几千个提交的历史可能要跑几十分钟,取决于 SVN 服务器的性能
迁完之后
删掉.git/svn目录里的缓存,仓库会小很多
SVN 的trunk / branches / tags目录结构不用保留,Git 用分支和 tag 自己管
💡 补充几个原文没写的:
工作副本和仓库的版本号
SVN 的版本号是整个仓库共用的,服务器上随便一次提交都是全局加一
所以本地工作副本的版本号和某个文件最近一次修改的版本号经常不一样
锁定与解锁
二进制文件(图片、安装包)SVN 默认不擅长合并,可以上锁独占
svn lock 文件svn unlock 文件
工作副本升级
从 1.6 及更早升级到 1.7+,会经历一次全量重写,.svn从每个目录一个变成根目录一个
这一步很慢,大仓库要留足时间
提交说明别写废话
SVN 的 commit message 是日后唯一能追溯的东西
写「修复问题」等于没写,写清楚改了哪个行为、为什么改
仓库要定期备份
集中式的意思是服务器挂了历史就全没了
svnadmin hotcopy做热备份,比直接拷目录安全
用 svnmucc 批量改历史
改提交说明、删文件、搬目录这类操作,svnmucc比逐个 checkout 改完再提交快得多
🔥 一句话总结:SVN 是集中式,所有历史在服务器上,本地只有工作副本,所以提交前必须先
svn update;分支和标签都是svn copy在服务器上复制出来的目录,svn switch切换、svn merge合并;冲突就在文件里插三行标记,手改完删标记再 commit;权限统一在conf/下的svnserve.conf、passwd、authz三个文件里配。
