svn使用
创建本地仓库
➜ svntest svnadmin create code
➜ svntest ls code
README.txt db hooks
conf format locks
创建项目
➜ svntest mkdir project
➜ svntest svn import project file:///home/svntest/code/project/trunk -m "Initial create"
正在读取事务
提交后的版本为 1
基础使用
➜ svntest svn co file:///home/svntest/code/project/trunk
取出版本 1。
➜ svntest cd trunk
➜ trunk echo 'hello, world!' > a.txt
➜ trunk svn add a.txt
A a.txt
➜ trunk svn ci a.txt -m "hello"
正在增加 a.txt
传输文件数据.done
回退
没提交的回退直接svn revert即可,如果是整个目录回退,那加个-R也就搞定了。这里要说的是已经提交的回退。
# 先通过日志找到要回退到的版本,例如要回退到r9
➜ trunk svn merge -r HEAD:9 a.txt
--- 正在反向合并 r12 到 “a.txt”:
U a.txt
--- 记录反向合并 r12 到“a.txt”的信息:
G a.txt
--- 正在从 'a.txt' 删除合并信息:
U a.txt
# 也可以单独回退某个版本的修改,但可能会有冲突,例如回退r14的修改,注意14前的负号
➜ trunk svn merge -c -14 a.txt
--- 正在反向合并 r14 到 “a.txt”:
U a.txt
--- 记录反向合并 r14 到“a.txt”的信息:
G a.txt
--- 正在从 'a.txt' 删除合并信息:
U a.txt
还有找回已经删除的文件。
➜ trunk svn delete a.txt
D a.txt
➜ trunk svn ci -m "delete a.txt"
正在删除 a.txt
正在读取事务
提交后的版本为 15。
# 找到删除时的版本,然后copy它的上一个版本即可
➜ trunk svn copy ^/project/trunk/a.txt@14 .
A a.txt
当然还有其他方式,但是使用svn copy的好处是旧的提交日志也回来了。
分支
一般来说,项目会创建trunk/branches/tags三个分支目录,其中trunk仅用作主干分支,branches和tags目录下则会再细分。上面创建项目时已经建立了trunk目录,现在再来把另外别个加上。
➜ svntest svn mkdir file:///home/svntest/code/project/branches -m "add branches directory"
正在读取事务
提交后的版本为 3。
➜ svntest svn mkdir file:///home/svntest/code/project/tags -m "add tags directory"
正在读取事务
提交后的版本为 4。
➜ svntest svn ls file:///home/svntest/code/project
branches/
tags/
trunk/
当一个新功能需要比较长的时间去进行开发,为了保持主干的稳定,一般会拉个新分支进行开发。
➜ svntest svn copy file:///home/svntest/code/project/trunk file:///home/svntest/code/project/branches/dev_test -m "add branch dev_test"
正在读取事务
提交后的版本为 5。
➜ svntest svn co file:///home/svntest/code/project/branches/dev_test
A dev_test/a.txt
取出版本 5。
➜ dev_test echo 'welcome!' >> a.txt
➜ dev_test svn ci -m "welcome"
正在发送 a.txt
传输文件数据.done
分支开发完成,合并回主干。合并前需要一份最新的干净的主干环境。
➜ trunk svn up
正在升级 '.':
版本 6。
➜ trunk svn merge ^/project/branches/dev_test
--- 正在合并 r5,经由 r6,到 “.”:
U a.txt
--- 记录合并 r5,经由 r6,到“.”的信息:
U .
➜ trunk svn propget svn:mergeinfo .
/project/branches/dev_test:5-6
➜ trunk svn ci -m "merge from dev_test"
正在发送 .
正在发送 a.txt
传输文件数据.done
实际情况并不会这么理想,两个分支并行开发,可能同时对相同的资源作了修改,合并时就会产生冲突。也有可能一个分支修改了另一个分支的某些依赖,就算合并不冲突,合并完也跑不起来。
并行是分支开发带来的好处,而合并时要解决的冲突就是账单。
开发周期越长,冲突就越多,解决所需要的时间也就越长。一种缓解最终合并时压力的方案是定期同步trunk的修改到分支上,避免后期冲突过大。而且越早感知到冲突,越容易避免产生更多的冲突,例如当你发现一个函数不存在了,也就不会再用了。
# 两边同时修改文件,然后在某个时间节点,dev从trunk同步最新修改,解决冲突,避免后期冲突过大
➜ dev_test svn merge ^/project/trunk
--- 正在合并版本库 URL 之间的差异到 “.”:
C a.txt
--- 记录版本库 URL 之间的合并信息到“.”:
U .
冲突概要:
Text conflicts: 1
Merge conflict discovered in file 'a.txt'.
Select: (p) Postpone, (df) Show diff, (e) Edit file, (m) Merge,
(s) Show all options: e
Select: (p) Postpone, (df) Show diff, (e) Edit file, (m) Merge,
(r) Mark as resolved, (s) Show all options: r
Merge conflicts in 'a.txt' marked as resolved.
冲突概要:
Text conflicts: 0 remaining (and 1 already resolved)
➜ dev_test svn ci -m "merge from trunk"
正在发送 .
正在发送 a.txt
传输文件数据.done
# 分支开发完成,合并回主干,这时冲突就比较小了
# 甚至可以在最终全并前再从主干同步一次到分支,那就基本完全没冲突了
➜ trunk svn up
正在升级 '.':
版本 11。
➜ trunk svn merge ^/project/branches/dev_test
--- 正在合并版本库 URL 之间的差异到 “.”:
U a.txt
--- 记录版本库 URL 之间的合并信息到“.”:
U .
➜ trunk svn ci -m "merge from dev_test"
正在发送 .
正在发送 a.txt
传输文件数据.done
冲突说明
格式
一般合并发生冲突时会产生如下输出:
line 0 by dev
abc
m4
<<<<<<< .working
line 3 by trunk
||||||| .merge-left.r10
afd
=======
line by dev_3 again
>>>>>>> .merge-right.r15
ghi
line 6 by trunk
其中:
.working 到 |||||| .merge-left.r10之间的内容为本地分支的版本。
|||||| .merge-left.r10 到 ======= 之间的内容为两个分支共同祖先的版本。
======== 到 .merge-right.r15之间的内容为合并分支的版本。
合并次序
如果是整个分支合并,则冲突是与target分支HEAD版本的冲突。而如果是挑着版本号来合,则会逐个版本号进行合并,所以冲突可能解决一次之后又来第二次,例如target分支重复改了同一个地方。
如果merge时选择postpone,则会在发现更多冲突时停下来。
svn vs git
git的优势:断网后还能工作、分支用起来更方便。 git的劣势:需要的磁盘空间比较大,基本不可能用来管理资源文件。 svn的优势:简单的C/S架构,容易上手、磁盘空间占用较小。 svn的劣势:分支使用起来相对来说没那么得心应手。
客观说一句,其实如果git分支合并时有产生冲突,那跟svn也没多大区别。当然没冲突时直接移动HEAD指针就完成合并,倒是真的快。