撰写本说明文件是为了描述 Subversion 1.4。如果您运行的是较新的 Subversion 版本,我们强烈建议您访问 https://svnbook.subversion.org.cn/,并查阅与您所用 Subversion 版本相符的版本。

基本工作周期

Subversion 有许多功能、选项以及花里胡哨的东西,但在日常工作中,您可能只会用到其中的一些。在本部分中,我们将会简单介绍一下在日常工作中您可能会用到的 Subversion 最常用操作。

典型的工作周期如下所示

更新您的工作副本

与团队合作处理某个项目时,您需要更新工作副本,以便接收自上次更新后其他开发人员对该项目所作的更改。使用 svn update 将您的工作副本与存储库中的最新修订同步。

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

在此情况下,自上次更新以来,其他人已对 foo.cbar.c 进行了修改的签入,Subversion 已更新您的工作副本以包括那些更改。

当服务器通过 svn update 将更改发送至您的工作副本时,每个项目旁会显示一个字母代码,告知您 Subversion 执行了哪些操作以使您的工作副本保持最新状态。要了解这些字母的含义,请参阅 svn update

对您的工作副本进行更改

现在,您可以开始在工作副本中进行更改。通常,最方便的做法是决定进行一次单独的更改(或一组更改),例如撰写新功能,修复错误,等等。您在此处将使用到的 Subversion 命令包括 svn addsvn deletesvn copysvn movesvn mkdir。但是,如果您只是编辑 Subversion 中已经存在的文件,那么您可能无需使用任何这些命令,直到您提交为止。

您的工作副本有两种可以修改的方式:修改文件和修改目录树。您不必告诉 Subversion 您打算修改一个文件;只需使用文本编辑器、文字处理程序、图形程序或通常使用的任何工具来完成修改。Subversion 会自动检测已修改的文件,此外,处理二进制文件就像处理文本文件一样容易且有效。对于目录树修改,您可以要求 Subversion “标记”文件和目录以安排删除、添加、复制或移动。这些修改可能会立即在您的工作副本中生效,但在您提交它们之前,不会在代码仓库中添加或删除任何内容。

以下是有关五个 Subversion 子命令的概述,您最常使用它们来修改目录树。

svn add foo

安排在代码仓库中添加文件、目录或符号链接 foo。当您下次提交时,foo 将成为其父目录的子代。请注意,如果 foo 是目录,那么 foo 下面的所有内容都将安排为待添加。如果您只希望添加 foo 本身,请传递 --non-recursive (-N) 选项。

svn delete foo

安排从代码仓库中删除文件、目录或符号链接 foo。如果 foo 是文件或链接,则会立即从您的工作副本中将其删除。如果 foo 是目录,则不会删除它,但 Subversion 会计划将其删除。在您提交修改后,foo 将完全从您的工作副本和代码仓库中移除。[4]

svn copy foo bar

创建新项 bar,作为 foo 的副本,并自动安排添加 bar。当在下次提交中将 bar 添加到代码库时,其复制历史会被记录下来(最初来自 foo)。svn copy 不会创建中间目录。

svn move foo bar

此命令与运行 svn copy foo bar; svn delete foo 完全相同。即,安排添加 bar,以 foo 的副本形式,并安排删除 foosvn move 不会创建中间目录。

svn mkdir blort

此命令与运行 mkdir blort; svn add blort 完全相同。即,创建新目录,名为 blort,并安排添加。

检查你的更改

完成更改后,你需要将它们提交到代码库,但在提交之前,通常最好仔细查看已经更改的内容。通过在提交之前检查你的更改,你可以创建一个更准确的日志消息。你可能也会发现自己无意间更改了一个文件,这让你有机会在提交之前还原这些更改。此外,这是一个很好的机会,可以在发布更改之前复查和仔细检查这些更改。你可以使用 svn status 查看所做更改的概览,并使用 svn diff 查看这些更改的详细信息。

Subversion 经过优化,可以帮助你完成此任务,并且无需与存储库通信就能执行很多操作。具体而言,工作副本包含 .svn 区域内每个受版本控制的文件的隐藏缓存“原始”副本。因此,Subversion 可以快速向你展示工作文件发生了哪些变化,甚至允许你在不连接存储库的情况下撤消更改。

查看更改的概览

要获取更改的概览,可以使用 svn status 命令。你可能会比其他任何 Subversion 命令都更频繁地使用 svn status 命令。

如果您在工作副本的顶层运行 svn status,而不带任何参数,它将检测所有您做出的文件和 tree 更改。以下是 svn status 可返回的几个最常见的状态代码的示例。(注意:# 后面的文本并不是 svn status 实际打印出来的。)

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 打印六列字符,后跟几个空格字符,再后跟一个文件或目录名称。第一列显示文件或目录及其内容的状态。我们列出的代码是

A 项目

文件、目录或符号链接 item 已安排添加到存储库中。

C 项目

文件 item 处于冲突状态。也就是说,在更新过程中从服务器接收的更改与您工作副本中已有的本地更改相重叠。在将更改提交到存储库之前,您必须解决此冲突。

D 项目

文件、目录或符号链接 item 已安排从存储库中删除。

M 项目

文件 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 的“长格式”输出。第一列中的字母与之前相同,但第二列显示该项目的 working-revision。第三和第四列显示该项目最后一次更改的修订版本,以及是谁更改的。

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

检查您本地修改的详细信息

另外一种检查您更改的方法是使用 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 还会打印 patch 程序有用的文件名和偏移信息,因此您可以通过将差异输出重定向到文件来生成“补丁

$ svn diff > patchfile

例如,您可以在提交前通过电子邮件将补丁文件发送给另一位开发者进行审查或测试。

Subversion 默认情况下使用其内部 diff 引擎,该引擎生成统一 diff 格式。如果您希望以不同格式输出 diff,请使用 --diff-cmd 指定一个外部 diff 程序,并使用 --extensions (-x) 选项向它传递您要使用的任何标志。例如,要在忽略大小写差异的情况下以上下文输出格式查看 foo.c 文件中的本地差异,您可以运行 svn diff --diff-cmd /usr/bin/diff --extensions '-i' foo.c

撤消工作更改

假设在查看 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 项目 和从工作副本删除 项目,然后运行 svn update -r BASE 项目 具有完全相同的效果。但是,如果你撤销的是一个文件,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.

UG 代码无需担心;这些文件干净地吸收了存储库中的更改。用 U 标记的文件没有本地更改,但通过存储库的更改进行了 Update(更新)。G 代表 Ged(合并),这意味着该文件最初有一些本地更改,但来自存储库的更改与本地更改没有重叠。

C 代表 conflict(冲突)。这意味着来自服务器的更改与你自己的更改重叠,现在你必须手动从中进行选择。

每当发生冲突时,通常会发生三件事来帮助你注意并解决该冲突

  • Subversion 在更新期间会打印一个 C,并记住该文件处于冲突状态。

  • 如果 Subversion 认为该文件可以合并,它会将 冲突标记(用于区分冲突“各方”的特殊文本字符串)放置到文件中,以直观地展示重叠的区域。(Subversion 使用 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 -m "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 不再认为文件处于冲突状态。[6]

$ 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 解释您无法从一家意大利熟食店购买酸菜的时候。[7] 一旦您就您要检入的更改达成一致,编辑您的文件并移除冲突标记。

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."

请注意 svn resolved 与本章中我们介绍的大多数其他命令不同,它需要一个参数。无论如何,您都必须谨慎,只在确信已修复文件中的冲突时运行 svn resolved — 临时文件一旦被删除,Subversion 就会允许您提交文件,即使文件仍包含冲突标记。

如果您在编辑冲突的文件时感到困惑,您可以随时参考 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

如果您遇到冲突,在检查后决定丢弃更改并重新开始编辑,只需还原更改即可

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

请注意,当您还原冲突文件时,您不必运行 svn resolved

提交您的更改

最后!您的编辑已完成,您已合并服务器中的所有更改,并且您已准备好将更改提交到存储库。

svn commit 命令将您所有的更改发送到存储库。当您提交更改时,您需要提供一个 日志消息 来描述您的更改。您的日志消息将附加到您创建的新修订版上。如果您的日志消息很简短,您可能希望使用 --message(或 -m)选项在命令行中提供它

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

但是,如果您在工作时一直在编写日志消息,您可能希望通过使用 --file (-F) 选项传递文件名来告知 Subversion 从文件中获取消息

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

如果您未能指定 --message--file 选项,那么 Subversion 将自动启动您最喜欢的编辑器(请参阅 “配置” 一节 中的 editor-cmd 部分),以编写日志消息。

提示

如果您在编辑器中编写提交消息并决定取消提交,您只需退出编辑器即可,无需保存更改。如果您已保存了提交消息,只需删除文本,再次保存,然后中止。

$ svn commit
Waiting for Emacs...Done

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

仓库并不知道或不在意你的更改是否作为一个整体是有意义的;它只会检查以确保其他人没有在你不知情的情况下更改你的任何文件。如果有人确实这样做,整个提交会失败,并会通过一条消息通知你一个或多个文件已过期。

$ svn commit -m "Add another rule"
Sending        rules.txt
svn: Commit failed (details follow):
svn: Your file or directory 'sandwich.txt' is probably out-of-date
…

(此错误消息的具体措辞取决于你使用的网络协议和服务器,但所有情况下的含义都是相同的。)

这时,你需要运行 svn update,处理任何产生的合并或冲突,然后再次尝试提交。

这就涵盖了使用 Subversion 的基本工作周期。还有许多其他特性可以在 Subversion 中用来管理你的代码仓库和工作拷贝,但你的日常 Subversion 使用多数时候只需涉及本章至今讨论的命令而已。不过,我们还会讲解几个你经常会用到的其他命令。



[4] 当然,没有什么实际上会被永久从仓库删除,仅从仓库的 HEAD 中删除。你可以通过检出(或将你的工作拷贝更新到)删除它的某个早于该修订版的修订版来取回任何你删除的内容。另请参见名为“复原已删除的项目”的部分。

[5] 而且,你也没有 WAN 卡。以为你抓到我们了,是吧?

[6] 你可以随时自己删除这些临时文件,但当 Subversion 可以为你删除时,你会真的想要自己动手吗?我们认为不会。

[7] 如果你向他们索要,他们很可能真的会让你骑着铁轨离开小镇。