Version Control with Subversion

Draft Revision 1411M

Ben Collins-Sussman

Brian W. Fitzpatrick

C. Michael Pilato

(TBA)


Table of Contents

前言
目標讀者
本書結構
排版慣例
本書是自由的
致謝
1. 簡介
什麼是 Subversion?
Subversion 的歷史
Subversion 的功能
安裝 Subversion
Subversion 的元件
用戶端元件 (供使用者使用)
伺服器元件 (供管理員使用)
2. 基本概念
檔案庫
各種版本控制的模型
檔案分享的問題
鎖定-修改-解鎖的解決方案
複製-修改-合併的解決方案
Subversion 實務
工作複本
修訂版本
工作複本如何追蹤檔案庫
混合修訂版的限制
摘要
3. 導覽
幫幫我!
匯入
修訂版: 數字, 關鍵字, 與日期. 我的天啊!
修訂版號
修訂版關鍵字
修訂版日期
最初的取出動作
基本工作流程
更新工作複本
對工作複本產生更動
檢視你的更動
svn status
svn diff
svn revert
解決衝突 (合併他人的更動)
手動合併衝突
將檔案複製並蓋過你的工作檔
棄踢: 使用 svn revert
送交更動
檢視歷史紀錄
svn log
svn diff
檢視本地端更動
比較檔案庫與本地複本
檔案庫與檔案庫之間的比較
svn cat
svn list
對歷史紀錄的最後叮嚀
其它有用的命令
svn cleanup
svn import
摘要
4. 分支與合併
何謂分支?
使用分支
建立一個分支
與分支共事
事情的內涵
在分支之間複製更動
複製特定的更動
重覆合併問題
合併整個分支
從檔案庫移除一個更動
切換工作複本
標記
建立一個簡單的標記
建立一個複雜的標記
分支維護
檔案庫配置
資料生命週期
摘要
5. Repository 管理
檔案庫的基本知識
了解異動與修訂版
無版本控制的性質
檔案庫的建立與設定
Hook scripts
Berkeley DB 設定
檔案庫維護
管理員的工具箱
svnlook
svnadmin
svnshell.py
Berkeley DB 工具
檔案庫善後
檔案庫回復
匯入檔案庫
檔案庫備份
網路檔案庫
httpd, Apache HTTP 伺服器
你需要什麼, 才能設定基於 HTTP 的檔案庫存取
基本 Apache 設定
權限, 認證, 以及授權
伺服器名稱與 COPY 要求
瀏覽檔案庫的 HEAD 修訂版
雜項的 Apache 功能
svnserve, 自訂的 Subversion 伺服器
設定匿名 TCP/IP 存取
設定使用 SSH 存取
使用哪一個伺服器?
檔案庫權限
新增專案
選擇一種檔案庫配置
建立配置, 匯入起始資料
摘要
6. 進階主題
執行時期的設定區域
設定區域配置
設定與 Windows 登錄檔
設定選項
Servers
Config
性質
為什麼要用性質?
使用性質
特殊性質
svn:executable
svn:mime-type
svn:ignore
svn:keywords
svn:eol-style
svn:externals
外部定義
供應商分支
通用供應商分支管理程序
svn-load-dirs.pl
7. Developer Information
Layered Library Design
Repository Layer
Repository Access Layer
RA-DAV (Repository Access Using HTTP/DAV)
RA-SVN (Proprietary Protocol Repository Access)
RA-Local (Direct Repository Access)
Your RA Library Here
Client Layer
Using the APIs
The Apache Portable Runtime Library
URL and Path Requirements
Using Languages Other than C and C++
Inside the Working Copy Administration Area
The Entries File
Pristine Copies and Property Files
WebDAV
Programming with Memory Pools
Contributing to Subversion
Join the Community
Get the Source Code
Become Familiar with Community Policies
Make and Test Your Changes
Donate Your Changes
8. 完整 Subversion 參考手冊
Subversion 命令列用戶端: svn
svn 選項
svn 子命令
svn add
svn cat
svn checkout
svn cleanup
svn commit
svn copy
svn delete
svn diff
svn export
svn help
svn import
svn info
svn list
svn log
svn merge
svn mkdir
svn move
svn propdel
svn propedit
svn propget
svn proplist
svn propset
svn resolved
svn revert
svn status
svn switch
svn update
svnadmin
svnadmin 選項
svnadmin 子命令
svnadmin list-unused-dblogs
svnadmin create
svnadmin dump
svnadmin help
svnadmin load
svnadmin lstxns
svnadmin recover
svnadmin rmtxns
svnadmin setlog
svnlook
svnlook 選項
svnlook author
svnlook cat
svnlook changed
svnlook date
svnlook diff
svnlook dirs-changed
svnlook help
svnlook history
svnlook info
svnlook log
svnlook proplist
svnlook tree
svnlook youngest
A. 給 CVS 使用者的 Subversion 指引
不同的修訂版號
目錄版本
更多不需網路的動作
區分狀態與更新
分支與標記
中介資料性質
衝突排解
二進制檔案與轉換
Versioned Modules
B. 匯入 CVS 檔案庫
需求
執行 cvs2svn.py
C. 故障排除
常見問題
使用 Subversion 的問題
每當我想要存取檔案庫時, 我的 Subversion 用戶端會停在那裡.
當我想要執行 svn 時, 它就說我的工作複本被鎖定了.
尋找或開啟檔案庫時有錯誤發生, 但是我確定我的檔案庫 URL 是正確的.
我要如何在 file:// URL 中指定 Windows 的磁碟機代號?
我沒有辦法經由網路寫入資料至 Subversion 檔案庫.
在 Windows XP 中, Subversion 伺服器有時會送出損壞的資料.
要在 Subversion 用戶端與伺服器進行網路傳輸的檢查, 最好的方法是什麼?
編譯 Subversion 的問題
我把執行檔編輯好了, 但是當我想要取出 Subversion 時, 我得到 Unrecognized URL scheme. 的錯誤.
當我執行 configure, 我得到像 subs-1.sed line 38: Unterminated `s' command 的錯誤.
我無法在 Windows 以 MSVC++ 6.0 來編譯 Subversion.
D. WebDAV 與自動版本
基本 WebDAV 概念
簡易 WebDAV
DeltaV 擴充
Subversion 與 DeltaV
將 Subversion 對映至 DeltaV
自動版本支援
mod_dav_lock 的替代品
自動版本互通性
Win32 網路資料夾
Mac OS X
Unix: Nautilus 2
Linux davfs2
E. 其它 Subversion 用戶端
Out of One, Many
F. 協力廠商工具
ViewCVS
SubWiki
Glossary

List of Figures

2.1. 典型的主從式系統
2.2. 應避免的問題
2.3. 鎖定-修改-解鎖的解決方案
2.4. 複製-修改-合併的解決方案
2.5. …複製-修改-合併的解決方案 (續)
2.6. 檔案庫的檔案系統
2.7. 檔案庫
4.1. 發展的分支
4.2. 起始檔案庫的配置
4.3. 有新複本的檔案庫
4.4. 檔案歷史的分支
5.1. 一種建議的檔案庫配置.
5.2. 另一種建議的檔案庫配置.
7.1. Subversion's "Big Picture"
7.2. Files and Directories in Two Dimensions
7.3. Revisioning Time—the Third Dimension!

List of Tables

2.1. 檔案庫存取的 URL
7.1. A Brief Inventory of the Subversion Libraries
E.1. Subversion 的圖形用戶端

List of Examples

5.1. 利用 svnshell, 在檔案庫之中巡行
5.2. txn-info.sh (回報未處理異動)
5.3. 使用漸進式檔案庫傾印
6.1. 登錄項目 (.REG) 檔案的範例.
7.1. Using the Repository Layer
7.2. Using the Repository Layer with Python
7.3. A Simple Script to Check Out a Working Copy.
7.4. Contents of a Typical .svn/entries File
7.5. Effective Pool Usage

前言

如果 C 給了你夠多的繩子來吊死自己, 那麼 Subversion 可視為是一種收納繩子的器具. ”—Brian Fitzpatrick

在開放原碼軟體的世界中, Concurrent Versions System (CVS)長久以來, 一直都是版本控制的不二選擇. CVS本身是自由軟體, 而且它是 “非鎖定式” 的系統 —這讓分布廣闊的程式設計人員能夠分享彼此的工作— 完全符合開放原碼世界的合作模式. CVS, 以及它那半混亂式的發展模式, 已經成為開放原碼文化的基石.

但是就像許多的工具, CVS 已經開始顯露疲態. 比較起來, Subversion 是一個新的工具, 是設計來成為 CVS 的後繼者. 設計者要以兩個方法來贏得 CVS 使用者的心: 產生一個設計 (還有 "外觀與感覺") 類似 CVS 的開放原碼系統, 以及試著修正 CVS 中最廣為人知的缺點. 雖然結果不見得會是版本控制設計的下一個偉大革命, 但是 Subversion 絕對 會是個強力, 可用性高, 而且深具彈性的工具.

目標讀者

本書是寫給那些想要以 Subversion 來管理資料的電腦使用者. 雖然 Subversion 可以在許多不同的作業系統上執行, 不過主要的使用界面還是命令列. 由於這樣的原因, 本書的例子都假設使用者使用的是類似 Unix 的作業系統, 而且熟悉 Unix 與其命令列的界面.

大多數的使用者可能是程式設計師或系統管理員, 需要追蹤原始碼的的變更; 這是 Subversion 最常見的用法, 因此也是本書例子的情景. 但是請記住, Subversion 可以用來管理任何類似的資訊: 圖形, 音樂, 資料庫, 文件等等. 對 Subversion 而言, 所有的資料就只是資料而已.

雖然本書撰寫時, 我們假設讀者都沒有使用過版本控制軟體, 但是我們也試著讓 CVS 的使用者能夠很快地上手. 一些 sidebar 會隨時出現討論 CVS, 而且也有一章附錄用來概述 CVS 與 Subversion 之間的不同.

本書結構

本書的前三章對 Subversion 有概括性的介紹. 我們一開始先介紹 Subversion 的功能, 討論它的設計與使用者模型, 然後進行一個導引. 不管是否有相關的經驗, 所有的讀者都應該讀這幾章的內容. 它們是本書其它章節的基礎.

第四, 五, 六章討論比較複雜的議題, 像是分支, 管理檔案庫 (repository), 以及進階的功能, 像是性質, 外部定義, 以及存取控制. 系統管理員與進階使用者絕對會希望讀這幾章的.

第七章是特別寫給那些想要在他們的軟體裡使用 Subversion 的 API, 或者是想要修改 Subversion 的程式設計師.

本書最後以參考資料結束: 第八章是所有 Subversion 命令的參考指南, 而附錄則涵蓋了許多有用的主題. 這幾章大概是你在讀完本書之後, 最常使用的部份.

排版慣例

### O'Reilly almost certainly needs to fill this in, depending on how they typeset the book.

請注意這裡使用的程式碼例子, 就只是—單純的例子. 在適當的編譯器設定之下, 它們都能夠正常被編譯, 但是只是用來示範手邊的問題而已, 而不是用來作為程式設計的範例.

本書是自由的

本書起初只是 Subversion 計畫的發展人員所寫的文件而已, 後來就成為獨立的工作, 並且重新改寫. 因為如此, 這個文件也和 Subversion 一樣, 使用的是自由的開放原碼授權. 事實上, 本書是在公眾面前寫成的, 是 Subversion 的一部份. 這意味著兩件事:

  • 你可以在 Subversion 的源碼樹中, 找到本書的最新版本.

  • 你可以任意散佈、修改本書 — 它用的是自由授權. 當然了, 除了散佈自己私有的版本, 我們比較希望你能夠將回應與修正送回給 Subversion 開發者社群. 請參見 the section called “Contributing to Subversion”, 以了解如何加入本社群.

You can send publishing comments and questions to O'Reilly here: ###insert boilerplate.

http://svnbook.red-bean.com 可以找到還滿新的線上版本.

致謝

### Huge list of thanks to the many svn developers who sent patches/feedback on this book.

### Also, individual-author acknowledgements to specific friends and family.

Chapter 1. 簡介

版本控制是管理資訊變化的技術. 對於程式設計者來說, 它已經成為不可或缺的工具, 他們常常將花時間修改軟體, 產生部份的變更, 然後第二天再取消所作的變更. 想像有一群程式設計人員同時工作的情況, 你就能夠理解, 為什麼需要一個良好的系統來管理可能的混亂.

什麼是 Subversion?

Subversion 是一個自由/開放源碼的版本控制系統, 也就是說 Subversion 管理著隨時間改變的檔案. 這些檔案放置在一個中央 檔案庫 (repository) 中. 這個檔案庫 很像一個尋常的檔案伺服器, 不過它會記住每一次檔案的變動. 這樣你就可以把檔案回復到舊的版本, 或是瀏覽檔案的變動歷程. 許多人會把版本控制系統想像成某種 “時光機器”.

某些版本控制系統也是 software configuration management (SCM) 系統. 這些系統是特別設計來管理大量程式碼的, 而且具有許多功能, 專門用在軟體發展之用 — 像是可完全了解程式語言, 或是提供編譯軟體的工作. 不過 Subversion 並不是這樣的系統; 它是一個泛用系統, 可用來管理任何 類型的檔案, 其中包括了程式源碼.

Subversion 的歷史

在 1995 年時, Karl Fogel 與 Jiim Blandy 成立了 Cyclic Software, 提供 Concurrent Versions System (CVS) 的商業支援, 並著手改良它. Cyclic 作出了第一個具網路功能的 CVS 公開版本 (由 Cygnus 軟體公司捐贈). 在 1999 年, Karl Fogel 出版了一本書, 講的是 CVS, 以及它所促成的開放源碼發展模式. Karl 與 Jim 很早前就提過, 要製作一個 CVS 的取代軟體的概想; Jim 甚至還起草了一個新的, 理論性的 檔案庫設計, 而且還想到了一個不錯的計劃名稱. 最後, 在 2000 年二月, CollabNet (http://www.collab.net) 的 Brian Behlendorf 提供 Karl 全職的工作, 專職發展 CVS 的替代程式. Karl 集合了一個團隊, 於五月開始發展. 由於 Subversion 是以自由授權撰寫的, 它很快就吸引了一堆發展人員.

Subversion 的原始設計團隊定下了幾個簡單的目標. 他們決定它必須在功能上可取代 CVS. 也就是說, 所有 CVS 可達成的事, 它都要能夠作到. 在修正最顯而易見的瑕疵的同時, 還要保留相同的發展模式. 還有, Subversion 應該要和 CVS 很相像, 任何 CVS 使用者只要花費少許的力氣, 就可以很快地上手.

經過十四個月的撰寫之後, Subversion 於 2001 年 8 月 31 號開始 “自行管理”. 也就是說, 發展人員不再使用 CVS 來管理 Subversion 的程式碼, 而以 Subversion 自己來管理.

雖然起始這個計畫, 與提供大部份成果的資金都歸功於 CollabNet (它付出幾位全職 Subversion 開發人員的薪水), 這還是個開放源碼計畫, 由一般開放源碼界所公認的規則所支配. CollabNet 擁有程式碼的版權, 不過程式碼是以 Apache/BSD 風格的版權發行, 完全符合 Debian Free Software Guidelines. 換句話說, 每個人都可以隨意地自由下載、修改、以及重新散播 Subversion; 完全不需要經過 CollabNet, 或是任何人的允許.

Subversion 的功能

Subversion 哪裡比 CVS 的設計更好? 這裡是個簡短的列表, 以滿足你的好奇心. 如果你不熟悉 CVS 的話, 可能不了解這些特色在哪裡. 別害怕: 第二章會提供你版本控制的簡單介紹.

目錄版本控制

CVS 只能追蹤單獨檔案的歷史, 不過 Subversion 實作了一個 “虛擬” 的版本控管檔案系統, 能夠依時間追蹤整個目錄的更動. 目錄檔案都被納入版本控管. 最後, 用戶端有真正可用的 move (移動) 與 copy 指令.

不可分割的送交

一個送交動作, 不是導致所有更動都送入檔案庫, 就是完全不會送入. 這讓發展人員以邏輯區段建立更動, 並送交更動.

納入版本控管的描述資料 (Meta-data)

每一個檔案與目錄都附有一組隱形 “性質 (property)”. 你可以自己發明, 並儲存任何你想要的鍵值對. 性質是隨著時間來作版本控管的, 就像檔案內容一樣.

選擇不同的網路層

Subversion 有抽象的檔案庫存取概念, 可以讓人很容易地實作新的網路機制. Subversion “先進” 的網路伺服器, 是 Apache 網頁伺服器的一個模組, 它以稱為 WebDAV/DeltaV 的 HTTP 變體協定與外界溝通. 這對 Subversion 的穩定性與互通性有很大的幫助, 而且額外提供了許多重要功能: 舉例來說, 有身份認證, 授權, 線上壓縮, 以及檔案庫瀏覽. 另外也有小而獨立的 Subversion 伺服器程式, 使用的是自訂的通訊協定, 可以很容易地透過 ssh 以 tunnel 方式使用.

一致的資料處理方式

Subversion 使用二進制差異運算法, 來表示檔案的差異, 它對文字 (人類可理解的) 與二進制檔案 (人類無法理解) 兩類的檔案都一視同仁. 這兩類的檔案都同樣地以壓縮形態儲存在檔案庫中, 而且檔案差異是以兩個方向在網路上傳送的.

更有效率的分支 (branch) 與標記 (tag)

分支與標記的花費並不必一定要與計畫大小成正比. Subversion 建立分支與標記的方法, 就只是複製該計畫, 使用的方法就像 hard-link 一樣. 所以這些動作只會花費很小, 而且是固定的時間.

Hackability

Subversion 沒有任何的歷史包袱; 它主要是一群共用的 C 程式庫, 具有定義完善的 API. 這使得 Subversion 便於維護, 並且可被其它應用程式與程式語言使用.

安裝 Subversion

Subversion 建立在一個可移殖的 layer, 稱為 APR (Apache Portable Runtime 程式庫) 上. 這表示 Subversion 應該可以在任何可以執行 Apache 的 httpd 伺服器的作業系統上: Windows, Linux, 所有的 BSD 分支, Mac OS X, Netware, ... 等等.

取得 Subversion 最簡單的方式, 就是下載為你的作業系統所編譯的二進制套件. Subversion 的網站 (http://subversion.tigris.org) 常常有自願者提供的, 可供下載的可執行檔. 網站上, 也常有供微軟作業系統使用者使用的圖形介面安裝套件. 如果你使用的是 Unix 系的作業系統, 你可以使用系統內定的套件發行系統 (rpm, deb, ports), 來取得 Subversion.

另外, 你也可以直接從源碼建立 Subversion. 你可以從網站上, 下載最新的源碼發行檔. 解開之後, 請遵循 INSTALL 檔案裡的說明, 把它建立起來. 請注意發行的源碼套件中, 包含了所有你需要建立命令列用戶端, 可與遠端檔案庫溝通的套件 (尤其是 apr, apr-util, 以及 neon 程式庫). 但是 Subversion 還有許多其它相依套件, 像是 Berkeley DB, 另外 Apapche httpd 也是個可能性. 如果你想要建立一個 “完整的” 的編譯, 請確定你具備了所有記載在 INSTALL 裡的套件. 如果你計劃要與 Subversion 工作, 你可以使用你的用戶端程式, 抓取最新的, 流血前線的源碼. 這記載在 the section called “Get the Source Code” 內.

Subversion 的元件

安裝好之後, Subversion 會有數個不同的部份. 以下是你取得的程式的快速綜覽.

用戶端元件 (供使用者使用)

svn

命令列用戶端程式. 這是用來管理資料的主要工具, 在第 2, 3, 4, 以及第 6 章有詳細說明.

svnversion

用來回報工作複本的混合版本狀態. (請參考 Chapter 2, 基本概念, 以了解混合版本的工作複本.)

伺服器元件 (供管理員使用)

這些會在 Chapter 5, Repository 管理 中討論.

svnlook

用來檢閱 Subversion 的檔案庫的工具.

svnadmin

用來調整與修整 Subversion 的檔案庫的工具.

mod_dav_svn

給 Apache-2.X 網頁伺服器使用的外掛模組; 可以用來將你的檔案庫透過網路對外開放, 以供他人進行存取。

svnserve

一個獨立的伺服器程式, 可以作為伺服器行程執行, 或是被 SSH 啟動; 另一個讓你的檔案庫在網路上可供其他人存取的方法.

假設你已經正確地安裝 Subversion, 你應該可以開始使用了. 接下來的兩章, 我們會帶領你涵蓋 Subversion 的命令列用戶端程式 svn 的使用.

Chapter 2. 基本概念

本章對 Subversion 有簡短與非正式的描述. 如果你是版本控制的新手, 本章相當適合你. 我們一開始會討論一般性的版本控制, 慢慢導向 Subversion 背後的概念, 並且利用 Subversion 舉出簡單的例子.

即使本章都以人們共同一群程式源碼來作為例子, 但是請記住 Subversion 可用來管理任何種類的檔案 — 並非僅僅侷限在幫助程式設計師而已.

檔案庫

Subversion 是一個用以分享資訊的中央系統, 核心為 檔案庫 (repository), 作為儲存資料的集散地. 檔案庫儲存資料的形式是 檔案系統樹 (filesystem tree) — 也就是典型的目錄與檔案的架構. 許多的 用戶端 會先連上檔案庫, 然後對這些檔案作讀取或寫入的動作. 藉由寫入資料, 一個用戶端能夠讓資訊為他人所用; 藉由讀取資料, 該用戶端能夠擷取他人的資訊.

Figure 2.1. 典型的主從式系統

典型的主從式系統

為什麼這樣會很有趣? 到目前為止, 這些聽起來就像一個典型的檔案伺服器. 事實上, 檔案庫 就是 一種檔案伺服器, 但是與你所見的不太相同. 讓 Subversion 檔案庫如此不同的原因, 在於 它會記住所有的更動: 每個檔案的每一個更動, 甚至是每一個目錄所作的更動, 像是目錄與檔案的新增, 刪除, 以及重新編排.

當一個用戶端自檔案庫讀取資料時, 它通常只會看到最新版本的檔案系統樹. 但是用戶端也可以看到早先的檔案系統. 舉例來說, 用戶端可以詢問歷史性的問題, 像是 "上個星期三, 這個目錄裡有什麼東西?", 或 "誰是最後一個更動這個檔案的人, 而且作了哪些更動?" 這就是任何 版本控制系統 的核心問題: 記錄並追蹤隨著時間對資料所作的更動.

各種版本控制的模型

檔案分享的問題

所有的版本控制系統都必須解決同樣的基本問題: 如何讓使用者分享資料, 但是不讓他們不小心成為彼此的阻礙? 使用者要不小心覆寫掉彼此在檔案庫裡的更動, 實在是太容易了.

考慮一下以下的情景: 假設我們有兩個協同工作人員, Harry 與 Sally. 他們決定同時編輯同一個儲存在檔案庫的檔案. 如果 Harry 先存入檔案庫, (幾個月之後) Sally 很有可能以她自己的新版檔案覆寫過去. 雖然 Harry 的版本並不會就此消失 (因為系統記得每一次的更動), 但是任何 Harry 所作的更動不會出現在 Sally 的新版檔案中, 因為她打從一開始就沒看過 Harry 的更動. 總地來說, Harry 的心血就這麼消失了—至少從該檔案的最新版就遺漏掉了—. 這絕對是我們想要避免的情況!

Figure 2.2. 應避免的問題

應避免的問題

鎖定-修改-解鎖的解決方案

許多版本控制系統以鎖定-修改-解鎖的方式 來解決這個問題, 這是個很簡單的解決方案. 在這樣的系統中, 檔案庫在同一時間只允許一個人修改一個檔案. 首先 Harry 必須在他開始更動之前, 先 "鎖定" 該檔案. 鎖定檔案就像從圖書館借書; 如果 Harry 已鎖定一個檔案, 那麼 Sally 就無法對其進行任何更動. 如果她想要鎖定該檔案, 檔案庫會拒絕她的要求. 她所能作的, 就只是讀取這個檔案, 然後等著 Harry 完成他的更動, 解除他設下的鎖定. 在 Harry 解除該檔案的鎖定之後, 他的回合就結束了, 現在輪到 Sally 可以對其進行鎖定並編輯內容.

Figure 2.3. 鎖定-修改-解鎖的解決方案

鎖定-修改-解鎖的解決方案

鎖定-修改-解鎖模型的問題, 在於它的限制多了點, 而且經常會成為使用者的絆腳石:

  • 鎖定可能會造成管理上的問題. 有的時候 Harry 在鎖定一個檔案之後, 然後就忘了這件事. 在此同時, 由於 Sally 還在等著編輯這個檔案, 她什麼事也不能作, 然後 Harry 就跑去休假了. 現在 Sally 必須找個管理員, 才能解除 Harry 的鎖定. 這樣狀況, 最後導致了許多不需要的延遲與時間的浪費.

  • 鎖定可能造成不必要的工作瓶頸 如果 Harry 編輯的是文字檔的開頭部份, 而 Sally 只是要修改同一檔案的結尾部份呢? 這樣的更動完全不會重疊在一起. 他們可以同時修改同一個檔案, 而不會造成任何的傷害, 只要這些更動適當地合併在一起. 像這樣的情況, 他們並不需要輪流進行才能完成.

  • 鎖定可能會造成安全的假象 假設 Harry 鎖定並編輯檔案 A, 同時 Sally 鎖定並編輯檔案 B. 但是假設 A 與 B 彼此相依, 而對各檔案所作的修改是完全不相容的語意. 突然之間 A 與 B 彼此無法執行. 鎖定系統完全無法避免這樣的狀況 —但是它在某種程度上提供了一種安全的假象. 它很容易讓 Harry 與 Sally 認為藉由鎖定檔案, 每個人都有個好的開始, 工作互不干涉, 如此讓他們不會在早期就討論他們互不相容的更動

複製-修改-合併的解決方案

Subversion, CVS, 還有其它的版本控制系統使用一種 複製-修改-合併的模型, 作為鎖定的取代方法. 在這種模型下, 每一個使用者用戶端會讀取檔案庫, 然後建立檔案或計畫的 工作複本. 然後使用者進行各自的工作, 修改他們自己的私有複本. 最後, 私有複本會合併在一起, 以產生一個新的, 最後的版本. 版本控制系統通常會協助合併的動作, 但是最後人類還是負有讓它能夠產生正確結果的責任.

這裡舉個例子. 假設 Harry 與 Sally 各自從檔案庫 建立同一個專案的工作複本. 他們同時工作, 並對同一個複本中的檔案 "A" 作了修改. Sally 先將她的更動送回檔案庫. 當 Harry 試著要存回他的更動時, 檔案庫會通知他, 他的檔案 A 已經 過時. 換句話說, 檔案庫裡的檔案 A 在他上次產生複本後已經更動過了. 所以 Harry 要求他的用戶端程式, 將檔案庫裡新的更動與他的檔案 A 工作複本 合併 起來. 通常 Sally 的更動與他的更動不會重疊; 所以只要讓兩組更動整合在一起, 他就可以將他的工作複本回存至檔案庫.

Figure 2.4. 複製-修改-合併的解決方案

複製-修改-合併的解決方案

Figure 2.5. …複製-修改-合併的解決方案 (續)

…複製-修改-合併的解決方案 (續)

要是 Sally 的更動真的與 Harry 的更動重疊的話呢? 這該怎麼辦呢? 這種情況稱為 衝突 (conflict), 通常也不會是多大的問題. 當 Harry 要求用戶端程式將最新的檔案庫更動, 合併到他自己的工作複本時, 他自己的檔案 A 會被標示為衝突的狀態: 他可以看到兩邊互相衝突的更動, 然後手動在這兩者之間作選擇. 請注意, 軟體不會自動地解決衝突; 只有人類才有理解能力, 進而作出明智的決定. 當 Harry 手動地解決重疊的更動之後 (也許就是和 Sally 討論這個衝突!), 他就可以安全地將手動合併的檔案存回檔案庫.

複製-修改-合併模型聽起來有點混亂, 但是實務上跑起來可是相當地平順. 使用者可以同時各自工作, 不需等待他人. 當他們同時處理同一個檔案時, 大多數的情況下, 這些同時產生的更動都不會互相重疊; 衝突是相當少見的. 而且解決衝突所需要的時間, 也遠低於鎖定系統所損失的時間.

最後, 這些通通都回歸到一個最重要的因素: 使用者溝通. 當使用者彼此溝通不良時, 語法與語意衝突都會增加. 沒有任何系統能夠強迫使用者完美地溝通, 而且也沒有任何系統能夠偵測出語意衝突. 所以並沒有任何論點, 能夠導出鎖定系統可減少衝突發生的虛假承諾; 實務上, 鎖定系統似乎比其它系統對生產力有更大的傷害.

Subversion 實務

工作複本

你已經讀過有關工作複本的部份; 現在我們要示範 Subversion 如何建立並使用它.

Subversion 工作複本就只一個普通的目錄樹, 位於你的本地系統中, 其中包含了一堆檔案. 你可以依你喜好, 自由地編輯這些檔案. 而且如果這些是源碼檔, 你可以依一般的方法編譯它們. 你的工作複本就是你自己的私有工作空間: Subversion 不會將其它人的更動合併進來, 也不會讓你的更動讓他人取得, 除非你明確地要求要這樣作.

你在自己的工作複本檔案中作了一些更動, 並且確認它們都能正常地工作, 此時 Subversion 提供你許多命令, 讓你將這些更動 "發表" 給同一計畫的其他人使用 (藉由寫至檔案庫). 如果別人發表了他們的更動, Subversion 提供你許多命令, 可將這些更動合併至你的工作目錄中 (藉由讀取檔案庫).

工作複本也包含了其它額外的檔案, Subversion 建立並維護這些檔案, 讓它自己可以執行這些命令. 特別一提, 工作複本中的每個目錄都會有一個名為 .svn 的子目錄, 也就是工作複本的 管理目錄. 每個工作目錄中的檔案, 都能夠幫助 Subversion 了解哪些檔案有未出版的更動, 哪些與其他人的工作相比是過時的檔案.

一個典型的 Subversion 檔案庫通常會包含數個專案使用的檔案 (或源碼檔); 一般來講, 每一個專案是檔案庫檔案樹中的子目錄. 在這樣的安排中, 一個使用者的工作複本, 通常對應到檔案庫裡的某一特定的子目錄.

舉例來說, 假設你有一個包含兩個軟體專案的檔案庫.

Figure 2.6. 檔案庫的檔案系統

檔案庫的檔案系統

換句話說, 檔案庫的根目錄有兩個子目錄: paintcalc.

要取得一個工作複本, 你必須 取出 (check out) 某個檔案庫裡的子目錄. ("check out" 聽起來有點像是要鎖定或預約某項資源, 但是它不是; 它就只是為你建立一個計畫的私有複本.) 舉個例子, 如果你取出 /calc, 你會得到一個像這樣的工作複本:

$ svn checkout http://svn.example.com/repos/calc
A  calc
A  calc/Makefile
A  calc/integer.c
A  calc/button.c

$ ls -a calc
Makefile  integer.c  button.c  .svn/

這一串字母 A, 表示 Subversion 已經加入幾個物件到你的工作複本之中. 現在你有檔案庫的 /calc 目錄的個人複本, 外加一些額外的東西—.svn— 這裡面有 Subversion 所需的額外資訊, 早先有提到過.

假設你更動了 button.c. 由於 .svn 目錄會記得檔案的修改日期與原始的內容, Subversion 能夠知道你改了這個檔案. 但是 Subversion 不會讓你的更動公諸於世, 除非你明確地表明要這麼作. 發表你的更動的行為, 通常稱為 送交 (或 存入 (check in)) 更動至檔案庫.

要發表你的更動讓其他人知道, 可以使用 Subversion 的 commit 命令:

$ svn commit button.c
Sending button.c
Transmitting file data..
Committed revision 57.

現在對 button.c 所作的更動, 已經送交至檔案庫了; 如果其他使用者取出 /calc 的工作複本, 他們會在最新版的檔案中看到你的更動.

假設你有個合作伙伴 Sally, 他和你同一時間取出 /calc 的工作複本. 當你送交你對 button.c 的更動, Felix 的工作複本並沒有改變; Subversion 只會依使用者要求來變更工作複本.

要讓她的專案也能跟上變動, Sally 可以藉由使用 Subversion 的 update 命令, 要求 Subversion 更新 他的工作複本. 這會讓你的更動合併到她的工作複本中, 外加他人自上次取出檔案以後所送交的更動.

$ pwd
/home/sally/calc

$ ls -a 
.svn/ Makefile integer.c button.c

$ svn update
U button.c

以上取自svn update命令的輸出, 表示 Subversion 更新了 button.c 的內容. 請注意 Felix 不必指定要更新哪個檔案; Subversion 使用 .svn 目錄與檔案庫裡的額外資訊, 來決定哪些檔案必須更新到最新版.

修訂版本

svn commit的動作, 可以將不限數目的檔案與目錄的更動, 視為單一不可分割的異動以進行發表. 在你的工作複本中, 你可以修改檔案的內容, 建立, 刪除, 更名, 以及複製檔案與目錄, 然後將所有的更動視為一個單位, 一口氣送交回去.

在檔案庫中, 每一次的送交都被視為是一個不可分割的異動: 不是所有送交的更動都成功, 就是全部都不成功. Subversion 會試著維持這樣的不可分割特性, 不管是遭遇程式失敗, 系統當機, 網路有問題, 還是其它使用者進行的動作.

每一次檔案庫接受一個送交的更動, 就會讓檔案樹進入一個新的狀態, 稱之為 修訂版本. 每一個修訂版本都會被賦與一個唯一的, 比前一個修訂版號大一的自然數. 一個新建立的檔案庫的修訂版號為零, 其中除了空的根目錄外, 什麼都沒有.

有一個把檔案庫具象化的好方法, 就是將之視為一系列的樹. 想像有一系列的修訂版號, 自左至右, 由 0 開始. 每一個修訂版號下都有一個檔案系統的樹狀結構, 每一個檔案樹都是每一次送交之後的檔案庫 “快照”.

Figure 2.7. 檔案庫

檔案庫

請特別注意, 工作複本並不見得一定會符合檔案庫某一特定的修訂版; 每個檔案可能會對應到不同的修訂版本. 舉個例子, 假設你的工作目錄, 是從檔案庫登出了 4 號修訂版:

calc/Makefile:4
     integer.c:4
     button.c:4

此時工作目錄對應的是檔案庫的 4 號修訂版. 不過要是你修改了 button.c, 送交這個更動. 假設此時其它人都沒有送交任何更動, 你所送交的更動就會變成檔案庫的第 5 號修訂版, 然後你的工作複本就會變成像這樣:

calc/Makefile:4
     integer.c:4
     button.c:5

假設在這個時候, Sally 送交了 integer.c 的更動, 產生了 6 號修訂版. 如果你以 svn update 更新你的工作目錄, 它看起來就會像這樣:

calc/Makefile:6
     integer.c:6
     button.c:6

Sally 對 integer.c 的更動會出現在你的工作複本中, 而你的更動還是在 button.c 之中. 在這個例子中, 版本 4, 5, 6 的 Makefile 內容都是一樣的, 不過 Subversion 會將工作複本中的 Makefile 標示為版本 6, 表示它還是最新版本. 所以, 在你對工作複本作了一次完整更新之後, 它基本上就是完全對應到檔案庫的某一個版本.

工作複本如何追蹤檔案庫

對每個工作目錄中的檔案, Subversion 會在管理區域 .svn/ 中, 記錄兩個重要的資訊:

  • 工作檔案的基本修訂版本 (亦稱為檔案的 工作版本), 以及

  • 時間戳記, 記錄本地複本最近一次被檔案庫更新的時間.

有了這些資訊, 再藉由諮詢檔案庫, Subversion 就可以決定某個工作檔案是處於下列四個狀態何者之一:

未更動, 現行版本

本檔案在工作目錄中未被更動, 而且自工作版本之後, 也沒有任何該檔案的更動被送交回去. 對它執行 svn commit 不會發生任何事, 執行 svn update 也不會發生任何事.

本地修改, 現行版本

這個檔案在工作目錄中被修改過, 而自其基礎修訂版號後, 也沒有任何更動送交回檔案庫. 由於有尚未送交回去的本地端修改, 所以對它的 svn commit 會成功地發表你的更動, 而 svn update 則不會作任何事.

未更動, 過時版本

這個檔案在工作目錄中並未更動, 但是檔案庫已被更動. 本檔案應該要更新, 以符合公開修訂版. 對它的 svn commit 不會發生任何事, 而 svn update 會讓工作目錄中的檔案更新至最新版本.

本地修改, 過時版本

這個檔案在工作目錄與檔案庫都受到了更動. 對它執行 svn commit 會產生 "out-of-date" 錯誤. 這個檔案應該要先被更新; svn update 會試著將已發表的更動, 與本地的更動合併在一起. 如果 Subversion 無法自動無誤地完成它, 那麼就會留給使用者, 讓他來解決這個衝突.

聽起來好像要注意很多東西, 但是 svn status 的命令會顯示任何在工作複本裡的項目的狀態. 欲取得該命令更詳細的資訊, 請參見 the section called “svn status”.

混合修訂版的限制

Subversion 的一個基本原則, 就是要盡量地保有彈性. 有一種特別的彈性, 就是工作複本可包含混合修訂版的能力.

一開始可能無法理解, 為什麼這樣的彈性會被視為一項特色, 而不是責任. 在完成至檔案庫的送交動作後, 剛送交的檔案與目錄的修訂版, 會比工作複本其它部份的修訂版還要新. 這看起來有點混亂. 就像我們早先示範過的, 我們永遠都可藉由 svn update, 將工作複本帶回至單一的工作修訂版. 為什麼會有人要 故意 混合不同的工作修訂版呢?

假設你的專案夠複雜, 你發現有時強制將工作複本的某些部份帶回到 “舊版本” 反而更好; 在第 3 章, 你會學到怎麼達成. 也許你想要對早先版本的子模組進行測試, 也許你想要在最新的檔案樹中, 檢視某個檔案幾個過去的版本.

但是, 當你在工作複本中使用混合修訂版時, 這項彈性有幾個限制.

首先, 如果檔案或目錄不全都是最新版本時, 對它們的刪除動作是無法送交的. 如果一個項目在檔案庫中, 存在著比目前更新的版本, 你想要送交的刪除動作會被拒絕, 以防止你不小心毀了還沒看過的更動.

第二, 你無法送交一個對目錄的描述資訊更動, 除非它完全是最新版本的. 你會在第 6 章學到如何將 “性質” 附加到項目. 一個目錄的工作修訂版, 會定義出特定的實體與性質的集合, 因此送交一個對過時目錄的性質更動, 很有可能會毀掉你還沒檢視過的性質.

摘要

本章涵蓋了幾個 Subversion 的基本概念:

  • 我們介紹了中央檔案庫, 用戶端工作複本, 以及檔案庫修訂版樹的陣列.

  • 本章示範幾個例子, 說明兩個協同工作者如何利用 '複製-修改-合併' 模式, 透過 Subversion 來發表並接收彼此所作的更動.

  • 我們也談了一些有關 Subversion 如何追蹤與管理工作複本的資料.

現在, 你應該對 Subversion 如何工作有個清楚的概念. 有了這樣的知識, 你現在已經準備好進行到下一章, 對 Subversion 的命令與功能來個詳細的巡禮.

Chapter 3. 導覽

現在我們將詳細講解如何使用 Subversion. 當你看完這一章後, 應該就能夠進行每日會用到的功能. 一開始會先取出程式碼, 更動程式, 然後檢視這些更動. 你也會看到如何將別人的更動, 取回至自己的工作複本. 檢視這些更動, 然後處理可能發生的衝突.

請注意本章並不打算列出所有 Subversion 的命令—更確切地說, 它只是個對話式的介紹, 講解最常遇到的 Subversion 工作. 本章假設你已經讀過並了解 Chapter 2, 基本概念, 也熟悉 Subversion 的基本模型. 想要取得所有命令列表, 請參照Chapter 8, 完整 Subversion 參考手冊.

幫幫我!

在繼續下去之前, 以下是你在使用 Subversion 時, 最重要、最常用到的命令: svn help. Subversion 的命令列用戶端提供自己的說明文件 —在任何時間, 只要打 svn help <子命令>, 就會有 子命令 的文件, 選項, 以及運作方式的說明.

匯入

你可使用 svn import 來匯入一個新的計畫至 Subversion 的檔案庫中. 雖然在設定你的 Subversion 伺服器時, 這可能是你第一件作的事, 但是它很少會使用到. 欲取得更詳細的匯入功能說明, 請參見 本章稍後的 the section called “svn import”.

修訂版: 數字, 關鍵字, 與日期. 我的天啊!

在我們繼續下去之前, 你應該要知道如何指定檔案庫中的特定修訂版. 如你在 the section called “修訂版本” 學到的, 修訂版是檔案庫在某一特定時間的 “快照”. 只要你會繼續送交更動, 讓檔案庫成長下去, 你就需要一個指定這些快照的機制.

使用 --revision (-r) 選項, 再加上你想要的修訂版號 (svn --revision REV), 就可以指定修訂版, 或是以分號隔開兩個修訂版號 (svn --revision REV1:REV2), 就可以指定一個修訂版範圍. Subversion 還可以讓你以數字, 關鍵字, 或是日期來指定這些修訂版號.

修訂版號

當你建立了一個新的 Subversion 檔案庫, 它就以修訂版號 0 開始它的生命. 其後的送交動作, 都會讓修訂版號加一. 在你的送交動作完成後, Subversion 用戶端會告知你新的修訂版號:

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

在未來的某一時間, 如果你想參考到這個版號時 (稍後在本章, 我們會看看如何, 以及為什麼我們會想這樣作), 你可以以 “3” 來指定它.

修訂版關鍵字

Subversion 用戶端懂得幾個 修訂版關鍵字. 這些關鍵字可在 --revision 選項中, 用來取代整數引數, 它們將會被 Subversion 轉換成特定的修訂版號:

Note

每一個工作複本的目錄內, 都有一個 .svn 管理區域. Subversion 會在管理區域中, 存放目錄裡每一個檔案的複本. 這個複本是你上次進行更新時, 所取得的修訂版 (稱為 “BASE” 修訂版) 內的無更動 (沒有關鍵字展開, 沒有列尾字元展開, 什麼都沒有) 檔案複本. 我們稱這個檔案為 “原始未更動 (prestine)” 複本.

HEAD

檔案庫內的最新版本.

BASE

一個物件在工作複本中的 “原始未更動” 修訂版.

COMMITTED

一個物件距 BASE 修訂版之前 (包含), 最近一次修改的修訂版.

PREV

最近一次修改的修訂版的 前一個 修訂版. (技術上來講, 就是 COMMITTED - 1.)

以下是幾個使用修訂版關鍵字的範例 (如果不懂這些命令, 沒有關係; 藉由本章的引導, 我們會逐步解釋這些命令):

$ svn diff --revision PREV:COMMITTED foo.c
# 顯示最近一次送交 foo.c 所產生的更動

$ svn log --revision HEAD
# 顯示最新的檔案庫送交的記錄訊息

$ svn diff --revision HEAD
# 將工作檔案 (含有本地的修改) 與檔案庫的最新版本作比較

$ svn diff --revision BASE:HEAD foo.c
# 將你的 “原始未更動” foo.c (未含本地修改)
# 與檔案庫裡的最新版本作比較

$ svn log --revision BASE:HEAD
# 顯示自你上次更新後的所有送交訊息

$ svn update --revision PREV foo.c
# 復原上一個 foo.c 的更動
# (foo.c 的工作修訂版本會被減少.)
      

這些關鍵字感覺並不是非常重要, 但是它們可以讓你進行一些常見 (而且有用) 的動作, 而不必先找出確切的修訂版號, 或是記住工作複本確實的修訂版本.

修訂版日期

任何你可以指定修訂版號或修訂關鍵字的地方, 你也可以在大括號 “{}” 內指定日期, 就可以指定修訂版日期. 你還可以一起使用日期與修訂版, 以存取檔案庫裡一個範圍的更動.

Subversion 可接受相當多的日期格式— 只要記得將有空白的日期以引號包起來就好. 這裡只是幾個 Subversion 接受的格式的例子而已:

$ svn checkout --revision {2002-02-17}
$ svn checkout --revision {2/17/02}
$ svn checkout --revision {"17 Feb"}
$ svn checkout --revision {"17 Feb 2002"}
$ svn checkout --revision {"17 Feb 2002 15:30"}
$ svn checkout --revision {"17 Feb 2002 15:30:12 GMT"}
$ svn checkout --revision {"10 days ago"} 
$ svn checkout --revision {"last week"} 
$ svn checkout --revision {"yesterday"} 
…
      

當你將日期指定為修訂版時, Subversion 會找出與該日期最接近的檔案庫的修訂版:

$ svn log --revision {11/28/2002}
------------------------------------------------------------------------
r12:  ira | 2002-11-27 12:31:51 -0600 (Wed, 27 Nov 2002) | 6 lines
…
      

你也可以使用一個範圍的日期. Subversion 會找出所有兩個日期之間的修訂版, 包含這兩個日期在內:

$ svn log --revision {2002-11-20}:{2002-11-29}
…
      

就像我們前面指出的, 你也可以混合日期與修訂版號:

$ svn log -r {11/20/02}:4040
      

最初的取出動作

大部份開始使用 Subversion 的檔案庫的動作, 就是對你的專案進行 取出 (checkout) 的動作. “取出” 檔案庫會在你的機器裡建立一份複本. 這個複本包含了你在命令列裡指定的檔案庫的 HEAD (也就是最新的) 版本:

$ svn checkout http://svn.collab.net/repos/svn/trunk
A  trunk/subversion.dsw
A  trunk/svn_check.dsp
A  trunk/COMMITTERS
A  trunk/configure.in
A  trunk/IDEAS
…
Checked out revision 2499.
    

雖然上面例子取出的是 trunk 目錄, 但是你可以取出任何檔案庫深層的目錄, 只要在取出的 URL 指定副目錄即可:

$ svn checkout http://svn.collab.net/repos/svn/trunk/doc/book/tools
A  tools/readme-dblite.html
A  tools/fo-stylesheet.xsl
A  tools/svnbook.el
A  tools/dtd
A  tools/dtd/dblite.dtd
…
Checked out revision 3678.
    

由於 Subversion 使用的是 “複製-修改-合併” 模式, 而不是 “鎖定-修改-解鎖” 模式 (參見 Chapter 2, 基本概念), 你現在已經可以修改已取出的檔案與目錄 (整個稱之為 工作複本.

換句話說, 現在你的 “工作複本” 就像其它在系統中的目錄與檔案的集合一樣 [1]. 你可以編輯或更動它們, 把它們移來移去, 甚至還可以刪掉整個工作複本, 然後完全把它拋諸腦後.

Note

雖然你的工作複本 “就只是跟其它在系統上的目錄與檔案的集合沒有兩樣”, 但是你要重新擺放工作複本裡的東西的話, 你還是得讓 Subversion 知道. 如果你要複製或移動工作複本中的某個項目, 你應該使用 svn copysvn move, 而非作業系統所提供的複製與移動的指令. 本章稍後會再討論到這些指令.

除非你準備要 送交 (commit) 新檔案或目錄, 或是對現有項目的更動, 否則你不需通知 Subversion 伺服器你所作的事情.

雖然你可以用檔案庫的 url 作為唯一的參數, 來取出工作複本, 你還是可以在檔案庫 url 之後指定一個目錄, 如此會將你的工作複本置於你命名的新目錄中. 舉個例子:

$ svn checkout http://svn.collab.net/repos/svn/trunk subv
A  subv/subversion.dsw
A  subv/svn_check.dsp
A  subv/COMMITTERS
A  subv/configure.in
A  subv/IDEAS
…
Checked out revision 2499.
    

這會將你的目錄置於 subv 中, 而不是我們之前的 trunk 目錄.

基本工作流程

Subversion 有許多特色, 選項, 以及其它有的沒有的功能, 不過對每天例行的工作來說, 你會用到的只有其中的一小部份而已. 在本節中, 我們會詳加介紹所有你會在每天的工作中, 最常會使用到的功能.

典型的工作流程, 看起來像這樣:

  • 更新工作複本

    • svn update

  • 產生更動

    • svn add

    • svn delete

    • svn copy

    • svn move

  • 檢視你的更動

    • svn status

    • svn diff

    • svn revert

  • 合併其它人的更動

    • svn merge

    • svn resolved

  • 送交更動

    • svn commit

更新工作複本

與一組團隊同時修改同一個專案時, 你會想先 更新 (update) 你的工作複本: 也就是說, 取回同一專案中, 其它發展人員所作的更動. 你可以使用 svn update, 將你的工作複本裡的版本同步至檔案庫的最新修訂版.

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

在這個例子中, 有人從你上次更新後, 登錄了 foo.cbar.c 所產生的更動, 而且 Subversion 已經更新了工作目錄, 以包含這些新的更動.

讓我們更詳細地檢視 svn update 的輸出. 當伺服器送出更動至你的工作目錄時, 在每一個項目的旁邊, 都有一個字母代碼, 讓你知道 Subversion 會執行什麼動作來更新你的工作目錄.

U foo

檔案 foo 會被更新 (Update) (自伺服器取得更動).

A foo

檔案或目錄 foo 會被新增 (Add) 到工作目錄中.

D foo

檔案或目錄 foo 會自工作目錄刪除 (Delete).

R foo

工作複本的檔案或目錄 foo 被取代 (Replace); 也就是說, foo 被刪除, 然後新增同一名稱的新項目. 雖然它們的名稱是相同的, 但是檔案庫會認為它們是不同的, 而且有著不同的歷史進程.

G foo

檔案 foo 自檔案庫取得新的更動, 但是本地複本的檔案含有你的更動. 不過這些更動並沒有重疊的部份, 所以 Subversion 可以毫無困難地合併 (merGe) 檔案庫的更動.

C foo

檔案 foo 自伺服器收到衝突的 (Conflict) 更動. 從伺服器來的更動, 與你對該檔案的更動有重疊的部份, 不過不必太驚慌失措. 衝突必須由人類 (也就是你) 來解決; 我們在本章稍後會討論這個狀況.

對工作複本產生更動

現在你可以開始工作, 修改工作複本了. 比較好的作法, 是產生特定的一個更動 (或是一組更動), 像是加個新功能, 修正臭蟲等等. 這裡會用到的 Subversion 指令, 有 svn add, svn delete, svn copy, 以及 svn move. 如果你只是要修改一個已在 Subversion 裡的檔案 (或是不只一個檔案), 可能在送交更動前都不會用到這些指令. 你能對工作複本所作的更動:

變更檔案

這是最簡單的更動了. 你不需要先和 Subversion 說你要修改檔案; 直接更動它即可. Subversion 有能力自動偵測哪些檔案被更動過.

目錄結構更動

你可以要求 Subversion 先 “標示” 預定要移除、 新增、複製、 或是移動的目錄或檔案. 雖然這些更動會馬上出現在工作複本中, 不過在你送交更動前, 檔案庫並不會跟著有所變動.

要產生檔案的更動, 請使用文字編輯程式, 文書處理程式, 圖形程式, 或是你平常使用的任何工具. Subversion 可以很容易地處理二進制檔案, 如同文字檔案一般 —而且一樣有效率.

以下是你最常用來更動目錄結構的四個常用的 Subversion 指令 (我們稍候會講到 svn importsvn mkdir).

svn add foo

預定將檔案 foo 新增至檔案庫中. 下一次送交時, foo 會成為其父目錄的子項目. 請注意如果 foo 是一個目錄的話, 所有在 foo 的東西都預定會被加入檔案庫. 如果你只想要 foo 本身而已, 請使用 --non-recursive (-N) 選項.

svn delete foo

預定將 foo 自檔案庫刪除. 如果 foo 是檔案, 那麼它會馬上自工作複本中刪除. 如果 foo 是目錄的話, 它不會被刪除, 但是 Subversion 會預定將其刪除. 當你送交你的更動時, foo 會自工作複本與檔案庫刪除. [2]

svn copy foo bar

建立一個新的項目 bar, 成為 foo 的複本. bar 會自動被預定新增至檔案庫中. 當 bar 在下一次被送交時, 它的複製歷程就會被紀錄下來 (也就是來自 foo).

svn move foo bar

這個命令就像執行 svn cp foo bar; svn delete foo 一樣. 也就是說, bar 預定被新增為 foo 的複本, 而 foo 則預定被刪除.

檢視你的更動

當你修改完畢後, 你必須將更動送交至檔案庫, 不過在這樣作之前, 先看看自己作了哪些更動, 是個不錯的主意. 在送交前先檢視更動的部份, 讓你可以寫出更清楚的記錄訊息. 你還可能會發現意外更動到的檔案, 這讓你在送交前還有回復這項錯誤的機會. 除此之外, 這也是在發表之前, 仔細檢視並檢查所作的更動的時機. 想要更清楚地了解所作的更動, 你可以藉由 svn status, svn diff, 以及 svn revert 這些命令, 來看看你所作的更動是什麼. 通常你會用前兩個命令, 來找你對工作複本所作的更動, 然後可能用第三個命令來復原這些更動.

Subversion 經過特別調校, 可在不與檔案庫溝通的情況下, 幫你進行以下的工作, 外加許多其它的工作. 尤其是你的工作複本裡, .svn 區域擁有每一個受版本控制的檔案的 “原始未更動” 複本. 也因為如此, Subversion 可以很快地告訴你檔案是如何被更動, 甚至讓你可以取消你所作的變更, 而不需使用到檔案庫.

svn status

你使用 svn status 的次數, 可能會遠大於其它的 Subversion 命令.

如果你對工作目錄不帶任何引數執行 svn status, 它會偵測出所有在檔案樹裡所產生的更動. 以下的例子, 是用來顯示所有 svn status 可傳回的狀態碼. 接在 # 後的文字, 並不是由 svn status 所產生的.

  L    ./abc.c               # svn 在 .svn 目錄中, 有 abc.c 的鎖定
M      ./bar.c               # bar.c 的內容, 有本地端的變更
 M     ./baz.c               # baz.c 已變更了性質, 但是沒有內容的更動
?      ./foo.o               # svn 並未管理 foo.o
!      ./some_dir            # svn 管理它, 但是不是不見了, 就是不完整
~      ./qux                 # 納入管理的是目錄, 但是這裡是檔案, 或是相反的情況
A  +   ./moved_dir           # 新增項目, 並以來源的歷史紀錄為其歷史紀錄
M  +   ./moved_dir/README    # 新增項目, 使用來源的歷史紀錄, 再加上本地更動
D      ./stuff/fish.c        # 本檔案已預定要被刪除
A      ./stuff/loot/bloo.h   # 本檔案已預定要被新增
C      ./stuff/loot/lump.c   # 這個檔案有因更新而產生的衝突
    S  ./stuff/squawk        # 這個檔案或目錄已切換到分支
        

在這個 svn status 的輸出格式中, 先印出了五欄字元, 後接幾個空白, 再接一個檔案或目錄的名稱. 第一欄表示檔案或目錄本身, 以及/或是其內容的狀態. 這裡顯示的代碼有:

A file_or_dir

目錄或檔案 file_or_dir 已預定要被新增至檔案庫.

M file

檔案 file 的內容已被修改.

D file_or_dir

目錄或檔案 file_or_dir 已預定要自檔案庫刪除.

X dir

目前 dir 未納入版本控制, 但是關聯到一個 Subversion 的外部定義. 欲了解更多外部定義, 請參考 the section called “外部定義”.

? file_or_dir

目錄或檔案 你可以藉由 svn status 命令的 --quite (-q) 選項, 或是對父目錄設定其 svn:ignore 性質, 就不會顯示問號代碼. 想知道有哪些檔案會被忽略, 請參照 the section called “svn:ignore”.

! file_or_dir

目錄或檔案 file_or_dir 已納入版本控制之中, 但是它不是消失, 就是不完整. 如果這個檔案或目錄被非 Subversion 的命令所刪除, 就會被判定為消失. 如果是目錄的話, 如果你在取出或是更新時中斷, 那麼它就會變成不完整. 只要執行 svn update, 就可以從檔案庫重新取得目錄或檔案, 或者以 svn revert file_or_dir, 回存消失的檔案.

~ file_or_dir

目錄或檔案 file_or_dir 在檔案庫裡是一種物件, 但是實際在工作複本中又是另一種. 舉個例子, Subversion 可能在檔案庫裡有一個檔案, 但是你刪除了這個檔案, 而以原名稱建立了一個目錄, 但是都沒有使用 svn deletesvn add 命令來處理.

C file

file_or_dir 處於衝突的狀態. 也就是說, 在更新時, 來自伺服器的更新與工作複本中的本地更動, 有重疊的部份. 在送交更動回檔案庫之前, 你必須先解決這個這個衝突.

第二欄指示的是目錄或檔案的性質 (請參見 the section called “性質”, 以了解更多性質的資訊). 如果第二欄出現的是 M, 那麼表示其性質曾變更過, 不然顯示的是空白字元.

第三欄只會顯示空白字元, 或是 L, 表示 Subversion 在 .svn 工作區中有物件的鎖定. 如果你處於正在執行 svn commit 的目錄中, 此時執行 svn status 的話, 你就會看到 L—大概那時正在編輯記錄訊息. 如果 Subversion 並沒有在執行的話, 那麼很有可能 Subversion 因故中斷. 這個鎖定可以利用 svn cleanup 來清除 (稍後本章會提到).

第四欄只會顯示空白字元, 或是 +, 表示這個檔案或目錄已預定要加入檔案庫, 或是以額外附加的歷史紀錄修改. 通常發生的時機, 是你對檔案或目錄執行 svn movesvn cp 命令. 如果你看到 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
        

請注意那兩個星號: 如果你現在執行 svn update, 那麼你會取得 READMEtrout.c 的更新. 這告訴你一件很重要的事情— 你需要在你送交變更之前, 先作更新, 自伺服器取得 README 檔案的更新, 不然檔案庫 會因檔案過時而拒絕你的送交動作. (稍後會著墨這個課題.)

svn diff

另一個檢視更動的方法, 是使用 svn diff 命令. 你可以得知 切確 修改的地方, 只要不帶引數執行 svn diff 即可, 它會以統一差異格式 (unified diff) 格式顯示檔案的更動: [3]

$ svn diff
Index: ./bar.c
===================================================================
--- ./bar.c
+++ ./bar.c	Mon Jul 15 17:58:18 2002
@@ -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
+++ ./README	Mon Jul 15 17:58:18 2002
@@ -193,3 +193,4 @@ 
+Note to self:  pick up laundry.

Index: ./stuff/fish.c
===================================================================
--- ./stuff/fish.c
+++ ./stuff/fish.c  Mon Jul 15 17:58:18 2002
-Welcome to the file known as 'fish'.
-Information on fish will be here soon.

Index: ./stuff/things/bloo.h
===================================================================
--- ./stuff/things/bloo.h
+++ ./stuff/things/bloo.h  Mon Jul 15 17:58:18 2002
+Here is a new file to describe
+things about bloo.
        

svn diff 命令藉由比較你的工作複本, 以及 .svn 裡的 “原始未更動” 複本, 以產生這個輸出. 預定要加入的檔案, 會以全部新增的方式顯示, 而預定要刪除的檔案則以全部刪除的方式顯示.

輸出是以 統一差異格式 顯示的. 也就是說, 刪除的文字列是以 - 開頭的, 新增的文字列是以 + 開頭的. 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
        

Note

svn revert ITEM 的效果, 就跟自工作複本刪除 ITEM, 然後執行 svn update 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
      

代碼 UG 沒什麼好擔心的; 這幾個檔案都很順利地接受了來自檔案庫的更動. 標示有 U 的檔案, 表示它沒有本地端的更動, 但是更新了檔案庫的更動. G 代表的它已經合併更動, 也就是說有本地端的更動, 但是來自檔案庫的更動並沒有與它重疊.

但是 C 表示有衝突, 也就是說來自伺服器的更動與你的更動有重疊的地方, 而現在你必須手動在這兩者之間作選擇.

不管衝突於哪裡發生, 用戶端的 Subversion 會作三件事:

  • Subversion 在更新時, 會顯示 C, 並且記得這個檔案 “有衝突”.

  • Subversion 會將 衝突標記 置於檔案中, 明確地將重疊的部份標示出來.

  • 對每一個衝突的檔案而言, Subversion 會額外放置三個檔案在你的工作複本中:

    filename.mine

    這是你在更新工作複本前, 就在工作複本中的檔案—也就是說, 它沒有衝突標記. 這個檔案就只有你的最新更動, 沒有包含其它的東西.

    filename.r舊版號

    這是在你更新工作複本前, BASE 修訂版的檔案. 也就是在你開始進行修改之前所取出的檔案.

    filename.r新版號

    這是 Subversion 用戶端在你更新工作複本時, 剛從伺服器取得的檔案. 這個檔案對應的是檔案庫中的 HEAD 修訂版的檔案.

    這裡的 舊版號 是該檔案在 .svn 目錄中的修訂版號, 而 新版號 是檔案庫中的 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: A conflict in the working copy obstructs the current operation
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 Chickenn
>>>>>>> .r2
        

通常你不會直接刪掉衝突標記與 Sally 的更動—當她看到 sandwich, 發現與她預期的不同時, 可是會嚇個老大一跳. 所以現在請拿起你的電話, 或是走過辦公室, 跟她解釋在義大利食品店, 是買不到德國泡菜的. [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 commit -m "Go ahead and use Sally's sandwich, discarding my edits."
        

棄踢: 使用 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
Transmitting file data .
Committed revision 4.
      

如果你並沒有指定 --message--file, 那麼 Subversion 會自動叫用你偏好的編輯器 (定義於環境變數 $EDITOR 中) 來編輯記錄訊息.

Tip

如果你在編輯器中寫了送交訊息, 然後決定要取消這次的送交, 只要不儲存更動, 直接結束編輯器即可. 如果你已經儲存了送交訊息, 只要把文字都刪除, 然後再儲存一次即可.

$ 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: Transaction is out of date
svn: Commit failed (details follow):
svn: out of date: `rules.txt' in txn `g'
$

此時, 你需要執行 svn update, 處理產生的合併或衝突, 然後再試著送交一次.

這樣就涵蓋了 Subversion 的基本工作流程. Subversion 還有許多功能, 可用來管理你的檔案庫與工作複本, 但是我們在本章所介紹的命令, 已足以讓你輕鬆地應付日常工作所需.

檢視歷史紀錄

我們早先曾說過, 檔案庫就像時光機器一樣, 它會記錄所有曾送交過的更動, 而且允許你檢視檔案與目錄, 以及伴隨的描述資料的過去修訂版, 以了解過往的歷史. 透過一個 Subversion 的命令, 你可以取出檔案庫 (或回復現有的工作複本), 讓它完全符合過去某一個修訂版號, 或是某一天的樣子. 不過呢, 有的時候你只是想 看看 過去, 而不是真的 回到 過去.

有幾個命令, 都能夠從檔案庫提供歷史資料:

svn log

給你比較廣的資訊: 附加在修訂版的記錄訊息, 以及每一個修訂版所更動的路徑.

svn diff

提供檔案隨著時間的確切變更內容.

svn cat

這是用來取得任何存於某一特定修訂版的檔案, 並將檔案內容顯示在螢幕上.

svn list

用來顯示任何指定修訂版的目錄內的檔案.

svn log

要找出某一檔案或目錄的歷史紀錄資訊, 請用 svn log 命令. svn log 可告訴你誰改了檔案或目錄, 該修訂版的日期與時間. 另外, 如果有提供送交時的記錄訊息, 它也會一併顯示出來.

$ svn log
------------------------------------------------------------------------
r3:  sally | Mon, 15 Jul 2002 18:03:46 -0500 | 1 line

Added include lines and corrected # of cheese slices.
------------------------------------------------------------------------
r2:  harry | Mon, 15 Jul 2002 17:47:57 -0500 | 1 line

Added main() methods.
------------------------------------------------------------------------
r1:  sally | Mon, 15 Jul 2002 17:40:08 -0500 | 2 lines

Initial import
------------------------------------------------------------------------
      

請注意預設記錄訊息是以 反向時間順序 顯示的. 如果你希望以特定順序顯示某個範圍的修訂版, 或是僅僅某一個修訂版而已, 請使用 --revision (-r) 選項:

$ svn log --revision 5:19    # 以時間順序, 顯示 5 到 19 的紀錄訊息

$ svn log -r 19:5            # 以反向時間順序, 顯示 5 到 19 的紀錄訊息

$ svn log -r 8               # 顯示修訂版 8 的紀錄訊息
      

你也可以檢視某一個檔案或目錄的訊息歷程. 舉個例子:

$ svn log foo.c
…
$ svn log http://foo.com/svn/trunk/code/foo.c
…
      

這些就 只會 顯示指定檔案 (或是 URL) 變動過的修訂版紀錄訊息.

如果你還想要更多有關於檔案或目錄的資訊, svn log 也有 --verbose (-v) 詳細訊息輸出選項. 由於 Subversion 允許你移動複製檔案或目錄, 追蹤檔案系統內的路徑更動是很重要的, 所以開啟詳細訊息輸出的話, svn log 會在修訂版的輸出中, 包含一串 變動路徑 的列表:

$ svn log -r 8 -v
------------------------------------------------------------------------
r8:  sally | 2002-07-14 08:15:29 -0500 | 1 line
Changed paths:
U /trunk/code/foo.c
U /trunk/code/bar.h
A /trunk/code/doc/README

Frozzled the sub-space winch.

------------------------------------------------------------------------
      

svn diff

我們在前面已經看過 svn diff— 它會以統一差異格式來顯示檔案的差異; 在我們送交至檔案庫之前, 可用來顯示我們對本地的工作複本所作的修改.

事實上, svn diff 總共有 三種 不同的用法:

  • 檢視本地端更動

  • 比較檔案庫與本地複本

  • 檔案庫與檔案庫之間的比較

檢視本地端更動

我們已經看過, 不帶引數執行 svn diff, 會比較 .svn 區域裡的 “原始未更動” 複本與工作複本之間的差異:

$ svn diff
Index: rules.txt
===================================================================
--- rules.txt	(revision 3)
+++ rules.txt	(working copy)
@@ -1,4 +1,5 @@
 Be kind to others
 Freedom = Responsibility
 Everything in moderation
-Chew with your mouth open
+Chew with your mouth closed
+Listen when others are speaking
$
        

比較檔案庫與本地複本

如果只有傳遞一個 --revision (-v) 的修訂版號, 那麼你的工作複本會與指定的檔案庫修訂版作比較.

$ svn diff --revision 3 rules.txt 
Index: rules.txt
===================================================================
--- rules.txt	(revision 3)
+++ rules.txt	(working copy)
@@ -1,4 +1,5 @@
 Be kind to others
 Freedom = Responsibility
 Everything in moderation
-Chew with your mouth open
+Chew with your mouth closed
+Listen when others are speaking
$
        

檔案庫與檔案庫之間的比較

如果透過 --revision (-r), 傳遞兩個以冒號隔開的修訂版號, 那麼這兩個修訂版號會直接拿來比較.

$ svn diff --revision 2:3 rules.txt 
Index: rules.txt
===================================================================
--- rules.txt	(revision 2)
+++ rules.txt	(revision 3)
@@ -1,4 +1,4 @@
 Be kind to others
-Freedom = Chocolate Ice Cream
+Freedom = Responsibility
 Everything in moderation
 Chew with your mouth closed 
$
        

svn diff 不但能拿來比較工作複本與檔案庫的檔案, 如果你的引數是 URL 的話, 就可以檢視檔案庫中的兩個物件, 甚至完全不需要先有工作複本. 如果你想看的更動, 是本地機器沒有工作複本的檔案, 這樣就非常有用了:

$ svn diff --revision 4:5 http://svn.red-bean.com/repos/example/trunk/text/rules.txt
…
$
        

svn cat

如果你想要檢視某一檔案早先的版本, 而不是兩個檔案之間的差異, 你可以使用 svn cat:

$ svn cat --revision 2 rules.txt 
Be kind to others
Freedom = Chocolate Ice Cream
Everything in moderation
Chew with your mouth closed
$
      

你可以將輸出直接重導到一個檔案中:

$ svn cat --revision 2 rules.txt > rules.txt.v2
$
      

你大概在想, 為何我們不使用 svn update --revision 將檔案更新至舊的修訂版. 有幾個理由, 讓我們會想用 svn cat.

首先, 你可能想要使用外部的 diff 程式, 來看一個檔案兩個不同版本之間的差異 (也許是圖形界面的, 也許你的檔案已經是這樣的格式, 輸出統一差異格式是完全沒有意思的). 在這種情況下, 你需要取得舊版本的複本, 重導向至一個檔案中, 然後將它與在你的工作目錄中的檔案一起傳給你的外部 diff 程式.

有的時候直接看整個先前版本的檔案, 要比只看它與其它修訂版的差異要方便得多.

svn list

svn list 命令可用來顯示檔案庫的目錄中有什麼檔案, 而不必實際將檔案下載至本地機器中:

$ svn list http://svn.collab.net/repos/svn
README
branches/
clients/
tags/
trunk/
      

如果你想要更詳細的列表, 加上 --verbose (-v) 選項, 就可以取得像這樣的輸出.

$ svn list --verbose http://svn.collab.net/repos/svn
   2755 harry          1331 Jul 28 02:07 README
   2773 sally               Jul 29 15:07 branches/
   2769 sally               Jul 29 12:07 clients/
   2698 harry               Jul 24 18:07 tags/
   2785 sally               Jul 29 19:07 trunk/
      

這些欄位告訴你, 這個檔案或目錄最後一次修改的修訂版, 修改它的使用者, 檔案則會有檔案大小, 上一次更新的日期, 以及該項目的名稱.

對歷史紀錄的最後叮嚀

除了以上的命令, 你還可以使用 svn updatesvn checkout, 加上 --revision 選項, 就可以將整個工作複本帶回到 “過去的時間[6]:

$ svn checkout --revision 1729 # 取出修訂版 1729 為新的工作複本
…
$ svn update --revision 1729 # 更新現有的工作複本為修訂版 1729
…
      

其它有用的命令

雖然不像本章前面討論過的命令那麼常用到, 這些命令還是有需要的時候.

svn cleanup

當 Subversion 修改你的工作複本時 (或是任何在 .svn 的資訊) 時, 它會試著儘量以安全的方式來進行. 在修改任何東西前, 會先將其意圖寫入一個紀錄檔, 執行紀錄檔裡的命令, 然後刪除紀錄檔 (這很像日誌檔案系統的設計). 如果一個 Subversion 的動作被中斷 (像是你按了 Control-C, 或是機器當掉了), 那麼紀錄檔會繼續存在磁碟之中. 藉由重新執行紀錄檔, Subversion 可以完成先前進行的動作, 而你的工作複本也能回復到一致的狀態.

而這就是 svn cleanup 所作的事: 它會搜尋你的工作複本, 執行任何遺留下來的紀錄檔, 移除動作進行時所使用的鎖定檔. 如果 Subversion 曾說過工作複本的某些地方被 “鎖定” 了, 那麼這就是你該執行的命令. 另外, svn status 也會在被鎖定的項目旁顯示 L:

$ svn status
  L    ./somedir
M      ./somedir/foo.c 

$ svn cleanup
$ svn status
M      ./somedir/foo.c
      

svn import

svn import 匯入命令, 是用來將未納入版本控制的檔案樹放進檔案庫的快速方法.

$ svnadmin create /usr/local/svn/newrepos
$ svn import mytree  file:///usr/local/svn/newrepos/fooproject
Adding  mytree/foo.c
Adding  mytree/bar.c
Adding  mytree/subdir
Adding  mytree/subdir/quux.h
Transmitting file data....
Committed revision 1.
      

以上的例子, 是將目錄 mytree 的內容, 直接放至檔案庫的 fooproject 目錄裡:

/fooproject/foo.c
/fooproject/bar.c
/fooproject/subdir
/fooproject/subdir/quux.h
      

摘要

現在我們已經涵蓋了大多數的 Subversion 用戶端命令. 顯而未提的部份, 是處理分支與合併 (參見 Chapter 4, 分支與合併), 以及處理性質 (參見 the section called “性質”) 的命令. 不過, 你可能會想花點時間略讀 Chapter 8, 完整 Subversion 參考手冊, 以了解 Subversion 還有哪些不同的命令— 還有你能如何使用它們, 讓你的工作能夠輕鬆點.



[1] 嗯, 除了一件事, 就是 “工作複本” 中的每個目錄都會有一個 .svn 子目錄. 不過現在講這個言之過早.

[2] 當然囉, 沒有任何東西會真正地自檔案庫中刪除— 它只是在檔案庫的 HEAD 版本之後被刪除了. 你還是可以取回被刪除的東西, 只要取出 (或更新工作複本) 到比你刪除的修訂版還早的版本即可.

[3] Subversion 使用它自己內部的差異引擎, 預設使用的是統一差異格式. 如果你想要使用不同的差異格式輸出, 請以 --diff-cmd 指定外部差異程式, 並以 --extensions 選項傳遞任何你想要使用的旗標. 舉個例子, 要以文脈輸出格式 (context output format) 來看 foo.c 的本地端差異, 但是要忽略空白字元的更動, 你就可以執行 “svn diff --diff-cmd /usr/bin/diff --extensions '-bc' foo.c”.

[4] 你絕對可以自行移除暫存檔, 不過 Subversion 可以幫你作好的話, 你還會想要自己來嗎? 我們不這麼認為.

[5] And if you ask them for it, they may very well ride you out of town on a rail.

[6] 看到了吧? 我們跟你講過, Subversion 是一個時光機器.

Chapter 4. 分支與合併

分支, 標記, 以及合併, 幾乎都是所有版本控制系統的共通概念. 如果你對這些概念不了解, 我們在這一章提供了一個不錯的介紹. 如果你熟悉這些概念的話, 那麼我們希望你能對 Subversion 如何實作這些概念的作法感到有興趣.

分支是版本控制的基本部份. 如果你要讓 Subversion 來管理你的資料, 那麼你一定會依賴這個功能. 本章假設你已經了解 Subversion 的基本概念. (Chapter 2, 基本概念).

何謂分支?

假設你的工作是負責管理公司部門的文件, 某種使用手冊什麼的. 有一天, 另一個部門跟你要同一份使用手冊, 不過他們行事不同, 要的內容也有 '些微' 的不一樣.

在這種情況下, 你該怎麼辦? 你會使用直接了當的方法, 直接產生文件的第二份複本, 然後開始分別維護這兩份文件. 隨著各個部份要求你作些小修改, 你會將這些修改整入某一份, 或是另一份文件.

對這兩份文件, 你常會想要作同樣的修改. 舉個例子, 如果你在第一份複本裡看到一個錯字, 很有可能第二份複本裡也會有. 畢竟這兩份文件幾乎都一樣; 它們只有在特定的一小部份不一樣而已.

這就是 分支 的基本概念 — 換句話說, 有一條發展途徑與另一條是獨立的, 但是如果回溯的時間夠久, 你會發現它們都有共同的歷史紀錄. 一個分支都是以某樣東西的複本開始其生命週期, 然後就自行發展下去, 有著自己的歷史紀錄發生.

Figure 4.1. 發展的分支

發展的分支

Subversion 有一些命令, 可以幫助你維持檔案與目錄的平行分支. 它讓你藉由複製資料來產生分支, 並且會記住這些複本是彼此相關的. 它也會幫你從一個分支中, 重製更動到另一個分支去. 最後, 它還能夠讓工作複本的不同部份反映出不同的分支, 這樣你就能在你每日的工作中, "混合搭配" 不同的發展路徑.

使用分支

此時, 你應該了解, 每一次的送交都會在檔案庫建立一個全新的檔案系統樹 (稱為 "修訂版"). 如果不是的話, 請回去 the section called “修訂版本” 閱讀有關修訂版的概念.

我們會使用第 2 章的範例, 作為本章的例子. 請記得你和你的協同工作者 Sally, 兩個人都共用同一個檔案庫, 其中包含 paintcalc 兩個計劃. 不過這一次有人在檔案庫中, 建立了兩個最頂層的目錄, 稱為 trunkbranches. 這兩個計劃本身是 trunk 的子目錄, 我們稍候會解釋.

Figure 4.2. 起始檔案庫的配置

起始檔案庫的配置

就像前面的一樣, 假設你與 Sally 兩個人都有 /trunk/calc 計劃的工作複本.

假設你被指定了一個工作, 要對該計劃進行全面性的重新整理. 這需要花費許多時間撰寫, 而且會影響計劃中全部的檔案. 問題是你並不想妨礙到 Sally 的工作, 此時她正在修正隨處可見的臭蟲. 她所依賴的, 就是計劃的每一份最新修訂版都是可用的. 如果你開始送交你所作的更動, 肯定會打斷 Sally 的工作.

有個方法, 就是與世隔絕: 在一兩個星期中, 你和 Sally 不分享資訊. 也就是說, 開始在工作複本中修改並重整所有的檔案, 但是在完成工作之前, 不進行任何送交或更新. 這樣會有一些問題. 首先, 它並不安全. 大多數的人喜歡時常將他們的工作存回至檔案庫中, 以防工作複本發生什麼致命的意外. 如果你在不同的電腦上工作 (也許你在兩台不同的電腦上, 都有 /trunk/calc 的工作複本), 你就需要在這兩台電腦之間手動複製你的修改, 不然就是只在一台電腦上工作. 最後, 當你完成了之後, 你可能會發現送交更動是相當困難的. Sally (或其它人) 也許會在檔案庫作一些更動, 而它們很難合併到你的工作複本中 — 尤其是全部一口氣來的時候.

較好的方式, 就是在檔案庫中建立你自己的分支, 或稱為發展支線. 這讓你能夠常常儲存進行到一半的工作, 又不會妨礙到其它人, 而你還可以選擇性地與其它協同工作者分享資訊. 稍候你就可以了解, 這是如何運作的.

建立一個分支

建立一個分支相當地容易 — 利用 svn copy 命令, 在檔案庫中建立計劃的複本. Subversion 不僅能夠複製單一檔案, 整個目錄也沒有問題. 既然如此, 你會想要產生一份 /trunk/calc 目錄的複本. 新的複本該放在哪裡? 哪裡都可以 — 這實際上是計劃的原則. 我們假設你所屬團隊的原則, 是將分支置於檔案庫裡的 /branches/calc 區域, 而你想將分支命名為 "my-calc-branch". 你想要建立一個新的目錄 /branches/calc/my-calc-branch, 它一開始為 /trunk/calc 的複本.

建立一份複本有兩種不同的方法. 我們先從麻煩的方法開始, 讓這個概念能夠更清楚. 一開始, 請先取出檔案庫根目錄 (/) 的工作複本:

$ svn checkout http://svn.example.com/repos bigwc
A  bigwc/branches/
A  bigwc/branches/calc
A  bigwc/branches/paint
A  bigwc/trunk/
A  bigwc/trunk/calc
A  bigwc/trunk/calc/Makefile
A  bigwc/trunk/calc/integer.c
A  bigwc/trunk/calc/button.c
A  bigwc/trunk/paint
A  bigwc/trunk/paint/Makefile
A  bigwc/trunk/paint/canvas.c
A  bigwc/trunk/paint/brush.c
Checked out revision 340.

現在, 建立複本就只是將兩個工作複本路徑傳給 svn copy 命令:

$ cd bigwc
$ svn copy trunk/calc branches/calc/my-calc-branch
$ svn status
A  +   branches/calc/my-calc-branch

在這裡, svn copy 命令會將 trunk/calc 工作目錄裡所有的東西複製到 branches/calc/my-calc-branch. 你可以從 svn status 命令看得出來, 新的目錄現在預計要被新增至檔案庫中. 不過請注意字母 A 旁邊的 + 號. 它表示這預計要新增的項目, 實際上是某項目的 複本, 而不是全新的東西. 當你送交你的更動時, Subversion 會在檔案庫複製 /trunk/calc 以建立 /branches/calc/my-calc-branch, 而不是將所有工作複本資料再經由網路重傳一次.

$ svn commit -m "Creating a private branch of /trunk/calc."
Adding      branches/calc/my-calc-branch
Committed revision 341.

現在介紹的是另一個建立分支較容易的方法, 我們該在一開始就告訴你: svn copy 可以處理兩個 URL.

$ svn copy http://svn.example.com/repos/trunk/calc \
           http://svn.example.com/repos/branches/calc/my-calc-branch \
      -m "Creating a private branch of /trunk/calc"

Committed revision 341.

這兩個方法實際上沒什麼差別. 這兩個方法都在修訂版 341 中建立一個新的目錄, 而這個新目錄是 /trunk/calc 的複本. 但是呢, 請注意第二個方法進行的是 立即 送交. [7] 這個方法比較容易的原因, 是它不需要你先取出檔案庫的東西. 事實上, 這個技巧甚至不要求你得先有工作複本.

Figure 4.3. 有新複本的檔案庫

有新複本的檔案庫

與分支共事

現在你已經建立了一個新的計劃分支, 你可以取出新的工作複本, 然後開始使用它:

$ svn checkout http://svn.example.com/repos/branches/calc/my-calc-branch
A  my-calc-branch/Makefile
A  my-calc-branch/integer.c
A  my-calc-branch/button.c
Checked out revision 341.

這個工作複本沒有什麼特殊的地方; 它只是映射出另一個檔案庫的位置. 但是當你送交更動時, Sally 在更新時也不會看到它們. 她的工作複本是在 /trunk/calc.

讓我們假裝一個星期已經過去了, 而我們有以下的送交更動:

  • 你修改了 /branches/calc/my-calc-branch/button.c, 產生修訂版 342.

  • 你修改了 /branches/calc/my-calc-branch/integer.c, 產生修訂版 343.

  • Sally 修改了 /trunk/calc/integer.c, 產生修訂版 344.

現在 integer.c 有兩條獨立的發展支線:

Figure 4.4. 檔案歷史的分支

檔案歷史的分支

如果你看看你自己的 integer.c 複本的歷程紀錄, 事情變得滿有趣的:

$ pwd
/home/user/my-calc-branch

$ svn log integer.c
------------------------------------------------------------------------
r343:  user | 2002-11-07 15:27:56 -0600 (Thu, 07 Nov 2002) | 2 lines

* integer.c:  frozzled the wazjub.

------------------------------------------------------------------------
r303:  sally | 2002-10-29 21:14:35 -0600 (Tue, 29 Oct 2002) | 2 lines

* integer.c:  changed a docstring.

------------------------------------------------------------------------
r98:  sally | 2002-02-22 15:35:29 -0600 (Fri, 22 Feb 2002) | 2 lines

* integer.c:  adding this file to the project.

------------------------------------------------------------------------

請注意 Subversion 會依時間回溯 integer.c 的歷程紀錄, 直到它被複製之時. (請記得你的分支於修訂版 341 時建立.) 現在看看 Sally 對她自己的檔案複本執行相同命令時, 會發生什麼事:

$ pwd
/home/sally/calc

$ svn log integer.c
------------------------------------------------------------------------
r344:  sally | 2002-11-07 15:27:56 -0600 (Thu, 07 Nov 2002) | 2 lines

* integer.c:  fix a bunch of spelling errors.

------------------------------------------------------------------------
r303:  sally | 2002-10-29 21:14:35 -0600 (Tue, 29 Oct 2002) | 2 lines

* integer.c:  changed a docstring.

------------------------------------------------------------------------
r98:  sally | 2002-02-22 15:35:29 -0600 (Fri, 22 Feb 2002) | 2 lines

* integer.c:  adding this file to the project.

------------------------------------------------------------------------

Sally 會看到她自己的修訂版 344 的更動, 但是不會看到你在修訂版 343 的更動. 就 Subversion 的認知, 這兩個送交更動影響的是檔案庫裡不同位置的不同檔案. 但是 Subversion 顯示這兩個檔案有共同的歷史. 在修訂版 341 建立分支複本之前, 它們兩個是同一個檔案. 這也是為什麼你和 Sally 兩個都會看到修訂版 303 與 98.

事情的內涵

這一節中, 有兩點課題是你應該要記住的.

  1. 不像其它的版本控制系統, Subversion 的分支是以 正常的檔案系統目錄 存在於檔案庫裡, 而不是其它獨立的維度.

  2. Subversion 內部並沒有 "分支" 的概念—只有複本而已. 如果你複製了一個目錄, 結果的目錄會是 "分支", 只是因為 賦與它這個意義. 你可以把它想成不同的意義, 或是以不同的方式處理它, 但是對 Subversion 而言, 它只是一個因複製而產生的普通目錄而己.

在分支之間複製更動

現在你與 Sally 在同一專案的不同平行分支上工作: 你的是私有分支, 而 Sally 則是 主幹 (trunk), 也就是主要的發展線.

對於有眾多貢獻人員的專案來說, 大多數的人都有主幹的工作複本是很常見的. 要是有哪個人需要進行可能會妨礙到主幹的長期變動, 標準的作法是建立一個私有的分支, 然後將更動都送交至該分支, 直到工作結束為止.

所以, 好消息就是你和 Sally 兩個不會互相影響, 壞消息是這兩個很容易就跑得 遠了. 請記得 "與世隔絕" 策略的一個問題, 就是在你結束分支的工作之後, 幾乎不可能將你所作的更動, 在不產生大量衝突的情況下, 把它合併回主分支去.

相反地, 你和 Sally 應該在你工作時, 也一直分享彼此的更動. 哪些更動應該被分享, 完全由你來決定; Subversion 提供你在各個分支之間 "複製" 選擇性更動的能力. 而當你已經處理完你的分支時, 整個分支的所有更動就可以複製回主幹去.

複製特定的更動

在前一節中, 我們提到你與 Sally 兩個在不同的分支中, 都修改了 integer.c. 如果你看看 Sally 在修訂版 334 的記錄訊息, 你可以看到她修改了幾個拼字錯誤. 毫無疑問的, 你這相同檔案的複本也還有同樣的拼字錯誤. 很有可能你未來對這個檔案的更動, 是針對有拼字錯誤的地方, 那麼某一天你要合併分支的時候, 也就很有可能會產生衝突. 所以最好是現在就取得 Sally 的更動, 在你開始在同一地方進行大改 之前.

現在該是使用 svn merge 命令的時候. 這個命令結果是很像 svn diff 命令 (這在第 3 章就介紹過了). 這兩個命令都可以比較兩個檔案庫裡物件, 並表示它們之間的差異. 舉個例子, 你可以要求 svn diff 為你顯示 Sally 在修訂版 344 時所作的更動:

$ svn diff -r 343:344 http://svn.example.com/repos/trunk/calc

Index: integer.c
===================================================================
--- integer.c	(revision 343)
+++ integer.c	(revision 344)
@@ -147,7 +147,7 @@
     case 6:  sprintf(info->operating_system, "HPFS (OS/2 or NT)"); break;
     case 7:  sprintf(info->operating_system, "Macintosh"); break;
     case 8:  sprintf(info->operating_system, "Z-System"); break;
-    case 9:  sprintf(info->operating_system, "CPM"); break;
+    case 9:  sprintf(info->operating_system, "CP/M"); break;
     case 10:  sprintf(info->operating_system, "TOPS-20"); break;
     case 11:  sprintf(info->operating_system, "NTFS (Windows NT)"); break;
     case 12:  sprintf(info->operating_system, "QDOS"); break;
@@ -164,7 +164,7 @@
     low = (unsigned short) read_byte(gzfile);  /* read LSB */
     high = (unsigned short) read_byte(gzfile); /* read MSB */
     high = high << 8;  /* interpret MSB correctly */
-    total = low + high; /* add them togethe for correct total */
+    total = low + high; /* add them together for correct total */
 
     info->extra_header = (unsigned char *) my_malloc(total);
     fread(info->extra_header, total, 1, gzfile);
@@ -241,7 +241,7 @@
      Store the offset with ftell() ! */
 
   if ((info->data_offset = ftell(gzfile))== -1) {
-    printf("error: ftell() retturned -1.\n");
+    printf("error: ftell() returned -1.\n");
     exit(1);
   }
 
@@ -249,7 +249,7 @@
   printf("I believe start of compressed data is %u\n", info->data_offset);
   #endif
   
-  /* Set postion eight bytes from the end of the file. */
+  /* Set position eight bytes from the end of the file. */
 
   if (fseek(gzfile, -8, SEEK_END)) {
     printf("error: fseek() returned non-zero\n");

svn merge 幾乎是完全一樣的. 但是它不會在終端機上顯示差異, 而是直接當成 本地修改 套用到你的工作複本上:

$ svn merge -r 343:344 http://svn.example.com/repos/trunk/calc
U  integer.c

$ svn status
M  integer.c

svn merge 的輸出, 顯示了你的 integer.c 複本已經修補過了. 它現在包含了 Sally 的更動 — 這個更動從主幹 "複製" 到你的私有分支內, 並以本地修改的形式存在. 此時, 你可檢視本地的更動, 確認它沒有問題.

在另一個場景中, 事情可能就不會這麼順利, 使得 integer.c 進入了衝突的狀態. 你必須使用標準的步驟 (參見第 3 章) 來解決衝突, 如果你認為合併根本不是個好主意的話, 直接捨棄它, 以 svn revert 來回復本地更動.

不過假裝你已經檢視過合併的更動, 你可以一如往常地使用 svn commit 來送交更動. 此時, 這個更動就被合併到你的 檔案庫分支. 在版本控制的術語中, 這個在不同的分支之間複製更動的動作, 稱為 移植 更動.

有件事要提醒一下: 雖然 svn diffsvn merge 的概念很類似, 但是它們在許多情況下的語法並不相同. 請閱讀第 8 章, 或是使用 svn help 以了解細節. 舉個例子, svn merge 需要一個工作複本路徑作為目標, 也就是要套用檔案樹更動的地方. 如果目標沒有指定的話, 它會假設你要執行以下的動作之一:

  1. 你想要合併目錄更動至目前的工作目錄中.

  2. 你想要合併特定檔案的更動至目前工作目錄中的同名檔案中.

如果你要合併一個目錄, 但是沒有指定目標路徑的話, svn merge 會假設第一種情況, 試著將更動套用到目前的工作目錄中. 如果你要合併一個檔案, 而那個檔案 (或同名檔案) 存在於目錄的工作目錄中, 那麼 svn merge 會假設第二種情況, 並試著將更動套用到同名的本地端檔案.

如果你要讓更動套用到別的地方, 你要這樣下:

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

重覆合併問題

合併更動聽起來很簡單, 但是實務上, 它可能讓你很頭痛. 問題在於如果你將更動從一個分支重覆複製到另一個分支的話, 你很有可能不小心合併相同的更動 兩次. 如果發生的話, 有可能並不會出什麼問題. 當 Subversion 套用更動時, 一般來說, 它會幫你記住這個檔案是已經有這個更動, 如果有的話, 不會發生任何事. 不過這個已套用的更動已經被更動的話, 你就會得到衝突. 理想的狀況下, Subversion 會自動防止重覆套用更動.

這是個困擾許多版本控制軟體的問題, 包括 CVS 與 Subversion. 目前而言, 在 Subversion 中, 唯一防止這個問題的方法, 就是記錄哪些更動已經合併了, 哪些還沒. 當你建立一個分支目錄時, 你必須記錄它是從哪個修訂版產生出來的 — 自己建立在別的地方. 當你合併一個修訂版 (或是一個範圍的修訂版) 到工作複本時, 你也得把它們記錄下來. 如果你忘了任何一個這樣的資訊, 你可以利用 svn log -v 分支目錄 的輸出來重新找出這個資訊. 但是這裡的重點, 是每一個後續的合併必須小心翼翼地手動建立, 以確定之前合併過的修訂版不會再重新合併一遍.

當然囉, Subversion 預計在發行版 1.0 之後, 再來解決這個問題. 所有這樣的合併資訊可以在性質描述資料中找到 (參見 the section called “性質”), 這樣 Subversion 有一天就能夠自動地防止重覆的合併.

合併整個分支

要讓我們所舉的實例更完整, 讓我們把時間往後拉. 幾天過去了, 主幹與你的私人分支都有了許多更動. 假設你已經完成你的私有分支; 它該有的功能, 或是臭蟲修正終於完成了, 現在你想把分支裡所有的更動都合併回主幹, 讓別人也能享用.

所以, 這種情況我們該如何使用 svn merge 呢? 請記住這個命令會比較兩個檔案樹, 然後把更動套用到工作複本去. 所以要接收這些更動, 你需要有主幹的工作複本. 我們假設你手邊還有一份 (完全更新的), 或者你已取出一份新的 /trunk/calc 工作複本.

但是要比較哪兩個檔案樹? 隨便一想, 答案似乎很明顯: 比較最新的主幹樹與你最新的分支樹就好了. 但是注意 — 這個想法是 錯的, 而且重創不少初學者! 由於 svn merge 的行為與 svn diff 相似, 比較最新的主幹與分支樹並 不會 直接給你你對分支所產生的更動: 它不只顯示出你新加入的分支更動, 還會 移除 你從未在分支作過的主幹更動.

想要表達只在你的分支中產生的更動, 你必須比較分支的初始狀態, 以及其最終狀態. 對你的分支執行 svn log , 你可以發現分支是在修訂版 341 產生的. 而分支的最終狀態, 只要使用修訂版 HEAD 即可.

那麼, 這就是最後的合併手續:

$ cd trunk/calc

$ svn merge -r 341:HEAD http://svn.example.com/repos/branches/calc/my-calc-bran
ch
U   integer.c
U   button.c
U   Makefile

$ svn status
M   integer.c
M   button.c
M   Makefile

[examine the diffs, compile, test, etc.]

$ svn commit -m "Merged all my-calc-branch changes into the trunk."
…

從檔案庫移除一個更動

svn merge 一個常用的用法, 就是回復一個已經送交的更動. 假設你以前在修訂版 303 所作的更動, 修改了 integer.c, 這根本就是錯誤的, 它根本就不該被送交. 你可以在工作複本中使用 svn merge 來 "反悔" 這個更動, 然後再將這個本地更動送交回檔案庫. 你只要指定一個 反向 的差異即可:

$ svn merge -r 303:302 http://svn.example.com/trunk/calc
U  integer.c

$ svn status
M  integer.c

$ svn commit -m "Undoing change committed in r303."
Sending    integer.c
Transmitting file data .
Committed revision 350.

一種想像檔案庫修訂版的方法, 就是將它們視為一群更動 (某些版本控制系統稱這為 更動組). 藉由使用 -r 選項, 你可以要求 svn merge 將一個更動組, 或是一個範圍的更動組, 套用到工作複本中. 在我們反悔更動的情況中, 我們要求 svn merge 逆向 套到更動組 #303 到我們的工作複本中.

你要謹記在心, 像這樣回復一個更動, 就跟其它的 svn merge 動作沒什麼兩樣, 所以你應該要使用 svn statussvn diff 來確認你的所作的跟你想要的是相同的, 然後再使用 svn commit 將最終的版本送交回檔案庫. 在送交之後, 該特定的更動組就不會再出現在 HEAD 修訂版中.

當然囉, 你可能會想: 呃, 這根本就不是反悔先前的送交, 不是嗎? 這個更動還是存在於修訂版 303 中. 如果某人取出了修訂版 303 與 349 之間的 某個 calc 計劃的版本, 就會看到這個錯誤的更動, 對吧?

是的, 沒有錯. 當我們講到 "移除" 一個更動時, 我們真正講的是 "從 HEAD 移除". 原先的更動還是會存在於檔案庫的歷程中. 大多數的情況下, 這樣已經夠好了. 大多數的人只對追蹤一個計劃的 HEAD 版本有興趣而已. 不過還是可能存在著特殊的情況, 你會希望完全地消除所有該送交的證據 (也許某人不小心送交了一個機密文件). 但是它非常地不容易, 因為 Subversion 的設計, 就是不會丟棄任何資訊. 修訂版是不可變動的檔案樹, 它們一個個建立在另一個之上. 從歷史進程移除一個修訂版會產生骨牌效應, 會對所有後續的修訂版造成混亂, 還有可能讓所有的工作複本都無效. [9]

切換工作複本

svn switch 命令可將現有的工作複本切換到不同的分支去. 雖然這個命令與分支運作沒什麼很直接的關係, 但是對使用者來說, 是個滿不錯的捷徑. 我們早先的例子中, 在建立了自己的私有分支之後, 你要從新的檔案庫目錄取出一個全新的工作複本. 但是你也可以改為要求 Subversion, 將你的 /trunk/calc 的工作複本改為映射到新的分支位置去:

$ cd calc

$ svn info | grep URL
URL: http://svn.example.com/trunk/calc

$ svn switch http://svn.example.com/branches/calc/my-calc-branch
U   integer.c
U   button.c
U   Makefile
Updated to revision 341.

$ svn info | grep URL
URL: http://svn.example.com/branches/calc/my-calc-branch

在 "切換" 到指定的分支之後, 你的工作複本與取出全新的目錄沒有什麼差別. 而且使用這個命令是更有效率的, 因為分支通常都只有小部份不同而己. 伺服器只會送出最小的, 足以讓你的工作複本反映出分支目錄的更動集合.

svn switch 命令也接受 --revision (-r) 選項, 所以工作複本並不一定得是分支的 “尖端”.

當然囉, 大多數的專案要比你的 calc 範例要複雜得多了, 包括了許多子目錄. Subversion 的使用者在使用分支時, 通常都依循一個特定的演算法:

  1. 將整個專案 '主幹' 複製到新的分支目錄.

  2. 只切換 部份 的主幹工作複本, 以反映分支.

換句話說, 如果使用者知道分支的工作只需要在某個特定子目錄下發生的話, 他們可以用 svn switch, 將他們工作複本的子目錄轉移到某特定的分支去. (有的時候, 使用者可能只要將某個工作檔案切換到分支去!) 這樣, 他們大部份的工作複本還是可以繼續接受正常的 '主幹' 更新, 但是被切換的部份就會保持不動 (除非有對該分支的送交更動). 這個功能對 "混合工作複本" 的概念, 提供了全新的維度, 工作複本不只是可以混合不同的工作修訂版, 還可以混合不同的檔案庫位置.

如果你的工作複本包含了來自不同檔案庫位置的切換子檔案樹, 它們還是能夠正常地工作. 當你更動時, 你會從每個子檔案樹收到修補更新. 當你送交更動時, 你的本地修改還是會以單一的, 不可分割的更動送回檔案庫.

請注意一點, 雖然你的工作複本可以對映到混合的檔案庫位置, 但是它們必須全在 同一個檔案庫中. Subversion 的檔案庫還無法彼此溝通; 這個功能預計在 Subversion 1.0 之後加入. [10]

由於 svn switch 實際上是 svn update 的變形, 它們有相同的行為; 當有新的資料從檔案庫來的時候, 任何在工作複本裡的本地修改還是會保存下來. 這可以讓你進行各式各樣的妙技.

舉個例子, 假設你有一個 /trunk 的工作複本, 並且對它進行了一些修改, 然後你突然發現, 應該修改的是分支才對. 沒問題! 當你以 svn switch 來切換工作複本到分支時, 本地更動還是會存在. 此時你就可以進行測試, 然後將它們送交回分支去.

標記

另一個常見的版本控制概念, 就是 標記. 標記只是一個計劃在時間軸上的 "快照". 在 Subversion 中, 這個概念已經隨處都是了. 每一個檔案庫的修訂版, 就是檔案系統在每一次送交之後的快照.

但是呢, 人們通常會想要標記一個便於記憶的名稱, 像是 "release-1.0". 而且他們只想要的, 只是一部份檔案系統子目錄的快照而已. 要記住某一個軟體的 release-1.0 版, 對應的是修訂版 4822 裡的某個特定子目錄, 畢竟是不太容易的.

建立一個簡單的標記

再一次地, svn copy 又來解救眾生了. 如果你想要建立一個完全符合 HEAD 修訂版的 /trunk/calc 的快照, 那就建立一個它的複本:

$ svn copy http://svn.example.com/repos/trunk/calc \
           http://svn.example.com/repos/tags/calc/release-1.0 \
      -m "Tagging the 1.0 release of the 'calc' project."

Committed revision 351.

這個例子假設 /tags/calc 的目錄已經存在了. 在複製動作完成後, 新的 release-1.0 目錄就永遠是這個計劃的快照, 完全符合你建立複本時的 HEAD 修訂版的內容. 當然囉, 你可能想要更準確地指定你所複製的修訂版, 以免別人在你沒注意時, 又送交了幾個更動. 所以如果你知道 /trunk/calc 的 350 修訂版, 就是你想要的快照的話, 你可以藉由傳給 svn copy 命令的 -r 350 選項來指定它.

不過先等一下: 這個建立標記的步驟, 不是和我們用來建立分支的步驟是一樣的嗎? 沒錯, 事實上, 根本就是一樣的. 在 Subversion 中, 標記與分支是沒有任何分別的. 兩者都只是透過複製而建立的尋常目錄而已. 就像分支一樣, 被複製的目錄之所以為 "標記", 只是因為 人們 決定它是: 只要沒有人再送交更動到這個目錄中, 它就永遠是個快照而已. 如果有人開始送交更動進去, 它就會變成一個分支.

如果你管理一個檔案庫的話, 有兩種方法可以用來管理標記. 第一個方法是 "放任": 把它視為計劃的原則, 決定標記放置的位置, 確定所有的使用者都知道如何處理複製進去的目錄. (也就是說, 確定他們都知道別送交更動進去.) 第二個方法就很偏執: 你可以利用 Subversion 內附的存取控制的腳本檔, 讓別人只能在標記區域建立新的複本, 但是無法送交東西進去. (###cross-ref a section that demonstrates how to use these scripts?). 不過呢, 偏執的方法並不是那麼必要的. 如果一個使用者不小心送交更動到標記目錄, 你只要像前一節所講的, 把它回復回來即可. 畢竟, 這可是版本控制軟體.

建立一個複雜的標記

有的時候, 你想要你的 "快照" 要比單一修訂版的單一目錄要來得複雜一點.

舉例來說, 假設你的計劃比我們的 calc 例子要大得多: 也許它包含了幾個子目錄, 以及許多的檔案. 在你工作之間, 你可能決定要建立一個工作複本, 它有特定的功能, 以及特定的臭蟲修正. 想要達到這樣的目的, 你可以藉由選擇性地將檔案與目錄固定在某一個修訂版, (大方地使用 svn update -r), 或是將目錄與檔案切換到某一個分支 (使用 svn switch). 當你完成之後, 你的工作複本就會變成來自不同版本的不同檔案庫位置的大雜燴. 但是在測試過後, 你確定這就是你需要的資料組合.

現在是作快照的時候了. 這裡無法將一個 URL 複製到另一個去. 在這個情況中, 你想要建立一個你現在調整的工作複本的快照, 並將它存到檔案庫中. 很幸運的, svn copy 事實上有四種不同的使用方法 (你可以參考第 8 章), 包括將工作複本的檔案樹複製到檔案庫的功能:

$ ls
./    ../   my-working-copy/

$ svn copy my-working-copy http://svn.example.com/repos/tags/calc/mytag

Committed revision 352.

現在在檔案庫中, 有了一個新的目錄 /tags/calc/mytag, 它就是完全符合你的工作複本的快照 — 混合的修訂版, url, 所有的東西.

有別的使用者找出這個功能的有趣用法. 有的情況下, 你的工作複本可能有一群本地更動, 而你想讓你的協同工作者看看. 除了執行 svn diff, 將修補檔送過去以外 (這沒辦法捕捉到檔案樹的變化), 你可以改用 svn copy 來 "上載" 你的工作複本至某一個檔案庫的私有區域. 你的協同工作者就可以取出一個一模一樣的工作複本, 或是利用 svn merge 來接收你所作的更動.

分支維護

你大概注意到, Subversion 相當地有彈性. 因為它以相同的機制 (目錄複本) 來實作分支與標記, 也因為分支與標記是出現在正常的檔案系統空間中, 有許多人覺得 Subversion 真是太可怕了. 它可以說是 有彈性了. 在本節中, 我們提供了幾點建議, 作為你依時間來安排與管理資料的參考.

檔案庫配置

管理檔案庫有數種標準建議的方法. 大多數人會建立一個 trunk 的目錄來放置 "主要" 的發展途徑, 建立一個 branches 目錄來放置分支複本, 以及建立一個 tags 目錄來放置標記複本. 如果一個檔案庫只放置一個計畫, 那麼大多數的人會建立這樣的最頂層目錄:

/trunk
/branches
/tags

如果一個檔案庫包含了多個計劃, 那麼人們建立目錄配置的方法, 會以分支為準:

/trunk/paint
/trunk/calc
/branches/paint
/branches/calc
/tags/paint
/tags/calc

…或是依計劃:

/paint/trunk
/paint/branches
/paint/tags
/calc/trunk
/calc/branches
/calc/tags

當然囉, 你還是有忽略這些常用配置的自由. 你可以自己建立各種變化, 只要最能符合你與你的團隊需要即可. 請記住, 不管你作什麼樣的選擇, 都不表示就永遠都不可變更. 不管何時, 你都可以重新整理你的檔案庫. 由於分支與標記只是普通的目錄, 你可以利用 svn move 命令來移動或更名這些目錄. 從一種配置換到另一種, 只是對伺服端進行一連串的移動而己; 如果你不喜歡檔案庫現在的目錄結構, 直接動它們吧.

資料生命週期

另一個Subversion 模型的特色, 就是分支與標記可以是有限生命週期的, 就像其它受控管的項目一樣. 舉個例子, 假設你最後完成了個人 calc 計劃分支裡的工作. 在將你所有的更動送交回 /trunk/calc 後, 你的私有分支目錄就沒有必要再存在了:

$ svn delete http://svn.example.com/repos/branches/calc/my-calc-branch \
             -m "Removing obsolete branch of calc project."

Committed revision 375.

現在, 你的分支就消失了. 它當然不是真的不見了: 這個目錄只是從 HEAD 修訂版消失, 不會再引起別人的注意. 如果你檢視更早一點的修訂版 (使用 svn checkout -r, svn switch -r, 或是 svn list -r), 你還是可以看到以前的分支.

如果只是瀏覽被刪除的目錄還不夠, 你還是可以再讓它回來. Subversion 要恢復舊資料相當地容易. 如果你想把被刪除的目錄 (或檔案) 叫回 HEAD 修訂版, 只要使用 svn copy -r 將它從以前的修訂版複製回來就好了:

$ svn copy -r 374 http://svn.example.com/repos/branches/calc/my-calc-branch \
                  http://svn.example.com/repos/branches/calc/my-calc-branch

Committed revision 376.

在我們的例子中, 你的個人分支有個滿短的生命週期: 你可能只是把它建立起來, 用來修正臭蟲, 或是實作一個新的功能. 在工作完成後, 這個分支也沒有存在的必要了. 不過在軟體發展的過程中呢, 長時間同時有兩個 "主要" 的發展主線也是很常見的. 舉個例子, 假設現在該對外發佈一個穩定的 calc 的版本, 而你也知道大概要花好幾個月才能把臭蟲都清掉. 你不想讓別人對這個計劃加新功能, 但是你也不想叫所有的發展人員都停止工作, 所以你改為計劃建立一個不會有太大變更的 "stable" 分支:

$ svn copy http://svn.example.com/repos/trunk/calc \
         http://svn.example.com/branches/calc/stable-1.0
         -m "Creating stable branch of calc project."

Committed revision 377.

現在設計人員可以繼續自由地將最先進的 (或實驗性的) 功能加到 /trunk/calc 中, 你也可以宣佈 /branches/calc/stable-1.0 只接受臭蟲修正的計劃原則. 也就是說, 大家還是可以繼續對主體工作, 但是選擇性將臭蟲修正移植到穩定分支去. 就算在穩定分支已經送出去了, 你可能還是得繼續維護該分支很長一段時間 — 換句話說, 只要你繼續為客戶支援該發行版本, 就得繼續維護.

摘要

我們在本章中, 涵蓋了相當大的範圍. 我們討論了標記與分支的概念, 也示範了 Subversion 如何藉由透過 svn copy 命令複製目錄, 實作這樣的概念. 我們示範如何使用 svn merge 命令, 從一個分支複製更動到另一個分支, 或是回復錯誤的更動. 我們也展示了以 svn switch 來建立混合位置的工作複本. 我們還提到在檔案庫中, 如何管理分支的結構與其生命週期.

請注意 Subversion 的 mantra: 分支是廉價的. 所以請大方地使用它們吧!



[7] Subversion 不支援跨檔案庫的複製. 當 svn copysvn move 與 URL 一同使用時, 你只能複製同一個檔案庫裡的項目.

[8] 未來呢, Subversion 打算使用 (或發明) 可以描述檔案樹更動的擴充修補格式.

[9] 不過 Subversion 計劃還是有個計劃, 打算在某天實作 svnadmin obliterate 命令, 可以達到永久刪除資訊的任務.

[10] 要是伺服器的 URL 變更了, 而你又不想拋棄現有的工作複本, 你 可以svn switch--relocate 一同使用. 請參閱 Chapter 8, 完整 Subversion 參考手冊svn switch 一節, 以瞭解更多的資訊.

Chapter 5. Repository 管理

Subversion 的檔案庫是個中央倉儲, 用來存放任意數量專案的受版本控管資料. 因為如此, 它成為管理員集中注意的焦點. 檔案庫一般並不需要太多的照顧, 但是了解如何適當地設定它, 照顧它是很重要的, 如此才能避免一些潛在性的問題, 而實際的問題得以安全地解決.

在本章中, 我們會討論如何建立與設定 Subversion 的檔案庫, 以及如何開放檔案庫的網路存取. 我們也會提到檔案庫的維護, 包括 svnlooksvnadmin 工具的使用方法 (它們都包含 Subversion 中). 我們也會說明常見的問題與錯誤, 並提供幾個如何安排檔案庫資料的建議.

如果你存取 Subversion 的檔案庫的目的, 只是打算成為一個單純將資料納入版本控管的使用者的話 (也就是透過 Subversion 用戶端), 那你可以完全跳過本章. 但是如果你是, 或想要成為 Subversion 的檔案庫管理員, [11] 本章絕對是你要投注注意力的地方.

當然了, 一個人無法成為檔案庫的管理員, 除非他有檔案庫可管理.

檔案庫的基本知識

了解異動與修訂版

就概念來講, Subversion 的檔案庫是一連串的目錄樹. 每一個目錄樹, 就是檔案庫的目錄與檔案於不同時間點的快照. 這些快照是使用者進行作業的結果, 稱為修訂版.

每一個修訂版, 是以異動樹 (transaction tree) 開始其生命週期. 在送交發生時, Subversion 用戶端會建立一個異動, 其中反映了本地端的更動 (以及自用戶端開始進行送交的任何額外更動), 然後指示檔案庫將該樹儲存為下一個快照. 如果送交成功的話, 這個異動就會實際成為新的修訂版樹, 並被賦與新的修訂版號. 如果送交因為某些原因失敗的話, 這個異動會被捨棄, 用戶端會被通知該動作失敗.

更新的動作也類似這樣. 用戶端會建立一個暫時的異動樹, 反映工作複本的狀態. 接著, 檔案庫會將異動樹與指定的修訂版樹作比較 (通常是最新的, 或是 “最年輕的” 樹), 然後送回指示用戶端該如何進行何種更動的資訊, 以將工作複本轉換成該修訂版樹的樣子. 在更新完成後, 這個暫時的異動樹就會被刪除.

使用異動樹, 是對檔案庫的版本控制檔案系統產生永久更動的唯一方法. 但是, 了解異動的生命週期極富彈性是很重要的. 在更動的情況下, 異動只是馬上會被消滅的暫時檔案樹而已. 在送交的情況下, 異動會變成固定的修訂版 (如果失敗的情況下, 則是被移除). 如果有錯誤或是臭蟲的話, 異動很有可能不小心遺留在檔案庫之中 (雖然不會影響什麼東西, 但是會佔用空間).

理論上, 某一天整個流程能夠發展出對異動生命週期能夠有更細部的控制. 可以想像一下成一個系統, 在用戶端已經描述完它對檔案庫所產生的更動後, 每個無法成為修訂版的異動會先放到某個地方. 如此, 每個新的送交就可以被某個人檢閱, 也許是主管, 也許是工程品管小組, 他們可以決定是要接受這個異動成為修訂版, 還是捨棄它.

這些跟檔案庫管理有什麼關係呢? 答案很簡單: 如果你要管理一個 Subversion 的檔案庫, 除了監控檔案庫的情況外, 你還必須檢視修訂版與異動情況.

無版本控制的性質

Subversion 檔案庫的異動與修訂版也可以附加性質上去. 這些性質只是基本的鍵值對應, 可以用來儲存與對應檔案樹有關的資料. 這些鍵值與你的檔案樹資料一樣, 都是儲存在檔案庫的檔案系統之中.

對於儲存某些檔案樹的資料, 但是這些資料並不完全與這些目錄與檔案相關時, 修訂版與異動性質是很有用的 — 像是不被用戶端工作複本管理的性質. 例如, 當一個新的送交異動於檔案庫中建立時, Subversion 會對這個異動新增一個名為 svn:date 的性質 — 一個用來表示異動何時建立的時間戳記. 當送交程序結束後, 而該異動被提昇為一個固定的修訂版時, 這個檔案樹會再多出儲存修訂版作者的使用者名稱性質 (svn:author), 以及一個用來儲存該修訂版的紀錄訊息性質 (svn:log).

修訂版與異動性質都是 無版本控制的性質 (unversioned property) — 因為它們被修改後, 原先的值就永遠被捨棄了. 另外, 雖然修訂版樹本身是不會再變的, 附加在它們上的性質卻不是. 你可以在日後對修訂版性質作新增, 移除, 以及修改的動作. 如果你送交了一個新的修訂版, 但是日後發現紀錄訊息寫錯了, 或是有拼字錯誤的話, 你可以直接以正確的新訊息蓋過 svn:log 的值.

檔案庫的建立與設定

建立一個 Subversion 的檔案庫出乎意料地簡單. Subversion 所提供的 svnadmin 工具, 有個專門處理這件事的子命令. 要建立一個新的檔案庫, 只要執行:

$ svnadmin create path/to/repos

這會在目錄 path/to/repos 裡建立一個新的檔案庫. 這個新的檔案庫會以修訂版 0 開始其生命週期, 裡面除了最上層的根目錄 (/), 什麼都沒有. 剛開始, 修訂版 0 還有一個單一的修訂版性質 svn:date, 會設定在檔案庫初建立的時間.

你可能注意到 svnadmin 的路徑引數只是一個普通的檔案系統路徑, 而不是像 svn 用戶端程式用來表示檔案庫的 URL. svnadminsvnlook 都被視為是伺服器端的工具 — 它們是在檔案庫所在的機器上, 對檔案庫作檢視或修改之用. Subversion 新手常犯的錯誤, 就是試著將 URL (即使是 "本地端" 的 file: 路徑) 傳給這兩個程式.

所以, 在你執行 svnadmin create 命令之後, 這個目錄中就會有全新的 Subversion 檔案庫. 讓我們看一下在這個目錄裡產生了什麼東西.

$ ls repos
dav/  db/  format  hooks/  locks/  README.txt

除了 README.txtformat 檔以外, 檔案庫是由一群子目錄組成. 就像 Subversion 其它部份的設計一樣, 模組化是很重要的原則, 而且階層式組織要比雜亂無章好. 以下是新的檔案庫目錄中, 各個項目的簡單敘述:

dav

提供給 Apache 與 mod_dav_svn 使用的目錄, 讓它們儲存內部資料.

db

主要的 Berkeley DB 環境, 裡面都是儲存 Subversion 檔案系統 (就是你置於版本控制的全部資料所在) 的資料庫表格.

format

一個內容為一個整數的檔案, 表示檔案庫配置的版本號碼.

hooks

一個放置 hook 腳本檔範本的目錄 (如果你有安裝的話, 還有腳本檔本身的檔案).

locks

用來放置 Subversion 檔案庫鎖定資料的目錄, 用來追蹤存取檔案庫的用戶端.

README.txt

這個檔案只是用來告知使用者, 他們在看的是 Subversion 的檔案庫.

一般來說, 你不需要自已 “手動” 處理檔案庫. svnadmin 工具就足以用來處理對檔案庫的任何改變, 不然也可以使用協力廠商的工具 (像是 Berkeley DB 工具組) 來調整部份的檔案庫. 不過還是有些例外, 我們會在這裡提到.

Hook scripts

Hook scripts

掛勾 (hook) 是某些檔案庫事件所觸發的程式, 像是建立新的修訂版時, 修改未納入版本控制的性質時. 每一個掛勾都會得到足夠的資訊, 可以分辨出得到的是什麼事件, 針對哪個 (哪些) 目錄, 以及被誰觸發. 依掛勾輸出或傳回狀態的不同, 掛勾程式可以繼續, 停止, 或是暫停該動作.

hook 子目錄中, 預設是放置各個檔案庫掛勾的範本.

$ ls repos/hooks/
post-commit.tmpl          pre-revprop-change.tmpl
post-revprop-change.tmpl  start-commit.tmpl
pre-commit.tmpl           

每一個 Subversion 檔案庫實作出來的掛勾, 都各自有對應的範本, 只要檢視這些範本的內容, 你就知道每一個命令稿是被什麼觸發的, 什麼資料會傳給命令稿. 每個範本還包含包含一些範例, 示範如何與其它的 Subversion 隨附工具一同完成某些常見工作. 要安裝一個真正可用的掛勾程式, 只要把可執行檔案或命令稿置於 repos/hooks 目錄下, 並且讓它能以掛勾的名稱 (像是 start-commitpost-commit) 執行即可.

在 Unix 平台上, 這表示提供與掛勾完全同名的命令稿或是程式即可 (可以是 shell 命令稿, Python 程式, 編譯過的 C 二進制程式, 或是任何的可能). 當然囉, 範本檔案存在的目的, 並不只有提供資訊而已 — 在 Unix 平台上安裝掛勾最簡單的方法, 就是把適當的範本拷貝成去掉 .tmpl 副檔名的新檔案, 修改掛勾的內容, 然後確定命令稿是可執行的. 但是 Windows 是利用檔案副檔名來決定它是不是可執行檔, 所以你必須提供主檔名為掛勾名稱的程式, 再加上 Windows 認為是可執行檔的副檔名, 像是視為程式的 .exe.com, 以及批次檔的 .bat.

目前 Subversion 檔案庫實作的掛勾有五個:

start-commit

這個掛勾在送交異動還沒建立之前就會執行, 通常用來決定使用者是否有送交的權限. 檔案庫會傳遞兩個引數給這個程式: 檔案庫的路徑, 以及想要進行送交的使用者名稱. 如果程式傳回一個非零的結束值, 在送交異動還沒建立之前, 送交就會結束.

pre-commit

本掛勾執行的時間為異動完成之後, 送交之前. 一般來講, 這個掛勾是用來阻止因內容或位置而不被允許的送交 (舉個例子, 你的站台可能要求某個分支的送交都必須包含臭蟲追蹤的申請單號碼, 或是進來的記錄訊息不可為空白的). 檔案庫會傳遞兩個引數給這個程式: 檔案庫的路徑, 以及準備送交的異動名稱. 如果程式傳回一個非零的結束值, 送交會被中止, 而異動會被刪除.

Subversion 的發行檔案包含了幾個存取控制的命令稿 (位於 Subversion 源碼樹的 tools/hook-scripts 目錄中), 可在 pre-commit 中使用, 以進行更細微的存取控制. 在這個時候, 除了 httpd.conf 所提供的以外, 這是管理員唯一可以進行細微的存取控制的方法. 在未來的 Subversion 中, 我們計劃直接在檔案系統實作 ACL。

post-commit

本掛勾執行的時間是在異動送交, 新修訂版被建立之後. 大多數的人用這個掛勾來寄出關於本次送交的電子郵件, 或是建立檔案庫的備份. 檔案庫會傳遞兩個引數給這個程式: 檔案庫的路徑, 以及新建立的修訂版號. 本程式的結束碼會被忽略.

Subversion 的發行檔案包含了一個 commit-email.pl 命令稿 (位於 Subversion 源碼樹的 tools/hook-script 目錄中), 可以用來寄送包含描述指定送交的電子郵件. 這個郵件包含了更動路徑列表, 該送交所對應的記錄訊息, 使用者, 送交的日期,以及一個以 GNU diff 樣式表示的本次更動差異.

另一個 Subversion 隨附的有用工具是 hot-backup.py 命令稿 (位於 Subversion 源碼樹的 tools/backup/ 目錄內). 這個命令稿可以進行 Subversion 檔案庫的即時備份 (這是 Berkeley DB 資料庫後端支援的功能), 可以用來建立每一次送交的檔案庫快照, 作為歸檔紀錄, 或是緊急回復之用.

pre-revprop-change

由於 Subversion 的修訂版性質並未納入版本控制, 修改這樣的性質 (舉個例子, svn:log 送交訊息性質) 將會永遠地覆寫原先的數值. 由於資料有可能會消失, Subversion 提供了這個掛勾 (以及相對應的 post-revprop-change), 以便檔案庫管理員能夠依其意願, 利用其它外部機制來記錄曾作過的更動.

這個掛勾在檔案庫即將發生更動之前執行. 檔案庫會傳遞四個引數給這個掛勾: 檔案庫的路徑, 更動性質所在的修訂版, 產生更動的認證使用者名稱, 以及性質名稱本身.

post-revprop-change

如同我們早先提過的, 這個掛勾是 pre-revprop-change 的對應掛勾. 事實上, 為了偏執狂著想, 如果 pre-revprop-change 不存在的話, 這個命令稿不會執行. 這兩個掛勾都存在的話, post-revprop-change 掛勾只會在修訂版性質更動之後才會執行, 一般是用來寄送包含更動性質的新數值的電子郵件. 檔案庫會傳遞四個引數給這個掛勾: 檔案庫的路徑, 該性質所在的修訂版, 產生更動的認證使用者名稱, 以及性質名稱.

Subversion 的發行檔案包括了一個 propchange-email.pl 命令稿 (位於 Subversion 源碼樹的 tools/hook-scripts/ 目錄中), 可用來寄送包含修訂版性質更動細節的電子郵件 (或/且附加至記錄檔中). 這個郵件會包含更動性質所在的修訂版與名稱, 產生更動的使用者, 以及新的性質數值.

Subversion 會試著以正在存取 Subversion 檔案庫的使用者身份來執行掛勾程式. 在大多數的情況下, 檔案庫是透過 Apache HTTP 伺服器與 mod_dav_svn 存取的, 所以使用者會與 Apache 執行的使用者身份相同. 掛勾程式本身必須以作業系統層級的權限進行設定, 以便讓使用者可以執行. 另外, 這也表示任何會被掛勾程式直接或間接存取的檔案或程式 (包括 Subversion 檔案庫本身), 都會以同一個使用者的身份來進行存取. 換句話說, 請注意任何因檔案權限而可能造成的潛在問題, 以免掛勾無法進行你想進行的工作.

Berkeley DB 設定

Berkeley DB 環境有它自己預設的設定值, 像是任何時間可使用的鎖定數目, 或是 Berkeley 日誌記錄檔的截止大小等等. Subversion 檔案系統的程式會額外地為幾個 Berkeley DB 設定選項選擇其它的預設值. 不過, 你的檔案庫可能會有自己獨特的資料集合與存取型態, 也許就需要不同的設置選項數值.

Sleepycat (Berkeley DB 的製造廠商) 的人員瞭解不同的資料庫有不同的需求, 所以他們提供了一種執行時期的機制, 可以更動許多 Berkeley DB 環境的設定選項數值. Berkeley 會檢查每個環境目錄是否存在著一個名為 DB_CONFIG 的檔案, 然後剖析其中的為某個特定 Berkeley 環境所用的選項.

檔案庫的 Berkeley 設定檔位於 db 環境目錄中, 也就是 repos/db/DB_CONFIG. Subversion 在建立檔案庫時, 就會自行建立這個檔案. 這個檔案一開始包含了幾個預設值, 也包含了幾個 Berkeley DB 線上文件的參照, 這樣你可以自己去查這些選項所代表的意義. 當然囉, 你可以任意在 DB_CONFIG 檔案裡加入任何支援 Berkeley DB 選項. 不要請記得, 雖然 Subversion 不會試著去讀取或解讀這個檔案的內容, 或是使用任何裡面的選項, 你還是得避免某些設定選項, 讓 Berkeley DB 產生出 Subversion 不知如何處理的行為. 另外, 對 DB_CONFIG 的更動並不會馬上生效, 除非你回復了資料庫環境 (使用 svnadmin recover.)

檔案庫維護

管理員的工具箱

svnlook

svnlook 是 Subversion 提供的工具, 用來檢視檔案庫不同的修訂版與異動. 本程式完全不會試著去修改檔案庫 — 這是 “唯讀” 工具. svnlook 通常用在檔案庫掛勾程式中, 用來回報檔案庫即將送交的更動 (用在 pre-commit 掛勾時), 或剛送交的更動 (用在 post-commit 掛勾時). 檔案庫管理員也許會將這個工具用於診斷之用.

svnlook 的語法很直接:

$ svnlook help
general usage: svnlook SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]
Note: any subcommand which takes the '--revision' and '--transaction'
      options will, if invoked without one of those options, act on
      the repository's youngest revision.
Type "svnlook help <subcommand>" for help on a specific subcommand.
…

幾乎每一個 svnlook 的子命令可以針對修訂版或異動樹運作, 顯示檔案樹的資訊, 或是它與檔案庫的前一個修訂版之間有什麼差異. 你可以使用 --revision--transaction 選項來指定要檢視的修訂版與異動. 請注意, 雖然修訂版號看起來像是自然數, 但是異動名稱是包含英文字母與數字的字串. 請記得檔案系統只允許瀏灠未送交的異動 (未變成新修訂版的異動). 大多數的檔案庫不會有這樣的異動, 因為異動不是已送交了 (這讓它們失去被檢視的資格), 就是被中止然後移除.

如果完全沒有 --revision--transaction 選項的話, svnlook 會檢視檔案庫中最年輕的 (或 “HEAD”) 修訂版. 所以這兩個命令作的事情都一樣, 如果 19 是 /path/to/repos 檔案庫的最年輕修訂版:

$ svnlook info /path/to/repos
$ svnlook info /path/to/repos --revision 19

這些子命令規則的唯一例外, 就是 svnlook youngest 子命令, 它不需要指定選項, 就只會印出 HEAD 修訂版號.

$ svnlook youngest /path/to/repos
19

svnlook 的輸出是設計為人類與機器都易讀的. 以 info 子命令的輸出作為例子:

$ svnlook info path/to/repos
sally
2002-11-04 09:29:13 -0600 (Mon, 04 Nov 2002)
27
Added the usual
Greek tree.

info 的輸出命令定義如下:

  1. 作者, 後接換行字元.

  2. 日期, 後接換行字元.

  3. 紀錄訊息的字元數目, 後接換行字元.

  4. 紀錄訊息, 後接換行字元.

這個輸出是人類可解讀的, 像是日期戳記等具有意義的項目, 皆以文字形式表示, 而不是用看不懂的方式 (像是某個可愛外星人出現至今的毫微秒計數). 但是這個輸出也是機器可讀 — 因為紀錄訊息可以有好幾行, 沒有長度的限制, 所以 svnlook 在訊息之前提供了訊息的長度. 這讓命令稿與其它包裝這個程式的命令稿, 可以對記錄訊息作出聰明的決定, 要是這段資料不是串流的最後一個部份時, 至少也知道要略過幾個位元組.

另一個 svnlook 常見的用法, 就是檢視某個修訂版或異動樹的內容. svnlook tree 會顯示要求檔案樹的目錄與檔案系統 (還可以選擇顯示每一個路徑的檔案系統節點修訂版編號), 藉由檢視其輸出, 對於管理員決定是否可以移除某個看起來無效的異動是很有用的, 對 Subversion 發展人員在診斷發生檔案系統相關的問題時也很有用.

$ svnlook tree path/to/repos --show-ids
/ <0.0.1>
 A/ <2.0.1>
  B/ <4.0.1>
   lambda <5.0.1>
   E/ <6.0.1>
    alpha <7.0.1>
    beta <8.0.1>
   F/ <9.0.1>
  mu <3.0.1>
  C/ <a.0.1>
  D/ <b.0.1>
   gamma <c.0.1>
   G/ <d.0.1>
    pi <e.0.1>
    rho <f.0.1>
    tau <g.0.1>
   H/ <h.0.1>
    chi <i.0.1>
    omega <k.0.1>
    psi <j.0.1>
 iota <1.0.1>

svnlook 還可以進行其它的查詢, 顯示我們早先提到過的資訊的一部份, 回報某個指定的修訂版或異動中更動的路徑, 顯示檔案與目錄產生的文字與性質的差異, 諸如此類的資訊. 以下是目前 svnlook 所支援的子命令, 簡單的描述, 以及他們輸出的東西:

author

顯示檔案樹的作者.

cat

顯示檔案樹裡的檔案內容.

changed

列出檔案樹中, 所有更動的檔案與目錄.

date

顯示檔案樹的日期戳記.

diff

顯示更動檔案的統一差異格式.

dirs-changed

顯示檔案樹裡本身更動的目錄, 或是其下子檔案有更動的目錄.

history

顯示某個納入版本控制路徑的歷程紀錄點 (更動或複製發生的地方).

info

顯示檔案樹的作者, 日期戳記, 紀錄訊息字元計數, 以及紀錄訊息.

log

顯示檔案樹的紀錄訊息.

propget

顯示設定於檔案樹路徑的性質內容.

proplist

顯示設定於檔案樹路徑的性質名稱與內容.

tree

顯示檔案樹列表, 還可選擇顯示關聯到每一個路徑的檔案系統節點修訂版編號.

uuid

顯示檔案樹的唯一使用者代號 (UUID).

youngest

顯示最年輕的修訂版號.

svnadmin

svnadmin 程式是檔案庫管理員最好的朋友. 除了可以建立 Subversion 檔案庫, 這個程式還可以讓你對檔案庫進行幾種維護動作. svnadmin 的語法與 svnlook 很類似:

$ svnadmin help
general usage: svnadmin SUBCOMMAND REPOS_PATH  [ARGS & OPTIONS ...]
Type "svnadmin help <subcommand>" for help on a specific subcommand.

Available subcommands:
   create
   dump
   help (?, h)
…

我們已經提過 svnadmincreate 子命令 (見 the section called “檔案庫的建立與設定”). 在本章中, 我們會仔細講解大多數其它的命令. 現在, 我們先簡單地看看每個可用的子命令提供什麼樣的功能.

create

建立一個新的 Subversion 檔案庫.

deltify

在指定的修訂版範圍中, 對其中變動的路徑作 deltification. 如果沒有指定修訂版的話, 則直接對 HEAD 修訂進行.

dump

以可移植傾印格式, 傾印指定範圍修訂版的檔案庫內容.

list-dblogs

列出關聯至檔案庫的 Berkeley DB 紀錄檔案的路徑. 這個列表包含了所有的紀錄檔 — Subversion 仍在使用的, 以及不再使用的.

hotcopy

產生檔案庫的即時備份. 你可以在任何時候執行這個命令以安全地產生檔案庫的複本, 毋須理會是否有其它的行程也同時在存取檔案庫.

list-unused-dblogs

列出所有關聯到檔案庫的, 但是已經不再使用的 Berkeley DB 紀錄檔. 你可以安全地自檔案庫的目錄結構中移除這些紀錄檔, 也可以將其備份下來, 日後要在遭遇災難事件後, 可於回復檔案庫時使用.

load

從一個使用可移植傾印格式、由 dump 子命令產生的資料串流中, 載入一組修訂版至檔案庫中.

lstxns

列出目前存在於檔案庫中, 尚未送交的 Subversion 異動.

recover

對一個有需要的檔案庫進行回復步驟, 可能因為曾發生重大的錯誤, 讓某個行程無法正常地中止與檔案庫的溝通.

rmtxns

安全地從檔案庫中移除 Subversion 異動 (可以直接使用 lstxns 子命令的輸出).

setlog

將檔案庫中指定修訂版的 svn:log (送交紀錄訊息) 性質的現值, 以指定的新值取代.

verify

驗證檔案庫的內容. 這包括了比較存放於檔案庫裡的版本控制資料的總和檢查碼.

svnshell.py

Subversion 源碼樹還包含了一個類似 shell, 與檔案庫溝通的界面. svnshell.py 這個 Python 命令稿 (位於源碼樹的 tools/examples/ 目錄中), 它使用 Subversion 的語言繫結 (因此你必須正確地編譯並安裝它們, 以便讓這個命令稿能正常執行), 以連接上檔案庫與檔案系統程式庫.

執行之後, 這個程式就像 shell 一樣, 讓你可以瀏灠檔案庫裡的目錄. 一開始, 你 “位於” 檔案庫的 HEAD 修訂版的根目錄, 並給你一個提示符號. 你可以在任何時候使用 help 命令, 它會顯示可用命令的列表, 以及它們功用為何.

$ svnshell.py /path/to/repos
<rev: 2 />$  help
Available commands:
  cat FILE     : dump the contents of FILE
  cd DIR       : change the current working directory to DIR
  exit         : exit the shell
  ls [PATH]    : list the contents of the current directory
  lstxns       : list the transactions available for browsing
  setrev REV   : set the current revision to browse
  settxn TXN   : set the current transaction to browse
  youngest     : list the youngest browsable revision number
<rev: 2 />$

在檔案庫的目錄結構之間移動, 就像在普通的 Unix 或 Windows shell 作法一樣 — 請用 cd 命令. 在任何時候, 命令指示符號都會顯示目前檢視的修訂版 (前置 rev:) 或異動 (前置 txn:), 以及該修訂版或異動的路徑位置. 你可以利用 setrevsettxn, 來變更目前你的修訂版或異動. 就像在 Unix shell, 你可以使用 ls 命令來顯示目前目錄的內容, 也可以使用 cat 命令來顯示檔案的內容.

Example 5.1. 利用 svnshell, 在檔案庫之中巡行

<rev: 2 />$ ls
   REV   AUTHOR  NODE-REV-ID     SIZE         DATE NAME
----------------------------------------------------------------------------
     1    sally <     2.0.1>          Nov 15 11:50 A/
     2    harry <     1.0.2>       56 Nov 19 08:19 iota
<rev: 2 />$ cd A
<rev: 2 /A>$ ls
   REV   AUTHOR  NODE-REV-ID     SIZE         DATE NAME
----------------------------------------------------------------------------
     1    sally <     4.0.1>          Nov 15 11:50 B/
     1    sally <     a.0.1>          Nov 15 11:50 C/
     1    sally <     b.0.1>          Nov 15 11:50 D/
     1    sally <     3.0.1>       23 Nov 15 11:50 mu
<rev: 2 /A>$ cd D/G 
<rev: 2 /A/D/G>$ ls
   REV   AUTHOR  NODE-REV-ID     SIZE         DATE NAME
----------------------------------------------------------------------------
     1    sally <     e.0.1>       23 Nov 15 11:50 pi
     1    sally <     f.0.1>       24 Nov 15 11:50 rho
     1    sally <     g.0.1>       24 Nov 15 11:50 tau
<rev: 2 /A>$ cd ../..
<rev: 2 />$ cat iota
This is the file 'iota'.
Added this text in revision 2.

<rev: 2 />$ setrev 1; cat iota
This is the file 'iota'.

<rev: 1 />$ exit
$

如你在前一個範例中看到的, 在一個命令指示中可以用分號隔開, 指定數個命令. 還有, shell 瞭解絕對路徑與相對路徑的表示法, 它可以正確地處理 "." 與 ".." 的特殊路徑部份.

youngest 命令會顯示最年輕的修訂版號, 它很適合用來決定你可用在 setrev 命令引數的有效修訂版範圍 — 你可以瀏灠包含 0 到最年輕的修訂版內容 (請回想一下, 修訂版號是以整數表示的). 決定可瀏灠的有效異動就不是那麼容易了, 請使用 lstxns 命令來列出你可瀏灠的異動. 可瀏灠的異動列表就跟 svnadmin lstxns 傳回的是一樣的, 它們也可以用在 svnlook--transaction 選項中.

當你完成了 shell 的工作, 你可以使用 exit 命令, 以便正確地結束. 另外, 你也可以輸入檔尾字元 — Control-D (不過某些 Win32 的 Python 發行版本改用 Windows 傳統的 Control-Z.)

Berkeley DB 工具

目前 Subversion 檔案庫只有一種資料庫後端 — Berkeley DB. 所有你的檔案系統結構與資料都存在於檔案庫的 db 子目錄的一組資料表格中. 這個子目錄是一般的 Berkeley DB 環境目錄, 因此可以跟任何 Berkeley 資料庫工具一起使用 (你可以在 SleepyCat 的網站看看這些工具的文件, http://www.sleepycat.com/). 日常使用 Subversion 是不需要這些工具的, 但是他們提供的一些重要功能, 是目前 Subversion 本身所沒有的.

舉個例子, 因為 Subversion 使用 Berkeley DB 的日誌功能, 資料庫本身會先寫下任何它打算進行的更動的描述, 然後再進行實際的更動. 這是為了確定在出錯的情況下, 資料庫系統還是可以回溯到前一個 檢查點 (checkpoint) — 一個已知沒有問題的紀錄檔位置 — 然後再重新進行異動, 直到資料回復到一個可用的狀態. 因為這個功能, 就是為什麼選擇 Berkeley DB 作為 Subversion 一開始的主要資料庫後端的主要原因之一.

這些紀錄檔案會隨著時間累積. 這實際上是資料庫系統的一項特色 — 你應該可以只藉由這些紀錄檔, 就可以重建整個資料庫, 所以這些檔案對於災後的資料庫重建是很重要的. 但是一般來說, 你會想要把 Berkeley DB 不再需要的紀錄檔案給歸檔, 然後把它們從磁碟刪去以節省空間. Berkeley DB 提供了一個 db_archive 工具, 其中之一的功能就是列出關聯到指定資料庫, 但是不再被使用的紀錄檔. 如此, 你就可以知道哪些資料可以歸檔, 然後將其移除. svnadmin 工具程式對這個 Berkeley DB 工具提供了一個方便的包裝:

$ svnadmin list-unused-dblogs /path/to/repos
/path/to/repos/log.0000000031
/path/to/repos/log.0000000032
/path/to/repos/log.0000000033

$ svnadmin list-unused-dblogs /path/to/repos | xargs rm
## 釋放磁碟空間!

Subversion 的檔案庫使用 post-commit 掛勾命令稿, 它在執行檔案庫的 “即時備份” 之後, 就會移除這些多餘的紀錄檔案. 在 Subversion 的源碼樹, tools/backup/hot-backup.py 命令稿示範了一個安全的方法, 如何在 Berkeley DB 資料庫環境仍被存取的情況下, 還能進行備份: 以遞迴方式複製整個檔案庫目錄, 然後重新複製 db_archive -l 列出的檔案.

一般來說, 只有真正的偏執狂才真的需要在每次送交時進行即時備份. 但是我們假設任何一個檔案庫都有某種程度的冗餘機制, 可到某一細微的程度 (例如每一次的送交), 檔案庫管理員仍有可能想要進行資料庫的即時備份, 作為系統層級的每日備份. 就大多數的檔案庫來說, 歸檔的送交電子郵件本身就足以成為回復的來源, 至少可用於最後幾個送交. 但是這是你的資料; 請以你希望的程度來保護它.

Berkeley DB 還包含了兩個用來轉換成 ASCII 文字檔, 以及自它轉換成資料庫的工具. db_dumpdb_load 這兩個程式, 各是用來寫入與讀取一個自訂的檔案格式, 可用來描述 Berkeley DB 資料庫裡的資料鍵與資料值. 由於 Berkeley 資料庫在不同的機器平台並不具移植性, 這個格式在不同的機器之間傳輸資料庫就很有用了, 完全不必煩惱機器架構與作業系統.

檔案庫善後

一般來講, 在你的 Subversion 檔案庫依你需要作好設定之後, 就不需要太多的注意. 但是有的時候還是會需要管理員的介入. svnadmin 工具提供了幾個有用的功能, 可幫助你進行以下的工作:

  • 修改送交紀錄訊息,

  • 移除無效的異動,

  • 修復 “卡住的” 檔案庫, 以及

  • 將檔案庫內容移至不同的檔案庫.

也許 svnadmin 最常使用的子命令是 setlog. 當一個異動送交到檔案庫, 並且提升成為修訂版之後, 關聯到新修訂版的描述紀錄訊息 (由使用者提供) 會被儲存為附加到修訂版的未納入版本控制的性質. 換句話說, 檔案庫只會記得該性質的最新值, 直接捨棄前一個值.

有的時候, 使用者的紀錄訊息會出錯 (也許是拼錯字, 或是寫錯資訊). 如果檔案庫設定為 (使用 pre-revprop-changepost-revprop-change 掛勾; 請參照 the section called “Hook scripts”) 送交之後還可以接受紀錄訊息的更動, 那麼使用者可以利用 svn 程式的 propset 命令 (請參照 Chapter 8, 完整 Subversion 參考手冊), 從遠端 “修正” 紀錄訊息. 但是呢, 因為有永遠失去資訊的可能, Subversion 檔案庫的預設設定是不允許更動未納入版本控制的性質 — 除非由管理員為之.

如果紀錄訊息必須由管理員來更正, 這可由 svnadmin setlog 來達成. 這個命令會修改檔案庫中, 指定修訂版的紀錄訊息 (svn:log 性質), 並且由提供的檔案中讀取新值.

$ echo "Here is the new, correct log message" > newlog.txt
$ svnadmin setlog myrepos newlog.txt -r 388

另一個 svnadmin 常用的用法, 就是查詢檔案庫中未處理的 — 有可能是已經不再使用了的 — Subversion 異動. 在送交注定失敗的時候, 異動一般都會被清除, 也就是說, 異動本身會自檔案庫移除, 任何與該異動有關 (也只有與其有關) 的資料會被移除. 但是有的時候, 發生的錯誤不會清除異動. 發生的原因有幾種: 也許用戶端的動作被使用者無預期中斷, 或是網路在運作之中突然中斷等等. 不管原因為何, 這些未處理的異動只會分散檔案庫, 只會佔用資源而已.

你可以使用 svnadminlstxns 命令, 列出目前未處理異動的名稱.

$ svnadmin lstxns myrepos
19
3a1
a45
$

產生的輸出中, 每一個項目都可以供 svnlook 使用 (以及它的 --transaction 選項), 看看是誰建立這個異動, 何時建立, 異動中有哪種類型的更動 — 換句話說, 就是這些異動是不是可以安全地被移除! 如果這樣的話, 異動的名稱可以傳給 svnadmin rmtxns, 用來清除異動. 事實上, rmtxns 子命令可以直接以 lstxns 的輸出作為輸入.

$ svnadmin rmtxns myrepos `svnadmin lstxns myrepos`
$

如果你像這樣使用這兩個子命令的話, 你應該考慮暫時不讓用戶端存取檔案庫. 如此, 在你開始進行清除之前, 沒有人可以進行有效的異動. 以下是簡單的 shell 命令稿, 可以快速地產生每一個檔案庫裡的未處理異動的資訊:

Example 5.2. txn-info.sh (回報未處理異動)

#!/bin/sh

### 產生 Subversion 檔案庫中, 未處理異動的相關訊息.

SVNADMIN=/usr/local/bin/svnadmin
SVNLOOK=/usr/local/bin/svnlook

REPOS=${1}
if [ x$REPOS = x ] ; then
  echo "usage: $0 REPOS_PATH"
  exit
fi

for TXN in `${SVNADMIN} lstxns ${REPOS}`; do 
  echo "---[ Transaction ${TXN} ]-------------------------------------------"
  ${SVNLOOK} info ${REPOS} --transaction ${TXN}
done

你可以以 /path/to/txn-info.sh /path/to/repos 來使用前面的命令檔. 這個輸出基本上是 svnlook info 輸出區塊合併起來而已 (請參考 the section called “svnlook”), 看起來就像這樣:

$ txn-info.sh myrepos
---[ Transaction 19 ]-------------------------------------------
sally
2001-09-04 11:57:19 -0500 (Tue, 04 Sep 2001)
0
---[ Transaction 3a1 ]-------------------------------------------
harry
2001-09-10 16:50:30 -0500 (Mon, 10 Sep 2001)
39
Trying to commit over a faulty network.
---[ Transaction a45 ]-------------------------------------------
sally
2001-09-12 11:09:28 -0500 (Wed, 12 Sep 2001)
0
$

一般來講, 如果你看到一個沒有關聯紀錄訊息的無效異動, 這是一個失敗的更新 (或類似更動) 作業的結果. 這些動作是偷偷使用 Subversion 異動來模仿工作複本的狀態. 由於它們並不打算送交, Subversion 並不要求這些異動要有紀錄訊息. 有紀錄訊息的異動幾乎可以肯定是某種失敗的送交. 還有, 異動的日期戳記可以提供一些有趣的資訊 — 舉個例子, 一個九個月前開始的作業, 仍為有效的可能性有多大?

簡單地說, 是否要清除異動的決定不可輕率為之. 不同的資料來源 — 包括 Apache 的錯誤與存取紀錄, 成功的 Subversion 送交紀錄, 諸如此類的 — 都可以用來幫助你作決定. 最後, 管理員常常只要簡單地跟疑似異動的擁有者溝通 (像是透過電子郵件), 就可以確定這個異動實際上是處於僵屍狀態.

檔案庫回復

為了要保護檔案庫中的資料, 資料庫後端使用了鎖定的機制. 這個機制保證不會同時有多個資料庫存取者同時修改資料庫的某一部份, 而資料從資料庫讀取出來時, 每一個行程都會看到資料是在正確的狀態. 當一個行程需要修改資料庫裡的東西, 首先會檢查目標資料是否有鎖定. 如果資料沒有被鎖定的話, 行程就會鎖定資料, 產生它想要的更動, 然後解除鎖定. 另一個行程會被迫等待, 直到鎖定解除之後, 它才可以繼續存取該部份的資料庫.

在使用 Subversion 檔案庫的過程中, 嚴重的錯誤 (像是磁碟空間, 或是可用的記憶體耗盡) 或中斷會導致行程沒有機會移除它在資料庫產生的鎖定, 結果就是後端資料庫會 “卡住”. 當這樣的情況發生時, 任何存取檔案庫的嚐試都會無限期停住 (因為每一個新的存取者都在等鎖定解除 — 而這件事不會發生).

如果你的檔案庫發生這樣的情況, 首先請勿慌張. Subversion 的檔案系統充份地利用了資料庫的異動, 檢查點, 以及事先寫入日誌的優點, 以確保只有最嚴重的災難事件 [12] 才會永久地毀壞資料庫環境. 一個夠偏執的檔案庫管理員會作出某種形式的 off-site 檔案庫備份, 但是還別忙著找你的系統管理員將磁帶回存回來.

第二, 使用以下的步驟, 試著 “解開” 卡住的檔案庫:

  1. 確定沒有別的行程在存取 (或是試著去存取) 檔案庫. 對網路檔案庫來說, 這表示也要把 Apache HTTP 伺服器給關掉.

  2. 成為擁有與管理檔案庫的使用者身份.

  3. 執行 svnadmin recover /path/to/repos 命令. 你會看到像這樣的輸出:

    Acquiring exclusive lock on repository db, and running recovery procedures.
    Please stand by...
    Recovery completed.
    The latest repos revision is 19.
    
  4. 重新起動 Subversion 伺服器.

這個程序幾乎能夠解決所有檔案庫鎖死的情況. 請確定你以擁有與管理資料庫的身份來執行這個命令, 而不是只以 root 執行. 修復程序的某些部份會從新建立數個資料庫檔案 (舉例來說, 共用的記憶體部份). 以 root 重建的話, 這些檔案將為 root 所擁有, 這表示就算你重新開放檔案庫供人存取, 一般的使用者也無法存取它.

如果前述的步驟因為某些原因而無法解開你的檔案庫, 你應該作兩件事. 首先, 將已損壞的資料庫移到別的地方, 然後回存最新的備份. 然後, 寄一封電子郵件到 Subversion 發展人員郵件論壇 (在 ), 詳細地描述你的問題. 對 Subversion 的發展人員來說, 資料完整性有著極高的優先權.

匯入檔案庫

Subversion 檔案系統將其資料散佈在數個資料庫表格之中, 通常只有 Subversion 管理員了解 (也只有他們想了解). 但是有的時候, 我們需要將所有的資料, 或是部份的資料, 集合在單一可移植的平面檔. Subversion 提供這樣的機制, 由兩個 svnadmin 子命令實作出來: dump 以及 load.

傾印與載入 Subversion 檔案庫的最常見原因, 就是 Subversion 本身的改變. 隨著 Subversion 日漸成熟, 有時會因後端資料庫 schema 的改變, 導致 Subversion 與前一版的檔案庫不相容. 當你升級遇到這樣的相容性問題時, 我們建議以下列簡單的步驟來進行:

  1. 使用你 現有 版本的 svnadmin, 將檔案庫傾印至傾印檔案.

  2. 升級至新版的 Subversion.

  3. 將舊的檔案庫移開, 在原處以 新版本svnadmin, 建立新的空檔案庫

  4. 再利用 新版本svnadmin, 將你的傾印檔載入至剛剛建立的檔案庫.

  5. 最後, 請記得將原來檔案庫的自訂部份複製到新的去, 包括 DB_CONFIG 與掛勾命令稿. 你應該要檢查一下新版本 Subversion 的發行備註, 看看從你上次更新後, 有沒有影響這些掛勾或設定選項的更動.

svnadmin dump 會以 Subversion 自訂的檔案系統傾印檔格式, 輸出一個範圍的檔案庫修訂版. 傾印檔格式會輸出到標準輸出串流, 而資訊訊息會送至標準錯誤串流. 這讓你可以將輸出串流轉向到一個檔案, 但是還能在終端機看到狀況輸出. 舉個例子:

$ svnlook youngest myrepos
26
$ svnadmin dump myrepos > dumpfile
* Dumped revision 0.
* Dumped revision 1.
* Dumped revision 2.
…
* Dumped revision 25.
* Dumped revision 26.

作業結束之後, 你會有一個檔案 (在前述的例子中, 就是 dumpfile), 其中包含所有儲存於要求的檔案庫修訂版範圍的資料.

相對的另一個子命令是 svnadmin load, 它會將標準輸入串流以 Subversion 檔案庫傾印檔案進行剖析, 可以有效地將這些傾印的修訂版重播到目標的檔案庫. 它也會提供資訊訊息, 但是這次是送到標準輸出串流:

$ svnadmin load newrepos < dumpfile
<<< Started new txn, based on original revision 1
     * adding path : A ... done.
     * adding path : A/B ... done.
     …
------- Committed new rev 1 (loaded from original rev 1) >>>

<<< Started new txn, based on original revision 2
     * editing path : A/mu ... done.
     * editing path : A/D/G/rho ... done.

------- Committed new rev 2 (loaded from original rev 2) >>>

…

<<< Started new txn, based on original revision 25
     * editing path : A/D/gamma ... done.

------- Committed new rev 25 (loaded from original rev 25) >>>

<<< Started new txn, based on original revision 26
     * adding path : A/Z/zeta ... done.
     * editing path : A/mu ... done.

------- Committed new rev 26 (loaded from original rev 26) >>>

請注意, 因為 svnadmin 使用標準輸入與輸出串流, 作為傾印與載入作業, 膽子很大的人可以這樣試試 (可能還在管道兩旁使用不同版本的 svnadmin):

$ svnadmin create newrepos
$ svnadmin dump myrepos | svnadmin load newrepos

我們在前面說過, svnadmin dump 會輸出一個範圍的修訂版. 透過 --revision 選項, 可以指定傾印單一修訂版, 或是一個範圍的修訂版. 如果你省略這個選項, 所有檔案庫裡的現有修訂版都會被傾印出來.

$ svnadmin dump myrepos --revision 23 > rev-23.dumpfile
$ svnadmin dump myrepos --revision 100:200 > revs-100-200.dumpfile

雖然 Subversion 會傾印所有的修訂版, 它只會輸出夠用的資訊, 讓後來的載入程式可以依此來進行重建. 換句說, 對任何一個傾印檔裡的修訂版來說, 只有在該修訂版被更動的項目會出現在傾印檔中. 本規則的唯一例外, 就是目前 svnadmin dump 命令所輸出的第一個修訂版.

Subversion 預設並不會將第一個傾印的修訂版, 以可用於一個修訂版的差異輸出. 第一, 在傾印檔中並沒有前一個修訂版! 第二個, Subversion 無法得知後來載入傾印檔時 (如果真的有的話), 載入它的檔案庫的狀態為何. 要確定每一次 svnadmin dump 都能自己自給自足的話, 預設第一個傾印出來的修訂版會完整地表示出該檔案庫修訂版的目錄, 檔案, 以及性質.

但是, 你可以變更這樣的預設行為. 如果在傾印檔案庫時, 加上了 --incremental 選項, svnadmin 會將檔案庫的第一個傾印修訂版, 與檔案庫中前一個修訂版作比較, 就像其它被傾印的修訂版的處理方法一樣. 第一個修訂版的輸出, 就像傾印檔裡其它的修訂版輸出一樣 — 只會顯示該修訂版的更動部份而已. 這樣的好處, 是你可以建立幾個可連續載入的小傾印檔, 而不是一個很大的, 像這樣:

$ svnadmin dump myrepos --revision 0:1000 > dumpfile1
$ svnadmin dump myrepos --revision 1001:2000 --incremental > dumpfile2
$ svnadmin dump myrepos --revision 2001:3000 --incremental > dumpfile3

這些傾印檔可以透過下列一連串的命令, 載入到新的檔案庫中:

$ svnadmin load newrepos < dumpfile1
$ svnadmin load newrepos < dumpfile2
$ svnadmin load newrepos < dumpfile3

另一個可以透過 --incremental 選項的技巧, 就是你可以把新範圍的傾印修訂版附加至現有的傾印檔. 舉個例子, 你可能有一個 post-commit 掛勾, 它就會附加觸動它的修訂版的傾印內容. 或是你可以每晚執行像下面的命令稿, 將檔案庫中, 自上一次執行後新增的修訂版附加至傾印檔.

Example 5.3. 使用漸進式檔案庫傾印

#!/usr/bin/perl -w

use strict;

my $repos_path  = '/path/to/repos';
my $dumpfile    = '/usr/backup/svn-dumpfile';
my $last_dumped = '/var/log/svn-last-dumped';
 
# Figure out the starting revision. Use 0 if we cannot read the
# last-dumped file, else use the revision in that file incremented
# by 1.
my $new_start = 0;
if (open LASTDUMPED, $last_dumped)
  {
    my $line = <LASTDUMPED>;
    if (defined $line and $line =~ /^(\d+)/)
      {
        $new_start = $1 + 1;
      }
    close LASTDUMPED;
  }

# Query the youngest revision in the repos.
my $youngest = `svnlook youngest $repos_path`;
defined $youngest && $youngest =~ /^\d+$/
  or die "$0: 'svnlook youngest $repos_path' cannot get youngest revision.\n";
chomp $youngest;

# Do the backup.
system("svnadmin dump $repos_path --revision $new_start:$youngest --incremental >> $dumpfile.tmp") == 0
  or die "$0: svnadmin dump to '$dumpfile.tmp' failed.\n";

# Store a new last-dumped revision.
open LASTDUMPED, "> $last_dumped.tmp"
  or die "$0: cannot open '$last_dumped.tmp' for writing: $!\n";
print LASTDUMPED "$youngest\n";
close LASTDUMPED
  or die "$0: error in closing '$last_dumped.tmp' for writing: $!\n";

# Rename to final locations.
rename("$dumpfile.tmp", $dumpfile)
  or die "$0: cannot rename '$dumpfile.tmp' to '$dumpfile': $!\n";
rename("$last_dumped.tmp", $last_dumped)
  or die "$0: cannot rename '$last_dumped.tmp' to '$last_dumped': $!\n";

# All done!

以這樣子使用的話, svnadmindumpload 命令就成了相當有用的工具, 可進行檔案庫更動的備份, 以避免系統當機, 或是其它災難事件所帶來的損害.

最後, 另一個 Subversion 檔案庫傾印檔案格式的可能應用, 就是從不同的儲存機制, 或是不同的版本控制系統進行轉換. 因為傾印檔案格式絕大部份都是人類可懂的, [13]

檔案庫備份

從現代電腦誕生以來, 不過技術有多進步, 有件事情還是很不幸地一直不變 — 有的時候, 事情會有非常非常嚴重的問題. 電力中斷, 網路斷線, 爛掉的記憶體, 墜毀的硬碟, 無論管理員是如何努力作好他們的工作, 還是有可能會遇到這些暗黑命運的魔爪. 所以我們遇到了最重要的課題 — 如何備份你的檔案庫的資料.

基本上, Subversion 檔案庫管理員可以使用兩種備份 — 漸進式與完整式. 我們在本章稍早的章節曾討論過, 如果使用 svnadmin dump --incremental 來進行漸進式備份 (請參見 the section called “匯入檔案庫”). 基本上, 它的概念就是只備份自上次製作備份後, 一定時間內的檔案庫更動.

就跟字面上的意思一樣, 檔案庫的完整備份是整個檔案庫目錄 (這包括了 Berkeley 資料庫環境) 的複製. 現在, 除非你暫時關閉所有其它對檔案庫的存取, 不然直接進行遞迴式的目錄複製的話, 會有得到無效備份的風險存在, 這是因為別人仍有可能正在寫入資料庫.

很幸運地, Sleepcat 的 Berkeley DB 文件載明了以怎麼樣的步驟複製資料庫檔案, 就可以保證取得有效的備份複本. 更好的是你不必親自實作, 因為 Subversion 發展團隊已經作好了. 在 Subversion 源碼的 tools/backup/ 目錄中, 可以找到 hot-backup.py 命令稿. 只要指定一個檔案庫的路徑與備份的位置, hot-backup.py 就會進行必要的步驟, 進行檔案庫的即時備份 — 完全不需要中止所有的公共存取 — 然後會清除檔案庫中無用的 Berkeley 記錄檔.

就算你已經有了漸近式備份, 你還是會想要定期執行這個程式. 舉例來說, 你會想把 hot-backup.py 加進定時執行的程式裡 (像是 Unix 系統的 crond). 或者你偏好可微調的備份方案的話, 可以讓你的 post-commit 掛勾命令稿呼叫 hot-backup.py (請參見 the section called “Hook scripts”), 這樣在每次有新的修訂版出現時, 就會建立檔案庫的新備份. 只要將以下這一行加進你的檔案庫目錄的 hooks/post-commit 命令稿即可:

(cd /path/to/hook/scripts; ./hot-backup.py ${REPOS} /path/to/backups &)

產生出來的備份是完全可用的 Subversion 檔案庫, 在事情無法收拾時, 可以立即用來作為檔案庫的替代品.

這兩種備份方式都有其長處. 目前最簡單的是完整備份, 它一定能夠產生正確無誤的檔案庫複製. 這表示不管線上檔案庫發生多糟糕的事, 只要一個簡單的遞迴目錄複製指令, 就能從備份回復回來. 不過很不幸地, 如果你維護多個檔案庫的備份, 這些完整備份會佔用跟你線上檔案庫一樣多的磁碟空間.

在 Subversion 前後版本的資料庫綱要變動的時候, 使用檔案庫傾印格式的漸進式備份在手邊是很方便的. 由於將檔案庫升級到新版的資料庫綱要需要進行完整的檔案庫傾印與載入, 如果已經有一半的步驟 (傾印的部份) 已經完成的話, 事情就方便多了. 不過很不幸地, 漸進式備份的建立 — 以及回復 — 要花較長的時間, 因為實際上每一個送交都是重播至傾印檔或是檔案庫之中.

不管是哪一種備份方式, 檔案庫管理員必須知道未納入版本控制的性質更動如何影響它們的備份. 由於這些更動不會產生新的修訂版, 它們不會觸發 post-commit 掛勾, 甚至也不會觸發 pre-revprop-change 與 post-revprop-change 掛勾. [14] 由於你能夠不依時間順序更動修訂版性質 — 你可以在任何時間修改任何修訂版的性質 — 最新幾個修訂版的漸進式備份也許不會知道原先備份的性質更動.

通常最好的修訂版備份是包含多種方式的. 你可以妥善運用完整備份與漸進備分的組合, 再加上電子郵件的送交檔案. 舉個例子, Subversion 的發展人員在每一次新的修訂版建立後, 就會備份 Subversion 的源碼, 並且保留所有的送交與性質的電子郵件通知. 你的方案可能很類似, 但是應該配合你的需要, 在便利與偏執之間取得平衡. 雖然這些都無法從暗黑命運的魔拳 [15] 中解救你的硬體, 它應該能在她肆機而動的時候解救你.

網路檔案庫

一個 Subversion 檔案庫可能同時被其所在機器上的多個用戶端同時存取, 不過常見的 Subversion 組態, 牽涉到一個可被其它辦公室用戶端存取的伺服器 — 當然也有可能是被全世界存取.

本節描述如何讓你的 Subversion 檔案庫開放給遠端用戶使用. 我們會涵蓋所有可用的伺服器機制, 討論每一個的設定與用法. 讀完本章後, 你應該能夠描述哪一種網路設定適合你的需求, 並且瞭解如何在你的電腦上啟用這樣的設定.

httpd, Apache HTTP 伺服器

Subversion 的主要網路伺服器為 Apache HTTP 伺服器 (httpd), 可以 WebDAV/deltaV 通訊協定溝通. 這個通訊協定 (它是 HTTP 1.1 的擴充; 請參照 http://www.webdav.org/) 採用廣為使用的 HTTP 通訊協定, 這是全球資訊網的核心, 再加上寫入 — 更具體地說, 有版本控制的寫入 — 的能力. 其結果就是一個標準的, 強固的系統, 包裝成一個 Apache 2.0 軟體的一部份, 常用作業系統與協力廠商都支援, 又不需要網路管理員開啟另一個自訂的連接埠. [16]

以下的討論中, 提到許多 Apache 設定的指令. 雖然有些範例中提供了指令的用法, 但是完整的描述並不在本書的範圍中. Apache 團隊維護一份很不錯的文件, 可在他們的網站 http://httpd.apache.org 取得. 例如, 一般性的設定指令位於 http://httpd.apache.org/docs-2.0/mod/directives.html.

另外, 在你修改 Apache 的設定時, 很有可能會在過程中引入其它的錯誤. 就算你不熟悉 Apache 的紀錄子系統, 你也應該要知道有它的存在. 在你的 httpd.conf 檔中, 有著指定 Apache 產生的存取紀錄檔與錯誤紀錄檔應放置的位置 (各為 CustomLogErrorLog 指令). Subversion 的 mod_dav_svn 亦使用 Apache 的錯誤紀錄界面. 你都可以瀏灠這些檔案的內容, 它們可能會顯示出其它方法無法發生的錯誤來源.

你需要什麼, 才能設定基於 HTTP 的檔案庫存取

要讓你的檔案庫透過 HTTP 供他人存取, 基本上你需要四個元件, 可從兩個套件中取得. 你需要 Apache 的 httpd 2.0, 它也包含了 mod_dav 的 DAV 模組. Subversion, 以及包含的 mod_dav_svn 檔案系統供應模組. 當你有了這些元件後, 將檔案庫放到網路上的步驟就只是簡單的:

  • 讓 httpd 2.0 跑起來, 並與 mod_dav 模組一併執行,

  • 將 mod_dav_svn 安插模組安裝至 mod_dav, 它會透過 Subversion 程式庫來存取檔案庫, 以及,

  • 設定你的 httpd.conf 檔案, 匯出 (讓他人可存取) 檔案庫.

要完成前兩項, 你可以自源碼編譯 httpd 與 Subversion, 或是在系統上安裝事先編譯好的二進位套件. 欲得知如何將 Subversion 編譯成與 Apache HTTPD 伺服器一同使用, 以及如何編譯與設定 Apache 以達成這樣效果的最新資訊, 請看看 Subversion 源碼樹最上層的 INSTALL 檔案.

基本 Apache 設定

當你把所有需要的元件都安裝到系統上之後, 剩下的只就是透過 httpd.conf 檔案設定 Apache. 請使用 LoadModule 指令, 讓 Apache 載入 mod_dav_svn 模組, 這個指令必須出現在其它的 Subversion 相關指令之前. 如果你的 Apache 是以預設的目錄配置安裝的, 你的 mod_dav_svn 模組應該會安裝在 Apache 安裝位置 (通常是 /usr/local/apache2) 的 modules 子目錄內. LoadModule 指令的語法很簡單, 就是將一個具名模組對映到共用程式庫在磁碟上的位置:

LoadModule dav_svn_module     modules/mod_dav_svn.so
        

請注意, 如果 mod_dav 是編譯成共用 object (而不是直接以靜態連結到 httpd 可執行檔), 你也需要對它使用一個類似的 LoadModule 敘述.

稍後於設定檔中, 你需要告訴 Apache, Subversion 檔案庫 (或多個檔案庫) 的位置. Location 指令有類似 XML 的表示法, 以一個起始標籤開始, 以完結標籤結束, 中間包含了其它不同的指令. Location 指令的目的, 是告訴 Apache 在處理指向指定 URL 或其子項目之一的要求時, 對它進行特別的處理. 在 Subversion 的情況中, 你要讓 Apache 把指向具版本控制資源的要求, 直接交給 DAV 去處理. 你可以利用以下的 httpd.conf 語法, 指示 Apache 將所有對路徑部份 (就是 URL 中, 在伺服器名稱與可能出現的連接埠之後的部份) 以 /repos/ 開始的 URL 的處理, 通通由 DAV 提供者來處理, 其檔案庫位於 /absolute/path/to/repository:

<Location /repos>
  DAV svn
  SVNPath /absolute/path/to/repository
</Location>

如果你計劃支援多個 Subversion 檔案庫, 而它們都有著共同的本地磁碟路徑, 你可以使用另一種指令 SVNParentPath, 指示它們共同的父路徑. 舉個例子, 如果你知道你會在路徑 /usr/local/svn 之下建立多個 Subversion 檔案庫, 並以類似 http://my.server.com/svn/repos1,http://my.server.com/svn/repos2 等等的 URL 供人存取, 你可以使用下列例子中的 httpd.conf 設定語法:

<Location /svn>
  DAV svn
  SVNParentPath /usr/local/svn
</Location>

使用前述的語法, Apache 會將所有路徑以 /svn/ 開始的 URL 都交給 Subversion DAV 供應模組處理, 它會假設任何以 SVNParentPath 指令指定的目錄都是 Subversion 檔案庫. 不像 SVNPath, 這個相當便利的語法可以讓你在建立新的檔案庫時, 仍舊不必重跑 Apache.

權限, 認證, 以及授權

在這個階段, 你應該要強烈地考慮權限的問題. 如果你已經讓 Apache 以普通的網頁伺服器執行了一陣子, 你應該已經有一堆的內容 — 網頁, 命令稿, 諸如此類的. 這些項目都已經以一組權限執行, 讓他們能夠與 Apache 一起使用, 更適切地講, 讓 Apache 與這些檔案一起工作. 當 Apache 以 Subversion 伺服器運作時, 它也需要正確的權限, 以便能夠讀取與寫入你的 Subversion 檔案庫.

你需要決定一套權限系統設定, 能夠滿足 Subversion 的要求, 又不會弄亂先前安裝的網頁或命令稿. 這表示你要變更 Subversion 檔案庫的權限, 以符合 Apache 其它一起協同工作的部份, 或是利用 httpd.confUserGroup 指令, 讓 Apache 以擁有 Subversion 檔案庫的使用者與群組的身份來執行. 設定權限並沒有一個絕對正確的方法, 而且每一個管理員有自已行事的標準. 你只要注意, 要讓 Subversion 檔案庫與 Apache 一起使用時, 權限會是最常發生的問題.

既然我們在談論權限的問題, 我們也應該談談 Apache 提供的認證與授權的機制可以如何運用. 除非你對這些有某種系統層面的設定, 基本上, 你透過 Location 開放的 Subversion 檔案庫是所有人都可以存取的. 換句話說,

  • 任何人都可以用它們的 Subversion 用戶端, 取出檔案庫 URL (或其任意的子目錄) 的工作複本.

  • 任何人只要將他們的瀏覽器指向檔案庫 URL, 就可以互動式地瀏覽檔案庫最新的修訂版, 以及,

  • 任何人可以送交至檔案庫.

如果你要限制整個檔案庫的讀取或寫入存取, 你可以使用 Apache 內建的存取控制功能. 在這些功能中, 最簡單的是基本設證機制, 它只會使用使用者名稱與密碼, 用以確認使用者是他所聲稱的身份. Apache 提供了 htpasswd 工具程式, 來管理接受的使用者名稱與密碼, 也就是你想要授與存取 Subversion 檔案庫權限的使用者. 讓我們授與 Sally 與 Harry 送交存取的權限. 首先, 我們必須把它們加入到密碼檔案.

$ ### 第一次: 以 -c 建立檔案
$ htpasswd -c /etc/svn-auth-file harry
New password: ***** 
Re-type new password: *****
Adding password for user harry
$ htpasswd /etc/svn-auth-file sally
New password: *******
Re-type new password: *******
Adding password for user sally
$
        

接著, 你需要在 httpd.confLocation 區塊中新增幾個指令, 告訴 Apache 如何處理你的新密碼檔. AuthType 指令指定應使用何種認證系統. 在目前的狀況中, 我們想要指定 Basic 認證系統. AuthName 是一個任意的名稱, 讓你用來指定認證領域 (authentication domain). 大多數的瀏覽器在向使用者詢問使用者代號與密碼時, 會將這個名稱顯示在彈出的對話框中. 最後, 使用 AuthUserFile 指令, 指定你以 htpasswd 產生的密碼檔.

在新增這三個指令後, 你的 <Location> 區塊看起來應該像這樣:

<Location /svn>
  DAV svn
  SVNParentPath /usr/local/svn
  AuthType Basic
  AuthName "Subversion repository"
  AuthUserFile /path/to/users/file
</Location>

現在如果你重新啟動 Apache, 任何需要認證的 Subversion 動作都會從 Subversion 用戶端取得使用者代號與密碼, 這可能是使用先前置於快取的值, 或是向使用者詢問. 剩下要作的, 就是告訴 Apache 哪些動作需要這樣的認證.

你可以藉由將 Require valid-user 指令加進 <Location> 區塊, 對所有存取檔案庫的動作進行限制. 以先前的例子而言, 這表示只有聲稱他們自己是 harrysally, 並且能夠提供這些代號的正確密碼的用戶端, 才可以對 Subversion 檔案庫作任何事.

有的時候, 你不需要這麼地嚴格. 舉例來說, Subversion 源碼所在的 http://svn.collab.net/repos/svn 檔案庫就允許世界上的任何人對它進行唯讀的存取 (像是取得工作複本, 或是透過瀏覽器來瀏覽檔案庫), 但是所有寫入的動作僅限由認證用戶為之. 要達到這樣的選擇性限制, 你可以使用 LimitLimitExcept 設定指令. 就像 Location 指令, 這些區塊都有起始與完結標籤, 而且你應該把它們放置在你的 <Location> 區塊.

LimitLimitExcept 出現的參數, 是該區塊中受到影響的 HTTP 要求類別. 舉個例子, 如果除了目前支援的唯讀動作, 其它的存取通通不允許, 你可以使用 LimitExcept 指令, 並且使用 GET, PROPFIND, OPTIONS, 以及 REPORT 的要求類別參數. 然後前述的 Require valid-user 指令就應該擺在 <LimitExcept> 區塊中, 而不只是 <Location> 中而已.

<Location /svn>
  DAV svn
  SVNParentPath /usr/local/svn
  AuthType Basic
  AuthName "Subversion repository"
  AuthUserFile /path/to/users/file
  <LimitExcept GET PROPFIND OPTIONS REPORT>
    Require valid-user
  </LimitExcept>
</Location>

這只是幾個簡單的例子而已. 欲得知更詳細的 Apache 存取控制的資訊, 請參考 http://httpd.apache.org/docs-2.0/misc/tutorials.html 的 Apache 的導覽文件中的 安全 一節.

伺服器名稱與 COPY 要求

Subversion 使用 COPY 要求類別, 進行伺服器端的目錄與檔案的複製. 由於 Apache 模組要進行的完整性檢查, 複製來源必須與目標處於同一台機器上. 要達到這個要求, 你需要告訴 mod_dav 用以作為伺服器主機的名稱. 一般來說, 你可以在 httpd.conf 中使用 ServerName 指令來達到這樣的目的.

ServerName svn.red-bean.com
        

如果你透過 NameVirtualHost 使用 Apache 的虛擬主機功能, 那麼你必須使用 ServerAlias 指令來指定伺服器的其它名稱. 當然囉, 請參考 Apache 的文件, 以瞭解詳細的細節.

瀏覽檔案庫的 HEAD 修訂版

對你的 Subversion 檔案庫進行 Apache/WebDAV 設定, 最有用的功效之一, 就是可以馬上使用普通的瀏覽器來存取最年輕修訂版、納入版本控制的目錄與檔案. 由於 Subversion 使用 URL 來指定受版本控制的資源, 這些用來存取基於 HTTP 的檔案庫存取的 URL, 可直接輸入到網頁瀏覽器之內. 你的瀏覽器會為這個 URL 送出一個 GET 要求, 依該 URL 所代表的是受版本控制的目錄還是檔案, mod_dav_svn 會以目錄列表或檔案內容進行回應.

由於 URL 中沒有包含你想知道的資源的版本資訊, mod_dav_svn 都會以最年輕的版本回應. 這個功能有個很棒的副作用, 就是你可以將 Subversion 的 URL 當作文件參照, 丟給同事使用, 然後這些 URL 永遠都指向該文件的最新版本. 當然囉, 你還可以將這些 URL 當作其它網頁的超連結之用.

你通常會找到指向受版本控制檔案的 URL 的其它用法 — 畢竟, 通常那也是人家有興趣的內容所在. 但是你可能偶而也看過 Subversion 的目錄列表, 很快就會發現產生出來的 HTML 功能很陽春, 一點也不漂亮 (甚至一點也不有趣). 如果想要自訂目錄列表, Subversion 提供了 XML 索引功能. 在 httpd.conf 中的檔案庫的 Location 區塊, 一個 SVNIndexXSLT 指令就會告訴 mod_dav_svn, 在顯示目錄列表時, 產生 XML 的輸出, 並且使用你所指定的 XSLT 式樣表:

<Location /svn>
  DAV svn
  SVNParentPath /usr/local/svn
  SVNIndexXSLT "/svnindex.xsl"
  …
</Location>

透過 SVNIndexXSLT 指令與一個有創意的 XSLT 式樣表, 你可以讓你的目錄列表符合網站其它部份的色彩運用與影像. 或者你喜歡的話, 也可以使用 Subversion 源碼中, 置於 tools/xslt/ 目錄裡的式樣表範例. 請記得 SVNIndexXSLT 指令所提供的路徑, 實際上是 URL 路徑 — 瀏覽器必須要能夠讀取你的式樣表, 才能使用它們!

雜項的 Apache 功能

作為一個強固的網頁伺服器, Apache 本身已有的幾項功能, 也可以用在提供 Subversion 的功能性與安全性. Subversion 透過 Neon 與 Apache 進行溝通, 這是一個通用型的 HTTP/WebDAV 程式庫, 支援了幾個像是 SSL (secure socket layer) 與 Deflate 壓縮 (和 gzipPKZIP 用來將檔案 “縮小 (shrink)” 成較小的區塊的演算法相同) 的機制. 你要作的, 就只是把你想要的功能與 Subversion 和 Apache 編譯在一起, 然後正確地設定程式, 讓它們使用這些功能.

這表示啟用 SSL 的 Subversion 用戶端可以存取開啟 SSL 的 Apache 伺服器, 透過加密的通訊協定進行所有的溝通, 僅僅需要把 http://https:// 的 URL 取代即可. 有些需要把它們的檔案庫對防火牆外開放的公司, 必須要考慮到惡意第三方可能會 “竊聽” 網路封包. SSL 讓這樣不受歡迎的行為不容易造成敏感資料外洩. Apache 可以設定成只讓開啟 SSL 的 Subversion 用戶端與檔案庫進行溝通.

Deflate 壓縮會在用戶端與伺服器加諸小小的負擔, 對網路傳遞進行壓縮與解壓縮, 以將實際的資料傳輸量降至最低. 在網路頻寬相當不足的情況下, 這樣的壓縮功能能夠大幅地加速伺服器與用戶端之間的傳輸速度. 在很極端的情況下, 像這樣被減少的網路傳輸, 就可以決定動作會逾時, 還是成功地完成.

有些比較不引人注意, 但是同樣有用的, 是 Apache 與 Subversion 之間的功能, 像是指定自訂連接埠 (而不使用預設 HTTP 的 80 連接埠), 可使用虛擬網域名稱對 Subversion 檔案庫進行存取的能力, 或是透過代理伺服器存取檔案庫的功能. 這些都被 Neon 支援, 所以 Subversion 不費吹灰之力就得到這些功能.

svnserve, 自訂的 Subversion 伺服器

除了 Apache 之外, Subversion 還提供另一個單獨的伺服器程式, 也就是 svnserve. 這個程式要比 Apache 更輕便, 而且更容易設定. 它會與 Subversion 用戶端透過 TCP/IP 連線以自訂的通訊協定溝通.

svnserve 有兩種基本的使用方法:

未授權 (匿名) 存取

在這個情況下, 一個 svnserve 的背景監控行程會在伺服器上執行, 聆聽外部的連線. svn 用戶端會使用自訂的 svn:// URL schema 連線. 用戶端連線會無條件地接受, 而檔案庫會以未授權的使用者名稱進行存取. 大多數的情況下, 管理員會將這個背景監控程式設定為允取唯讀動作.

授權 (SSH) 存取

在這個情況下, svn 用戶端使用的是自訂的 svn+ssh:// URL schema; 這會啟動一個本地端的 Secure Shell (SSH) 行程, 它會連線至伺服器, 然後進行授權. 使用者必須在伺服器有系統帳號, 才能這樣作. 在授權完成後, SSH 行程會在伺服器上執行一個暫時的私有 svnserve 行程, 以授權使用者的身份執行. 伺服器與用戶端會透過加密的 ssh 通道進行溝通.

請注意, 這些 svnserve 的使用方法並不互相衝突; 你可以很簡單地同時在你的伺服器上, 使用這兩種技巧.

設定匿名 TCP/IP 存取

svnserve 不帶引數執行時, 它會將資料寫至標準輸出, 並自標準輸入讀取資料, 試著與一個 svn 用戶端商議出一個進程:

$ svnserve
( success ( 1 1 ( ANONYMOUS ) ( ) ) ) 

這對任何人都沒有立即的效果; 因為 svnserve 的運作如此, 它才能被 inetd 背景監控程式執行. 但是要將 svnserve 以背景監控程式執行的話, 方法還有很多種.

有一個方法就是對伺服器主機的 inetd 背景監控程式登記一個 svn 服務. 那麼當一個用戶端試著要連接到連接埠 3690 時, [17] inetd 就會起動一個 “只用一次” 的 svnserve 行程來處理該用戶的要求.

當你以這種方式設定的話, 請記住你不該以 root 使用者 (或其它有無限權限的使用者) 的身份來執行 svnserve 行程. 依你匯出檔案庫的所有權與權限的不同, 不同的 — 也許是自訂的 — 的使用者可能更合適. 舉個例子, 你可能會想要建立一個名為 svn 的新使用者, 給這個使用者可使用 Subversion 檔案庫的專有權限, 然後設定你的 svnserve 行程以該使用者的身份執行.

當然囉, 第一個方法只適用於 inetd (或類似 inetd 的) 背景監控程式的機器. 通常這只限於 Unix 平台上的. 另一個方法是將 svnserve 以單一個背景監控程式執行. 以 -d 選項執行的話, svnserve 會馬上自目前的 shell 行程分離, 以背景行程一直地執行下去, 當然還是在連接埠 3690 上等待新進的連線.

$ svnserve -d
$ # svnserve 仍在執行, 但是使用者已經回到提示字元

當一個用戶端透過網路與 svnserve 行程 (以背景監控程式執行, 或是 “只用一次” 的處理行程) 連線時, 完全沒有認證發生. 伺服器行程會以它執行的使用者身份來處理檔案庫, 如果用戶端進行送交的話, 新的修訂版完全不會設定 svn:author 性質.

只要 svnserve 伺服器開始執行, 它會讓整個檔案庫都開放給網路使用. 換句話說, 如果一個用戶端試著要對在 example.com 上執行的 svnserve 行程取出 svn://example.com/usr/local/repos/project, 它會試著去找位於絕對路徑 /usr/local/repos/project 上的檔案庫. 如果你想增加安全性, 你應該以 -r 選項執行 svnserve, 它會限制只開於該路徑下的檔案庫:

$ svnserve -d -r /usr/local
…

透過 -r 選項, 可以有效地變更程式認為是遠端檔案系統空間的根目錄. 用戶端就可以把相同的路徑拿掉, 就會得到比較短的 (也比較不明顯的) URL:

$ svn checkout svn://example.com/repos/project
…

要取消檔案庫的寫入存取, 請以 -R 選項起動 svnserve. 這樣就只允許讀取檔案庫裡的資料.

設定使用 SSH 存取

通常來說, 我們都想知道 (在幾近無限的使用者中) 哪一個使用者該為哪些更動負責, 但是限制哪些使用者有檔案庫的寫入能力要來得更重要. [18] 要達到這兩個目的, 使用 libsvn_ra_svn 的用戶端可以透過 SSH 通道來使用網路進程.

在這種情況下, 每一次用戶端想要連上 Subversion 檔案庫, 本地端的 SSH 層就會在伺服器機器上啟動一個新的 svnserve. svnserve 背景監控程式並沒有必要執行起來 — 已認證過的 SSH 用戶連線會在伺服器上啟動一個私有的 svnserve 行程, 會以認證的使用者身份執行. (事實上, CVS 在使用 :ext: 存取方式與 SSH 時, 也是這麼作的.) SSH 本身需要認證過的使用者, 所以不會有匿名的身份. 由於存取不是匿名的, 已認證的使用者代號會被用來當作檔案庫更動的作者儲存起來.

一個用戶端可以藉由使用 svn+ssh:// schema, 以及指定檔案庫的絕對路徑, 就可以進行 SSH 通道, 像這樣:

$ svn checkout svn+ssh://example.com/usr/local/repos/project
Password:
…

svn 用戶端預設會在使用者的 $PATH 中, 尋找並啟動一個名為 ssh 的本地端程式, 不過 SSH 可由以下兩種方法之一置換掉. 你可以將 SVN_SSH 環境變數設為新的值, 或是可以設定用戶端執行時期設定檔 config[tunnels] 一節中的 ssh 變數.

舉個例子, ssh 行程在試著與伺服器進行認證時, 會使用你目前的使用者名稱. 你可能希望 ssh 使用不同的使用者名稱. 要達成這個目的, 你可以這樣執行本命令

$ export SVN_SSH="ssh -l username"

… 或者你也可以修改執行時期設定檔 config, 讓它包含

[tunnels]
ssh = ssh -l username
        

欲知更多有關修改 config 執行時期設定檔的資訊, 請參考 the section called “Config”.

使用哪一個伺服器?

當你在 Apache HTTP 伺服器與自訂的 svnserve 程式之間作抉擇時, 其實並沒有一個標準的答案. 依你自己的要求, 某一個可用的方案可能看起來會比較適合. 事實上, 這些伺服器都可以一起使用, 每個都以它們自己的方式存取檔案庫, 不會互相干擾. 作為前兩章的總結, 以下是兩個 Subversion 可用的伺服器的簡單比較表 — 請選擇最適合你與使用者之用的:

Apache/mod_dav_svn
  • 認證: http basic/digest auth, certificates, LDAP 等等. Apache 有為數眾多的認證模組. 不需要為使用者建立真實的系統帳號.

  • 認證: 讀寫權限可以依檔案庫 (使用 httpd.conf 指令), 或是依目錄 (使用 mod_authz_svn) 為基礎進行設定.

  • 毋需開啟新的防火牆連接埠.

  • 內建的檔案庫網頁瀏覽功能 (有限度)

  • 與其它 WebDAV 用戶端的有限配合

  • 使用快取 HTTP 代理伺服器的能力

  • 通過時間驗證的強大擴充能力 — Apache 是無數大型企業網站的網頁伺服器首選. It takes a lickin' and keeps traffickin'

  • 強大的擴充能力: 可使用許多現有的認證與授權方式, 以及加密, 壓縮等等的功能.

svnserve
  • 認證: 只能透過 SSH 通道. 使用者必須要有系統帳號.

  • Authorization: via shared ownership/permissions on repository DB files.

    授權: 透過檔案庫 DB 檔案的共享擁有者/檔案權限.

  • 比 Apache 要更輕巧地多, 而且大多數的動作也更快.

  • 比 Apache 要更容易設定.

  • 可以使用現有的 SSH 安全架構.

檔案庫權限

你已經看到檔案庫如何能以許多不同的方式存取. 但是有可能 — 或是能夠安全地 — 讓檔案庫同時被多種不同的檔案庫存取方法來存取嗎? 答案是肯定的, 前提是你得有些先見之明.

在任何時間, 這些行程也許都需要對檔案庫進行讀取與/或寫入存取:

  • 一般的系統使用者, 會使用 Subversion 用戶端 (以他們自己的身份) 直接存取檔案庫;

  • 一般的系統使用者, 連接到 SSH 執行的私有 svnserve 行程 (以他們自己的身份執行), 經由它來存取檔案庫;

  • 一個 svnserve 行程 — 無論是背景監控程式, 還是被 inetd 執行的 — 會以某一個固定的使用者身份執行;

  • 一個 Apache 的 httpd 行程, 會以某一個固定的使用者身份執行.

絕大部份管理員會遇到的共同問題, 是檔案庫的擁有權與權限. 每個前項列表中的行程 (或使用者) 都有權限可以讀取與寫入 Berkeley DB 檔案嗎? 假設你有一個類似 Unix 的作業系統, 最直接的方法, 大概就是把每一個可能的檔案庫使用者加入到新的 svn 群組, 並且確定該群組擁有檔案庫. 但是即使如此也還不夠, 因為一個行程可能以不友善的 umask 寫入資料庫檔案 — 一個會讓其他使用者無法存取的 umask.

在為檔案庫使用者設定一個共同群組之後, 接下來是為每一個存取檔案庫的行程設定一個合理的 umask. 對於直接存取檔案庫的使用者, 你可以將 svn 程式放在包裝程式中, 先設定 umask 002, 然後再執行真正的 svn 用戶程式. 你可以為 svnserve 程式寫一個類似的包裝器, 並且將 umask 002 加在 Apache 自己的啟動命令稿 apachectl 中.

在你搞定這些東西之後, 你的檔案庫就應該可以被所有有需要旳行程存取. 這看起來可能有點亂, 有點複雜, 不過讓多個使用者對共同檔案都可進行寫入存取, 一直都是經典的問題, 而且通常都沒有很優雅的解決方法.

很幸運的, 大多數的檔案庫管理員根本不 需要 有這麼複雜的設定. 使用者想要存取同一台機器上的檔案庫時, 其實並不限只能使用 file:// URL 而已 — 他們也可以透過 Apache HTTP 伺服器或 svnserve, 只要將 http://svn:// URL 中的主機名稱改為 localhost 即可. 為你的 Subversion 檔案庫維護多個伺服器行程, 大概都是頭痛大於需要. 我們建議你使用最適合需要的伺服器, 然後就別再三心二意了.

新增專案

在檔案庫建立並設定好之後, 剩下的就是使用它了. 如果你有一群現有的資料, 準備要納入版本控制的話, 你會想要用 svn 用戶端程式的 import 子命令來進行. 不過在你這麼作之前, 你應該仔細地考慮檔案庫的長期計畫. 在本節中, 我們會提供一些如何規畫檔案庫配置的建議, 以及如何在這個配置中放置你的資料.

選擇一種檔案庫配置

雖然 Subversion 可以讓你隨意移動受版本控管的目錄與檔案, 而又不會遺失資訊, 不過這麼作還是會打亂經常存取檔案庫, 習慣什麼東西會在什麼地方的使用者的工作流程. 請試著稍微考慮一下未來; 在把資料納入版本控制之前, 先作好計劃. 藉由一開始就對檔案庫的內容先作好 “規劃”, 你可以免去未來的頭痛事件.

在設定 Subversion 檔案庫時, 有幾件事得考慮. 假設你是檔案庫管理員, 你得負責用於數個專案的版本控制系統的支援. 第一個決定, 你要對多個專案使用一個檔案庫, 還是每個專案都有它自己的檔案庫, 還是這兩者的折衷方案.

對多個專案使用一個檔案庫有一些好處, 最明顯的是不需作重複的維護動作. 一個檔案庫表示它有一組掛勾命令稿, 一個例行的備份, 如果 Subversion 發行一個不相容的新版本, 也只要進行一個傾印與載入, 諸如此類的. 再者, 你也可以在各個不同的專案之間搬移檔案, 不會漏失掉任何版本資訊的歷程紀錄.

使用單一檔案庫的缺點, 就是不同的專案可能會有不同的送交郵遞論壇, 或是不同的認證與授權的要求. 另外, 請記得 Subversion 使用的檔案庫範圍的修訂版號. 有人對於別的專案中正如火如荼在進行修改, 但是自己的專案卻因為全域修訂版的關係, 其最年輕的修訂版號一直努力攀升會相當地反感.

不過還是有中間路線可供取捨. 舉個例子, 不同的專案可以根據他們的相依程度歸類在一起. 你可以在每個檔案庫中, 放上幾個專案. 這樣子, 很有可能會分享資料的專案可以很容易達到目的, 當檔案庫增加修訂版號時, 發展人員至少可以知道, 這些更動對同一個檔案庫上的人員都是有一些影響的.

在決定你的專案如何與檔案庫組織起來後, 你大概就要思考檔案庫的目錄架構. 由於 Subversion 利用一般的目錄複本來作分支與標記 (請參考 Chapter 4, 分支與合併), Subversion 社群建議使用兩個方法之一. 這兩種方法都使用以下的目錄, 分別名為 trunk, 表示主要專案發展的目錄位置, 以及 branches, 於其中建立主要發展線不同具名分支.

第一種方法是把每個專案放在根檔案系統目錄之下的子目錄, 每個專案計劃目錄其下接著為 trunkbranches 目錄, 如圖 5-1 所示.

Figure 5.1. 一種建議的檔案庫配置.

一種建議的檔案庫配置.

第二種則是反過來 — 在檔案系統最頂層之下的是 trunkbranches 目錄, 其下則為所有檔案庫中的專案目錄, 如圖 5-2 所示:

Figure 5.2. 另一種建議的檔案庫配置.

另一種建議的檔案庫配置.

請以你認為最適合的配置來規畫檔案庫. Subversion 並不會期望或強制你要用哪一種配置 — 在它的眼中, 是目錄的目錄就只是目錄. 歸根究底, 你應該選擇一種最適合你, 以及專案的檔案庫配置.

建立配置, 匯入起始資料

決定檔案庫要如何安排專案之後, 你應該就開始要以選擇的配置來建立檔案庫, 並放置資料進去. Subversion 有幾種方式可以達到這樣的目的. 你可以使用 svn mkdir 命令 (請參考 Chapter 8, 完整 Subversion 參考手冊) 命令, 依檔案庫配置的骨架, 一個一個將目錄建立起來. 比較快達到相同目的的方法, 就是使用 svn import 命令 (請參考 the section called “svn import”). 在磁碟的暫時位置先建立好檔案庫配置後, 你可以利用單一送交, 將整個配置樹匯入至檔案庫中:

$ mkdir tmpdir
$ cd tmpdir
$ mkdir projectA
$ mkdir projectA/trunk
$ mkdir projectA/branches
$ mkdir projectB
$ mkdir projectB/trunk
$ mkdir projectB/branches
…
$ svn import projectA file:///path/to/repos --message 'Initial repository layout'
Adding         projectA
Adding         projectA/trunk
Adding         projectA/branches
Adding         projectB
Adding         projectB/trunk
Adding         projectB/branches

Committed revision 1.
$ cd ..
$ rm -rf tmpdir
$

當你的配置骨架就定位後, 如果資料還沒進去的話, 你就可以開始將真正的資料匯入你的檔案庫. 再聲明一下, 要達到這個目的有很多方法. 你可以使用 svn import 命令. 你可以從新的檔案庫取出工作複本, 將資料移到工作複本中放好, 然後使用 svn addsvn commit. 不過當我們開始講到這樣的事情之後, 我們就不是在討論檔案庫管理. 如果你還不熟悉 svn 用戶端程式的話, 請參考 Chapter 3, 導覽.

摘要

現在, 你應該已經對如何建立, 設定, 以及維護 Subversion 檔案庫有了基本的認識, 我們已經介紹幾個可用來幫助你的工具. 我們還涵蓋了一些基本的 Apache 設定步驟, 可讓你在網路上開放你的檔案庫. 在本章中, 我們也注意到共通的管理陷阱, 以及如何避免它們的建議.

剩下的, 就是讓你決定該在檔案庫裡放些什麼有趣的資料.



[11] 這聽起來好像很高尚, 很飄飄然的樣子, 不過我們指的, 就是任何除了工作複本以外, 還對存放所有人的資料的神祕領域有興趣的人而已.

[12] 舉例: 硬碟 + 特大號電磁鐵 = 災難

[13] Subversion 的檔案庫傾印檔案格式很像 RFC-822 格式, 與大部份電子郵件所用的一模一樣.

透過這個檔案格式要描述更動集合 — 每一個都應視為是新的修訂版 —, 應該是較為容易的.

[14] 例如 svnadmin setlog 就會完全跳過掛勾界面.

[15] 你知道的 — 就是她的魔爪的集合名詞.

[16] 他們真的很討厭這麼作.

[17] 這個連接埠編號已獲 Internet Assigned Numbers Authority (IANA) 指定.

[18] 大家一起來” 的模式, 有的時候是, 我們得這麼說, 有點混亂的.

Chapter 6. 進階主題

如果你是從頭開始逐章閱讀本書的, 你應該已經知道如何使用 Subversion 用戶端, 進行大多數常見的版本控制作業. 你知道如何從 Subversion 檔案庫取出一份工作複本. 你已經習慣利用 svn commitsvn update 功能來送交與取得更動. 你甚至可能已經會反射性地使用 svn status 命令而不自覺. 不管從哪個角度看, 你已經相當習慣使用 Subversion 了.

但是 Subversion 的功能不僅止於 "常見的版本控制作業".

本章著重於 Subversion 並不常用的功能. 在本章中, 我們會討論 Subversion 的性質 (或 “描述資料”) 支援, 如何透過修改 Subversion 的執行時期設定檔, 來改變它的預設行為. 我們會描述你要怎麼利用外部定義, 讓 Subversion 從多個檔案庫取得資料. 對於 Subversion 發行檔中的額外用戶端與伺服器端的工具, 我們也會有詳盡的解說.

在閱讀本章之前, 你應該已經熟悉 Subversion 的基本目錄與檔案能力. 如果你還沒看過這部份, 或是需要再重新熟悉一下, 我們建議你看看 Chapter 2, 基本概念Chapter 3, 導覽. 當你能夠駕馭基本的功能, 也了解本章的內容, 你就是 Subversion 的強力使用者了 — 無效退費! [19]

執行時期的設定區域

Subversion 提供許多選用的行為, 可讓使用者自由控制. 許多像這樣的選項, 都是使用者想要套用在所有的 Subversion 作業上的. 所以, Subversion 不強迫使用者記下指定這些選項的命令列選項, 然後在每個進行的作業都用上它們, 而是透過設定檔, 將它們隔離在 Subversion 設定區域中.

Subversion 設定區域 是一個包含選項名稱與對應值的兩層次結構. 一般來講, 它們通通被擠到一個特別的目錄, 其中包含 設定檔 (第一層結構), 就只是標準 INI 格式 (以 “區塊” 作為第二層) 的純文字檔. 這些檔案可以輕易地利用你常用的文字編輯器 (像是 Emacs 或 vi) 進行編輯, 其中包含被用戶端讀取的指令, 用來決定幾個使用者偏好的選擇性行為.

設定區域配置

svn 命令列用戶端在第一次執行的時候, 它會建立一個使用者設定區域. 在類 Unix 系統上, 它是使用者家目錄裡的 .subversion 目錄. 在 Win32 系統上, Subversion 會建立一個名為 Subversion 的檔案夾, 一般是在使用者 profile 目錄的 Application Data 區域中. 但是在這個平台上, 實際的位置會依各個系統不同而不同, 而且都是由 Windows 登錄檔指定的. [20] 我們會以 Unix 上的名稱 .subversion 來表示使用者設定區域.

除了使用者設定區域之外, Subversion 還會注意到系統設定區域的存在. 這讓系統管理員能夠在特定的機器上, 為所有使用者設定預設環境. 請注意, 系統設定區域並不具有強制性 — 使用者設定區域裡的設定會蓋過系統設定區域, 而在 svn 程式的命令列選項是最後決定行為的部份. 在類 Unix 平台上, 系統設定區域是在 /etc/subversion 目錄; 在 Windows 平台上, 它還是會在公用的 Application Data 區域 (同樣地, 還是要依 Windows 登錄檔裡的設定而定) 裡的 Subversion 目錄. 不像使用者設定區域一樣, svn 程式不會試著去建立系統設定區域.

目前 .subversion 目錄包含三個檔案 — 兩個設定檔 (config servers), 以及一個 README.txt 檔案, 用來描述 INI 格式. 在建立它們的時候, 這些檔案包含 Subversion 支援選項的預設值, 大多數都被設定為註解, 並且包括這些設定值如何影響 Subversion 行為的描述文字. 要改變某一種行為, 你只需要以文字編輯器開啟適當的檔案, 然後修改對應選項的值. 如果任何時候你想要回復一個或多個檔案的預設值, 你只要刪除這些檔案, 然後執行某些無害的 svn 命令, 像是 svn --version, 那些消失的檔案就會以它們的預設狀態重新建立起來.

使用者設定區域也會包含認證資料的快取. auth 目錄擁有一群子目錄, 裡面包含數種 Subversion 支援的認證方式所需要的快取資料. 這個目錄建立時, 其權限設定為只讓使用者可以讀取其內容.

設定與 Windows 登錄檔

除了一般使用 INI 程式的設定區域, 執行於 Windows 上的 Subversion 用戶端也會利用 Windows 登錄檔來紀錄設定資料. 選項名稱與值都與 INI 檔案內的相同, 而 “檔案/區塊” 的架構也是一樣的, 但是說明時會有點不同 — 在該種方式下, 檔案與區塊只是登錄的不同階層而已.

Subversion 會在 HKEY_LOCAL_MACHINE\Software\Tigris.org\Subversion 鍵裡找系統設定值. 舉個例子, global-ignore 是一個在 config 檔的 miscellany 區塊中的選項, 可以在 HKEY_LOCAL_MACHINE\Software\Tigris.org\Subversion\Config\Miscellany\global-ignores 中找到. 使用者設定值則應該存於 HKEY_CURRENT_USER\Software\Tigris.org\Subversion 中.

登錄檔設定選項會在其對應的設定檔案 之前 被處理, 所以會被設定檔案中的數值給覆蓋過去. 換句話說, Windows 系統上的設定優先次序一定是以下列順序處理的:

  1. 命令列選項

  2. 使用者的 INI 檔案

  3. 使用者的登錄檔數值

  4. 系統的 INI 檔案

  5. 系統的登錄檔數值

另外, Windows 的登錄檔並不支援 “註解” 的語法. 不過 Subversion 會忽略任何以井字號 (#) 作為開頭的選項鍵. 這讓你能夠把一個 Subversion 選項變成註解, 而不必將其自登錄檔中刪去, 顯而易見地, 讓回復該選項的動作簡單多了.

svn 命令列用戶程式永遠不會寫入 Windows 登錄檔, 也不會在那裡建立預設的設定區域. 你可以利用 REGEDIT 來建立你需要的鍵. 另外, 你也可以建立一個 .REG 檔, 然後在檔案總管中雙擊該檔, 這樣會讓裡面的資料併入你的登錄檔中.

Example 6.1. 登錄項目 (.REG) 檔案的範例.

REGEDIT4

[HKEY_LOCAL_MACHINE\Software\Tigris.org\Subversion\Servers\groups]

[HKEY_LOCAL_MACHINE\Software\Tigris.org\Subversion\Servers\global]
"#http-proxy-host"=""
"#http-proxy-port"=""
"#http-proxy-username"=""
"#http-proxy-password"=""
"#http-proxy-exceptions"=""
"#http-timeout"="0"
"#http-compression"="yes"
"#neon-debug-mask"=""
"#ssl-authority-files"=""
"#ssl-trust-default-ca"=""
"#ssl-client-cert-file"=""
"#ssl-client-cert-password"=""

[HKEY_CURRENT_USER\Software\Tigris.org\Subversion\Config\auth]
"#store-password"="no"

[HKEY_CURRENT_USER\Software\Tigris.org\Subversion\Config\helpers]
"#editor-cmd"="notepad"
"#diff-cmd"=""
"#diff3-cmd"=""
"#diff3-has-program-arg"=""

[HKEY_CURRENT_USER\Software\Tigris.org\Subversion\Config\miscellany]
"#global-ignores"="*.o *.lo *.la #*# .*.rej *.rej .*~ *~ .#*"
"#log-encoding"=""
"#use-commit-times"=""
"#template-root"=""
"#enable-auto-props"=""

[HKEY_CURRENT_USER\Software\Tigris.org\Subversion\Config\tunnels]

[HKEY_CURRENT_USER\Software\Tigris.org\Subversion\Config\auto-props]

前述的範例是 .REG 檔案的內容, 其中包含了幾個常用的設定選項, 以及它們的預設值. 請注意裡面同時包含了系統 (網路代理伺服器相關的選項) 與使用者設定 (編輯器程式與儲存密碼的地方, 以及有的沒有的). 你只需要自選項名稱之前移去井字號 (#), 然後設定你想要的值.

設定選項

在本節中, 我們會討論目前 Subversion 支援的執行時期設定選項.

Servers

servers 檔案包含了有關於網路部份的 Subversion 設定選項. 本檔案中有兩個特定的區塊名稱 — groupsglobal. groups 區塊實際上是個交互參照的表格. 本區塊中的設定鍵是檔案中其它區塊的名稱; 它們的值是 glob — 一種文字記號, 可以包含萬用字元 — 它們會用來與 Subversion 提出要求的主機名稱進行比較.

[groups]
beanie-babies = *.red-bean.com
collabnet = svn.collab.net

[beanie-babies]
…

d[collabnet]
…

當 Subversion 透過網路使用時, 它會試著將嚐試連接的主機名稱與 groups 區塊的群組名稱作比對. 如果比對成功, Subversion 會在 servers 檔案中, 找尋名稱與比對到的群組名稱相同的區塊. 然後由該區塊中, 讀取真正的網路設定區塊.

所有無法與任何一個 groups 區塊的 glob 比對到的伺服器, 都會使用 global 區塊中的設定. 本區塊中所能使用的設定, 與該檔案中 (當然了, 特殊的 groups 區塊是例外) 的其它伺服器區塊內所使用的設定是相同的, 可用的如下:

http-proxy-host

用來指定代理伺服器主機名稱, Subversion 的 HTTP 要求會經由它傳送出去. 預設為空白值, 表示 Subversion 不會將 HTTP 要求透過代理伺服器傳送, 而是試著直接與目標機器溝通.

http-proxy-port

用來指定代理伺服器主機的連接埠, 預設為空白值.

http-proxy-username

用來指定代理伺服器所用的使用者名稱, 預設為空白值.

http-proxy-password

用來指定代理伺服器所用的密碼, 預設為空白值.

http-timeout

用來指定等待伺服器回應的時間, 單位為秒. 如果你遇到因為低速網路連線所導致的 Subversion 作業逾時, 你應該增加本選項的數值. 預設值為 0, 表示讓低層的 HTTP 程式庫, 也就是 Neon, 直接使用它的預設逾時設定.

http-compression

指定 Subversion 是否應該壓縮送往 DAV 伺服器的網路要求. 預設值為 yes (不過只有連壓縮的功能一起編進網路層才有用). 將它設定為 no 會關閉這個功能, 像是對網路傳輸進行除錯時.

neon-debug-mask

這是一個整數遮罩, 由底層 HTTP 程式庫 Neon 使用, 用來選擇會產生哪些類型的除錯輸出. 預設值為 0, 表示不會顯示任何除錯輸出. 想要知道 Subversion 如何使用 Neon, 請參見 Chapter 7, Developer Information.

ssl-authory-file

這是用來指定包含 certificate authority (或稱 CA) 的檔案, 當 Subversion 透過 HTTPS 存取檔案庫時, 這些會被用戶端程式所接受.

svn-tunnel-agent

指定外部代理程式, 作為 SVN 通訊協定的通道.

Config

config 檔案包含了其它目前 Subversion 可用的執行時期選項, 這些選項都與網路無關. 目前可用的選項不多, 不過它們還是分類成不同的區塊, 以應付未來的需求.

auth 區塊包含了與 Subversion 檔案庫的認證與授權有關的選項, 它包含了:

store-password

這個選項告訴 Subversion 是否要快取使用者對伺服器認證回應的密碼. 預設值是 yes. 將這個選項設為 no 將關閉這個儲存在磁碟上的快取. 你可以透過 svn 命令的 --no-auth-cache 命令列參數 (如果子命令支援它的話), 暫時取代這個選項的效用.

helpers 區塊設定 Subversion 用來完成其工作的外部應用程式. 本區塊的有效選項為:

editor-cmd

指定 Subversion 在進行送交作業時, 用來向使用者要求送交訊息的程式, 像是使用 svn commit 而又沒有提供 --message (-m) 或 --file (-F) (-F) 選項時. 這個程式也會用在 svn propedit 命令中 — 會有一個暫存檔, 裡面有使用者想要編輯的性質的值, 編輯的行為會直接在編輯器中發生 (參見 the section called “性質”). 這個選項的預設值為空白值. 如果這個選項沒有設定的話, Subversion 會退而檢查 SVN_EDITOR, VISUALEDITOR 環境變數 (依此順序), 以取得編輯器命令.

diff-cmd

指示差異程式的絕對路徑, 在 Subversion 產生 “差異” 輸出 (像是使用 svn diff 命令時) 時使用. 預設值是 GNU diff 程式的路徑, 由 Subversion 源碼編譯系統所決定.

diff3-cmd

指定三向差異程式的絕對路徑. Subversion 使用這個程式, 將使用者產生的更動與來自檔案庫的更動合併在一起. 預設值是 GNU diff3 程式的路徑, 由 Subversion 源碼編譯系統所決定.

diff3-has-program-arg

如果 diff3-cmd 選項接受 --diff-program 命令列參數的話, 本旗標應該設為 true. 由於 diff3-cmd 選項的預設值是在編譯時期決定的, diff3-has-program-arg 的預設值亦同.

miscellany 區塊是所有不屬於其它區塊的選項集合處. [21] 在本區塊中, 你可以看到:

global-ignores

在執行 svn status 命令時, 除了納入版本控制的之外, Subversion 也會列出所有未納入版本控制的目錄與檔案, 並以 ? 字元表示 (請參見 the section called “svn status”). 有的時候, 看到列表中有一些讓人不感興趣或未納入版本控制的項目 — 舉個例子, 像是程式編譯時所產生的 object 檔 — 是很令人厭煩的. global-ignores 選項是一個以空白字元隔開的 glob 列表, 用以描述 Subversion 不應顯示的目錄與檔案名稱, 除非已納入版本控制. 預設值是 *.o *.lo *.la #*# .*.rej *.rej .*~ *~ .#*.

你可以在單次執行 svn status 命令時忽略本選項, 只要透過 --no-ignore 命令列旗標即可. 想要知道更多忽略項目的控制細節, 請參考 the section called “svn:ignore”.

性質

我們已經詳細講解 Subversion 如何在它的檔案庫中儲存與取出納入版本控制的檔案. 有一整章的內容, 都在講解這個工具所提供的基本功能. 就版本控制的角度來看, 如果版本控制的支援只有這些, Subversion 還是一個完整的工具. 不過它有的還不止如此而已.

除了對你的目錄與檔案進行版本控制之外, Subversion 還提供了一個界面, 可用來新增, 修改, 以及移除已納入版本控制的目錄與檔案的版本控制描述資料. 我們稱這個描述資料為 性質, 可以把它們想成一個兩欄的表格, 將每個檔案庫內的項目所關聯的性質名稱與任意的數值對映在一起. 一般來講, 性質的名稱與數值可以依你所需任意設定, 唯一的要求, 就是名稱必須是人類可讀的文字. 最棒的部份, 就是這些性質也是納入版本控制的, 就像你的檔案的文字內容一樣. 你可以修改, 送交, 以及回復性質修改 , 就像送交文字更動一樣簡單. 在你更新工作複本的時候, 也可以接收到別人的性質更動.

在本節中, 我們會檢視性質支援的工具 — 供 Subversion 的使用者, 以及 Subversion 本身使用的都有. 你會學到與性質有關的 svn 子命令, 以及性質修改如何影響正常的 Subversion 工作流程. 我們希望你會發現, Subversion 的性質可以增進你在使用版本控制的經驗.

為什麼要用性質?

對你的工作複本來說, 性質可說是相當有用的附加價值. 事實上, Subversion 自己就利用性質來放置特殊的資訊, 而且當作一種表示還需要進行某些處理的方法. 同樣地, 你可以依自己所需來使用性質. 當然囉, 任何你可以透過性質達到的, 你也可以透過一般納入版本控制的檔案作到, 不過先看看以下的 Subversion 性質使用例子.

假設你想要設計一個放置了許多數位照片的網站, 並以標題與日期戳記來顯示它們. 現在, 你的相片一直在變動, 所以你希望像大部份的站台一樣, 都儘量能夠自動化. 這些相片可能相當地大, 就像這類站台的共通特性一樣, 你希望能夠為站台訪客提供縮小的圖片. 你可以利用傳統的檔案來達到這樣的目的, 像是同時擺放 image123.jpgimage123-thumbnail.jpg. 或者如果你想要讓檔案名稱保持相同, 你可能會把縮小圖片放在不同的目錄, 像是 thumbnails/image123.jpg. 你也可以利用類似的方法, 儲存標題與日期戳記, 一樣還是存放在與原始影像不同的檔案. 很快地, 你的檔案樹就會一團糟, 每當有新的相片放上去, 就會增加好幾個檔案.

現在考慮以 Subversion 的檔案性質, 來進行相同的設定. 想像有一個影像檔 image123.jpg, 然後將該檔的性質設定為 caption, datestampe 以及 thumbnail. 現在你的工作複本目錄看起來更容易管理了 — 事實上, 裡面除了影像檔以外, 就沒別的了. 但是你的自動命令檔了解的更多. 它們知道可以利用 svn (更好的, 則是利用 Subversion 的語言繫結 — 請參考 the section called “Using Languages Other than C and C++”), 挖出網站所需的額外資訊, 而不必再去讀取索引檔, 或是玩著操弄路徑的遊戲.

你該如何 (或者該說是否) 使用 Subversion 的性質, 都由你自己決定. 如前所述, Subversion 對性質有自己的用法, 我們在本章稍候會討論到. 但是首先, 讓我們討論如何以 svn 程式來使用性質.

使用性質

svn 命令提供了幾種新增或修改目錄與檔案性質的方法. 對於有著簡短而可讀的性質內容, 要新增性質最簡單的方法, 就是利用命令列的 propset 來指定子命令的性質名稱與數值.

$ svn propset copyright '(c) 2003 Red-Bean Software' calc/button.c
property `copyright' set on 'calc/button.c'
$

但是我們已強調, Subversion 為性質的數值提供了強大的彈性. 如果你的性質內容想要有多行文字, 更甚是二進制內容的話, 你大概不會想要在命令列指定其內容, 所以 propset 子命令提供了一個 --file (-F) 選項, 用以指定一個檔案名稱, 將該檔的內容作為性質的內容.

$ svn propset license -F /path/to/LICENSE calc/button.c
property `license' set on 'calc/button.c'
$

除了 propset 命令以外, svn 程式還提供了 propset 命令. 這個命令會使用設定的文字編輯程式 (參見 the section called “Config”) 以新增或修改性質. 當你執行這個命令時, svn 會讓文字編輯器修改一個暫存檔, 裡面包含了目前指定性質的內容 (或者什麼都沒有, 如果你要新增一個性質的話). 接著你只要在文字編輯器中修改這個內容, 直到它成為你想要的新內容, 然後存檔並跳離程式. 如果 Subversion 發現你真的修改了該性質的現有內容, 那麼它會接受, 並視為新的性質內容. 如果你未作任何修改就跳離文字編輯器的話, 那麼性質並不會有任何變動.

$ svn propedit copyright calc/button.c  ### exit the editor without changes
No changes to property `copyright' on `calc/button.c'
$

就像其它的 svn 子命令, 我們應該注意到性質也可以一次作用在多個路徑上. 這讓你能夠利用一個命令, 一次修改一組檔案的性質. 舉個例子, 我們可以這樣作:

$ svn propset copyright '(c) 2002 Red-Bean Software' calc/*
property `copyright' set on 'calc/Makefile'
property `copyright' set on 'calc/button.c'
property `copyright' set on 'calc/integer.c'
…
$

如果你不能很容易地取得儲存的性質內容的話, 這些新增與編輯性質的功能就沒有多大的用處. 因此 svn 程式提供了兩個子命令, 用以顯示儲存在目錄與檔案的性質名稱與內容. svn proplist 命令會列出所有儲存於某一路徑的所有性質名稱. 當你知道該節點的性質名稱之後, 你就可以利用 svn propget 來取得個別的內容. 只要指定一個路徑 (或一組路徑) 以及一個性質名稱, 這個命令會在標準輸出串流上顯示性質內容.

$ svn proplist calc/button.c
Properties on 'calc/button.c':
  copyright
  license
$ svn propget copyright calc/button.c
(c) 2003 Red-Bean Software

proplist 命令甚至還有變形, 可以列出所有性質的名稱與其內容. 只要指定 --verbose (-v) 選項即可.

$ svn proplist --verbose calc/button.c
Properties on 'calc/button.c':
  copyright : (c) 2003 Red-Bean Software
  license : ================================================================
Copyright (c) 2003 Red-Bean Software.  All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions 
are met:

1. Redistributions of source code must retain the above copyright
notice, this list of conditions, and the recipe for Fitz's famous
red-beans-and-rice.
…

最後一個與性質有關的子命令, 是 propdel. 由於 Subversion 允許你儲存空值的性質, 你無法利用 propeditpropset 來移除性質. 舉個例子, 以下的命令 不會 產生你想要的效果:

$ svn propset license '' calc/button.c
property `license' set on 'calc/button.c'
$ svn proplist --verbose calc/button.c
Properties on 'calc/button.c':
  copyright : (c) 2003 Red-Bean Software
  license : 
$

你需要使用 propdel 命令來刪除性質. 它的語法很接近其它性質命令:

$ svn propdel license calc/button.c
property `foo' deleted from ''.
$ svn proplist --verbose calc/button.c
Properties on 'calc/button.c':
  copyright : (c) 2003 Red-Bean Software
$

現在你已經熟悉了所有關於性質的 svn 子命令, 讓我們看看性質修改如何影響一般的 Subversion 工作流程. 如我們早先提過的, 目錄與檔案的性質都被納入版本管理, 就像你的檔案內容一樣. 這樣的結果, 就是 Subversion 也提供合併 — 無誤地合併, 或是產生衝突 — 別人更動的機會.

就像檔案內容一樣, 性質的更動算是本地修改, 只有在你透過 svn commit 送進檔案庫後, 它才會永久生效. 你所作的性質更動也可以很容易地回復 — svn revert 命令會把你的目錄與檔案回復至它們尚未編輯的狀態、內容、性質、所有的東西. 還有, 透過 svn statussvn diff, 你也可以得到關於目錄與檔案的一些有趣資訊.

$ svn status calc/button.c
 M     calc/button.c
$ svn diff calc/button.c
Property changes on: calc/button.c
___________________________________________________________________
Name: copyright
   + (c) 2003 Red-Bean Software

$

請注意 status 子命令是如何在第二欄, 而非第一欄中顯示 M. 那是因為我們修改了 calc/button.c 的性質, 但是還沒有修改它的文字內容. 要是我們兩位都變更了, 我們也會在第一欄看到 M (請參見 the section called “svn status”).

你也許也注意到, Subversion 使用非標準的方式來顯示性質差異. 你還是可以執行 svn diff, 然後將結果重導向, 以產生出適合的修補檔. patch 程式會忽略掉性質的部份 — 一般來說, 它會忽略任何它看不懂的東西. 很不幸地, 如果你想要完全套用 svn diff 的修補檔的話, 所有性質修改的地方, 都必須手動為之.

如你所見, 性質修改的存在, 對於一般的 Subversion 工作流程並沒有很大的影響. 更新工作複本, 檢查目錄與檔案的狀態, 回報你所作的更動, 以及送交更動回檔案庫的工作型態, 與性質的存在與否是毫不相干的. svn 程式是多了幾個可以用來修改性質的子命令, 不過也就只有這個明顯可見的差異而已.

特殊性質

Subversion 對性質沒有什麼特別的規定 — 你可以為任何理由而使用它們. Subversion 只要求使用者不要使用以 svn: 作為開頭的性質. 這個命名空間已經保留下來, 有它自己的用途. 事實上, Subversion 定義了幾個特殊的性質, 它對關聯到的目錄與檔案有神奇的效用. 在本節中, 我們會解開這層神祕的面紗, 說明這些特殊的性質是如何讓你的生命稍微好過一點.

svn:executable

svn:execute 性質是以半自動的方式, 用以控制一個納入版本控制的檔案在檔案系統中的執行權限位元. 這個性質並沒有指定內容 — 只要它存在, 就表示 Subversion 應該開啟它旳執行權限位元. 移除它的話, 就完全由作業系統控制執行權限位元.

在許多作業系統中, 一個檔案是否能當作命令來執行, 取決於執行權限位元是否存在. 這個位元通常是被關閉的, 必須由使用者對每一個需要的檔案開啟它. 在工作複本中, 在每一次更新收到現有檔案的新版本時, 會當作新檔案來建立. 這表示你可以開啟一個檔案的執行權限位元, 然後更新工作複本, 如果那個檔案也是更新的一部份, 它的執行權限位元會被關閉. 為此, Subversion 提供了 svn:executable 性質, 以作為保持權行權限位元開啟的方法.

在沒有執行權限位元概念的檔案系統上, 這個性質是沒有作用的, 像是 FAT32 與 NTFS. [23] 另外, 雖然它沒有定義值, Subversion 在設定這個性質時, 會強制它的值為 *. 最後, 這個性質只對檔案有效而已, 對目錄是無效的.

svn:mime-type

svn:mime-type 性質在 Subversion 中有很多作用. 除了作為儲存檔案的多用途網際網路郵件延伸語法 (MIME) 分類之外, 這個性質的內容還會決定幾項 Subversion 的行為特徵.

舉個例子, 如果 svn:mime-type 性質設為文字的 MIME 類別 (一般來說, 即為不是以 text/ 開始的, 不過還是有例外), Subversion 會假設該檔的內容是二進制 — 也就是人類看不懂 — 的資料. Subversion 提供的功能中, 其中一項是在從伺服器收到工作檔的更新中, 依文字內容與文字列進行合併. 但是對據信含有二進制資料的檔案, 根本就沒有 “文字列” 的概念. 因此, Subversion 對這些檔案在更新時, 不會試著進行內文合併. 它改用另一種方式, 更新時如果有二進制檔案有本地更動的話, 你的檔案會改以 .orig 副檔名為名, 然後 Subversion 會以原始檔名儲存一份新的工作檔案, 其中包含有更新時收到的更動, 但是不包括你自己的本地更動. 這樣的行為是要保護使用者, 以避免對無法進行內文合併的檔案進行內文合併.

Subversion 在執行 svn importsvn add 子命令時, 會使用二進制偵測運算法的方式來協助使用者. 這些子命令透過聰明的方法, 偵測檔案的 “二進制性”, 然後設定據信為二進位檔案的 svn:mime-type 性質為 application/octet-stream (一般的 “這是一些位元集合” 的 MIME 類別). 如果 Subversion 猜錯了, 或是你希望將 svn:mime-type 設定成更為明確的值 — 可能是 image/pngapplication/x-shockwave-flash — 你都可以移除或是手動編輯這個性質.

最後, 如果 svn:mime-type 性質被設定的話, 在回應 GET 要求時, Subversion 的 Apache 模組會使用這個值來作為 HTTP 標頭 Content-type: 的內容. 在以瀏灠器觀看檔案庫的內容時, 它對如何顯示一個檔案提供了決定性的提示.

svn:ignore

svn:ignore 性質包含了檔案樣式的列表, Subversion 處理時會忽略. 它也許是最常使用特殊性質, 可以與執行時期設定的 global-ignores 選項 (請參見 the section called “Config”) 一起工作, 以便在類似 svn status 的命令中過濾掉未納入版本控制的目錄與檔案.

svn:ignore 性質的基本原理很容易解釋. Subversion 並不會假設每個在工作複本目錄裡的子目錄或檔案都會被納入版本控制. 一個資源必須透過 svn add 命令, 才會被納入 Subversion 的管理. 也因為如此, 工作複本中常有許多並未納入版本控制的資源.

現在, svn status 命令會將工作複本中每個沒有被 global-ignores 選項 (或其預設值) 過濾掉的目錄與檔案給顯示出來. 這樣作的原因, 是讓使用者可以看看是否忘了把某個資源加進版本控制.

但是 Subversion 沒有任何方法可以猜測什麼名字是應該忽略的. 而且某個檔案庫的 每一個 工作複本都常常會有固定該忽略的東西. 要強迫每一個使用該檔案庫的使用者將這些資源的樣式加進他們自己的執行時期設定檔, 不只是負擔而已, 還有可能會搞亂該使用者為其它已取出的工作複本所作的設定.

解決的方法, 就是把針對可能出現在某個目錄裡的資料的忽略樣式, 儲存在該目錄上. 常見的專屬於某個目錄卻又不希望納入版本控制的例子, 包括編譯過程中產生的檔案, 或者 — 用個更適合本書的例子 — 像是 HTML, PDF, 或是 PostScript 檔案, 這些由某些 DocBook XML 源碼檔案轉換成更合用的輸出格式的結果.

因為這個緣故, svn:ignore 性質就是解決方案. 它的內容是多行的檔案樣式集合, 每一個樣式一行. 這個性質是設在你希望它生效的目錄之上. [24] 舉個例子, 假設你有以下的 svn status 的輸出:

$ svn status calc
 M     calc/button.c
?      calc/calculator
?      calc/data.c
?      calc/debug_log
?      calc/debug_log.1
?      calc/debug_log.2.gz
?      calc/debug_log.3.gz

在這個範例中, 你對 button.c 作了一些性質更動, 但是在你的工作複本中還有其它的未納入版控制的檔案. 像是在這個例子中, 有從源碼編輯出來的最新 calculator 程式, 一個名為 data.c 的源碼檔, 以及一組除錯用的輸出記錄檔. 現在, 你知道你的編譯系統都會產生 calculator 程式. [25] 然後你還知道你的測試工具都會到處留下除錯用的紀錄檔. 這對每一個工作複本都是既存的事實, 不單只有你的如此. 你也知道你在每次執行 svn status 時, 並不希望看到這些檔案. 所以你透過 svn propedit svn:ignore calccalc 目錄加上一些忽略樣式. 舉個例子, 你也許會將以下的值作為 svn:ignore 性質的新內容:

calculator
debug_log*

在你加上這個性質後, calc 目錄就有了本地更動. 請注意你的 svn status 輸出的不同處:

$ svn status
 M     calc
 M     calc/button.c
?      calc/data.c

現在, 所有不想看到的東西都從輸出中消失了! 當然囉, 這些檔案都還在你的工作複本中, Subversion 只是不會再提醒你這些東西的存在, 還有它們尚未納入版本控制. 現在, 這些無用的雜音自輸出移開之後, 你就能專注在更有趣的項目上 — 像是你可能忘了加進版本控制的源碼.

如果你想要看到被忽略的檔案, 你可以傳遞 --no-ignore 選項給 Subversion:

$ svn status --no-ignore
 M     calc/button.c
I      calc/calculator
?      calc/data.c
I      calc/debug_log
I      calc/debug_log.1
I      calc/debug_log.2.gz
I      calc/debug_log.3.gz

svn:keywords

Subversion 具有取代 關鍵字 — 有關納入版本控制檔案的有用資訊 — 進入檔案內容的功能. 一般來講, 關鍵字描述了檔案最近一次更動的資訊. 由於這些資訊會隨著每次檔案的更動而變更, 更重要的, 是在檔案更動 之後. 如果不是由版本控制系統來保持這些資料的即時性的話, 不管以哪些處理方式都很麻煩. 如果留給人類使用者處理的話, 這些資訊不可避免地會變成無人維護.

舉個例子, 假設你有個文件, 想要在裡面顯示最近一次修改的日期. 你可以把這個負擔加諸文件的作者身上, 只要在每一次送交他們的更動之前, 順便把修改紀錄最近一次修改日期的部份. 但是遲早有人會忘記這件事. 換個方式, 只要叫 Subversion 對 LastChangedDate 關鍵字進行關鍵字取代即可. 藉由擺放 關鍵字定位錨, 你可以控制關鍵字出現在文字中的位置. 前述的定位錨就只是格式為 $關鍵字名稱$ 的文字字串.

Subversion 定義了可用來進行取代的關鍵字列表. 這個列表包含了以下五個關鍵字, 有些還可以使用較短的別名:

LastChangedDate

這個關鍵字描述了最近一次更動的日期, 看起來像 $LastChangedDate: 2002-07-22 21:42:37 -0700 (Mon, 22 Jul 2002) $. 它也可以縮寫成 Date.

LastChangedRevision

這個關鍵字描述本檔於檔案庫中最後一次更動的修訂版, 看起來像 $LastChangedRevision: 144 $. 它可以縮寫成 Rev.

LastChangedBy

這個關鍵字描述了本檔案於檔案庫最後一次修改的使用者, 看起來像 $LastChangedBy: harry $. 它可以縮寫成 Author.

HeadURL

這個關鍵字描述了本檔案於檔案庫中的最新版的完整 URL, 看起來像 $HeadURL: http://svn.collab.net/repos/trunk/README $. 它可以縮寫成 URL.

Id

這個關鍵字是其它關鍵字的壓縮集合. 它被取代後的樣子, 看起來像 $Id: calc.c 148 2002-07-28 21:30:43Z sally $, 表示檔案 calc.c 最近一次是在修訂版 14, 於 2002 年七月 28 日晚上被使用者 sally 修改.

只把關鍵字定位錨加進檔案裡的話, 什麼事也不會發生. 除非你明確地表達要這麼作的意願, 不然 Subversion 不會試著對你的檔案內容進行內容取代. 畢竟, 你可能在寫一份關於如何使用關鍵字的文件 [26] , 並不想要 Subversion 把你不需要取代的關鍵字定位錨的美麗例子給取代掉.

要告訴 Subversion 是否該對某一個檔案進行關鍵字取代, 我們得轉向使用性質相關的子命令. 當某一個納入版本控制檔案的 svn:keywords 性質被設定時, 它會控制該檔案哪個關鍵字應該被取代. 這個值的內容, 是前述表格中的關鍵字或別名, 以空白隔開的列表.

舉個例子, 假設你有一個納入版本控制的檔案, 名為 weather.txt, 看起來像這樣:

Here is the latest report from the front lines.
$LastChangedDate$
$Rev$
Cumulus clouds are appearing more frequently as summer approaches.

如果沒有設定該檔案的 svn:keywords 性質, Subversion 什麼事也不會作. 讓我們開啟關鍵字 LastChangedDate 的內容取代.

$ svn propset svn:keywords "LastChangedDate Author" weather.txt
property `svn:keywords' set on 'weather.txt'
$

現在你對 weather.txtt 產生了一個本地的性質修改. 你還不會看到檔案的內容有任何的變化 (除非你已經設定過了這個性質). 請注意這個檔案包含了一個 Rev 關鍵字的定位錨, 但是我們的性質內容並未包含這個關鍵字. Subversion 會很高興地略過不在檔案中的關鍵字取代要求, 而且也不會取代不在 svn:keywords 性質內容中的關鍵字.

在你送交了這個性質更動之後, Subversion 會以新的取代文字更新你的工作檔案. 你看到的不會是關鍵字定位錨 $LastChangedDate$, 而是它被取代後的結果. 結果會包含關鍵字的名稱, 而且仍舊包含在錢字號 ($) 裡. 如我們所預測的, Rev 關鍵字並不會被取代, 因為我們並未這樣要求.

Here is the latest report from the front lines.
$LastChangedDate: 2002-07-22 21:42:37 -0700 (Mon, 22 Jul 2002) $
$Rev$
Cumulus clouds are appearing more frequently as summer approaches.

如果別人現在送交了 weather.txt 的更動, 你的檔案副本還是會像原來的樣子顯示被取代的關鍵字 — 除非你更新工作複本. 在那個時候, 你的 weather.txt 裡的關鍵字會重新被取代, 以反映該檔案最近一次所作的送交.

svn:eol-style

除非另外指定版本控制檔案的 svn:mime-type 性質, Subversion 會假設檔案包含人類可讀的資料. 一般來說, Subversion 只會這樣來決定回報檔案內容差異是否可行. 不然的話, 對 Subversion 來說, 位元就只是位元而已.

這表示 Subversion 預設並不會處理檔案裡 列尾符號 (EOL) 標示. 很不幸地, 不同的作業系統使用不同的符號來表示一列的結尾. 舉個例子, 一般用在 Windows 平台上的列尾符號是兩個 ASCII 控制字元 — 返回字元 (CR) 與換行字元 (LF). 但是 Unix 軟體就只使用 LF 字元來表示一列的結尾.

並不是這些作業系統上的程式, 都會認得執行平台的 原生列尾樣式 以外的格式. 一般的結果, 是 Unix 程式把 Windows 檔案的 CR 字元表示成一般的字元 (一般顯示成 ^M), 而 Windows 程式會把 Unix 檔案裡的所有文字列合併成一個超長的文字列, 這是因為沒有返回-換行 (或是 CRLF) 字元組合的存在來表示一列的結束..

像這樣對外來的 EOL 符號這麼敏感, 對不同作業系統之間共用檔案的人很不方便. 舉個例子, 想像有一個源碼檔, 而發展人員各在 Windows 與 Unix 系統上編輯這個檔案. 如果所有的發展人員都使用會保留檔案的列尾樣式的工作, 那就一點問題都沒有.

但是實際上, 許多常見的工具不是無法正確地讀出有外來的 EOL 符號的檔案, 就是在存檔時, 會轉換成原生的列尾符號. 如果對發展人員是前者的話, 他就必須使用一個外部轉換工作 (像是 dos2unix 或是 unix2dos), 讓這個檔案可以被編輯. 後者就不必作額外的處理了. 不過這兩種方法, 都會讓檔案的每一列都與原來的不一樣! 在送交他的更動之前, 使用者有兩種選擇. 不是使用轉換工具, 把它還原成與編輯之前檔案相同的列結尾樣式, 就是直接送交這個檔案 — 完全變成新的 EOL 符號.

像這樣情況之下, 我們得到的就是時間的浪費, 以及對送交檔案不必要的修改. 浪費時間已經夠苦的了, 但是當送交的更動會改變檔案的每一行時, 要找出到底修改了哪些地方就不是一件簡單的工作. 臭蟲到底在哪裡被修正了? 哪一行導致了新的語法錯誤?

解決的方法是 svn:eol-style 性質. 當這個性質設定為一個有效值時, Subversion 會利用它來決定是否對該檔案進行特別處理, 以免因為每一次有從不同作業系統來的送交, 而造成檔案的列尾樣式就要換一次. 有效的值為:

native

這會讓檔案的 EOL 符號為 Subversion 執行的平台的原生列尾符號. 換句話說, 如果一個 Windows 使用者取出的檔案, 它的 svn:eol-style 性質設定為 native, 那個檔案會包含 CRLF 的 EOL 符號. Unix 使用者取出相同檔案的工作複本的話, 會看到該檔其中的是 LF EOL 符號.

請注意, 不管作業系統為何, Subversion 實際上是以正規化的 LF EOL 符號來儲存的. 不過呢, 基本上使用者是不需要知道的.

CRLF

這會讓檔案包含 CRLF 序列作為 EOL 符號, 不管使用中的作業系統為何.

LF

這會讓檔案包含 LF 字元作為 EOL 符號, 不管使用中的作業系統為何.

CR

這會讓檔案包含 CR 字元作為 EOL 符號, 不管使用中的作業系統為何. 這種列尾樣式並不常見. 它是用在舊型的麥金塔平台上 (不過 Subversion 在上面根本不能執行).

svn:externals

svn:externals 性質的值, 會指示 Subversion 以其它 Subversion 取出的工作複本來產生納入版本控制的目錄. 想要知道更多這個關鍵字的資訊與用法, 請參見 the section called “外部定義”.

外部定義

有的時候, 一個工作複本包含了數個不同來源的工作複本是很方便的. 舉個例子, 你可能想要有數個不同的目錄, 各來自同一個檔案庫的不同位置, 或是根本就是來自不同的檔案庫. 你當然可以手動建立 — 透過 svn checkout , 你就可以建立你想要建立的目錄架構. 但是如果這個架構對每個使用這個檔案庫的人都很重要的話, 每個人就必須進行你所作過的相同動作.

很幸運地, Subversion 提供 外部定義 的支援. 外部定義是本地目錄與外部版本控制資料的 URL. 在 Subversion 中, 你應該利用 svn:externals 性質來宣告外部定義的群組. 這個性質是對納入版本控制的目錄設定, 內容是子目錄 (相對於本性質設定的納入版本控制的目錄) 對應至 Subversion 檔案庫 URL 的多行表格.

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

svn:externals 性質便利之處, 在於一旦對納入版本控制的目錄設定完成之後, 每一個取出該目錄的工作複本的人也一併會得到外部定義的好處. 換句話說, 一旦有人花時間定義了這些巢狀工作複本架構, 別人就不必煩惱了 — 在取出原本的工作複本時, Subversion 也會一併取出外部工作複本.

請注意先前的外部定義範例. 當有人取出 calc 目錄的工作複本, Subversion 還會繼續取出在外部定義裡的項目.

$ svn checkout http://svn.example.com/repos/calc
A  calc
A  calc/Makefile
A  calc/integer.c
A  calc/button.c
Checked out revision 148.

Fetching external item into calc/third-party/sounds
A  calc/third-party/sounds/ding.ogg
A  calc/third-party/sounds/dong.ogg
A  calc/third-party/sounds/clang.ogg
…
A  calc/third-party/sounds/bang.ogg
A  calc/third-party/sounds/twang.ogg
Checked out revision 14.

Fetching external item into calc/third-party/skins
…

如果你需要更改外部定義, 你可以使用正常的性質修改子命令來達成. 當你送交了 svn:externals 性質的更動, Subversion 會在你下次執行 svn update 時, 重新對更動過後的性質取出工作複本. 相同的事會發生在別人更新他們的工作複本, 然後收到你對外部定義更動時.

供應商分支

發展軟體特別會遇到的情況, 就是你在版本控制下維護的軟體, 常常是與別人資料相關, 或是根基於其上. 一般來講, 你的專案會要求你隨時更新這個外部軟體, 但是又不損及你自己專案的穩定性. 這種情況一直都在發生 — 任何地方, 某一群人產生的資料, 對另一群人有直接的影響.

舉個例子, 軟體發展人員可能發展一個應用程式, 它使用第三方的程式庫. Subversion 就是如此使用 Apache Portable Runtime library (請參見 the section called “The Apache Portable Runtime Library”). Subversion 的源碼, 完全依靠 APR 程式庫來達成它對移植性的要求. 在 Subversion 發展的早期階段, 本專案緊密地追蹤 APR 不斷改動的 API, 一直盯住在程式庫不停變動的 “流血前線”. 現在 APR 與 Subversion 都已成熟了, Subversion 試著只對已充份測試, 穩定的發行版本的 APR 程式庫的 API 同步.

現在, 如果你的專案倚靠別人的資訊, 你有幾種方法可以讓這個資訊與你自己的同步. 最麻煩的方法, 你可以口頭或書面的告知專案的所有貢獻者, 應該如何確認他們有專案所需的特定版本的第三方資訊. 如果第三方資訊是維護於 Subversion 檔案庫中, 你可以使用 Subversion 的外部定義, 以有效地將該資訊的特定版本都 “釘死在” 工作複本中的相同位置 (請參見 the section called “外部定義”).

不過, 有的時候你也會想要在你自己的版本控制系統裡, 維護自已對第三方資料的自訂更動. 回到軟體發展的例子, 程式設計師也許會依他們的需要, 對第三方程式庫進行修改. 這些修改, 也許包含了新功能, 也許是臭蟲修正, 只會在內部維護, 讓它成為第三方程式庫正式發行的一部份. 也許這個更動永遠也不會回到程式庫的維護人員, 它就只是讓程式庫更依軟體發展人員所需而作的小更動.

現在你面臨了一個有趣的情況. 你的專案可以將它對第三方資料的修改, 以和原來無關的方式儲存, 像是修補檔, 或是整個不同的目錄或是檔案. 但是很快地, 這些就會變成維護上的包袱, 需要某種機制將你的自訂修改套用到第三方的資料上, 而且需要對每一個你追蹤的第三方資料各個版本產生重新這些更動.

這個問題的解決方法, 就是使用 供應商分支. 供應商分支是在你的版本控制軟體裡的目錄, 其中包含由第三方, 或是供應商所提供的資訊. 每一個你決定置放在專案中的供應商資料版本, 稱為 供應商 drop.

供應商分支有兩個優點. 第一, 將現有支援的供應商 drop 存在你自已的版本控制系統, 專案的成員就不必煩惱他們是否有供應商資料正確版本的問題, 你會隨著例行的工作複本更新而接收到正確版本的資料. 第二, 由於這些資料在你的 Subversion 檔案庫中, 你可以就地存放你的更動 — 你不需要自動 (或者更糟, 手動) 方法來切換你的自訂更動.

通用供應商分支管理程序

管理供應商分支的方法, 一般是以這種方式處理. 你建立一個最上層的目錄 (像是 /vendor) 來存放供應商分支. 然後將第三方源碼匯入到這個最上層目錄的一個子目錄中. 然後, 你將這個子目錄複製到你的主要發展分支 (舉個例子, /trunk) 至適當的位置. 你的更動, 一定都是在主要發展分支裡. 每次你追蹤的程式碼有了新的發行版本時, 就把它放進供應商分支, 將更動合併回 /trunk, 並且解決本地更動與上游更動之間的衝突.

也許來個例子, 就更能了解這個方法. 我們使用一個案例, 你的發展團隊正在製作一個計算機程式, 它需要連結到一個第三方的複數運算程式庫, 叫作 libcomplex. 我們一開始先建立一個供應商分支, 然後匯入第一個供應商 drop.

…
$ svn import /path/to/libcomplex-1.0 \
             http://svn.example.com/repos/calc/vendor/libcomplex/current \
             -m 'importing initial 1.0 vendor drop'
…

我們現在在 /vendor/libcomplex/current 中, 放置了現行版本的 libcomplex 源碼. 現在我們建立這個版本的標記 (請參見 the section called “標記”), 然後將把它複製到主發展分支, 這樣我們才能對它建立自訂修改.

$ svn copy http://svn.example.com/repos/calc/vendor/libcomplex/current  \
           http://svn.example.com/repos/calc/vendor/libcomplex/1.0      \
           -m 'tagging libcomplex-1.0'
…
$ svn copy http://svn.example.com/repos/vendor/libcomplex/1.0  \
           http://svn.example.com/repos/calc/libcomplex        \
           -m 'bringing libcomplex-1.0 into the main branch'
…

我們取出專案的主要分支 — 現在它包含了最初的供應商 drop 複本 — 然後開始建立 libcomplex 的自訂源碼. 在我們注意到之前, 我們修改過的 libcomplex 已經完全整合到我們的計算機程式. [27]

幾個星期後, libcomplex 的發展人員釋出了新版的程式庫 — 1.1 版 — 它包含了幾個非常想要的特色與功能. 但是我希望升級到這個新版本, 但是又仍保有我們對現存版本的自訂修正. 就如你所猜想的, 我們實際要作的, 就是以 libcomplex 1.1 的複本取代我們現存的基準版本 libcomplex 1.0, 然後再將我們對該程式庫所作的自訂修改套用到新的版本去.

要進行這樣的升級, 我們先取得一份供應商分支的複本, 然後以新的 libcomplex 1.1 源碼取得 current 版本. 在送交這個更動之後, 現在我們的 current 分支就包含了新的供應商 drop. 我們標記新的版本, 然後再將前一個版本的標記與新的現行版本之間的差異, 合併至我們的主要發展分支.

$ cd working-copies/calc
$ svn merge http://svn.example.com/repos/vendor/libcomplex/1.0      \
            http://svn.example.com/repos/vendor/libcomplex/current  \
            libcomplex
… # resolve all the conflicts between their changes and our changes
… # 解決所有他們與我們的更動之間的衝突
$ svn commit -m 'merging libcomplex-1.1 into the main branch'
…

在普通的使用情況下, 從目錄與檔案的觀點來說, 第三方工具的新版本看起來與前一個版本沒什麼差別. 換句話說, 沒有任何一個 libcomplex 源碼檔案被刪除, 更名, 或是移到新的位置 — 簡單地請, 在完美的世界中, 我們的更動可以無誤地套用在新版的程式庫, 一點也不複雜, 也不會有衝突發生.

但是事情並不總是那麼簡單, 而且事實上, 源碼在不同發行版本之間常常會移動. 確認我們的更動仍能適用在新版本的源碼變得複雜, 很快地, 我們會陷入必須手動對新版本重新作出我們的自訂更動. 不過只要 Subversion 知道某個指定源碼檔的歷史 — 包括它先前的位置 — 合併新版本程式庫的過程就很簡單了. 但是我們必須告訴 Subversion 在供應商 drop 版本之間的檔案配置更動.

svn-load-dirs.pl

如果供應商 drop 包含好幾個刪除, 新增, 以及移動的檔案的話, 更新第三方資料的連續版本就變得複雜. 所以 Subversion 提供了 svn_load_dirs.pl 腳本檔, 幫助你進行這樣的作業. 這個腳本檔會自動執行我們在通用供應商分支管理程序一節中所講的匯入方法, 以將錯誤降到最低. 不過你還是要自已執行合併命令, 以將新版本的第三方資料合併至你的主發展分支, 但是 svn_load_dirs.pl 可以幫助更快更輕鬆地達到那個階段.

簡單地說, svn_load_dirs.plsvn import 的加強, 它有以下幾個重要特性:

  • 它可以在任何時候, 即時地將現有檔案庫中的目錄變得與外部目錄一模一樣, 並且執行所有需要的新增與刪除動作, 有必要還可以執行移動的動作.

  • 它會處理一連串需要 Subversion 先進行送交的複雜作業 — 像是在重新命名目錄或檔案兩次之前.

  • 它可以依需要, 標記新匯入的目錄.

  • 它可以依需要, 對符合正規表示式的目錄與檔案加上任意的性質.

svn_load_dirs.pl 需要三個主要的引數. 第一個引數是用來工作的基礎 Subversion 目錄的 URL. 接著這個引數的是 URL — 相對於第一個引數 — 目前的供應商 drop 會匯入至該目錄下. 最後, 第三個引數是要匯入的本地目錄. 以我們前面的例子來說, svn_load_dirs.pl 執行起來就像這樣;

$ svn_load_dirs.pl http://svn.example.com/repos/calc/vendor/libcomplex \
                   current                                             \
                   /path/to/libcomplex-1.1
…

藉由傳遞 -t 命令列選項, 並且指定一個標籤名稱, 你可以讓 svn_load_dirs.pl 來標記新的供應商 drop. 這個標記是另一個相對於第一個程式引數的 URL.

$ svn_load_dirs.pl -t libcomplex-1.1                                   \
                   http://svn.example.com/repos/calc/vendor/libcomplex \
                   current                                             \
                   /path/to/libcomplex-1.1
…

當你執行 svn_load_dirs.pl 時, 它會檢查你 “現有” 供應商 drop 的內容, 然後將它與新的供應商 drop 作比較. 在最簡單的情況中, 不會有一個檔案出現在一個版本, 而在另一個版本不見的情況, 命令稿會毫無問題地進行新的匯入. 不過要是版本之間有不同檔案配置, svn_load_dirs.pl 會問你要如何解決這些差異. 舉個例子, 你有機會告訴命令稿 libcomplex 1.0 版的 math.c 已經更名為 libcomplex 1.1 的 arithmetic.c. 任何沒有以移動來解釋的差異, 將會當作普通的新增與刪除.

這個命令稿也接受一個獨立的設定檔, 設定符合正規表示式、 新增 到檔案庫的目錄與檔案的性質. 可以透過 -p 命令列, 向 svn_load_dirs.pl 指定設定檔. 設定檔的每一列, 包含以空白分隔的兩個或四個值: 一個類似 Perl 風格的正規表示式, 用以比對新增的路徑, 一個控制關鍵字 (可為 breakcont), 然後可以接一個性質的名稱與其內容.

\.png$              break   svn:mime-type   image/png
\.jpe?g$            break   svn:mime-type   image/jpeg
\.m3u$              cont    svn:mime-type   audio/x-mpegurl
\.m3u$              break   svn:eol-style   LF
.*                  break   svn:eol-style   native

對每個新增的路徑, 當依序比對的路徑正規表示式比對成功時, 與其對應的性質會變更, 除非控制指定為 break (這表示該路徑已經沒有性質要變動的). 如果控制指定為 cont — 這是 continue 的縮寫 — 那麼比對會繼續往控制檔案的下一列進行.

任何在正規表示式、性質名稱、或是性質內容裡的空白字元, 都必須以單引號或雙引號字元包起來. 你可以將不是用來包起空白字元的引號字元, 於其前加上倒斜線 (\) 將其逸出 (escape). 在剖析設定檔時, 倒斜線只會用來逸出引號, 所以對正規表示式, 不必對不必要的字元保護過頭.



[19] 這聲明只對那些不花分文取得 Subversion 的大多數人有效.

[20] APPDATA 環境變數會指向 Application Data, 所以你一定可以使用 %APPDATA%\Subversion 來表示這個資料夾.

[21] Anyone for potluck dinner?

[22] 修正送交紀錄訊息的拼字錯誤, 文法瑕疵, 以及 “就是不正確” 的問題, 大概是 --revprop 選項最常見的使用情況.

[23] Windows 檔案系統使用檔案副檔名 (像是 .EXE, .BAT, 以及 .COM) 來表示可執行檔.

[24] 這些樣式只對該目錄有效而已 — 它們不會遞迴式地加諸在子目錄上.

[25] 這不就是編譯系統的重點嗎?

[26] … 也有可能是書中的一個章節 …

[27] 而且還是沒有臭蟲的!

Chapter 7. Developer Information

Subversion is an open-source software project developed under an Apache-style software license. The project is financially backed by CollabNet, Inc., a California-based software development company. The community that has formed around the development of Subversion always welcomes new members who can donate their time and attention to the project. Volunteers are encouraged to assist in any way they can, whether that means finding and diagnosing bugs, refining existing source code, or fleshing out whole new features.

This chapter is for those who wish to assist in the continued evolution of Subversion by actually getting their hands dirty with the source code. We will cover some of the software's more intimate details, the kind of technical nitty-gritty that those developing Subversion itself—or writing entirely new tools based on the Subversion libraries—should be aware of. If you don't foresee yourself participating with the software at such a level, feel free to skip this chapter with confidence that your experience as a Subversion user will not be affected.

Layered Library Design

Subversion has a modular design, implemented as a collection of C libraries. Each library has a well-defined purpose and interface, and most modules are said to exist in one of three main layers—the Repository Layer, the Repository Access (RA) Layer, or the Client Layer. We will examine these layers shortly, but first, see our brief inventory of Subversion's libraries in Table 7-1. For the sake of consistency, we will refer to the libraries by their extensionless Unix library names (e.g.: libsvn_fs, libsvn_wc, mod_dav_svn).

Table 7.1. A Brief Inventory of the Subversion Libraries

LibraryDescription
libsvn_clientPrimary interface for client programs
libsvn_deltaTree and text differencing routines
libsvn_fsThe Subversion filesystem library
libsvn_raRepository Access commons and module loader
libsvn_ra_davThe WebDAV Repository Access module
libsvn_ra_localThe local Repository Access module
libsvn_ra_svnA proprietary protocol Repository Access module
libsvn_reposRepository interface
libsvn_subrMiscellaneous helpful subroutines
libsvn_wcThe working copy management library
mod_dav_svnApache module for mapping WebDAV operations to Subversion ones

The fact that the word "miscellaneous" only appears once in Table 7-1 is a good sign. The Subversion development team is serious about making sure that functionality lives in the right layer and libraries. Perhaps the greatest advantage of the modular design is its lack of complexity from a developer's point of view. As a developer, you can quickly formulate that kind of "big picture" that allows you to pinpoint the location of certain pieces of functionality with relative ease.

And what could better help a developer gain a "big picture" perspective than a big picture? To help you understand how the Subversion libraries fit together, see Figure 7-1, a diagram of Subversion's layers. Program flow begins at the top of the diagram (initiated by the user) and flows "downward".

Figure 7.1. Subversion's "Big Picture"

Subversion's "Big Picture"

Another benefit of modularity is the ability to replace a given module with a whole new library that implements the same API without affecting the rest of the code base. In some sense, this happens within Subversion already. The libsvn_ra_dav, libsvn_ra_local, and libsvn_ra_svn all implement the same interface. And all three communicate with the Repository Layer— libsvn_ra_dav and libsvn_ra_svn do so across a network, and libsvn_ra_local connects to it directly.

The client itself also highlights modularity in the Subversion design. While Subversion currently comes with only a command-line client program, there are already a few other programs being developed by third parties to act as GUIs for Subversion. Again, these GUIs use the same APIs that the stock command-line client does. Subversion's libsvn_client library is the one-stop shop for most of the functionality necessary for designing a working Subversion client (see the section called “Client Layer”).

Repository Layer

When referring to Subversion's Repository Layer, we're generally talking about two libraries—the repository library, and the filesystem library. These libraries provide the storage and reporting mechanisms for the various revisions of your version-controlled data. This layer is connected to the Client Layer via the Repository Access Layer, and is, from the perspective of the Subversion user, the stuff at the "other end of the line."

The Subversion Filesystem is accessed via the libsvn_fs API, and is not a kernel-level filesystem that one would install in an operating system (like the Linux ext2 or NTFS), but a virtual filesystem. Rather than storing "files" and "directories" as real files and directories (as in, the kind you can navigate through using your favorite shell program), it uses a database system for its back-end storage mechanism. Currently, the database system in use is Berkeley DB. [28] However, there has been considerable interest by the development community in giving future releases of Subversion the ability to use other back-end database systems, perhaps through a mechanism such as Open Database Connectivity (ODBC).

The filesystem API exported by libsvn_fs contains the kinds of functionality you would expect from any other filesystem API: you can create and remove files and directories, copy and move them around, modify file contents, and so on. It also has features that are not quite as common, such as the ability to add, modify, and remove metadata ("properties") on each file or directory. Furthermore, the Subversion Filesystem is a versioning filesystem, which means that as you make changes to your directory tree, Subversion remembers what your tree looked like before those changes. And before the previous changes. And the previous ones. And so on, all the way back through versioning time to (and just beyond) the moment you first started adding things to the filesystem.

All the modifications you make to your tree are done within the context of a Subversion transaction. The following is a simplified general routine for modifying your filesystem:

  1. Begin a Subversion transaction.

  2. Make your changes (adds, deletes, property modifications, etc.).

  3. Commit your transaction.

Once you have committed your transaction, your filesystem modifications are permanently stored as historical artifacts. Each of these cycles generates a single new revision of your tree, and each revision is forever accessible as an immutable snapshot of "the way things were."

Most of the functionality provided by the filesystem interface comes as an action that occurs on a filesystem path. That is, from outside of the filesystem, the primary mechanism for describing and accessing the individual revisions of files and directories comes through the use of path strings like /foo/bar, just as if you were addressing files and directories through your favorite shell program. You add new files and directories by passing their paths-to-be to the right API functions. You query for information about them by the same mechanism.

Unlike most filesystems, though, a path alone is not enough information to identify a file or directory in Subversion. Think of a directory tree as a two-dimensional system, where a node's siblings represent a sort of left-and-right motion, and descending into subdirectories a downward motion. Figure 7-2 shows a typical representation of a tree as exactly that.

Figure 7.2. Files and Directories in Two Dimensions

Files and Directories in Two Dimensions

Of course, the Subversion filesystem has a nifty third dimension that most filesystems do not have—Time! [29] In the filesystem interface, nearly every function that has a path argument also expects a root argument. This svn_fs_root_t argument describes either a revision or a Subversion transaction (which is usually just a revision-to-be), and provides that third-dimensional context needed to understand the difference between /foo/bar in revision 32, and the same path as it exists in revision 98. Figure 7-3 shows revision history as an added dimension to the Subversion filesystem universe.

Figure 7.3. Revisioning Time—the Third Dimension!

Revisioning Time—the Third Dimension!

As we mentioned earlier, the libsvn_fs API looks and feels like any other filesystem, except that it has this wonderful versioning capability. It was designed to be usable by any program interested in a versioning filesystem. Not coincidentally, Subversion itself is interested in that functionality. But while the filesystem API should be sufficient for basic file and directory versioning support, Subversion wants more—and that is where libsvn_repos comes in.

The Subversion repository library (libsvn_repos) is basically a wrapper library around the filesystem functionality. This library is responsible for creating the repository layout, making sure that the underlying filesystem is initialized, and so on. Libsvn_repos also implements a set of hooks—scripts that are executed by the repository code when certain actions take place. These scripts are useful for notification, authorization, or whatever purposes the repository administrator desires. This type of functionality, and other utility provided by the repository library, is not strictly related to implementing a versioning filesystem, which is why it was placed into its own library.

Developers who wish to use the libsvn_repos API will find that it is not a complete wrapper around the filesystem interface. That is, only certain major events in the general cycle of filesystem activity are wrapped by the repository interface. Some of these include the creation and commit of Subversion transactions, and the modification of revision properties. These particular events are wrapped by the repository layer because they have hooks associated with them. In the future, other events may be wrapped by the repository API. All of the remaining filesystem interaction will continue to occur directly with libsvn_fs API, though.

For example, here is a code segment that illustrates the use of both the repository and filesystem interfaces to create a new revision of the filesystem in which a directory is added. Note that in this example (and all others throughout this book), the SVN_ERR macro simply checks for a non-successful error return from the function it wraps, and returns that error if it exists.

Example 7.1. Using the Repository Layer

/* Create a new directory at the path NEW_DIRECTORY in the Subversion
   repository located at REPOS_PATH.  Perform all memory allocation in
   POOL.  This function will create a new revision for the addition of
   NEW_DIRECTORY.  */
static svn_error_t *
make_new_directory (const char *repos_path,
                    const char *new_directory,
                    apr_pool_t *pool)
{
  svn_error_t *err;
  svn_repos_t *repos;
  svn_fs_t *fs;
  svn_revnum_t youngest_rev;
  svn_fs_txn_t *txn;
  svn_fs_root_t *txn_root;
  const char *conflict_str;

  /* Open the repository located at REPOS_PATH.  */
  SVN_ERR (svn_repos_open (&repos, repos_path, pool));

  /* Get a pointer to the filesystem object that is stored in
     REPOS.  */
  fs = svn_repos_fs (repos);

  /* Ask the filesystem to tell us the youngest revision that
     currently exists.  */
  SVN_ERR (svn_fs_youngest_rev (&youngest_rev, fs, pool));

  /* Begin a new transaction that is based on YOUNGEST_REV.  We are
     less likely to have our later commit rejected as conflicting if we
     always try to make our changes against a copy of the latest snapshot
     of the filesystem tree.  */
  SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, pool));

  /* Now that we have started a new Subversion transaction, get a root
     object that represents that transaction.  */
  SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool));
  
  /* Create our new directory under the transaction root, at the path
     NEW_DIRECTORY.  */
  SVN_ERR (svn_fs_make_dir (txn_root, new_directory, pool));

  /* Commit the transaction, creating a new revision of the filesystem
     which includes our added directory path.  */
  err = svn_repos_fs_commit_txn (&conflict_str, repos, 
                                 &youngest_rev, txn);
  if (! err)
    {
      /* No error?  Excellent!  Print a brief report of our success.  */
      printf ("Directory '%s' was successfully added as new revision "
              "'%" SVN_REVNUM_T_FMT "'.\n", new_directory, youngest_rev);
    }
  else if (err->apr_err == SVN_ERR_FS_CONFLICT))
    {
      /* Uh-oh.  Our commit failed as the result of a conflict
         (someone else seems to have made changes to the same area 
         of the filesystem that we tried to modify).  Print an error
         message.  */
      printf ("A conflict occurred at path '%s' while attempting "
              "to add directory '%s' to the repository at '%s'.\n", 
              conflict_str, new_directory, repos_path);
    }
  else
    {
      /* Some other error has occurred.  Print an error message.  */
      printf ("An error occurred while attempting to add directory '%s' "
              "to the repository at '%s'.\n", 
              new_directory, repos_path);
    }

  /* Return the result of the attempted commit to our caller.  */
  return err;
} 

In the previous code segment, calls were made to both the repository and filesystem interfaces. We could just as easily have committed the transaction using svn_fs_commit_txn. But the filesystem API knows nothing about the repository library's hook mechanism. If you want your Subversion repository to automatically perform some set of non-Subversion tasks every time you commit a transaction (like, for example, sending an email that describes all the changes made in that transaction to your developer mailing list), you need to use the libsvn_repos-wrapped version of that function—svn_repos_fs_commit_txn. This function will actually first run the "pre-commit" hook script if one exists, then commit the transaction, and finally will run a "post-commit" hook script. The hooks provide a special kind of reporting mechanism that does not really belong in the core filesystem library itself. (For more information regarding Subversion's repository hooks, see the section called “Hook scripts”.)

The hook mechanism requirement is but one of the reasons for the abstraction of a separate repository library from the rest of the filesystem code. The libsvn_repos API provides several other important utilities to Subversion. These include the abilities to:

  1. create, open, destroy, and perform recovery steps on a Subversion repository and the filesystem included in that repository.

  2. describe the differences between two filesystem trees.

  3. query for the commit log messages associated with all (or some) of the revisions in which a set of files was modified in the filesystem.

  4. generate a human-readable "dump" of the filesystem, a complete representation of the revisions in the filesystem.

  5. parse that dump format, loading the dumped revisions into a different Subversion repository.

As Subversion continues to evolve, the repository library will grow with the filesystem library to offer increased functionality and configurable option support.

Repository Access Layer

If the Subversion Repository Layer is at "the other end of the line", the Repository Access Layer is the line itself. Charged with marshalling data between the client libraries and the repository, this layer includes the libsvn_ra module loader library, the RA modules themselves (which currently includes libsvn_ra_dav, libsvn_ra_local, and libsvn_ra_svn), and any additional libraries needed by one or more of those RA modules, such as the mod_dav_svn Apache module with which libsvn_ra_dav communicates or libsvn_ra_svn's server, svnserve.

Since Subversion uses URLs to identify its repository resources, the protocol portion of the URL schema (usually file:, http:, https:, or svn:) is used to determine which RA module will handle the communications. Each module registers a list of the protocols it knows how to "speak" so that the RA loader can, at runtime, determine which module to use for the task at hand. You can determine which RA modules are available to the Subversion command-line client, and what protocols they claim to support, by running svn --version:

$ svn --version
svn, version 0.25.0 (dev build)
   compiled Jul 18 2003, 16:25:59
 
Copyright (C) 2000-2003 CollabNet.
Subversion is open source software, see http://subversion.tigris.org/
 
The following repository access (RA) modules are available:
 
* ra_dav : Module for accessing a repository via WebDAV (DeltaV) protocol.
  - handles 'http' schema
* ra_local : Module for accessing a repository on local disk.
  - handles 'file' schema
* ra_svn : Module for accessing a repository using the svn network protocol.
  - handles 'svn' schema

RA-DAV (Repository Access Using HTTP/DAV)

The libsvn_ra_dav library is designed for use by clients that are being run on different machines than the servers with which they communicating, specifically machines reached using URLs that contain the http: or https: protocol portions. To understand how this module works, we should first mention a couple of other key components in this particular configuration of the Repository Access Layer—the powerful Apache HTTP Server, and the Neon HTTP/WebDAV client library.

Subversion's primary network server is the Apache HTTP Server. Apache is a time-tested, extensible open-source server process that is ready for serious use. It can sustain a high network load and runs on many platforms. The Apache server supports a number of different standard authentication protocols, and can be extended through the use of modules to support many others. It also supports optimizations like network pipelining and caching. By using Apache as a server, Subversion gets all of these features for free. And since most firewalls already allow HTTP traffic to pass through, sysadmins typically don't even have to change their firewall configurations to allow Subversion to work.

Subversion uses HTTP and WebDAV (with DeltaV) to communicate with an Apache server. You can read more about this in the WebDAV section of this chapter, but in short, WebDAV and DeltaV are extensions to the standard HTTP 1.1 protocol that enable sharing and versioning of files over the web. Apache 2.0 comes with mod_dav, an Apache module that understands the DAV extensions to HTTP. Subversion itself supplies mod_dav_svn, though, which is another Apache module that works in conjunction with (really, as a back-end to) mod_dav to provide Subversion's specific implementations of WebDAV and DeltaV.

When communicating with a repository over HTTP, the RA loader library chooses libsvn_ra_dav as the proper access module. The Subversion client makes calls into the generic RA interface, and libsvn_ra_dav maps those calls (which embody rather large-scale Subversion actions) to a set of HTTP/WebDAV requests. Using the Neon library, libsvn_ra_dav transmits those requests to the Apache server. Apache receives these requests (exactly as it does generic HTTP requests that your web browser might make), notices that the requests are directed at a URL that is configured as a DAV location (using the Location directive in httpd.conf), and hands the request off to its own mod_dav module. When properly configured, mod_dav knows to use Subversion's mod_dav_svn for any filesystem-related needs, as opposed to the generic mod_dav_fs that comes with Apache. So ultimately, the client is communicating with mod_dav_svn, which binds directly to the Subversion Repository Layer.

That was a simplified description of the actual exchanges taking place, though. For example, the Subversion repository might be protected by Apache's authorization directives. This could result in initial attempts to communicate with the repository being rejected by Apache on authorization grounds. At this point, libsvn_ra_dav gets back the notice from Apache that insufficient identification was supplied, and calls back into the Client Layer to get some updated authentication data. If the data is supplied correctly, and the user has the permissions that Apache seeks, libsvn_ra_dav's next automatic attempt at performing the original operation will be granted, and all will be well. If sufficient authentication information cannot be supplied, the request will ultimately fail, and the client will report the failure to the user.

By using Neon and Apache, Subversion gets free functionality in several other complex areas, too. For example, if Neon finds the OpenSSL libraries, it allows the Subversion client to attempt to use SSL-encrypted communications with the Apache server (whose own mod_ssl can "speak the language"). Also, both Neon itself and Apache's mod_deflate can understand the "deflate" algorithm (the same used by the PKZIP and gzip programs), so requests can be sent in smaller, compressed chunks across the wire. Other complex features that Subversion hopes to support in the future include the ability to automatically handle server-specified redirects (for example, when a repository has been moved to a new canonical URL) and taking advantage of HTTP pipelining.

RA-SVN (Proprietary Protocol Repository Access)

In addition to the standard HTTP/WebDAV protocol, Subversion also provides an RA implementation that uses a proprietary protocol. The libsvn_ra_svn module implements its own network socket connectivity, and communicates with a stand-alone server—the svnserve program—on the machine that hosts the repository. Clients access the repository using the svn:// schema.

This RA implementation lacks most of the advantages of Apache mentioned in the previous section; however, it may be appealing to some sysadmins nonetheless. It is dramatically easier to configure and run; setting up an svnserve process is nearly instantaneous. It is also much smaller (in terms of lines of code) than Apache, making it much easier to audit, for security reasons or otherwise. Furthermore, some sysadmins may already have an SSH security infrastructure in place, and want Subversion to use it. Clients using ra_svn can easily tunnel the protocol over SSH.

RA-Local (Direct Repository Access)

Not all communications with a Subversion repository require a powerhouse server process and a network layer. For users who simply wish to access the repositories on their local disk, they may do so using file: URLs and the functionality provided by libsvn_ra_local. This RA module binds directly with the repository and filesystem libraries, so no network communication is required at all.

Subversion requires the server name included as part of the file: URL be either localhost or empty, and that there be no port specification. In other words, your URLs should look like either file://localhost/path/to/repos or file:///path/to/repos.

Also, be aware that Subversion's file: URLs cannot be used in a regular web browser the way typical file: URLs can. When you attempt to view a file: URL in a regular web browser, it reads and displays the contents of the file at that location by examining the filesystem directly. However, Subversion's resources exist in a virtual filesystem (see the section called “Repository Layer”), and your browser will not understand how to read that filesystem.

Your RA Library Here

For those who wish to access a Subversion repository using still another protocol, that is precisely why the Repository Access Layer is modularized! Developers can simply write a new library that implements the RA interface on one side and communicates with the repository on the other. Your new library can use existing network protocols, or you can invent your own. You could use inter-process communication (IPC) calls, or—let's get crazy, shall we?—you could even implement an email-based protocol. Subversion supplies the APIs; you supply the creativity.

Client Layer

On the client side, the Subversion working copy is where all the action takes place. The bulk of functionality implemented by the client-side libraries exists for the sole purpose of managing working copies—directories full of files and other subdirectories which serve as a sort of local, editable "reflection" of one or more repository locations—and propagating changes to and from the Repository Access layer.

Subversion's working copy library, libsvn_wc, is directly responsible for managing the data in the working copies. To accomplish this, the library stores administrative information about each working copy directory within a special subdirectory. This subdirectory, named .svn is present in each working copy directory and contains various other files and directories which record state and provide a private workspace for administrative action. For those familiar with CVS, this .svn subdirectory is similar in purpose to the CVS administrative directories found in CVS working copies. For more information about the .svn administrative area, see the section called “Inside the Working Copy Administration Area”in this chapter.

The Subversion client library, libsvn_client, has the broadest responsibility; its job is to mingle the functionality of the working copy library with that of the Repository Access Layer, and then to provide the highest-level API to any application that wishes to perform general revision control actions. For example, the function svn_client_checkout takes a URL as an argument. It passes this URL to the RA layer and opens an authenticated session with a particular repository. It then asks the repository for a certain tree, and sends this tree into the working copy library, which then writes a full working copy to disk (.svn directories and all).

The client library is designed to be used by any application. While the Subversion source code includes a standard command-line client, it should be very easy to write any number of GUI clients on top of the client library. New GUIs (or any new client, really) for Subversion need not be clunky wrappers around the included command-line client—they have full access via the libsvn_client API to same functionality, data, and callback mechanisms that the command-line client uses.

Using the APIs

Developing applications against the Subversion library APIs is fairly straightforward. All of the public header files live in the subversion/include directory of the source tree. These headers are copied into your system locations when you build and install Subversion itself from source. These headers represent the entirety of the functions and types meant to be accessible by users of the Subversion libraries.

The first thing you might notice is that Subversion's datatypes and functions are namespace protected. Every public Subversion symbol name begins with "svn_", followed by a short code for the library in which the symbol is defined (such as "wc", "client", "fs", etc.), followed by a single underscore ("_") and then the rest of the symbol name. Semi-public functions (used among source files of a given library but not by code outside that library, and found inside the library directories themselves) differ from this naming scheme in that instead of a single underscore after the library code, they use a double underscore ("__"). Functions that are private to a given source file have no special prefixing, and are declared static. Of course, a compiler isn't interested in these naming conventions, but they definitely help to clarify the scope of a given function or datatype.

The Apache Portable Runtime Library

Along with Subversion's own datatype, you will see many references to datatypes that begin with "apr_"—symbols from the Apache Portable Runtime (APR) library. APR is Apache's portability library, originally carved out of its server code as an attempt to separate the OS-specific bits from the OS-independent portions of the code. The result was a library that provides a generic API for performing operations that differ mildly—or wildly—from OS to OS. While Apache HTTP Server was obviously the first user of the APR library, the Subversion developers immediately recognized the value of using APR as well. This means that there are practically no OS-specific code portions in Subversion itself. Also, it means that the Subversion client compiles and runs anywhere that the server does. Currently this list includes all flavors of Unix, Win32, BeOS, OS/2, and Mac OS X.

In addition to providing consistent implementations of system calls that differ across operating systems, [30] APR gives Subversion immediate access to many custom datatypes, such as dynamic arrays and hash tables. Subversion uses these types extensively throughout the codebase. But perhaps the most pervasive APR datatype, found in nearly every Subversion API prototype, is the apr_pool_t—the APR memory pool. Subversion uses pools internally for all its memory allocation needs (unless an external library requires a different memory management schema for data passed through its API), [31] and while a person coding against the Subversion APIs is not required to do the same, they are required to provide pools to the API functions that need them. This means that users of the Subversion API must also link against APR, must call apr_initialize() to initialize the APR subsystem, and then must acquire a pool for use with Subversion API calls. See the section called “Programming with Memory Pools” for more information.

URL and Path Requirements

With remote version control operation as the whole point of Subversion's existence, it makes sense that some attention has been paid to internationalization (i18n) support. After all, while "remote" might mean "across the office", it could just as well mean "across the globe." To facilitate this, all of Subversion's public interfaces that accept path arguments expect those paths to be canonicalized, and encoded in UTF-8. This means, for example, that any new client binary that drives the libsvn_client interface needs to first convert paths from the locale-specific encoding to UTF-8 before passing those paths to the Subversion libraries, and then re-convert any resultant output paths from Subversion back into the locale's encoding before using those paths for non-Subversion purposes. Fortunately, Subversion provides a suite of functions (see subversion/include/svn_utf.h) that can be used by any program to do these conversions.

Also, Subversion APIs require all URL parameters to be properly URI-encoded. So, instead of passing file:///home/username/My File.txt as the URL of a file named My File.txt, you need to pass file:///home/username/My%20File.txt. Again, Subversion supplies helper functions that your application can use—svn_path_uri_encode and svn_path_uri_decode, for URI encoding and decoding, respectively.

Using Languages Other than C and C++

If you are interested in using the Subversion libraries in conjunction with something other than a C program—say a Python script or Java application—Subversion has some initial support for this via the Simplified Wrapper and Interface Generator (SWIG). The SWIG bindings for Subversion are located in subversion/bindings/swig and are slowly maturing into a usable state. These bindings allow you to call Subversion API functions indirectly, using wrappers that translate the datatypes native to your scripting language into the datatypes needed by Subversion's C libraries.

There is an obvious benefit to accessing the Subversion APIs via a language binding—simplicity. Generally speaking, languages such as Python and Perl are much more flexible and easy to use than C or C++. The sort of high-level datatypes and context-driven type checking provided by these languages are often better at handling information that comes from users. As you know, only a human can botch up the input to a program as well as they do, and the scripting-type language simply handle that misinformation more gracefully. Of course, often that flexibility comes at the cost of performance. That is why using a tightly-optimized, C-based interface and library suite, combined with a powerful, flexible binding language is so appealing.

Let's look at an example that uses Subversion's Python SWIG bindings. Our example will do the same thing as our last example. Note the difference in size and complexity of the function this time!

Example 7.2. Using the Repository Layer with Python

from svn import fs
import os.path

def crawl_filesystem_dir (root, directory, pool):
  """Recursively crawl DIRECTORY under ROOT in the filesystem, and return
  a list of all the paths at or below DIRECTORY.  Use POOL for all 
  allocations."""

  # Get the directory entries for DIRECTORY.
  entries = fs.dir_entries(root, directory, pool)

  # Initialize our returned list with the directory path itself.
  paths = [directory]

  # Loop over the entries
  names = entries.keys()
  for name in names:
    # Calculate the entry's full path.
    full_path = os.path.join(basepath, name)

    # If the entry is a directory, recurse.  The recursion will return
    # a list with the entry and all its children, which we will add to
    # our running list of paths.
    if fs.is_dir(fsroot, full_path, pool):
      subpaths = crawl_filesystem_dir(root, full_path, pool)
      paths.extend(subpaths)

    # Else, it is a file, so add the entry's full path to the FILES list.
    else:
      paths.append(full_path)

  return paths

An implementation in C of the previous example would stretch on quite a bit longer. The same routine in C would need to pay close attention to memory usage, and need to use custom datatypes for representing the hash of entries and the list of paths. Python has hashes and lists (called "dictionaries" and "sequences", respectively) as built-in datatypes, and provides a wonderful selection of methods for operating on those types. And since Python uses reference counting and garbage collection, users of the language don't have to bother themselves with allocating and freeing memory.

In the previous section of this chapter, we mentioned the libsvn_client interface, and how it exists for the sole purpose of simplifying the process of writing a Subversion client. The following is a brief example of how that library can be accessed via the SWIG bindings. In just a few lines of Python, you can check out a fully functional Subversion working copy!

Example 7.3. A Simple Script to Check Out a Working Copy.

#!/usr/bin/env python
import sys
from svn import util, _util, _client

def usage():
  print "Usage: " + sys.argv[0] + " URL PATH\n"
  sys.exit(0)

def run(url, path):
  # Initialize APR and get a POOL.
  _util.apr_initialize()
  pool = util.svn_pool_create(None)

  # Checkout the HEAD of URL into PATH (silently)
  _client.svn_client_checkout(None, None, url, path, -1, 1, None, pool)

  # Cleanup our POOL, and shut down APR.
  util.svn_pool_destroy(pool)
  _util.apr_terminate()

if __name__ == '__main__':
  if len(sys.argv) != 3:
    usage()
  run(sys.argv[1], sys.argv[2])

Currently, it is Subversion's Python bindings that are the most complete. Some attention is also being given to the Java bindings. Once you have the SWIG interface files properly configured, generation of the specific wrappers for all the supported SWIG languages (which currently includes versions of C#, Guile, Java, Mzscheme, OCaml, Perl, PHP, Python, Ruby, and Tcl) should theoretically be trivial. Still, some extra programming is required to compensate for complex APIs that SWIG needs some help generalizing. For more information on SWIG itself, see the project's website at http://www.swig.org.

Inside the Working Copy Administration Area

As we mentioned earlier, each directory of a Subversion working copy contains a special subdirectory called .svn which houses administrative data about that working copy directory. Subversion uses the information in .svn to keep track of things like:

  • Which repository location(s) are represented by the files and subdirectories in the working copy directory.

  • What revision of each of those files and directories are currently present in the working copy.

  • Any user-defined properties that might be attached to those files and directories.

  • Pristine (un-edited) copies of the working copy files.

While there are several other bits of data stored in the .svn directory, we will examine only a couple of the most important items.

The Entries File

Perhaps the single most important file in the .svn directory is the entries file. The entries file is an XML document which contains the bulk of the administrative information about a versioned resource in a working copy directory. It is this one file which tracks the repository URLs, pristine revision, file checksums, pristine text and property timestamps, scheduling and conflict state information, last-known commit information (author, revision, timestamp), local copy history—practically everything that a Subversion client is interested in knowing about a versioned (or to-be-versioned) resource!

The following is an example of an actual entries file:

Example 7.4. Contents of a Typical .svn/entries File

<?xml version="1.0" encoding="utf-8"?>
<wc-entries
   xmlns="svn:">
<entry
   committed-rev="1"
   name="svn:this_dir"
   committed-date="2002-09-24T17:12:44.064475Z"
   url="http://svn.red-bean.com/tests/.greek-repo/A/D"
   kind="dir"
   revision="1"/>
<entry
   committed-rev="1"
   name="gamma"
   text-time="2002-09-26T21:09:02.000000Z"
   committed-date="2002-09-24T17:12:44.064475Z"
   checksum="QSE4vWd9ZM0cMvr7/+YkXQ=="
   kind="file"
   prop-time="2002-09-26T21:09:02.000000Z"/>
<entry
   name="zeta"
   kind="file"
   schedule="add"
   revision="0"/>
<entry
   url="http://svn.red-bean.com/tests/.greek-repo/A/B/delta"
   name="delta"
   kind="file"
   schedule="add"
   revision="0"/>
<entry
   name="G"
   kind="dir"/>
<entry
   name="H"
   kind="dir"
   schedule="delete"/>
</wc-entries>

As you can see, the entries file is essentially a list of entries. Each entry tag represents one of three things: the working copy directory itself (noted by having its name attribute set to "svn:this-dir"), a file in that working copy directory (noted by having its kind attribute set to "file"), or a subdirectory in that working copy (kind here is set to "dir"). The files and subdirectories whose entries are stored in this file are either already under version control, or (as in the case of the file named zeta above) are scheduled to be added to version control when the user next commits this working copy directory's changes. Each entry has a unique name, and each entry has a node kind.

Developers should be aware of some special rules that Subversion uses when reading and writing its entries files. While each entry has a revision and URL associated with it, note that not every entry tag in the sample file has explicit revision or url attributes attached to it. Subversion allows entries to not explicitly store those two attributes when their values are the same as (in the revision case) or trivially calculable from [32] (in the url case) the data stored in the "svn:this-dir" entry. Note also that for subdirectory entries, Subversion stores only the crucial attributes—name, kind, url, revision, and schedule. In an effort to reduce duplicated information, Subversion dictates that the method for determining the full set of information about a subdirectory is to traverse down into that subdirectory, and read the "svn:this-dir" entry from its own .svn/entries file. However, a reference to the subdirectory is kept in its parent's entries file, with enough information to permit basic versioning operations in the event that the subdirectory itself is actually missing from disk.

Pristine Copies and Property Files

As mentioned before, the .svn directory also holds the pristine "text-base" versions of files. Those can be found in .svn/text-base. The benefits of these pristine copies are multiple—network-free checks for local modifications and "diff" reporting, network-free reversion of modified or missing files, smaller transmission of changes to the server—but comes at the cost of having each versioned file stored at least twice on disk. These days, this seems to be a negligible penalty for most files. However, the situation gets uglier as the size of your versioned files grows. Some attention is being given to making the presence of the "text-base" an option. Ironically though, it is as your versioned files' sizes get larger that the existence of the "text-base" becomes more crucial—who wants to transmit a huge file across a network just because they want to commit a tiny change to it?

Similar in purpose to the "text-base" files are the property files and their pristine "prop-base" copies, located in .svn/props and .svn/prop-base respectively. Since directories can have properties, too, there are also .svn/dir-props and .svn/dir-prop-base files. Each of these property files ("working" and "base" versions) uses a simple "hash-on-disk" file format for storing the property names and values.

WebDAV

WebDAV ("Web-based Distributed Authoring and Versioning") is an extension of the standard HTTP protocol designed to make the web into a read/write medium, instead of the basically read-only medium that exists today. The theory is that directories and files can be shared—as both readable and writable objects—over the web. RFCs 2518 and 3253 describe the WebDAV/DeltaV extensions to HTTP, and are available (along with a lot of other useful information) at http://www.webdav.org/.

A number of operating system file browsers are already able to mount networked directories using WebDAV. On Win32, the Windows Explorer can browse what it calls "WebFolders" (which are just WebDAV-ready network locations) as if they were regular shared folders. Mac OS X also has this capability, as do the Nautilus and Konqueror browsers (under GNOME and KDE, respectively).

How does all of this apply to Subversion? The mod_dav_svn Apache module uses HTTP, extended by WebDAV and DeltaV, as its primary network protocol. Rather than implementing a new proprietary protocol, the original Subversion designers decided to simply map the versioning concepts and actions used by Subversion onto the concepts exposed by RFCs 2518 and 3253. [33]

For a more thorough discussion of WebDAV, how it works, and how Subversion uses it, see Appendix D, WebDAV 與自動版本. Among other things, that appendix discusses the degree to which Subversion adheres to the generic WebDAV specification, and how that affects interoperability with generic WebDAV clients.

Programming with Memory Pools

Almost every developer who has used the C programming language has at some point sighed at the daunting task of managing memory usage. Allocating enough memory to use, keeping track of those allocations, freeing the memory when you no longer need it—these tasks can be quite complex. And of course, failure to do those things properly can result in a program that crashes itself, or worse, crashes the computer. Fortunately, the APR library that Subversion depends on for portability provides the apr_pool_t type, which represents a "pool" of memory.

A memory pool is an abstract representation of a chunk of memory allocated for use by a program. Rather than requesting memory directly from the OS using the standard malloc() and friends, programs that link against APR can simply request that a pool of memory be created (using the apr_pool_create() function). APR will allocate a moderately sized chunk of memory from the OS, and that memory will be instantly available for use by the program. Any time the program needs some of the pool memory, it uses one of the APR pool API functions, like apr_palloc(), which returns a generic memory location from the pool. The program can keep requesting bits and pieces of memory from the pool, and APR will keep granting the requests. Pools will automatically grow in size to accommodate programs that request more memory than the original pool contained, until of course there is no more memory available on the system.

Now, if this were the end of the pool story, it would hardly have merited special attention. Fortunately, that's not the case. Pools can not only be created; they can also be cleared and destroyed, using apr_pool_clear() and apr_pool_destroy() respectively. This gives developers the flexibility to allocate several—or several thousand— things from the pool, and then clean up all of that memory with a single function call! Further, pools have hierarchy. You can make "subpools" of any previously created pool. When you clear a pool, all of its subpools are destroyed; if you destroy a pool, it and its subpools are destroyed.

Before we go further, developers should be aware that they probably will not find many calls to the APR pool functions we just mentioned in the Subversion source code. APR pools offer some extensibility mechanisms, like the ability to have custom "user data" attached to the pool, and mechanisms for registering cleanup functions that get called when the pool is destroyed. Subversion makes use of these extensions in a somewhat non-trivial way. So, Subversion supplies (and most of its code uses) the wrapper functions svn_pool_create(), svn_pool_clear(), and svn_pool_destroy().

While pools are helpful for basic memory management, the pool construct really shines in looping and recursive scenarios. Since loops are often unbounded in their iterations, and recursions in their depth, memory consumption in these areas of the code can become unpredictable. Fortunately, using nested memory pools can be a great way to easily manage these potentially hairy situations. The following example demonstrates the basic use of nested pools in a situation that is fairly common—recursively crawling a directory tree, doing some task to each thing in the tree.

Example 7.5. Effective Pool Usage

/* Recursively crawl over DIRECTORY, adding the paths of all its file
   children to the FILES array, and doing some task to each path
   encountered.  Use POOL for the all temporary allocations, and store
   the hash paths in the same pool as the hash itself is allocated in.  */
static apr_status_t 
crawl_dir (apr_array_header_t *files,
           const char *directory,
           apr_pool_t *pool)
{
  apr_pool_t *hash_pool = files->pool;  /* array pool */
  apr_pool_t *subpool = svn_pool_create (pool);  /* iteration pool */
  apr_dir_t *dir;
  apr_finfo_t finfo;
  apr_status_t apr_err;
  apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;

  apr_err = apr_dir_open (&dir, directory, pool);
  if (apr_err)
    return apr_err;

  /* Loop over the directory entries, clearing the subpool at the top of
     each iteration.  */
  for (apr_err = apr_dir_read (&finfo, flags, dir);
       apr_err == APR_SUCCESS;
       apr_err = apr_dir_read (&finfo, flags, dir))
    {
      const char *child_path;

      /* Skip entries for "this dir" ('.') and its parent ('..').  */
      if (finfo.filetype == APR_DIR)
        {
          if (finfo.name[0] == '.'
              && (finfo.name[1] == '\0'
                  || (finfo.name[1] == '.' && finfo.name[2] == '\0')))
            continue;
        }

      /* Build CHILD_PATH from DIRECTORY and FINFO.name.  */
      child_path = svn_path_join (directory, finfo.name, subpool);

      /* Do some task to this encountered path. */
      do_some_task (child_path, subpool);

      /* Handle subdirectories by recursing into them, passing SUBPOOL
         as the pool for temporary allocations.  */
      if (finfo.filetype == APR_DIR)
        {
          apr_err = crawl_dir (files, child_path, subpool);
          if (apr_err)
            return apr_err;
        }

      /* Handle files by adding their paths to the FILES array.  */
      else if (finfo.filetype == APR_REG)
        {
          /* Copy the file's path into the FILES array's pool.  */
          child_path = apr_pstrdup (hash_pool, child_path);

          /* Add the path to the array.  */
          (*((const char **) apr_array_push (files))) = child_path;
        }

      /* Clear the per-iteration SUBPOOL.  */
      svn_pool_clear (subpool);
    }

  /* Check that the loop exited cleanly. */
  if (apr_err)
    return apr_err;

  /* Yes, it exited cleanly, so close the dir. */
  apr_err = apr_dir_close (dir);
  if (apr_err)
    return apr_err;

  /* Destroy SUBPOOL.  */
  svn_pool_destroy (subpool);

  return APR_SUCCESS;
}

The previous example demonstrates effective pool usage in both looping and recursive situations. Each recursion begins by making a subpool of the pool passed to the function. This subpool is used for the looping region, and cleared with each iteration. The result is memory usage is roughly proportional to the depth of the recursion, not to total number of file and directories present as children of the top-level directory. When the first call to this recursive function finally finishes, there is actually very little data stored in the pool that was passed to it. Now imagine the extra complexity that would be present if this function had to alloc() and free() every single piece of data used!

Pools might not be ideal for every application, but they are extremely useful in Subversion. As a Subversion developer, you'll need to grow comfortable with pools and how to wield them correctly. Memory usage bugs and bloating can be difficult to diagnose and fix regardless of the API, but the pool construct provided by APR has proven a tremendously convenient, time-saving bit of functionality.

Contributing to Subversion

The official source of information about the Subversion project is, of course, the project's website at http://subversion.tigris.org. There you can find information about getting access to the source code and participating on the discussion lists. The Subversion community always welcomes new members. If you are interested in participating in this community by contributing changes to the source code, here are some hints on how to get started.

Join the Community

The first step in community participation is to find a way to stay on top of the latest happenings. To do this most effectively, you will want to subscribe to the main developer discussion list () and commit mail list (). By following these lists even loosely, you will have access to important design discussions, be able to see actual changes to Subversion source code as they occur, and be able to witness peer reviews of those changes and proposed changes. These email based discussion lists are the primary communication media for Subversion development. See the Mailing Lists section of the website for other Subversion-related lists you might be interested in.

But how do you know what needs to be done? It is quite common for a programmer to have the greatest intentions of helping out with the development, yet be unable to find a good starting point. After all, not many folks come to the community having already decided on a particular itch they would like to scratch. But by watching the developer discussion lists, you might see mentions of existing bugs or feature requests fly by that particularly interest you. Also, a great place to look for outstanding, unclaimed tasks is the Issue Tracking database on the Subversion website. There you will find the current list of known bugs and feature requests. If you want to start with something small, look for issues marked as "bite-sized".

Get the Source Code

To edit the code, you need to have the code. This means you need to check out a working copy from the public Subversion source repository. As straightforward as that might sound, the task can be slightly tricky. Because Subversion's source code is versioned using Subversion itself, you actually need to "bootstrap" by getting a working Subversion client via some other method. The most common methods include downloading the latest binary distribution (if such is available for your platform), or downloading the latest source tarball and building your own Subversion client. If you build from source, make sure to read the INSTALL file in the top level of the source tree for instructions.

After you have a working Subversion client, you are now poised to checkout a working copy of the Subversion source repository from http://svn.collab.net/repos/svn/trunk: [34]

$ svn checkout http://svn.collab.net/repos/svn/trunk subversion
A  HACKING
A  INSTALL
A  README
A  autogen.sh
A  build.conf
...

The above command will checkout the bleeding-edge, latest version of the Subversion source code into a subdirectory named subversion in your current working directory. Obviously, you can adjust that last argument as you see fit. Regardless of what you call the new working copy directory, though, after this operation completes, you will now have the Subversion source code. Of course, you will still need to fetch a few helper libraries (apr, apr-util, etc.)—see the INSTALL file in the top level of the working copy for details.

Become Familiar with Community Policies

Now that you have a working copy containing the latest Subversion source code, you will most certainly want to take a cruise through the HACKING file in that working copy's top-level directory. The HACKING file contains general instructions for contributing to Subversion, including how to properly format your source code for consistency with the rest of the codebase, how to describe your proposed changes with an effective change log message, how to test your changes, and so on. Commit privileges on the Subversion source repository are earned—a government by meritocracy. [35] The HACKING file is an invaluable resource when it comes to making sure that your proposed changes earn the praises they deserve without being rejected on technicalities.

Make and Test Your Changes

With the code and community policy understanding in hand, you are ready to make your changes. It is best to try to make smaller but related sets of changes, even tackling larger tasks in stages, instead of making huge, sweeping modifications. Your proposed changes will be easier to understand (and therefore easier to review) if you disturb the fewest lines of code possible to accomplish your task properly. After making each set of proposed changes, your Subversion tree should be in a state in which the software compiles with no warnings.

Subversion has a fairly thorough [36] regression test suite, and your proposed changes are expected to not cause any of those tests to fail. By running make check (in Unix) from the top of the source tree, you can sanity-check your changes. The fastest way to get your code contributions rejected (other than failing to supply a good log message) is to submit changes that cause failure in the test suite.

In the best-case scenario, you will have actually added appropriate tests to that test suite which verify that your proposed changes actually work as expected. In fact, sometimes the best contribution a person can make is solely the addition of new tests. You can write regression tests for functionality that currently works in Subversion as a way to protect against future changes that might trigger failure in those areas. Also, you can write new tests that demonstrate known failures. For this purpose, the Subversion test suite allows you to specify that a given test is expected to fail (called an XFAIL), and so long as Subversion fails in the way that was expected, a test result of XFAIL itself is considered a success. Ultimately, the better the test suite, the less time wasted on diagnosing potentially obscure regression bugs.

Donate Your Changes

After making your modifications to the source code, compose a clear and concise log message to describe those changes and the reasons for them. Then, send an email to the developers list containing your log message and the output of svn diff (from the top of your Subversion working copy). If the community members consider your changes acceptable, someone who has commit privileges (permission to make new revisions in the Subversion source repository) will add your changes to the public source code tree. Recall that permission to directly commit changes to the repository is granted on merit—if you demonstrate comprehension of Subversion, programming competency, and a "team spirit", you will likely be awarded that permission.



[28] The choice of Berkeley DB brought several automatic features that Subversion needed, such as data integrity, atomic writes, recoverability, and hot backups.

[29] We understand that this may come as a shock to sci-fi fans who have long been under the impression that Time was actually the fourth dimension, and we apologize for any emotional trauma induced by our assertion of a different theory.

[30] Subversion uses ANSI system calls and datatypes as much as possible.

[31] Neon and Berkeley DB are examples of such libraries.

[32] That is, the URL for the entry is the same as the concatenation of the parent directory's URL and the entry's name.

[33] As it turns out, Subversion has more recently evolved a proprietary protocol anyway, implemented by the libsvn_ra_svn module.

[34] Note that the URL checked out in the example above ends not with svn, but with a subdirectory thereof called trunk. See our discussion of Subversion's branching and tagging model for the reasoning behind this.

[35] While this may superficially appear as some sort of elitism, this "earn your commit privileges" notion is about efficiency—whether it costs more in time and effort to review and apply someone else's changes that are likely to be safe and useful, versus the potential costs of undoing changes that are dangerous.

[36] You might want to grab some popcorn. "Thorough", in this instance, translates to somewhere around thirty minutes of non-interactive machine churn.

Chapter 8. 完整 Subversion 參考手冊

本章是用來作為 Subversion 的完整參考手冊, 裡面包含了命令列用戶端 (svn) 與其所有的子命令, 以及檔案庫管理程式 (svnadminsvnlook) 與其子命令.

Subversion 命令列用戶端: svn

要使用命令列用戶端, 請輸入 svn, 欲使用的子命令, [37] 然後再加上任何你想使用的選項或目標 — 子命令與選項並沒有特定的次序. 舉個例子, 以下都是 svn status 的有效用法:

$ svn -v status
$ svn status -v 
$ svn status -v myfile
    

Chapter 3, 導覽 裡, 可以找到大部份用戶端命令的範例, 在 the section called “性質” 裡, 可以找到管理性質的命令範例.

svn 選項

雖然 Subversion 的子命令有許多不同的選項, 但是所有的選項都是全面通用的 — 也就是不管使用的子命令為何, 每個選項都保證表示同樣的東西. 像是 --verbose (-v) 一定是 “詳細輸出”, 不管你與哪個子命令使用.

--diff-cmd CMD

指定用以顯示檔案差異的外部程式. 當 svn diff 執行時, 它會使用 Subversion's 內部的差異引擎, 預設為輸出統一差異格式. 如果你想使用外部的差異程式, 請使用 --diff-cmd. 你可以透過 --extensions 選項 (本節稍後會提到) 來傳遞差異程式的選項.

--diff3-cmd CMD

指定用以合併檔案的外部程式.

--dry-run

進行所有命令該作的動作, 但是實際上不進行更動 — 不管是對磁碟, 還是檔案庫的內容.

--editor-cmd CMD

指定用以編輯送交訊息或性質內容的外部程式.

--encoding ENC

讓 Subversion 知道你的送交訊息是以指定字集編碼的. 預設是你的作業系統的原生語系環境設定, 如果你的送交訊息使用不同的編碼, 你應該要指定編碼方式.

--extensions (-x) "ARGS"

指定 Subversion 在產生檔案之間差異時, 要傳給外部差異程式的一個或多個引數. 如果你想要傳遞多個引數的話, 你必須以引號將它們全都包起來 (舉個例子, svn diff --diff-cmd /usr/bin/diff -x "-b -E"). 本選項 只有--diff-cmd 選項也一併使用時, 才會發生效用.

--file (-F) FILENAME

對某個子命令, 使用本選項引數指定的檔案的內容.

--force

強迫執行某個特定的命令或是動作. 在正常的使用下, 有些動作會被 Subversion 阻止, 但是使用這個選項後, 就等於告訴 Subversion “我知道我在作什麼, 可能會發生的後果我也知道, 所以讓我作吧”. 這個選項就像你在總開關開著的時候, 修理電氣線路 — 如果你不知道你在作什麼, 你很有可能被電得很慘.

--force-log

強制將送給 --message (-m) 或 --file (-F) 選項的有問題參數視為有效的. 如果這些參數的選項看起來像是給子命令用的, Subversion 預設會發出錯誤訊息. 舉個例子, 如果你將一個納入版本控制的檔案路徑傳給 --file (-F) 選項, Subversion 會假設你搞錯了, 這個路徑應該是該項作業的目標, 而你就是忘了指定其它 —未納入版本控制— 檔案作為記錄訊息的來源. 要堅持你的立場, 渺視這類的錯誤檢查, 請傳遞 --force-log 選項給接受記錄訊息的命令.

--help (-h-?)

如果與一個或以上的子命令一起使用, 顯示每個子命令的內建求助訊息. 如果單獨使用, 它會顯示通用的用戶端求助訊息.

--notice-ancestry

在計算差異時, 將其演進歷程考慮進去.

--incremental

以適合附加的格式來輸出訊息.

--message (-m) MESSAGE

表示你在這個選項後, 在命令列指定一個送交訊息. 例如:

$ svn commit -m "They don't make Sunday."
            
--new ARG

將 ARG 視為較新的目標.

--no-auth-cache

防止在 Subversion 的管理目錄中暫存認證資訊 (例如使用者名稱與密碼).

--no-diff-deleted

防止 Subversion 顯示已刪除檔案的差異. 在刪除檔案後, svn diff 的預設行為會將差異顯示出來, 但是假裝檔案還在, 而檔案內容已悉數刪除.

--no-ignore

在列出檔案狀態時, 顯示一般因符合 svn:ignore 性質的樣式而被忽略的檔案. 請參考 the section called “Config” 以了解詳細資訊.

--non-interactive

如果認證失敗, 或是沒有足夠的憑證, 也不會顯示輸入憑證的提示字 (也就是使用者名稱與密碼). 如果你是在自動執行的腳本檔中使用 Subversion 就很有用, 讓 Subversion 直接失敗, 要比要求輸入的提示要更恰當.

--non-recursive (-N)

阻止子命令遞迴至子目錄裡去. 大多數的子命令預設會使用遞迴, 但是有些子命令 — 通常是可能會移除, 或是反悔本地修改的 — 不會.

--old ARG

將 ARG 視為較舊的目標.

--password PASS

表示你在命令列提供了認證用的密碼 — 不然的話, Subversion 會依需要提示你輸入密碼.

--quiet (-q)

要求用戶端在進行作業時, 只顯示必要的資訊.

--recursive (-R)

讓子命令會遞迴地處理子目錄. 大多數的子命令預設行為皆如此.

--relocate FROM TO [PATH...]

svn switch 子命令一同使用, 用來變更工作複本所參考的檔案庫位置. 當你的檔案庫位置變更, 而你已經有一個正在使用的工作複本時, 這個選項就很有用. 請參考 svn switch 的範例.

--revision (-r) REV

表示你對某一個作業指定一個 (或一個範圍的) 修訂版. 你可以指定修訂版號, 修訂版關鍵字, 或是日期 (要包在大括號裡) 作為本選項的引數. 如果你想要指定一個範圍的修訂版, 請在中間以冒號隔開. 例如:

$ svn log -r 1729
$ svn log -r 1729:HEAD
$ svn log -r 1729:1744
$ svn log -r {12/04/01}:{2/17/02}
$ svn log -r 1729:{2/17/02}
            

參見 the section called “修訂版關鍵字” 以取得更多的資訊.

--revprop

針對一個目錄或檔案修訂版性質, 而不是 Subversion 性質. 使用本選項時, 必須一併以 --revision (-r) 選項指定修訂版. 請參考 the section called “無版本控制的性質”, 以了解更多未納入版本控制性質的細節.

--show-updates (-u)

讓用戶端顯示哪些工作目錄的檔案已過時. 這不會真的更新你的檔案 — 它只會顯示如果真的執行 svn update 時, 哪些檔案會被更新.

--stop-on-copy

如果 Subversion 的子命令會收集納入版本控制的歷史紀錄, 那麼這個選項會讓它在收集歷史紀錄的過程中, 停在複製 — 也就是從檔案庫的一處複製至另一處的地方 — 的位置.

--strict

讓 Subversion 使用嚴格的語意.

--targets FILENAME

讓 Subversion 自指定的檔案取得欲處理的檔案列表, 而不是在命令列中列出所有的檔案.

--usernameNAME

表示你在命令列提供了認證用的使用者名稱 — 不然的話, Subversion 會依需要提示你輸入使用者名稱.

--verbose

要求用戶端在執行任何子命令時, 儘量提供詳細的資訊. 這可能讓 Subversion 列出額外的欄位, 每個檔案的詳細資訊, 或是更詳細的行為資訊.

--version

顯示用戶端版本資訊. 不只用戶端的版本號碼, 還包括了所有可用來存取 Subversion 檔案庫的檔案庫存取模組.

--xml

以 XML 格式輸出.

svn 子命令

Name

svn add — 新增目錄或檔案

摘要

svn add PATH...

描述

將目錄與檔案新增至工作複本中, 並且排定預備加入檔案庫. 它們會在你下一次送交時, 上載並新增到檔案庫中. 如果你新增了某個東西, 在送交前又反悔了, 你可以使用 svn revert 來取消新增的排程.

替代名稱

更動

工作複本

選項

--targets FILENAME
--non-recursive (-N)
--quiet (-q)
          

範例

新增一個檔案至工作複本中:

  $ svn add foo.c 
  A         foo.c
          

新增一個目錄時, 預設的 svn add 行為會遞迴地進行:

$ svn add testdir
A         testdir
A         testdir/a
A         testdir/b
A         testdir/c
A         testdir/d
          

你也可以新增一個目錄, 而不包含其內容:

$ svn add --non-recursive otherdir
A         otherdir
          

Name

svn cat — 輸出指定檔案或 URL 的內容.

摘要

svn cat TARGET...

描述

輸出指定的檔案或 URL 的內容. 如果要列出目錄的內容, 請參見 svn list.

替代名稱

更動

存取檔案庫

選項

--revision (-r) REV
--username USER
--password PASS
          

範例

如果你想要看檔案庫裡的 readme.txt 的內容, 而又不想要取出它:

$ svn cat http://svn.red-bean.com/repos/test/readme.txt
This is a README file.
You should read this.
          

Tip

如果你的工作複本已過時的話 (或者已有了本地更動), 而你想看看工作複本裡某個檔案的 HEAD 修訂版, 當你指定一個路徑給 svn cat 時, 它可自動取出 HEAD 修訂版:

$ cat foo.c
This file is in my local working copy 
and has changes that I've made.

$ svn cat foo.c
Latest revision fresh from the repository!
          

Name

svn checkout — 自檔案庫取出一個工作複本.

摘要

svn checkout URL... [PATH]

描述

自檔案庫取出一個工作複本. 如果省略 PATH 的話, URL 的主檔名 (basename) 會被視作目錄名稱. 如果指定多個 URL 的話, 每一個都會取出為 PATH 的子目錄, 其名稱為 URL 的主檔名.

替代名稱

co

更動

產生工作複本.

存取檔案庫

選項

--revision (-r) REV
--quiet (-q)
--non-recursive (-N)
--username USER
--password PASS
--no-auth-cache
--non-interactive
          

範例

取出一個工作複本為 'mine' 的目錄:

$ svn checkout file:///tmp/repos/test mine
A  mine/a
A  mine/b
Checked out revision 2.
$ ls
mine
          

取出兩個不同的目錄, 變成兩個不同的工作複本:

$ svn checkout file:///tmp/repos/test  file:///tmp/repos/quiz
A  test/a
A  test/b
Checked out revision 2.
A  quiz/l
A  quiz/m
Checked out revision 2.
$ ls
quiz  test
            

取出兩個不同的目錄, 變成兩個不同的工作複本, 但是將它們置於名為 'working-copies' 的目錄之內:

$ svn checkout file:///tmp/repos/test  file:///tmp/repos/quiz working-copies
A  working-copies/test/a
A  working-copies/test/b
Checked out revision 2.
A  working-copies/quiz/l
A  working-copies/quiz/m
Checked out revision 2.
$ ls
working-copies
          

如果你中斷了取出的動作 (或是其它事情中斷了正在進行的取出動作, 像是斷線等等), 你可以再利用相同的 checkout 命令重新開始, 或是更新尚未完整的工作複本:

$ svn checkout file:///tmp/repos/test test
A  test/a
A  test/b
^C
svn: The operation was interrupted
svn: caught SIGINT

$ svn checkout file:///tmp/repos/test test
A  test/c
A  test/d
^C
svn: The operation was interrupted
svn: caught SIGINT

$ cd test
$ svn update
A  test/e
A  test/f
Updated to revision 3.
          

Name

svn cleanup — 遞迴式地清除工作複本.

摘要

svn cleanup [PATH ... ]

描述

遞迴式地清除工作複本, 移除未完成動作的鎖定. 如果你遇到了 “working copy locked” (工作複本已鎖定) 的錯誤, 執行這個命令以移除遺留下來的鎖定, 讓工作複本再回復到可用的狀態. 請參見 Appendix C, 故障排除.

替代名稱

更動Changes

工作複本

存取檔案庫

選項:

範例

因為 svn cleanup 不會產生任何輸出, 所以這裡沒什麼範例. 如果你沒有指定 PATH, 就使用 '.'.

$ svn cleanup

$ svn cleanup /path/to/working-copy
          

Name

svn commit — 從工作複本傳送更動至檔案庫.

摘要

svn commit [PATH ... ]

描述

從工作複本傳送更動至檔案庫. 如果你沒有以 --file--message 選項來指定紀錄訊息, svn 就會執行你的文字編輯器, 讓你撰寫送交訊息. 請參考 the section called “Config”editor-cmd 一節.

Tip

如果你開始送交的動作, Subversion 也已經執行編輯器, 讓你撰寫送交訊息, 你還是可以中斷送交更動的動作. 如果你想要取消你的送交, 只要結束編輯器, 不要儲存送交訊息, 那麼 Subversion 就會給你提示訊息, 是想要中斷送交, 以無訊息而繼續, 還是再編輯訊息一次.

替代名稱

ci (“check in” 的簡稱, 而不是 “co”, 那是 “checkout” 的簡稱)

更動

工作複本, 檔案庫

存取檔案庫

選項

--message (-m) TEXT
--file (-F) FILE
--quiet (-q)
--non-recursive (-N)
--targets FILENAME
--force-log
--username USER
--password PASS
--no-auth-cache
--non-interactive
--encoding ENC
          

範例

送交一個簡單的檔案更動, 在命令列指定送交訊息, 並以目前的工作目錄 (“.”) 作為隱含的目標:

$ svn commit -m "added howto section."
Sending        a
Transmitting file data .
Committed revision 3.
          

送交檔案 foo.c (在命令列明確地指定) 的更動, 並以檔案 msg 的內容為送交訊息:

$ svn commit -F msg foo.c
Sending        foo.c
Transmitting file data .
Committed revision 5.
          

如果你想要讓 --file 使用一個納入版本控制的檔案來指定送交訊息, 你必須加上 --force-log 選項:

$ svn commit --file file_under_vc.txt foo.c
svn: The log message file is under version control
svn: Log message file is a versioned file; use `--force-log' to override.

$ svn commit --force-log --file file_under_vc.txt foo.c
Sending        foo.c
Transmitting file data .
Committed revision 6.
          

要送交一個預定被刪除的檔案:

svn commit -m "removed file 'c'."
Deleting       c

Committed revision 7.
          

Name

svn copy — 複製一個工作複本或檔案庫的目錄或檔案.

摘要

svn copy SRC DST

描述

複製一個工作複本或檔案庫裡的檔案. SRC 與 DST 可以是工作複本 (WC), 或是 URL:

WC -> WC

複製一個項目, 並排定新增它 (連同歷史紀錄).

WC -> URL

立即送交一個 WC 的複本至 URL.

URL -> WC

將 URL 取出至 WC, 並排定新增它.

URL -> URL

完成伺服器端的複製. 這通常是用來進行分支與標記.

Warning

你只能複製同一個檔案庫裡的檔案. Subversion 不支援跨檔案庫的複製.

替代名稱

cp

更動

如果目標是 URL, 則為檔案庫.

如果目標是工作複本, 則為工作複本.

存取檔案庫

如果來源或目標在檔案庫內, 或是需要查看來源的修訂版號時.

選項

--message (-m) TEXT
--file (-F) FILE
--revision (-r) REV
--quiet (-q)
--username USER
--password PASS
--no-auth-cache
--non-interactive
--force-log
--encoding ENC
          

範例

在工作複本裡複製一個項目 (只是排程複製而已 — 在你進行送交之前, 不會有東西送入檔案庫):

$ svn copy foo.txt bar.txt
A         bar.txt
$ svn status
A  +   bar.txt
          

將一個工作複本的項目複製到檔案庫的 URL (這是立即送交, 所以你必須提供送交訊息):

$ svn copy near.txt file:///tmp/repos/test/far-away.txt -m "Remote copy."

Committed revision 8.
          

將一個檔案庫的項目複製到工作複本 (只是排程複製而已 — 在你進行送交之前, 不會有東西送入檔案庫):

Tip

這是重新取回已於檔案庫消失的檔案的建議用法!

$ svn copy file:///tmp/repos/test/far-away near-here
A         near-here
          

最後, 從一個 URL 複製到另一個 URL:

$ svn copy file:///tmp/repos/test/far-away file:///tmp/repos/test/over-there -m "remote copy."

Committed revision 9.
          

Tip

這是在檔案庫中, '標記' 修訂版的最簡易方法 — 只要以 svn copy 將該修訂版 (通常是 HEAD) 複製到你的 tags 目錄即可.

$ svn copy file:///tmp/repos/test/trunk file:///tmp/repos/test/tags/0.6.32-prerelease -m "tag tree"

Committed revision 12.
          

如果你忘了作標記, 不必太擔心 — 你可以在任何時間, 在標記時指定舊的修訂版:

$ svn copy -r 11 file:///tmp/repos/test/trunk file:///tmp/repos/test/tags/0.6.32-prerelease -m "Forgot to tag at rev 11"

Committed revision 13.
          

Name

svn delete — 刪除一個工作複本或檔案庫的項目.

摘要

svn delete PATH...
svn delete URL...

描述

以 PATH 指定的項目, 會被排定在下一次送交時刪除. 檔案 (還有尚未送交的目錄) 會馬上自工作複本中刪除. 本命令不會刪除任何未受版本控制, 或是被修改過的檔案; 請使用 --force 選項來強制取消這個限制.

以 URL 指定的項目, 會透過立即送交自檔案庫中刪除. 多個 URL 會自動地以不可分割的方式送交.

替代名稱

del, remove, rm

更動

若針對檔案則為工作複本, 若針對 URL 則為檔案庫.

存取檔案庫

僅在針對 URL 時

選項

--force
--force-log
--message (-m) TEXT
--file (-F) FILE
--quiet (-q)
--targets FILENAME
--username USER
--password PASS
--no-auth-cache
--non-interactive
--encoding ENC
          

範例

使用 svn 來刪除工作複本的檔案, 只是預定被刪除. 當你送交更動時, 該檔案才會自檔案庫刪除.

$ svn delete myfile
D         myfile

$ svn commit -m "Deleted file 'myfile'."
Deleting       myfile
Transmitting file data .
Committed revision 14.
          

但是刪除 URL 的效果是立即的, 所以你必須提供紀錄訊息:

$ svn delete -m "Deleting file 'yourfile'" file:///tmp/repos/test/yourfile

Committed revision 15.
          

這裡的例子, 是如何強制進行有本地更動的檔案:

$ svn delete over-there 
svn: Attempting restricted operation for modified resource
svn: Use --force to override this restriction
svn: 'over-there' has local modifications

$ svn delete --force over-there 
D         over-there
          

Name

svn diff — 顯示兩個路徑之間的差異.

摘要

svn diff [-r N[:M]] [TARGET...]
svn diff URL1[@N] URL2[@M]

描述

顯示兩個路徑之間的差異. 每一個 TARGET 可以是工作複本, 或是 URL. 如果沒有指定 TARGET 的話, 會使用 '.'.

如果 TARGET 是 URL 的話, 那麼就必須透過 --revision 指定修訂版 N 與 M.

如果 TARGET 是工作複本路徑, 那麼 --revision 選項表示:

--revision N:M

伺服器會比較 TARGET@N 與 TARGET@M.

--revision N

用戶端會將 TARGET@N 與工作複本作比較.

(沒有 --revision)

用戶端會比較 TARGET 的 BASE 修訂版與工作複本.

如果使用另一個語法的話, 伺服器會比較 URL1 的 N 修訂版與 URL2 的 M 修訂版. 如果 N 與 M 都省略的話, 就假設是 HEAD.

替代名稱

di

更動

存取檔案庫

只有在取得工作複本 BASE 以外修訂版的差異時

選項

--revision (-r) REV
--extensions (-x) "ARGS"
--non-recursive (-N)
--diff-cmd CMD
--username USER
--password PASS
--no-auth-cache
--non-interactive
--no-diff-deleted
          

範例

比較 BASE 修訂版與工作複本 (svn diff 最常見的用法之一):

$ svn diff COMMITTERS 
Index: COMMITTERS
===================================================================
--- COMMITTERS	(revision 4404)
+++ COMMITTERS	(working copy)
          

查看工作複本的修改與舊修訂版之間的差異:

$ svn diff -r 3900 COMMITTERS 
Index: COMMITTERS
===================================================================
--- COMMITTERS	(revision 3900)
+++ COMMITTERS	(working copy)
          

利用 '@' 語法, 比較修訂版 3000 與修訂版 3500 之間的差異:

$ svn diff http://svn.collab.net/repos/svn/trunk/COMMITTERS@3000 http://svn.collab.net/repos/svn/trunk/COMMITTERS@3500
Index: COMMITTERS
===================================================================
--- COMMITTERS	(revision 3000)
+++ COMMITTERS	(revision 3500)
...
          

透過指定範圍的語法, 比較修訂版 3000 與 3500 之間的差異 (此時, 你只要指定一個 URL 即可):

$ svn diff -r 3000:3500 http://svn.collab.net/repos/svn/trunk/COMMITTERS
Index: COMMITTERS
===================================================================
--- COMMITTERS	(revision 3000)
+++ COMMITTERS	(revision 3500)
          

如果你有工作複本的話, 你可以不必輸入冗長的 URL, 就可以取得差異:

$ svn diff -r 3000:3500 COMMITTERS 
Index: COMMITTERS
===================================================================
--- COMMITTERS	(revision 3000)
+++ COMMITTERS	(revision 3500)
          

利用 --diff-cmd CMD -x, 直接傳遞參數給外部的差異程式:

svn diff --diff-cmd /usr/bin/diff -x "-i -b" COMMITTERS 
Index: COMMITTERS
===================================================================
0a1,2
> This is a test
> 
          

Name

svn export — 輸出一個乾淨的目錄樹.

摘要

svn export [-r REV] URL [PATH]
svn export PATH1 PATH2

描述

第一種形式會從 URL 指定的檔案庫, 匯出一個乾淨的目錄樹到 PATH. 如果有指定 REV 的話, 內容即為該修訂版的, 否則就是 HEAD 修訂版. 如果 PATH 被省略的話, URL 的最後部份會被用來當成本地的目錄名稱.

第二種形式會在工作複本中, 從指定的 PATH1 匯出一個乾淨的目錄樹到 PATH2. 所有的本地修改都還會保持著, 但是未納入版本控制的檔案不會被複製.

替代名稱

修改

本地磁碟

存取檔案庫

只有從 URL 匯出時

選項

--revision (-r) REV
--quiet (-q)
--username USER
--password PASS
--no-auth-cache
--non-interactive
          

範例

自你的工作複本匯出 (不會顯示每一個目錄與檔案):

$ svn export a-wc my-export
pantheon: /tmp>
          

直接從檔案庫匯出 (顯示每一個目錄與檔案):

$ svn export file:///tmp/repos my-export
A  my-export/test
A  my-export/quiz
…
Exported revision 15.
          

Name

svn help — 救助!

摘要

svn help [SUBCOMMAND...]

描述

當你正在使用 Subversion, 而這本書又不在手邊, 這是你最好的朋友!

替代名稱

?, h

更動

存取檔案庫

選項

--version
--quiet (-q)
          

Name

svn import — 遞迴式地送交 PATH 的複本至 URL.

摘要

svn import [PATH] URL

描述

遞迴式地送交 PATH 的複本至 URL. 如果省略 PATH, 預設為 '.'. 父目錄會依需要, 在檔案庫內建立.

替代名稱

更動

檔案庫

存取檔案庫

選項

--message (-m) TEXT
--file (-F) FILE
--quiet (-q)
--non-recursive (-N)
--username USER
--password PASS
--no-auth-cache
--non-interactive
--force-log
--encoding ENC
          

範例

這會直接從本地目錄 'myproj' 匯入至檔案庫的根目錄中.

$ svn import -m "New import" myproj http://svn.red-bean.com/repos/test
Adding         myproj/sample.txt
…
Transmitting file data .........
Committed revision 16.
          

這會匯入本地目錄 'myproj' 至檔案庫的 'trunk/vendors'. 'trunk/vendors' 目錄不需在匯入之前就存在 — svn import 會幫你遞迴地建立目錄:

$ svn import -m "New import" myproj \
    http://svn.red-bean.com/repos/test/trunk/vendors/myproj
Adding         myproj/sample.txt
…
Transmitting file data .........
Committed revision 19.
          

Name

svn info — 顯示 PATH 的資訊.

摘要

svn info [PATH...]

描述

顯示工作目錄裡的路徑資訊, 包括了:

  • 路徑 (Path)

  • 名稱 (Name)

  • 網址 (Url)

  • 修訂版 (Revision)

  • 節點類型 (Node Kind)

  • 前次更動作者 (Last Changed Author)

  • 前次更動修訂版 (Last Changed Revision)

  • 前次更動日期 (Last Changed Date)

  • 前次本文更新日期 (Text Last Updated)

  • 前次性質更新日期 (Properties Last Updated)

  • 總和檢查碼 (Checksum)

替代名稱

更動

存取檔案庫

選項

--targets FILENAME
--recursive (-R)
          

範例

svn info 會提供給你所有關於工作複本的項目的有用資訊. 它會顯示檔案的資訊:

$ svn info foo.c
Path: foo.c
Name: foo.c
Url: http://svn.red-bean.com/repos/test/foo.c
Revision: 4417
Node Kind: file
Schedule: normal
Last Changed Author: sally
Last Changed Rev: 20
Last Changed Date: 2003-01-13 16:43:13 -0600 (Mon, 13 Jan 2003)
Text Last Updated: 2003-01-16 21:18:16 -0600 (Thu, 16 Jan 2003)
Properties Last Updated: 2003-01-13 21:50:19 -0600 (Mon, 13 Jan 2003)
Checksum: /3L38YwzhT93BWvgpdF6Zw==
          

它也會顯示目錄的資訊:

$ svn info vendors
Path: trunk
Url: http://svn.red-bean.com/repos/test/vendors
Revision: 19
Node Kind: directory
Schedule: normal
Last Changed Author: harry
Last Changed Rev: 19
Last Changed Date: 2003-01-16 23:21:19 -0600 (Thu, 16 Jan 2003)
          

Name

svn list — 列出檔案庫中的目錄項目.

摘要

svn list [TARGET...]

描述

列出每個 TARGET 檔案與每個檔案庫裡的 TARGET 目錄. 如果 TARGET 是工作複本的路徑, 那麼相對應的檔案庫 URL 就會被拿來使用.

預設的 TARGET 是 '.', 表示目前工作複本目錄的檔案庫 URL.

加上 --verbose 的話, 以下的欄位會顯示出項目的狀態:

  • 前次送交的修訂版(Revision number of the last commit)

  • 前次送交的作者(Author of the last commit)

  • 大小 (Size) (以位元組為單位)

  • 前次送交的日期與時間(Date and time of the last commit)

替代名稱

ls

更動

存取檔案庫

選項

--revision (-r) REV
--verbose (-v)
--recursive (-R)
--username USER
--password PASS
--no-auth-cache
--non-interactive
            

範例

如果你想知道 repository 中有什麼檔案, 又不想下載工作複本的話, svn list 就很方便.

$ svn list http://svn.red-bean.com/repos/test/support
README.txt
INSTALL
examples/
…
          

就像 UNIX 的 ls, 你也可以傳遞 --verbose 選項, 以取得更詳細的資訊:

svn list --verbose file:///tmp/repos
     16 sally         28361 Jan 16 23:18 README.txt
     27 sally             0 Jan 18 15:27 INSTALL
     24 harry               Jan 18 11:27 examples/
          

欲知詳情, 請參考 the section called “svn list”.


Name

svn log — 顯示送交紀錄訊息.

摘要

svn log [PATH]
svn log URL [PATH...]

描述

預設的目標, 是你目前工作目錄的路徑. 如果沒有提供任何引數的話, svn log 會顯示目前工作複本的工作目錄裡, 所有檔案的紀錄訊息. 你可以透過指定一個路徑, 一個或多個修訂版, 或兩者的任何組合來微調產生的結果. 預設對本地路徑的修訂版範圍為 BASE:1.

如果你只有指定一個 URL, 那麼它會顯示該 URL 所有的每一個項目的紀錄訊息. 如果你在 URL 之後指定路徑, 那麼只會顯示該 URL 下的這些指定的路徑的訊息. 預設對 URL 的修訂版範圍為 HEAD:1.

使用 --verbose 的話, 每一個紀錄訊息的影響路徑也會一併顯示. 使用 --quiet 的話, svn log 不會顯示紀錄訊息 (這與 --verbose 相容).

每一個紀錄訊息只會顯示一次而已, 就算明確地指定多個被它所影響的路徑亦同. 預設的顯示訊息會跟著複製歷史; 使用 --stop-on-copy 可以關閉這個功能, 在找出分支點的很方便.

替代名稱

更動

存取檔案庫

選項

--revision (-r) REV
--quiet (-q)
--verbose (-v)
--targets FILENAME
--username USER
--password PASS
--no-auth-cache
--non-interactive
--stop-on-copy
--incremental
--xml
            

範例

你可以在工作目錄的最上層目錄執行 svn log, 以得知所有更動過的路徑的紀錄訊息:

$ svn log
------------------------------------------------------------------------
r20:  harry | 2003-01-17 22:56:19 -0600 (Fri, 17 Jan 2003) | 1 line

Tweak.
------------------------------------------------------------------------
r17:  sally | 2003-01-16 23:21:19 -0600 (Thu, 16 Jan 2003) | 2 lines
…
          

檢視特定工作複本檔案的所有紀錄訊息.

$ svn log foo.c
------------------------------------------------------------------------
r32:  sally | 2003-01-13 16:43:13 -0600 (Mon, 13 Jan 2003) | 1 line

Added defines.
------------------------------------------------------------------------
r28:  sally | 2003-01-07 21:48:33 -0600 (Tue, 07 Jan 2003) | 3 lines
…
          

如果你沒有工作複本的話, 也可以對 URL 執行本命令:

$ svn log http://svn.red-bean.com/repos/test/foo.c
------------------------------------------------------------------------
r32:  sally | 2003-01-13 16:43:13 -0600 (Mon, 13 Jan 2003) | 1 line

Added defines.
------------------------------------------------------------------------
r28:  sally | 2003-01-07 21:48:33 -0600 (Tue, 07 Jan 2003) | 3 lines
…
          

如果你要看在同一個 URL 下的數個不同的路徑, 你可以使用 URL [PATH...] 語法.

$ svn log http://svn.red-bean.com/repos/test/ foo.c bar.c
------------------------------------------------------------------------
r32:  sally | 2003-01-13 16:43:13 -0600 (Mon, 13 Jan 2003) | 1 line

Added defines.
------------------------------------------------------------------------
r31:  harry | 2003-01-10 12:25:08 -0600 (Fri, 10 Jan 2003) | 1 line

Added new file bar.c
------------------------------------------------------------------------
r28:  sally | 2003-01-07 21:48:33 -0600 (Tue, 07 Jan 2003) | 3 lines
…
          

它與明確地在命令列指定兩個 URL 的效果相同:

$ svn log http://svn.red-bean.com/repos/test/foo.c \
          http://svn.red-bean.com/repos/test/foo.c
…
          

當你要把數個 log 命令的結果合併起來時, 你可能會想要使用 --incremental 選項. 正常來講, svn log 會在紀錄訊息開頭, 在後續的紀錄訊息之間, 在最後一個紀錄訊息之後加上虛線列. 如果你對兩個修訂版的範圍執行 svn log 的話, 你會得到以下的結果:

$ svn log -r 14:15
------------------------------------------------------------------------
r14: ...

------------------------------------------------------------------------
r15: ...

------------------------------------------------------------------------
          

不過如果你想要把兩個不連續的紀錄訊息放在同一個檔案中, 你應該會作像下列的事:

$ svn log -r 14 > mylog
$ svn log -r 19 >> mylog
$ svn log -r 27 >> mylog
$ cat mylog
------------------------------------------------------------------------
r14: ...

------------------------------------------------------------------------
------------------------------------------------------------------------
r19: ...

------------------------------------------------------------------------
------------------------------------------------------------------------
r27: ...

------------------------------------------------------------------------
          

你可以藉由使用 --incremental 選項, 避免在你的輸出出現連續兩個虛線列:

$ svn log --incremental -r 14 > mylog
$ svn log --incremental -r 19 >> mylog
$ svn log --incremental -r 27 >> mylog
$ cat mylog
------------------------------------------------------------------------
r14: ...

------------------------------------------------------------------------
r19: ...

------------------------------------------------------------------------
r27: ...
            

當使用 --xml 選項時, --incremental 選項也提供了類似的輸出控制.

Tip

如果你對特定的路徑執行 svn log, 提供了特定的修訂版, 但是沒有得到任何的輸出:

$ svn log -r 20 http://svn.red-bean.com/untouched.txt
------------------------------------------------------------------------
            

這只是表示那個路徑並沒有在該修訂版中被修改. 如果你從檔案庫最上層執行本命令的話, 或是知道在該修訂版變動的檔案, 你可以明確地指定它:

$ svn log -r 20 touched.txt 
------------------------------------------------------------------------
r20:  sally | 2003-01-17 22:56:19 -0600 (Fri, 17 Jan 2003) | 1 line

Made a change.
------------------------------------------------------------------------
            

Name

svn merge — 將兩個來源之間的差異套用到工作複本路徑.

摘要

svn merge sourceURL1[@N] sourceURL2[@M] [WCPATH]
svn merge -r N:M SOURCE [PATH]

描述

第一種形式中, 來源 URL 各被指定到修訂版 N 與 M. 這兩個就是作為比較的來源. 如果沒有指定修訂版的話, 預設為 HEAD.

第二種形式中, SOURCE 可為 URL 或工作複本項目, 後者會使用對應的 URL. 在修訂版 N 與 M 的 URL, 定義出兩個比較的來源.

WCPATH 是接收更動的工作複本路徑. 如果省略 WCPATH 的話, 預設值為 '.', 除非有一個 '.' 中的檔案與來源中的主檔名相同; 此時, 產生的差異會套用到該檔案.

替代名稱

更動

工作複本

存取檔案庫

僅在有 URL 的情況下

選項

--revision (-r) REV
--non-recursive (-N)
--quiet (-q)
--force
--dry-run
--username USER
--password PASS
--no-auth-cache
--non-interactive
          

範例

將分支合併回 trunk (假設你有一份 trunk 的工作複本):

$ svn merge http://svn.red-bean.com/repos/trunk/vendors \
            http://svn.red-bean.com/repos/branches/vendors-with-fix
U  myproj/tiny.txt
U  myproj/thhgttg.txt
U  myproj/win.txt
U  myproj/flo.txt
          

如果你在修訂版 23 建立分支, 然後想要把 trunk 的更動合併至分支中, 你可以從分支的工作複本中執行下列命令:

$ svn merge -r 23:30 file:///tmp/repos/trunk/vendors
U  myproj/thhgttg.txt
…
          

將更動合併到一個檔案中:

$ cd myproj
$ svn merge -r 30:31 thhgttg.txt 
U  thhgttg.txt
          

Name

svn mkdir — 在版本控制之下建立一個新目錄.

摘要

svn mkdir PATH...
svn mkdir URL...

描述

建立一個與 PATH 或 URL 的最後部份同名的目錄. 以工作複本 PATH 指定的目錄會預訂加入至工作複本裡. 以 URL 指定的目錄, 會透過立即送交, 於檔案庫內建立. 在兩種情況下, 中間的目錄都必須事先建立.

替代名稱

更動

工作複本, 若針對 URL 則為檔案庫

存取檔案庫

僅在有 URL 的情況下

選項

--message (-m) TEXT
--file (-F) FILE
--quiet (-q)
--username USER
--password PASS
--no-auth-cache
--non-interactive
--encoding ENC
--force-log
          

範例

在工作複本中建立一個目錄:

$ svn mkdir newdir
A         newdir
          

在檔案庫建立目錄 (這是立即送交, 需要提供紀錄訊息):

$ svn mkdir -m "Making a new dir." http://svn.red-bean.com/repos/newdir

Committed revision 26.
          

Name

svn move — 移動目錄或檔案.

摘要

svn move SRC DST

描述

本命令可移動工作複本或檔案庫的目錄或檔案.

Tip

本命令等同於 svn copy, 再接著 svn delete.

Warning

Subversion 不支援工作複本與 URL 之間的移動動作. 此外, 你只能於相同的檔案庫內移動檔案 — Subversion 不支援跨檔案庫的移動.

WC -> WC

移動並排定新增目錄或檔案 (連同歷史紀錄一起).

URL -> URL

完成伺服端的更名動作.

替代名稱

mv, rename, ren

更動

工作複本, 若針對 URL 則為檔案庫

存取檔案庫

僅在有 URL 的情況下

選項

--message (-m) TEXT
--file (-F) FILE
--revision (-r) REV
--quiet (-q)
--force
--username USER
--password PASS
--no-auth-cache
--non-interactive
--encoding ENC
--force-log
          

範例

在工作複本中移動檔案:

$ svn move foo.c bar.c
A         bar.c
D         foo.c
          

在檔案庫中移動檔案 (這是立即送交, 需要提供紀錄訊息):

$ svn move -m "Move a file" http://svn.red-bean.com/repos/foo.c \
                            http://svn.red-bean.com/repos/bar.c

Committed revision 27.
          

Name

svn propdel — 移除一個項目的性質.

摘要

svn propdel PROPNAME [PATH...]
svn propdel PROPNAME --revprop -r REV [URL]

描述

本命令會移除目錄, 檔案, 以及修訂版的性質. 第一種形式會移除納入版本控制的工作複本性質, 而第二種形式會移除無版本控制的檔案庫遠端修訂版性質.

替代名稱

pdel

更動

工作複本, 如果針對 URL 則為檔案庫

存取檔案庫

僅在有 URL 的情況下

選項

--quiet (-q)
--recursive (-R)
--revision (-r) REV
--revprop
          

範例

刪除工作複本檔案的性質

$ svn propdel svn:mime-type  some-script
property `svn:mime-type' deleted from 'some-script'.
          

刪除一個修訂版性質:

$ svn propdel --revprop -r 26 release-date 
property `release-date' deleted from repository revision '26'
          

Name

svn propedit — 編輯納入版本控制的一個或多個項目的性質.

摘要

svn propedit PROPNAME PATH...
svn propedit PROPNAME --revprop -r REV [URL]

描述

使用你偏愛的文字編輯器, 編輯一個或多個性質. 第一種形式可編輯納入版本控制的工作複本性質, 而第二種形式可編輯無版本控制的檔案庫遠端修訂版性質更動.

替代名稱

pedit, pe

更動

工作複本, 若針對 URL 則為檔案庫

存取檔案庫

僅在有 URL 的情況下

選項

--revision (-r) REV
--revprop
--encoding ENC
          

範例

svn propedit 讓修改擁有多個數值的性質相當地容易:

$ svn propedit svn:keywords  foo.c 
    <svn 在這裡會執行你偏好的文字編輯器, 開啟一個含有目前 svn:keywords
    數值的編輯內容. 你只要在這裡每一列輸入一個數值, 就可以很容易地對一個
    性質新增多個數值.>
Set new value for property `svn:keywords' on `foo.c'
          

Name

svn propget — 顯示性質的數值.

摘要

svn propget PROPNAME [PATH...]
svn propget PROPNAME --revprop -r REV [URL]

描述

顯示目錄, 檔案, 或修訂版的性質數值. 第一個形式會顯示納入版本控制的工作複本性質, 而第二個形式會顯示無版本控制的檔案庫修訂版性質. 請參考 the section called “性質” 以取得更多有關性質的資訊.

替代名稱

pget, pg

更動

工作複本, 若針對 URL 則為檔案庫

存取檔案庫

僅在有 URL 的情況下

選項

--recursive (-R)
--revision (-r) REV
--revprop
          

範例

檢視工作複本檔案的性質:

$ svn propget svn:keywords foo.c
Author
Date
Rev
          

一樣, 但是目標是修訂版性質:

$ svn propget svn:log --revprop -r 20 
Began journal.
          

Name

svn proplist — 列出所有的性質.

摘要

svn proplist [PATH...]
svn proplist --revprop -r REV [URL]

描述

列出所有檔案, 目錄, 或是修訂版的性質. 第一種形式會顯示納入版本管理的工作複本性質, 第二種形式會顯示無版本控制的檔案庫遠端修訂版性質更動.

替代名稱

plist, pl

更動

工作複本, 若針對 URL 則為檔案庫

存取檔案庫

僅在有 URL 的情況下

選項

--verbose (-v)
--recursive (-R)
--revision (-r) REV
--revprop
          

範例

你可以使用本命令, 察看工作複本中某一個項目的性質:

$ svn proplist foo.c
Properties on 'foo.c':
  svn:mime-type
  svn:keywords
  owner
          

如果使用 --verbose 選項的話, svn proplist 就變得更加地方便, 因為它會連性質的數值也一併顯示出來:

$ svn proplist --verbose foo.c
Properties on 'foo.c':
  svn:mime-type : text/plain
  svn:keywords : Author Date Rev
  owner : sally
          

Name

svn propset — 設定目錄, 檔案, 或修訂版的 PROPNAME 內容為 PROPVAL.

摘要

svn propset PROPNAME [PROPVAL | -F VALFILE] PATH...
svn propset PROPNAME --revprop -r REV [PROPVAL | -F VALFILE] [URL]

描述

設定目錄, 檔案, 或修訂版的 PROPNAME 內容為 PROPVAL. 第一個例子會在工作複本中建立一個納入版本控制的本地性質更動, 第二個例子對檔案庫修訂版建立一個無版本控制的遠端性質更動.

Tip

Subversion 的行為會被幾個 “特殊” 性質影響. 請參考 the section called “特殊性質” 以了解更多這類的性質.

替代名稱

pset, ps

更動

工作複本, 若針對 URL 則為檔案庫

存取檔案庫

僅在有 URL 的情況下

選項

--file (-F) FILE
--quiet (-q)
--revision (-r) REV
--targets FILENAME
--recursive (-R)
--revprop
--encoding ENC
          

範例

設定一個檔案的 mime 型別:

$ svn propset svn:mime-type image/jpeg foo.jpg 
property `svn:mime-type' set on 'foo.jpg'
          

在 UNIX 系統上, 如果你想要設定一個檔案的執行權限:

$ svn propset svn:executable ON somescript
property `svn:executable' set on 'somescript'
          

也許你們有個內部原則, 為了同事們的方便, 要設定某些性質:

$ svn propset owner sally foo.c
property `owner' set on 'foo.c'
          

如果你寫錯了某個修訂版的紀錄訊息, 想要修正它, 使用 --revprop 並設定 svn:log 為新的紀錄訊息:

$ svn propset --revprop -r 25 svn:log "Journaled about trip to New York."
property `svn:log' set on repository revision '25'
          

或者沒有工作複本的話, 你可以提供一個 URL.

$ svn propset --revprop -r 26 svn:log "Document nap." http://svn.red-bean.com/repos
property `svn:log' set on repository revision '25'
          

最後, 你可以讓 propset 從檔案取得它的輸入內容. 你甚至可以透過這個功能, 將某個性質設定成二進制的內容:

$ svn propset owner-pic -F sally.jpg moo.c 
property `owner-pic' set on 'moo.c'
          

Warning

預設情況下, 你無法設定 Subversion 的檔案庫的修訂版性質. 你的檔案庫管理員必須先建立名為 'pre-revprop-change' 的掛勾程式, 明確地開啟更動修訂版性質的功能. 請參考 the section called “Hook scripts”, 以了解更多關於掛勾程式的資訊.


Name

svn resolved — 移除工作複本的目錄或檔案的 “衝突” 狀態.

摘要

svn resolve PATH...

描述

移除工作複本的目錄或檔案的 “衝突” 狀態. 這個動作並不會依語法來解決衝突標記; 它只是移除衝突的相關檔案, 然後讓 PATH 可以再度送交; 也就是說, 它告訴 Subversion 衝突已經 “解決” 了. 請參考 the section called “解決衝突 (合併他人的更動)”, 以便對解決衝突有更進一步的了解.

替代名稱

更動

工作複本

存取檔案庫

選項

--targets FILENAME
--recursive (-R)
--quiet (-q)
          

範例

如果你在更新時得到一個衝突, 你的工作複本會出現三個新檔案:

$ svn update
C  foo.c
Updated to revision 31.
$ ls
foo.c
foo.c.mine
foo.c.r30
foo.c.r31
          

當衝突解決了之後, foo.c 就可以進行送交. 執行 svn resolved, 讓工作複本知道你已經處理妥當了.

Warning

可以 直接刪除衝突檔案, 然後送行送交, 但是 svn resolved 除了刪除衝突檔案之外, 還會在工作複本的管理區域進行一些狀態更新的工作, 所以我們建議你使用本命令.


Name

svn revert — 回復所有的本地編輯.

摘要

svn revert PATH...

描述

回復所有目錄或檔案的本地修改, 並且解除衝突的狀況. svn revert 不只會回復檔案的內容而己, 它連性質更動也會回復. 最後, 你可以用它來解除你已進行的預定排程動作 (像是預定新增或刪除的檔案, 可以 “取消預定排程”).

替代名稱

更動

工作複本

存取檔案庫

選項

--targets FILENAME
--recursive (-R)
--quiet (-q)
          

範例

放棄檔案的更動:

$ svn revert foo.c
Reverted foo.c
          

如果你想要回復一整個目錄的檔案, 請使用 --recursive 旗標:

$svn revert --recursive .
Reverted newdir/afile
Reverted foo.c
Reverted bar.txt
          

最後, 你可以取消任何預定的排程:

$ svn add mistake.txt whoops
A         mistake.txt
A         whoops
A         whoops/oopsie.c

$ svn revert mistake.txt whoops
Reverted mistake.txt
Reverted whoops

$ svn status
?      mistake.txt
?      whoops
          

Warning

如果你沒有指定任何目錄給 svn revert 的話, 它什麼都不會作 — 為了預防你不小心失去所有工作複本的更動, svn revert 要求你必須提供至少一個目標.


Name

svn status — 顯示工作複本目錄與檔案的狀態.

摘要

svn status [PATH...]

描述

顯示工作複本目錄與檔案的狀態. 未指定引數時, 它只會顯示本地修改的項目 (沒有檔案庫的存取動作). 使用 --show-updates 時, 會增加工作修訂版與伺服器過時的資訊. 使用 --verbose 時, 顯示每一個項目的完整修訂版資訊.

輸出的前五個欄位各為一個字元寬, 每一個欄位提供你每一個工作複本項目不同部份的資訊.

第一個欄位表示一個項目是新增, 刪除, 不然就是修改過的.

' '

無修改.

'A'

該項目預計要新增.

'D'

該項目預計要刪除.

'M'

該項目已被修改.

'C'

該項目與來自檔案庫的更新有所衝突.

'I'

該項目已被忽略 (例如透過 svn:ignore 性質).

'?'

該項目未納入版本控制.

'!'

該項目已遺失 (例, 未使用 svn 而刪除它), 或是不完整 (取出或更新時被中斷).

'~'

該項目納入版本控制為目錄, 但是已經被取代成檔案, 或是相反的情況.

第二欄顯示目錄或檔案的性質狀態:

' '

無修改.

'M'

本項目的性質已被更動.

'C'

本項目的性質與來自檔案庫的性質更新有所衝突.

第三欄只在工作複本目錄被鎖定時才會出現.

' '

本項目未被鎖定.

'L'

本項目已被鎖定.

第四欄只在本項目已預定要連同歷史紀錄被新增時, 才會出現.

' '

預定的送交並不包含歷史紀錄.

'+'

預定的送交包含歷史紀錄.

相對其父目錄, 該項目已被切換 (參見 the section called “切換工作複本”) 時, 第五欄才會出現.

' '

該項目是其父目錄的子項目.

'S'

該項目已被切換.

是否過時的資訊, 出現的位置是第八欄 (僅於使用 --show-updates 選項時).

' '

工作複本的項目是最新版的.

'*'

伺服器上有該項目的更新修訂版.

剩餘的欄位皆為變動寬度, 並以空白隔開. 如果使用 --show-updates--verbose 的話, 工作修訂版就是下一個欄位.

如果使用 --verbose 選項的話, 跟著會顯示最後送交的修訂版, 以及最後送交的作者.

工作複本路徑一定是最後一欄, 所以它可以包含空白字元.

替代名稱

stat, st

更動

存取檔案庫

僅在使用 --show-updates 的情況下

選項

--show-updates (-u)
--verbose (-v)
--non-recursive (-N)
--quiet (-q)
--username USER
--password PASS
--no-auth-cache
--non-interactive
--no-ignore
          

範例

這是更容易找出你在工作複本中作了哪些更動的方法:

$ svn status wc
 M     wc/bar.c
A  +   wc/qax.c
          

如果你想要找出哪些工作複本的檔案是過時的話, 請使用 --show-updates 選項 (這 不會 對你的工作複本產生任何更動). 在這裡, 你可以看出 wc/foo.c 在我們上次更新工作複本後, 已經在檔案庫中有所變動了:

$ svn status --show-updates wc
 M           965    wc/bar.c
       *     965    wc/foo.c
A  +         965    wc/qax.c
Head revision:   981
            

Warning

--show-updates 只會 在過時的項目 (也就是執行 svn update 時, 會自檔案庫更新的項目) 旁邊加上星號. --show-updates 不會 讓列出來的狀態, 顯示出該項目位於檔案庫的版本.

最後, 以下是你從 status 子命令所能取得最詳細的資訊:

$ svn status --show-updates --verbose wc
 M           965       938 sally        wc/bar.c
       *     965       922 harry        wc/foo.c
A  +         965       687 harry        wc/qax.c
             965       687 harry        wc/zig.c
Head revision:   981
          

欲得知更多有關 svn status 的例子, 請參考 the section called “svn status”.


Name

svn switch — 將工作複本更新至不同的 URL.

摘要

svn switch URL [PATH]

描述

本子命令會更新你的工作複本, 映射到一個新的 URL — 通常是一個與工作複本有共通起源的 URL, 不過這不是必要的. 這是 Subversion 移動工作複本到一個新的分支的方法. (請參考 the section called “切換工作複本”), 以了解更多有關於切換的細節.

替代名稱

sw

更動

工作複本

存取檔案庫

選項

--revision (-r) REV
--non-recursive (-N)
--quiet (-q)
--relocate
--username USER
--password PASS
--no-auth-cache
--non-interactive
          

範例

如果你現在在目錄 'vendors' 之中, 它有一個分支 'vendors-with-fix', 而你想要將工作複本切換到該分支去:

$ svn switch http://svn.red-bean.com/repos/branches/vendors-with-fix .
U  myproj/foo.txt
U  myproj/bar.txt
U  myproj/baz.c
U  myproj/qux.c
Updated to revision 31.
          

要切換回來, 只要使用原來用以取出工作複本的檔案庫位置的 URL 即可:

$ svn switch http://svn.red-bean.com/repos/trunk/vendors .
U  myproj/foo.txt
U  myproj/bar.txt
U  myproj/baz.c
U  myproj/qux.c
Updated to revision 31.
          

Tip

如果你不需要將整個工作複本都切換過去的話, 你也可以只切換部份工作複本到某個分支.

如果你的檔案庫位置變動了, 但是現有的工作複本仍想繼續使用, 不想變更時, 你可以使用 svn switch --relocate, 將工作複本的 URL 從這一個換到另外一個:

$ svn checkout file:///tmp/repos test
A  test/a
A  test/b
…

$ mv repos newlocation
$ cd test/

$ svn update
svn: Couldn't open a repository.
svn: Unable to open an ra_local session to URL
svn: Unable to open repository 'file:///tmp/repos'

$ svn switch --relocate file:///tmp/repos file:///tmp/newlocation .
$ svn update
At revision 3.
          

Name

svn update — 更新工作複本.

摘要

svn update [PATH...]

描述

svn update 會將檔案庫的更動帶入至工作複本. 如果沒有提供修訂版的話, 它會將你的工作複本更新至 HEAD 修訂版. 不然的話, 同步至 --revision 選項所指定的修訂版.

對每一個更新的項目, 每一列開頭會以一個字元表示所採取的行為. 這些字元代表如下:

A

新增

D

刪除

U

更新

C

衝突

M

合併

第一欄的字元表示實際檔案的更新, 而檔案性質顯示在第二欄.

替代名稱

up

更動

工作複本

存取檔案庫

選項

--revision (-r) REV
--non-recursive (-N)
--quiet (-q)
--username USER
--password PASS
--no-auth-cache
--non-interactive
          

範例

取得上次更新後, 檔案庫裡產生的更動:

$ svn update
A  newdir/toggle.c
A  newdir/disclose.c
A  newdir/launch.c
D  newdir/README
Updated to revision 32.
          

你也可以將工作複本更新至舊的修訂版 (Subversion 沒有 CVS 的 “sticky” 概念. 請參見 Appendix A, 給 CVS 使用者的 Subversion 指引):

svn update -r30
A  newdir/README
D  newdir/toggle.c
D  newdir/disclose.c
D  newdir/launch.c
U  foo.c
Updated to revision 30.
          

Tip

如果你想要檢視單一檔案的舊修訂版內容, 你可能會想用 svn cat.

svnadmin

svnadmin 是用來監控與修復 Subversion 檔案庫的管理工具. 詳情請參考 the section called “svnadmin”.

由於 svnadmin 是以直接存取檔案庫的方式工作 (所以只能在存放有檔案庫的機器上使用), 因此它是以 路徑 來指定檔案庫, 而非 URL.

svnadmin 選項

--bypass-hooks

略過檔案庫掛勾系統.

--copies

檢視路徑時, 跟隨複製歷史紀錄.

--in-repos-template ARG

在建立新的檔案庫時, 指定一個作為檔案庫結構的範本.

in-repository” 範本可指定檔案庫本身的配置 (存在於 db/ 目錄的 Berkeley DB 檔案中), 像是 /trunk, /branches 等等. 這些範本可被管理員或應用程式作為設定檔案庫初始載入之用 (不需要執行掛勾程式). 這沒有預設值; 檔案庫一開始是 “空的”, 除非你作另外的指定.

--incremental

將修訂版內容以對前一版的差異傾印出來, 而非一般使用的完整文字.

--on-disk-template ARG

指定一個範本, 作為你想要建立的檔案庫在磁碟中的目錄結構 (也就是 conf/, hooks/ 等等).

磁碟” 範本描述檔案庫目錄. 每一個範本都有一個名稱, 而 “預設” 的磁碟範本包含了:

  • default/

  • README.txt

  • dav/

  • format

  • hooks/

  • post-commit.tmpl

  • post-revprop-change.tmpl

  • pre-commit.tmpl

  • pre-revprop-change.tmpl

  • start-commit.tmpl

  • locks/

  • db.lock

磁碟結構一般用來預先定義要建立的掛勾命令稿. 舉例來說, 你可以預先建立 post-commit 命令稿, 裡面使用郵件與備份命令稿. 接下來, 每一次管理員建立一個新的檔案庫時, 她就可以使用這個新的範本, 自動地將所有的掛勾包含進來.

--revision (-r) ARG

指定運作的修訂版.

svnadmin 子命令

Name

svnadmin list-unused-dblogs — 詢問 Berkeley DB, 哪些紀錄檔可安全地刪除

摘要

svnadmin list-unused-dblogs REPOS_PATH

描述

Berkeley 會對所有檔案庫的更動建立紀錄, 以便在災難事件發生時, 得以進行重建. 隨著使用時間的增加, 紀錄檔會逐漸累積, 但是大部份都不再使用, 可以將之刪除, 以釋放磁碟空間. 請參照 the section called “Berkeley DB 工具”, 以取得更多的資訊.

範例

自檔案庫移除所有不再使用的紀錄檔:

$ svnadmin list-unused-dblogs /path/to/repos
/path/to/repos/log.0000000031
/path/to/repos/log.0000000032
/path/to/repos/log.0000000033

$ svnadmin list-unused-dblogs /path/to/repos | xargs rm
## 釋放磁碟空間!
          

Name

svnadmin create — 在 REPOS_PATH 建立一個新的, 空的檔案庫.

摘要

svnadmin create REPOS_PATH

描述

在指定的路徑建立一個新的, 空的檔案庫. 如果提供的目錄不存在, 它會自動被建立出來.

選項

--on-disk-template arg
--in-repos-template arg
          

範例

建立一個新的檔案庫就是這麼簡單:

$ svnadmin create /usr/local/svn/repos
          

Name

svnadmin dump — 將檔案系統的內容傾印到標準輸出.

摘要

svnadmin dump REPOS_PATH [-r LOWER[:UPPER]] [--incremental]

描述

將檔案系統的內容, 以一種可攜式 '傾印檔' 格式輸出到標準輸出, 並將訊息回報輸出到標準錯誤. 將 LOWER 與 UPPER 之間修訂版內容傾印出來. 如果沒有指定修訂版的話, 傾印所有的修訂版樹. 如果只有指定 LOWER 的話, 只傾印一個修訂版樹. 請參考 the section called “匯入檔案庫” 看看實際的使用.

選項

--revision (-r)
--incremental
          

範例

傾印整個檔案庫:

$ svnadmin dump /usr/local/svn/repos
SVN-fs-dump-format-version: 1
Revision-number: 0
* Dumped revision 0.
Prop-content-length: 56
Content-length: 56
…
          

以遞增的方式, 從檔案庫傾印單一異動 (transaction):

$ svnadmin dump /usr/local/svn/repos -r 21 --incremental 
* Dumped revision 21.
SVN-fs-dump-format-version: 1
Revision-number: 21
Prop-content-length: 101
Content-length: 101
…
          

Name

svnadmin help

摘要

svn help [SUBCOMMAND...]

描述

當你困在荒島上, 沒有網路, 沒有這本書的時候, 這個子命令可提供很大的幫助.


Name

svnadmin load — 自標準輸入讀取 “傾印檔格式” 的串流.

摘要

svnadmin load REPOS_PATH

描述

從標準輸入讀取 “傾印檔” 格式的串流, 將新的修訂版送交至檔案庫的檔案系統中. 將進度回報送至標準輸出.

範例

這裡示範開始從備份檔 (當然囉, 由 svn dump 產生的) 載入至檔案庫中:

$ svnadmin load /usr/local/svn/restored < repos-backup
<<< Started new txn, based on original revision 1
     * adding path : test ... done.
     * adding path : test/a ... done.
…
          

Name

svnadmin lstxns — 顯示所有未處理異動的名稱.

摘要

svnadmin lstxns REPOS_PATH

描述

顯示所有未處理異動的名稱. 請參考 the section called “檔案庫善後”, 以得知更多有關於未送交的異動是如何產生的, 以及你該怎麼處理.

範例

列出所有檔案庫中未處理的異動:

$ svnadmin lstxns /usr/local/svn/repos/ 
1w
1x
          

Name

svnadmin recover — 修復檔案庫失去的狀態.

摘要

svnadmin recover REPOS_PATH

描述

如果你遇到要求修復檔案庫的錯誤訊息, 請執行本命令.

Warning

只有在你 絕對確定 你是唯一存取檔案庫的人時, 才執行本命令 — 本命令必須有獨佔的存取權. 請參考 the section called “檔案庫回復”, 以取得修復檔案庫更詳細的說明.

範例

修復一個有問題的檔案庫:

$ svnadmin recover /usr/local/svn/repos/ 
Acquiring exclusive lock on repository db.
Recovery is running, please stand by...
Recovery completed.
The latest repos revision is 34.
          

Name

svnadmin rmtxns — 自檔案庫刪除異動.

摘要

svnadmin rmtxns REPOS_PATH TXN_NAME...

描述

從檔案庫刪除所有未處理的異動. 細節請參照在 the section called “檔案庫善後”.

範例

移除一個具名異動:

$ svnadmin rmtxns /usr/local/svn/repos/ 1w 1x
          

很幸運的, svn lstxns 的輸出可以直接作為 rmtxns 的輸入:

$ svnadmin rmtxns /usr/local/svn/repos/  `svnadmin lstxns /usr/local/svn/repos/`
          

這樣子, 所有尚未處理的異動就會從檔案庫中移除.


Name

svnadmin setlog — 設定某個修訂版的紀錄訊息.

摘要

svnadmin setlog REPOS_PATH -r REVISION FILE

描述

將 FILE 的內容, 設定為修定版 REVISION 的紀錄訊息.

這跟使用 svn propset --revprop 來設定 修訂版的 svn:log 性質相當類似, 但是你也可以使用 --bypass-hooks 選項, 不執行任何 pre-commit 或 post-commit 掛勾. 如果 pre-revprop-change 掛勾中並不允許修改修訂版性質的話, 此時就很方便了.

Warning

修訂版性質並未納入版本控管, 所以這個命令會永遠蓋寫掉先前的紀錄訊息.

選項

--revision (-r) ARG
--bypass-hooks
          

範例

將修訂版 19 的紀錄訊息設定成檔案 'msg' 的內容:

$ svnadmin setlog /usr/local/svn/repos/ -r 19 msg
          

svnlook

svnlook 是用來檢視 Subversion 檔案庫不同方面的命令列工具程式. 它不會對檔案庫作任何的更動 — 它只是用來 “偷看” 而己. svnlook 通常被檔案庫的掛勾程式所使用, 但是檔案庫管理員也可以用它來進行檢測系統.

由於 svnlook 是透過直接存取檔案庫來運作 (因為也只能在擁有檔案庫的機器上使用), 所以它都是以路徑作為目標, 而非 URL.

如果沒有指定修訂版或交易的話, svnlook 預設會以檔案庫最年輕的 (最近的) 修訂版.

svnlook 選項

svnlook 的選項是全面通用的, 就像 svnsvnadmin 一樣. 但是大多數的選項只能用在一個子命令上, 因為 svnlook 的功能都限定在單一的範圍上.

--no-diff-deleted

不讓 svnlook 顯示已刪除檔案的差異. 如果有一個檔案在異動/修訂版中被刪除, 預設行為會將差異顯示出來, 就好像你還留著檔案, 但是檔案內容已悉數刪除.

--revision (-r)

指定你想要檢視的修訂版號.

--transaction (-t)

指定你想要檢視的異動編號.

--show-ids

顯示檔案系統樹中, 每一個路徑的檔案系統節點修訂版編號.

Name

svnlook author — 顯示作者.

摘要

svnlook author REPOS_PATH

描述

顯示檔案庫中, 某個修訂版或異動的作者.

選項

--revision (-r)
--transaction (-t)
          

範例

svnlook author 很方便, 但是沒什麼好玩的:

$ svnlook author -r 40 /usr/local/svn/repos 
sally
          

Name

svnlook cat — 顯示檔案的內容.

摘要

svnlook cat REPOS_PATH PATH_IN_REPOS

描述

顯示檔案的內容.

選項

--revision (-r)
--transaction (-t)
          

範例

這會顯示異動 ax8 中, 位於 /trunk/README 的檔案內容:

$ svnlook cat -t ax8 /usr/local/svn/repos /trunk/README

               Subversion, a version control system.
               =====================================

$LastChangedDate: 2003-07-17 10:45:25 -0500 (Thu, 17 Jul 2003) $

Contents:

     I. A FEW POINTERS
    II. DOCUMENTATION
   III. PARTICIPATING IN THE SUBVERSION COMMUNITY
…
          

Name

svnlook changed — 顯示已更動的路徑.

摘要

svnlook changed REPOS_PATH

描述

顯示某個修訂版或異動中更動的路徑, 並在第一欄顯示 “svn 更新樣式” 的狀態碼: A 表示新增, D 為刪除, U 為更新 (更動).

選項

--revision (-r)
--transaction (-t)
          

範例

以下會顯示某個測試檔案庫的修訂版 39 中, 所有更動的檔案:

$ svnlook changed -r 39 /usr/local/svn/repos
A   trunk/vendors/deli/
A   trunk/vendors/deli/chips.txt
A   trunk/vendors/deli/sandwich.txt
A   trunk/vendors/deli/pickle.txt
          

Name

svnlook date — 顯示日期戳記.

摘要

svnlook date REPOS_PATH

描述

顯示檔案庫某個修訂版或異動的時間戳記.

選項

--revision (-r)
--transaction (-t)
          

範例

這會顯示某個測試檔案庫的修訂版 40 的日期.

            
$ svnlook date -r 40 /tmp/repos/
2003-02-22 17:44:49 -0600 (Sat, 22 Feb 2003)
          

Name

svnlook diff — 顯示更動檔案與性質的差異.

摘要

svnlook diff REPOS_PATH

描述

以 GNU 樣式, 顯示檔案庫中更動的檔案與性質差異.

選項

--revision (-r)
--transaction (-t)
--no-diff-deleted
            

範例

這會顯示一個新增的 (空的) 檔案, 一個被刪除檔案, 以及一個複製的檔案:

$ svnlook diff -r 40 /usr/local/svn/repos/
Copied: egg.txt (from rev 39, trunk/vendors/deli/pickle.txt)

Added: trunk/vendors/deli/soda.txt
==============================================================================

Modified: trunk/vendors/deli/sandwich.txt
==============================================================================
--- trunk/vendors/deli/sandwich.txt	(original)
+++ trunk/vendors/deli/sandwich.txt	2003-02-22 17:45:04.000000000 -0600
@@ -0,0 +1 @@
+Don't forget the mayo!

Deleted: trunk/vendors/deli/chips.txt
==============================================================================

Deleted: trunk/vendors/deli/pickle.txt
==============================================================================
          

Name

svnlook dirs-changed — 顯示本身曾更動過的目錄.

摘要

svnlook dirs-changed REPOS_PATH

描述

顯示本身曾更動過的目錄 (性質編輯), 或是其下的檔案更曾動過目錄.

選項

--revision (-r)
--transaction (-t)
          

範例

這會顯示我們範例檔案庫中, 在修訂版 40 中曾更動過的目錄:

$ svnlook dirs-changed -r 40 /usr/local/svn/repos
trunk/vendors/deli/
          

Name

svnlook help

摘要

亦為 svnlook -h 與 svnlook -?.

描述

顯示 svnlook 的求助訊息. 這個命令就像它的兄弟 svn help 一樣, 就算你不再與它連絡, 連上次聚會都忘了邀請它, 它還是你忠實的盟友.


Name

svnlook history — 顯示檔案庫中, 某個路徑 (如果沒有指定, 則為根目錄) 的歷史紀錄資訊

摘要

svnlook history REPOS_PATH 
            [PATH_IN_REPOS]

描述

顯示檔案庫中, 某個路徑 (如果沒有指定, 則為根目錄) 的歷史紀錄資訊

選項

--revision (-r)
--show-ids
          

範例

以下顯示我們的範例檔案庫中, 修訂版 15 的 /tags/1.0 路徑的歷史資訊輸出.

$ svnlook history -r 20 /usr/local/svn/repos /tags/1.0 --show-ids
REVISION   PATH <ID>
--------   ---------
      19   /tags/1.0 <1.2.12>
      17   /branches/1.0-rc2 <1.1.10>
      16   /branches/1.0-rc2 <1.1.x>
      14   /trunk <1.0.q>
      13   /trunk <1.0.o>
      11   /trunk <1.0.k>
       9   /trunk <1.0.g>
       8   /trunk <1.0.e>
       7   /trunk <1.0.b>
       6   /trunk <1.0.9>
       5   /trunk <1.0.7>
       4   /trunk <1.0.6>
       2   /trunk <1.0.3>
       1   /trunk <1.0.2>
          

Name

svnlook info — 顯示作者, 日期戳記, 紀錄訊息大小, 以及紀錄訊息.

摘要

svnlook info REPOS_PATH

描述

顯示作者, 日期戳記, 紀錄訊息大小, 以及紀錄訊息

選項

--revision (-r)
--transaction (-t)
          

範例

這會顯示我們範例檔案庫中, 修訂版 40 的資訊.

$ svnlook info -r 40 /usr/local/svn/repos
sally
2003-02-22 17:44:49 -0600 (Sat, 22 Feb 2003)
15
Rearrange lunch.
          

Name

svnlook log — 顯示紀錄訊息.

摘要

svnlook log REPOS_PATH

描述

顯示紀錄訊息

選項

--revision (-r)
--transaction (-t)
          

範例

這會顯示我們的範例檔案庫中, 修訂版 40 的紀錄訊息:

$ svnlook log /tmp/repos/
Rearrange lunch.
          

Name

svnlook proplist — 顯示納入版本控制的檔案與目錄的性質名稱與內容.

摘要

svnlook proplist REPOS_PATH PATH_IN_REPOS

描述

列出檔案庫中, 某個路徑的性質. 加上 -v 的話, 一併顯示性質內容.

選項

--revision (-r)
--transaction (-t)
--verbose (-v)
          

範例

這會顯示 HEAD 修訂版中, 設定給檔案 /trunk/README 的性質名稱:

$ svnlook proplist /usr/local/svn/repos /trunk/README
  original-author
  svn:mime-type
          

與前一個例子的命令相同, 但是這次一併顯示性質的內容:

$ svnlook proplist /usr/local/svn/repos /trunk/README
  original-author : fitz
  svn:mime-type : text/plain
          

Name

svnlook tree — 顯示檔案樹

摘要

svnlook tree REPOS_PATH [PATH_IN_REPOS]

描述

顯示檔案樹, 自 PATH_IN_REPOS 開始 (如果有提供的話, 不然就從根目錄開始), 外加選擇性的節點修訂版編號.

選項

--revision (-r)
--transaction (-t)
--show-ids
            

範例

這會顯示我們範例檔案庫中, 修訂版 40 的檔案樹 (連同節點編號一起):

$ svnlook tree -r 40 /usr/local/svn/repos --show-ids
/ <0.0.2j>
 trunk/ <p.0.2j>
  vendors/ <q.0.2j>
   deli/ <1g.0.2j>
    egg.txt <1i.e.2j>
    soda.txt <1k.0.2j>
    sandwich.txt <1j.0.2j>
            

Name

svnlook youngest — 顯示最年輕的修訂版號.

摘要

svnlook youngest REPOS_PATH

描述

顯示檔案庫的最年輕修訂版號.

範例

這會顯示我們示範檔案庫的最年輕修訂版號:

          
$ svnlook youngest /tmp/repos/ 
42
            


[37] 是, 是, 使用 --version 選項不必使用任何子命令, 不過等一下我們就會提到了.

Appendix A. 給 CVS 使用者的 Subversion 指引

本附錄是給剛開始接觸 Subversion 的 CVS 使用者的指引. 基本上, 就是 “從 10,000 呎來看” 兩個系統不同之處的列表. 如果可以的話, 我們會在每個章節放上相關章節的參照.

雖然 Subversion 的目標, 是接收現在與未來的 CVS 使用者基礎, 但是某些新的功能與設計變更還是需要的, 以修正 CVS 特有的 “不正確” 行為. 也就是說, 身為一個 CVS 使用者, 你可能需要戒除一些習慣— 一些你已經忘了, 在剛開始覺得很奇怪的事情.

不同的修訂版號

在 CVS 中, 修訂版號是每個檔案不同的. 這是因為 CVS 是根基在 RCS 之上; 每一個檔案在檔案庫都有對應的 RCS 檔案, 而檔案庫的結構, 大致上就是依計劃的目錄結構展開.

在 Subversion 中,檔案庫看起來就像一個檔案系統. 每一個送交動作, 會產生一個全新的檔案系統; 本質上來說,檔案庫就是一個檔案樹的陣列. 每一個這樣的檔案樹, 都以單一修訂版號標示出來. 當某個人說 “54 號修訂版” 時, 所指的就是某一個特定的檔案樹 (間接地來說, 就是第 54 次送交之後的檔案系統).

技術上來說, 講 foo.c 的 “5 號修訂版” 是不正確的, 應該說 “foo.c 在 5 號修訂版的狀態 ”. 另外, 在認定檔案的演進狀態時也要注意. 在 CVS 中, foo.c 的 5 號與 6 號修訂版是一定不同的. 在 Subversion 中, foo.c 在 5 號與 6 號修訂版, 非常有可能是 沒有 任何更動的.

欲更深入了解這個主題, 請參閱 the section called “修訂版本”.

目錄版本

Subversion 也會追蹤檔案樹結構, 而不光只是檔案內容而已. 這也是 Subversion 是用來取代 CVS 的最大原因.

以下是要讓身為前 CVS 使用者的你, 了解這代表什麼意義:

  • svn addsvn rm 命令可使用在目錄上, 就像使用在檔案一樣. svn copysvn move 亦同. 但是這些目錄 不會 馬上讓檔案庫有任何的變化. 相反地, 工作項目只是 “預定” 要被新增或刪除. 除非你執行 svn commit, 不然檔案庫不會有任何變動.

  • 目錄不再只是單純的收容物而已; 他們像檔案一樣, 也有修訂版號. (更適當地說, 講 “5 號修訂版裡的目錄 foo/ 是正確的”.)

讓我們對最後一點再多作說明. 目錄版本控管是個困難的問題; 因為我們允許混合修訂版的工作複本, 所以會有一些限制, 以防止我們破壞這樣的模型.

就理論觀點, 我們定義 “目錄 foo 的 5 號修訂版”, 表示特定的目錄項目與性質. 現在假設我們開始自 foo 中新增與刪除檔案, 然後送交更動. 說我們仍然有 5 號修訂版的 foo 是個謊言. 但是, 如果我們在送交後, 就直接增加 foo 的修訂版號, 這還是個謊言; 可能還有其它我們尚未取得的 foo 更動, 因為我們還沒進行更新.

Subversion 解決這個問題的方法, 就是悶不坑聲地在 .svn 區域裡新增與刪除項目. 當你終於執行 svn update 後, 所有需注意的東西, 都會在檔案庫裡塵埃落定, 而目錄的修訂版號也會被正確地設定. 因此, 只有在作過更新之後, 我們才能說你有一個 “正確無誤” 的目錄修訂版. 絕大多部份的時間, 你的工作複本會有 “不怎麼正確” 的目錄修訂版.

類似的情形, 如果你想要送交目錄的性質更動的話, 也會有問題產生. 正常的情況下, 送交動作會增加工作目錄的本地修訂版號. 但是這還是個謊言, 因為你還是可能會有這個目錄還沒有的新增與刪除, 因為你還沒有作過更新. 因此, 除非目錄是最新版的話, 你還是不能送交性質的更動.

欲了解有關於目錄版本控管的限制, 請參見 the section called “混合修訂版的限制”.

更多不需網路的動作

近幾年來, 磁碟空間愈來愈便宜, 愈來愈大, 但是網路頻寬並非如此. 因此, Subversion 的工作複本是針對這項珍貴的資源作最佳化.

.svnCVS 目錄一樣, 都是管理用的目錄, 但是他還多存放了檔案的 “原始未更動” 複本. 這讓你能夠離線進行許多事:

svn status

顯示你產生的本地更動 (請參見 the section called “svn status”)

svn diff

顯示詳細的更動細節 (請參見 the section called “svn diff”)

svn revert

移除你的本地更動 (請參見 the section called “svn revert”)

另外, 快取的未更動檔案, 可讓 Subversion 用戶端在送交時僅僅傳送差異即可, 這點是 CVS 作不到的.

列表最後的子命令是新的; 它不只是移除本地更動, 它還能取消像新增與刪除的預定動作. 這是個較適宜的復原檔案的方法; 執行 rm file; svn update 還是有用, 但是它會模糊更新動作的目的. And, while we're on this subject…

區分狀態與更新

在 Subversion 中, 我們試著要洗刷 cvs statuscvs update 命令之間的混淆不清.

cvs status 命令有兩個目的: 第一, 顯示使用者在工作複本中的本地更動, 第二, 顯示使用者的過時檔案. 很不幸地, 由於 CVS 顯示的狀態不易理解, 許多 CVS 的使用者完全無法善用這個命令. 取而代之地, 他們發展出一個習慣, 就是執行 cvs up 來看他們的更動. 當然囉, 它有副作用, 就是合併你尚未準備要處理的檔案庫的更動.

就 Subversion 來說, 我們試著讓 svn status 輸出的資料易於讓人與剖析器理解, 來解決這個不清楚的地方. 另外, svn update 只會顯示被更新的檔案資訊, 而 不會 顯示本地的更動.

以下是 svn status 的快速指引. 我們鼓勵所有的 Subversion 使用者儘早使用, 多多使用:

svn status 顯示所有有本地更動的檔案; 預設不會使用到網路.

-u

增加取自檔案庫的是否過時的資料.

-v

顯示 所有 納入版本控制的項目.

-N

非遞迴式的.

status 命令有兩種輸出格式. 預設是 “簡短” 格式, 本地更動看起來像這樣:

% svn status
M     ./foo.c
M     ./bar/baz.c
    

如果你指定了 --show-update (-u) 選項, 會使用較長的輸出格式:

% svn status -u
M             1047    ./foo.c
       *      1045    ./faces.html
       *         -    ./bloo.png
M             1050    ./bar/baz.c
Head revision:   1066
    

在這個例子中, 出現了兩個新的欄位. 如果檔案或目錄是過時的話, 第二欄會有星號. 第三個欄位顯示該項目在工作複本的修訂版號. 在上面的例子中, 星號表示更新會讓 faces.html 取得更動修補, 而 bloo.png 是一個檔案庫新增的檔案. (在 bloo.png 旁的 -, 表示它尚未出現在工作複本中.)

最後, 以下是一份簡短的摘要, 說明你可能最常會看到的狀態碼:

A    新增
D    刪除
R    取代  (刪除, 然後重新加入)
M    本地更動
U    已更新
G    被合併
C    衝突
X    外部

A    預計要新增的資源
D    預計要刪除的資源
M    有本地更動的資源
C    有衝突的資源 (檔案庫與工作複本之間的更動無法完全地合併)
X    本工作複本的外部資源 (來自其它的檔案庫. 請參見
      the section called “svn:externals”)
?    未納入版本控制的資源
!    遺失或不完全的資源 (被 Subversion 以外的工作移除)
    

Subversion 將 CVS 的 PU 結合成一個 U. 當合併或衝突發生時, Subversion 只會顯示 GC, 而不是冗長的一句話.

欲了解更多有關 svn status 的詳細討論, 請參照 the section called “svn status”.

分支與標記

Subversion 不會區分檔案系統空間與 “分支” 空間的差別; 分支與標記都只是檔案系統裡的普通目錄而已. 這也許是 CVS 使用者所需跨越的最大心理障礙. 請參閱 Chapter 4, 分支與合併 以了解詳情.

中介資料性質

Subversion 的一個新功能, 就是你可以將任何的中介資料, 附加到檔案與目錄上. 我們偏好稱此資料為 性質, 而它們可以想成是附加到工作複本中, 隨意的名稱/數值對的集合.

要設定或取得性質的名稱, 可使用 svn propsetsvn propget 子命令. 要列出一個物件上所有的性質, 可使用 svn proplist.

欲取得更多的資訊, 請參見 the section called “性質”.

衝突排解

CVS 會在檔案內放置 “衝突標記”, 將衝突地方標示出來, 在更新時顯示一個 C 代碼. 就以前的記錄來看, 這導致很多的問題, 因為 CVS 作的並不夠. 許多使用者忘了 (或沒看到) 在終端機上快速閃掉的 C 代碼. 使用者也常常忘了有衝突標記的存在, 然後就不小心將含有衝突標記的檔案送交回去.

Subversion 解決這個問題的方法, 是讓衝突更明確地表示出來. 它會記得檔案是處於衝突的狀態中, 除非你執行了 svn resolved, 它不會允許你送交更動. 請參見 the section called “解決衝突 (合併他人的更動)” 以了解更多細節.

二進制檔案與轉換

一般而言, Subversion 比 CVS 更能優雅地處理二進制檔案. 因為 CVS 使用 RCS 的關係, 對於一個變動中的二進檔案, 它只能將每個更動的複本都儲存下來. 但是 Subversion 不管檔案是文字或是二進制類型, 在內部都是以二進制差異比較演算法來表示檔案的差異. 這表示所有的檔案在檔案庫中, 都是以 (壓縮的) 差異來儲存的, 而且在網路上傳輸的, 都是較小的檔案差異而已.

CVS 使用者必須以 -kb 標記二進制檔案, 以避免資料被搞爛 (因為關鍵字展開, 以及列尾符號轉換的關係). 他們有時候會忘了作這件事.

Subversion 採行比較瘋狂的行徑: 首先, 它絕不進行任何的關鍵字或列尾符號轉換, 除非你要求它這麼作 (請參見 the section called “svn:keywords”the section called “svn:eol-style”). Subversion 預設會將所有的資料視為字面位元組字串, 而檔案都會以未轉換的狀態, 儲存在檔案庫中.

再者, Subversion 內部會維護記錄檔案是否為 “文字” 或 “二進制” 資料的記錄, 但是這項記錄 只會 存在於工作複本中. 在執行 svn update 的過程中, Subversion 會對本地的文字檔案進行內容合併, 但是不會對二進制檔案作這樣的事.

要決定內容合併是否可行, Subversion 會檢視 svn:mime-type 性質. 如果檔案沒有 svn:mime-type 性質, 或是有一個文字的 mime 類型 (例, text/*), Subversion 就會假設它是文字. 不然的話, Subversion 會假設它是二進制的. 在 svn importsvn add 中, Subversion 會進行二進制偵測演算法來幫助使用者. 這些命令會產生一個不錯的猜測, 然後 (可能) 對要加入的檔案設定二進制的 svn:mime-type 性質. (如果 Subversion 猜錯了, 使用可以自行移除該性質, 或是手動移除它.)

Versioned Modules

Unlike CVS, a Subversion working copy is aware that it has checked out a module. That means that if somebody changes the definition of a module, then a call to svn update will update the working copy appropriately.

Subversion defines modules as a list of directories within a directory property: see the section called “外部定義”.

Appendix B. 匯入 CVS 檔案庫

Table of Contents

需求
執行 cvs2svn.py

由於 Subversion 是設計成為 CVS 的繼任者, 只有提供匯入的工具才有意義. Subversion 有命令稿可以將 CVS 檔案庫匯入至 Subversion 檔案庫. 是的, 你 可以 帶著 CVS 的歷史一起進入美麗新世界.

需求

這工具稱為 cvs2svn.py, 它是個 Python 命令稿, 位於 Subversion 源碼樹的 tools 子目錄裡. 要執行這個程式, 你需要一些外部的東西:

python 2.0

確定你有安裝 python 2.0 或更新版本. 你可以從 http://www.python.org/ 取得最新版.

rcsparse.py

這是個用來剖析 RCS 檔案的 python 模組, 也是 ViewCVS 專案的一部份. 我們需要它來讀取你的 CVS 模組. 為了方便起見, 一份複本置於 cvs2svn.py 相同目錄中, 不過更即時的版本可以從 ViewCVS 專案取得 — http://viewcvs.sf.net/.. 只要把這個模組放在 python 可以找得到的地方即可, 像是 /usr/local/lib/python2.2/.

執行 cvs2svn.py

由於 CVS 沒有不可分割的送交, cvs2svn.py 很難將其推衍出來. 它是藉由檢視 RCS 檔案, 然後找出每一個檔案修訂版的相同送交訊息. 如果兩個 RCS 修訂版有著相同的送交訊息, 又差不多在相同的時間送交 (差不多彼此相差幾分鐘的時間), cvs2svn.py 會將它們置於一個共同的更動 “群組”, 然後將送交群組以單一修訂版送交至新的 Subversion 檔案庫.

前面的解釋有點簡化; 它要記錄的東西比那還要多得多. 事實上, cvs2svn.py 以許多不同的 “階段” 在磁碟上建立大量的暫存資料, 來進行它的工作. 如果你中斷該命令稿, 你可以稍後傳遞 <options>-p</options> 選項給命令稿, 表示它應該從哪一個階段繼續下去.

執行該命令稿很簡單:

$ svnadmin create /new/svn/repos
$ cvs2svn.py -s /new/svn/repos /cvs/repos
…
    

轉換可能要花數分鐘, 到十幾個小時不等, 完全視你的 CVS 檔案庫大小而定. 對 Subversion 的 CVS 檔案庫執行時 (Subversion 第一年的歷史, 當時還無法以自已來管理), 花了 30 分鐘, 送交了大概 3000 個修訂版到 Subversion 檔案庫.

Appendix C. 故障排除

常見問題

在安裝與使用 Subversion 時, 你可能會遇到一些問題. 在你熟悉 Subversion 如何運作之後, 有些問題就會解決, 而其它問題發生的原因, 是因為你習於別種版本控制系統運作的關係. 還有因為某些 Subversion 所執行的作業系統的關係, 有一些問題是無法解決的 (思考一下 Subversion 可以執行的作業系統有多少, 有趣的是, 有許多是我們沒用過的).

以下的列表, 是使用 Subversion 數年經驗中所編纂的, 它們涵蓋的範圍, 就是你在 Subversion 經常會遇到的問題 — 編譯, 安裝, 使用. 如果你無法在這裡找到你遇到的問題, 或是已經試過了所有我們給的建議, 但是徒勞無功的話, 請寄電子郵件至 , 並且詳加描述你的問題 [38]

使用 Subversion 的問題

  • 每當我想要存取檔案庫的時候, svn 就會停在那裡.

  • 當我想要執行 svn 時, 它就說我的工作複本被鎖定了.

  • 尋找或開啟檔案庫時有錯誤發生, 但是我確定我的檔案庫 URL 是正確的.

  • 我要如何在 file:// URL 中指定 Windows 的磁碟機代號?

  • 我沒有辦法經由網路寫入資料至 Subversion 檔案庫.

  • 在 Windows XP 中, Subversion 伺服器有時會送出損壞的資料.

  • 要在 Subversion 用戶端與伺服器進行網路傳輸的檢查, 最好的方法是什麼?

編譯 Subversion 的問題

  • 我把執行檔編譯好了, 但是當我想要取出 Subversion 時, 我得到 “Unrecognized URL scheme” 的錯誤.

  • 當我執行 configure, 我得到像 subs-1.sed line 38: Unterminated `s' command 的錯誤.

  • 我無法在 Windows 以 MSVC++ 6.0 編譯 Subversion.

使用 Subversion 的問題

每當我想要存取檔案庫時, 我的 Subversion 用戶端會停在那裡.

其它想要存取檔案庫的用戶端就會直接卡住, 等鎖定檔消失. 要叫醒你的檔案庫, 你需要告訴 Berkeley DB 結束該項工作, 或是把資料庫回復到先前已知正確的狀態. 要達到這個目的, 從含有 Subversion 檔案庫的機器上執行以下的命令:

          
$ svnadmin recover /path/to/repos
          

在作這件事之前, 請確定你把所有檔案庫的存取方式都關掉 (關掉 httpd 或 svnserve). 確定你以擁有與管理該資料庫的使用者來執行這個命令, 而且也不要以 root 來進行, 不然這樣會在 db/ 目錄裡留下 root 擁有的檔案, 這樣會讓非 root 的使用者無法開啟, 這表示是你, 或是 Apache 的 httpd 行程.

當我想要執行 svn 時, 它就說我的工作複本被鎖定了.

Subversion 的工作複本就像 Berkeley DB 一樣, 使用日誌機制來進行所有的工作. 也就是說, 它會在進行工作之前, 先把它紀錄下來. 如果 svn 在進行工作時被中斷, 那會留下一個或多個鎖定檔案, 以及描述尚未結束的檔案. (svn status 會在被鎖定的目錄旁邊顯示 L).

其它想要存取工作複本的行為在遇到鎖定時, 就會失敗. 要叫醒你的工作複本, 你需要叫 svn 用戶端來結束該項工作. 要修正這個問題, 請從工作複本最上層的目錄執行這個命令:

$ svn cleanup working-copy
          

我要如何在 file:// URL 中指定 Windows 的磁碟機代號?

請參見 檔案庫 URL.

我沒有辦法經由網路寫入資料至 Subversion 檔案庫.

如果以本地存取進行匯入是沒有問題的話:

$ mkdir test
$ touch test/testfile
$ svn import test file:///var/svn/test -m "Initial import"
Adding         test/testfile
Transmitting file data .
Committed revision 1.
          

但是無法經由達端主機:

$ svn import test http://svn.red-bean.com/test -m "Initial import"
harry's password: xxxxxxx

svn_error: #21110 : 

The specified activity does not exist.
          

我們看過這個問題, 它發生在 REPOS/dav/ 目錄無法被 httpd 行程寫入時. 請檢查檔案權限, 確定 Apache httpd 可以寫入 dav/ 目錄 (當然還有對應的 db/ 目錄).

在 Windows XP 中, Subversion 伺服器有時會送出損壞的資料.

你需要安裝 Window XP Service Pack 1. 你可以在 http://support.microsoft.com/default.aspx?scid=kb;EN-US;q317949 取得所有有關該 Service Pack 的資訊.

要在 Subversion 用戶端與伺服器進行網路傳輸的檢查, 最好的方法是什麼?

Use Ethereal to eavesdrop on the conversation:

使用 Ethereal 來偷聽對話:

Note

以下的指令是特別針對 Ethereal 的圖形介面, 可能無法套用在命令列版本上 (其二進制程式通常名為 tethereal).

  • 拉下 Capture 目錄, 選擇 Start.

  • 在 Filter 輸入 port 80, 然後關掉 promiscuous 模式.

  • 執行你的 Subversion 用戶端.

  • 按下 Stop. 現在你就有了封包擷取. 它看起來是一大串的文字列.

  • 按下 Protocol 欄位, 對其排序.

  • 然後, 點選第一個 TCP 列.

  • 按右鍵, 然後選擇 Follow TCP Stream. 你會得到 Subversion 用戶端的 HTTP 對話的要求回應對.

另外, 你也可以在伺服器設定檔中設定 http-debug 參數, 讓你執行 svn 用戶端時, 會顯示 neon 的除蟲訊息. neon-debug 的數值是 ne_utils.h 標頭檔的 NE_DBG_* 數值的組合. 設定 http-debug 為 130 (意即 NE_DEBUG_HTTP + NE_DBG_HTTPBODY) 會讓 HTTP 資料也顯示出來.

你可能在進行網路傳輸檢查時, 需要關閉壓縮的功能. 請參考 config 設定檔中的 compression 參數.

編譯 Subversion 的問題

我把執行檔編輯好了, 但是當我想要取出 Subversion 時, 我得到 “Unrecognized URL scheme.” 的錯誤.

Subversion 使用外掛系統來存取檔案庫. 目前有三個這樣的外掛: ra_local 可以存取本地檔案庫, ra_dav 可以透過 WebDAV 存取檔案庫, 而 ra_svn 可以透過 svnserve 伺服器來進行本地或遠端的存取. 當你想要在 Subversion 進行一個作業時, 用戶端會試著依 URL schema 動態載入一個外掛. file:// URL 會試著載入 ra_local, 而 http:// URL 會試著輸入 ra_dav, 以此類推.

你看到的這個錯誤, 表示動態連結器/載入器無法找到要載入的外掛. 這個發生的原因, 通常是因為你以共享程式庫的方式編譯 Subversion, 但是還沒有執行 make install 就要執行它. 另一個可能就是你執行了 make install, 但是程式庫把它存在動態連結器/載入器不認得的地方. 在 Linux 下, 你可以把那個程式庫目錄加進 /etc/ld.so.conf, 然後執行 ldconfig, 讓連結器/載入器可以找到程式庫. 如果你不想這麼作, 或是你沒有 root 存取權限, 你可以在 LD_LIBRARY_PATH 環境變數指定該程式庫目錄.

當我執行 configure, 我得到像 subs-1.sed line 38: Unterminated `s' command 的錯誤.

你可能在系統上有 /usr/local/bin/apr-config/usr/local/bin/apu-config 舊的複本. 移除它們, 確定你正在編譯的 apr/apr-util/ 是最新的版本, 然後再試一次.

我無法在 Windows 以 MSVC++ 6.0 來編譯 Subversion.

你必須取得最新的作業系統 SDK. 隨 VC++ 6.0 附的並不夠新.



[38] 請記住, 你所提供的設定與問題的詳細程度, 與從郵件論壇得到回答的機率成正比. 我們鼓勵你附上所有的資訊, 除了早餐吃什麼和令堂閨名.

Appendix D. WebDAV 與自動版本

WebDAV 是 HTTP 的擴充語法, 愈來愈多人把它拿來當作檔案分享的標準. 今日的作業系統愈來注重在 Web 功能, 很多現在都有內建支援掛載 WebDAV 伺服器匯出的 “分享點”.

如果你使用 Apache/mod_dav_svn 作為 Subversion 的網路伺服器, 就某種程度上來說, 你就在執行 WebDAV 伺服器. 這篇附錄對本通訊協定提供了一些基本背景資料, Subversion 如何使用它, 以及 Subversion 如何與其它使用 WebDAV 軟體一起協同工作.

基本 WebDAV 概念

本節對 WebDAV 的背景提供了很簡單的基本概念. 它對了解 WebDAV 用戶端與伺服器之間的相容課題提供了基礎知識.

簡易 WebDAV

RFC 2518 定義了對 HTTP 1.1 的概念與擴充功能, 讓網站能夠變成更統一的讀寫媒介. 背後的基本概念, 是一個了解 WebDAV 的網站伺服器可以當作一個通用的檔案伺服器; 用戶端可以掛載 WebDAV “分享”, 就像 NFS 或 SMB 分享一樣.

但是我們必須注意, 在 RFC 2518 之中, 除了 DAV 裡的 “V” 以外, 它 並未 提供任何形式的版本控制模型. 基本 WebDAV 用戶端與伺服器都假設每個目錄或檔案都只有一個版本存在而已, 但是可以一直被蓋寫過去. [39]

以下為基本 WebDAV 所提出的概念與方法:

新的寫入方法

除了標準的 HTTP PUT 方法 (它會建立或覆寫一個網頁資源), WebDAV 還定義了新的 COPYMOVE 方法, 用以複製與重新安排資源.

集合

這只是 WebDAV 的術語, 用來將資源 (URI) 群組在一起. 在大多數的情況下, 這就類似像 “檔案目錄” 一樣. 你可以藉由結尾的 “/”, 來辨別它是不是一個群組. 檔案資源可以藉由 PUT 方法對其蓋寫或建立, 不過集合資源則以 MKCOL 方法來建立.

性質

這與 Subversion 中的作法是一樣的 — 連繫至集合與檔案的描述資料. 用戶端可以利用新的 PROPFIND 方法列出或取得連繫至某個資源的性質, 也可以利用 PROPPATCH 方法來變更. 某些性質完全是被使用者控制的 (像是被稱為 “color” 的性質), 而有些則是完全被 WebDAV 伺服器建立與控制的 (像是存有最近一次更動檔案的時間). 前者被稱為 “dead” 性質, 而後者被稱為 “live” 性質.

鎖定

一個 WebDAV 伺服器可能會提供鎖定功能供用戶端使用 — 這個部份的規格是選用的, 不過大部份的 WebDAV 伺服器都會提供這樣的功能. 如果有的話, 用戶端可以使用新的 LOCkUNLOCK 方法, 來協調對某資源的存取. 在大多數的情況下, 這些方法都是用來建立獨佔式寫入鎖定 (像是 the section called “鎖定-修改-解鎖的解決方案” 所討論的情況), 不過也有可能是分享式寫入鎖定.

DeltaV 擴充

由於 RFC 2518 並未提及版本控制的概念, 另一個有能力的團體就寫出了 RFC 3256, 它對 WebDAV 加上了版本控制. WebDAV/DeltaV 用戶端與伺服器通常只被稱為 “DeltaV” 用戶端與伺服器, 這是因為 DeltaV 就已隱含了基本 WebDAV 的存在.

DeltaV 又引進了一狗票的縮寫, 不過別害怕, 它們都滿簡單的. 以下是 DeltaV 引進的新概念與方法:

各資源的版本控制

如同 CVS 與其它的版本控制系統一樣, DeltaV 假設每一項資源都有可能有無限數量的狀態. 用戶端一開始以新的 VERSION-CONTROL 方法, 將一個資源置於版本控制下. 這會建立一個新的版本控制資源 (Version Controlled Resource, VCR) 每一次你更動 VCR (透過 PUT , PROPPATCH 等等), 該資源就會建立一個新狀態, 稱為版本資源 (Version Resource, VR). VCR 與 VR 仍然是一般的網頁資源, 藉由 URL 來使用. 某些特定的 VR 也可以擁有人類易用的名稱.

伺服端的工作複本模型

有些 DeltaV 伺服器允許在伺服器上建立一個虛擬 “工作空間”, 你的所有工作都可以在那裡進行. 用戶端使用 MKWORKSPACE 建立一個私有區域, 藉由 “取出特定 VCR” 至工作空間, 表示想要更改它們, 對其編輯, 然後再 “存入它們”. 以 HTTP 的術語來說, 這些方法的順序為 CHECKOUT, PUT, CHECKIN. 在每一個 CHECKIN 之後, 新的 VR 會被建立, 被編輯的 VCR 的內容會 “指向” 最新的 VR. 每一個 VR 也會有一個 “歷史” 資源, 它是用來追蹤並記錄各個 VR 狀態.

用戶端工作複本模型

有些 DeltaV 伺服器也支援用戶端可對特定的 VR 擁有自己的工作複本. (這就是 CVS 與 Subversion 運作的方法.) 當用戶端想要送交更動至伺服器時, 它一開始會先以MKACTIVITY 方法, 建立一個暫時的伺服器異動 (稱為 activity). 接著, 用戶端會對每一個打算更動的 VR 進行 CHECKOUT, 它會在 activity 中建立數個暫時的 “工作資源”, 然後可以利用 PUTPROPPATCH 方法進行修改. 最後, 用戶端會對每一個工作資源進行 CHECKIN, 這會在每一個 VCR 中建立一個新的 VR, 然後整個 activity 就會被刪除.

配置

DeltaV 允許你定義包含 VCR 的彈性配置, 稱為 “配置”, 它並不一定必須對應到某些特定的目錄. 每一個 VCR 的內容可以透過 UPDATE 方法, 對應到特定的 VR. 只要配置沒有問題的話, 用戶端可以建立整個配置的 “快照”, 稱為 “baseline” 用戶端使用 CHECKOUTCHECKIN 方法以取得某項配置的狀態, 與使用這些方法來建立特定 VCR 的 VR 狀態是一樣的.

擴充性

DeltaV 定義了一個新的方法 REPORT, 它可以讓用戶端與伺服器進行自訂的資料交換. 用戶端送出 REPORT 要求, 以及包含許多自訂資料的 properly-labeled XML 訊息; 假設伺服器能夠了解該特定的報告類型, 它會以相同的自訂 XML 訊息回應. 這個技巧與 XML-RPC 是非常相似的.

自動版本

對許多人而言, 這是 DeltaV 的 “必殺” 功能. 如果 DeltaV 伺服器支援這個功能的話, 那個基本的 WebDAV 用戶端 (也就是沒有版本控制) 還是可以對伺服器寫入資料, 伺服器就會安安靜靜地進行版本控制. 以最簡單的例子來說, 由基本 WebDAV 用戶端送出的普通 PUT 命令, 可能會被這樣的伺服器解釋為 CHECKOUT, PUT, CHECKIN.

Subversion 與 DeltaV

那麼, Subversion 與其它的 DeltaV 軟體有多 “相容” 呢? 簡單地說, 不很高. 至少 Subversion 1.0 如此.

雖然 libsvn_ra_dav 會對伺服器送出 DeltaV 要求, Subversion 用戶端並 不是 通用的 DeltaV 用戶端. 事實上, 它預期伺服器會有某些自訂的功能 (尤其是自訂的 REPORT 要求). 另外, mod_dav_svn 並不是 一個通用的 DeltaV 伺服器. 它只有實作 DeltaV 規格的一小部份而已. 更通用的 WebDAV 或 DeltaV 用戶端可能更能夠與它工作, 但是僅限於該用戶端是在伺服器的有限實作功能之內運作的. Subversion 發展團隊計畫在未來的 Subversion 版本中, 會特別著重在一般 WebDAV 的通用性.

將 Subversion 對映至 DeltaV

以下是 Subversion 用戶端的動作如何使用 DeltaV 的 “高階” 描述. 在許多情況下, 這些解釋只是過份簡化的粗略說明. 它們 應該被視為閱讀 Subversion 源碼, 或是與發展人員交換意見的替代品.

svn checkout/list

對指定集合進行深度為 1 的 PROPFIND 動作, 以取得其下的子項. 對每一個子項進行 GET (也許還有 PROPFIND). 會遞迴進入集合再繼續前述動作.

svn commit

MKACTIVITY 建立 activity, 再對每一個更動的項目進行 CHECKOUT, 接著對新資料進行 PUT. 最後, 一個 MERGE 要求, 會導致對所有工作資源作一個隱含的 CHECKIN.

svn update/switch/status/merge/diff

送出一個自訂的 REPORT 要求, 它會描述工作複本的混合修訂版 (還有混合 URL) 的狀態. 伺服器會送出自訂回應, 說明哪些項目應該要更新. 用戶端會檢查每一個回應, 依需要進行 GETPROPFIND. 對 update 與 switch 動作, 會將新資料安裝於工作複本中. 對 diff 與 mere 命令, 比較資料與工作複本, 也許會套用更動為本地更動.

自動版本支援

在寫作之時, 實際上並沒有多少 DeltaV 用戶端存在; RFC 3256 還是很新的. 不過使用者還是可以取得 “通用” 的用戶端, 因為幾乎所有現代作業系統都有一個整合的基本 WebDAV 用戶端. 了解到這一點, Subversion 發展人員發現, 如果 Subversion 1.0 要有 任何 互通功能的話, 支援 DeltaV 的自動版本功能是最好的策略.

要開啟 mod_dav_svn 的自動版本功能, 請在 httpd.conf Location 區塊裡使用 SVNAutoversioning, 像這樣:

<Location /repos>
  DAV svn
  SVNPath /absolute/path/to/repository
  SVNAutoversioning on
</Location>

由於許多作業系統已經整合了 WebDAV 功能, 這個功能的使用情況, 感覺就像天方夜譚一樣: 想像一間辦公室, 其中的有使用微軟 Windows 或 Mac OS 的一般使用者. 每一台電腦都 “掛載” Subversion 的檔案庫, 它會變成一般的網路分享. 然後就像平常一樣使用這台伺服器: 從伺服器開啟檔案, 進行編輯, 然後把它們存回伺服器. 但是在這個幻想中, 伺服器會自動地對任何東西進行版本控制. 然後系統管理員就可以使用 Subversion 用戶端, 尋找並取得所有舊的版本.

這個幻想是真的嗎? 並不完全是. 最大的阻礙是 Subversion 1.0 並不支援 WebDAV 的 LOCKUNLOCK 方法. 大多數作業系統的 DAV 用戶端, 都會試著對一個直接從 DAV 掛載網路分享開啟的資源進行 LOCK. 自此, 使用者可能得從 DAV 分享複製檔案至本地磁碟, 編輯檔案, 然後再把它複製回去. 不是個很理想的自動版本, 不過還是可行的.

mod_dav_lock 的替代品

mod_dav Apache 模組是很複雜的怪獸: 它能夠瞭解, 並且剖析所有 WebDAV 與 DeltaV 的方法, 但是它還是要依靠後端的 “provider” 來存取資源.

舉個最簡單的例子, 使用者可以使用 mod_dav_fs 作為 mod_dav 的 provider. mod_dav_fs 使用一般的檔案系統來儲存目錄與檔案, 而且也只認得普通的 WebDAV, 而非 DeltaV.

但是 Subversion 則是使用 mod_dav_svn 作為 mod_dav 的 provider. mod_dav_svn 瞭解 LOCK 以外的方法, 而且也瞭解大半的 DeltaV 方法. 它會存取 Subversion 檔案庫中的資料, 而不是直接位於真實檔案系統的資料. Subversion 1.0 不支援鎖定, 這是因為 Subversion 使用複製-修改-合併模式, 實作上相當困難. [40]

在 Apache httpd-2.0 中, mod_dav 支援 LOCK 的方法, 是藉由在私有資料庫中追蹤鎖定, 它假設 provider 可以接受這樣的要求. 但是在 Apache httpd-2.1 或其後的版本, 這個鎖定支援就分離至一個獨立的 mod_dav_lock 模組中. 它允許任何的 mod_dav provider 善用鎖定資料庫, 包括 mod_dav_svn 在內, 即使 mod_dav_svn 並不真的瞭解鎖定.

搞糊塗了沒?

簡單地講, 你可以在 Aapche httpd-2.1 (或其後的版本) 使用 mod_dav_lock, 製造 mod_dav_svn 能夠處理 LOCK 要求的 假象. 請確定 mod_dav_lock 被編譯在 httpd, 或是在 httpd.conf 載入. 那麼只要在你的 Location 中加入 DAVGenericLockDB 指令, 像這樣:

<Location /repos>
  DAV svn
  SVNPath /absolute/path/to/repository
  SVNAutoversioning on
  DavGenericLockDB /path/to/store/locks
</Location>

這個技巧是危險的交易: 某種方面來說, mod_dav_svn 是在對 WebDAV 用戶端扯謊. 它宣稱可以接受 LOCK 要求, 但是實際上, 並不是所有的階段都有鎖定. 如果有第二個 WebDAV 用戶端試著對要同一個資源進行 LOCK 操作, 那麼 mod_dav_lock 會發現, 然後正確地否決該要求. 但是即使如此, 還是擋不住以普通的 Subversion 用戶端執行正常的 svn commit 而進行的檔案更動! 如果你使用這樣的技巧, 使用者就很有可能互相蓋寫彼此的更動. 特別的是 WebDAV 用戶端很有可能不小心蓋寫普通 svn 用戶端所送交的更動.

另一方面, 如果你很小心地設定你的環境, 這樣的風險是能夠降低的. 舉個例子, 如果 所有 的用戶都透過基本的 WebDAV 用戶端 (而不是 svn 用戶端), 那麼就不會有什麼問題.

自動版本互通性

在本節中, 我們會描述 (寫作此時) 最常見的通用 WebDAV 用戶端, 還有它們與使用 SVNAutoversioning 指令的 mod_dav_svn 伺服器的互通性. RFC 2518 有點大 ,也許也有點太過於彈性了. 每一個 WebDAV 用戶端的行為都有點不一樣, 也有各自不同的問題.

Win32 網路資料夾

Windows 98, 2000 與 XP 都有整合的 WebDAV 用戶端, 稱為 “網路資料夾”. 在 Windows 98 上, 這個功能需要另外安裝; 如果有的話, 在我的電腦中, 會有一個 “網路資料夾” 目錄出現. 在 Windows 2000 與 XP 上, 只要開啟網路芳鄰, 然後執行新增網路位置圖示即可. 在提示輸入時, 鍵入 WebDAV 的網址. 分享出去的資料夾會出現在網路芳鄰中.

對自動版本的 mod_dav_svn 伺服器進行寫入可以運作地很好, 不過有幾個問題:

  • 如果電腦是 NT 網域的一員, 那麼就似乎無法連上 WebDAV 分享. 它要不停地要求使用者名稱與密碼, 就算 Apache 伺服器並未送出認證要求! 有些人指出, 這個問題發生的原因, 可能是因為網路資料夾是設計來與 Microsoft 的 SharePoint DAV 伺服器一起運作的. 如果機器不是 NT 網域的一部份, 那麼掛載該分享就不會有任何的問題. 這個神祕事件尚未解決.

  • 一個檔案無法直接由分享進行直接編輯; 它都會以唯讀的方式開啟. mod_dav_lock 的技巧無法提供任何幫助, 因為網路資料夾完全不使用 LOCK 方法. 不過原先提到的 “複製, 編輯, 重新複製” 方法倒是可以使用. 分享上的檔案, 可以被本地編輯複本蓋寫.

Mac OS X

Apple 的 OS X 作業系統有個整合的 WebDAV 用戶端. 從 Finder 中, 從 Go 選單選取 “Connect to Server” 項目. 輸入 WebDAV 網址, 然後它會以磁碟出現在桌面上, 就跟其它的檔案伺服器一樣. [41]

很不幸地, 這個用戶端無法與自動版本 mod_dav_svn 工作, 因為它缺少 LOCK 的支援. Mac OS X 會在一開始的 HTTP OPTIONS 功能交換時, 發現沒有 LOCK 功能, 因此決定將 Subversion 檔案庫以唯讀分享掛載起來. 之後, 就完全無法進行寫入作業. 要將檔案庫以讀寫分享掛載的話, 你 必須 使用前面討厭的 mod_dav_lock 技巧. 當鎖定能夠工作之後, 該分享就能正常地工作: 檔案可以直接以讀寫模式開啟, 只是每一次寫入的作業, 會讓用戶端以 PUT 移到暫存位置, DELETE 刪除原來的檔案, 然後 MOVE 將暫存資源移回原來的檔案名稱. 每一次存檔, 就是三個新的 Subversion 修訂版!

還有一件事要注意: OS X 的 WebDAV 用戶端對 HTTP 的重導向太過敏感. 如果你無法掛載檔案庫, 你可能需要在你的 httpd.conf 中, 加上 BrowserMatch 指令:

BrowserMatch "^WebDAVFS/1.[012]" redirect-carefully

Unix: Nautilus 2

Nautilus 是 GNOME 桌面環境的官方檔案管理員/瀏覽器, 其網頁位於 http://www.gnome.org/projects/nautilus/. 只要將 WebDAV 網址輸入 Nautilus 視窗, DAV 分享就會以本地檔案系統出現.

一般來說, Nautilus 2 與自動版本 mod_dav_svn 可以合作無間, 不過有以下的例外:

  • 任何直接從該分享開啟的檔案, 都會被視為唯讀. 即使是 mod_dav_lock 技巧也發揮不了作用. 感覺上, 似乎 Nautilus 完全不會送出 LOCK 方法. 不過 “本地複製, 編輯, 複製回去” 技巧還是可以工作. 很不幸地, Nautilus 會先送出一個 DELETE 方法, 這樣會產生一個額外的修訂版.

  • 在覆寫或建立一個檔案時, Nautilus 首先會對一個空檔案作 PUT, 然後再以第二個 PUT 蓋寫它. 這會建立兩個 Subversion 檔案系統修訂版, 而不是一個.

  • 在刪除一個集合時, 它會對每一個子項目發出 HTTP DELETE, 而不是對該集合本身. 這樣會產生一大堆的新修訂版.

Linux davfs2

Linux davfs2 是供 Linux 核心使月的檔案系統模組, 其發展位於 http://dav.sourceforge.net/. 安裝之後, 就可以使用 Linux 的 mount 命令來掛載 WebDAV 網路分享.

有謠言說這個 DAV 用戶端完全無法與 mod_dav_svn 的自動版本工作. 在每一次嘗試對伺服器寫入之前, 都會先發出 LOCK 要求, 而這是 mod_dav_svn 不支援的. 到目前為止, 還沒有任何資料顯示使用 mod_dav_lock 能夠解決這個問題.



[39] 因為這個理由, 有些人戲稱普通的 WebDAV 用戶端為 “WebDA” 用戶端!

[40] 也許哪天 Subversion 會發展出可以與複製-修改-合併共存的 reserved-checkout 鎖定模型, 但是不會馬上成真.

[41] Unix 使用者也可以執行 mount -t webdav URL /mountpoint.

Appendix E. 其它 Subversion 用戶端

Table of Contents

Out of One, Many

Out of One, Many

Subversion 命令列用戶端 svn 是正式 [42] 的 Subversion 用戶端實作程式. 很幸運地, 為了那些有興趣發展 Subversion 用戶端的人們, Subversion 實作成一系列的程式庫. 這些程式庫除可過透過 C API 存取外, 還可被其它語言使用 (參見 the section called “Using Languages Other than C and C++”).

這樣的元件設計, 表示它很容易 (呃, 至少 比較容易) 利用這些程式庫來寫出用戶端. 所得到的結果, 就是在 1.0 之前, 有了許多為 Subversion 所用的 GUI 用戶端, 每一個目前都在不同的發展階段.

Table E.1. Subversion 的圖形用戶端

名稱語言可移植性授權網址
RapidSVNC++是, 原生 widgetApache-stylehttp://rapidsvn.tigris.org/
gsvnPython僅 Unix, 非原生 widgetGPLhttp://gsvn.tigris.org/
TortoiseSVNC++僅 Win32GPLhttp://tortoisesvn.tigris.org/
svnupJava, JNI bindingsYesApache-stylehttp://svnup.tigris.org/
jsvnJava, 包裝 svn 命令列用戶端YesAcademic Free Licensehttp://jsvn.alternatecomputing.com


[42] 我們說了算.

Appendix F. 協力廠商工具

Table of Contents

ViewCVS
SubWiki

Subversion 的模組化設計 (涵蓋於 the section called “Layered Library Design” 中) 與程式語言繫結功能 (描述於 the section called “Using Languages Other than C and C++”), 讓 Subversion 適合擴充其它程式的功能, 或是成為其它軟體的後端. 在本附錄中, 我們會介紹幾個使用 Subversion 的協力廠商開發的工具. 我們不會涵蓋真正的 Subversion 用戶端 — 請另行參考 Appendix E, 其它 Subversion 用戶端.

ViewCVS

也許第一個 — 而且絕對也是最受歡迎的 — 充份使用到 Subversion 的公用 API 的工具, 就是 ViewCVS. ViewCVS 其實是一個 CGI 命令稿, 允許瀏灠一個版本控制系統的目錄與檔案. 原先是設計為以 Python 寫成, 用來取代 cvsweb 的工具. [43] ViewCVS 對 CVS 檔案庫提供了完整功能的網頁界面, 允許別人可以查看這些檔案庫內的檔案的版本控制歷史, 也可以進行一些精巧的功能, 像是產生這些檔案於不同修訂版之間的差異.

在 2002 年早期, ViewCVS 的檔案庫存取部份就已經進行模組化, 成為一個半通用的界面, 另外又有兩個模組對 CVS 檔案庫提供這樣的功能. 該年稍後, Subversion 的 Python 語言繫結已經夠成熟, 於是就為 ViewCVS 界面寫出了 Subversion 檔案庫模組. 現在, ViewCVS 可以瀏灠 Subversion 的檔案庫, 並且為這些檔案庫提供歷史與差異的機制, 就像為 CVS 所提供的一樣.

想要得知更多有關 ViewCVS 的資訊, 請參考該專案位於 http://viewcvs.sf.net/ 的網站.

SubWiki

SubWiki 是一個以 Subversion 為後端的 Wiki 程式. Wiki 是由 World Wide Web 中竄起的特殊出版與社群工具 — 基本上, 就是以網頁界面來編輯網頁. SubWiki 吸收了 Wiki 的概念, 並且向前延伸, 使用版本控制系統作為其後端儲存機制. 結果就是一個 CGI 程式, 讓你可以就地編輯網頁, 但是又不會失去這些網頁的舊版資料.

想要得知更多有關 SubWiki 的資訊, 請參考該專案位於 http://subwiki.tigris.org/ 的網站.



[43] CVSWeb 是以 Perl 寫成的.

Glossary

Colophon

Etc.