Subversion 實踐

是時候從抽像轉到具體了,在本小節,我們會展示一個Subversion真實使用的例子。

Subversion 版本庫的 URL

正如我們在整本書裡描述的,Subversion使用URL來識別Subversion版本庫中的版本化資源,通常情況下,這些URL使用標準的語法,允許伺服器名稱和埠號作為URL的一部分:

$ svn checkout http://svn.example.com:9834/repos
…

但是Subversion處理URL的一些細微的不同之處需要注意,例如,使用file:訪問方法的URL(用來訪問本地版本庫)必須與習慣一致,可以包括一個localhost伺服器名或者沒有伺服器名:

$ svn checkout file:///path/to/repos
…
$ svn checkout file://localhost/path/to/repos
…

同樣,在Windows平台下使用file://模式時需要使用一個非正式的「標準」語法來訪問本機上不在同一個磁碟分區中的版本庫。下面的任意一個URL路徑語法都可以工作,其中的X表示版本庫所在的磁碟分區:

C:\> svn checkout file:///X:/path/to/repos
…
C:\> svn checkout "file:///X|/path/to/repos"
…

在第二個語法裡,你需要使用引號包含整個URL,這樣豎線字元才不會被解釋為管道。當然,也要注意URL使用普通的斜線而不是Windows本地(不是URL)的反斜線。

注意

也必須意識到Subversion的file: URL不能在普通的web伺服器中工作。當你嘗試在web伺服器查看一個file:的URL時,它會通過直接檢測文件系統讀取和顯示那個位置的文件內容,但是Subversion的資源存在於虛擬文件系統(見「版本庫層」一節)中,你的瀏覽器不會理解怎樣讀取這個文件系統。

最後,必須注意Subversion的客戶端會根據需要自動編碼URL,這一點和一般的web瀏覽器一樣,舉個例子,如果一個URL包含了空格或是一個字元編碼大於128的ASCII字元:

$ svn checkout "http://host/path with space/project/españa"

…Subversion會迴避這些不安全字元,並且會像你輸入了這些字元一樣工作:

$ svn checkout http://host/path%20with%20space/project/espa%C3%B1a

如果URL包含空格,一定要使用引號,這樣你的腳本才會把它做一個單獨的svn參數。

工作副本

你已經閱讀過了關於工作副本的內容;現在我們要講一講客戶端怎樣建立和使用它。

一個Subversion工作副本是你本地機器上的一個普通目錄,保存著一些文件,你可以任意的編輯文件,而且如果是原始碼文件,你可以像平常一樣編譯,你的工作副本是你的私有工作區,在你明確的做了特定操作之前,Subversion不會把你的修改與其他人的合併,也不會把你的修改展示給別人,你甚至可以擁有同一個項目的多個工作副本。

當你在工作副本作了一些修改並且確認它們工作正常之後,Subversion提供了一個命令可以「發佈」你的修改給項目中的其他人(通過寫到版本庫),如果別人發佈了各自的修改,Subversion提供了手段可以把這些修改與你的工作目錄進行合併(通過讀取版本庫)。

工作副本也包括一些由 Subversion 建立並維護的額外文件,用來協助執行命令。通常情況下,你的工作副本的每個文件夾都有一個以 .svn 為名的文件夾,也被叫做工作副本的管理目錄,這個目錄裡的文件能夠幫助 Subversion 識別哪些文件做過修改,哪些文件相對於別人的工作已經過期。

一個典型的Subversion的版本庫經常包含許多項目的文件(或者說原始碼),通常每一個項目都是版本庫的子目錄,在這種部署下,一個用戶的工作副本往往對應版本庫的的一個子目錄。

舉一個例子,你的版本庫包含兩個軟體項目,paintcalc。每個項目在它們各自的最上層子目錄下,見圖 1.6 「版本庫的文件系統」

圖 1.6. 版本庫的文件系統

版本庫的文件系統

為了得到一個工作副本,你必須檢出(check out)版本庫的一個子樹,(術語「check out」聽起來像是鎖定或者保留資源,實際上不是,只是簡單的得到一個項目的私有拷貝),舉個例子,你檢出 /calc,你可以得到這樣的工作副本:

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

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

列表中的A表示Subversion增加了一些條目到工作副本,你現在有了一個/calc的個人拷貝,有一個附加的目錄—.svn—保存著前面提及的Subversion需要的額外訊息。

假定你修改了button.c,因為.svn目錄記錄著文件的修改日期和原始內容,Subversion可以告訴你已經修改了文件,然而,在你明確告訴它之前,Subversion不會將你的改變公開,將改變公開的操作被叫做提交(committing,或者是checking in)修改到版本庫。

將你的修改發佈給別人,你可以使用Subversion的提交(commit)命令。

$ svn commit button.c -m "Fixed a typo in button.c."
Sending        button.c
Transmitting file data .
Committed revision 57.

這時你對button.c的修改已經提交到了版本庫,其中包含了關於此次提交的日誌訊息(例如是修改了拼寫錯誤)。如果其他人取出了/calc的一個工作副本,他們會看到這個文件最新的版本。

假設你有個合作者,Sally,她和你同時取出了/calc的一個工作副本,你提交了你對button.c的修改,Sally的工作副本並沒有改變,Subversion只在用戶要求的時候才改變工作副本。

要使項目最新,Sally可以要求Subversion更新她的工作備份,通過使用更新(update)命令,將結合你和所有其他人在她上次更新之後的改變到她的工作副本。

$ pwd
/home/sally/calc

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

$ svn update
U    button.c
Updated to revision 57.

svn update命令的輸出表明Subversion更新了button.c的內容,注意,Sally不必指定要更新的文件,subversion利用.svn以及版本庫的進一步訊息決定哪些文件需要更新。

修訂版本

一個svn commit操作可以作為一個原子事務操作發佈任意數量文件和目錄的修改,在你的工作副本裡,你可以改變文件內容、刪除、改名以及拷貝文件和目錄,然後作為一個原子事務一起提交。

原子事務」的意思是:要麼所有的改變發生,要麼都不發生,Subversion努力保持原子性以應對程序錯誤、系統錯誤、網路問題和其他用戶行為。

每當版本庫接受了一個提交,文件系統進入了一個新的狀態,叫做一次修訂(revision),每一個修訂版本被賦予一個獨一無二的自然數,一個比一個大,初始修訂號是0,只建立了一個空目錄,沒有任何內容。

圖 1.7 「版本庫」可以更形象的描述版本庫,想像有一組修訂號,從0開始,從左到右,每一個修訂號有一個目錄樹掛在它下面,每一個樹好像是一次提交後的版本庫「快照」。

圖 1.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的改變還在,在這個例子裡,Makefile在4、5、6修訂版本都是一樣的,但是Subversion會把他的Makefile的修訂號設為6來表明它是最新的,所以你在工作副本最上層目錄作一次乾淨的更新,會使得所有內容對應版本庫的同一修訂版本。

工作副本怎樣追蹤版本庫

對於工作副本的每一個文件,Subversion在管理區域.svn/記錄兩項關鍵的訊息:

  • 工作文件所作為基準的修訂版本(叫做文件的工作修訂版本)和

  • 一個本地拷貝最後更新的時間戳。

給定這些訊息,通過與版本庫通訊,Subversion可以告訴我們工作文件是處於如下四種狀態的那一種:

未修改且是當前的

文件在工作目錄裡沒有修改,在工作修訂版本之後沒有修改提交到版本庫。svn commit操作不做任何事情,svn update不做任何事情。

本地已修改且是當前的

在工作目錄已經修改,從基本修訂版本之後沒有修改提交到版本庫。本地修改沒有提交,因此svn commit會成功提交,svn update不做任何事情。

未修改且不是當前的了

這個文件在工作目錄沒有修改,但在版本庫中已經修改了。這個文件最終將更新到最新版本,成為當時的公共修訂版本。svn commit不做任何事情,svn update將會取得最新的版本到工作副本。

本地已修改且不是最新的

這個文件在工作目錄和版本庫都得到修改。一個svn commit將會失敗,這個文件必須首先更新,svn update命令會合併公共和本地修改,如果Subversion不可以自動完成,將會讓用戶解決衝突。

這看起來需要記錄很多事情,但是svn status命令可以告訴你工作副本中文件的狀態,關於此命令更多的訊息,請看「查看你的修改概況」一節

混合修訂版本的工作副本

作為一個普遍原理,Subversion努力做到盡可能的靈活,一個特殊的靈活特性就是讓工作副本包含不同工作修訂版本的文件和目錄,不幸的是,這個靈活性會讓許多入門使用者感到迷惑。如果上一個混合修訂版本的例子讓你感到困惑,這裡是一個為何有這種特性和如何利用這個特性的基礎介紹。

更新和提交是分開的

Subversion有一個基本原則就是一個「」動作不會導致「」,反之亦然,因為你準備好了提交你的修改並不意味著你已經準備好了從其他人那裡接受修改。如果你的新的修改還在進行,svn update將會優雅的合併版本庫的修改到你的工作副本,而不會強迫將修改發佈。

這個規則的主要副作用就是工作副本需要記錄額外的訊息來追蹤混合修訂版本,並且也需要能容忍這種混合,當目錄本身也是版本化的時候情況更加複雜。

舉個例子,假定你有一個工作副本,修訂版本號是10。你修改了foo.html,然後執行svn commit,在版本庫裡建立了修訂版本15。當成功提交之後,許多用戶希望工作副本完全變成修訂版本15,但是事實並非如此。修訂版本從10到15會發生任何修改,可是客戶端在運行svn update之前不知道版本庫發生了怎樣的改變,svn commit不會拖出任何新的修改。另一方面,如果svn commit會自動下載最新的修改,可以使得整個工作副本成為修訂版本15—但是,那樣我們會打破「push」和「pull」完全分開的原則。因此,Subversion客戶端最安全的方式是標記一個文件—foo.html—為修訂版本15,工作副本餘下的部分還是修訂版本10。只有運行svn update才會下載最新的修改,整個工作副本被標記為修訂版本15。

混合修訂版本很常見

事實上,每次運行svn commit,你的工作副本都會進入混合多個修訂版本的狀態,剛剛提交的文件會比其他文件有更高的修訂版本號。經過多次提交(之間沒有更新),你的工作副本會完全是混合的修訂版本。即使只有你一個人使用版本庫,你依然會見到這個現象。為了檢驗混合工作修訂版本,可以使用svn status --verbose命令(詳細訊息見「查看你的修改概況」一節)。

通常,入門使用者對於工作副本的混合修訂版本一無所知,這會讓人糊塗,因為許多客戶端命令對於所檢驗條目的修訂版本很敏感。例如svn log命令顯示一個文件或目錄的歷史修改訊息(見「產生歷史修改列表」一節),當用戶對一個工作副本對像調用這個命令,他們希望看到這個對象的整個歷史訊息。但是如果這個對象的修訂版本已經相當老了(通常因為很長時間沒有運行svn update),此時會顯示比這個對象更老的歷史。

混合版本很有用

如果你的項目十分複雜,有時候你會發現強制工作副本的一部分「回溯」到過去非常有用(或者更新到過去的某個修訂版本),你將在第 2 章 基本使用學習到如何這樣做。或許你很希望測試某一子目錄下某一子模塊的早期版本,又或是要測試一個bug什麼時候發生,這是版本控制系統像「時間機器」的一個方面—這個特性允許工作副本的任何一個部分在歷史中前進或後退。

混合版本有限制

無論你如何在工作副本中利用混合修訂版本,這種靈活性還是有限制的。

首先,你不可以提交一個不是完全最新的文件或目錄,如果有個新的版本存在於版本庫,你的刪除操作會被拒絕,這防止你不小心破壞你沒有見到的東西。

第二,如果目錄已經不是最新的了,你不能提交一個目錄的Meta資料更改。你將會在第 3 章 進階主題學習附加「屬性」,一個目錄的工作修訂版本定義了許多條目和屬性,因而對一個過期的版本提交屬性會破壞一些你沒有見到的屬性。