維護一個Subversion版本庫是一項令人沮喪的工作,主要因為有資料庫後端與生俱來的複雜性。做好這項工作需要知道一些工具——它們是什麼,什麼時候用以及如何使用。這一節將會向你介紹Subversion自帶的版本庫管理工具,以及如何使用它們來完成諸如版本庫移植、升級、備份和整理之類的任務。
Subversion提供了一些用來建立、查看、修改和修復版本庫的工具。讓我們首先詳細瞭解一下每個工具,然後,我們再看一下僅在Berkeley DB後端分發版本中提供的版本資料庫工具。
svnadmin程序是版本庫管理員最好的朋友。除了提供建立Subversion版本庫的功能,這個程序使你可以維護這些版本庫。svnadmin的語法同其他Subversion命令類似:
$ svnadmin help general usage: svnadmin SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...] Type 'svnadmin help <subcommand>' for help on a specific subcommand. Type 'svnadmin --version' to see the program version and FS modules. Available subcommands: crashtest create deltify …
我們已經討論了svnadmin的create子命令(參照「建立和設定你的版本庫」一節),本章後面我們會詳細講解大多數其他的子命令,關於所有的子命令你可以參考「
svnadmin
」一節。
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.
Type 'svnlook --version' to see the program version and FS modules.
…
幾乎svnlook的每一個子命令都能操作修訂版本或事務樹,顯示樹本身的訊息,或是它與版本庫中上一個修訂版本的不同。你可以用--revision (-r) 和 --transaction (-t)選項指定要查看的修訂版本或事務。如果沒有指定--revision (-r)和--transaction (-t)選項,svnlook會檢查版本庫最新的(或者說「HEAD」)修訂版本。所以當19是位於/path/to/repos的版本庫的最新版本時,如下的兩個名字起到相同的效果:
$ svnlook info /path/to/repos $ svnlook info /path/to/repos -r 19
這些子命令的唯一例外是svnlook youngest,它不需要任何選項,只會打印出版本庫的最新修訂版本號。
$ svnlook youngest /path/to/repos 19
請記住只能瀏覽未提交的事物,大多數版本庫沒有這樣的事物,因為事物要麼是已經提交的(也就是你可以--revision (-r)訪問的修訂版本),要麼是退出的和刪除的。
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子命令的輸出定義如下:
作者,後接換行。
日期,後接換行。
日誌消息的字數,後接換行。
日誌訊息本身, 後接換行。
這種輸出是人可閱讀的,像是時間戳這種有意義的條目,使用文字表示,而不是其他比較晦澀的方式(例如許多無聊的人推薦的十億分之一秒的數量)。這種輸出也是機器可讀的—因為日誌訊息可以有多行,沒有長度的限制,svnlook在日誌消息之前提供了消息的長度,這使得腳本或者其他這個命令的封裝器能夠針對日誌訊息做出許多職能的決定,或僅僅是在這個輸出成為最後一個字節之前應該略過多少字節。
svnlook還可以做很多別的查詢:顯示我們先前提到的訊息的一些子集,遞歸顯示版本目錄樹,報告指定的修訂版本或事務中哪些路徑曾經被修改過,顯示對文件和目錄做過的文字和屬性的修改,等等。「 svnlook 」一節是svnlook命令能接受子命令的完全特性參考。
雖然在管理員的日常工作中並不會經常使用,不過svndumpfilter提供了一項特別有用的功能—可以簡單快速的作為Subversion版本庫歷史的以路徑為基礎的過濾器。
svndumpfilter的語法如下:
$ svndumpfilter help general usage: svndumpfilter SUBCOMMAND [ARGS & OPTIONS ...] Type "svndumpfilter help <subcommand>" for help on a specific subcommand. Type 'svndumpfilter --version' to see the program version. Available subcommands: exclude include help (?, h)
有意義的子命令只有兩個。你可以使用這兩個子命令說明你希望保留和不希望保留的路徑:
exclude
將指定路徑的資料從轉儲串流(Streaming)中排除。
include
將指定路徑的資料新增到轉儲串流(Streaming)中。
關於這些子命令和svndumpfilter的唯一目的的,可以見「過濾版本庫歷史」一節。
svnsync程序是Subversion 1.4版的新特性,提供了維護一個只讀版本庫鏡像的全部功能。這個程序只有一個工作—將一個版本庫的歷史轉移到另一個,儘管有幾種方法,但這種方法的主要特點是可以遠程操作—「源」,「目標」[30]版本庫以及svnsync程序可能在不同的計算機上。
就像你期望的,svnsync的語法與本節提到的其他命令非常類似。
$ svnsync help general usage: svnsync SUBCOMMAND DEST_URL [ARGS & OPTIONS ...] Type 'svnsync help <subcommand>' for help on a specific subcommand. Type 'svnsync --version' to see the program version and RA modules. Available subcommands: initialize (init) synchronize (sync) copy-revprops help (?, h) $
我們會在「版本庫複製」一節詳細討論使用svnsync實現版本庫複製。
如果你使用Berkeley DB版本庫,那麼所有納入版本控制的文件系統結構和資料都儲存在一系列資料庫的表中,而這個目錄就是版本庫的db/。這個子目錄是一個標準的Berkeley DB環境目錄,可以應用任何Berkeley資料庫工具進行操作,通常這些工具隨Berkeley DB發佈。
對於Subversion的日常使用來說,這些工具並沒有什麼用處。大多數Subversion版本庫必須的資料庫操作都集成到svnadmin工具中。比如,svnadmin list-unused-dblogs和svnadmin list-dblogs實現了Berkeley db_archive命令功能的一個子集,而svnadmin recover則起到了db_recover工具的作用。
當然,還有一些Berkeley DB工具有時是有用的。db_load和db_dump分別將Berkeley DB資料庫中的鍵值對以特定的格式讀寫文件。Berkeley資料庫本身不支持跨平台轉移,這兩個工具在這樣的情況下就可以實現在平台間轉移資料庫的功能,而無需關心操作系統或機器架構。就像我們以前描述的,你可以使用svnadmin dump和svnadmin load實現類似的目的,但是db_dump和db_load可以更快一點,它們也可以協助Berkeley DB的hacker來篡改BDB後端的資料,這是Subversion工具不允許的。此外,db_stat工具能夠提供關於Berkeley DB環境的許多有用訊息,包括詳細的鎖定和儲存子系統的統計訊息。
關於Berkeley DB工具的更多訊息,可以訪問Oracle網站的Berkeley DB文件部分,在http://www.oracle.com/technology/documentation/berkeley-db/db/。
有時用戶輸入的日誌訊息有錯誤(比如拼寫錯誤或者內容錯誤)。如果設定版本庫時設定了(使用pre-revprop-change和 post-revprop-change鉤子;參見「實現版本庫鉤子」一節)允許用戶在提交後修改日誌訊息的選項,那麼用戶可以使用svn程序的propset命令(參見第 9 章 Subversion 完全參考)「修正」日誌訊息中的錯誤。不過為了避免永遠丟失訊息,Subversion版本庫通常設定為僅能由管理員修改非版本化屬性(這也是默認的選項)。
如果管理員想要修改日誌訊息,那麼可以使用svnadmin setlog命令。這個命令從指定的文件中讀取訊息,取代版本庫中某個修訂版本的日誌訊息(svn:log屬性)。
$ echo "Here is the new, correct log message" > newlog.txt $ svnadmin setlog myrepos newlog.txt -r 388
即使是svnadmin setlog命令也受到限制。pre-和 post-revprop-change鉤子同樣會被觸發,因此必須進行相應的設定才能允許修改非版本化屬性。不過管理員可以使用svnadmin setlog命令的--bypass-hooks選項跳過鉤子。
不過需要注意的是,一旦跳過鉤子也就跳過了鉤子所提供的所有功能,比如郵件通知(通知屬性有改動)、系統備份(可以用來追蹤非版本化的屬性變更)等等。換句話說,要留心你所作出的修改,以及你作出修改的方式。
雖然儲存器的價格在過去的幾年裡以讓人難以致信的速度滑落,但是對於那些需要對大量資料進行版本管理的管理員們來說,磁碟空間的消耗依然是一個重要的因素。版本庫每增加一個字節都意味著需要多一個字節的磁碟空間進行備份,對於多重備份來說,就需要消耗更多的磁碟空間。Berkeley DB版本庫的主要儲存機制是基於一個複雜的資料庫系統建立的,因此瞭解一些資料性質是有意義的,比如哪些資料必須保持在線,哪些資料需要備份、哪些資料可以安全的刪除等等。
為了盡可能減小版本庫的體積,Subversion在版本庫中採用了增量化技術(或稱為「增量儲存技術」)。增量化技術可以將一組資料表示為相對於另一組資料的不同。如果這兩組資料十分相似,增量化技術就可以僅保存其中一組資料以及兩組資料的差別,而不需要同時保存兩組資料,從而節省了磁碟空間。每次一個文件的新版本提交到版本庫,版本庫就會將之前的版本(之前的多個版本)相對於新版本做增量化處理。採用了這項技術,版本庫的資料量大小基本上是可以估算出來的—主要是版本化的文件的大小—並且遠小於「全文」保存所需的資料量。而Subversion 1.4以後,空間儲存變得更為節省—現在文件內容的全文本身都是壓縮的了。
由於Subversion版本庫的增量化資料保存在單一Berkeley DB資料庫文件中,減少資料的體積並不一定能夠減小資料庫文件的大小。但是,Berkeley DB會在內部記錄未使用的資料庫文件區域,並且在增加資料庫文件大小之前會首先使用這些未使用的區域。因此,即使增量化技術不能立桿見影的節省磁碟空間,也可以極大的減慢資料庫的膨脹速度。
儘管不太常見,Subversion的提交進程也有失敗,同時留下將要生成的修訂版本—未提交的事物和所有隨之的文件和目錄修改。出現這種情況可能有以下原因:客戶端的用戶粗暴的結束了操作,操作過程中出現網路故障,等等。不管是什麼原因,死亡的事務總是有可能會出現。這類事務不會產生什麼負面影響,僅僅是消耗了一點點磁碟空間。不過,嚴厲的管理員總是希望能夠將它們清除出去。
可以使用svnadmin的lstxns 命令列出當前的事務名。
$ svnadmin lstxns myrepos 19 3a1 a45 $
將輸出的結果條目作為svnlook(設定--transaction (-t)選項)的參數,就可以獲得事務的詳細訊息,如事務的建立者、建立時間,事務已作出的更改類型,由這些訊息可以判斷出是否可以將這個事務安全的刪除。如果可以安全刪除,那麼只需將事務名作為參數輸入到svnadmin rmtxns,就可以將事務清除掉了。其實rmtxns子命令可以直接以lstxns的輸出作為輸入進行清理。
$ svnadmin rmtxns myrepos `svnadmin lstxns myrepos` $
在按照上面例子中的方法清理版本庫之前,你或許應該暫時關閉版本庫和客戶端的連接。這樣在你開始清理之前,不會有正常的事務進入版本庫。例 5.1 「txn-info.sh(報告異常事務)」中的shell腳本可以用來迅速獲得版本庫中異常事務的訊息。
例 5.1. txn-info.sh(報告異常事務)
#!/bin/sh
### Generate informational output for all outstanding transactions in
### a Subversion repository.
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}" -t "${TXN}"
done
該命令的輸出主要由多個svnlook info(參見「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 $
一個廢棄了很長時間的事務通常是提交錯誤或異常中斷的結果。事務的時間戳可以提供給我們一些有趣的訊息,比如一個進行了9個月的操作居然還是活動的等等。
簡言之,作出事務清理的決定前應該仔細考慮一下。許多訊息源—比如Apache的錯誤和訪問日誌,已成功完成的Subversion提交日誌等等—都可以作為決策的參考。當然,管理員還可以直接和那些似乎已經死亡事務的提交者直接交流(比如通過郵件),來確認該事務確實已經死亡了。
目前為止,Subversion版本庫中耗費磁碟空間的最大兇手是日誌文件,每次Berkeley DB在修改真正的資料文件之前都會進行預寫入(pre-writes)操作。這些文件記錄了資料庫從一個狀態變化到另一個狀態的所有動作——資料庫文件反映了特定時刻資料庫的狀態,而日誌文件則記錄了所有狀態變化的訊息。因此,日誌文件會以很快的速度膨脹起來。
幸運的是,從版本4.2開始,Berkeley DB的資料庫環境無需額外的操作即可刪除無用的日誌文件。如果編譯svnadmin時使用了高於4.2版本的Berkeley DB,那麼由此svnadmin程序建立的版本庫就具備了自動清除日誌文件的功能。如果想屏蔽這個功能,只需設定svnadmin create命令的--bdb-log-keep選項即可。如果建立版本庫以後想要修改關於此功能的設定,只需編輯版本庫中db目錄下的DB_CONFIG文件,註釋掉包含set_flags DB_LOG_AUTOREMOVE內容的這一行,然後運行svnadmin recover強制設定生效就行了。查閱「Berkeley DB 設定」一節獲得更多關於資料庫設定的幫助訊息。
如果不自動刪除日誌文件,那麼日誌文件會隨著版本庫的使用逐漸增加。這多少應該算是資料庫系統的特性,通過這些日誌文件可以在資料庫嚴重損壞時恢復整個資料庫的內容。但是一般情況下,最好是能夠將無用的日誌文件收集起來並刪除,這樣就可以節省磁碟空間。使用svnadmin list-unused-dblogs命令可以列出無用的日誌文件:
$ svnadmin list-unused-dblogs /path/to/repos /path/to/repos/log.0000000031 /path/to/repos/log.0000000032 /path/to/repos/log.0000000033 … $ rm `svnadmin list-unused-dblogs /path/to/repos` ## disk space reclaimed!
BDB後端的版本庫的日誌文件如果是用來作為備份或容災恢復計劃時,不要使用日誌文件的自動刪除特性。從日誌文件重新構建版本庫資料只有在所有的日誌文件都存在時才能完成,如果有一些文件在別的程序將其拷貝之前就已經被刪除了,不完整的備份日誌文件就沒有用了。
就像在「Berkeley DB」一節提到的,如果沒有正確的關閉,Berkeley DB版本庫有時候會進入凍結的狀態。當發生這種情況時,管理員需要恢復版本庫進入一致的狀態。當然這種情況只發生在BDB版本庫,FSFS版本庫不會有這種情況。對於使用Subversion 1.4和Berkeley DB 4.4或更新版本的用戶,你一定發現Subversion對於這種情況已經更富彈性,但是Berkeley DB楔住的情況還是會發生,管理員需要知道如何安全的處理種情況。
Berkeley DB使用一種鎖機制保護版本庫中的資料。鎖機制確保資料庫不會同時被多個訪問進程修改,也就保證了從資料庫中讀取到的資料始終是穩定而且正確的。當一個進程需要修改資料庫中的資料時,首先必須檢查目標資料是否已經上鎖。如果目標資料沒有上鎖,進程就將它鎖上,然後作出修改,最後再將鎖解除。而其它進程則必須等待鎖解除後才能繼續訪問資料庫中的相關內容。(你對這種鎖無能為力,作為一個用戶,可以應用版本庫的版本化文件;我們會在鎖定的三種含義討論因為術語衝突導致的概念混淆。)
在操作Subversion版本庫的過程中,致命錯誤(如記憶體或硬碟空間不足)或異常中斷可能會導致某個進程沒能及時將鎖解除。結果就是後端的資料庫系統被「塞住」了。一旦發生這種情況,任何訪問版本庫的進程都會掛起(每個訪問進程都在等待鎖被解除,但是鎖已經無法解除了)。
如果你的版本庫出現這種情況,沒什麼好驚慌的。Berkeley DB的文件系統採用了資料庫事務、檢查點以及預寫入日誌等技術來確保只有災難性的事件[31]才能永久性的破壞資料庫環境。所以雖然一個過於穩重的版本庫管理員通常都會按照某種方案進行大量的版本庫離線備份,不過不要急著通知你的管理員進行恢復。
然後,使用下面的方法試著「恢復」你的版本庫:
確保沒有其它進程訪問(或者試圖訪問)版本庫。對於網路版本庫,這意味著關閉Apache HTTP Server或svnserve。
成為版本庫的擁有者和管理員。這一點很重要,如果以其它用戶的身份恢復版本庫,可能會改變版本庫文件的訪問權限,導致在版本庫「恢復」後依舊無法訪問。
運行命令svnadmin recover /path/to/repos。 輸出如下:
Repository lock acquired。 Please wait; recovering the repository may take some time... Recovery completed. The latest repos revision is 19.
此命令可能需要數分鐘才能完成。
重新啟動服務進程。
這個方法能修復幾乎所有版本庫鎖住的問題。記住,要以資料庫的擁有者和管理員的身份運行這個命令,而不一定是root用戶。恢復過程中可能會使用其它資料儲存區(例如共享記憶體區)重建一些資料庫文件。如果以root用戶身份恢復版本庫,這些重建的文件擁有者將變成root用戶,也就是說,即使恢復了到版本庫的連接,一般的用戶也無權訪問這些文件。
如果因為某些原因,上面的方法沒能成功的恢復版本庫,那麼你可以做兩件事。首先,將破損的版本庫保存到其它地方,然後從最新的備份中恢復版本庫。然後,發送一封郵件到Subversion用戶列表(地址是:<users@subversion.tigris.org>),寫清你所遇到的問題。對於Subversion的開發者來說,資料安全是最重要的問題。
Subversion文件系統將資料保存在許多資料庫表中,而這些表的結構只有Subversion開發者們才瞭解(也只有他們才感興趣),不過,有些時候我們會想到把所有或一部分資料轉移到另一個版本庫。
Subversion提供了轉儲版本庫的功能,一個版本庫轉儲流(當存放在磁碟上叫做「dumpfile」)是一種可移植的,普通文件格式,可以用來描述版本庫的不同版本—什麼發生了修改,誰做的,何時等等。這種轉儲流是解析版本化歷史的主要機制—全部或部分,包含或部包含修改—在版本庫之間。Subversion也提供了建立和加載這些轉儲流的工具—對應的svnadmin dump和svnadmin load子命令。
雖然Subversion版本庫轉儲格式包含了人可讀的部分和熟悉的結構(類似RFC-822格式,大多數郵件使用的),它不是純文字的格式,這種格式必須作為二進制文件格式處理,對修改高度敏感。例如,許多文字編輯器會破壞這種文件的內容,通常是因為自動換行符替換。
有很多導出和加載Subversion版本庫資料的方法,在Subversion的早期階段,最主要的原因是Subversion本身的進化。隨著Subversion的成熟,對於資料後端模式的改變會導致更多的兼容性問題,所以用戶需要使用舊版本的Subversion將版本庫資料導出,然後用新版的版本庫加載內容到新建的版本庫。目前,這種類型的模式修改從Subversion 1.0版本還沒有發生,而且Subversion開發者也許諾不會強制用戶在小版本(如1.3到1.4)升級之間匯入和導出版本庫。但是也有一些其它原因導出和匯入,包括重新部署Berkeley DB到版本庫到新的OS或CPU架構,在Berkeley DB和FSFS後端之間切換,或者(我們會在「過濾版本庫歷史」一節覆蓋)從版本庫歷史中清理文件。
無論你是什麼原因需要移植版本庫歷史,都可以直接使用svnadmin dump和svnadmin load。svnadmin dump命令會將版本庫中的修訂版本資料按照特定的格式輸出到轉儲流中,轉儲資料會輸出到標準輸出,而提示訊息會輸出到標準錯誤。這就是說,可以將轉儲資料儲存到文件中,而同時在終端窗口中監視運行狀態,例如:
$ 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 dump從版本庫中讀取修訂版本樹與其它「讀者」(比如svn checkout)的過程相同,所以可以在任何時候安全的運行這個命令。
另一個命令,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) >>>
load命令的結果就是新增一些新的修訂版本—與使用普通Subversion客戶端直接提交到版本庫相同。正像一次簡單的提交,你也可以使用鉤子腳本在每次load的開始和結束執行一些操作。通過傳遞--use-pre-commit-hook和--use-post-commit-hook選項給svnadmin load,你可以告訴Subversion的對每一個加載修訂版本執行pre-commit和post-commit鉤子腳本,可以利用這個選項確保這種提交也能通過一般提交的檢驗。當然,你要小心使用這個選項,你一定不想接受一大堆提交郵件。你可以查看「實現版本庫鉤子」一節來得到更多相關訊息。
既然svnadmin使用標準輸入流和標準輸出流作為轉儲和裝載的輸入和輸出,那麼更漂亮的用法是(管道兩端可以是不同版本的svnadmin:
$ svnadmin create newrepos $ svnadmin dump oldrepos | svnadmin load newrepos
默認情況下,轉儲文件的體積可能會相當龐大——比版本庫自身大很多。這是因為在轉儲文件中,每個文件的每個版本都以完整的文字形式保存下來。這種方法速度很快,而且很簡單,尤其是直接將轉儲資料通過管道輸入到其它進程中時(比如一個壓縮程序,過濾程序,或者一個裝載進程)。不過如果要長期保存轉儲文件,那麼可以使用--deltas選項來節省磁碟空間。設定這個選項,同一個文件的數個連續修訂版本會以增量式的方式保存—就像儲存在版本庫中一樣。這個方法較慢,但是轉儲文件的體積則基本上與版本庫的體積相當。
之前我們提到svnadmin dump輸出指定範圍內的修訂版本,使用--revision (-r)選項可以指定一個單獨的修訂版本,或者一個修訂版本的範圍。如果忽略這個選項,所有版本庫中的修訂版本都會被轉儲。
$ svnadmin dump myrepos -r 23 > rev-23.dumpfile $ svnadmin dump myrepos -r 100:200 > revs-100-200.dumpfile
Subversion在轉儲修訂版本時,僅會輸出與前一個修訂版本之間的差異,通過這些差異足以從前一個修訂版本中重建當前的修訂版本。換句話說,在轉儲文件中的每一個修訂版本僅包含這個修訂版本作出的修改。這個規則的唯一一個例外是當前svnadmin dump轉儲的第一個修訂版本。
默認情況下,Subversion不會把轉儲的第一個修訂版本看作對前一個修訂版本的更改。 首先,轉儲文件中沒有比第一個修訂版本更靠前的修訂版本了!其次,Subversion不知道裝載轉儲資料時(如果真的需要裝載的話)的版本庫是什麼樣的情況。為了保證每次運行svnadmin dump都能得到一個獨立的結果,第一個轉儲的修訂版本默認情況下會完整的保存目錄、文件以及屬性等資料。
不過,這些都是可以改變的。如果轉儲時設定了--incremental選項,svnadmin會比較第一個轉儲的修訂版本和版本庫中前一個修訂版本,就像對待其它轉儲的修訂版本一樣。轉儲時也是一樣,轉儲文件中將僅包含第一個轉儲的修訂版本的增量訊息。這樣的好處是,可以建立幾個連續的小體積的轉儲文件代替一個大文件,比如:
$ svnadmin dump myrepos -r 0:1000 > dumpfile1 $ svnadmin dump myrepos -r 1001:2000 --incremental > dumpfile2 $ svnadmin dump myrepos -r 2001:3000 --incremental > dumpfile3
這些轉儲文件可以使用下列命令裝載到一個新的版本庫中:
$ svnadmin load newrepos < dumpfile1 $ svnadmin load newrepos < dumpfile2 $ svnadmin load newrepos < dumpfile3
另一個有關的技巧是,可以使用--incremental選項在一個轉儲文件中增加新的轉儲修訂版本。舉個例子,可以使用post-commit鉤子在每次新的修訂版本提交後將其轉儲到文件中。或者,可以編寫一個腳本,在每天夜裡將所有新增的修訂版本轉儲到文件中。這樣,svnadmin dump命令就變成了很好的版本庫備份工具,以防萬一出現系統崩潰或其它災難性事件。
轉儲還可以用來將幾個獨立的版本庫合併為一個版本庫。使用svnadmin load的--parent-dir選項,可以在裝載的時候指定根目錄。也就是說,如果有三個不同版本庫的轉儲文件,比如calc-dumpfile,cal-dumpfile,和ss-dumpfile,可以在一個新的版本庫中保存所有三個轉儲文件中的資料:
$ svnadmin create /path/to/projects $
然後在版本庫中建立三個目錄分別保存來自三個不同版本庫的資料:
$ svn mkdir -m "Initial project roots" \
file:///path/to/projects/calc \
file:///path/to/projects/calendar \
file:///path/to/projects/spreadsheet
Committed revision 1.
$
最後,將轉儲文件分別裝載到各自的目錄中:
$ svnadmin load /path/to/projects --parent-dir calc < calc-dumpfile … $ svnadmin load /path/to/projects --parent-dir calendar < cal-dumpfile … $ svnadmin load /path/to/projects --parent-dir spreadsheet < ss-dumpfile … $
我們再介紹一下Subversion版本庫轉儲資料的最後一種用途——在不同的儲存機制或版本控制系統之間轉換。因為轉儲資料的格式的大部分是可以閱讀的,所以使用這種格式描述變更集(每個變更集對應一個新的修訂版本)會相對容易一些。事實上,cvs2svn工具(參見 「遷移 CVS 版本庫到 Subversion」一節)正是將CVS版本庫的內容轉換為轉儲資料格式,如此才能將CVS版本庫的資料匯入Subversion版本庫之中。
因為Subversion使用底層的二進制區別和壓縮算法(也可以選擇完全非透明資料庫系統)儲存各類資料,手工調整是不明智的,即使這樣做並不困難,我們也不鼓勵這樣做。然而,一旦你的資料存進了版本庫,Subversion沒有提供刪除資料的簡單辦法。[32]但是不可避免的,總會有些時候你需要處理版本庫的歷史資料。你也許想把一個不應該出現的文件從版本庫中徹底清除(無論任何原因不應該在那個位置出現)。或者,你曾經用一個版本庫管理多個工程,現在又想把它們分開。要完成這樣的工作,管理員們需要更易於管理和擴展的方法表示版本庫中的資料,Subversion版本庫轉儲文件格式就是一個很好的選擇。
就像我們在「版本庫資料的移植」一節中說的,Subversion版本庫轉儲文件記錄了所有版本資料的變更訊息,而且以易於閱讀的格式保存。可以使用svnadmin dump命令生成轉儲文件,然後用svnadmin load命令生成一個新的版本庫。(參見 「版本庫資料的移植」一節)。轉儲文件易於閱讀意味著你可以查看和修改它。當然,問題是如果你有一個運行了三年的版本庫,那麼生成的轉儲文件會很龐大,閱讀和手工修改起來都會花費很多時間。
這正是svndumpfilter發揮作用的地方,這個程序可以對版本庫轉儲流進行特定路徑的過濾。這是一個獨特而很有意義的用法,可以幫助你快速方便的修改轉儲的資料。使用時,只需提供一個你想要保留的(或者不想保留的)路徑列表,然後把你的版本庫轉儲文件送進這個過濾器。最後你就可以得到一個僅包含你想保留路徑(明確的或含蓄的)的轉儲串流(Streaming)。
現在我來演示如何使用這個命令。我們會在其它章節(參見 「規劃你的版本庫結構」一節)討論關於如何選擇設定版本庫部署的問題,比如應該使用一個版本庫管理多個項目還是使用一個版本庫管理一個項目,或者如何在版本庫中安排資料等等。不過,有些時候,即使在項目已經展開以後,你還是希望對版本庫的部署做一些調整。最常見的情況是,把原來存放在同一個版本庫中的幾個項目分開,各自成家。
假設有一個包含三個項目的版本庫: calc,calendar,和 spreadsheet。它們在版本庫中的部署如下:
/
calc/
trunk/
branches/
tags/
calendar/
trunk/
branches/
tags/
spreadsheet/
trunk/
branches/
tags/
現在要把這三個項目轉移到三個獨立的版本庫中。首先,轉儲整個版本庫:
$ svnadmin dump /path/to/repos > repos-dumpfile * Dumped revision 0. * Dumped revision 1. * Dumped revision 2. * Dumped revision 3. … $
然後,將轉儲文件三次送入過濾器,每次僅保留一個最上層目錄,就可以得到三個轉儲文件:
$ svndumpfilter include calc < repos-dumpfile > calc-dumpfile … $ svndumpfilter include calendar < repos-dumpfile > cal-dumpfile … $ svndumpfilter include spreadsheet < repos-dumpfile > ss-dumpfile … $
現在你必須要作出一個決定了。這三個轉儲文件中,每個都可以用來建立一個可用的版本庫,不過它們保留了原版本庫的精確路徑結構。也就是說,雖然項目calc現在獨佔了一個版本庫,但版本庫中還保留著名為calc的最上層目錄。如果希望trunk、tags和branches這三個目錄直接位於版本庫的根路徑下,你可能需要編輯轉儲文件,調整Node-path和Copyfrom-path頭參數,將路徑calc/刪除。同時,你還要刪除轉儲資料中建立calc目錄的部分。一般來說,就是如下的一些內容:
Node-path: calc Node-action: add Node-kind: dir Content-length: 0
如果你打算通過手工編輯轉儲文件來移除一個最上層目錄,注意不要讓你的編輯器將換行符轉換為本地格式(比如將\r\n轉換為\n)。否則文件的內容就與所需的格式不相符,這個轉儲文件也就失效了。
剩下的工作就是建立三個新的版本庫,然後將三個轉儲文件分別匯入:
$ svnadmin create calc; svnadmin load calc < calc-dumpfile
<<< Started new transaction, based on original revision 1
* adding path : Makefile ... done.
* adding path : button.c ... done.
…
$ svnadmin create calendar; svnadmin load calendar < cal-dumpfile
<<< Started new transaction, based on original revision 1
* adding path : Makefile ... done.
* adding path : cal.c ... done.
…
$ svnadmin create spreadsheet; svnadmin load spreadsheet < ss-dumpfile
<<< Started new transaction, based on original revision 1
* adding path : Makefile ... done.
* adding path : ss.c ... done.
…
$
svndumpfilter的兩個子命令都可以通過選項設定如何處理「空」修訂版本。如果某個指定的修訂版本僅包含路徑的更改,過濾器就會將它刪除,因為當前為空的修訂版本通常是無用的甚至是讓人討厭的。為了讓用戶有選擇的處理這些修訂版本,svndumpfilter提供了以下指令列選項:
--drop-empty-revs
不生成任何空修訂版本,忽略它們。
--renumber-revs
如果空修訂版本被剔除(通過使用--drop-empty-revs選項),依次修改其它修訂版本的編號,確保編號序列是連續的。
--preserve-revprops
如果空修訂版本被保留,保持這些空修訂版本的屬性(日誌訊息,作者,日期,自行定義屬性,等等)。如果不設定這個選項,空修訂版本將僅保留初始時間戳,以及一個自動生成的日誌訊息,表明此修訂版本由svndumpfilter處理過。
儘管svndumpfilter十分有用,能節省大量的時間,但它卻是把不折不扣的雙刃劍。首先,這個工具對路徑語義極為敏感。仔細檢查轉儲文件中的路徑是不是以斜線開頭。也許Node-path和Copyfrom-path這兩個頭參數對你有些幫助。
… Node-path: spreadsheet/Makefile …
如果這些路徑以斜線開頭,那麼你傳遞給svndumpfilter include和svndumpfilter exclude的路徑也必須以斜線開頭(反之亦然)。如果因為某些原因轉儲文件中的路徑沒有統一使用或不使用斜線開頭,[33]也許需要修正這些路徑,統一使用斜線開頭或不使用斜線開頭。
此外,複製操作生成的路徑也會帶來麻煩。Subversion支持在版本庫中進行複製操作,也就是複製一個存在的路徑,生成一個新的路徑。問題是,svndumpfilter保留的某個文件或目錄可能是由某個svndumpfilter排除的文件或目錄複製而來的。也就是說,為了確保轉儲資料的完整性,svndumpfilter需要切斷這些複製自被排除路徑的文件與原始文件的關係,還要將這些文件的內容以新建的方式新增到轉儲資料中。但是由於Subversion版本庫轉儲文件格式中僅包含了修訂版本的更改訊息,因此原始文件的內容基本上無法獲得。如果你不能確定版本庫中是否存在類似的情況,最好重新考慮一下到底保留/排除哪些路徑。
最後,svndumpfilter就是字面上的意思,如果你嘗試將目錄trunk/my-project中的內容遷移到其自己版本庫,你可以使用svndumpfilter include命令保持trunk/my-project目錄下的所有修改。但是結果轉儲文件對於將要被加載入的版本庫沒有任何假定,特別的,目錄trunk/my-project可能從建立這個目錄的修訂版本開始,而它不會包含以自己建立trunk目錄的指示(因為trunk沒有匹配include過濾)。在嘗試將轉儲流存放到版本庫之前,你需要確定任何轉儲流將要存在的目錄必須存在於目標版本庫。
有許多場景下會存在一個Subversion版本庫的版本歷史與另一個完全相同。或許最明顯的就是在主版本庫因為硬體故障或網路已出或其他原因而不可用時,維護一個簡單的備份版本庫。其他的場景包括,部署一個鏡像版本庫來分流壓力,作為軟升級機制等等。
Subversion 1.4提供了管理這種場景的工具—svnsync。svnsync實質上就是通知版本庫「重放」修訂版本,一次一個,然後將修訂版本訊息模擬提交到另一個版本庫。svnsync運行不需要能夠本地訪問版本庫—它的參數是版本庫URL,所有的工作是通過Subversion版本庫訪問層(RA)介面實現的,所有要做的就是讀源版本庫,然後讀寫訪問目標版本庫。
當對遠程源版本庫使用svnsync時,Subversion版本庫的伺服器必須是Subversion1.4或更高的版本。
假定你已經有了一個希望鏡像的源版本庫,下一步就是你要有一個作為鏡像的目標版本庫。目標版本庫可以使用任意文件系統資料儲存後端(見「選擇資料儲存格式」一節),但是其中一定不能有歷史版本。svnsync的通訊議對於源和目標版本庫版本歷史的不一致非常敏感,因此,雖然svnsync無法要求目標版本庫是只讀的,[34]最好的辦法就是只允許鏡像進程修改目標版本庫內容。
不要做出會對鏡像版本庫產生版本庫歷史偏移的修改,所有提交和版本庫的屬性修改必須是由svnsync執行的。
對於目標版本庫的另一種需求是svnsync可以修改特定版本化屬性。svnsync在目標版本庫的修訂版本0的特別屬性上記錄了簿記訊息,因為svnsync在版本庫的鉤子系統的框架下工作的,版本庫預設的狀態(關閉了版本庫屬性修改;見pre-revprop-change)是不夠的。你會需要明確的實現pre-revprop-change鉤子,而且你的腳本必須允許svnsync設定它的特別屬性,有了這些準備工作,你就可以開始鏡像版本庫修訂版本了。
實現授權措施允許複製進程的操作,同時防止其他用戶修改鏡像版本庫內容是一個好主意。
讓我們在一個典型的鏡像場景中瀏覽一下svnsync的使用,我們急著討論實踐推薦,但是如果你們不需要或者感到不適合你們的環境,你可以不必去關注。
作為開發者喜歡的版本控制系統的一個服務,我們會Subversion的原始碼版本庫鏡像到Internet,存放在不同的主機上,而不僅僅只有最初的Subversion版本庫。遠程主機的全域設定允許匿名用戶讀取版本庫的訊息,但是需要認證的用戶才能修改版本庫。(請原諒我們在此刻這裡曲解Subversion伺服器設定的細節—這些內容在第 6 章 服務設定。)因為沒有更多的理由來建立更有趣的例子,我們會在第三個機器上建立複製進程,我們正在使用的一個例子。
首先,我們會建立一個作為鏡像的版本庫,下面兩步需要我們能夠通過shell訪問鏡像版本庫的機器。一旦版本庫設定完成,我們不必再直接碰它了。
$ ssh admin@svn.example.com \
"svnadmin create /path/to/repositories/svn-mirror"
admin@svn.example.com's password: ********
$
此刻,我們有了我們的版本庫,因為我們伺服器的設定,這個版本庫現在「存在於」Internet。現在,因為除了複製進程我們不希望任何其他修改,我們需要將這個進程同其他可能的提交者區分開來。為此,我們的進程使用專用的用戶,只有特定用戶syncuser的提交和屬性修改可以被執行。
我們會使用版本庫的鉤子系統來允許複製進程完成我們的任務,我們通過實現兩個版本庫事件鉤子pre-revprop-change和start-commit來強制這個過程。我們的pre-revprop-change鉤子腳本可以在例 5.2 「鏡像版本庫的 pre-revprop-change 鉤子」找到,只是驗證嘗試修改屬性的用戶是syncuser,如果是,則允許修改;否則,拒絕修改。
例 5.2. 鏡像版本庫的 pre-revprop-change 鉤子
#!/bin/sh USER="$3" if [ "$USER" = "syncuser" ]; then exit 0; fi echo "Only the syncuser user may change revision properties" >&2 exit 1
這裡覆蓋了修訂版本屬性修改,我們現在需要來確認只有用戶syncuser允許提交新版本到版本庫,我們使用了一個像例 5.3 「鏡像版本庫的 start-commit 鉤子」的start-commit鉤子。
例 5.3. 鏡像版本庫的 start-commit 鉤子
#!/bin/sh USER="$2" if [ "$USER" = "syncuser" ]; then exit 0; fi echo "Only the syncuser user may commit new revisions" >&2 exit 1
在安裝了我們的鉤子腳本和確定它們可以被Subversion伺服器執行後,我們完成了鏡像版本庫的設定,現在我們開始實際的鏡像。
對於svnsync,我們首先需要在目標版本庫上註冊源版本庫,我們通過svnsync initialize實現這一步。注意,svnsync子命令提供了許多類似svn認證相關的選項,包括:--username、--password、--non-interactive、--config-dir和--no-auth-cache。
$ svnsync help init
initialize (init): usage: svnsync initialize DEST_URL SOURCE_URL
Initialize a destination repository for synchronization from
another repository.
The destination URL must point to the root of a repository with
no committed revisions. The destination repository must allow
revision property changes.
You should not commit to, or make revision property changes in,
the destination repository by any method other than 'svnsync'.
In other words, the destination repository should be a read-only
mirror of the source repository.
Valid options:
--non-interactive : do no interactive prompting
--no-auth-cache : do not cache authentication tokens
--username arg : specify a username ARG
--password arg : specify a password ARG
--config-dir arg : read user configuration files from directory ARG
$ svnsync initialize http://svn.example.com/svn-mirror \
http://svn.collab.net/repos/svn \
--username syncuser --password syncpass
Copied properties for revision 0.
$
我們的目標版本庫現在記住了它是Subversion公共原始碼版本庫的鏡像,注意我們在svnsync提供了一個用戶名和密碼—這是我們的鏡像版本庫pre-revprop-change鉤子的要求。
提供給svnsync的URL必須是指向目標和源版本庫的根目錄,這個工具不支持對版本庫子樹的鏡像處理。
svnsync的最初版本(在Subversion 1.4)有一些缺陷—用來認證的--username和--password指令列參數同時作用於源和目標版本庫。顯然,我們無法保證同步的用戶認證訊息是相同的,如果不一樣,用戶使用非交互模式(--non-interactive選項)來運行svnsync時會遇到這個問題。
現在有趣的部分開始了,通過一個單獨的子命令,我們可以告訴svnsync將所有未鏡像的修訂版本從源版本庫拷貝到目標版本庫。[35]svnsync synchronize子命令會查看目標版本庫特定修訂版本的屬性,並且檢測同步的版本庫是哪一個,以及最新鏡像的修訂版本是0。然後它會查詢源版本庫,檢測其最新的修訂版本。最後,它會詢問源版本庫伺服器來開始重演從修訂版本0到最新修訂版本。svnsync從源版本庫伺服器得到返回的結果,然後將其作為新的提交轉發到目標版本庫伺服器。
$ svnsync help synchronize
synchronize (sync): usage: svnsync synchronize DEST_URL
Transfer all pending revisions from source to destination.
…
$ svnsync synchronize http://svn.example.com/svn-mirror \
--username syncuser --password syncpass
Committed revision 1.
Copied properties for revision 1.
Committed revision 2.
Copied properties for revision 2.
Committed revision 3.
Copied properties for revision 3.
…
Committed revision 23406.
Copied properties for revision 23406.
Committed revision 23407.
Copied properties for revision 23407.
Committed revision 23408.
Copied properties for revision 23408.
鏡像修訂版本有一點特別有趣,首先是到目標版本庫的修訂版本提交,然後跟著屬性修改。這是因為最初的提交是通過用戶syncuser執行的,而時間戳是提交的時間,而且Subversion底層的版本庫訪問介面不允許在提交時任意修改修訂版本屬性,所以svnsync會立即使用屬性修改,將源版本庫發現的所有修訂版本屬性拷貝到目標版本庫,這其中就包括了修改作者和時間戳使之與源版本庫一致的效果。
值得注意的是svnsync會小心簿記所有的操作,可以安全的中斷並重新開始,而不必破壞鏡像資料的完整性。如果在svnsync synchronize時出現網路故障,只需要重新運行svnsync synchronize,她會從中斷處開始。實際上,隨著新的修訂版本在源版本庫出現,這樣就可以保證你的鏡像不會過時。
然而,這個進程還有一點不雅的地方,因為Subversion屬性修改可以發生在整個生命週期的任何時候,不會留下任何審計痕跡來說明所作的修改,扶植進程需要對此額外關注。如果你已經鏡像了某個版本庫的15個修訂版本,而某個人修改了修訂版本12的屬性,你需要告訴它手工使用(或一些額外的工具)svnsync copy-revprops子命令,只是簡單的重新複製某個特定修訂版本的屬性。
$ svnsync help copy-revprops
copy-revprops: usage: svnsync copy-revprops DEST_URL REV
Copy all revision properties for revision REV from source to
destination.
…
$ svnsync copy-revprops http://svn.example.com/svn-mirror 12 \
--username syncuser --password syncpass
Copied properties for revision 12.
$
版本庫複製只是一個殼,你一定會希望利用這個進程的自動化。例如,如果我們的例子是一個「拖和推」設定,你或許希望在post-commit和post-revprop-change鉤子實現中從你的主版本庫將修改推倒一個或多個鏡像,這樣就可以近乎實時的保持鏡像的時效性。
而且,這樣做並不平凡,在人證用戶只有部分讀權限時svnsync也會優雅的鏡像,它只會拷貝允許查看的版本庫內容,顯然這種鏡像不適合備份方案。
只要用戶與版本庫和鏡像的交互繼續,是可以有一個工作副本直接與這兩個版本庫交互。但是你需要跳出幾個圈子才能做到這樣。第一,你需要保證主和鏡像版本庫有相同的UUID(通常預設不是相同),你可以加載一個包含住版本庫的UUID轉儲文件來設定鏡像版本庫的UUID。
$ cat - <<EOF | svnadmin load --force-uuid dest SVN-fs-dump-format-version: 2 UUID: 65390229-12b7-0310-b90b-f21a5aa7ec8e EOF $
現在兩個版本庫有了相同的UUID,你可以使用svn switch --relocate指向任何你希望操作的版本庫,詳細方法見svn switch。這裡也可能有危險,儘管如果主和鏡像版本庫沒有同步的關閉,一個工作副本對於主版本庫沒有過時,而重定位的鏡像卻是過時的,顯然期望存在的修訂版本缺失會造成困惑。如果發生這個情況,你可以將工作副本重新定位到主版本庫,然後等待鏡像版本庫變成最新,或者將工作副本恢復到你知道的版本庫修訂版本,再嘗試重新定位。
最後我們需要意識到,svnsync只支持修訂版本為基礎的複製,它沒有包括諸如鉤子實現,版本庫或伺服器設定資料,未提交事務或關於用戶鎖定版本庫路徑的訊息,只有Subversion版本庫轉儲文件格式在複製時包含這些訊息。
儘管現代計算機的誕生帶來了許多便利,但有一件事聽起來是完全正確的—有時候,事情變的糟糕,很糟糕,動力損耗、網路中斷、壞掉的記憶體和損壞的硬碟都是對魔鬼的一種體驗,即使對於最盡職的管理員,命運也早已注定。所以我們來到了這個最重要的主題—怎樣備份你的版本庫資料。
Subversion版本庫管理有兩種備份方法—完全和增量。一個完全的版本庫備份包含了在重大災難後重建版本庫所需的所有訊息,通常,這意味著對版本庫目錄(包括Berkeley DB或FSFS環境)的完全複製,增量備份的內容要少一些,只包含在上次備份後改變的部分。
隨著完全備份的使用,這種幼稚的方法或許看起來有點不夠健全,但是除非你臨時關閉所有訪問版本庫的進程,否則這種遞歸的拷貝目錄會有產生錯誤拷貝的風險。Berkeley DB的情況下,其文件中記述了按照什麼順序拷貝可以保證正確的備份拷貝,FSFS也有類似的順序。但是你不必自己實現這種算法,因為Subversion的開發團隊已經這樣做了。svnadmin hotcopy關注了在熱拷貝版本庫時的所有細節,它的調用就像Unix的cp或Windows的copy一樣瑣碎:
$ svnadmin hotcopy /path/to/repos /path/to/repos-backup
作為結果的備份是一個完全功能的版本庫,當發生嚴重錯誤時可以作為你的活動版本庫的替換。
當進行Berkeley DB版本庫的備份時,你可以指導svnadmin hotcopy清理源版本庫中無用的Berkeley DB日誌文件(見「刪除不使用的 Berkeley DB 日誌文件」一節),只需要簡單的在指令列裡提供--clean-logs。
$ svnadmin hotcopy --clean-logs /path/to/bdb-repos /path/to/bdb-repos-backup
還有一些附加的加工命令,Subversion原始碼程式中的tools/backup/目錄包含了hot-backup.py腳本,這個腳本在hot-backup.py之上增加了備份管理功能,你可以保存每個版本庫最近的設定號碼。為了防止與以前的備份衝突,它會自動管理備份版本庫目錄名字,「循環」利用備份名,刪除掉舊的,保存新的。即使你也有一個增量的備份,你還是會希望有規律的運行這個程序。例如,你會在一個調度程序(例如Unix系統的cron)中調用hot-backup.py會導致它在半夜執行(或者是任何你認為安全的時間間隔)。
一些管理員使用不同的備份機制,通過生成和保存版本庫轉儲資料。我們在「版本庫資料的移植」一節中描述如何使用svnadmin dump --incremental來對一個修訂版本或一個修訂版本範圍執行增量備份。當然,通過取消--incremental選項可以得到完整的備份。在備份訊息中方法的值非常靈活—不會與特定平台,版本化的文件系統類型或Subversion和Berkeley DB的版本綁定。但是靈活帶來了代價,資料恢復會佔用更長的時間—比每個新版本提交更長。此外,在非完全的量轉儲生成時,對已經備份修訂版本的修訂版本屬性的修改不會被採納,因為這些原因,我們不建議你單獨依賴轉儲為基礎的備份方法。
如你所見,幾種備份方式都有各自的優點,最簡單的方式是完全熱備份,將會每次建立版本庫的完美複製品,這意味著如果當你的活動版本庫發生了什麼事情,你可以用備份恢復。但不幸的是,如果你維護多個備份,每個完全的備份會吞噬掉和你的活動版本庫同樣的空間。與之相對照的是增量備份,能夠快速生成小的備份,但是恢復過程將會很痛苦,通常要包括多個增量拷貝的應用。其他方法都有自己的特點,管理員需要在建立拷貝和恢復的代價之間尋求平衡。
svnsync(見「版本庫複製」一節)實際上提供了一種更易實施的妥協方法,如果你有規律的同步鏡像版本庫,則在必要時,鏡像版本庫就成了主版本庫發生問題時的一個合適替代者。這個方法最大的缺點是只有版本化的資料得到了同步—版本庫的設定訊息,用戶指定的路徑鎖定和其它以物理形式存在於版本庫路徑而不存在於版本庫虛擬文件系統的項目不會被svnsync處理。
在每一種備份情境下,版本庫管理員需要意識到對未版本化的修訂版本屬性的修改對備份的影響,因為這些修改本身不會產生新的修訂版本,所以不會觸發post-commit的鉤子程序,也不會觸發pre-revprop-change和post-revprop-change的鉤子。 [36]而且因為你可以改變修訂版本的屬性,而不需要遵照時間順序—你可在任何時刻修改任何修訂版本的屬性—因此最新版本的增量備份不會捕捉到以前特定修訂版本的屬性修改。
通常說來,在每次提交時,只有妄想狂才會備份整個版本庫,然而,假設一個給定的版本庫擁有一些恰當粒度的冗余機制(如每次提交的郵件)。版本庫管理員也許會希望將版本庫的熱備份引入到系統級的每夜備份,對大多數版本庫,歸檔的提交郵件為保存資源提供了足夠的冗余措施,至少對於最近的提交。但是它是你的資料—你喜歡怎樣保護都可以。
通常情況下,最好的版本庫備份方式是混合的,你可以平衡完全和增量備份,另外配合提交郵件的歸檔。Subversion開發者,舉個例子,使用hot-backup.py對Subversion版本庫進行完全備份並使用rsync同步這些備份;同時保存所有的提交日至和修改通知郵件;並且使用許多志願者維護的svnsync鏡像版本庫。你們的解決方案可能非常類似,但是要實現滿足需要和便利性的平衡。無論你做了什麼,你需要一次次的驗證你的備份—就像要檢查備用輪胎是否有個窟窿?當然,所有做的事情都無法迴避我們的硬體來自鋼鐵的命運,[37]它將幫助你從艱難的時光恢復過來。
[30] 或者是, 「sync」 ?
[31] 比如:硬碟 + 大號電磁鐵 = 毀滅。
[32] 那就是你是用版本控制的原因,對嗎?
[33] 儘管svnadmin dump對是否以斜線作為路徑的開頭有統一的規定——這個規定就是不以斜線作為路徑的開頭——其它生成轉儲文件的程序不一定會遵守這個規定。
[34] 實際上,它不是真的完全只讀,或者svnsync本身有時間將版本庫歷史拷入。
[35] 要預先警告一下,儘管對於普通讀者只需要幾秒鐘就可以理解下面的輸出,而對於整個鏡像過程花費的時間可能會非常長。
[36] svnadmin setlog可以被繞過鉤子程序被調用。
[37] 你知道的—只是對各種變化莫測的問題的統稱。