### 版本庫維護
維護一個Subversion版本庫是一項令人沮喪的工作,主要因為有數據庫后端與生俱來的復雜性。做好這項工作需要知道一些工具――它們是什么,什么時候用以及如何使用。這一節將會向你介紹Subversion自帶的版本庫管理工具,以及如何使用它們來完成諸如版本庫移植、升級、備份和整理之類的任務。
### 管理員的工具箱
Subversion提供了一些用來創建、查看、修改和修復版本庫的工具。讓我們首先詳細了解一下每個工具,然后,我們再看一下僅在Berkeley DB后端分發版本中提供的版本數據庫工具。
#### 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”)。當版本庫中的`/path/to/repos`的最年輕的修訂版本是19時,下邊的兩個命令執行結果完全相同:
~~~
$ 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.
作者,后接換行。
1.
日期,后接換行。
1.
日志消息的字數,后接換行。
1.
日志信息本身, 后接換行。
這種輸出是人可閱讀的,像是時間戳這種有意義的條目,使用文本表示,而不是其他比較晦澀的方式(例如許多無聊的人推薦的十億分之一秒的數量)。這種輸出也是機器可讀的―因為日志信息可以有多行,沒有長度的限制,**svnlook**在日志消息之前提供了消息的長度,這使得腳本或者其他對這個命令進行的封裝提供了更強的功能,比如日志消息使用了多少內存,或在這個輸出成為最后一個字節之前應該略過多少字節。
另一個**svnlook**常見的用法是查看修訂版本樹或事務樹的內容。**svnlook tree** 命令顯示在請求的樹中的目錄和文件。如果你提供了`--show-ids`選項,它還會顯示每個路徑的文件系統節點修訂版本ID(這一點對開發者往往更有用)。
~~~
$ 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 cat**,**svnlook propget**, 和**svnlook proplist**命令來查看這些目錄和文件的細節。
**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
deltify
dump
help (, h)
…
~~~
我們已經提過**svnadmin**的`create`子命令(參照[“版本庫的創建和配置”一節]( "版本庫的創建和配置"))。本章中我們會詳細講解大多數其他的命令。現在,我們來簡單的看一下每個可用的子命令提供了什么功能。
`create`
創建一個新的Subversion版本庫。
`deltify`
在指定的修訂版本范圍內,對其中修改過的路徑做增量化操作。如果沒有指定修訂版本,這條命令會修改`HEAD`修訂版本。
`dump`
導出版本庫修訂一定版本范圍內的內容,使用可移植轉儲格式。
`hotcopy`
對版本庫做熱拷貝,用這個方法你能任何時候安全的備份版本庫而無需考慮是否正在使用。
`list-dblogs`
(Berkeley DB版本庫專有)列出Berkeley DB中與版本庫有關的日志文件清單。這個清單包括所有的日志文件―仍然被版本庫使用的和不再使用的。
`list-unused-dblogs`
(Berkeley DB版本庫專有)列出Berkeley DB版本庫有關的不在使用日志文件路徑清單。你能安全的從版本庫中刪除那些日志文件,也可以將它們存檔以用來在災難事件后版本庫的恢復。
`load`
導入由`dump`子命令導出的可移植轉儲格式的一組修訂版本。
`lstxns`
列出剛剛在版本庫的沒有提交的Subversion事務清單。
`recover`
恢復版本庫,通常在版本庫發生了致命錯誤的時候,例如阻礙進程干凈的關閉同版本庫的連接的錯誤。
`rmtxns`
從版本庫中清除Subversion事務(通過加工`lstxns`子命令的輸出即可)。
`setlog`
替換給定修訂版本的`svn:log`(提交日志信息)屬性值。
`verify`
驗證版本庫的內容,包括校驗比較本地版本化數據和版本庫。
#### svndumpfilter
因為Subversion使用底層的數據庫儲存各類數據,手工調整是不明智的,即使這樣做并不困難。何況,一旦你的數據存進了版本庫,通常很難再將它們從版本庫中刪除。但是不可避免的,總會有些時候你需要處理版本庫的歷史數據。你也許想把一個不應該出現的文件從版本庫中徹底清除。或者,你曾經用一個版本庫管理多個工程,現在又想把它們分開。要完成這樣的工作,管理員們需要更易于管理和擴展的方法表示版本庫中的數據,Subversion版本庫轉儲文件格式就是一個很好的選擇。
Subversion版本庫轉儲文件記錄了所有版本數據的變更信息,而且以易于閱讀的格式保存。可以使用**svnadmin dump**命令生成轉儲文件,然后用**svnadmin load**命令生成一個新的版本庫。(參見 [“版本庫的移植”一節]( "版本庫的移植"))。轉儲文件易于閱讀意味著你可以小心翼翼的查看和修改它。當然,問題是如果你有一個運行了兩年的版本庫,那么生成的轉儲文件會很龐大,閱讀和手工修改起來都會花費很多時間。
雖然在管理員的日常工作中并不會經常使用,不過**svndumpfilter**可以對特定的路徑進行過濾。這是一個獨特而很有意義的用法,可以幫助你快速方便的修改轉儲的數據。使用時,只需提供一個你想要保留的(或者不想保留的)路徑列表,然后把你的版本庫轉儲文件送進這個過濾器。最后你就可以得到一個僅包含你想保留的路徑的轉儲數據流。
**svndumpfilter**的語法如下:
~~~
$ svndumpfilter help
general usage: svndumpfilter SUBCOMMAND [ARGS & OPTIONS ...]
Type "svndumpfilter help <subcommand>" for help on a specific subcommand.
Available subcommands:
exclude
include
help (, h)
~~~
有意義的子命令只有兩個。你可以使用這兩個子命令說明你希望保留和不希望保留的路徑:
`exclude`
將指定路徑的數據從轉儲數據流中排除。
`include`
將指定路徑的數據添加到轉儲數據流中。
現在我來演示如何使用這個命令。我們會在其它章節(參見 [“選擇一種版本庫布局”一節]( "選擇一種版本庫布局"))討論關于如何選擇設定版本庫布局的問題,比如應該使用一個版本庫管理多個項目還是使用一個版本庫管理一個項目,或者如何在版本庫中安排數據等等。不過,有些時候,即使在項目已經展開以后,你還是希望對版本庫的布局做一些調整。最常見的情況是,把原來存放在同一個版本庫中的幾個項目分開,各自成家。
假設有一個包含三個項目的版本庫: `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.
…
$
~~~
然后,將轉儲文件三次送入過濾器,每次僅保留一個頂級目錄,就可以得到三個轉儲文件:
~~~
$ cat repos-dumpfile | svndumpfilter include calc > calc-dumpfile
…
$ cat repos-dumpfile | svndumpfilter include calendar > cal-dumpfile
…
$ cat repos-dumpfile | svndumpfilter include spreadsheet > 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**的路徑也必須以斜線開頭(反之亦然)。如果因為某些原因轉儲文件中的路徑沒有統一使用或不使用斜線開頭,[[14](#)]也許需要修正這些路徑,統一使用斜線開頭或不使用斜線開頭。
此外,復制操作生成的路徑也會帶來麻煩。Subversion支持在版本庫中進行復制操作,也就是復制一個存在的路徑,生成一個新的路徑。問題是,**svndumpfilter**保留的某個文件或目錄可能是由某個**svndumpfilter**排除的文件或目錄復制而來的。也就是說,為了確保轉儲數據的完整性,**svndumpfilter**需要切斷這些復制自被排除路徑的文件與源文件的關系,還要將這些文件的內容以新建的方式添加到轉儲數據中。但是由于Subversion版本庫轉儲文件格式中僅包含了修訂版本的更改信息,因此源文件的內容基本上無法獲得。如果你不能確定版本庫中是否存在類似的情況,最好重新考慮一下到底保留/排除哪些路徑。
#### svnshell.py
Subversion源代碼樹中有一個類似于shell的版本庫訪問界面。Python腳本**svnshell.py**(位于源代碼樹的`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:`,以及你所在的路徑。你可以用`setrev`和`settxn`切換到其它修訂版本或事務中去。你可以像在Unix shell中那樣,使用`ls`命令列出目錄的內容,使用`cat`命令列出文件的內容。
**例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**命令列出哪些事務可以瀏覽。**lstxns**命令的輸出與**svnadmin lstxns**的輸出相同,設置了`--transaction`選項的**svnlook**命令也可以得到相同的結果。
使用**exit**命令可以退出這個shell。也可以使用文件結束符―Control-D(在某些Win32的Python版本中用Control-Z代替)。
#### Berkeley DB工具
如果你使用Berkeley DB版本庫,那么所有納入版本控制的文件系統結構和數據都儲存在一系列數據庫的表中,而這個位于版本庫的`db`子目錄下。這個子目錄是一個標準的Berkeley DB環境目錄,可以應用任何Berkeley數據庫工具進行操作(參考SleepyCat網站`http://www.sleepycat.com/`上關于這些工具的介紹)。
對于Subversion的日常使用來說,這些工具并沒有什么用處。大多數Subversion版本庫必須的數據庫操作都集成到**svnadmin**工具中。比如,**svnadmin list-unused-dblogs**和**svnadmin list-dblogs**實現了Berkeley **db_archive**命令功能的一個子集,而**svnadmin recover**則起到了 **db_recover**工具的作用。
當然,還有一些Berkeley DB工具有時是有用的。**db_dump**將Berkeley DB數據庫中的鍵值對以特定的格式寫入文件中,而**db_load**則可以將這些鍵值對注入到數據庫中。Berkeley數據庫本身不支持跨平臺轉移,這兩個工具在這樣的情況下就可以實現在平臺間轉移數據庫的功能,而無需關心操作系統或機器架構。此外,**db_stat**工具能夠提供關于Berkeley DB環境的許多有用信息,包括詳細的鎖定和存儲子系統的統計信息。
### 版本庫清理
Subversion版本庫一旦按照需要配置完成,一般情況下不需要特別的關照。不過有些時候還是需要管理員手工干預一下。**svnadmin**工具就能夠幫你完成以下這類工作:
-
修改提交日志信息,
-
移除中止的事務,
-
恢復“塞住”的版本庫,以及
-
將一個版本庫中的內容搬移到另一個版本庫中。
**svnadmin**的子命令中最經常用到的恐怕就是`setlog`。用戶在提交時輸入的日志信息隨著相關事務提交到版本庫并升級成為修訂版本后,便作為新修訂版本的非版本化(即沒有進行版本管理)屬性保存下來。換句話說,版本庫只記得最新的屬性值,而忽略以前的。
有時用戶輸入的日志信息有錯誤(比如拼寫錯誤或者內容錯誤)。如果配置版本庫時設置了(使用`pre-revprop-change`和 `post-revprop-change`鉤子;參見[“鉤子腳本”一節]( "鉤子腳本"))允許用戶在提交后修改日志信息的選項,那么用戶可以使用**svn**程序的`propset`命令(參見[第9章 *Subversion完全參考*]( "第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`選項跳過鉤子。
### 警告
不過需要注意的是,一旦跳過鉤子也就跳過了鉤子所提供的所有功能,比如郵件通知(通知屬性有改動)、系統備份(可以用來跟蹤非版本化的屬性變更)等等。換句話說,要留心你所作出的修改,以及你作出修改的方式。
**svnadmin**的另一個常見用途是查詢異常的―可能是已經死亡的―Subversion事務。通常提交操作失敗時,與之相關的事務就會被清除。也就是說,事務本身及所有與該事務相關(且僅與該事務相關)的數據會從版本庫中刪除。不過偶爾也會出現操作失敗而事務沒有被清除的情況。出現這種情況可能有以下原因:客戶端的用戶粗暴的結束了操作,操作過程中出現網絡故障,等等。不管是什么原因,死亡的事務總是有可能會出現。這類事務不會產生什么負面影響,僅僅是消耗了一點點磁盤空間。不過,嚴厲的管理員總是希望能夠將它們清除出去。
可以使用**svnadmin**的`lstxns` 命令列出當前的異常事務名。
~~~
$ svnadmin lstxns myrepos
19
3a1
a45
$
~~~
將輸出的結果條目作為**svnlook**(設置`--transaction`選項)的參數,就可以獲得事務的詳細信息,如事務的創建者、創建時間,事務已作出的更改類型,由這些信息可以判斷出是否可以將這個事務安全的刪除。如果可以安全刪除,那么只需將事務名作為參數輸入到**svnadmin rmtxns**,就可以將事務清除掉了。其實`rmtxns`子命令可以直接以`lstxns`的輸出作為輸入進行清理。
~~~
$ svnadmin rmtxns myrepos `svnadmin lstxns myrepos`
$
~~~
在按照上面例子中的方法清理版本庫之前,你或許應該暫時關閉版本庫和客戶端的連接。這樣在你開始清理之前,不會有正常的事務進入版本庫。下面例子中的shell腳本可以用來迅速獲得版本庫中異常事務的信息:
**例5.2.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}" --transaction "${TXN}"
done
~~~
可以用下面的命令使用上例中腳本: **/path/to/txn-info.sh /path/to/repos**。該命令的輸出主要由多個**svnlook info**參見[“svnlook”一節]( "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提交日志等等―都可以作為決策的參考。管理員還可以直接和那些似乎已經死亡事務的提交者直接交流(比如通過郵件),來確認該事務確實已經死亡了。
### 管理磁盤空間
雖然存儲器的價格在過去的幾年里以讓人難以致信的速度滑落,但是對于那些需要對大量數據進行版本管理的管理員們來說,磁盤空間的消耗依然是一個重要的因素。版本庫每增加一個字節都意味著需要多一個字節的磁盤空間進行備份,對于多重備份來說,就需要消耗更多的磁盤空間。Berkeley DB版本庫的主要存儲機制是基于一個復雜的數據庫系統建立的,因此了解一些數據性質是有意義的,比如哪些數據必須保留。哪些數據需要備份、哪些數據可以安全的刪除等等。本節的內容專注于Berkeley DB類型的版本庫。FSFS類型的版本庫不需要進行數據清理和回收。
目前為止,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配置”一節]( "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
$ svnadmin list-unused-dblogs /path/to/repos | xargs rm
## disk space reclaimed!
~~~
為了盡可能減小版本庫的體積,Subversion在版本庫中采用了*增量化技術*(或稱為“增量存儲技術”)。增量化技術可以將一組數據表示為相對于另一組數據的不同。如果這兩組數據十分相似,增量化技術就可以僅保存其中一組數據以及兩組數據的差別,而不需要同時保存兩組數據,從而節省了磁盤空間。每次一個文件的新版本提交到版本庫,版本庫就會將之前的版本(之前的多個版本)相對于新版本做增量化處理。采用了這項技術,版本庫的數據量大小基本上是可以估算出來的―主要是版本化的文件的大小―并且遠小于“全文”保存所需的數據量。
### 注意
由于Subversion版本庫的增量化數據保存在單一Berkeley DB數據庫文件中,減少數據的體積并不一定能夠減小數據庫文件的大小。但是,Berkeley DB會在內部記錄未使用的數據庫文件區域,并且在增加數據庫文件大小之前會首先使用這些未使用的區域。因此,即使增量化技術不能立桿見影的節省磁盤空間,也可以極大的減慢數據庫的膨脹速度。
### 版本庫的恢復
[“Berkeley DB”一節]( "Berkeley DB")中曾提到,Berkeley DB版本庫如果沒有正常關閉可能會進入凍結狀態。這時,就需要管理員將數據庫恢復到正常狀態。
Berkeley DB使用一種鎖機制保護版本庫中的數據。鎖機制確保數據庫不會同時被多個訪問進程修改,也就保證了從數據庫中讀取到的數據始終是穩定而且正確的。當一個進程需要修改數據庫中的數據時,首先必須檢查目標數據是否已經上鎖。如果目標數據沒有上鎖,進程就將它鎖上,然后作出修改,最后再將鎖解除。而其它進程則必須等待鎖解除后才能繼續訪問數據庫中的相關內容。
在操作Subversion版本庫的過程中,致命錯誤(如內存或硬盤空間不足)或異常中斷可能會導致某個進程沒能及時將鎖解除。結果就是后端的數據庫系統被“塞住”了。一旦發生這種情況,任何訪問版本庫的進程都會掛起(每個訪問進程都在等待鎖被解除,但是鎖已經無法解除了)。
首先,如果你的版本庫出現這種情況,沒什么好驚慌的。Berkeley DB的文件系統采用了數據庫事務、檢查點以及預寫入日志等技術來取保只有災難性的事件[[15](#)]才能永久性的破壞數據庫環境。所以雖然一個過于穩重的版本庫管理員通常都會按照某種方案進行大量的版本庫離線備份,不過不要急著通知你的管理員進行恢復。
然后,使用下面的方法試著“恢復”你的版本庫:
1.
確保沒有其它進程訪問(或者試圖訪問)版本庫。對于網絡版本庫,關閉Apache HTTP服務器是個好辦法。
1.
成為版本庫的擁有者和管理員。這一點很重要,如果以其它用戶的身份恢復版本庫,可能會改變版本庫文件的訪問權限,導致在版本庫“恢復”后依舊無法訪問。
1.
運行命令**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.
~~~
此命令可能需要數分鐘才能完成。
1.
重新啟動Subversion服務器。
這個方法能修復幾乎所有版本庫鎖住的問題。記住,要以數據庫的擁有者和管理員的身份運行這個命令,而不一定是`root`用戶。恢復過程中可能會使用其它數據存儲區(例如共享內存區)重建一些數據庫文件。如果以`root`用戶身份恢復版本庫,這些重建的文件擁有者將變成`root`用戶,也就是說,即使恢復了到版本庫的連接,一般的用戶也無權訪問這些文件。
如果因為某些原因,上面的方法沒能成功的恢復版本庫,那么你可以做兩件事。首先,將破損的版本庫保存到其它地方,然后從最新的備份中恢復版本庫。然后,發送一封郵件到Subversion用戶列表(地址是:`<[users@subversion.tigris.org]>`),寫清你所遇到的問題。對于Subversion的開發者來說,數據安全是最重要的問題。
### 版本庫的移植
Subversion文件系統將數據保存在許多數據庫表中,而這些表的結構只有Subversion開發者們才了解(也只有他們才感興趣)不過,有些時候我們會想到把所有的數據(或者一部分數據)保存在一個獨立的、可移植的、普通格式的文件中。Subversion通過**svnadmin**的兩個子命令`dump`和`load`提供了類似的功能。
對版本庫的轉儲和裝載的需求主要還是由于Subversion自身處于變化之中。在Subversion的成長期,后端數據庫的設計多次發生變化,這些變化導致之前的版本庫出現兼容性問題。當然,將Berkeley DB版本庫移植到不同的操作系統或者CPU架構上,或者在Berkeley DB和FSFS后端之間進行轉化也需要轉儲和裝載功能。按照下面的介紹,只需簡單幾步就可以完成數據庫的移植:
1.
使用*當前*版本的**svnadmin**將版本庫轉儲到文件中。
1.
升級Subversion。
1.
移除以前的版本庫,并使用*新版本*的**svnadmin**在原來版本庫的位置建立空的版本庫。
1.
還是使用*新版本*的**svnadmin**從轉儲文件中將數據裝載到新建的空版本庫中。
1.
記住從以前的版本庫中復制所有的定制文件到新版本庫中,包括`DB_CONFIG`文件和鉤子腳本。最好閱讀一下新版本的release notes,看看此次升級是否會影響鉤子和配置選項。
1.
如果移植的同時改變的版本庫的訪問地址(比如移植到另一臺計算機或者改變了訪問策略),那么可以通知用戶運行**svn switch --relocate**來切換他們的工作副本。參見[svn switch]( "svn switch")。
**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) >>>
~~~
既然**svnadmin**使用標準輸入流和標準輸出流作為轉儲和裝載的輸入和輸出,那么更漂亮的用法是(管道兩端可以是不同版本的**svnadmin**:
~~~
$ svnadmin create newrepos
$ svnadmin dump myrepos | svnadmin load newrepos
~~~
默認情況下,轉儲文件的體積可能會相當龐大――比版本庫自身大很多。這是因為在轉儲文件中,每個文件的每個版本都以完整的文本形式保存下來。這種方法速度很快,而且很簡單,尤其是直接將轉儲數據通過管道輸入到其它進程中時(比如一個壓縮程序,過濾程序,或者一個裝載進程)。不過如果要長期保存轉儲文件,那么可以使用`--deltas`選項來節省磁盤空間。設置這個選項,同一個文件的數個連續修訂版本會以增量式的方式保存―就像儲存在版本庫中一樣。這個方法較慢,但是轉儲文件的體積則基本上與版本庫的體積相當。
之前我們提到**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`鉤子在每次新的修訂版本提交后將其轉儲到文件中。或者,可以編寫一個腳本,在每天夜里將所有新增的修訂版本轉儲到文件中。這樣,**svnadmin**的`dump`和`load`命令就變成了很好的版本庫備份工具,萬一出現系統崩潰或其它災難性事件,它的價值就體現出來了。
轉儲還可以用來將幾個獨立的版本庫合并為一個版本庫。使用**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.py**工具(參見 [“轉化CVS版本庫到Subversion”一節]( "轉化CVS版本庫到Subversion"))正是將CVS版本庫的內容轉換為轉儲數據格式,如此才能將CVS版本庫的數據導入Subversion版本庫之中。
### 版本庫備份
盡管現代計算機的誕生帶來了許多便利,但有一件事聽起來是完全正確的―有時候,事情變的糟糕,很糟糕,動力損耗、網絡中斷、壞掉的內存和損壞的硬盤都是對魔鬼的一種體驗,即使對于最盡職的管理員,命運也早已注定。所以我們來到了這個最重要的主題―怎樣備份你的版本庫數據。
Subversion版本庫管理員通常有兩種備份方式―增量的和完全的。我們在早先的章節曾經討論過如何使用**svnadmin dump --incremental**命令執行增量備份(見[“版本庫的移植”一節]( "版本庫的移植")),從本質上講,這個方法只是備份了從你上次備份版本庫到現在的變化。
一個完全的版本庫備份照字面上講就是對整個版本庫目錄的復制(包括伯克利數據庫或者文件FSFS環境),現在,除非你臨時關閉了其他對版本庫的訪問,否則僅僅做一次迭代的拷貝會有產生錯誤備份的風險,因為有人可能會在并行的寫數據庫。
如果是伯克利數據庫,惱人的文檔描述了保證安全拷貝的步驟,對于FSFS的數據,也有類似的順序。我們有更好的選擇,我們不需要自己去實現這個算法,因為Subversion開發小組已經為你實現了這些算法。Subversion源文件分發版本的`tools/backup/`目錄有一個**hot-backup.py**文件。只要給定了版本庫路徑和備份路徑,**hot-backup.py**―一個包裹了**svnadmin hotcopy**但更加智能的命令―將會執行必要的步驟來備份你的活動的版本庫―不需要你首先禁止公共的版本庫訪問―而且之后會從你的版本庫清理死掉的伯克利日志文件。
甚至當你用了一個增量備份時,你也會希望有計劃的運行這個程序。舉個例子,你考慮在你的調度程序(如Unix下的**cron**)里加入**hot-backup.py**,或者你喜歡更加細致的備份解決方案,你可以讓你的post-commit的鉤子腳本執行**hot-backup.py**(見see [“鉤子腳本”一節]( "鉤子腳本")),這樣會導致你的版本庫的每次提交執行一次備份,只要在你的`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的鉤子。 而且因為你可以改變修訂版本的屬性,而不需要遵照時間順序―你可在任何時刻修改任何修訂版本的屬性―因此最新版本的增量備份不會捕捉到以前特定修訂版本的屬性修改。
通常說來,在每次提交時,只有妄想狂才會備份整個版本庫,然而,假設一個給定的版本庫擁有一些恰當粒度的冗余機制(如每次提交的郵件)。版本庫管理員也許會希望將版本庫的熱備份引入到系統級的每夜備份,對大多數版本庫,歸檔的提交郵件為保存資源提供了足夠的冗余措施,至少對于最近的提交。但是它是你的數據―你喜歡怎樣保護都可以。
通常情況下,最好的版本庫備份方式是混合的,你可以平衡完全和增量備份,另外配合提交郵件的歸檔,Subversion開發者,舉個例子,在每個新的修訂版本建立時備份Subversion的源代碼版本庫,并且保留所有的提交和屬性修改通知文件。你的解決方案類似,必須迎合你的需要,平衡便利和你的偏執。然而這些不會改變你的硬件來自鋼鐵的命運。 這一定會幫助你減少嘗試的時間。
順便說一句,這是Subversion的*特性*,而不是bug。
盡管**svnadmin dump**對是否以斜線作為路徑的開頭有統一的規定――這個規定就是不以斜線作為路徑的開頭――其它生成轉儲文件的程序不一定會遵守這個規定。
比如:硬盤 + 大號電磁鐵 = 毀滅。
Subversion版本庫的轉儲文件格式類似于RFC-822格式,后者廣泛的應用于電子郵件系統中。
**svnadmin setlog**可以被繞過鉤子程序被調用。
你知道的―只是對各種變化莫測的問題的統稱。
- 第1章介紹
- Subversion的歷史
- Subversion的特性
- Subversion的架構
- 安裝Subversion
- Subversion的組件
- 快速入門
- 第2章基本概念
- 版本模型
- Subversion實戰
- 摘要
- 第3章指導教程
- 導入
- 修訂版本: 號碼、關鍵字和日期,噢,我的!
- 初始化的Checkout
- 基本的工作周期
- 檢驗歷史
- 其他有用的命令
- 摘要
- 第4章分支與合并
- 使用分支
- 在分支間拷貝修改
- 常見用例
- 轉換工作拷貝
- 標簽
- 分支維護
- 摘要
- 第5章版本庫管理
- 版本庫的創建和配置
- 版本庫維護
- 添加項目
- 摘要
- 第6章配置服務器
- 網絡模型
- svnserve,一個自定義的服務器
- httpd,Apache的HTTP服務器
- 支持多種版本庫訪問方法
- 第7章高級主題
- 屬性
- Peg和實施修訂版本
- 外部定義
- 賣主分支
- 本地化
- Subversion版本庫URL
- 第8章開發者信息
- 使用API
- 進入工作拷貝的管理區
- WebDAV
- 使用內存池編程
- 為Subversion做貢獻
- 第9章Subversion完全參考
- svn add
- svn blame
- 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 create
- svnadmin deltify
- svnadmin dump
- svnadmin help
- svnadmin hotcopy
- svnadmin list-dblogs
- svnadmin list-unused-dblogs
- svnadmin load
- svnadmin lstxns
- svnadmin recover
- svnadmin rmtxns
- svnadmin setlog
- svnadmin verify
- svnlook
- svnlook author
- svnlook cat
- svnlook changed
- svnlook date
- svnlook diff
- svnlook dirs-changed
- svnlook help
- svnlook history
- svnlook info
- svnlook log
- svnlook propget
- svnlook proplist
- svnlook tree
- svnlook uuid
- svnlook youngest
- svnserve
- svnversion
- mod_dav_svn Configuration Directives
- 附錄A.Subversion對于CVS用戶
- 目錄的版本
- 更多離線操作
- 區分狀態和更新
- 分支和標簽
- 元數據屬性
- 沖突解決
- 二進制文件和轉化
- 版本化的模塊
- 認證
- 轉化CVS版本庫到Subversion
- 附錄C.WebDAV和自動版本化
- 自動版本化交互性
- Subversion和DeltaV
- 術語表