4. 基本的工作循环

Subversion 有许多特性, 选项和华而不实的高级功能,但日常的工作中你只使用其中的一小部分,在这一节里,我们会介绍许多你在日常工作中常用的命令。

典型的工作周期是这样的:

  1. 更新你的工作副本。

    • svn update

  2. 做出修改

    • svn add

    • svn delete

    • svn copy

    • svn move

  3. 检验修改

    • svn status

    • svn diff

  4. 可能会取消一些修改

    • svn revert

  5. 解决冲突(合并别人的修改)

    • svn update

    • svn resolve

  6. 提交你的修改

    • svn commit

4.1. 更新你的工作副本

当你在一个团队的项目里工作时,你希望更新你的工作副本得到所有其他人这段时间作出的修改,使用 svn update 让你的工作副本与最新的版本同步:

$ svn update
U  foo.c
U  bar.c
Updated to revision 2.

这种情况下,其他人在你上次更新之后提交了对foo.cbar.c的修改,因此Subversion更新你的工作副本来引入这些更改。

当服务器通过 svn update 将修改传递到你的工作副本时,每一个项目之前会有一个字母,来让你知道 Subversion 为保持最新对你的工作副本作了哪些工作。关于这些字母的详细含义,可以执行 svn help update

4.2. 修改你的工作副本

现在你可以开始工作,并且修改你的工作副本了。你很容易决定作出一个修改(或者是一组),像写一个新的特性,修正一个错误等等。这时可以使用的 Subversion 命令包括 svn add, svn delete, svn copy, svn movesvn mkdir。如果你只是修改版本库中已经存在的文件,在你提交之前,不必使用上面的任何一个命令。

你可以对工作副本做出两种修改:文件修改目录树修改。你不需要告诉 Subversion 你希望修改一个文件,只需要用你的编辑器, 字处理器, 图形程序,或任何工具做出修改,Subversion 会自动检测到文件的修改。此外,二进制文件的处理方式和文本文件一样,也有同样的效率。对于目录树更改,你可以告诉 Subversion 将文件和目录标记为调度删除, 添加, 拷贝或移动。这些动作会在工作副本上立刻发生效果,但只有提交后才会在版本库里生效。

下面是 Subversion 用来修改目录树结构的五个最常用的子命令。

svn add foo

调度将文件, 目录或者符号链接 foo 添加到版本库。当你下次提交后,foo 会成为其父目录的一个子对象。注意,如果 foo 是目录,所有 foo 中的内容也会调度增加。如果你只想添加 foo 本身,请使用 --non-recursive (-N) 参数。

svn delete foo

调度将文件, 目录或者符号链接 foo 从版本库中删除,如果 foo 是文件或符号链接,它会马上从工作副本中删除。如果 foo 是目录,不会被删除,但是 Subversion 调度删除它。当你提交修改后,foo 就会在你的工作副本和版本库中被删除。[3]

svn copy foo bar

建立一个新条目 bar 作为 foo 的复制品,并且自动调度增加 bar,当在下次提交时会将 bar 添加到版本库,这种复制会记录下来历史(按照来自 foo 的方式记录)。如果不传递 --parentssvn copy 并不建立中介目录。

svn move foo bar

这个命令与与运行 svn copy foo bar; svn delete foo 完全相同,bar 作为 foo 的拷贝调度添加,foo 已经调度删除。如果不传递 --parentssvn move 不建立中介的目录。

svn mkdir blort

这个命令同运行 mkdir blort; svn add blort 相同,也就是创建一个叫做 blort 的文件,并且调度增加到版本库。

4.3. 检查你的修改

当你完成修改,你需要提交它们到版本库,但是在此之前,检查一下做过什么修改是个好主意。通过提交前的检查,你可以整理一份精确的日志信息。你也可以发现你不小心修改的文件,给了你一次撤销修改的机会。此外,这是一个在发布之前,复审和检查的好机会。你可通过命令 svn status 浏览所做的修改,通过命令 svn diff 检查修改的详细信息。

Subversion 已经被优化来帮助你完成这个任务,可以在不与版本库通讯的情况下做许多事情。详细来说,对于每一个文件,你的的工作副本在 .svn 包含了一个原始的副本,所以 Subversion 可以快速的告诉你哪些文件修改了,甚至允许你在不与版本库通讯的情况下恢复修改。

4.3.1. 查看你的修改概况

为了浏览你的修改,可以使用 svn status 命令。在所有的 Subversion 命令中,svn status 可能会是你使用最多的命令。

如果你在工作副本的顶级目录运行不带参数的 svn status 命令,它会检测你对所有文件或目录作出的修改。以下的例子是来展示 svn status 可能返回的状态代码(注意 # 之后的内容不是 svn status 打印的信息)。

?       scratch.c           # file is not under version control
A       stuff/loot/bloo.h   # file is scheduled for addition
C       stuff/loot/lump.c   # file has textual conflicts from an update
D       stuff/fish.c        # file is scheduled for deletion
M       bar.c               # the content in bar.c has local modifications

在这种输出格式中,svn status 打印 6 列字符,紧跟一些空格,接着是文件或目录名。第一列告诉文件或目录的状态或它的内容。返回代码如下:

A item

预定加入到版本库的文件, 目录或符号链的item

C item

文件 item 发生了冲突。从服务器收到的修改与工作副本的本地修改发生交迭(在更新期间不会被解决)。在你提交到版本库前,必须手工解决冲突。

D item

文件, 目录或是符号链item预定从版本库中删除。

M item

文件item的内容被修改了。

如果你传递一个路径给svn status,它只给你这个项目的信息:

$ svn status stuff/fish.c
D      stuff/fish.c

svn status也有一个--verbose(-v)选项,它可以显示工作副本中的所有项目,即使没有改变过的:

$ svn status -v
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 -u -v
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,你的 READMEtrout.c 会被更新。这告诉你许多有用的信息—你需要在提交之前,使用更新操作得到服务器中文件 README 的更新,否则服务器会说文件已经过时,拒绝你的提交(后面还有更多关于此主题的信息)。

svn status 可以比我们的展示显示更多关于文件和目录的内容—svn status 的完整描述可以参见svn status

4.3.2. 检查你的本地修改的详情

另一种检查修改的方式是 svn diff 命令。你可以通过不带参数的 svn diff 精确的找出你所做的修改,它会输出统一差异格式的修改信息:

$ 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原始文件来输出信息,预定要增加的文件会显示所有增加的文本,预定要删除的文件会显示所有要删除的文本。

输出的格式为统一差异格式。删除的行前面加一个 -,增加的行前面有一个 +svn diff 命令也打印文件名和补丁程序需要的位置信息,所以你可以通过重定向一个差异文件来生成补丁

$ svn diff > patchfile

举个例子,你可以通过邮件把补丁文件发送到其他开发者,在提交之前审核或测试。

Subversion 使用内置差异引擎,默认输出统一差异格式。如果你期望不同的输出格式,你可以使用 --diff-cmd 指定外置的比较程序,并且通过选项 --extensions (-x) 来传递其它参数。例如,察看本地文件 foo.c 的本地修改,同时忽略大小写差异,你可以运行 svn diff --diff-cmd /usr/bin/diff -x "-i" foo.c

4.4. 取消本地修改

假定我们在察看 svn diff 的输出,发现对某个文件的所有修改都是错误的。或许你根本不应该修改这个文件,或者是从开头重新修改会更加容易。

这是使用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

$ svn delete README
D         README

$ svn revert README
Reverted 'README'

$ svn status README

4.5. 解决冲突(合并别人的修改)

We've already seen how svn status -u can predict conflicts. Suppose you run svn update and some interesting things occur:

$ svn update
U  INSTALL
G  README
Conflict discovered in 'bar.c'.
Select: (p) postpone, (df) diff-full, (e) edit,
        (h) help for more options:

The U and G codes are no cause for concern; those files cleanly absorbed changes from the repository. The files marked with U contained no local changes but were Updated with changes from the repository. The G stands for merGed, which means that the file had local changes to begin with, but the changes coming from the repository didn't overlap with the local changes.

But the next two lines are part of a feature (new in Subversion 1.5) called interactive conflict resolution. This means that the changes from the server overlapped with your own, and you have the opportunity to resolve this conflict. The most commonly used options are displayed, but you can see all of the options by typing h:

…
  (p)  postpone    - mark the conflict to be resolved later
  (df) diff-full   - show all changes made to merged file
  (e)  edit        - change merged file in an editor
  (r)  resolved    - accept merged version of file
  (mf) mine-full   - accept my version of entire file (ignore their changes)
  (tf) theirs-full - accept their version of entire file (lose my changes)
  (l)  launch      - launch external tool to resolve conflict
  (h)  help        - show this list

在我们详细查看每个选项含义之前,让我们简短的回顾一下所有这些选项。

(p)ostpone

让文件在更新完成之后保持冲突状态。

(d)iff-(f)ull

使用标准区别格式显示base修订版本和冲突文件本身的区别。

(e)dit

用你喜欢的编辑器打开冲突的文件,编辑器是环境变量EDITOR设置的。

(r)esolved

After editing a file, tell svn that you've resolved the conflicts in the file and that it should accept the current contents—basically that you've resolved the conflict.

(m)ine-(f)ull

丢弃新从服务器接收的变更,并只使用你查看文件的本地修改。

(t)heirs-(f)ull

丢弃你对查看文件的本地修改,只使用从服务器新接收的变更。

(l)aunch

启动外部程序来执行冲突解决,这需要一些预先的准备。

(h)elp

显示所有在冲突解决时可能使用的命令。

我们现在会更详细的覆盖这些命令,根据关联功能对其进行分组。

4.5.1. 交互式的查看冲突区别

Before deciding how to attack a conflict interactively, odds are that you'd like to see exactly what is in conflict, and the diff-full command (df) is what you'll use for this:

…
Select: (p) postpone, (df) diff-full, (e) edit,
        (h)elp for more options : df
--- .svn/text-base/sandwich.txt.svn-base      Tue Dec 11 21:33:57 2007
+++ .svn/tmp/tempfile.32.tmp     Tue Dec 11 21:34:33 2007
@@ -1 +1,5 @@
-Just buy a sandwich.
+<<<<<<< .mine
+Go pick up a cheesesteak.
+=======
+Bring me a taco!
+>>>>>>> .r32
…

The first line of the diff content shows the previous contents of the working copy (the BASE revision), the next content line is your change, and the last content line is the change that was just received from the server (usually the HEAD revision). With this information in hand, you're ready to move on to the next action.

4.5.2. 交互式的解决冲突区别

There are four different ways to resolve conflicts interactively—two of which allow you to selectively merge and edit changes, and two of which allow you to simply pick a version of the file and move along.

If you wish to choose some combination of your local changes, you can use the edit command (e) to manually edit the file with conflict markers in a text editor (determined by the EDITOR environment variable). Editing the file by hand in your favorite text editor is a somewhat low-tech way of remedying conflicts (see 第 4.5.4 节 “手工合并冲突” for a walkthrough), so some people like to use fancy graphical merge tools instead.

To use a merge tool, you need to either set the SVN_MERGE environment variable or define the merge-tool-cmd option in your Subversion configuration file (see 第 1.3 节 “配置选项” for more details). Subversion will pass four arguments to the merge tool: the BASE revision of the file, the revision of the file received from the server as part of the update, the copy of the file containing your local edits, and the merged copy of the file (which contains conflict markers). If your merge tool is expecting arguments in a different order or format, you'll need to write a wrapper script for Subversion to invoke. After you've edited the file, if you're satisfied with the changes you've made, you can tell Subversion that the edited file is no longer in conflict by using the resolve command (r).

If you decide that you don't need to merge any changes, but just want to accept one version of the file or the other, you can either choose your changes (a.k.a. mine) by using the mine-full command (mf) or choose theirs by using the theirs-full command (tf).

4.5.3. 延后解决冲突

This may sound like an appropriate section for avoiding marital disagreements, but it's actually still about Subversion, so read on. If you're doing an update and encounter a conflict that you're not prepared to review or resolve, you can type p to postpone resolving a conflict on a file-by-file basis when you run svn update. If you're running an update and don't want to resolve any conflicts, you can pass the --non-interactive option to svn update, and any file in conflict will be marked with a C automatically.

The C stands for conflict. This means that the changes from the server overlapped with your own, and now you have to manually choose between them after the update has completed. When you postpone a conflict resolution, svn typically does three things to assist you in noticing and resolving that conflict:

  • Subversion在更新时打印C标记,并且标记这个文件已冲突。

  • If Subversion considers the file to be mergeable, it places conflict markers—special strings of text that delimit the sides of the conflict—into the file to visibly demonstrate the overlapping areas. (Subversion uses the svn:mime-type property to decide whether a file is capable of contextual, line-based merging. See 第 3.1 节 “文件内容类型” to learn more.)

  • 对于每一个冲突的文件,Subversion放置三个额外的未版本化文件到你的工作副本:

    filename.mine

    This is your file as it existed in your working copy before you updated your working copy—that is, without conflict markers. This file has only your latest changes in it. (If Subversion considers the file to be unmergeable, the .mine file isn't created, since it would be identical to the working file.)

    filename.rOLDREV

    This is the file that was the BASE revision before you updated your working copy. That is, the file that you checked out before you made your latest edits.

    filename.rNEWREV

    This is the file that your Subversion client just received from the server when you updated your working copy. This file corresponds to the HEAD revision of the repository.

    这里OLDREV是你的.svn目录中的修订版本号,NEWREV是版本库中HEAD的版本号。

For example, Sally makes changes to the file sandwich.txt, but does not yet commit those changes. Meanwhile, Harry commits changes to that same file. Sally updates her working copy before committing and she gets a conflict, which she postpones:

$ svn update
Conflict discovered in 'sandwich.txt'.
Select: (p) postpone, (df) diff-full, (e) edit,
        (h)elp for more options : p
C  sandwich.txt
Updated to revision 2.
$ ls -1
sandwich.txt
sandwich.txt.mine
sandwich.txt.r1
sandwich.txt.r2

At this point, Subversion will not allow Sally to commit the file sandwich.txt until the three temporary files are removed:

$ svn commit -m "Add a few more things"
svn: Commit failed (details follow):
svn: Aborting commit: '/home/sally/svn-work/sandwich.txt' remains in conflict

If you've postponed a conflict, you need to resolve the conflict before Subversion will allow you to commit your changes. You'll do this with the svn resolve command and one of several arguments to the --accept option.

如果你希望选择上次检出后修改之前的文件版本,选择base参数。

如果你希望选择只包含你修改的版本,选择mine-full参数。

如果你希望选择最近从服务器更新的版本(因此会丢弃你的所以编辑),选择theirs-full参数。

However, if you want to pick and choose from your changes and the changes that your update fetched from the server, merge the conflicted text by hand (by examining and editing the conflict markers within the file) and then choose the working argument.

svn resolve removes the three temporary files and accepts the version of the file that you specified with the --accept option, and Subversion no longer considers the file to be in a state of conflict:

$ svn resolve --accept working sandwich.txt
Resolved conflicted state of 'sandwich.txt'

4.5.4. 手工合并冲突

第一次尝试解决冲突让人感觉很害怕,但经过一点训练,它简单的像是骑着车子下坡。

Here's an example. Due to a miscommunication, you and Sally, your collaborator, both edit the file sandwich.txt at the same time. Sally commits her changes, and when you go to update your working copy, you get a conflict and you're going to have to edit sandwich.txt to resolve the conflict. First, let's take a look at the file:

$ 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

The strings of less-than signs, equals signs, and greater-than signs are conflict markers and are not part of the actual data in conflict. You generally want to ensure that those are removed from the file before your next commit. The text between the first two sets of markers is composed of the changes you made in the conflicting area:

<<<<<<< .mine
Salami
Mortadella
Prosciutto
=======

后两组之间的是Sally提交的修改冲突:

=======
Sauerkraut
Grilled Chicken
>>>>>>> .r2

Usually you won't want to just delete the conflict markers and Sally's changes—she's going to be awfully surprised when the sandwich arrives and it's not what she wanted. This is where you pick up the phone or walk across the office and explain to Sally that you can't get sauerkraut from an Italian deli. [5] Once you've agreed on the changes you will commit, edit your file and remove the conflict markers:

Top piece of bread
Mayonnaise
Lettuce
Tomato
Provolone
Salami
Mortadella
Prosciutto
Creole Mustard
Bottom piece of bread

Now use svn resolve, and you're ready to commit your changes:

$ svn resolve --accept working sandwich.txt
Resolved conflicted state of 'sandwich.txt'
$ svn commit -m "Go ahead and use my sandwich, discarding Sally's edits."

Note that svn resolve, unlike most of the other commands we deal with in this chapter, requires that you explicitly list any filenames that you wish to resolve. In any case, you want to be careful and use svn resolve only when you're certain that you've fixed the conflict in your file—once the temporary files are removed, Subversion will let you commit the file even if it still contains conflict markers.

If you ever get confused while editing the conflicted file, you can always consult the three files that Subversion creates for you in your working copy—including your file as it was before you updated. You can even use a third-party interactive merging tool to examine those three files.

4.5.5. 丢弃你的修改而接收新获取的修订版本

If you get a conflict and decide that you want to throw out your changes, you can run svn resolve --accept theirs-full CONFLICTED-PATH and Subversion will discard your edits and remove the temporary files:

$ svn update
Conflict discovered in 'sandwich.txt'.
Select: (p) postpone, (df) diff-full, (e) edit,
        (h) help for more options: p
C    sandwich.txt
Updated to revision 2.
$ ls sandwich.*
sandwich.txt  sandwich.txt.mine  sandwich.txt.r2  sandwich.txt.r1
$ svn resolve --accept theirs-full sandwich.txt
Resolved conflicted state of 'sandwich.txt'

4.5.6. 撤销:使用 svn revert

If you decide that you want to throw out your changes and start your edits again (whether this occurs after a conflict or anytime), just revert your changes:

$ svn revert sandwich.txt
Reverted 'sandwich.txt'
$ ls sandwich.*
sandwich.txt

Note that when you revert a conflicted file, you don't have to use svn resolve.

4.6. 提交你的修改

最后!你的修改结束了,你合并了服务器上所有的修改,你准备好提交修改到版本库。

The svn commit command sends all of your changes to the repository. When you commit a change, you need to supply a log message describing your change. Your log message will be attached to the new revision you create. If your log message is brief, you may wish to supply it on the command line using the --message (-m) option:

$ svn commit -m "Corrected number of cheese slices."
Sending        sandwich.txt
Transmitting file data .
Committed revision 3.

然而,如果你把写日志信息当作工作的一部分,你也许会希望告诉Subversion通过一个文件名得到日志信息,使用--file(-F)选项:

$ svn commit -F logmsg
Sending        sandwich.txt
Transmitting file data .
Committed revision 4.

If you fail to specify either the --message (-m) or --file (-F) option, Subversion will automatically launch your favorite editor (see the information on editor-cmd in 第 1.3.2 节 “配置”) for composing a log message.

[提示] 提示

If you're in your editor writing a commit message and decide that you want to cancel your commit, you can just quit your editor without saving changes. If you've already saved your commit message, simply delete the text, save again, and then abort:

$ svn commit
Waiting for Emacs...Done

Log message unchanged or not specified
(a)bort, (c)ontinue, (e)dit
a
$

The repository doesn't know or care whether your changes make any sense as a whole; it checks only to make sure nobody else has changed any of the same files that you did when you weren't looking. If somebody has done that, the entire commit will fail with a message informing you that one or more of your files are out of date:

$ svn commit -m "Add another rule"
Sending        rules.txt
svn: Commit failed (details follow):
svn: File '/sandwich.txt' is out of date
…

(错误信息的精确措辞依赖于网络协议和你使用的服务器,但对于所有的情况,其思想完全一样。)

At this point, you need to run svn update, deal with any merges or conflicts that result, and attempt your commit again.

That covers the basic work cycle for using Subversion. Subversion offers many other features that you can use to manage your repository and working copy, but most of your day-to-day use of Subversion will involve only the commands that we've discussed so far in this chapter. We will, however, cover a few more commands that you'll use fairly often.



[3] 当然没有任何东西是在版本库里被删除了—只是在版本库的 HEAD 里消失了。你可以通过检出(或者调整你的工作副本到)你做出删除操作的前一个修订版本,来找回所有的东西,详情参见第 3.5 节 “找回删除的项目”

[4] 而且你也没有无线上网卡。不要妄想连接到我们,哈!

[5] 如果你向他们询问,他们非常有理由把你带到城外的铁轨上。