转载自:http://blog.csdn.net/tengbaichuan/article/details/10632349
------------------ 参考文档 --------------------
官方文档: http://www.subversion.org.cn/svnbook/ 包括可下载的PDF 和一页HTML。
配置文档: 去ubuntu的wiki吧~~ 如果用 svn + ssh 的话,基本不用配置,按下面“快速工作”即可。
------------------ 快速工作: -------------------
1. 建立版本库:
我的方式,是用svn+ssh来访问版本库,这意味着:
不用启动svnserve守护进程,而是由每次ssh连接到版本库时,自动启动svnserve,工作完再退出svnserve进程。这些东西,都是本地的svn调用ssh让它那样做的。而启动的svnserve进程的身份就是ssh用户的身份,而ssh用户来自于系统用户,这就是这种方法的优点:就用系统用户了。
正因为如此,每个用户是否对版本库能读写,就依赖于版本库的权限设置,就是你用svnadmin create生成的文件的权限设置。
我的策略是,为了让某些用户读写版本库,我创建了一个svn用户,和一个svn组,然后在/home/svn下建立版本库。然后把所有需要访问svn的用户,加到svn组中,并设置版本库的所有文件,svn组的人都能够读写。
在版本库机器上,具体如下:
adduser svn # 创建svn用户和svn组
gpasswd -a fengyi svn # 把fengyi用户加到svn组
svnadmin create /home/svn/buptnic # 创建版本库buptnic
chmod -R g+w /home/svn/buptnic # 让所有的文件为组可写(因为默认一般是组可读了)
以上两步,也可以改为,先用umask 0002,再用svnadmin 一样的效果。因为默认的umask一般是0022,这会去掉组的写权限。
注意:一旦svnadmin create之后,其他的事情,就应该用svn在工作拷贝机器上做了。———— 要点是,大部分svn的子命令,最终效果都是操作版本库,但是分两种途径。如果子命令带URL的,一般对版本库直接操作,如果不带URL的,一般是先对工作拷贝进行操作,然后用commit使版本库生效。当然,好多命令,即可以带URL,也可以不带,一般都是先update,再更改工作拷贝,再commit。
在工作拷贝机器上,具体如下:
svn mkdir svn+ssh://svn@210.25.137.252/home/svn/buptnic/flow # 建立flow项目目录
svn mkdir svn+ssh://svn@210.25.137.252/home/svn/buptnic/flow/trunk # 建立flow/trunk目录
svn mkdir svn+ssh://svn@210.25.137.252/home/svn/buptnic/flow/branches # 建立flow/branches目录
svn mkdir svn+ssh://svn@210.25.137.252/home/svn/buptnic/flow/tags # 建立flow/tags目录
svn import /home/echo/src svn+ssh://svn@210.25.137.252/home/svn/buptnic/flow/trunk # 将本地的src下的源文件导入到版本库的trunk目录下。注意,trunk是主分支,一般习惯是放任何东西,包括文档,第三方库。
然后,就OK了,你可以找个地方,用fengyi用户做checkout了。
svn checkout svn+ssh://fengyi@210.25.137.252/home/svn/buptnic/flow
注意的是:版本库中,虽然你通过mkdir建立了那些目录,上传了那些文件,但是,你是在/home/svn/buptnic/flow中直接看不到的,它们都放在flow 下的db目录中,是虚拟文件系统里。
2. 用户基本操作原则:
(1) 不管是 版本库 还是 工作拷贝,他们的维护文件,都在他们的文件夹内,所以,如果要删除他们,直接用文件系统的rm即可彻底删除。
(2) 用svn子命令,对版本库的直接操作,必须用一种形式的URL,而不能是普通文件路径,包括在版本库机器上!!!!!但是svnadmin等命令,就是直接用普通文件路径来操作版本库,并要求在版本库机器上执行。
(3) 在工作拷贝的哪个目录执行 svn 子命令,该命令的最后一个参数,默认就是当前目录,这使得命令的效果从当前目录开始作用。
这从每个目录里有一个.svn看得出来,每个目录都有维护文件,都可以单独工作。这也是工作拷贝的不同部分允许不同版本号的基础。
(4) commit只有当当前目录下所有文件的版本号 等于 版本库当前版本号时,才能成功。
工作拷贝的不同部分允许不同版本号,所以,当你commit时,可能当前目录下,比如各个子目录里 的版本号并不同,如果有比版本库低的版本号,commit就不能成功,提示信息是 ... out of date ...,这时,你需要update一次,然后,再commit。当然,其他很多情形,也可能导致这种情况,最典型的就是(5)中文件冲突的情况。
(5) 先在工作拷贝上操作,然后commit到版本库。
除了修改文件以外的任何操作,都用svn子命令完成,比如添加一个文件,创建之后,用svn add添加到本地拷贝。当你觉得本次的工作拷贝工作结束时,可以将其commit到版本库,使得版本库更新到新的revision。
(6) 解决冲突
解决冲突最好的办法,就是两个人不要编辑同一个文件。否则,见下面或资料,你就需要update一次,来合并两人的文件(因为别人先commit了,版本库里的版本号比自己的高,自己不会commit提交成功,需要先update)。如果需要手工解决冲突,见下面的解决办法。
(7) 是的,如果两个分支独立发展太久,则必然改动较大,那merge是会有冲突的,你还是要手动解决。
merge和update冲突时,生成的冲突临时文件名不同,但是源文件,都是被冲突标志标注了的,你只需要改源文件即可。里面<<<到>>>部分,是冲突区域,即svn认为,在这个区域上,两个文件各自发展了自己的修改,所以第一个文件的修改,放到<<<和===中,第二文件的修改,放到===和>>>中,你自己选择,保留哪个修改。如果某个修改为空,即如===和>>>中没有代码,那说明“修改”就是“删除”操作。
所以,当我们把branches的文件merge到trunk时,得考虑清楚了,因为branches在修改bug,而trunk在增加新特性,为了把branches的bug修改通过merge引入trunk,是很可能导致冲突的。
(8) revert 和 merge
revert是在版本内恢复,即:当commit后,就是一个版本了,之后,如果更改了任何东西,但是还没有commit的话,就可以恢复到上次commit后的状态。revert不依靠版本库,仅靠.svn目录来维护信息。
而merge,则是依赖版本库,做的则是版本间的差异比较,从而作用于某个目录。使得内容恢复到某个版本状态。
(9) 忘了的命令,用svn help subcmd 来查阅。
(10) diff, status, log 怎么用阿?
下面是trunk目录下,svn status -v 的输出:
28 28 fengyi .
28 26 fengyi sheep
28 26 fengyi sheep/sheep.c
28 20 svn main.c
28 28 fengyi cow
28 28 fengyi cow/main2.c
注意到:第一列是当前工作拷贝的版本号,是因为在trunk update后导致所有文件的版本号变为最新。但是,后面的一列数字,表明自那个版本号以来,文件没有改过(不保证版本库没有更新)。你用svn log看那个文件,也只能看到那个号版本,看不到当前工作拷贝的版本号(没改,没commit,怎么可能有log!)
-------------------- 笔记 ---------------------
第一章 基本概念
svn就是能够跟踪数据变化的文件服务器
“拷贝-修改-合并”模型而不是“锁定-修改-解锁”(见保存的图)
每当版本库由用户commit了一个提交,文件系统进入了一个新的状态,叫做一次修订(revision),每一个修订版本被赋予一个独一无二的自然数,commit显然是原子操作。同时,可以看出,版本库没有回退的概念,只有工作拷贝有这个概念,版本库只不过一直在往前走。
(许多其它版本控制系统使用每文件一个修订号的策略,所以会感觉这些概念有点不一样。)
SVN概念中,版本库的修订版本是统一的,就是说,任何时候,每个文件的“修订版本”都是一样的。
但是工作拷贝中经常是“混合修订版本”,因为你只提交一个文件,导致版本库中修订版本加1,这时,你的工作拷贝中这个文件的修订版本也加1,但是工作拷贝中其他的文件,只能维持原版本号,除非你update一次。
这是很有用的一个特性,它允许工作拷贝的任何一个部分在历史中前进或后退。
svn有一个基本原则就是“推”动作不会导致“拉”,反之亦然。
svn commit & svn update 和 四种状态 (是否修改+是否当前)
svn status --verbose 检查“混合修订版本”
svn log 显示一个文件或目录的历史修改信息。
$ svn checkout http://svn.example.com:9834/repos
$ svn checkout file:///path/to/repos
$ svn checkout file://localhost/path/to/repos
C:\> svn checkout file:///X:/path/to/repos
$ svn checkout http://svn.example.com/repos/calc
$ svn commit button.c -m "Fixed a typo in button.c."
$ svn update
Subversion可以通过多种方式访问版本库!!
———— 一个重要意思是说:各种svn的子命令,都可以直接访问版本库。如果你在版本库机器上,那么就用file:///,如果你在自己机器上,就用下面四种方式。
———— 但是,你怎么只操作本地的工作拷贝呢,以为你想在本地改,改完后再commit,那就不要加这些东西了,直接是文件名,或者不加就好。
模式 访问方法
file:/// 直接版本库访问(本地磁盘)
http:// 通过配置Subversion的Apache服务器的WebDAV协议
https:// 与http://相似,但是包括SSL加密。
svn:// 通过svnserve服务自定义的协议
svn+ssh:// 与svn://相似,但通过SSH封装。
但是注意:一般的做法,还是先update,merge,copy等命令把版本库数据弄到工作拷贝,修改好工作拷贝后,再commit,这会导致版本库的进入下一个revision。
如果直接在版本库中操作的,比如copy,delete等,也会使得版本库进入下一个reversion.
第二章 基本使用
svn help SUBCOMMAND
1. 创建版本库
创建新的版本库名 newrepos
$ svnadmin create /usr/local/svn/newrepos
将mytree下未版本化的文件,导入该版本库,文件放在some/project下,没有mytree文件夹了。
$ svn import mytree file:///usr/local/svn/newrepos/some/project -m "Initial import"
注意:import后的路径,可以在版本库上,也可以在自己机器上。一般,可以在自己机器上的代码,直接这样导入服务器上的版本库。
列出newrepos版本库中某文件夹下的文件。
$ svn list file:///usr/local/svn/newrepos/some/project
推荐的版本库布局: 这里有paint和calc两个项目
$ svn list file:///usr/local/svn/repos
/paint/trunk
/paint/branches
/paint/tags
/calc/trunk
/calc/branches
/calc/tags
/vender/vendorlib1/current #供方最新版本
/vender/vendorlib1/1.0 #上次最新版本的快照(svn copy)
注意:文件夹用 svn mkdir 创建。看你在版本库主机上还是工作拷贝主机上,选用上面某一个URL可以对版本库进行操作。
2. 初始化检出
在本地创建一个工作拷贝,这个拷贝可能仅仅是一个版本库中的一个文件夹。
$ svn checkout http://svn.collab.net/repos/svn/trunk
同上,只不过,在本地创建的是subv目录,而不是trunk目录。
$ svn checkout http://svn.collab.net/repos/svn/trunk subv
3. 基本的工作周期
更新你的工作拷贝
svn update
做出修改: 只有修改一个文件,可以直接修改,其他任何对文件和目录的操作,均是先通过以下命令在工作拷贝生效,然后,你下次update的时候,才在版本库生效。这种状态,常见英文为: sth is scheduled for addition.(比如这个文件是add处理的)
svn add foo (先vim foo,然后用add命令添加)
svn delete foo (但是只有在update时,版本库和工作拷贝内的删除才会生效)
svn copy foo bar
svn move foo bar
svn mkdir foo (等于mkdir foo; snv add foo)
检验修改: 在你提交你的工作拷贝时,检查一下修改了哪些东西是个好习惯,这不需要网络(不需要和版本库比对)
svn status (浏览所做的修改,传说中用的最多的命令)
-v 显示所有文件
-u 这个文件常用于预测update前的冲突。会联系版本库,看是否工作拷贝里,有过期的文件,以 * 标注。
可能的状态码为:
A # file is scheduled for addition
C # file has textual conflicts from an update
D # file is scheduled for deletion
M # the content in bar.c has local modifications
? # 这个文件还没有版本化,你需要用svn add
svn diff (检查修改的详细信息,通过 svn diff > patchfile 可以生成补丁文件)
可能会取消一些修改
svn revert (任何修改,然后用revert就可以撤销任何schedule 的操作,即上次update以来 schedule的操作)
解决冲突(合并别人的修改)
svn update (只要版本库被别人修改,你铁定不能commit,必须update一次,而update会尝试合并被人修改过的版本库和工作拷贝,生成新的工作拷贝,下面U表示更新(你没有修改,但是版本库被修改),G表示合并(你修改了,版本库也修改了,两者可以合并),C表示冲突(你修改了,版本库修改了,两者不能按照一定规则合并)。如果是C存在,那么你必须解决冲突,svn才能允许你下次commit。如果有冲突,那么对应bar.c 会有三个临时文件被生成,它们是bar.c.mine, bar.c.rR1(你上次update时的文件), bar.c.rR2(版本库中的最新版本号的文件)。这个三个临时文件,会阻止你commit。此时的svn只是要求你考虑下更改冲突的文件,如果考虑好了,用svn resolved bar.c来告诉svn,你改好了,然后resolved会自动删除这三个临时文件,然后,你再commit,就行了!所以,svn只是要求你考虑清楚后,删除这三个文件,它就可以commit了,不管你到底是不是真的改恰当了,这还是你的事,所以,如果不用svn resolved,而是直接删除这三个文件,也是可以commit的。另外,这三个文件,可以用于你查阅,如果愿意的话。
更改冲突文件,通常可以:直接编辑冲突文件;用bar.c.rR2(R2 > R1)来替换bar.c;或者直接用svn revert来恢复文件到上次update时的状态。如果是前面两种方法,要接上svn resolved来告诉svn,冲突已解决好了,可以允许自己commit了。
$ svn update
U INSTALL
G README
C bar.c
Updated to revision 46.
svn resolved
提交你的修改
只有当你的工作拷贝的版本号=版本库版本号时,才能提交成功!!!(小于就得先update,大于是不可能的)
svn commit -m "message"/ -F logfile
4. 检验历史:
(1) svn log (从下面就看出每次 commit时 -m 的重要性了,对于追溯很重要)
$ svn log
------------------------------------------------------------------------
r3 | sally | Mon, 15 Jul 2002 18:03:46 -0500 | 1 line
Added include lines and corrected # of cheese slices.
------------------------------------------------------------------------
r2 | harry | Mon, 15 Jul 2002 17:47:57 -0500 | 1 line
Added main() methods.
------------------------------------------------------------------------
r1 | sally | Mon, 15 Jul 2002 17:40:08 -0500 | 1 line
Initial import
------------------------------------------------------------------------
$ svn log -r 5:19 # shows logs 5 through 19 in chronological order
$ svn log -r 19:5 # shows logs 5 through 19 in reverse order
$ svn log -r 8 # shows log for revision 8
$ svn log foo.c
$ svn log http://foo.com/svn/trunk/code/foo.c
注意:
a. 加上 -v 能显示文件和目录的移动和复制 等 “路径修改” 信息,这可以用于找出删除的项目。
b. 如果 svn log -r 8 没有输出信息,那请在后面跟出版本库的路径。
(2) svn diff
$ svn diff #比较工作拷贝和.svn中存的上次update时的版本。
$ svn diff -r 3 rules.txt #比较工作拷贝和版本库3版
$ svn diff -r 2:3 rules.txt #比较版本库的2版和3版。
(3) svn cat
$ svn cat -r 2 rules.txt #查看版本库2版
(4) svn list
缺省列出的是当前工作拷贝对应的版本库的URL下的东西,当然,你可以直接写出版本库。
$ svn list -v http://svn.collab.net/repos/svn
5. 时间机: ———— 注意:版本库没有回退的概念,只有你的工作拷贝有回退的概念!!
(1) 将工作拷贝,回退到以往某个版本: ———— 但注意,根据commit原理,这种回退的工作拷贝,你是无法commit的了。如果,你原意是想回退后,以后还能继续提交,那应该用svn merge!!!!
$ svn checkout -r 1729 # Checks out a new working copy at r1729
$ svn update -r 1729 # Updates an existing working copy to r1729
(2) 将版本库的1729版本打包。
$ svn export http://svn.example.com/svn/repos1 -r 1729
6. 清理:
status发现有L的,表明是锁住了,这通常是由于svn上次要修改,所以锁住,但是由于某种原因,进程退出了,所以锁还在,这是用cleanup命令清除即可。
$ svn status
L somedir
M somedir/foo.c
$ svn cleanup
$ svn status
M somedir/foo.c
第三章 高级主题
1. 去掉未版本化的文件:
你当然不想让临时文件扰乱版本库,也让svn方法有svn自身提供的两种方法,见这一章相应部分。我还是觉得复杂。
那就当svn add 和 import时,后面显示指定出文件名,而不是默认让它递归查找所有的文件和目录。
2. 锁定:
如果你想当你update后,就能锁住某些版本库的话。这样使得一些非文本的对象,不会由于交替操作发生错误,如图像处理。
3. 其他很多高级主题。
见章节中。
第四章 分支与合并
当你创建了一个版本库时(svnadmin create),里面的目录和文件可以用 svn copy 或者 svn move来移动。但不能在不同版本库间移动。
1. 分支:
“分支”,没有被SVN赋予特殊的意义,我们称用 svn copy 后的新的目录,为一个分支,但对于svn来讲,只是一个普通目录。
(1) 创建分支:
由于分支是对版本库的操作,所以copy后的两个参数都是版本库URL,如果你在版本库机器上,用file:///一样。
$ svn copy http://svn.example.com/repos/calc/trunk \
http://svn.example.com/repos/calc/branches/my-calc-branch \
-m "Creating a private branch of /calc/trunk."
上面,也说明了分支放置的地方,在branches里,新建一个my-calc-branch。以后,也可以在branches里建立其他新的分支。
(2) 版本号还是往前走!
由于svn的一个版本库内部没有“分支”的内建概念,所以不管哪个“分支”被commit了,都会导致版本库的版本号往前走。
2. 合并:
“合并”,即 svn merge 做的事,就是比较版本库中两个版本的区别,并把区别应用于你的工作拷贝。这没有什么限制,两个版本可以是任何两个版本,你的工作拷贝,也可以是你本地的任何一个“分支”。但是,这个命令拿来干嘛呢?? 就是为了使得两个“分支”能够合并一些共同的东西,比如一个分支做了一些错误修改,另一个分支也应该跟着改。但是,这些修改是通过比较哪两个版本,这就要靠你自己判断了,一般是看别人用commit -m参数,提交的说明,比如它说,这次提交,修改了xxx,那你就可以比较这次提交和上次版本的差别,然后应用于自己的工作拷贝,这就是所谓的“合并”。
如果不能合并,不能合并的文件前,会打印C,就同update一样。这往往表明,你选了错误的两个版本作比较,使得差别不能应用于拷贝。这时,可用svn revert --recursive恢复修改。
另外,merge必须经常做,如果你打算维护两个相关的分支的话。否则,长时间不做,merge起来,会冲突很多,不好解决。
下面是些用法:
$ svn merge -c 344 http://svn.example.com/repos/calc/trunk #默认合并到当前工作目录
$ svn merge -c 344 http://svn.example.com/repos/calc/trunk my-calc-branch
$ svn merge http://svn.example.com/repos/branch1@150 \ #@后面接版本号
http://svn.example.com/repos/branch2@212 \
my-working-copy
$ svn merge -r 100:200 http://svn.example.com/repos/trunk my-working-copy
$ svn merge -r 100:200 http://svn.example.com/repos/trunk
不太懂 --ignore-ancestry 的用途,这个用于比较两个不相关的目录树
3. 典型例子:
(1) 把某个branch合并到trunk
先进入工作拷贝里的trunk目录,使得后面svn merge是针对这个工作拷贝。
$ cd calc/trunk
$ svn update
At revision 405.
用于merge的两个版本库版本,是my-calc-branch诞生时的版本341和目前的版本405。注意,这仅仅是对第一次merge,但是如果你后来又修改了分支,你还想merge,那显然,从406到目前的版本即可。最新版本,可用HEAD代替。不知道,就看上面svn update,告诉你最新是405。或者用svn log查询。
$ svn merge -r 341:405 http://svn.example.com/repos/calc/branches/my-calc-branch
U integer.c
U button.c
U Makefile
检查一下状态,不要有冲突的
$ svn status
M integer.c
M button.c
M Makefile
# ...examine the diffs, compile, test, etc...
然后提交,就可以完成合并到trunk。
$ svn commit -m "Merged my-calc-branch changes r341:405 into the trunk."
Sending integer.c
Sending button.c
Sending Makefile
Transmitting file data ...
Committed revision 406.
(2) 恢复工作拷贝到以前版本:
注意是303后面跟302,后面的小,这样就能够使得工作拷贝,回到以前的版本!! 这样之后,是可以commit的,这不同于用checkout和update得到的以前版本,这种情况下无法commit.
$ svn merge -r 303:302 http://svn.example.com/repos/calc/trunk
U integer.c
$ svn status
M integer.c
$ svn diff
…
# verify that the change is removed
…
$ svn commit -m "Undoing change committed in r303."
Sending integer.c
Transmitting file data .
Committed revision 350.
(3) 恢复单个文件到以前版本:
如果你想对单个文件回溯? 那还是用svn copy把,它能够直接把某个版本的文件拷贝到你当前的工作拷贝。如:
拷贝807版本的real.c到工作拷贝的当前目录
$ svn copy -r 807 \
http://svn.example.com/repos/calc/trunk/real.c ./real.c
查看状态
$ svn status
A + real.c
提交修改
$ svn commit -m "Resurrected real.c from revision 807, /calc/trunk/real.c."
Adding real.c
Transmitting file data .
Committed revision 1390.
(4) 当然,你可以选择直接在版本库中恢复文件和文件夹:
这所谓恢复,其实就是进入下一个revision,注意下面例子中revision号的变化。
比如你用这个删除了:
$ svn delete http://svn.example.com/repos/calc/branches/my-calc-branch \
-m "Removing obsolete branch of calc project."
Committed revision 375.
你可以用这个来恢复:
$ svn copy -r 374 http://svn.example.com/repos/calc/branches/my-calc-branch \
http://svn.example.com/repos/calc/branches/my-calc-branch
Committed revision 376.
4. 常用分支模式:
大多数软件存在这样一个生命周期:编码、测试、发布,然后重复。这样有两个问题,第一,开发者需要在质量保证小组测试假定稳定版本时继续开发新特性,新工作在软件测试时不可以中断,第二,小组必须一直支持老的发布版本和软件;如果一个bug在最新的代码中发现,它一定也存在已发布的版本中,客户希望立刻得到错误修正而不必等到新版本发布。
(1) 永久分支:
一般trunk做主分支(给开发者),branches做发布分支(给测试员测试,不允许添加特性,仅做bug修复),tag做打包分支(给用户):
这是版本控制可以做的帮助,典型的过程如下:
* 开发者提交所有的新特性到主干。 每日的修改提交到/trunk:新特性,bug修正和其他。
* 这个主干被拷贝到“发布”分支。 当小组认为软件已经做好发布的准备(如,版本1.0)然后/trunk会被拷贝到/branches/1.0。
* 项目组继续并行工作,一个小组开始对分支进行严酷的测试(一般只允许做bug修复性的提交),同时另一个小组在/trunk继续新的工作(如,添加新特性,准备2.0)。两边发现的bug,可以相互merge。注意,这种分支,也就是“维护某个版本”的由来。即不会添加新特性了,如果OK了,就放到tags里,然后以后有bug,也继续在分支改。
* 分支已经作了标签并且发布,当测试结束,/branches/1.0作为引用快照已经拷贝到/tags/1.0.0,这个标签被打包发布给客户。
* 分支多次维护。当继续在/trunk上为版本2.0工作,bug修正继续从/trunk运送到/branches/1.0,如果积累了足够的bug修正,管理部门决定发布1.0.1版本:拷贝/branches/1.0到/tags/1.0.1,标签被打包发布。
整个过程随着软件的成熟不断重复:当2.0完成,一个新的2.0分支被创建,测试、打标签和最终发布,经过许多年,版本库结束了许多版本发布,进入了“维护”模式,许多标签代表了最终的发布版本。
(2) 临时分支:
有时,可能做些trunk的临时分支,以开发某种特性,或做较大更改。
这些分支,一旦开发结束,就merge入主分支,并删除自己。
(3) 供方分支:
显然,我们需要用到别人提供的库。但是,我们怎么用呢?一般是,放到我们的版本库中,准备一个current目录,将其放入,然后做快照到如1.0。然后拷贝到brunch中做使用和定制。然后,一旦有新版本发布,我们想用的话,把最新版本替换current,然后和1.0做比较,用merge将修改应用于brunch中的版本。
目录结构为:
在repos根目录下,再创建一个vender目录,库名叫libcomplex比如,就创建libcomplex目录,在里面再创建current放最新版本,用1.0放上次版本的快照(svn copy)
-------- 从文档上摘抄的例子:---------
也许一个例子有助于我们阐述这个算法,我们会使用这样一个场景,我们的开发团队正在开发一个计算器程序,与一个第三方的复杂数字运算库libcomplex关联。我们从供方分支的初始创建开始,并且导入供方drop,我们会把每株分支目录叫做libcomplex,我们的代码drop会进入到供方分支的子目录current,并且因为svn import创建所有的需要的中间父目录,我们可以使用一个命令完成这一步。
$ svn import /path/to/libcomplex-1.0 \
http://svn.example.com/repos/vendor/libcomplex/current \
-m 'importing initial 1.0 vendor drop'
…
我们现在在/vendor/libcomplex/current有了libcomplex当前版本的代码,现在我们为那个版本作标签(见“标签”一节),然后拷贝它到主要开发分支,我们的拷贝会在calc项目目录创建一个新的目录libcomplex,它是这个我们将要进行自定义的供方数据的拷贝版本。
$ svn copy http://svn.example.com/repos/vendor/libcomplex/current \
http://svn.example.com/repos/vendor/libcomplex/1.0 \
-m 'tagging libcomplex-1.0'
…
$ svn copy http://svn.example.com/repos/vendor/libcomplex/1.0 \
http://svn.example.com/repos/calc/libcomplex \
-m 'bringing libcomplex-1.0 into the main branch'
…
我们取出我们项目的主分支—现在包括了第一个供方释放的拷贝—我们开始自定义libcomplex的代码,在我们知道之前,我们的libcomplex修改版本是已经与我们的计算器程序完全集成了。 [23]
几周之后,libcomplex得开发者发布了一个新的版本—版本1.1—包括了我们很需要的一些特性和功能。我们很希望升级到这个版本,但不希望失去在当前版本所作的修改。我们本质上会希望把我们当前基线版本是的libcomplex1.0的拷贝替换为libcomplex 1.1,然后把前面自定义的修改应用到新的版本。但是实际上我们通过一个相反的方向解决这个问题,应用libcomplex从版本1.0到1.1的修改到我们修改的拷贝。
为了执行这个升级,我们取出一个我们供方分支的拷贝,替换current目录为新的libcomplex 1.1的代码,我们只是拷贝新文件到存在的文件上,或者是解压缩libcomplex 1.1的打包文件到我们存在的文件和目录。此时的目标是让我们的current目录只保留libcomplex 1.1的代码,并且保证所有的代码在版本控制之下,哦,我们希望在最小的版本控制历史扰动下完成这件事。
完成了这个从1.0到1.1的代码替换,svn status会显示文件的本地修改,或许也包括了一些未版本化或者丢失的文件,如果我们做了我们应该做的事情,未版本化的文件应该都是libcomplex在1.1新引入的文件—我们运行svn add来将它们加入到版本控制。丢失的文件是存在于1.1但是不是在1.1,在这些路径我们运行svn delete。最终一旦我们的current工作拷贝只是包括了libcomplex1.1的代码,我们可以提交这些改变目录和文件的修改。
我们的current分支现在保存了新的供方drop,我们为这个新的版本创建一个新的标签(就像我们为1.0版本drop所作的),然后合并这从个标签前一个版本的区别到主要开发分支。
$ cd working-copies/calc
$ svn merge http://svn.example.com/repos/vendor/libcomplex/1.0 \
http://svn.example.com/repos/vendor/libcomplex/current \
libcomplex
… # resolve all the conflicts between their changes and our changes
$ svn commit -m 'merging libcomplex-1.1 into the main branch'
…
5. 聪明的switch
svn switch可以把工作拷贝的全部或部分,逻辑上转换为 其他分支 的工作拷贝! 这是继前面,一个工作拷贝中可以混合不同版本后,又一种混合方式 ———— 混合不同的分支的部分。 它表现得像update!!
举个例子,你的工作拷贝目录是/calc/trunk,你已经做了很多修改,然后你突然发现应该在分支上修改更好,没问题!你可以使用svn switch使得工作拷贝逻辑上变为分支的工作拷贝,而你本地修改还会保留,你可以测试并提交它们到分支。
如果服务器做了svn move造成文件移动,那么 svn update会?? 必须用svn switch??
6. 建立标签:
标签就是一次正式发行打包。这里再次说明了:标签和分支一样,都是人为的概念,对于svn来说,都是用svn copy制造的“快照”。比如标签就是从trunk拷贝到tags,或者从测试的branch拷贝到tags。
但是,需要用一种方法,来禁止用户提交commit,因为标签概念上,不允许修改了,只是历史沉淀。
怎么做呢??? svn+ssh的话,用基本文件系统的权限来解决吧。
http://hi.baidu.com/00%C6%F3%B6%EC/blog/item/c2bb0245d00dd03786947321.html
、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、
Subversion有一个很标准的目录结构,是这样的。
比如项目是proj,svn地址为svn://proj/,那么标准的svn布局是svn://proj/|+-trunk+-branches+-tags
这是一个标准的布局,trunk为主开发目录,branches为分支开发目录,tags为tag存档目录(不允许修改)。但是具体这几个目录应该如何使用,svn并没有明确的规范,更多的还是用户自己的习惯。
对于这几个开发目录,一般的使用方法有两种。我更多的是从软件产品的角度出发(比如freebsd),因为互联网的开发模式是完全不一样的。 1.第一种方法,使用trunk作为主要的开发目录
一般的,我们的所有的开发都是基于trunk进行开发,当一个版本/release开发告一段落(开发、测试、文档、制作安装程序、打包等)结束后,代码处于冻结状态(人为规定,可以通过hook来进行管理)。此时应该基于当前冻结的代码库,打tag。当下一个版本/阶段的开发任务开始,继续在trunk进行开发。
此时,如果发现了上一个已发行版本(Released Version)有一些bug,或者一些很急迫的功能要求,而正在开发的版本(Developing Version)无法满足时间要求,这时候就需要在上一个版本上进行修改了。应该基于发行版对应的tag,做相应的分支(branch)进行开发。
例如,刚刚发布1.0,正在开发2.0,此时要在1.0的基础上进行bug修正。
按照时间的顺序
1.0开发完毕,代码冻结
基于已经冻结的trunk,为release1.0打tag
此时的目录结构为
svn://proj/
+trunk/ (freeze)
+branches/
+tags/
+tag_release_1.0 (copy from trunk)
2.0开始开发,trunk此时为2.0的开发版
发现1.0有bug,需要修改,基于1.0的tag做branch
此时的目录结构为
svn://proj/
+trunk/ ( dev 2.0 )
+branches/
+dev_1.0_bugfix (copy from tag/release_1.0)
+tags/
+release_1.0 (copy from trunk)
在1.0 bugfix branch进行1.0 bugfix开发,在trunk进行2.0开发
在1.0 bugfix 完成之后,基于dev_1.0_bugfix的branch做release等
根据需要选择性的把dev_1.0_bugfix这个分支merge回trunk(什么时候进行这步操作,要根据具体情况)
这是一种很标准的开发模式,很多的公司都是采用这种模式进行开发的。trunk永远是开发的主要目录。
2.第二种方法,在每一个release的branch中进行各自的开发,trunk只做发布使用。
这种开发模式当中,trunk是不承担具体开发任务的,一个版本/阶段的开发任务在开始的时候,根据已经release的版本做新的开发分支,并且基于这个分支进行开发。还是举上面的例子,这里面的时序关系是:
1.0开发,做dev1.0的branch
此时的目录结构
svn://proj/
+trunk/ (不担负开发任务 )
+branches/
+dev_1.0 (copy from trunk)
+tags/
1.0开发完成,merge dev1.0到trunk
此时的目录结构
svn://proj/
+trunk/ (merge from branch dev_1.0)
+branches/
+dev_1.0 (开发任务结束,freeze)
+tags/
根据trunk做1.0的tag
此时的目录结构
svn://proj/
+trunk/ (merge from branch dev_1.0)
+branches/
+dev_1.0 (开发任务结束,freeze)
+tags/
+tag_release_1.0 (copy from trunk)
1.0开发,做dev2.0分支
此时的目录结构
svn://proj/
+trunk/
+branches/
+dev_1.0 (开发任务结束,freeze)
+dev_2.0 (进行2.0开发)
+tags/
+tag_release_1.0 (copy from trunk)
1.0有bug,直接在dev1.0的分支上修复
-----------------------------------------------------------------------------------------------------------------------------
eclipse svn 和putty协同合作的情况下merge:
1.eclipse: 右键->team->切换到trunk
右键->team->合并->按默认选项进行就好(参考:http://php.sabscape.com/blog/?p=413)
2.putty : svn sw ^/trunk
3.eclipse: team->提交
4.putty:svn update
eclipse 更新(可选,更新可能会更安全~)