svn学习笔记:使用命令篇

svn学习笔记:使用命令篇

这是我学习 svn 的权威指南《使用Subversion进行版本控制》(英文 "Version Control with Subversion")时的笔记。Subversion 是 svn 系统的第一个实现工具,此书详述了 Subversion 的各种使用细节,是 svn 使用的权威指南。另外,此书使用 Creative Commons 许可证发行,属于自由书籍,原书稿也使用 svn 进行版本管理。印刷书籍由 O'Reilly Media 出版。

此书的在线阅读和下载地址:svnbook.red-bean.comwww.subversion.org.cn

我读的是针对 Subversion 1.4 的版本,笔记中的内容除过:第8章 嵌入Subversion、第9章 Subversion 完全参考,和附录外都有所涉及。这一篇记录版本管理和 svn 的概念,以及 svn 客户端的使用。

Subversion 命令行工具是一个自文档的工具,在任何时候都可以运行 svn help [SUBCOMMAND] 来查看子命令的语法。

svn URL

看几个使用 svn URL 的例子:

HTTP URL

本地 URL

1svn checkout file:///path/to/repos
2svn checkout file://localhost/path/to/repos

Windows 本地 URL

1svn checkout file:///X:/path/to/repos
2svn checkout "file:///X|/path/to/repos"

本地拷贝的 URL

1svn info | grep URL

编码 ASCII外的字符和空格

svn 有以下几种访问协议使用的 URL:

  • file:///,通过文件系统,直接访问版本库。
  • http://,使用的 WebDAV 协议和 svn 的 Apache 扩展。
  • https://,与 http:// 相似,但是使用 SSL 加密通道。
  • svn://,svnserve 服务自定义的协议。
  • svn+ssh://,与 svn:// 相似,但使用 SSH 隧道。
修订版本概念

svn 的修订号是针对整个目录树的,而不是单个文件。当 svn 用户讨论“foo.c的修订号5”时,他们的实际意思是“在修订号5时的foo.c”。

更新(update)和提交(commit)是分开的

举个例子,假定你有一个工作拷贝,修订版本号是 10。你修改了 foo.html,然后执行 svn commit,在版本库里创建了修订版本 15。当成功提交之后,许多用户希望工作拷贝完全变成修订版本 15,但是事实并非如此。修订版本从 10 到 15 会发生任何修改,可是客户端在运行 svn update 之前不知道版本库发生了怎样的改变,svn commit 不会拖出任何新的修改。另一方面,如果 svn commit 会自动下载最新的修改,可以使得整个工作拷贝成为修订版本 15,但是,那样我们会打破“push”和“pull”完全分开的原则。因此,svn 客户端最安全的方式是标记一个文件——foo.html——为修订版本 15,工作拷贝余下的部分还是修订版本 10。只有运行 svn update 才会下载最新的修改,整个工作拷贝被标记为修订版本 15。

修订版本关键字

这些关键字可以用来代替 --revision (-r) 的数字参数,这会被 svn 解释到特定修订版本号:

  • HEAD:版本库中最新的(或者是“最年轻的”)版本。
  • BASE:工作拷贝中一个条目的修订版本号,如果这个版本在本地修改了,则“BASE版本”就是这个条目在本地未修改的版本。
  • COMMITTED:项目最近修改的修订版本,与 BASE 相同或更早。
  • PREV:一个项目最后修改版本之前的那个版本,技术上可以认为是 COMMITTED - 1。

版本日期

当你指定一个日期,svn 会在版本库找到接近这个日期的最近版本:

1svn checkout -r {2006-02-17}
2svn checkout -r {15:30}
3svn checkout -r {15:30:00.200000}
4svn checkout -r {"2006-02-17 15:30"}

使用时间段,svn 会找到这段时间的所有版本:

1svn log -r {2006-11-20}:{2006-11-29}
导入数据
1svnadmin create /path/to/repos
2svn import localpath svn://svnhost/repos/path -m "Initial import"

推荐的版本库布局

创建一个 trunk 目录来保存开发的主线,一个 branches 目录存放分支拷贝,tags 目录保存标签拷贝,这种目录模式称为:TTB。

检出工作拷贝
3  
4# 获得旧的版本库快照
5svn checkout -r 1729
典型的工作周期

更新你的工作拷贝:svn update

增加文件:svn add foo,如果 foo 是目录,所有 foo 中的内容也会预定添加进去,如果你只想添加 foo 本身,请使用 --non-recursive (-N) 参数。

删除文件:svn delete foo
拷贝文件:svn copy foo bar
移动文件:svn move foo bar
建立目录:svn mkdir foo

检验修改

在工作拷贝的顶级目录运行不带参数的 svn status 命令,它会检测你做的所有的文件或目录的修改。

svn status 列出的文件状态标记
状态标记含义
A将加入到版本库的文件
C文件发生冲突,在提交到版本前,须解决冲突
D文件将从版本库中删除
M文件的内容被修改了
K拥有锁定令牌
O其他人锁定了文件
B锁打破
T锁窃取

--verbose (-v) 选项可以显示工作拷贝中的所有项目,即使没有改变过的:

1$ svn status -v
2M               44        23    sally     README
3                44        30    sally     INSTALL
4M               44        20    harry     bar.c

第一列状态标记,第二列显示一个工作版本号,第三和第四列显示最后一次修改的版本号和修改人。

上面所有的 svn status 调用并没有联系版本库,只是与 .svn 中的原始数据进行比较的结果。使用 --show-updates (-u) 选项,它将会联系版本库为已经过时的数据添加新信息:

1$ svn status -u -v
2M      *        44        23    sally     README
3M               44        20    harry     bar.c
4       *        44        35    harry     stuff/trout.c

注意这两个星号:如果你现在执行 svn update,你的 README 和 trout.c 会被更新,或者说文件已经过时,版本库会拒绝了你的提交。

比较差别

svn diff 输出统一区别格式(UDF:Unified Diff Format)的区别信息,所以输出可以作为补丁文件:svn diff > patchfile。

使用 --diff-cmd 指定外置的比较程序,并且通过 --extensions 传递其他参数:

1svn diff --diff-cmd /usr/bin/diff --extensions '-bc' foo.c

svn diff 有三种不同的用法:

  1. 检查本地修改

    不使用任何参数调用时,svn diff 将会比较你的工作文件与缓存在 .svn 的原始拷贝。

  2. 比较工作拷贝与版本库

    传递一个 --revision (-r) 参数:

    1svn diff -r 47
    2svn diff -r HEAD
  3. 比较版本库与版本库

    通过 --revision (-r) 传递两个通过冒号分开的版本号:

    1svn diff -r 2:3 rules.txt

    与前一个修订版本比较更方便的办法是使用 --change (-c):

    1svn diff -c 3 rules.txt
取消修改
1svn revert ITEM

效果与删除 ITEM 然后执行 svn update -r BASE ITEM 完全一样,但是,如果你使用 svn revert 它不必通知版本库就可以恢复文件。

解决冲突
1$ svn update
2U  INSTALL
3G  README
4C  bar.c

U 表明本地没有修改,文件已经根据版本库更新。
G 标示合并,标示本地已经修改过,与版本库没有重迭的地方,已经合并。
C 表示冲突,说明服务器上的改动同你的改动冲突了,你需要自己手工去解决。

1$ svn update
2C  sandwich.txt
3Updated to revision 2.
4$ ls -1
5sandwich.txt
6sandwich.txt.mine
7sandwich.txt.r1
8sandwich.txt.r2
  • .mine:你更新前的文件,没有冲突标志,只是你最新更改的内容。如果 svn 认为这个文件不可以合并,.mine 文件不会创建,因为它和工作文件相同。

  • .rOLDREV:这是你的做更新操作以前的 BASE 版本文件,就是你在上次更新之后未作更改的版本。

  • .rNEWREV:这是你的 svn 客户端从服务器刚刚收到的版本,这个文件对应版本库的 HEAD 版本。

遇到冲突,三件事你可以选择:

  • 手动合并冲突文本。
  • 用某一个临时文件覆盖你的工作文件。
  • 运行 svn revert 来放弃所有的本地修改。

一旦解决了冲突,需要通过命令 svn resolved sandwich.txt,这样就会删除三个临时文件,svn 就不会认为这个文件是在冲突状态了。

提交修改
1svn commit -m "Corrected number of cheese slices."
2# 通过一个文件名得到日志信息,使用 --file (-F) 选项
3svn commit -F logmsg

版本库不知道也不关心你的修改作为一个整体是否有意义,它只检查是否有其他人修改了同一个文件,如果别人已经这样做了,你的整个提交会失败,并且提示你一个或多个文件已经过时了:

1$ svn commit -m "Add another rule"
2Sending        rules.txt
3svn: Commit failed (details follow):
4svn: Your file or directory 'sandwich.txt' is probably out-of-date
修改日志

svn log --verbose (-v)

--quiet (-q) 选项,会禁止日志信息的主要部分。

如果希望察看特定顺序的一段修订版本或者单一版本,使用 --revision (-r) 选项:

1$ svn log -r 5:19    # shows logs 5 through 19 in chronological order
2$ svn log -r 19:5    # shows logs 5 through 19 in reverse order
3$ svn log -r 8       # shows log for revision 8
浏览版本库

如果你只是希望检查一个过去的版本而不希望察看它们的区别,使用 svn cat

1svn cat -r 2 rules.txt > rules.txt.v2

svn list 可以在不下载文件到本地目录的情况下来察看目录中的文件:

察看详细信息,你可以使用 --verbose (-v) 参数。

没有任何参数的 svn list 命令缺省使用当前工作拷贝的版本库 URL,而不是本地工作拷贝的目录。

导出版本库

导出版本库的一部分文件而没有.svn目录:

svn cleanup

查找工作拷贝中的所有遗留的日志文件,删除进程中工作拷贝的锁。

属性

svn 内置了一组名称以 svn: 开头的属性。

一些人用属性来存放更容易记的修订版本名称:记住修订版本 1935 是一个完全测试的版本是很困难的,但是如果在修订版本上设置一个值为 all passing 的 test-results 属性,这就有了一个有用的信息。

  • 添加、编辑属性

    1$ svn propset copyright '(c) 2006 Red-Bean Software' calc/button.c
    2$ svn propset copyright '(c) 2006 Red-Bean Software' calc/*

    使用多行文本,或者是二进制属性值,你可能不会希望通过命令行提供这些值,所以 propset 子命令提供的 --file (-F) 选项可以指定包含属性值的文件:

    1$ svn propset license -F /path/to/LICENSE calc/button.c

    使用定制的编辑器程序来添加和修改属性:

    1$ svn propedit copyright calc/button.c
  • 列出、查看属性

    1svn proplist calc/button.c
    2svn propget copyright calc/button.c
    3# 列出所有属性的名称和值,用 --verbose(-v) 选项
    4svn proplist -v calc/button.c
  • 删除属性

    1$ svn propdel license calc/button.c
    2  
    3$ svn status calc/button.c
    4 M     calc/button.c
    5$ svn diff calc/button.c
    6Property changes on: calc/button.c
    7___________________________________________________________________
    8Name: copyright
    9   + (c) 2006 Red-Bean Software

    注意 status 子命令显示的 M 在第二列而不是在第一列,这是因为我们修改了 calc/button.c 的属性,而不是它的文本内容。

  • 内置属性

    svn:mime-type:文件内容类型。
    svn:executable:文件的可执行性。
    svn:eol-style:行结束字符串,取值:native、CRLF、LF、CR。

忽略未版本控制的条目

在任何工作拷贝,将版本化文件和目录与没有也不准备版本化的文件分开会是非常常见的情况。文本编辑器的备份文件会将目录搞乱,代码编译过程中生成的中间文件,甚至最终文件也不是你希望版本化的,用户在见到这些文件和目录(经常是版本控制工作拷贝中)的任何时候都会将他们删除。

svn 提供了两种方法让你指明哪些文件可以被漠视。一种方法需要你修改 svn 的运行配置系统,这样会使所有的 svn 操作都利用这个配置。另一种方法利用了 svn 目录属性支持,与版本化的目录树紧密结合,因而会影响所有拥有这个目录树工作拷贝的人:

  1. svn 运行配置系统提供一个 global-ignores 选项,其中的值是空格分开的 glob 文件名模式。如果文件名与其中的某个模式匹配,svn 会当这个文件不存在。这个文件模式最好是全局不期望版本化的模式,例如编辑器 Emacs 的备份文件 *~ 和 .*~。

  2. 如果是在版本化目录上发现 svn:ignore 属性,其内容是一列以行分割的文件模式,这些模式不会覆盖在运行配置设置的全局忽略。不像全局忽略选项,在 svn:ignore 属性中设置的值只会应用到其设置的目录,而不会应用到其子目录。

    svn 对于忽略文件模式的支持仅限于将未版本化文件和目录添加到版本控制时,如果一个文件已经在 svn 控制下,忽略模式机制不会再有效果,不要期望 svn 会阻止你提交一个符合忽略条件的修改文件,svn 一直认为它是版本化的对象。

    例如 svn:ignore 属性可以设置成:

    calculator
        debug_log*
        

    如果想查看被忽略的文件,可以使用 svn 的 --no-ignore 选项:

    1$ svn status --no-ignore
    2 M     calc
    3 M     calc/button.c
    4I      calc/calculator
    5?      calc/data.c
    6I      calc/debug_log
    7I      calc/debug_log.1
    8I      calc/debug_log.2.gz
    9I      calc/debug_log.3.gz
关键字替换
1$ svn propset svn:keywords "Date Author" weather.txt

weather.txt 中可以有这些关键字,比如:

1$Rev::               $:  Revision of last commit
2$Author::            $:  Author of last commit
3$Date::              $:  Date of last commit

允许的关键字有:

Date:类似于 $Date: 2006-07-22 21:42:37 -0700 (Sat, 22 Jul 2006) $,它也可以用 LastChangedDate 来指定。

Revision:像 $Revision: 144 $,也可以通过 LastChangedRevision 或者 Rev 引用。

Author:类似 $Author: harry $,也可以用 LastChangedBy 来指定。

HeadURL:类似 $HeadURL: http://svn.collab.net/repos/trunk/README $,可以缩写为 URL。

Id:就像 $Id: calc.c 148 2006-07-28 21:30:43Z sally $。

锁定

创建锁定

1svn lock banana.jpg -m "Editing file for tomorrow's release."

如果 Harry 的工作拷贝文件不是最新的,版本库会拒绝请求,强制 Harry 执行 svn update 并重新运行锁定命令,同样,如果此文件已经被别的用户锁定了,锁定命令也会失败。

通过 svn status 和 svn info 的输出我们可以看到文件已经锁定。

1$ svn status
2     K banana.jpg
3$ svn info banana.jpg

svn status 会在文件后面显示一个 K(locKed 缩写),表明了拥有锁定令牌。

需要注意到提交之后,svn status 显示工作拷贝已经没有锁定令牌了。假定 Harry 不小心锁定了 images 目录的 30 个文件,因为他不确定要修改什么文件,他最后只修改了四个文件,当他运行 svn commit images,会释放所有的 30 个锁定。

自动释放锁定的特性可以通过 svn commit 的 --no-unlock 选项关闭,当你要提交文件,同时期望继续修改而必须保留锁定时非常有用。这个特性也可以半永久性的设定,方法是设置运行中 config 文件的 no-unlock = yes

任何时候都可以通过运行 svn unlock 命令释放锁定:

1$ svn unlock banana.c

发现锁定

1$ svn status -u
2M              23   bar.c
3M    O         32   raisin.jpg

O 符号表示其他人锁定了文件。谁锁定了文件,什么时候,为什么。再一次,svn info 拥有答案:

解除和偷窃锁定

在版本库主机上列出锁定,并解除锁定:

1$ svnadmin lslocks /usr/local/svn/repos
2$ svnadmin rmlocks /usr/local/svn/repos /project/raisin.jpg

用户打破锁定:

01$ svn status -u
02M    O         32   raisin.jpg
03$ svn unlock raisin.jpg
04svn: 'raisin.jpg' is not locked in this working copy
05$ svn info raisin.jpg | grep URL
08svn: Unlock request failed: 403 Forbidden (http://svn.example.com)
10'raisin.jpg' unlocked.

窃取这个锁定:

1$ svn lock raisin.jpg
2svn: Lock request failed: 423 Locked (http://svn.example.com)
3$ svn lock --force raisin.jpg
4'raisin.jpg' locked by user 'sally'.

锁定原先拥有者发现锁定被打破或窃取:

1$ svn status -u
2     B         32   raisin.jpg

svn status -u 会在文件旁边显示一个 B(Broken)。如果有一个新的锁,就会显示一个 T(sTolen)符号。

钩子锁定脚本

svn 缺省是比较“宽松的”方式,但也允许管理员创建钩子脚本,来建立严格的控制策略,参考:章节 实现版本库钩子

具体来说,pre-lockpre-unlock 钩子允许管理员决定什么时候创建和释放锁定。根据锁定是否已经存在,这两个钩子脚本可以决定是否允许特定用户打破或窃取锁定。

锁定交流

假定 Harry 锁定了一个图片,并开始编辑。同时,几英里之外的 Sally 希望做同样的工作,她没想到运行 svn status --show-updates (-u),她不知道 Harry 已经锁定了文件。她花费了数小时来修改文件,当她真被提交时发现文件已经被锁定或者是她的文件已经过期了。她的修改不能和 Harry 的合并,他们中的一人需要抛弃自己的工作,许多时间被浪费了。

svn 针对此问题的解决方案是提供一种机制,提醒用户在开始编辑以前必须锁定这个文件,这个机制就是提供一种特别的属性 svn:needs-lock。当有这个值时,除非用户锁定这个文件,否则文件一直是只读的。当得到一个锁定令牌(运行 svn lock 的结果),文件变成可读写,当释放这个锁后,文件又变成只读。

01$ /usr/local/bin/gimp raisin.jpg
02gimp: error: file is read-only!
03$ ls -l raisin.jpg
04-r--r--r--   1 sally   sally   215589 Jun  8 19:23 raisin.jpg
05$ svn lock raisin.jpg
06svn: Lock request failed: 423 Locked (http://svn.example.com)
08Lock Token: opaquelocktoken:fc2b4dee-98f9-0310-abf3-653ff3226e6b
09Lock Owner: harry
10Lock Created: 2006-06-08 07:29:18 -0500 (Thu, 08 June 2006)
11Lock Comment (1 line):
12Making some tweaks.  Locking for the next two hours.
外部定义

一个外部定义是一个本地路经到 URL 的影射。在 svn 你可以使用 svn:externals 属性来定义外部定义,你可以用 svn propset 或 svn propedit 创建和修改这个属性:

1$ svn propget svn:externals calc
2third-party/sounds             http://sounds.red-bean.com/repos
3third-party/skins              http://skins.red-bean.com/repositories/skinproj
4third-party/skins/toolkit -r21 http://svn.red-bean.com/repos/skin-maker

检出 calc 时,同时也会检出这些外部定义的项目。

svn status 命令也认识外部定义,会为外部定义的子目录显示 X 状态码。

通过外部定义创建的工作拷贝与主工作拷贝没有连接起来,所以 svn 会以不关联的工作拷贝操作。所以举个例子,如果你希望提交一个或多个外部定义的拷贝,你必须在这些工作拷贝显式地运行 svn commit,对主工作拷贝的提交不会迭代到外部定义的部分。

你或许经常希望 svn 子命令不会识别或其它作为外部定义处理的结果的外部工作拷贝上的操作,在这种情况下,你可以对子命令使用 --ignore-externals 选项。

Peg和实施修订版本

在修订版本 1 添加我们第一个 concept 目录,并且在这个目录增加一个 IDEA 文件,在几个修订版本之后,修改这个目录为 frabnaggilywort。通过修订版本 27,有了一个新的概念,所以一个新的 concept 目录用来保存这些东西,一个新的 IDEA 文件来描述这个概念。

一年之后,我们想知道 IDEA 在修订版本1时是什么样子,但是 svn 需要知道我们是想询问当前文件在修订版本 1 时的样子,还是希望知道concepts/IDEA 在修订版本 1 时的那个文件?

你可以用两种方式询问:

  1. 为了知道当前的IDEA文件在旧版本的样子,我们可以运行:

    1svn cat -r 1 concept/IDEA
    2# 等同于
    3svn cat -r 1 concept/IDEA@BASE

    svn 是如何知道 news@11 是我的目录树中的一个目录,还是修订版本 11 的 news 文件?幸好,svn 会一直假定后者。你只需要在路径最后添加一个 @ 符号,例如 news@11@,svn 只关心最后一个 @ 标记。

  2. 然后让我们询问另一个问题:在修订版本 1,占据 concepts/IDEA 路径的文件的内容到底是什么?我们会使用一个明确的 peg 修订版本来帮助我们完成:

    1$ svn cat concept/IDEA@1

    注意这一次没有提供操作修订版本,那是因为如果没有指定操作修订版本,假定缺省的操作修订版本是 peg 修订版本。

客户端凭证缓存

当客户端成功的响应了服务器的认证请求,它会保存一个认证文件到用户的私有运行配置区。

类 Unix 系统下会在 ~/.subversion/auth/,Windows 在 %APPDATA%/Subversion/auth/,参考:运行配置区

关闭凭证缓存,使用参数 --no-auth-cache

1$ svn commit -F log_msg.txt --no-auth-cache

永远关闭凭证缓存,你可以编辑你的运行运行配置区的 config 文件,只需要把 store-auth-creds 设置为 no,这样在影响的主机上的 svn 操作就不会有凭证缓存在磁盘。通过修改系统级的运行配置区,这个功能也会影响到本机的所有用户。你可以浏览到 auth 目录,删除特定的缓存文件。

分支

一个分支总是从一个备份开始的,从那里开始,发展自己独有的历史。

创建分支

本地工作版本拷贝:

1$ svn copy trunk branches/my-calc-branch
2$ svn status
3A  +   branches/my-calc-branch

注意 A 后面的“+”号,这表明这个准备添加的东西是一份备份,而不是新的东西。当你提交修改,svn 会通过拷贝 /calc/trunk 建立 /calc/branches/my-calc-branch 目录,而不是通过网络传递所有数据。

建立分支最简单的方法:svn copy 可以直接对两个 URL 操作:

3    -m "Creating a private branch of /calc/trunk."

注意第二种方法,只是执行了一个立即提交,不需要你有工作拷贝,这是大多数用户创建分支的方式。

svn 并没有内在的分支概念——只有拷贝,当你拷贝一个目录,这个结果目录就是一个“分支”。

常用分支模式

发布分支

大多数软件存在这样一个生命周期:编码、测试、发布,然后重复。这样有两个问题,第一,开发者需要在质量保证小组测试假定稳定版本时继续开发新特性,新工作在软件测试时不可以中断,第二,小组必须一直支持老的发布版本和软件;如果一个 bug 在最新的代码中发现,它一定也存在已发布的版本中,客户希望立刻得到错误修正而不必等到新版本发布。

特性分支

一个特性分支是本章中那个重要例子中的分支,你正在那个分支上工作,而 Sally 还在 /trunk 继续工作,这是一个临时分支,用来作复杂的修改而不会干扰 /trunk 的稳定性,不象发布分支(也许要永远支持),特性分支出生,使用了一段时间,合并到主干,然后最终被删除掉,它们在有限的时间里有用。

你注意到 svn switch 和 svn update 的输出很像?switch 命令只是 update 命令的一个超集。

当你运行 svn update 时,你会告诉版本库比较两个目录树,版本库这样做,并且返回给客户区别的描述,svn switch 和 svn update 两个命令唯一区别就是 update 会一直比较同一路径。

供方分支

如果第三方信息是用 svn 版本库维护,你可以使用 svn 的外部定义来有效的“强制”特定的版本的信息在你的工作拷贝的的位置。

但是有时候,你希望在你自己的版本控制系统维护一个针对第三方数据的自定义修改。

一个供方分支是一个目录树保存了第三方实体或供应方的信息,每一个供应方数据的版本吸收到你的项目叫做供方 drop。

常规的供方分支管理过程

场景:我们的开发团队正在开发一个计算器程序,与一个第三方的复杂数字运算库 libcomplex 关联。

01$ svn import /path/to/libcomplex-1.0 \
03             -m 'importing initial 1.0 vendor drop'
04  
07           -m 'tagging libcomplex-1.0'
08  
11           -m 'bringing libcomplex-1.0 into the main branch'

几周之后,libcomplex 的开发者发布了一个新的版本——版本 1.1,包括了我们很需要的一些特性和功能。我们很希望升级到这个版本,但不希望失去在当前版本所作的修改。我们本质上会希望把当前基线版本的 libcomplex 1.0 的拷贝替换为libcomplex 1.1,然后把前面自定义的修改应用到新的版本。但是实际上我们通过一个相反的方向解决这个问题,应用 libcomplex 从版本 1.0 到 1.1 的修改到我们修改的拷贝。

提交新的 1.1 版本 libcomplex 到版本库的 /vendor/libcomplex/current,然后可以创建标签 /vendor/libcomplex/1.1,最后,对比 /vendor/libcomplex/1.0 和 /vendor/libcomplex/current,将差别合并到本地工作版本的 /calc/libcomplex 中:

1$ cd working-copies/calc
4            libcomplex
5# resolve all the conflicts between their changes and our changes
6$ svn commit -m 'merging libcomplex-1.1 into the main branch'

svn_load_dirs.pl

svn 提供了一个 svn_load_dirs.pl 脚本来辅助这个过程,这个脚本自动进行我们前面提到的常规供方分支管理过程的导入步骤,从而使得错误最小化。

第一个参数是 svn 工作的基本目录 URL,第二个参数在 URL 之后—相对于第一个参数—指向当前的供方分支将会导入的目录,最后,第三个参数是一个需要导入的本地目录,你可以说明你会希望 svn_load_dirs.pl 同时打上标签,这使用 -t 命令行选项,需要指定一个标签名,这个标签是第一个参数的一个相对 URL。

1$ svn_load_dirs.pl -t libcomplex-1.1                              \
2                   http://svn.example.com/repos/vendor/libcomplex \
3                   current                                        \
4                   /path/to/libcomplex-1.1
复制特定的修改

你可以使用 svn diff 来查看 Sally 在版本 344 作的修改:

svn merge 命令几乎完全相同,它会直接作为本地修改作用到你的本地拷贝:

2U  integer.c
3  
4$ svn status
5M  integer.c

当你审查过你的合并结果后,你可以使用 svn commit 提交修改,在那一刻,修改已经合并到你的分支上了,在版本控制术语中,这种在分支之间拷贝修改的行为叫做搬运修改。

如果你希望修改应用到别的目录,你需要说出来。举个例子,你在工作拷贝的父目录,你需要指定目标目录:

1$ svn merge -c 344 http://svn.example.com/repos/calc/trunk my-calc-branch
2U   my-calc-branch/integer.c

迷惑的主要原因是这个命令的名称,术语“合并”不知什么原因被用来表明分支的组合,或者是其他什么神奇的数据混合,这不是事实,一个更好的名称应该是 svn diff-and-apply,这是发生的所有事件:首先两个版本库树比较,然后将区别应用到本地拷贝。

merge 命令包括三个参数:

  1. 初始的版本树,通常叫做比较的左边。
  2. 最终的版本树,通常叫做比较的右边。
  3. 一个接收区别的工作拷贝,通常叫做合并的目标。
3    my-working-copy
4  
5$ svn merge -r 100:200 http://svn.example.com/repos/trunk my-working-copy
6  
7$ svn merge -r 100:200 http://svn.example.com/repos/trunk

第一种语法使用 URL@REV 的形式直接列出了所有参数,第二种语法可以用来作为比较同一个 URL 的不同版本的简略写法,最后一种语法表示工作拷贝是可选的,如果省略,默认是当前目录。

传递 --dry-run 选项给 merge 命令来预览 --dry-run 选项实际上并不修改本地拷贝,它只是显示实际合并时的状态信息。

祖先

举个例子,假设你提交版本 100,包括对 foo.c 的修改,则 foo.c@99 是 foo.c@100 的一个“祖先”,另一方面,假设你在版本 101 删除这个文件,而在 102 版本提交一个同名的文件,在这个情况下,foo.c@99 与 foo.c@102 看起来是关联的(有同样的路径),但是事实上他们是完全不同的对象,它们并不共享同一个历史或者说“祖先”。

指出 svn diff 和 svn merge 区别的重要性在于,前一个命令忽略祖先:diff 命令只是盲目的比较两条路径。

你仅仅是希望 svn merge 以路径为基础比较两棵树,而忽略文件和目录的不相关性,当为合并命令添加 --ignore-ancestry 选项时,就会像 svn diff 一样工作。相反,--notice-ancestry 会导致 svn diff 像 merge 命令一样工作。

查找分支产生的版本(分支的“基准”)的最好方法是在 svn log 中使用 --stop-on-copy 选项,log 子命令通常会显示所有关于分支的变化,包括创建分支的过程,就好像你在主干上一样,--stop-on-copy 会在 svn log 检测到目标拷贝或者改名时中止日志输出。

找回删除的项目

第一步首先要知道需要拯救的项目是什么:你可以认为任何存在于版本库的对象生活在一个二维的坐标系统里,第一维是一个特定的版本树,第二维是在树中的路径,所以你的文件或目录的任何版本可以通过这样一对坐标定义。

首先,你需要 svn log 来察看你需要找回的坐标对,一个好的策略是使用 svn log --verbose 来察看包含删除项目的目录,--verbose 选项显示所有改变的项目的每一个版本 ,你只需要找出你删除文件或目录的那一个版本。

在这个例子里,你可以假定你正在找已经删除了的文件 real.c,通过查找父目录的历史 ,你知道这个文件在 808 版本被删除,所以存在这个对象的版本在此之前 。结论:你想从版本 807 找回 /calc/trunk/real.c。

使用标签
3    -m "Tagging the 1.0 release of the 'calc' project."

但是等一下:标签的产生过程与建立分支是一样的?是的,实际上在 svn 中标签与分支没有区别,都是普通的目录,通过 copy 命令得到,与分支一样,一个目录之所以是标签只是人们决定这样使用它,只要没有人提交这个目录,它永远是一个快照,但如果人们开始提交,它就变成了分支。

把本地拷贝的布局做镜像(标签)并且保存到版本库中:

版本库布局

创建一个 trunk 目录来保存开发的“主线”,一个 branches 目录存放分支拷贝,一个 tags 目录保存标签拷贝,如果一个版本库只是存放一个项目,人们会在顶级目录创建这些目录,称为:TTB 模式。

假定你最终完成了 calc 项目你的个人分支上的所有工作,在合并了你的所有修改到 /calc/trunk 后,没有必要继续保留你的私有分支目录:

2    -m "Removing obsolete branch of calc project."

恢复一个已经删除的目录(或文件)到 HEAD,仅需要使用 svn copy -r 来从旧的版本拷贝出来:

2  
3http://svn.example.com/repos/calc/branches/my-calc-branch
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值