本手册旨在描述 Subversion 1.2。如果您运行的是更新版本的 Subversion,我们强烈建议您访问 https://svnbooks.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 采取了哪些操作来使您的工作副本更新。

U foo

文件 fooUpdated(从服务器接收更改)。

A foo

文件或目录 fooAdded 到您的工作副本中。

D foo

文件或目录 fooDeleted 从您的工作副本中。

R foo

文件或目录 foo 已在您的工作副本中被 Replaced;也就是说,foo 被删除,然后添加了一个具有相同名称的新项目。虽然它们可能具有相同的名称,但存储库认为它们是具有不同历史记录的不同对象。

G foo

文件 foo 从存储库中接收到新的更改,但您本地副本中的文件包含了您的修改。要么更改没有交集,要么更改与您的本地修改完全相同,因此 Subversion 已成功地将存储库中的更改Ged 到文件,没有任何问题。

C foo

文件 foo 从服务器接收到 Conflicting 更改。服务器中的更改与您对文件的本地更改直接重叠。但是,无需惊慌。此重叠需要由人(您)解决;我们将在本章后面讨论这种情况。

对您的工作副本进行更改

现在您可以开始工作并对您的工作副本进行更改。通常最方便的方法是决定要进行的特定更改(或一组更改),例如编写新功能、修复错误等等。您在此处将使用的 Subversion 命令是 svn addsvn deletesvn copysvn move。但是,如果您只是编辑已经存在于 Subversion 中的文件,那么您可能不需要使用任何这些命令,直到您提交更改。您可以对工作副本进行的更改:

文件更改

这是最简单的更改类型。您无需告诉 Subversion 您打算更改文件;只需进行更改即可。Subversion 将能够自动检测哪些文件已更改。

树更改

您可以要求 Subversion“标记”文件和目录以供计划删除、添加、复制或移动。虽然这些更改可能会立即在您的工作副本中发生,但直到您提交更改时,存储库中才会发生任何添加或删除操作。

要进行文件更改,请使用文本编辑器、文字处理程序、图形程序或您通常使用的任何工具。Subversion 与处理文本文件一样轻松地处理二进制文件,效率也一样。

以下是您最常用来进行树更改的四个 Subversion 子命令的概述(我们将在后面介绍 svn importsvn mkdir)。

警告

虽然您可以使用任何您喜欢的工具编辑文件,但您不应该在没有让 Subversion 知道您正在做什么的情况下更改工作副本的结构。使用 svn copysvn deletesvn move 命令更改工作副本的结构,并使用 svn add 命令将新文件和目录置于版本控制之下。

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)。svn copy 不会创建中间目录。

svn move foo bar

此命令与运行 svn copy foo bar; svn delete foo 完全相同。也就是说,bar 被计划添加作为 foo 的副本,而 foo 被计划删除。svn move 不会创建中间目录。

检查您的更改

完成更改后,您需要将它们提交到存储库,但在这样做之前,通常最好查看您所做的更改。通过在提交更改之前检查更改,您可以编写更准确的日志消息。您也可能会发现您无意中更改了一个文件,这使您有机会在提交之前恢复这些更改。此外,这是在发布更改之前审查和仔细检查更改的良好机会。您可以通过使用 svn statussvn diffsvn revert 来查看您所做的确切更改。您通常会使用前两个命令来找出工作副本中哪些文件已更改,然后也许使用第三个命令来恢复其中一些(或所有)更改。

Subversion 经过优化来帮助您完成此任务,并且能够在不与存储库通信的情况下执行许多操作。特别是,您的工作副本包含每个版本控制文件的秘密缓存“原始”副本,位于 .svn 区域。因此,Subversion 可以快速向您展示您的工作文件发生了哪些变化,甚至允许您撤消更改,而无需联系存储库。

svn status

您可能比任何其他 Subversion 命令都更频繁地使用 svn status 命令。

如果您在工作副本的顶部运行 svn status 而不带任何参数,它将检测您所做的所有文件和树更改。以下是 svn status 可以返回的不同状态代码的示例。(请注意,# 后的文本实际上不是由 svn status 打印的。)

  L     some_dir            # svn left a lock in the .svn area of some_dir
M       bar.c               # the content in bar.c has local modifications
 M      baz.c               # baz.c has property but no content modifications
X       3rd_party           # dir is part of an externals definition
?       foo.o               # svn doesn't manage foo.o
!       some_dir            # svn manages this, but it's missing or incomplete
~       qux                 # versioned as file/dir/link, but type has changed
I       .screenrc           # svn doesn't manage this, and is set to ignore it
A  +    moved_dir           # added with history of where it came from
M  +    moved_dir/README    # added with history and has local modifications
D       stuff/fish.c        # file is scheduled for deletion
A       stuff/loot/bloo.h   # file is scheduled for addition
C       stuff/loot/lump.c   # file has textual conflicts from an update
 C      stuff/loot/glub.c   # file has property conflicts from an update
R       xyz.c               # file is scheduled for replacement
    S   stuff/squawk        # file or dir has been switched to a branch
     K  dog.jpg             # file is locked locally; lock-token present 
     O  cat.jpg             # file is locked in the repository by other user
     B  bird.jpg            # file is locked locally, but lock has been broken
     T  fish.jpg            # file is locked locally, but lock has been stolen

在此输出格式中,svn status 打印五列字符,然后是几个空格字符,最后是文件或目录名称。第一列显示文件或目录以及/或其内容的状态。此处打印的代码为:

A item

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

C item

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

D item

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

M item

文件 item 的内容已修改。

R item

文件、目录或符号链接 item 已被计划替换存储库中的 item。这意味着该对象首先被删除,然后添加另一个具有相同名称的对象,所有这些都在一个修订版本中完成。

X item

目录 item 是未版本化的,但与 Subversion externals 定义相关。要了解有关 externals 定义的更多信息,请参阅 名为“Externals Definitions”的部分

? item

文件、目录或符号链接 item 不在版本控制之下。您可以通过以下两种方式消除问号:向 svn status 传递 --quiet (-q) 开关,或在父目录上设置 svn:ignore 属性。有关忽略文件的更多信息,请参阅 名为“svn:ignore”的部分

! item

文件、目录或符号链接 item 处于版本控制之下,但丢失或不完整。如果使用非 Subversion 命令删除了该项,则该项可能丢失。对于目录,如果您碰巧中断签出或更新,它可能不完整。快速 svn update 将从存储库中重新获取文件或目录,或 svn revert file 将恢复丢失的文件。

~ item

文件、目录或符号链接 item 在存储库中作为一种对象存在,但您工作副本中的实际内容是另一种类型。例如,Subversion 存储库中可能存在一个文件,但您删除了该文件并在其位置创建了一个目录,而没有使用 svn deletesvn add 命令。

I item

文件、目录或符号链接 item 不在版本控制之下,Subversion 配置为在 svn addsvn importsvn status 操作期间忽略它。有关忽略文件的更多信息,请参阅 名为“svn:ignore”的部分。请注意,此符号仅在您向 svn status 传递 --no-ignore 选项时才会出现——否则文件将被忽略,根本不会列出!

第二列显示文件或目录属性的状态(有关属性的更多信息,请参阅 名为“属性”的部分)。如果第二列中出现 M,则属性已修改,否则将打印空格。

第三列只显示空格或 L,这意味着 Subversion 已锁定目录的 .svn 工作区。如果您在目录中运行 svn status(例如,当您编辑日志消息时),您将看到 L,因为正在执行 svn commit。如果 Subversion 未运行,则可能是 Subversion 中断,并且需要通过运行 svn cleanup 来清理锁(本章稍后将详细介绍)。

第四列只显示空格或 +,这意味着文件或目录计划添加或修改,并附带附加的更改历史记录。这通常发生在您 svn movesvn copy 文件或目录时。如果您看到 A  +,则表示该项计划添加(并附带更改历史记录)。它可能是一个文件,或被复制目录的根目录。 + 表示该项是计划添加(并附带更改历史记录)的子树的一部分,即某个父目录被复制,并且该项只是随之而来。 M  + 表示该项是计划添加(并附带更改历史记录)的子树的一部分,并且它具有本地修改。当您提交时,首先将添加(并附带更改历史记录)父目录(复制),这意味着该文件将自动存在于复制目录中。然后,本地修改将上传到复制目录中。

第五列只显示空格或 S。这表示文件或目录已从工作副本(使用 svn switch)的其余路径切换到分支。

第六列显示有关锁的信息,这将在 名为“锁定”的部分 中进一步解释。(这些锁与第三列中 L 表示的锁不同;请参阅 名为“lock”的三个含义。)

如果您向 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,您将收到对 READMEtrout.c 的更改。这告诉您一些非常有用的信息——在您提交之前,您需要更新并获取服务器对 README 的更改,否则存储库将由于过期而拒绝您的提交。(稍后将详细介绍此主题。)

svn diff

检查更改的另一种方法是使用 svn diff 命令。您可以通过运行不带任何参数的 svn diff 来找出您对内容进行了哪些修改,它将以统一的 diff 格式打印出文件更改:[4]

$ 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 区域内的缓存“原始”副本生成此输出。计划添加的文件将显示为全部添加的文本,而计划删除的文件将显示为全部删除的文本。

输出以统一 diff 格式显示。也就是说,删除的行前面带有 -,添加的行前面带有 +svn diff 还打印文件名和偏移信息,这些信息对 patch 程序很有用,因此您可以通过将 diff 输出重定向到文件来生成“补丁”。

$ svn diff > patchfile

例如,您可以在提交之前将补丁文件发送给另一位开发人员以供审查或测试。

svn revert

现在假设您看到上面的 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.

UG 代码不用担心;这些文件从存储库中干净地吸收了更改。标记为 U 的文件不包含本地更改,但从存储库中Updated。 G 代表 merGed,这意味着该文件最初包含本地更改,但来自存储库的更改没有与本地更改重叠。

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 将不再认为该文件处于冲突状态。[5]

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

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

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

$ 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 的基本工作流程。Subversion 中还有许多其他功能可以用于管理您的存储库和工作副本,但您可以仅使用本章中讨论过的命令来轻松地进行操作。



[3] 当然,存储库中永远不会完全删除任何内容,只是从存储库的 HEAD 中删除。您可以通过检出(或更新您的工作副本)比您删除文件时更早的修订版本来恢复任何删除的内容。

[4] Subversion 默认情况下使用其内部 diff 引擎,该引擎生成统一 diff 格式。如果您想要以其他格式输出 diff,请使用 --diff-cmd 指定外部 diff 程序,并使用 --extensions 开关传递任何您想传递给它的标志。例如,要查看文件 foo.c 中的本地差异(以上下文输出格式,同时忽略空白更改),您可以运行 svn diff --diff-cmd /usr/bin/diff --extensions '-bc' foo.c

[5] 您可以随时自己删除临时文件,但您真的想在 Subversion 可以为您做到这一点的情况下这样做吗?我们不这么认为。

[6] 如果您向他们索要,他们很有可能会用铁轨把你赶出城。

TortoiseSVN 官方中文版 1.14.7 发布