Subversion有许多特性、选项和华而不实的高级功能,但日常的工作中� 只使用其中的一小部分,有一些只在特殊情况才会使用,在这一节里,我们会介绍许多� 在日常工作中常见的命令。
典型的工作周期是这� �的:
更新� 的工作拷贝
svn update
做出修改
svn add
svn delete
svn copy
svn move
检验修改
svn status
svn diff
svn revert
合并别人的修改到工作拷贝
svn update
svn resolved
提交� 的修改
svn commit
当� 在一个团队的项目里工作时,� 希望更新� 的工作拷贝得到所有其他人这段时间作出的修改,使用svn update让� 的工作拷贝与最新的版本同步。
$ svn update U foo.c U bar.c Updated to revision 2.
这种情况下,其他人在� 上次更新之后提交了对foo.c
和bar.c
的修改,� 此Subversion更新� 的工作拷贝来引入这些更改。
让我们认真检查svn update的输出,当服务器发送修改到� 的工作拷贝,一个字母显示在每一个项目之前,来让� 知道Subversion对� 的工作拷贝做了什么操作:
U foo
文件foo
更新了(从服务器收到修改)。
A foo
文件或目录foo
被添� 到工作拷贝。
D foo
文件或目录foo
在工作拷贝被� 除了。
R foo
文件或目录foo
在工作拷贝已经被替换了,这是说,foo
被� 除,而一个新的同� �名字的项目添� 进来,它们具有同� �的名字,但是版本库会把它们看作具备不同历史的不同对象。
G foo
文件foo
接收到版本库的更改,� 的本地版本也已经修改,但改变没有互相影响,Subversion成功的将版本库和本地文件合并,没有发生任何问题。
C foo
文件foo
的修改与服务器冲突,服务器的修改与� 的修改交迭在一起,不要恐慌,这种冲突需要人(� )来解决,我们在后面的� 节讨论这种情况。
现在� 可以开始工作并且修改� 的工作拷贝了,� 很容易决定作出一个修改(或者是一组),像写一个新的特性,修正一个错误等等。这时可以使用的Subversion命令包括svn add、 svn delete、svn copy和svn move。如果� 只是修改版本库中已经存在的文件,在� 提交之前,不必使用上面的任何一个命令。� 可以对工作备份作的修改包括:
这是最简单的一种修改,� 不必告诉Subversion� 想修改哪一个文件,只需要去修改,然后Subversion会自动地探测到哪些文件已经更改了。
� 可以“� �记”目录或者文件为预定要� 除、增� 、复制或者移动,也许这些改动在� 的工作拷贝马上发生,而版本库只在� 提交的时候才发生改变。
修改文件,可以使用文本编辑器、字处理软件、图形程序或任何� 常用的工具,Subverion处理二进制文件像同文本文件一� �—效率也一� �。
这些是常用的可以修改目录� �结构的子命令(我们会在后面包括svn import和svn mkdir)。
� 可以使用任何� 喜欢的工具编辑文件,但� 不可以在修改目录结构时不通知Subversion,需要使用svn copy、svn delete和svn move命令修改工作拷贝的结构,使用svn add增� 版本控制的新文件或目录。
预定将文件、目录或者符号链foo
添� 到版本库,当� 下次提交后,foo
会成为其父目录的一个子对象。注意,如果foo
是目录,所有foo中的内容也会预定添� 进去,如果� 只想添� foo
本身,使用--non-recursive
(-N
)参数。
预定将文件、目录或者符号链foo
从版本库中� 除掉,如果foo是文件,它马上从工作拷贝中� 除,如果是目录,不会被� 除,但是Subversion准备好� 除了,当� 提交� 的修改,foo
就会在� 的工作拷贝和版本库中被� 除。[2]
建立一个新的项目bar
作为foo
的复制品,当在下次提交时会将bar
添� 到版本库,这种拷贝历史会记录下来(按照来自foo
的方式记录),svn copy并不建立中介目录。
这个命令与与运行svn copy foo bar; svn delete foo完全相同,bar
作为foo
的拷贝准备添� ,foo
已经预定要被� 除,svn move不建立中介的目录。
当� 完成修改,� 需要提交他们到版本库,但是在此之前,检查一下做过什么修改是个好主意,通过提交前的检查,� 可以整理一份精确的日志信息,� 也可以发现� 不小心修改的文件,给了� 一次恢复修改的机会。此外,这是一个审查和仔细察看修改的好机会,� 可通过命令svn status、svn diff和svn revert精确地察看所做的修改。� 可以使用前两个命令察看工作拷贝中的修改,使用第三个来撤销部分(或全部)的修改。
Subversion已经被优化来帮助� 完成这个任务,可以在不与版本库通讯的情况下做许多事情,详细来说,对于每一个文件,� 的的工作拷贝在.svn
包含了一个“原始的”拷贝,所以Subversion可以快速的告诉� 那些文件修改了,甚至允许� 在不与版本库通讯的情况下恢复修改。
相对于其他命令,� 会更多地使用这个svn status命令。
如果� 在工作拷贝的顶级目录运行不带参数的svn status命令,它会检测� 做的所有的文件或目录的修改,以下的例子是来展示svn status可能返回的状态� �(注意,#
之后的不是svn status打印的)。
L abc.c # svn已经在.svn目录锁定了abc.c M bar.c # bar.c的内容已经在本地修改过了 M baz.c # baz.c属性有修改,但没有内容修改 X 3rd_party # 这个目录是外部定义的一部分 ? foo.o # svn并没有管理foo.o ! some_dir # svn管理这个,但它可能丢失或者不完整 ~ qux # 作为file/dir/link进行了版本控制,但类型已经改变 I .screenrc # svn不管理这个,配置确定要忽略它 A + moved_dir # 包含历史的添加,历史记录了它的来历 M + moved_dir/README # 包含历史的添加,并有了本地修改 D stuff/fish.c # 这个文件预定要删除 A stuff/loot/bloo.h # 这个文件预定要添加 C stuff/loot/lump.c # 这个文件在更新时发生冲突 R xyz.c # 这个文件预定要被替换 S stuff/squawk # 这个文件已经跳转到了分支
在这种� �式下,svn status打印五列字符,紧跟一些空� �,接着是文件或者目录名。第一列告诉一个文件的状态或它的内容,返回代� �解释如下:
A item
文件、目录或是符号链item
预定� 入到版本库。
C item
文件item
发生冲突,在从服务器更新时与本地版本发生交迭,在� 提交到版本库前,必须手工的解决冲突。
D item
文件、目录或是符号链item
预定从版本库中� 除。
M item
文件item
的内容被修改了。
R item
文件、目录或是符号链item
预定将要替换版本库中的item
,这意味着这个对象首先要被� 除,另外一个同名的对象将要被添� ,所有的操作发生在一个修订版本。
X item
目录没有版本化,但是与Subversion的外部定义关联,关于外部定义,可以看“外部定义”一节。
? item
文件、目录或是符号链item
不在版本控制之下,� 可以通过使用svn status的--quiet
(-q
)参数或父目录的svn:ignore
属性忽略这个问题,关于忽略文件的使用,见“svn:ignore
”一节。
! item
文件、目录或是符号链item
在版本控制之下,但是已经丢失或者不完整,这可能� 为使用非Subversion命令� 除� 成的,如果是一个目录,有可能是检出或是更新时的中断� 成的,使用svn update可以重新从版本库获得文件或者目录,也可以使用svn revert file恢复原来的文件。
~ item
文件、目录或是符号链item
在版本库已经存在,但� 的工作拷贝中的是另一个。举一个例子,� � 除了一个版本库的文件,新建了一个在原来的位置,而且整个过程中没有使用svn delete或是svn add。
I item
文件、目录或是符号链item
不在版本控制下,Subversion已经配置好了会在svn add、svn import和svn status命令忽略这个文件,关于忽略文件,见“svn:ignore
”一节。注意,这个符号只会在使用svn status的参数--no-ignore
时才会出现—否则这个文件会被忽略且不会显示!
第二列说明文件或目录的属性的状态(更多细节可以看“属性”一节),如果一个M
出现在第二列,说明属性被修改了,否则显示空白。
第三列只显示空白或者L
,L
表示Subversion已经在.svn
工作区域锁定了这个项目,当� 的svn commit正在运行的时候—也许正在输入log信息,运行svn status� 可以看到L
� �记,如果这时候Subversion并没有运行,可以推测Subversion发生中断并且已经锁定,� 必须运行svn cleanup来清除锁定(本节后面将有更多论述)。
第四列只会显示空白或+
,+
的意思是一个有附� 历史信息的文件或目录预定添� 或者修改到版本库,通常出现在svn move或是svn copy时,如果是看到A� � +
就是说要包含历史的增� ,它可以是一个文件或是拷贝的� �目录。+
表示它是即将包含历史增� 到版本库的目录的一部分,也就是说他的父目录要拷贝,它只是跟着一起的。 M� � +
表示将要包含历史的增� ,并且已经更改了。当� 提交时,首先会随父目录进行包含历史的增� ,然后本地的修改提交到更改后的版本。
第五列只显示空白或是S
,表示这个目录或文件已经转到了一个分支下了(使用svn switch)。
如果� � 递一个路径给svn status,它只给� 这个项目的信息:
$ svn status stuff/fish.c D stuff/fish.c
svn status也有一个--verbose
(-v
)选项,它可以显示工作拷贝中的所有项目,即使没有改变过:
$ svn status --verbose M 44 23 sally README 44 30 sally INSTALL M 44 20 harry bar.c 44 18 ira stuff 44 35 harry stuff/trout.c D 44 19 ira stuff/fish.c 44 21 sally stuff/things A 0 ? ? stuff/things/bloo.h 44 36 harry stuff/things/gloo.c
这是svn status的“� 长形式”,第一列保持相同,第二列显示一个工作版本号,第三和第四列显示最后一次修改的版本号和修改人。
上面所有的svn status调用并没有联系版本库,只是与.svn
中的元数据进行比较的结果,最后,是--show-updates
(-u
)参数,它将会联系版本库为已经过时的数据添� 新信息:
$ svn status --show-updates --verbose M * 44 23 sally README M 44 20 harry bar.c * 44 35 harry stuff/trout.c D 44 19 ira stuff/fish.c A 0 ? ? stuff/things/bloo.h Status against revision: 46
注意这两个星号:如果� 现在执行svn update,� 的README
和trout.c
会被更新,这告诉� 许多有用的信息—� 可以在提交之前,需要使用更新操作得到文件README
的更新,或者说文件已经过时,版本库会拒绝了� 的提交。(后面还有更多关于此主题)。
另一种检查修改的方式是svn diff命令,� 可以通过不带参数的svn diff精确的找出� 所做的修改,这会输出统一区别� �式:[3]
$ svn diff Index: bar.c =================================================================== --- bar.c (revision 3) +++ bar.c (working copy) @@ -1,7 +1,12 @@ +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <stdio.h> int main(void) { - printf("Sixty-four slices of American Cheese...\n"); + printf("Sixty-five slices of American Cheese...\n"); return 0; } Index: README =================================================================== --- README (revision 3) +++ README (working copy) @@ -193,3 +193,4 @@ +Note to self: pick up laundry. Index: stuff/fish.c =================================================================== --- stuff/fish.c (revision 1) +++ stuff/fish.c (working copy) -Welcome to the file known as 'fish'. -Information on fish will be here soon. Index: stuff/things/bloo.h =================================================================== --- stuff/things/bloo.h (revision 8) +++ stuff/things/bloo.h (working copy) +Here is a new file to describe +things about bloo.
svn diff命令通过比较� 的文件和.svn
的“原始”文件来输出信息,预定要增� 的文件会显示所有增� 的文本,要� 除的文件会显示所有要� 除的文本。
输出的� �式为统一区别� �式(unified diff format),� 除的行前面� 一个-
,添� 的行前面有一个+
,svn diff命令也打印文件名和打补丁需要的信息,所以� 可以通过重定向一个区别文件来生成“补丁”:
$ svn diff > patchfile
举个例子,� 可以把补丁文件发送邮件到其他开发者,在提交之前审� �和测试。
假设� 通过上面的diff输出发现� 不小心用编辑器在README
中输入了一些字符。
这是使用svn revert的好机会。
$ svn revert README Reverted 'README'
Subversion把文件恢复到未修改的状态,叫做.svn
目录的“原始”拷贝,应该知道svn revert可以恢复任何预定要做的操作,举个例子,� 不再想添� 一个文件:
$ svn status foo ? foo $ svn add foo A foo $ svn revert foo Reverted 'foo' $ svn status foo ? foo
svn revert ITEM
的效果与� 除ITEM
然后执行svn update -r BASE ITEM
完全一� �,但是,如果� 使用svn revert它不必通知版本库就可以恢复文件。
或许� 不小心� 除了一个文件:
$ svn status README README $ svn delete README D README $ svn revert README Reverted 'README' $ svn status README README
我们可以使用svn status -u来预测冲突,当� 运行svn update一些有趣的事情发生了:
$ svn update U INSTALL G README C bar.c Updated to revision 46.
U
和G
没必要关心,文件干净的接受了版本库的变化,文件� �示为U
表明本地没有修改,文件已经� �据版本库更新。G
� �示合并,� �示本地已经修改过,与版本库没有重迭的地方,已经合并。
但是C
表示冲突,说明服务器上的改动同� 的改动冲突了,� 需要自己手工去解决。
当冲突发生了,有三件事可以帮助� 注意到这种情况和解决问题:
Subversion打印C
� �记,并且� �记这个文件已冲突。
如果Subversion认为这个文件是可合并的,它会置入冲突� �记—特殊的横线分开冲突的“两面”—在文件里可视化的描述重� 的部分(Subversion使用svn:mime-type
属性来决定一个文件是否可以使用上下文的,以行为基础合并,更多信息可以看“svn:mime-type
”一节)。
对于每一个冲突的文件,Subversion放置三个额外的未版本化文件到� 的工作拷贝:
filename.mine
� 更新前的文件,没有冲突� �志,只是� 最新更改的内容。(如果Subversion认为这个文件不可以合并,.mine
文件不会创建,� 为它和工作文件相同。)
filename.rOLDREV
这是� 的做更新操作以前的BASE
版本文件,就是� 在上次更新之后未作更改的版本。
filename.rNEWREV
这是� 的Subversion客户端从服务器刚刚收到的版本,这个文件对应版本库的HEAD
版本。
这里OLDREV
是� 的.svn
目录中的修订版本号,NEWREV
是版本库中HEAD
的版本号。
举一个例子,Sally修改了sandwich.txt
,Harry刚刚改变了他的本地拷贝中的这个文件并且提交到服务器,Sally在提交之前更新它的工作拷贝得到了冲突:
$ svn update C sandwich.txt Updated to revision 2. $ ls -1 sandwich.txt sandwich.txt.mine sandwich.txt.r1 sandwich.txt.r2
在这种情况下,Subversion不会允许� 提交sandwich.txt
,直到� 的三个临时文件被� 掉。
$ svn commit --message "Add a few more things" svn: Commit failed (details follow): svn: Aborting commit: '/home/sally/svn-work/sandwich.txt' remains in conflict
如果� 遇到冲突,三件事� 可以选择:
“手动”合并冲突文本(检查和修改文件中的冲突� �志)。
用某一个临时文件覆盖� 的工作文件。
运行svn revert <filename>来放弃所有的修改。
一旦� 解决了冲突,� 需要通过命令svn resolved让Subversion知道,这� �就会� 除三个临时文件,Subversion就不会认为这个文件是在冲突状态了。[4]
$ svn resolved sandwich.txt Resolved conflicted state of 'sandwich.txt'
第一次尝试解决冲突让人感觉很害怕,但经过一点训练,它简单的像是骑着车子下坡。
这里一个简单的例子,由于不良的交流,� 和同事Sally,同时编辑了sandwich.txt
。Sally提交了修改,当� 准备更新� 的版本,冲突发生了,我们不得不去修改sandwich.txt
来解决这个问题。首先,看一下这个文件:
$ cat sandwich.txt Top piece of bread Mayonnaise Lettuce Tomato Provolone <<<<<<< .mine Salami Mortadella Prosciutto ======= Sauerkraut Grilled Chicken >>>>>>> .r2 Creole Mustard Bottom piece of bread
小于号、等于号和大于号串是冲突� �记,并不是冲突的数据,� 一定要确定这些内容在下次提交之前得到� 除,前两组� �志中间的内容是� 在冲突区所做的修改:
<<<<<<< .mine Salami Mortadella Prosciutto =======
后两组之间的是Sally提交的修改冲突:
======= Sauerkraut Grilled Chicken >>>>>>> .r2
通常� 并不希望只是� 除冲突� �志和Sally的修改—当她收到三明治时,会非常的吃惊。所以� 应该走到她的办公室或是拿起电话告诉Sally,� 没办法从从意大利熟食店得到想要的泡菜。[5]一旦� 们确认了提交内容后,修改文件并且� 除冲突� �志。
Top piece of bread Mayonnaise Lettuce Tomato Provolone Salami Mortadella Prosciutto Creole Mustard Bottom piece of bread
现在运行svn resolved,� 已经准备好提交了:
$ svn resolved sandwich.txt $ svn commit -m "Go ahead and use my sandwich, discarding Sally's edits."
记住,如果� 修改冲突时感到混乱,� 可以参考subversion生成的三个文件—包括� 未作更新的文件。� 也可以使用第三方的合并工具检验这三个文件。
如果� 只是希望取消� 的修改,� 可以仅仅拷贝Subversion为� 生成的文件替换� 的工作拷贝:
$ svn update C sandwich.txt Updated to revision 2. $ ls sandwich.* sandwich.txt sandwich.txt.mine sandwich.txt.r2 sandwich.txt.r1 $ cp sandwich.txt.r2 sandwich.txt $ svn resolved sandwich.txt
如果� 得到冲突,经过检查� 决定取消自己的修改并且重新编辑,� 可以恢复� 的修改:
$ svn revert sandwich.txt Reverted 'sandwich.txt' $ ls sandwich.* sandwich.txt
注意,当� 恢复一个冲突的文件时,不需要再运行svn resolved。
现在我们准备好提交修改了,注意svn resolved不像我们本� 学过的其他命令一� �需要参数,在任何� 认为解决了冲突的时候,只需要小心运行svn resolved,—一旦� 除了临时文件,Subversion会让� 提交这文件,即使文件中还存在冲突� �记。
最后!� 的修改结束了,� 合并了服务器上所有的修改,� 准备好提交修改到版本库。
svn commit命令发送所有的修改到版本库,当� 提交修改时,� 需要提供一些描述修改的日志信息,� 的信息会附到这个修订版本上,如果信息很简短,� 可以在命令行中使用--message
(-m
)选项:
$ svn commit --message "Corrected number of cheese slices." Sending sandwich.txt Transmitting file data . Committed revision 3.
然而,如果� 把写日志信息当作工作的一部分,� 也许会希望通过告诉Subversion一个文件名得到日志信息,使用--file
选项:
$ svn commit --file logmsg Sending sandwich.txt Transmitting file data . Committed revision 4.
如果� 没有指定--message
或者--file
选项,Subversion会自动地启动� 最喜欢的编辑器(见“config”一节的editor-cmd
部分)来编辑日志信息。
如果� 使用编辑器撰写日志信息时希望取消提交,� 可以直接关掉编辑器,不要保存,如果� 已经做过保存,只要简单的� 掉所有的文本并再次保存。
$ svn commit Waiting for Emacs...Done Log message unchanged or not specified a)bort, c)ontinue, e)dit a $
版本库不知道也不关心� 的修改作为一个整体是否有意义,它只检查是否有其他人修改了同一个文件,如果别人已经这� �做了,� 的整个提交会失败,并且提示� 一个或多个文件已经过时了:
$ svn commit --message "Add another rule" Sending rules.txt svn: Commit failed (details follow): svn: Out of date: 'rules.txt' in transaction 'g'
此刻,� 需要运行svn update来处理所有的合并和冲突,然后再尝试提交。
我们已经覆盖了Subversion基本的工作周期,还有许多其它特性可以管理� 得版本库和工作拷贝,但是只使用前面介绍的命令� 就可以很轻松的工作了。
[2] 当然没有任何东西是在版本库里被� 除了—只是在版本库的HEAD
里消失了,� 可以通过检出(或者更新� 的工作拷贝)� 做出� 除操作的前一个修订版本来找回所有的东西。
[3] Subversion使用内置区别引擎,缺省情况下输出为统一区别� �式。如果� 期望不同的输出� �式,� 可以使用--diff-cmd
指定外置的区别程序,并且通过--extensions
� 递其他参数,举个例子,察看本地文件foo.c
的区别,同时忽略空� �修改,� 可以运行svn diff --diff-cmd /usr/bin/diff --extensions '-bc' foo.c。
[4] � 也可以手工的� 除这三个临时文件,但是当Subversion会给� 做时� 会自己去做吗?我们是这� �想的。
[5] 如果� 向他们询问,他们非常有理由把� 带到城外的铁轨上。