使用Subversion進行版本控制


目錄git

譯者序
前言
序言
讀者
怎樣閱讀本書
本書約定
排版習慣
圖標
本書組織結構
這本書是免費的
致謝
來自Ben Collins-Sussman
來自Brian W. Fitzpatrick
來自C. Michael Pilato
1. 介紹
Subversion是什麼?
Subversion的歷史
Subversion的特性
Subversion的架構
安裝Subversion
Subversion的組件
快速入門
2. 基本概念
版本庫
版本模型
文件共享的問題
鎖定-修改-解鎖 方案
拷貝-修改-合併 方案
Subversion實戰
工做拷貝
修訂版本
工做拷貝怎樣追蹤版本庫
混合修訂版本的工做拷貝
更新和提交是分開的
混合修訂版本很是正常
混合修訂版本頗有用
混合修訂版本也有限制
摘要
3. 指導教程
幫助!
導入
修訂版本: 號碼、關鍵字和日期,噢,個人!
修訂版本號
修訂版本關鍵字
修訂版本日期
初始化的Checkout
基本的工做週期
更新你的工做拷貝
修改你的工做拷貝
檢查你的修改
svn status
svn diff
svn revert
解決衝突(合併別人的修改)
手工合併衝突
拷貝覆蓋你的工做文件
下注:使用svn revert
提交你得修改
檢驗歷史
svn log
svn diff
比較本地修改
比較工做拷貝和版本庫
比較版本庫與版本庫
svn cat
svn list
關於歷史的最後一個詞
其餘有用的命令
svn cleanup
svn import
摘要
4. 分支與合併
什麼是分支?
使用分支
建立分支
在分支上工做
分支背後的關鍵概念
在分支間拷貝修改
拷貝特定的修改
合併背後的關鍵概念
合併的最佳實踐
手工追蹤合併
預覽合併
合併衝突
關注仍是忽視祖先
常見用例
合併一條分支到另外一支
取消修改
找回刪除的項目
經常使用分支模式
發佈分支
特性分支
轉換工做拷貝
標籤
創建最簡單的標籤
創建複雜的標籤
分支維護
版本庫佈局
數據的生命週期
摘要
5. 版本庫管理
版本庫基本知識
理解事務和修訂版本
未受版本控制的屬性
版本庫數據存儲
Berkeley DB
FSFS
版本庫的建立和配置
鉤子腳本
Berkeley DB配置
版本庫維護
管理員的工具箱
svnlook
svnadmin
svndumpfilter
Berkeley DB工具
版本庫清理
管理磁盤空間
版本庫的恢復
版本庫的移植
版本庫備份
添加項目
選擇一種版本庫佈局
建立佈局,導入初始數據
摘要
6. 配置服務器
概述
網絡模型
請求和響應
客戶端憑證緩存
svnserve,一個自定義的服務器
調用服務器
內置的認證和受權
建立一個用戶文件和域
設置訪問控制
SSH認證和受權
SSH配置技巧
初始設置
控制調用的命令
httpd,Apache的HTTP服務器
必備條件
基本的Apache配置
認證選項
基本HTTP認證
SSL證書管理
受權選項
總體訪問控制
每目錄訪問控制
關閉路徑爲基礎的檢查
額外的糖果
版本庫瀏覽
其它特性
支持多種版本庫訪問方法
7. 高級主題
運行配置區
配置區佈局
配置和Windows註冊表
配置選項
服務器
config
屬性
爲何須要屬性?
處理屬性
特別屬性
svn:executable
svn:mime-type
svn:ignore
svn:keywords
svn:eol-style
svn:externals
svn:special
svn:needs-lock
自動屬性設置
鎖定
建立鎖定
發現鎖定
解除和偷竊鎖定
鎖定交流
Peg和實施修訂版本
外部定義
賣主分支
常規的賣主分支管理過程
svn_load_dirs.pl
本地化
理解地區
Subversion對地區的支持
使用外置區別工具
外置diff
外置diff3
Subversion版本庫URL
8. 開發者信息
分層的庫設計
版本庫層
版本庫訪問層
RA-DAV(使用HTTP/DAV版本庫訪問)
RA-SVN(自定義協議版本庫訪問)
RA-Local(直接版本庫訪問)
你的RA庫在這裏
客戶端層
使用API
Apache可移植運行庫
URL和路徑需求
使用C和C++之外的語言
進入工做拷貝的管理區
條目文件
原始拷貝和屬性文件
WebDAV
使用內存池編程
爲Subversion作貢獻
加入社區
取得源代碼
開始熟悉社區政策
做出修改並測試
貢獻你的修改
9. Subversion徹底參考
Subversion命令行客戶端:svn
svn選項
svn子命令
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 lock
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 unlock
svn update
svnadmin
svnadmin Switches
svnadmin Subcommands
svnadmin create
svnadmin deltify
svnadmin dump
svnadmin help
svnadmin hotcopy
svnadmin list-dblogs
svnadmin list-unused-dblogs
svnadmin load
svnadmin lslocks
svnadmin lstxns
svnadmin recover
svnadmin rmlocks
svnadmin rmtxns
svnadmin setlog
svnadmin verify
svnlook
svnlook選項
svnlook
svnlook author
svnlook cat
svnlook changed
svnlook date
svnlook diff
svnlook dirs-changed
svnlook help
svnlook history
svnlook info
svnlook lock
svnlook log
svnlook propget
svnlook proplist
svnlook tree
svnlook uuid
svnlook youngest
svnserve
svnserve選項
svnversion
svnversion
mod_dav_svn
mod_dav_svn Configuration Directives
A. Subversion對於CVS用戶
修訂版本號如今不一樣了
目錄的版本
更多離線操做
區分狀態和更新
分支和標籤
元數據屬性
衝突解決
二進制文件和轉化
版本化的模塊
認證
轉化CVS版本庫到Subversion
B. WebDAV和自動版本化
WebDAV基本概念
最初的WebDAV
DeltaV擴展
Subversion和DeltaV
自動版本化
客戶端交互性
獨立WebDAV應用程序
Microsoft Office,Dreamweaver,Photoshop
Cadaver,DAV Explorer
文件瀏覽器WebDAV擴展
Microsoft網絡文件夾
Nautilus,Konqueror
WebDAV文件系統實現
WebDrive,NetDrive
Mac OS X
Linux davfs2
C. 第三方工具
客戶端和插件
語言綁定
版本庫轉化
高級工具
版本庫瀏覽工具
D. 版權
術語表

最先接觸這本書是在2004上半年,當時Subversion 1.0剛剛發佈,而我很快把它引入到咱們的項目當中,相對於CVS的簡陋,Subversion顯得很是的完備,是一個通過了深思熟慮的產品,是新一代開源項目的表明。算法

當 我看到這本免費共享的圖書,注意到了它已經在O'Reilly出版,而網站上有最新的版本能夠下載,對於這種開源文化讚歎不已,萌生了本身翻譯這本書的想 法,可是苦於當時對DocBook很是不熟悉,因而使用文本格式,利用閒暇時間翻譯了前四章,但後來瑣事漸多,居然慢慢忘了此事。shell

一轉眼到了2005年,Subversion 1.2發佈了,個人注意力又轉到了這個領域,正好我有了作一個網站的念頭,因此就有了Subversion中文站(http://www.subversion.org.cn),而同時我也開始申請成爲這本書的中文官方翻譯。數據庫

這 本書的官方翻譯要求我必須使用DocBook,要求我必須有一個團隊,因而我在這兩方面進行了努力,因而有人開始與我並肩工做了。在這段翻譯的時間裏陸續 有人加入進來,按照時間順序是rocksun、jerry、nashwang、gxio、MichaelDuan、viv、lifengliu2000、 genedna、luyongshou、leasun和nannan。可是必需要說明這不是對翻譯貢獻大小的排序,你們都在本身的能力範圍內爲這個翻譯作 出了本身的貢獻,感謝咱們成員的努力,也感謝許多對咱們提出建議的朋友。

開始的時候並無以爲作好這件事有多難,但當看到翻譯的東西本身都讀不懂的時候,我感到了一種壓力。若是這翻譯還不如英文,咱們還有沒有必要繼續。好在在你們的支持下,我愈來愈喜歡這本書了,漸漸的發現本身能夠把這本書看成本身的參考材料了。

但 是,我也有過許多疑惑,在中國人們彷佛只是把版本控制工具當作一個代碼分享的工具,而沒有把它融入到整個軟件開發的生命週期當中,這也難怪,大多數中國軟 件的壽命彷佛並不長,不須要那麼多複雜的配置管理。因此咱們的這些翻譯可以給你們帶來多大的幫助要由中國軟件的發展決定,但願咱們的工做可以伴隨着中國軟 件的騰飛不斷成長。

讓咱們一塊兒努力吧!

Rock Sun,青島,2005年11月29日

一個不太好的常見問題列表(FAQ),經常並非由人們實際上的問題組成,而常常是由做者期待的問題組成。或許你曾經見過這種類型的問題:

這樣的FAQ並非其字面意義上的FAQ,沒有人會這樣詢問支持者,「怎樣提升生產率?」相反,人們常常詢問一些更具體的問題,像「怎樣修改日程系統提早兩天而不是提早一天去提醒被提醒人?」 等等。可是經過想象比去發現一個這樣的問題列表更容易,編輯一個真實的問題列表須要持續的,有組織的工做,覆蓋軟件的整個生命週期,提出的問題必須被追 蹤,要監控反饋,全部問題要收集爲一個一致的,可查詢的總體,而且可以反映全部用戶的經驗。這須要耐心,像實地博物學家同樣嚴謹的態度,不該該有浮華的假 設,虛幻的斷言—而須要開放的視野和精確的記錄。

之 因此會喜歡這本書,是由於這本書非凡的成長過程,這體如今每一頁裏,這是做者與用戶直接交流的結果。這一切的基礎是Ben Collins-Sussman's關於Subversion常見問題郵件列表的研究:使用subversion一般的流程是怎樣的?分支與標籤同其它版 本控制系統的工做方式是同樣的嗎?我怎樣知道某一處修改是誰作的?

因爲天天看到相同問題的失落,Ben在2002年夏天努力工做了一個月,撰寫了一本Subversion手冊,一本六十頁,涵蓋了全部基礎使用知識的手冊。這本手冊沒有說明何時要結束,伴隨着Subversion的版本,幫助用戶開始最初的學習。當O'Reilly決定出版一本完備的Subversion圖書的時候,最快捷的方式很明顯,就是擴充這本書。

三 個聯合做者於是面臨了一個不尋常的機會。從職責上講,他們的任務是以一些原始內容爲基礎,從頭至尾寫一個草稿。但實際上他們正在使用着一些豐富的自下而上 的原材料,像一條穩定的河流,也多是一口不可預料的間歇泉。Subversion被數以千計的用戶採用,這些用戶提供了大量的反饋,不只僅針對 Subversion,還包括業已存在的文檔。

在 寫這本書的過程裏,Ben,Mike 和 Brian想鬼魂同樣一直遊蕩在Subversion郵件列表和聊天室中,仔細的研究用戶實際遇到的問題。監視這些反饋是他們在CollabNet工做的 一部分,這給他們開始寫這本書時提供了巨大的便利。這本書創建在豐富經驗的根基之上,並非在流沙同樣的想象之上;它結合了用戶手冊和FAQ最好的一面, 在第一次閱讀時,這種二元性並不明顯,按照順序,從前到後,這本書只是簡單的從頭至尾的關於軟件細節的描述。有一個總的見解,有一個教程,有一章關於管理 配置,還有一些高級主題,固然也有一個命令參考和故障指南。只有當你過一段時間以後,返回來找一些特定問題的解決方案時,這種二元性才得以顯現:這些生動 的細節必定來自不可預料的實際用例的提煉,大可能是源於用戶的須要和視點。

固然,沒人能夠承諾這本書能夠回答全部問題。儘管有時候一些前人提問的驚人一致性讓你感受是心靈感應;你仍有可能在社區的知識庫裏摔跤,空手而歸。若是有這種狀況,最好的辦法是寫明問題發送email到users@subversion.tigris.org>, 做者還在那裏關注着社區,不只僅封面提到的三位,還包括許多曾經做出修正與提供原始材料的人。從社區的視角,幫你解決問題只是逐步的調整這本書,進一步調 整Subversion自己以更合理的適合用戶使用這樣一個大工程的一個有趣的額外效用。他們渴望你的信息,不只僅能夠幫助你,也由於能夠幫助他們。與 Subversion這樣活躍的自由軟件項目一塊兒,你並不孤單

讓這本書將成爲你的第一個夥伴。

Karl Fogel,芝加哥,2004年3月15日

若是C給你足夠的繩子吊死你本身,試着用Subversion做爲一種存放繩子的工具。」 —Brian W. Fitzpatrick

在開源軟件領域,並行版本系統(CVS)一直是版本控制的選擇。恰如其分的是,CVS自己是一個自由軟件,它的非限制性的技法和對網絡操做的支持—容許大量的不一樣地域分散的程序員能夠共享他們工做的特性—很是符合開源軟件領域合做的精神,CVS和它半混亂狀態的開發模型成爲了開源文化的基石。

但 是像許多其餘工具同樣,CVS開始顯露出衰老的跡象。Subversion是一個被設計成爲CVS繼任者的新版本控制系統。設計者經過兩個辦法來爭取現有 的CVS用戶: 使用它構建一個開源軟件系統的版本控制過程,從感受和體驗上與CVS相似,同時Subversion努力彌補CVS許多明顯的缺陷,結果就是不須要版本控 制系統一個大的革新。Subversion是很是強大、有用及靈活的工具。

這本書是爲Subversion 1.2系列撰寫的,咱們試圖涵蓋Subversion的全部內容,可是Subversion有一個興盛和充滿活力的開發社區,已經有許多特性和改進計劃在新的版本中實現,可能會與目前這本書中的命令與細節不一致。

這本書的目標讀者很是的普遍—從從未使用過版本控制的新手到經驗豐富的系統管理員。根據你的基礎,特定的章節可能對你更有用,下面的內容能夠看做是爲各種用戶提供的「推薦閱讀清單」:

資深管理員

假設你之前已經使用過CVS,但願獲得一個Subversion服務器而且儘快運行起來,第 5 章 版本庫管理第 6 章 配置服務器將會告訴你怎樣創建第一個版本庫,而且使之在網絡上可用,此後,根據你的CVS使用經驗,第 3 章 指導教程附錄 A, Subversion對於CVS用戶告訴你怎樣使用Subversion客戶端。

新用戶

你的管理員已經爲你準備好了Subversion服務,你將學習如何使用客戶端。若是你沒有使用過版本控制系統(像CVS),那麼第 2 章 基本概念第 3 章 指導教程是重要的介紹,若是你是CVS的老手,最好從第3章和附錄A開始。

高級用戶

不管你只是個使用者仍是管理員,最終你的項目會長大,你想經過Subversion做許多高級的事情,像如何使用分支和執行合併(第 4 章 分支與合併),怎樣使用Subversion的屬性支持,怎樣配製運行參數(第 7 章 高級主題)等等。這兩章一開始並不重要,但你適應了基本操做以後必定要讀一下。

開發者

大概你已經很熟悉Subversion了,你想擴展它並在它的API基礎之上開發新軟件,第 8 章 開發者信息將是爲你準備的。

這本書以一個參考材料做爲結束—第 9 章 Subversion徹底參考包括了全部命令的參考,這個附錄包括了許多有用的主題,當你完成了本書的閱讀,你會常常去看這個章節。

這一部分包括書中各類約定。

如下是章節和其中的內容介紹:

第 1 章 介紹

記述了Subversion的歷史與特性、架構、組件和安裝方法,還包括一個快速入門指南。

第 2 章 基本概念

解釋了版本控制的基礎知識,介紹了不一樣的版本模型,隨同講述了Subversion的版本庫,工做拷貝和修訂版本的概念。

第 3 章 指導教程

帶領你做爲一個Subversion用戶開始工做,示範了怎樣使用Subversion得到、修改和提交數據。

第 4 章 分支與合併

討論分支、合併與標籤,包括一個最佳實踐,一般的用例,怎樣取消修改,以及怎樣從一個分支轉到另外一個分支。

第 5 章 版本庫管理

講述Subversion版本庫的基本概念,怎樣創建、配置和維護版本庫,以及你能夠使用的工具。

第 6 章 配置服務器

解釋了怎樣配置Subversion服務器,以及三種訪問版本庫的方式,HTTPsvn協議和本地訪問。這裏也介紹了認證的細節,以及受權與匿名訪問方式。

第 7 章 高級主題

研究Subversion客戶配置文件,文件和目錄屬性,怎樣忽略工做拷貝中的文件,怎樣引入外部版本樹到工做拷貝,最後介紹瞭如何掌握賣主分支。

第 8 章 開發者信息

介紹了Subversion的核心,Subversion文件系統,以及從程序員的角度如何看待工做拷貝的管理區域,介紹瞭如何使用公共APIs寫程序使用Subversion,最重要的是,怎樣投身到Subversion的開發當中去。

第 9 章 Subversion徹底參考

深刻研究研究全部的命令,包括 svnsvnadmin、和svnlook以及大量的相關實例

附錄 A, Subversion對於CVS用戶

比較Subversion與CVS的異同點,消除多年使用CVS養出的壞習慣的建議,包括subversion版本號、標記版本的目錄、離線操做、updatestatus、分支、標籤、元數據、衝突和認證。

附錄 B, WebDAV和自動版本化

描述了WebDAV與DeltaV的細節,和怎樣將你的Subversion版本庫做爲可讀/寫的DAV共享裝載。

附錄 C, 第三方工具

討論一些支持和使用Subversion的工具,包括可選的客戶端工具,版本庫瀏覽工具等等。

這本書是做爲Subversion項目的文檔,由開發者開始撰寫的,後來成爲一個獨立工做並進行了重寫,所以,它一直有一個免費許可證(見附錄 D, 版權。)實際上,這本書是在公衆關注中寫出來的,做爲Subversion的一部分,它有兩種含義:

  • 你一直能夠在Subversion的版本庫裏找到本書的最新版本。

  • 對於這本書,你能夠任意分發或者修改—它是免費許可證,固然,相對於發佈你的私有版本,你最好向Subversion開發社區提供反饋。爲了可以參與到社區,見「爲Subversion作貢獻」一節來學習如何加入到社區。

一個相對新的在線版本能夠在http://svnbook.red-bean.com找到。

沒有Subversion就沒有可能(或者有用)有這本書,因此做者很樂意去感謝Brian Behlendorf和CollabNet,有眼光開始這樣一個冒險和野心勃勃的開源項目;Jim Blandy給了Subversion這個名字和最初的設計—咱們愛你。還有Karl Fogel,偉大社區領導和好朋友。 [1]

感謝O'Reilly和咱們的編輯Linda Mui和Tatiana對咱們的耐心的支持。

最後,咱們要感謝數不清的曾經爲社區做出貢獻的人們,他們提供了非正式的審計、建議和修正:這必定不是一個最終的完整列表,離開了這些人的幫助,這本書不會這樣完整和正確:Jani Averbach, Ryan Barrett, Francois Beausoleil, Jennifer Bevan, Matt Blais, Zack Brown, Martin Buchholz, Brane Cibej, John R. Daily, Peter Davis, Olivier Davy, Robert P. J. Day, Mo DeJong, Brian Denny, Joe Drew, Nick Duffek, Ben Elliston, Justin Erenkrantz, Shlomi Fish, Julian Foad, Chris Foote, Martin Furter, Dave Gilbert, Eric Gillespie, Matthew Gregan, Art Haas, Greg Hudson, Alexis Huxley, Jens B. Jorgensen, Tez Kamihira, David Kimdon, Mark Benedetto King, Andreas J. Koenig, Nuutti Kotivuori, Matt Kraai, Scott Lamb, Vincent Lefevre, Morten Ludvigsen, Paul Lussier, Bruce A. Mah, Philip Martin, Feliciano Matias, Patrick Mayweg, Gareth McCaughan, Jon Middleton, Tim Moloney, Mats Nilsson, Joe Orton, Amy Lyn Pilato, Kevin Pilch-Bisson, Dmitriy Popkov, Michael Price, Mark Proctor, Steffen Prohaska, Daniel Rall, Tobias Ringstrom, Garrett Rooney, Joel Rosdahl, Christian Sauer, Larry Shatzer, Russell Steicke, Sander Striker, Erik Sjoelund, Johan Sundstroem, John Szakmeister, Mason Thomas, Eric Wadsworth, Colin Watson, Alex Waugh, Chad Whitacre, Josef Wolf, Blair Zajac, 以及整個Subversion社區。

版本控制是管理信息變化的藝術,它很早就成爲了程序員重要的工具,程序員常常會花時間作一點小修改而後次日又把它改回來。可是版本控制的做用不只在軟件開發領域,任何須要管理頻繁信息改變的地方都須要它,這就是Subversion發揮的舞臺。

這一章是一個對Subversion高層次的介紹—它是什麼;它能作什麼;它是怎樣作到的。

早在2000年,CollabNet, Inc. (http://www.collab.net)就開始尋找CVS替代產品的開發人員,CollabNet提供了一個協做軟件套件叫作CollabNet企業版(CEE)[2],它的一個組件是版本控制系統。儘管SourceCast在初始時使用CVS做爲其版本控制系統,可是CVS的侷限性在一開始就很明顯,CollabNet知道早晚要找到一個更好的替代品。遺憾的是,CVS成爲了開源世界事實上的標準,由於沒有更好的產品,至少是沒有能夠自由使用的。因此CollabNet決定寫一個新的版本控制系統,創建在CVS思想之上的,可是修正其錯誤和不合理的特性。

2000年2月,他們聯繫Open Source Development with CVS(Coriolis, 1999)的做者Karl Fogel,而且詢問他是否但願爲這個新項目工做,巧合的是,當時Karl正在與朋友Jim Blandy討論設計一個新的版本控制系統。在1995年,他們兩個曾經開辦一個提供CVS支持的公司Cyclic Software,儘管他們最終賣掉了公司,但仍是每天使用CVS進行平常工做,在使用CVS時的挫折最終促使他們認真地去考慮如何管理標記版本的數據, 並且他們當時不只僅提出了「Subversion」這個名字,而且作出了Subversion版 本庫的基礎設計。因此當CollabNet提出邀請的時候,Karl立刻贊成爲這個項目工做,同時Jim也獲得了他的僱主,Red Hat軟件贊助他到這個項目並提供了一個寬鬆的時間。CollabNet僱傭了Karl和Ben Collins Sussman,詳細的設計從三月開始,在Behlendorf 、CollabNet、Jason Robbins 和 Greg Stein(當時是一個獨立開發者,活躍在WebDAV/DeltaV系統規範階段)的恰當激勵的幫助下,Subversion很快吸引了許多活躍的開發 者,結果是許多有CVS經驗的人們很樂於有機會爲這個項目作些事情。

最初的設計小組固定在簡單的目標上,他們不想在版本控制方法學中開墾處女地,他們只是但願修正CVS,他們決定Subversion匹配CVS的特性,保 留相同的開發模型,但不復制CVS明顯的缺陷。儘管它不須要成爲CVS的繼任者,它也應該與CVS保持足夠的類似性,使得CVS用戶能夠輕鬆的作出轉換。

通過14個月的編碼,2001年8月31日,Subversion本身可以「成爲服務」了,開發者中止使用CVS保存Subversion的代碼,而使用Subversion自己。

當CollabNet開始這個項目的時候,曾經資助了大量的工做(它爲全職的Subversion開發者提供薪水),Subversion像許多開源項目 同樣,被一些激勵知識界精英的寬鬆透明的規則支配着。CollabNet的版權許可證徹底符合Debian的自由軟件方針,也就是說,任何人能夠自由的下 載,修改和從新發布,不須要通過CollabNet或其餘人的容許。

當討論Subversion爲版本控制領域帶來的特性的時候,經過學習它在CVS基礎上所做的改進會是比較有效的方法。若是你不熟悉CVS,你會不太明白全部的特性,若是你根本就不熟悉版本控制,你會瞪着眼無所適從,你最好首先閱讀一下第 2 章 基本概念,它提供了一個版本控制的簡單介紹。

Subversion提供:

版本化的目錄

CVS只記錄單個文件的歷史,可是Subversion實現了一個能夠跟蹤目錄樹更改的「虛擬」版本化文件系統,文件目錄都是有版本的。

真實的版本歷史

由於CVS只記錄單個文件的版本,對於拷貝和更名—這些文件常常發生的操做,會改變一個目錄的內容—在CVS中並不支持。在CVS裏你也不能夠用一個徹底 不一樣的文件覆蓋原來的同名文件而又不繼承原來文件的歷史。經過Subversion,你能夠對文件或是目錄進行增長、拷貝和更名操做,也能夠新增一個具備 乾淨歷史的文件。

原子提交

一系列的改動,要麼所有提交到版本庫,要麼一個也不提交,這樣可讓用戶構建一個所要提交修改的邏輯塊,防止部分修改提交到版本庫。

版本化的元數據

每個文件或目錄都有一套屬性—鍵和它們的值,你能夠創建並存儲任何鍵/值對,屬性也是隨時間的流逝而歸入版本控制的,很像文件的內容。

可選的網絡層

Subversion在版本庫訪問方面有一個抽象概念,利於人們去實現新的網絡機制,Subversion能夠做爲一個擴展模塊與Apache結合,這給 了Subversion在穩定性和交互性方面很大的好處,能夠直接使用服務器的特性—認證、受權和傳輸壓縮等等。也有一個輕型的,單獨運行的 Subversion服務,這個服務使用本身的協議能夠輕鬆的用SSH封裝。

一致的數據操做

Subversion表示文件是創建在二進制文件區別算法基礎上的,對於文本(可讀)和二進制(不可讀)文件具有一致的操做方式,兩種類型的文件都壓縮存放在版本庫中,區別信息是在網絡上雙向傳遞的。

有效率的分支和標籤

分支與標籤的代價不與工程的大小成比例,Subversion創建分支與標籤時只是拷貝整個工程,使用了一種相似於硬連接的機制,於是這類操做一般只會花費不多而且相對固定的時間。

可修改性

Subversion沒有歷史負擔,它由一系列良好的共享C庫實現,具備定義良好的API,這使得Subversion很是容易維護,能夠輕易的用其餘語言操做。

Subversion創建在一個可移植層上,叫作APR—Apache Portable Runtime library,APR庫提供了許多Subversion在多種操做系統上須要的功能:磁盤訪問、內存管理等等。雖然Subversion能夠使用 Apache做爲服務器程序,這種對APR的依賴並不意味着Apache是必需的組件,APR是能夠獨立使用的庫。這意味着Subversion能夠在所 有可運行Apache服務器的平臺上工做:Windows、Linux、各類BSD、Mac OS X、Netware以及其餘。

最簡單的安裝辦法就是下載相應操做系統的二進制包,Subversion的網站(http://subversion.tigris.org)上一般會有志願者提供的包能夠下載,對於微軟操做系統,網站上一般會有圖形化的安裝包,對於類Unix系統,你能夠使用它們自己的打包系統(PRMs、DEBs、ports tree等等)獲得Subversion。

你也能夠選擇從源代碼直接編譯Subversion,從網站下載最新的源代碼,解壓縮,根據INSTALL文件的指導進行編譯。注意,經過這些源代碼能夠徹底編譯訪問服務器的命令行客戶端工具(一般是apr,apr-util和neno庫)。可是可選部分有許多依賴,如Berkeley DB和Apache httpd。若是你但願作一個徹底的編譯,肯定你有全部INSTALL文件中記述的包。若是你計劃經過Subversiong自己工做,你能夠使用客戶端程序取得最新的,帶血的源代碼,這部份內容見「取得源代碼」一節

許多人爲「從頭至尾」的方式讀一本介紹有趣新技術的書感到發愁,這一小節是一個很短的介紹,給許多「實用」的用戶一個實戰的機會,若是你是一個喜歡經過實驗進行學習的用戶,如下將告訴你怎麼作,相對應,咱們給出這本書相關的連接。

若是版本控制或者Subversion和CVS都用到的「拷貝-修改-合併」模型對於你來講是徹底的新概念,在進一步閱讀以前,你首先要讀第 2 章 基本概念

注意

如下的例子假定你有了1.2或更新的Subversion程序(運行svn --version來檢查版本)。

Subversion存儲全部版本控制的數據到一箇中心版本庫,做爲開始,新建一個版本庫:

$ svnadmin create /path/to/repos
$ ls /path/to/repos
conf/ dav/ db/ format hooks/ locks/ README.txt

這個命令創建了一個新的目錄/path/to/repos,包含了一個Subversion版本庫。這個目錄保存了一些數據庫文件,你打開後看不到你的已經版本化的文件。更多的版本庫建立和維護信息,見第 5 章 版本庫管理

Subversion沒有「項目」的概念,版本庫只是一個虛擬的版本化文件系統,能夠存放你想要得任何文件。有的管理員傾向於一個版本庫只存放一個項目,有的則喜歡存放多個項目到一個版本庫不一樣的目錄裏,每中方式的優勢將會在「選擇一種版本庫佈局」一節討論。每種方式,版本庫都是以「項目」管理文件和目錄,因此或許你會在整本書中常常發現項目這個詞,須要記住咱們只是在談論版本庫中的一些目錄(或者是一組目錄)。

在這個例子裏,咱們假定你已經有了一些但願導入到Subversion版本庫的項目(一組文件和目錄)。首先把這些條目整理到同一個叫作myproject(或任何名稱)的目錄裏,你的項目要有branchestagstrunk三個頂級目錄,後面你就會知道這樣作的緣由。trunk目錄保存全部的數據,而branchestags都是空的:

/tmp/myproject/branches/
/tmp/myproject/tags/
/tmp/myproject/trunk/
foo.c
bar.c
Makefile

branchestagstrunk子目錄不是Subversion必需的,它們只是稍候你就會但願使用的流行習慣。

一旦你你已經準備好了數據,就能夠使用svn import命令(見svn import」一節)將其導入到版本庫:

$ svn import /tmp/myproject file:///path/to/repos/myproject -m "initial import"
Adding /tmp/myproject/branches
Adding /tmp/myproject/tags
Adding /tmp/myproject/trunk
Adding /tmp/myproject/trunk/foo.c
Adding /tmp/myproject/trunk/bar.c
Adding /tmp/myproject/trunk/Makefile

Committed revision 1.
$

如今版本庫包含了這個目錄樹的數據,如前所述,直接察看版本庫看不到文件和目錄;它們存放在數據庫當中,可是版本庫假想的文件系統如今保存了頂級的目錄myproject,其中保存了你的數據。

注意最初的/tmp/myproject並無改變,Subversion並無處理它(實際上,你能夠隨意刪除這個目錄)。爲了開始處理版本庫數據,你須要建立一個新的包含數據的「工做拷貝」,這是一個私有工做區。從Subversion版本庫裏「check out」出一個myproject/trunk目錄的工做拷貝:

$ svn checkout file:///path/to/repos/myproject/trunk myproject
A myproject/foo.c
A myproject/bar.c
A myproject/Makefile

Checked out revision 1.

你如今在myproject目錄裏有了一個版本庫的我的拷貝,你能夠編輯你的工做備份中的文件,而且提交到版本庫。

  • 進入到你的工做備份,編輯一個文件的內容。

  • 運行svn diff來查看你的修改的標準區別輸出。

  • 運行svn commit來提交你的改變到版本庫。

  • 運行svn update將你的工做拷貝與版本庫「同步」。

對於你對工做拷貝可作操做的徹底教程能夠察看第 3 章 指導教程

目前,你能夠選擇使你的版本庫在網絡上可見,能夠參考第 6 章 配置服務器,學習使用不一樣的服務器以及配置。



[2] 也有一個針對小團隊的CollabNet團隊版(CTE)。

這一章是對Subversion一個簡短和隨意的介紹,若是你對版本控制很陌生,這一章節徹底爲你準備的,咱們從討論基本概念開始,深刻理解Subversion的思想,而後展現許多簡單的實例。

儘管咱們的例子展現了人們如何分享程序源代碼,仍然要記住Subversion能夠控制全部類型的文件—它並無限制在只爲程序員工做。

版本控制系統的核心任務是提供協做編輯和數據共享,可是不一樣的系統使用不一樣的策略來達到目的。

全部的版本控制系統都須要解決這樣一個基礎問題:怎樣讓系統容許用戶共享信息,而不會讓他們因意外而互相干擾?版本庫裏意外覆蓋別人的更改很是的容易。

考慮圖 2.2 「須要避免的問題」的情景,咱們有兩個共同工做者,Harry和Sally,他們想同時編輯版本庫裏的同一個文件,若是首先Harry保存它的修改,過了一會,Sally可能湊巧用本身的版本覆蓋了這些文件,Harry的更改不會永遠消失(由於系統記錄了每次修改),Harry全部的修改不會出如今Sally的文件中,因此Harry的工做仍是丟失了—至少是從最新的版本中丟失了—並且是意外的,這就是咱們要明確避免的狀況!

許多版本控制系統使用鎖定-修改-解鎖這種機制解決這種問題,在這樣的系統裏,在一個時間段裏版本庫的一個文件只容許被一我的修改。首先在修改以前,Harry要「鎖定」 住這個文件,鎖定很像是從圖書館借一本書,若是Harry鎖住這個文件,Sally不能作任何修改,若是Sally想請求獲得一個鎖,版本庫會拒絕這個請 求。在Harry結束編輯而且放開這個鎖以前,她只能夠閱讀文件。Harry解鎖後,就要換班了,Sally獲得本身的輪換位置,鎖定而且開始編輯這個文 件。圖 2.3 「鎖定-修改-解鎖 方案」描述了這樣的解決方案。

鎖定-修改-解鎖模型有一點問題就是限制太多,常常會成爲用戶的障礙:

Subversion,CVS和一些版本控制系統使用拷貝-修改-合併模型,在這種模型裏,每個客戶聯繫項目版本庫創建一個我的工做拷貝—版本庫中文件和目錄的本地映射。用戶並行工做,修改各自的工做拷貝,最終,各個私有的拷貝合併在一塊兒,成爲最終的版本,這種系統一般能夠輔助合併操做,可是最終要靠人工去肯定正誤。

這是一個例子,Harry和Sally爲同一個項目各自創建了一個工做拷貝,工做是並行的,修改了同一個文件A,Sally首先保存修改到版本庫,當Harry想去提交修改的時候,版本庫提示文件A已經過時,換句話說,A在他上次更新以後已經更改了,因此當他經過客戶端請求合併版本庫和他的工做拷貝以後,碰巧Sally的修改和他的不衝突,因此一旦他把全部的修改集成到一塊兒,他能夠將工做拷貝保存到版本庫,圖 2.4 「拷貝-修改-合併 方案」圖 2.5 「拷貝-修改-合併 方案(續)」展現了這一過程。

可是若是Sally和Harry的修改交迭了該怎麼辦?這種狀況叫作衝突, 這一般不是個大問題,當Harry告訴他的客戶端去合併版本庫的最新修改到本身的工做拷貝時,他的文件A就會處於衝突狀態:他能夠看到一對衝突的修改集, 並手工的選擇保留一組修改。須要注意的是軟件不能自動的解決衝突,只有人能夠理解並做出智能的選擇,一旦Harry手工的解決了衝突—也許須要與 Sally討論—它能夠安全的把合併的文件保存到版本庫。

拷貝-修改-合併模型感受是有一點混亂,但在實踐中,一般運行的很平穩,用戶能夠並行的工做,沒必要等待別人,當工做在同一個文件上時,也不多會有交迭發生,衝突並不頻繁,處理衝突的時間遠比等待解鎖花費的時間少。

最後,一切都要歸結到一條重要的因素:用戶交流。當用戶交流貧乏,語法和語義的衝突就會增長,沒有系統能夠強制用戶完美的交流,沒有系統能夠檢測語義上的衝突,因此沒有任何證據可以承諾鎖定系統能夠防止衝突,實踐中,鎖定除了約束了生產力,並無作什麼事。

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

你已經閱讀過了關於工做拷貝的內容,如今咱們要講一講客戶端怎樣創建和使用它。

一個Subversion工做拷貝是你本地機器一個普通的目錄,保存着一些文件,你能夠任意的編輯文件,並且若是是源代碼文件,你能夠像日常同樣編譯,你 的工做拷貝是你的私有工做區,在你明確的作了特定操做以前,Subversion不會把你的修改與其餘人的合併,也不會把你的修改展現給別人,你甚至能夠 擁有同一個項目的多個工做拷貝。

當你在工做拷貝做了一些修改而且確認它們工做正常以後,Subversion提供了一個命令能夠「發佈」你的修改給項目中的其餘人(經過寫到版本庫),若是別人發佈了各自的修改,Subversion提供了手段能夠把這些修改與你的工做目錄進行合併(經過讀取版本庫)。

一個工做拷貝也包括一些由Subversion建立並維護的額外文件,用來協助執行這些命令。一般狀況下,你的工做拷貝每個文件夾有一個以.svn爲名的文件夾,也被叫作工做拷貝管理目錄,這個目錄裏的文件可以幫助Subversion識別哪個文件作過修改,哪個文件相對於別人的工做已通過期了。

一個典型的Subversion的版本庫常常包含許多項目的文件(或者說源代碼),一般每個項目都是版本庫的子目錄,在這種安排下,一個用戶的工做拷貝每每對應版本庫的的一個子目錄。

舉一個例子,你的版本庫包含兩個軟件項目,paintcalc。每一個項目在它們各自的頂級子目錄下,見圖 2.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
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,只建立了一個空目錄,沒有任何內容。

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

須要特別注意的是,工做拷貝並不必定對應版本庫中的單個修訂版本,他們可能包含多個修訂版本的文件。舉個例子,你從版本庫檢出一個工做拷貝,最近的修訂號是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在四、五、6修訂版本都是同樣的,可是Subversion會把他的Makefile的修訂號設爲6來代表它是最新的,因此你在工做拷貝頂級目錄做一次乾淨的更新,會使得全部內容對應版本庫的同一修訂版本。

對於工做拷貝的每個文件,Subversion在管理區域.svn/記錄兩項關鍵的信息:

給定這些信息,經過與版本庫通信,Subversion能夠告訴咱們工做文件是處與以下四種狀態的那一種:

這看起來須要記錄不少事情,可是svn status命令能夠告訴你工做拷貝中文件的狀態,關於此命令更多的信息,請看svn status」一節

做 爲一個廣泛原理,Subversion努力作到儘量的靈活,一個特殊的靈活特性就是讓工做拷貝包含不一樣工做修訂版本號的文件和目錄,不幸的是,這個靈活 性會讓許多新用戶感到迷惑。若是上一個混合修訂版本的例子讓你感到困惑,這裏是一個爲什麼有這種特性和如何利用這個特性的基礎介紹。

事實上,每次運行svn commit,你的工做拷貝都會進入混合多個修訂版本的狀態,剛剛提交的文件會比其餘文件有更高的修訂版本號。通過屢次提交(之間沒有更新),你的工做拷貝會徹底是混合的修訂版本。即便只有你一我的使用版本庫,你依然會見到這個現象。爲了檢驗混合工做修訂版本,能夠使用svn status --verbose命令(詳細信息見svn status」一節)。

一般,新用戶對於工做拷貝的混合修訂版本一無所知,這會讓人糊塗,由於許多客戶端命令對於所檢驗條目的修訂版本很敏感。例如svn log命令顯示一個文件或目錄的歷史修改信息(見svn log」一節),當用戶對一個工做拷貝對象調用這個命令,他們但願看到這個對象的整個歷史信息。可是若是這個對象的修訂版本已經至關老了(一般由於很長時間沒有運行svn update),此時會顯示比這個對象更老的歷史。

如今,咱們將要深刻到Subversion到使用細節當中,完成本章,你將學會全部平常使用的Subversion命令,你將從一個初始化檢出開始,作出修改並檢查,你也將會學到如何將別人的修改取到工做拷貝,檢查他們,並解決全部可能發生的衝突。

這一章並非Subversion命令的徹底列表—而是你將會遇到的最經常使用任務的介紹,這一章假定你已經讀過而且理解了第 2 章 基本概念,並且熟悉Subversion的模型,若是想查看全部命令的參考,見第 9 章 Subversion徹底參考

在繼續以前你必定要知道如何識別版本庫的一個修訂版本,像你在「修訂版本」一節看到的,一個修訂版本就是版本庫的一個「快照」,當你的版本庫持續擴大,你必須有手段來識別這些快照。

你能夠使用--revision-r)參數來選擇特定修訂版本(svn --revision REV),你也能夠指定在兩個修訂版本之間的一個範圍 (svn --revision REV1:REV2)。你能夠在Subversion中經過修訂版本號、關鍵字或日期指定特定修訂版本。

Subversion客戶端能夠理解一些修訂版本關鍵字,這些關鍵字能夠用來代替--revision的數字參數,這會被Subversion解釋到特定版本:

下面是一些關鍵字使用的例子,不要擔憂如今沒有意義,咱們將在本章的後面解釋這些命令:

$ svn diff --revision PREV:COMMITTED foo.c
# shows the last change committed to foo.c

$ svn log --revision HEAD
# shows log message for the latest repository commit

$ svn diff --revision HEAD
# compares your working file (with local changes) to the latest version
# in the repository

$ svn diff --revision BASE:HEAD foo.c
# compares your 「pristine」 foo.c (no local changes) with the
# latest version in the repository

$ svn log --revision BASE:HEAD
# shows all commit logs since you last updated

$ svn update --revision PREV foo.c
# rewinds the last change on foo.c
# (foo.c's working revision is decreased)

這些關鍵字容許你執行許多經常使用(並且有用)的操做,而沒必要去查詢特定的修訂版本號,或者記住本地拷貝的修訂版本號。

在任何你使用特定版本號和版本關鍵字的地方,你也能夠在「{}」中使用日期,你也可經過日期或者版本號配合使用來訪問一段時間的修改!

以下是一些Subversion可以接受的日期格式,注意在日期中有空格時須要使用引號。

$ svn checkout --revision {2002-02-17}
$ svn checkout --revision {15:30}
$ svn checkout --revision {15:30:00.200000}
$ svn checkout --revision {"2002-02-17 15:30"}
$ svn checkout --revision {"2002-02-17 15:30 +0230"}
$ svn checkout --revision {2002-02-17T15:30}
$ svn checkout --revision {2002-02-17T15:30Z}
$ svn checkout --revision {2002-02-17T15:30-04:00}
$ svn checkout --revision {20020217T1530}
$ svn checkout --revision {20020217T1530Z}
$ svn checkout --revision {20020217T1530-0500}

當你指定一個日期,Subversion會在版本庫找到接近這個日期的最新版本:

$ svn log --revision {2002-11-28}
------------------------------------------------------------------------
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 --revision {2002-11-20}:4040

用 戶必定要認識到這種精巧會成爲處理日期的絆腳石,由於一個版本的時間戳是做爲一個屬性存儲的—不是版本化的,而是能夠編輯的屬性—版本號的時間戳能夠被修 改,從而創建一個虛假的年表明,也能夠被徹底刪除。這將大大破壞Subversion的這種時間—版本轉化功能的表現。

大多數時候,你會使用checkout從版本庫取出一個新拷貝開始使用Subversion,這樣會在本機建立一個項目的本地拷貝,這個拷貝包括版本庫中的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 2499.

由於Subversion使用「拷貝-修改-合併」模型而不是「鎖定-修改-解鎖」模型(見第 2 章 基本概念),你能夠開始修改工做拷貝中的目錄和文件,你的工做拷貝和你的系統中的其它文件和目錄徹底同樣,你能夠編輯並改變它,移動它,也能夠徹底的刪掉它,把它忘了。

注意

由於你的工做拷貝「同你的系統上的文件和目錄沒有什麼區別」,若是你但願從新規劃工做拷貝,你必需要讓Subversion知道,當你但願拷貝或者移動工做拷貝的一個項目時,你應該使用svn copy或者 svn move而不要使用操做系統的命令,咱們會在之後的章節詳細介紹。

除非你準備好了提交一個新文件或目錄,或改變了已存在的,不然沒有必要通知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有許多特性、選項和華而不實的高級功能,但平常的工做中你只使用其中的一小部分,有一些只在特殊狀況纔會使用,在這一節裏,咱們會介紹許多你在平常工做中常見的命令。

典型的工做週期是這樣的:

如今你能夠開始工做而且修改你的工做拷貝了,你很容易決定做出一個修改(或者是一組),像寫一個新的特性,修正一個錯誤等等。這時能夠使用的Subversion命令包括svn addsvn deletesvn copysvn move。若是你只是修改版本庫中已經存在的文件,在你提交以前,沒必要使用上面的任何一個命令。你能夠對工做備份做的修改包括:

修改文件,能夠使用文本編輯器、字處理軟件、圖形程序或任何你經常使用的工具,Subverion處理二進制文件像同文本文件同樣—效率也同樣。

這些是經常使用的能夠修改目錄樹結構的子命令(咱們會在後麪包括svn importsvn mkdir)。

svn add foo

預約將文件、目錄或者符號鏈foo添加到版本庫,當你下次提交後,foo會成爲其父目錄的一個子對象。注意,若是foo是目錄,全部foo中的內容也會預約添加進去,若是你只想添加foo自己,使用--non-recursive-N)參數。

svn delete foo

預約將文件、目錄或者符號鏈foo從版本庫中刪除掉,若是foo是文件,它立刻從工做拷貝中刪除,若是是目錄,不會被刪除,可是Subversion準備好刪除了,當你提交你的修改,foo就會在你的工做拷貝和版本庫中被刪除。[3]

svn copy foo bar

創建一個新的項目bar做爲foo的複製品,當在下次提交時會將bar添加到版本庫,這種拷貝歷史會記錄下來(按照來自foo的方式記錄),svn copy並不創建中介目錄。

svn move foo bar

這個命令與與運行svn copy foo bar; svn delete foo徹底相同,bar做爲foo的拷貝準備添加,foo已經預約要被刪除,svn move不創建中介的目錄。

當你完成修改,你須要提交他們到版本庫,可是在此以前,檢查一下作過什麼修改是個好主意,經過提交前的檢查,你能夠整理一份精確的日誌信息,你也能夠發現你不當心修改的文件,給了你一次恢復修改的機會。此外,這是一個審查和仔細察看修改的好機會,你可經過命令svn statussvn diffsvn revert精確地察看所作的修改。你能夠使用前兩個命令察看工做拷貝中的修改,使用第三個來撤銷部分(或所有)的修改。

Subversion已經被優化來幫助你完成這個任務,能夠在不與版本庫通信的狀況下作許多事情,詳細來講,對於每個文件,你的的工做拷貝在.svn包含了一個「原始的」拷貝,因此Subversion能夠快速的告訴你那些文件修改了,甚至容許你在不與版本庫通信的狀況下恢復修改。

相對於其餘命令,你會更多地使用這個svn status命令。

若是你在工做拷貝的頂級目錄運行不帶參數的svn status命令,它會檢測你作的全部的文件或目錄的修改,如下的例子是來展現svn status可能返回的狀態碼(注意,#以後的不是svn status打印的)。

  L     some_dir            # svn已經在.svn目錄鎖定了some_dir 
M bar.c # bar.c的內容已經在本地修改過了
M baz.c # baz.c屬性有修改,但沒有內容修改
X 3rd_party # 這個目錄是外部定義的一部分
? foo.o # svn並無管理foo.o
! some_dir # svn管理這個,但它可能丟失或者不完
~ qux # 做爲file/dir/link進行了版本控制,但類型已經改變
I .screenrc # svn無論理這個,配置肯定要忽略它
A + moved_dir # 包含歷史的添加,歷史記錄了它的來歷
M + moved_dir/README # 包含歷史的添加,並有了本地修改
D stuff/fish.c # 這個文件預約要刪除
A stuff/loot/bloo.h # 這個文件預約要添加
C stuff/loot/lump.c # 這個文件在更新時發生衝突

C stuff/loot/glub.c # 文件在更新時發生屬性衝突
R xyz.c # 這個文件預約要被替換
S stuff/squawk # 這個文件已經跳轉到了分支
K dog.jpg # 文件在本地鎖定;有鎖定令牌
O cat.jpg # 文件在版本庫被其餘用戶鎖定
B bird.jpg # 文件本地鎖定,但鎖定發生錯誤
T fish.jpg # 文件本地鎖定,但鎖定丟失

在這種格式下,svn status打印五列字符,緊跟一些空格,接着是文件或者目錄名。第一列告訴一個文件的狀態或它的內容,返回代碼解釋以下:

A item

文件、目錄或是符號鏈item預約加入到版本庫。

C item

文件item發生衝突,在從服務器更新時與本地版本發生交迭,在你提交到版本庫前,必須手工的解決衝突。

D item

文件、目錄或是符號鏈item預約從版本庫中刪除。

M item

文件item的內容被修改了。

R item

文件、目錄或是符號鏈item預約將要替換版本庫中的item,這意味着這個對象首先要被刪除,另一個同名的對象將要被添加,全部的操做發生在一個修訂版本。

X item

目錄沒有版本化,可是與Subversion的外部定義關聯,關於外部定義,能夠看「外部定義」一節

? item

文件、目錄或是符號鏈item不在版本控制之下,你能夠經過使用svn status--quiet-q)參數或父目錄的svn:ignore屬性忽略這個問題,關於忽略文件的使用,見svn:ignore」一節

! item

文件、目錄或是符號鏈item在版本控制之下,可是已經丟失或者不完整,這可能由於使用非Subversion命令刪除形成的,若是是一個目錄,有多是檢出或是更新時的中斷形成的,使用svn update能夠從新從版本庫得到文件或者目錄,也能夠使用svn revert file恢復原來的文件。

~ item

文件、目錄或是符號鏈item在版本庫已經存在,但你的工做拷貝中的是另外一個。舉一個例子,你刪除了一個版本庫的文件,新建了一個在原來的位置,並且整個過程當中沒有使用svn delete或是svn add

I item

文件、目錄或是符號鏈item不在版本控制下,Subversion已經配置好了會在svn addsvn importsvn status命令忽略這個文件,關於忽略文件,見svn:ignore」一節。注意,這個符號只會在使用svn status的參數--no-ignore時纔會出現—不然這個文件會被忽略且不會顯示!

第二列說明文件或目錄的屬性的狀態(更多細節能夠看「屬性」一節),若是一個M出如今第二列,說明屬性被修改了,不然顯示空白。

第三列只顯示空白或者LL表示Subversion已經鎖定了這個目錄的工做區域.svn,當你的svn commit正在運行的時候—也許正在輸入log信息,運行svn status你能夠看到L標記,若是這時候Subversion並無運行,能夠推測Subversion發生中斷而且已經鎖定,你必須運行svn cleanup來清除鎖定(本節後面將有更多論述)。

第四列只會顯示空白或++的意思是一個有附加歷史信息的文件或目錄預約添加或者修改到版本庫,一般出如今svn move或是svn copy時,若是是看到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
Status against revision: 46

注意這兩個星號:若是你如今執行svn update,你的READMEtrout.c會被更新,這告訴你許多有用的信息—你能夠在提交以前,須要使用更新操做獲得文件README的更新,或者說文件已通過時,版本庫會拒絕了你的提交。(後面還有更多關於此主題)。

另外一種檢查修改的方式是svn diff命令,你能夠經過不帶參數的svn diff精確的找出你所作的修改,這會輸出統一區別格式:[4]

$ svn diff
Index: bar.c
===================================================================
--- bar.c (revision 3)
+++ bar.c (working copy)
@@ -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 (revision 3)
+++ README (working copy)
@@ -193,3 +193,4 @@
+Note to self: pick up laundry.

Index: stuff/fish.c
===================================================================
--- stuff/fish.c (revision 1)
+++ stuff/fish.c (working copy)
-Welcome to the file known as 'fish'.
-Information on fish will be here soon.

Index: stuff/things/bloo.h
===================================================================
--- stuff/things/bloo.h (revision 8)
+++ stuff/things/bloo.h (working copy)
+Here is a new file to describe
+things about bloo.

svn diff命令經過比較你的文件和.svn的「原始」文件來輸出信息,預約要增長的文件會顯示全部增長的文本,要刪除的文件會顯示全部要刪除的文本。

輸出的格式爲統一區別格式(unified diff format),刪除的行前面加一個-,添加的行前面有一個+svn diff命令也打印文件名和打補丁須要的信息,因此你能夠經過重定向一個區別文件來生成「補丁」:

$ svn diff > patchfile

舉個例子,你能夠把補丁文件發送郵件到其餘開發者,在提交以前審覈和測試。

咱們能夠使用svn status -u來預測衝突,當你運行svn update一些有趣的事情發生了:

$ svn update
U INSTALL
G README
C bar.c
Updated to revision 46.

UG不必關心,文件乾淨的接受了版本庫的變化,文件標示爲U代表本地沒有修改,文件已經根據版本庫更新。G標示合併,標示本地已經修改過,與版本庫沒有重迭的地方,已經合併。

可是C表示衝突,說明服務器上的改動同你的改動衝突了,你須要本身手工去解決。

當衝突發生了,有三件事能夠幫助你注意到這種狀況和解決問題:

舉一個例子,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: Commit failed (details follow):
svn: Aborting commit: '/home/sally/svn-work/sandwich.txt' remains in conflict

若是你遇到衝突,三件事你能夠選擇:

  • 手動」合併衝突文本(檢查和修改文件中的衝突標誌)。

  • 用某一個臨時文件覆蓋你的工做文件。

  • 運行svn revert <filename>來放棄全部的修改。

一旦你解決了衝突,你須要經過命令svn resolved讓Subversion知道,這樣就會刪除三個臨時文件,Subversion就不會認爲這個文件是在衝突狀態了。[5]

$ 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 Chicken
>>>>>>> .r2

一般你並不但願只是刪除衝突標誌和Sally的修改—當她收到三明治時,會很是的吃驚。因此你應該走到她的辦公室或是拿起電話告訴Sally,你沒辦法從從意大利熟食店獲得想要的泡菜。[6]一旦大家確認了提交內容後,修改文件而且刪除衝突標誌。

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生成的三個文件—包括你未做更新的文件。你也能夠使用第三方的合併工具檢驗這三個文件。

最後!你的修改結束了,你合併了服務器上全部的修改,你準備好提交修改到版本庫。

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.txt
Transmitting file data .
Committed revision 4.

若是你沒有指定--message或者--file選項,Subversion會自動地啓動你最喜歡的編輯器(見「config」一節editor-cmd部分)來編輯日誌信息。

提示

若是你使用編輯器撰寫日誌信息時但願取消提交,你能夠直接關掉編輯器,不要保存,若是你已經作過保存,只要簡單的刪掉全部的文本並再次保存。

$ 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: Commit failed (details follow):
svn: Out of date: 'rules.txt' in transaction 'g'

此刻,你須要運行svn update來處理全部的合併和衝突,而後再嘗試提交。

咱們已經覆蓋了Subversion基本的工做週期,還有許多其它特性能夠管理你得版本庫和工做拷貝,可是隻使用前面介紹的命令你就能夠很輕鬆的工做了。

咱們曾經說過,版本庫就像是一臺時間機器,它記錄了全部提交的修改,容許你檢查文件或目錄以及相關元數據的歷史。經過一個Subversion命令你能夠根據時間或修訂號取出一個過去的版本(或者恢復如今的工做拷貝),然而,有時候咱們只是想看看歷史而不想回到歷史。

有許多命令能夠爲你提供版本庫歷史:

找出一個文件或目錄的歷史信息,使用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 | 1 line

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

注意日誌信息缺省根據時間逆序排列,若是但願察看特定順序的一段修訂版本或者單一版本,使用--revision-r)選項:

$ svn log --revision 5:19    # shows logs 5 through 19 in chronological order

$ svn log -r 19:5 # shows logs 5 through 19 in reverse order

$ svn log -r 8 # shows log for revision 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:
M /trunk/code/foo.c
M /trunk/code/bar.h
A /trunk/code/doc/README

Frozzled the sub-space winch.

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

svn log也有一個--quiet (-q)選項,會禁止日誌信息的主要部分,當與--verbose結合使用,僅會顯示修改的文件名。

咱們已經看過svn diff—使用標準區別文件格式顯示區別,它在提交前用來顯示本地工做拷貝與版本庫的區別。

事實上,svn diff種不一樣的用法:

除了以上的命令,你能夠使用帶參數--revisionsvn updatesvn checkout來使整個工做拷貝「回到過去[7]

$ svn checkout --revision 1729 # Checks out a new working copy at r1729

$ svn update --revision 1729 # Updates an existing working copy to r1729

不象這章前面討論的那些常常用到的命令,這些命令只是偶爾被用到。

咱們已經覆蓋了大多數Subversion的客戶端命令,引人注目的例外是處理分支與合併(見第 4 章 分支與合併)以及屬性(見「屬性」一節)的命令,然而你也許會但願跳到第 9 章 Subversion徹底參考來察看全部不一樣的命令—怎樣利用它們使你的工做更容易。



[3] 固然沒有任何東西是在版本庫裏被刪除了—只是在版本庫的HEAD裏消失了,你能夠經過檢出(或者更新你的工做拷貝)你作出刪除操做的前一個修訂版原本找回全部的東西。

[4] Subversion使用內置區別引擎,缺省狀況下輸出爲統一區別格式。若是你指望不一樣的輸出格式,你能夠使用--diff-cmd指定外置的區別程序,而且經過--extensions傳遞其餘參數,舉個例子,察看本地文件foo.c的區別,同時忽略空格修改,你能夠運行svn diff --diff-cmd /usr/bin/diff --extensions '-bc' foo.c

[5] 你也能夠手工的刪除這三個臨時文件,可是當Subversion會給你作時你會本身去作嗎?咱們是這樣想的。

[6] 若是你向他們詢問,他們很是有理由把你帶到城外的鐵軌上。

[7] 看到了吧?咱們說過Subversion是一個時間機器。

分支、標籤和合並是全部版本控制系統的共同概念,若是你並不熟悉這些概念,咱們會在這一章裏很好的介紹,若是你很熟悉,很是但願你有興趣知道Subversion是怎樣實現這些概念的。

分支是版本控制的基礎組成部分,若是你容許Subversion來管理你的數據,這個特性將是你所必須依賴的 ,這一章假定你已經熟悉了Subversion的基本概念(第 2 章 基本概念)。

在這一點上,你必須理解每一次提交是怎樣創建整個新的文件系統樹(叫作「修訂版本」)的,若是沒有,能夠回頭去讀「修訂版本」一節

對於本章節,咱們會回到第2章的同一個例子,還記得你和你的合做者Sally分享一個包含兩個項目的版本庫,paintcalc。注意圖 4.2 「開始規劃版本庫」,然而,如今每一個項目的都有一個trunkbranches子目錄,它們存在的理由很快就會清晰起來。

像之前同樣,假定Sally和你都有「calc」項目的一份拷貝,更準確地說,你有一份/calc/trunk的工做拷貝,這個項目的全部的文件在這個子目錄裏,而不是在/calc下,由於你的小組決定使用/calc/trunk做爲開發使用的「主線」。

假定你有一個任務,將要對項目作基本的從新組織,這須要花費大量時間來完成,會影響項目的全部文件,問題是你不會但願打擾Sally,她正在處理這樣或那樣的程序小Bug,一直使用整個項目(/calc/trunk)的最新版本,若是你一點一點的提交你的修改,你必定會干擾Sally的工做。

一 種策略是本身閉門造車:你和Sally能夠中止一個到兩個星期的共享,也就是說,開始做出本質上的修改和從新組織工做拷貝的文件,可是在完成這個任務以前 不作提交和更新。這樣會有不少問題,首先,這樣並不安全,許多人習慣頻繁的保存修改到版本庫,工做拷貝必定有許多意外的修改。第二,這樣並不靈活,若是你 的工做在不一樣的計算機(或許你在不一樣的機器有兩份/calc/trunk的工做拷貝),你須要手工的來回拷貝修改,或者只在一個計算機上工做,這時很難作到共享你即時的修改,一項軟件開發的「最佳實踐」 就是容許審覈你作過的工做,若是沒有人看到你的提交,你失去了潛在的反饋。最後,當你完成了公司主幹代碼的修改工做,你會發現合併你的工做拷貝和公司的主 幹代碼會是一件很是困難的事情,Sally(或者其餘人)也許已經對版本庫作了許多修改,已經很難和你的工做拷貝結合—當你單獨工做幾周後運行svn update時就會發現這一點。

最佳方案是建立你本身的分支,或者是版本庫的開發線。這容許你保存破壞了一半的工做而不打擾別人,儘管你仍能夠選擇性的同你的合做者分享信息,你將會看到這是怎樣工做的。

創建分支很是的簡單—使用svn copy命令給你的工程作個拷貝,Subversion不只能夠拷貝單個文件,也能夠拷貝整個目錄,在目前狀況下,你但願做/calc/trunk的拷貝,新的拷貝應該在哪裏?在你但願的任何地方—它只是在於項目的政策,咱們假設大家項目的政策是在/calc/branches創建分支,而且你但願把你的分支叫作my-calc-branch,你但願創建一個新的目錄/calc/branches/my-calc-branch,做爲/calc/trunk的拷貝開始它的生命週期。

有兩個方法做拷貝,咱們首先介紹一個混亂的方法,只是讓概念更清楚,做爲開始,取出一個工程的根目錄,/calc

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

創建一個備份只是傳遞兩個目錄參數到svn copy命令:

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

在這個狀況下,svn copy命令迭代的將trunk工做目錄拷貝到一個新的目錄branhes/my-calc-branch,像你從svn status看到的,新的目錄是準備添加到版本庫的,可是也要注意A後面的「+」號,這代表這個準備添加的東西是一份備份,而不是新的東西。當你提交修改,Subversion會經過拷貝/calc/trunk創建/calc/branches/my-calc-branch目錄,而不是經過網絡傳遞全部數據:

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

如今,咱們必須告訴你創建分支最簡單的方法:svn copy能夠直接對兩個URL操做。

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

Committed revision 341.

其實這兩種方法沒有什麼區別,兩個過程都在版本341創建了一個新目錄做爲/calc/trunk的一個備份,這些能夠在圖 4.3 「拷貝後的版本庫」看到,注意第二種方法,只是執行了一個當即提交。 [8]這是一個簡單的過程,由於你不須要取出版本庫一個龐大的鏡像,事實上,這個技術不須要你有工做拷貝。

如今你已經在項目裏創建分支了,你能夠取出一個新的工做拷貝來開始使用:

$ svn checkout http://svn.example.com/repos/calc/branches/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在更新時不會看到改變,她是/calc/trunk的工做拷貝。(肯定要讀本章後面的「轉換工做拷貝」一節svn switch命令是創建分支工做拷貝的另外一個選擇。)

咱們假定本週就要過去了,以下的提交發生:

  • 你修改了/calc/branches/my-calc-branch/button.c,生成版本號342。

  • 你修改了/calc/branches/my-calc-branch/integer.c,生成版本號343。

  • Sally修改了/calc/trunk/integer.c,生成了版本號344。

如今有兩個獨立開發線,圖 4.4 「一個文件的分支歷史」顯示了integer.c的歷史。

當你看到integer.c的改變時,你會發現頗有趣:

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

$ svn log --verbose integer.c
------------------------------------------------------------------------
r343 | user | 2002-11-07 15:27:56 -0600 (Thu, 07 Nov 2002) | 2 lines
Changed paths:
M /calc/branches/my-calc-branch/integer.c

* integer.c: frozzled the wazjub.

------------------------------------------------------------------------
r341 | user | 2002-11-03 15:27:56 -0600 (Thu, 07 Nov 2002) | 2 lines
Changed paths:
A /calc/branches/my-calc-branch (from /calc/trunk:340)

Creating a private branch of /calc/trunk.

------------------------------------------------------------------------
r303 | sally | 2002-10-29 21:14:35 -0600 (Tue, 29 Oct 2002) | 2 lines
Changed paths:
M /calc/trunk/integer.c

* integer.c: changed a docstring.

------------------------------------------------------------------------
r98 | sally | 2002-02-22 15:35:29 -0600 (Fri, 22 Feb 2002) | 2 lines
Changed paths:
M /calc/trunk/integer.c

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

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

注意,Subversion追蹤分支上的integer.c的歷史,包括全部的操做,甚至追蹤到拷貝以前。這表示了創建分支也是歷史中的一次事件,由於在拷貝整個/calc/trunk/時已經拷貝了一份integer.c。如今看Sally在她的工做拷貝運行一樣的命令:

$ pwd
/home/sally/calc

$ svn log --verbose integer.c
------------------------------------------------------------------------
r344 | sally | 2002-11-07 15:27:56 -0600 (Thu, 07 Nov 2002) | 2 lines
Changed paths:
M /calc/trunk/integer.c

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

------------------------------------------------------------------------
r303 | sally | 2002-10-29 21:14:35 -0600 (Tue, 29 Oct 2002) | 2 lines
Changed paths:
M /calc/trunk/integer.c

* integer.c: changed a docstring.

------------------------------------------------------------------------
r98 | sally | 2002-02-22 15:35:29 -0600 (Fri, 22 Feb 2002) | 2 lines
Changed paths:
M /calc/trunk/integer.c

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

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

sally看到她本身的344修訂,你作的343修改她看不到,從Subversion看來,兩次提交只是影響版本庫中不一樣位置上的兩個文件。然而,Subversion顯示了兩個文件有共同的歷史,在分支拷貝以前,他們使用同一個文件,因此你和Sally都看到版本號303到98的修改。

如今你與Sally在同一個項目的並行分支上工做:你在私有分支上,而Sally在主幹(trunk)或者叫作開發主線上。

因爲有衆多的人蔘與項目,大多數人擁有主幹拷貝是很正常的,任何人若是進行一個長週期的修改會使得主幹陷入混亂,因此一般的作法是創建一個私有分支,提交修改到本身的分支,直到這階段工做結束。

因此,好消息就是你和Sally不會互相打擾,壞消息是有時候分離會遠。記住「閉門造車」策略的問題,當你完成你的分支後,可能由於太多衝突,已經沒法輕易合併你的分支和主幹的修改。

相反,在你工做的時候你和Sally仍然能夠繼續分享修改,這依賴於你決定什麼值得分享,Subversion給你在分支間選擇性「拷貝」修改的能力,當你完成了分支上的全部工做,全部的分支修改能夠被拷貝回到主幹。

在上一章節,咱們提到你和Sally對integer.c在 不一樣的分支上作過修改,若是你看了Sally的344版本的日誌信息,你會知道她修正了一些拼寫錯誤,毋庸置疑,你的拷貝的文件也必定存在這些拼寫錯誤, 因此你之後的對這個文件修改也會保留這些拼寫錯誤,因此你會在未來合併時獲得許多衝突。最好是如今接收Sally的修改,而不是做了許多工做以後纔來作。

是時間使用svn merge命令,這個命令的結果很是相似svn diff命令(在第3章的內容),兩個命令均可以比較版本庫中的任何兩個對象而且描述其區別,舉個例子,你能夠使用svn diff來查看Sally在版本344做的修改:

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

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/calc/trunk
U integer.c

$ svn status
M integer.c

svn merge的輸出告訴你的integer.c文件已經做了補丁(patched),如今已經保留了Sally修改—修改從主幹「拷貝」到你的私有分支的工做拷貝,如今做爲一個本地修改,在這種狀況下,要靠你審查本地的修改來肯定它們工做正常。

在另外一種情境下,事情並不會運行得這樣正常,也許integer.c也許會進入衝突狀態,你必須使用標準過程(見第三章)來解決這種狀態,或者你認爲合併是一個錯誤的決定,你只須要運行svn revert放棄。

可是當你審查過你的合併結果後,你能夠使用svn commit提交修改,在那一刻,修改已經合併到你的分支上了,在版本控制術語中,這種在分支之間拷貝修改的行爲叫作搬運修改。

當你提交你的修改時,肯定你的日誌信息中說明你是從某一版本搬運了修改,舉個例子:

$ svn commit -m "integer.c: ported r344 (spelling fixes) from trunk."
Sending integer.c
Transmitting file data .
Committed revision 360.

你將會在下一節看到,這是一條很是重要的「最佳實踐」。

一個警告:爲何svn diffsvn merge在概念上是很接近,但語法上有許多不一樣,必定閱讀第9章來查看其細節或者使用svn help查看幫助。舉個例子,svn merge須要一個工做拷貝做爲目標,就是一個地方來施展目錄樹修改,若是一個目標都沒有指定,它會假定你要作如下某個普通的操做:

  1. 你但願合併目錄修改到工做拷貝的當前目錄。

  2. 你但願合併修改到你的當前工做目錄的相同文件名的文件。

若是你合併一個目錄而沒有指定特定的目標,svn merge假定第一種狀況,在你的當前目錄應用修改。若是你合併一個文件,而這個文件(或是一個有相同的名字文件)在你的當前工做目錄存在,svn merge假定第二種狀況,你想對這個同名文件使用合併。

若是你但願修改應用到別的目錄,你須要說出來。舉個例子,你在工做拷貝的父目錄,你須要指定目標目錄:

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

你已經看到了svn merge命 令的例子,你將會看到更多,若是你對合並是如何工做的感到迷惑,這並不奇怪,不少人和你同樣。許多新用戶(特別是對版本控制很陌生的用戶)會對這個命令的 正確語法感到不知所措,不知道怎樣和何時使用這個特性,不要懼怕,這個命令實際上比你想象的簡單!有一個簡單的技巧來幫助你理解svn merge的行爲。

迷惑的主要緣由是這個命令的名稱,術語「合併」不知什麼緣由被用來代表分支的組合,或者是其餘什麼神奇的數據混合,這不是事實,一個更好的名稱應該是svn diff-and-apply,這是發生的全部事件:首先兩個版本庫樹比較,而後將區別應用到本地拷貝。

這個命令包括三個參數:

一旦這三個參數指定之後,兩個目錄樹將要作比較,比較結果將會做爲本地修改應用到目標工做拷貝,當命令結束後,結果同你手工修改或者是使用svn addsvn delete沒有什麼區別,若是你喜歡這結果,你能夠提交,若是不喜歡,你能夠使用svn revert恢復修改。

svn merge的語法容許很是靈活的指定參數,以下是一些例子:

      
$ svn merge http://svn.example.com/repos/branch1@150 /
http://svn.example.com/repos/branch2@212 /
my-working-copy

$ svn merge -r 100:200 http://svn.example.com/repos/trunk my-working-copy

$ svn merge -r 100:200 http://svn.example.com/repos/trunk

第一種語法使用URL@REV的形式直接列出了全部參數,第二種語法能夠用來做爲比較同一個URL的不一樣版本的簡略寫法,最後一種語法表示工做拷貝是可選的,若是省略,默認是當前目錄。

就像svn update命令,svn merge會把修改應用到工做拷貝,所以它也會形成衝突,由於svn merge形成的衝突有時候會有些不一樣,本小節會解釋這些區別。

做爲開始,咱們假定本地沒有修改,當你svn update到一個特定修訂版本時,修改會「乾淨的」應用到工做拷貝,服務器產生比較兩樹的增量數據:一個工做拷貝和你關注的版本樹的虛擬快照,由於比較的左邊同你擁有的徹底相同,增量數據確保你把工做拷貝轉化到右邊的樹。

可是svn merge沒有這樣的保證,會致使不少的混亂:用戶能夠詢問服務器比較任何兩個樹,即便一個與工做拷貝絕不相關的!這意味着有潛在的人爲錯誤,用戶有時候會比較兩個錯誤的樹,建立的增量數據不會乾淨的應用,svn merge會盡力應用更多的增量數據,可是有一些部分也許會難以完成,就像Unix下patch命令有時候會報告「failed hunks」錯誤,svn merge會報告「skipped targets」:

$ svn merge -r 1288:1351 http://svn.example.com/repos/branch
U foo.c
U bar.c
Skipped missing target: 'baz.c'
U glub.c
C glorb.h

$

在前一個例子中,baz.c也許會存在於比較的兩個分支快照裏,但工做拷貝里不存在,比較的增量數據要應用到這個文件,這種狀況下會發生什麼?「skipped」信息意味着用戶多是在比較錯誤的兩棵樹,這是經典的驅動器錯誤,當發生這種狀況,能夠使用迭代恢復(svn revert --recursive)合併所做的修改,刪除恢復後留下的全部未版本化的文件和目錄,而且使用另外的參數運行svn merge

也應當注意前一個例子顯示glorb.h發生了衝突,咱們已經規定本地拷貝沒有修改:衝突怎麼會發生呢?由於用戶能夠使用svn merge將過去的任何變化應用到當前工做拷貝,變化包含的文本修改也許並不能乾淨的應用到工做拷貝文件,即便這些文件沒有本地修改。

另外一個svn updatesvn merge的小區別是衝突產生的文件的名字不一樣,在「解決衝突(合併別人的修改)」一節,咱們看到過更新產生的文件名字爲filename.minefilename.rOLDREVfilename.rNEWREV,當svn merge產生衝突時,它產生的三個文件分別爲 filename.workingfilename.leftfilename.right。在這種狀況下,術語「left」和「right」表示了兩棵樹比較時的兩邊,在兩種狀況下,不一樣的名字會幫助你區分衝突是由於更新形成的仍是合併形成的。

當與Subversion開發者交談時你必定會聽到說起術語祖先,這個詞是用來描述兩個對象的關係:若是他們互相關聯,一個對象就是另外一個的祖先,或者相反。

舉個例子,假設你提交版本100,包括對foo.c的修改,則foo.c@99是foo.c@100的一個「祖先」,另外一方面,假設你在版本101刪除這個文件,而在102版本提交一個同名的文件,在這個狀況下,foo.c@99foo.c@102看起來是關聯的(有一樣的路徑),可是事實上他們是徹底不一樣的對象,它們並不共享同一個歷史或者說「祖先」。

指出svn diffsvn merge區別的重要性在於,前一個命令忽略祖先,若是你詢問svn diff來比較文件foo.c的版本99和102,你會看到行爲基礎的區別,diff命令只是盲目的比較兩條路徑,可是若是你使用svn merge是比較一樣的兩個對象,它會注意到他們是不關聯的,並且首先嚐試刪除舊文件,而後添加新文件,輸出會是一個刪除緊接着一個增長:

D  foo.c
A foo.c

大多數合併包括比較包括祖先關聯的兩條樹,所以svn merge這樣運做,然而,你也許會但願merge命令可以比較兩個不相關的目錄樹,舉個例子,你有兩個目錄樹分別表明了賣主軟件項目的不一樣版本(見「賣主分支」一節),若是你使用svn merge進行比較,你會看到第一個目錄樹被刪除,而第二個樹添加上!

在這個狀況下,你只是但願svn merge可以作一個以路徑爲基礎的比較,忽略全部文件和目錄的關係,增長--ignore-ancestry選項會致使命令象svn diff同樣。(相應的,--notice-ancestry選項會使svn diffmerge命令同樣行事。)

分支和svn merge有不少不一樣的用法,這個小節描述了最多見的用法。

爲了完成這個例子,咱們將時間往前推動,假定已通過了幾天,在主幹和你的分支上都有許多更改,假定你完成了分支上的工做,已經完成了特性或bug修正,你想合併全部分支的修改到主幹上,讓別人也能夠使用。

這種狀況下如何使用svn merge?記住這個命令比較兩個目錄樹,而後應用比較結果到工做拷貝,因此要接受這種變化,你須要主幹的工做拷貝,咱們假設你有一個最初的主幹工做拷貝(徹底更新),或者是你最近取出了/calc/trunk的一個乾淨的工做拷貝。

可是要哪兩個樹進行比較呢?乍一看,回答很明確,只要比較最新的主幹與分支。可是你要意識到—這個想法是錯誤的,傷害了許多新用戶!由於svn merge的操做很像svn diff,比較最新的主幹和分支樹不只僅會描述你在分支上所做的修改,這樣的比較會展現太多的不一樣,不只包括分支上的增長,也包括了主幹上的刪除操做,而這些刪除根本就沒有在分支上發生過。

爲了表示你的分支上的修改,你只須要比較分支的初始狀態與最終狀態,在你的分支上使用svn log命令,你能夠看到你的分支在341版本創建,你的分支最終的狀態用HEAD版本表示,這意味着你但願可以比較版本341和HEAD的分支目錄,而後應用這些分支的修改到主幹目錄的工做拷貝。

以下是最終的合併過程,而後:

$ cd calc/trunk
$ svn update
At revision 405.

$ svn merge -r 341:405 http://svn.example.com/repos/calc/branches/my-calc-branch
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 my-calc-branch changes r341:405 into the trunk."
Sending integer.c
Sending button.c
Sending Makefile
Transmitting file data ...
Committed revision 406.

再次說明,日誌信息中詳細描述了合併到主幹的的修改範圍,記住必定要這麼作,這是你之後須要的重要信息。

舉個例子,你但願在分支上繼續工做一週,來進一步增強你的修正,這時版本庫的HEAD版本是480,你準備好了另外一次合併,可是咱們在「合併的最佳實踐」一節提到過,你不想合併已經合併的內容,你只想合併新的東西,技巧就是指出什麼是「」的。

第一步是在主幹上運行svn log察看最後一次與分支合併的日誌信息:

$ cd calc/trunk
$ svn log

------------------------------------------------------------------------
r406 | user | 2004-02-08 11:17:26 -0600 (Sun, 08 Feb 2004) | 1 line

Merged my-calc-branch changes r341:405 into the trunk.
------------------------------------------------------------------------

阿哈!由於分支上341到405之間的全部修改已經在版本406合併了,如今你只須要合併分支在此以後的修改—經過比較406和HEAD

$ cd calc/trunk
$ svn update
At revision 480.

# We notice that HEAD is currently 480, so we use it to do the merge:

$ svn merge -r 406:480 http://svn.example.com/repos/calc/branches/my-calc-branch
U integer.c
U button.c
U Makefile

$ svn commit -m "Merged my-calc-branch changes r406:480 into the trunk."
Sending integer.c
Sending button.c
Sending Makefile
Transmitting file data ...
Committed revision 481.

如今主幹有了分支上第二波修改的徹底結果,此刻,你能夠刪除你的分支(咱們會在之後討論),或是繼續在你分支上工做,重複這個步驟。

svn merge另外一個經常使用的作法是取消已經作得提交,假設你愉快的在/calc/trunk工做,你發現303版本對integer.c的修改徹底錯了,它不該該被提交,你能夠使用svn merge來「取消」這個工做拷貝上所做的操做,而後提交本地修改到版本庫,你要作得只是指定一個相反的區別:

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

$ svn status
M integer.c

$ svn diff

# verify that the change is removed


$ 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 status或是svn diff來肯定你的工做處於指望的狀態中,而後使用svn commit來提交,提交以後,這個特定修改集不會反映到HEAD版本了。

繼續,你也許會想:好吧,這不是真的取消提交吧!是吧?版本303還依然存在着修改,若是任何人取出calc的303-349版本,他還會獲得錯誤的修改,對吧?

是的,這是對的。當咱們說「刪除」一個修改時,咱們只是說從HEAD刪除,原始的修改還保存在版本庫歷史中,在多數狀況下,這是足夠好的。大多數人只是對追蹤HEAD版 本感興趣,在一些特定狀況下,你也許但願毀掉全部提交的證據(或許某我的提交了一個祕密文件),這不是很容易的,由於Subversion設計用來不丟失 任何信息,每一個修訂版本都是不可變的目錄樹 ,從歷史刪除一個版本會致使多米諾效應,會在後面的版本致使混亂甚至會影響全部的工做拷貝。 [10]

版本控制系統很是重要的一個特性就是它的信息從不丟失,即便當你刪除了文件或目錄,它也許從HEAD版本消失了 ,但這個對象依然存在於歷史的早期版本 ,一個新手常常問到的問題是「怎樣找回個人文件和目錄?

第一步首先要知道須要拯救的項目是什麼,這裏有個頗有用的比喻:你能夠認爲任何存在於版本庫的對象生活在一個二維的座標系統裏,第一維是一個特定的版本樹,第二維是在樹中的路徑,因此你的文件或目錄的任何版本能夠有這樣一對座標定義。

Subversion沒有向CVS同樣的古典目錄, [11] 因此你須要svn log來察看你須要找回的座標對,一個好的策略是使用svn log --verbose來察看你刪除的項目,--verbose選項顯示全部改變的項目的每個版本 ,你只須要找出你刪除文件或目錄的那一個版本。你能夠經過目測找出這個版本,也能夠使用另外一種工具來檢查日誌的輸出 (經過grep或是在編輯器裏增量查找)。

$ cd parent-dir
$ svn log --verbose

------------------------------------------------------------------------
r808 | joe | 2003-12-26 14:29:40 -0600 (Fri, 26 Dec 2003) | 3 lines
Changed paths:
D /calc/trunk/real.c
M /calc/trunk/integer.c

Added fast fourier transform functions to integer.c.
Removed real.c because code now in double.c.

在這個例子裏,你能夠假定你正在找已經刪除了的文件real.c,經過查找父目錄的歷史 ,你知道這個文件在808版本被刪除,因此存在這個對象的版本在此以前 。結論:你想從版本807找回/calc/trunk/real.c

以上是最重要的部分—從新找到你須要恢復的對象。如今你已經知道該恢復的文件,而你有兩種選擇。

一種是對版本反向使用svn merge到808(咱們已經學會了如何取消修改,見「取消修改」一節),這樣會從新添加real.c,這個文件會列入增長的計劃,通過一次提交,這個文件從新回到HEAD

在這個例子裏,這不是一個好的策略,這樣作不只把real.c加入添加到計劃,也取消了對integer.c的修改,而這不是你指望的。確實,你能夠恢復到版本808,而後對integer.c執行取消svn revert操做,但這樣的操做沒法擴大使用,由於若是從版本808修改了90個文件怎麼辦?

因此第二個方法不是使用svn merge,而是使用svn copy命令,精確的拷貝版本和路徑「座標對」到你的工做拷貝:

$ svn copy --revision 807 /
http://svn.example.com/repos/calc/trunk/real.c ./real.c

$ svn status
A + real.c

$ svn commit -m "Resurrected real.c from revision 807, /calc/trunk/real.c."
Adding real.c
Transmitting file data .
Committed revision 1390.

加號標誌代表這個項目不只僅是計劃增長中,並且還包含了歷史,Subversion記住了它是從哪一個拷貝過來的。在未來,對這個文件運行svn log會看到這個文件在版本807以前的歷史,換句話說,real.c不是新的,而是原先刪除的那一個的後代。

儘管咱們的例子告訴咱們如何找回文件,對於恢復刪除的目錄也是同樣的。

版 本控制在軟件開發中普遍使用,這裏是團隊里程序員最經常使用的兩種分支/合併模式的介紹,若是你不是使用Subversion軟件開發,可隨意跳過本小節,如 果你是第一次使用版本控制的軟件開發者,請更加註意,如下模式被許多老兵看成最佳實踐,這個過程並不僅是針對Subversion,在任何版本控制系統中 都同樣,可是在這裏使用Subversion術語會感受更方便一點。

大 多數軟件存在這樣一個生命週期:編碼、測試、發佈,而後重複。這樣有兩個問題,第一,開發者須要在質量保證小組測試假定穩定版本時繼續開發新特性,新工做 在軟件測試時不能夠中斷,第二,小組必須一直支持老的發佈版本和軟件;若是一個bug在最新的代碼中發現,它必定也存在已發佈的版本中,客戶但願馬上獲得 錯誤修正而沒必要等到新版本發佈。

這是版本控制能夠作的幫助,典型的過程以下:

整個過程隨着軟件的成熟不斷重複:當2.0完成,一個新的2.0分支被建立,測試、打標籤和最終發佈,通過許多年,版本庫結束了許多版本發佈,進入了「維護」模式,許多標籤表明了最終的發佈版本。

一個特性分支是本章中那個重要例子中的分支,你正在那個分支上工做,而Sally還在/trunk繼續工做,這是一個臨時分支,用來做複雜的修改而不會干擾/trunk的穩定性,不象發佈分支(也許要永遠支持),特性分支出生,使用了一段時間,合併到主幹,而後最終被刪除掉,它們在有限的時間裏有用。

還有,關因而否建立特性分支的項目政策也變化普遍,一些項目永遠不使用特性分支:你們均可以提交到/trunk,好處是系統的簡單—沒有人須要知道分支和合並,壞處是主幹會常常不穩定或者不可用,另一些項目使用分支達到極限:沒有修改曾經直接提交到主幹,即便最細小的修改都要建立短暫的分支,而後當心的審覈合併到主幹,而後刪除分支,這樣系統保持主幹一直穩定和可用,可是形成了巨大的負擔。

許多項目採用折中的方式,堅持每次編譯/trunk並進行迴歸測試,只有須要屢次不穩定提交時才須要一個特性分支,這個規則能夠用這樣一個問題檢驗:若是開發者在好幾天裏獨立工做,一次提交大量修改(這樣/trunk就不會不穩定。),是否會有太多的修改要來回顧?若是答案是「」,這些修改應該在特性分支上進行,由於開發者增量的提交修改,你能夠容易的回頭檢查。

最終,有一個問題就是怎樣保持一個特性分支「同步」於工做中的主幹,在前面提到過,在一個分支上工做數週或幾個月是頗有風險的,主幹的修改也許會持續涌入,由於這一點,兩條線的開發會區別巨大,合併分支回到主幹會成爲一個噩夢。

這種狀況最好經過有規律的將主幹合併到分支來避免,制定這樣一個政策:每週將上週的修改合併到分支,注意這樣作時須要當心,須要手工記錄合併的過程,以免重複的合併(在「手工追蹤合併」一節描述過),你須要當心的撰寫合併的日誌信息,精確的描述合併包括的範圍(在「合併一條分支到另外一支」一節中描述過),這看起來像是脅迫,但是其實是容易作到的。

在一些時候,你已經準備好了將「同步的」特性分支合併回到主幹,爲此,開始作一次將主幹最新修改和分支的最終合併,這樣之後,除了你的分支修改的部分,最新的分支和主幹將會絕對一致,因此在這個特別的例子裏,你會經過直接比較分支和主幹來進行合併:

$ cd trunk-working-copy

$ svn update
At revision 1910.

$ svn merge http://svn.example.com/repos/calc/trunk@1910 /
http://svn.example.com/repos/calc/branches/mybranch@1910
U real.c
U integer.c
A newdirectory
A newdirectory/newfile

經過比較HEAD修訂版本的主幹和HEAD修訂版本的分支,你肯定了只在分支上的增量信息,兩條開發線都有了分枝的修改。

能夠用另外一種考慮這種模式,你每週按時同步分支到主幹,相似於在工做拷貝執行svn update的命令,最終的合併操做相似於在工做拷貝運行svn commit,畢竟,工做拷貝不就是一個很是淺的分支嗎?只是它一次只能夠保存一個修改。

svn switch命令改變存在的工做拷貝到另外一個分支,然而這個命令在分支上工做時不是嚴格必要的,它只是提供了一個快捷方式。在前面的例子裏,完成了私有分支的創建,你取出了新目錄的工做拷貝,相反,你能夠簡單的告訴Subversion改變你的/calc/trunk的工做拷貝到分支的路徑:

$ cd calc

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

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

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

完成了到分支的「跳轉」,你的目錄與直接取出一個乾淨的版本沒有什麼不一樣。這樣會更有效率,由於分支只有很小的區別,服務器只是發送修改的部分來使你的工做拷貝反映分支。

svn switch命令也能夠帶--revision-r)參數,因此你不須要一直移動你的工做拷貝到最新版本。

固然,許多項目比咱們的calc要複雜的多,有更多的子目錄,Subversion用戶一般用以下的法則使用分支:

換句話說,若是一個用戶知道分支工做只發生在部分子目錄,咱們使用svn switch來跳轉部分目錄(有時候只是單個文件),這樣的話,他們依然能夠繼續獲得普通的「trunk」主幹的更新,可是已經跳轉的部分則被免去了更新(除非分支上有更新)。這個特性給「混合工做拷貝」概念添加了新的維度—不只工做拷貝的版本能夠混合,在版本庫中的位置也能夠混合。

若是你的工做拷貝包含許多來自不一樣版本庫目錄跳轉的子樹,它會工做如常。當你更新時,你會獲得每個目錄適當的補丁,當你提交時,你的本地修改會一直做爲一個單獨的原子修改提交到版本庫。

注意,由於你的工做拷貝能夠在混合位置的狀況下工做正常,可是全部的位置必須在同一個版本庫,Subversion的版本庫不能互相通訊,這個特性還不在Subversion 1.0的計劃裏。[12]

由於svn switchsvn update的一個變種,具備相同的行爲,當新的數據到達時,任何工做拷貝的已經完成的本地修改會被保存,這裏容許你做各類聰明的把戲。

舉個例子,你的工做拷貝目錄是/calc/trunk,你已經作了不少修改,而後你忽然發現應該在分支上修改更好,沒問題!你能夠使用svn switch,而你本地修改還會保留,你能夠測試並提交它們到分支。

另外一個常見的版本控制系統概念是標­¾(tag),一個標籤只是一個項目某一時間的「快照」,在Subversion裏這個概念無處不在—每一次提交的修訂版本都是一個精確的快照。

然而人們但願更人性化的標籤名稱,像release-1.0。他們也但願能夠對一個子目錄快照,畢竟,記住release-1.0是修訂版本4822的某一小部分不是件很容易的事。

svn copy再次登場,你但願創建一個/calc/trunk的一個快照,就像HEAD修訂版本,創建這樣一個拷貝:

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

Committed revision 351.

這個例子假定/calc/tags目錄已經存在(若是不是,見svn mkdir),拷貝完成以後,一個表示當時HEAD版本的/calc/trunk目錄的鏡像已經永久的拷貝到release-1.0目錄。固然,你會但願更精確一點,以防其餘人在你不注意的時候提交修改,因此,若是你知道/calc/trunk的版本350是你想要的快照,你能夠使用svn copy加參數 -r 350

可是等一下:標籤的產生過程與創建分支是同樣的?是的,實際上在Subversion中標籤與分支沒有區別,都是普通的目錄,經過copy命令獲得,與分支同樣,一個目錄之因此是標籤只是人們決定這樣使用它,只要沒有人提交這個目錄,它永遠是一個快照,但若是人們開始提交,它就變成了分支。

若是你管理一個版本庫,你有兩種方式管理標籤,第一種方法是禁止命令:做爲項目的政策,咱們要決定標籤所在的位置,肯定全部用戶知道如何處理拷貝的目錄(也就是確保他們不會提交他們),第二種方法看來很過度:使用訪問控制腳原本阻止任何想對標籤目錄作的非拷貝的操做(見第 6 章 配置服務器)這種方法一般是沒必要要的,若是一我的不當心提交了到標籤目錄一個修改,你能夠簡單的取消,畢竟這是版本控制啊。

有時候你但願你的「快照」可以很複雜,而不僅是一個單獨修訂版本的一個單獨目錄。

舉個例子,假定你的項目比咱們的的例子calc大的多:假設它保存了一組子目錄和許多文件,在你工做時,你或許決定建立一個包括特定特性和Bug修正的工做拷貝,你能夠經過選擇性的回溯文件和目錄到特定修訂版本(使用svn update -r)來實現,或者轉換文件和目錄到特定分支(使用svn switch),這樣作以後,你的工做拷貝成爲版本庫不一樣版本和分支的司令部,可是通過測試,你會知道這是你須要的一種精確數據組合。

是時候進行快照了,拷貝URL在這裏不能工做,在這個例子裏,你但願把本地拷貝的佈局作鏡像而且保存到版本庫中,幸運的是,svn copy包括四種不一樣的使用方式(在第9章能夠詳細閱讀),包括拷貝工做拷貝到版本庫:

$ ls
my-working-copy/

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

Committed revision 352.

如今在版本庫有一個新的目錄/calc/tags/mytag,這是你的本地拷貝的一個快照—混合了修訂版本,URL等等。

一些人也發現這一特性一些有趣的使用方式,有些時候本地拷貝有一組本地修改,你但願你的協做者看到這些,不使用svn diff併發送一個補定文件(不會捕捉到目錄、符號鏈和屬性的修改),而是使用svn copy來「上傳」你的工做拷貝到一個版本庫的私有區域,你的協做者能夠選擇完整的取出你的工做拷貝,或使用svn merge來接受你的精確修改。

你必定注意到了Subversion極度的靈活性,由於它用相同的底層機制(目錄拷貝)實現了分支和標籤,由於分支和標籤是做爲普通的文件系統出現,會讓人們感到懼怕,由於它靈活了,在這個小節裏,咱們會提供安排和管理數據的一些建議。

有一些標準的,推薦的組織版本庫的方式,許多人建立一個trunk目錄來保存開發的「主線」,一個branches目錄存放分支拷貝,一個目錄保存標籤拷貝,若是一個版本庫只是存放一個項目,人們會在頂級目錄建立這些目錄:

/trunk
/branches
/tags

若是一個版本庫保存了多個項目,管理員會經過項目來佈局(見「選擇一種版本庫佈局」一節關於「項目根目錄」):

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

固然,你能夠自由的忽略這些一般的佈局方式,你能夠建立任意的變化,只要是對你和你的項目有益,記住不管你選擇什麼,這不會是一種永久的承諾,你能夠隨時從新組織你的版本庫。由於分支和標籤都是普通的目錄,svn move命令能夠任意的更名和移動它們,從一種佈局到另外一種大概只是一系列服務器端的移動,若是你不喜歡版本庫的組織方式,你能夠任意修改目錄結構。

記住,儘管移動目錄很是容易,你必須體諒你的用戶,你的修改會讓你的用戶感到迷惑,若是一個用戶的擁有一個版本庫目錄的工做拷貝,你的svn move命令也許會刪除最新的版本的這個路徑,當用戶運行svn update,會被告知這個工做拷貝引用的路徑已經再也不存在,用戶須要強制使用svn switch轉到新的位置。

另外一個Subversion模型的可愛特性是分支和標籤能夠有有限的生命週期,就像其它的版本化的項目,舉個例子,假定你最終完成了calc項目你的我的分支上的全部工做,在合併了你的全部修改到/calc/trunk後,沒有必要繼續保留你的私有分支目錄:

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

Committed revision 375.

你的分支已經消失了,固然不是真的消失了:這個目錄只是在HEAD修訂版本里消失了,若是你使用svn checkoutsvn switch或者svn list來檢查一箇舊的版本,你仍會見到這個舊的分支。

若是瀏覽你刪除的目錄還不足夠,你能夠把它找回來,恢復數據對Subversion來講很簡單,若是你但願恢復一個已經刪除的目錄(或文件)到HEAD,僅須要使用svn copy -r來從舊的版本拷貝出來:

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

Committed revision 376.

在咱們的例子裏,你的我的分支只有一個相對短的生命週期:你會爲修復一個Bug或實現一個小的特性來建立它,當任務完成,分支也該結束了。在軟件開發過程當中,有兩個「主要的」分支一直存在很長的時間也是很常見的狀況,舉個例子,假定咱們是發佈一個穩定的calc項目的時候了,但咱們仍會須要幾個月的時間來修復Bug,你不但願添加新的特性,但你不但願告訴開發者中止開發,因此做爲替代,你爲軟件建立了一個「分支」,這個分支更改不會不少:

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

Committed revision 377.

並且開發者能夠自由的繼續添加新的(試驗的)特性到/calc/trunk,你能夠宣佈這樣一種政策,只有bug修正提交到/calc/branches/stable-1.0,這樣的話,人們繼續在主幹上工做,某我的會選擇在穩定分支上作出一些Bug修正,甚至在穩定版本發佈以後。你或許會在這個維護分支上工做很長時間—也就是說,你會一直繼續爲客戶提供這個版本的支持。



[8] Subversion不支持跨版本庫的拷貝,當使用svn copy或者svn move直接操做URL時你只能在同一個版本庫內操做。

[9] 在未來,Subversion項目將會計劃(或者發明)一種擴展補丁格式來描述目錄樹的結構和屬性變化。

[10] Subversion項目有計劃,無論用什麼方式,總有一天要實現svnadmin obliterate命令來進行永久刪除操做,而此時能夠看「svndumpfilter」一節

[11] 由於CVS沒有版本樹,它會在每一個版本庫目錄建立一個古典區域用來保存增量數據。

[12] 當你的服務器位置改變,而你不想放棄存在的本地拷貝,你能夠使用帶選項--relocatesvn switch命令轉換URL,見第 9 章 Subversion徹底參考svn switch查看更多信息和例子。

Subversion版本庫是保存任意數量項目版本化數據的中央倉庫,所以,版本庫成爲管理員關注的對象。版本庫的維護通常並不須要太多的關注,但爲了不一些潛在的問題和解決一些實際問題,理解怎樣適當的配置和維護仍是很是重要的。

在這一章裏,咱們將討論如何創建和配置一個Subversion版本庫,還會討論版本庫的維護,包括svnlooksvnadmin工具的使用(它們都包含在Subversion中)。咱們將說明一些常見的問題和錯誤,並提供一些安排版本庫數據的建議。

若是您只是以普通用戶的身份訪問版本庫對數據進行版本控制(就是說經過Subversion客戶端),您徹底能夠跳過本章。可是若是您已是或打算成爲Subversion版本庫的管理員,[13]您必定要關注一下本章的內容。

在 進入版本庫管理這塊寬廣的主題以前,讓咱們進一步肯定一下版本庫的定義,它是怎樣工做的?讓人有什麼感受?它但願茶是熱的仍是冰的,加糖或檸檬嗎?做爲一 名管理員,你應該既從邏輯視角-數據在版本庫中如何展現,又能從物理具體細節的視角-版本庫如何響應一個非Subversion的工具,來理解版本庫的組 成。下面的小節從一個比較高的層面覆蓋了這些基本概念。

從概念上來講,Subversion的版本庫就是一串目錄樹。每個目錄樹,就是版本庫的文件和目錄在某一時刻的快照。這些快照是客戶端使用者操做的結果,叫作修訂版本。

每 一個修訂版本都是以事務樹開始其生命週期。作提交操做時,客戶端創建了一個映射本地修改的Subversion事務(加上客戶端提交操做後任何對版本庫的 更改),而後指導版本庫將該樹存儲爲下一個快照。要是提交成功,這個事務就會成爲新的修訂版本樹,並被賦予新的修訂版本號。若是由於某些緣由提交失敗,事 務會被銷燬,客戶端將被通知這個事務失敗。

更新的動做也相似這樣。客戶端創建一個臨時的事務樹,映射工做文件的狀態。而後版本庫比較事務樹和被請求的修訂版本樹(一般是最新的,也就是最「年輕」的修訂版本樹),而後發回消息通知客戶端哪些變動須要將拷貝發送到修訂版本樹。更新完成後,臨時事務將被刪除。

事 務樹的使用是對版本庫中版本控制文件系統產生永久變動的惟一方法。一個事務的生命週期很是靈活,瞭解這一點很重要。在更新的狀況下,事務只是立刻會被銷燬 的臨時樹。在提交的狀況下,事務會變成固定的修訂版本(若是失敗的狀況下,則會被刪除)。在出現錯誤或bug的狀況下,事務可能會被留在版本庫中(不會影 響任何東西,可是會佔據空間)。

理 論上,有一天整個流程可以發展到對事務進行更加細密的流程控制。能夠想象一個系統,在客戶端完成操做,將要保存到版本庫中時,每一個加到它的事務都變成一個 修訂版本。這將會使每個新的提交均可以被別人查看到,也許是主管,也許是質量保證小組,他們能夠決定是要接收這個事務成爲修訂版本,仍是放棄它。

在Subversion1.1中,版本庫中存儲數據有兩種方式。一種是在Berkeley DB數據庫中存儲數據;另外一種是使用普通的文件,使用自定義格式。由於Subversion的開發者稱版本庫爲(版本化的)文件系統,他們接受了稱後一種存儲方式爲FSFS[14]的習慣,也就是說,使用本地操做系統文件系統來存儲數據的版本化文件的系統。

建 立一個版本庫時,管理員必須決定使用Berkeley DB仍是FSFS。它們各有優缺點,咱們將詳細描述。這兩個中並無一個是更正式的,訪問版本庫的程序與採用哪種實現方式無關。訪問程序並不知道版本庫 如何存儲數據,它們只是從版本庫的API讀取到修訂版本和事務樹。

表 5.1 「版本庫數據存儲對照表」從整體上比較了Berkeley DB和FSFS版本庫,下一部分將會詳細講述細節。

表 5.1. 版本庫數據存儲對照表

特性 Berkeley DB FSFS
對操做中斷的敏感 很敏感;系統崩潰或者權限問題會致使數據庫「塞住」,須要按期進行恢復。 不敏感。
可只讀加載 不能 能夠
存儲平臺無關 不能 能夠
可從網絡文件系統訪問 不能 能夠
版本庫大小 稍大 稍小
可擴展性:修訂版本樹的數量 數據庫,沒有限制 許多古老的本地文件系統在處理單一目錄包含上千個條目時出現問題。
可擴展性:文件較多的目錄 較慢 較快
速度:檢出最新的代碼 較快 較慢
速度: 大的提交 較慢,可是時間被分配在整個提交操做中 較快,可是最後較長的延時可能會致使客戶端操做超時
組訪問權處理 對於用戶的umask設置十分敏感,最好只由一個用戶訪問。 對umask設置不敏感
功能成熟時間 2001年開始使用 2004年開始使用

在Subversion的初始設計階段,開發者由於多種緣由而決定採用Berkeley DB,好比它的開源協議、事務支持、可靠性、性能、簡單的API、線程安全、支持遊標等。

Berkeley DB提供了真正的事務支持-這或許是它最強大的特性,訪問你的Subversion版本庫的多個進程沒必要擔憂偶爾會破壞其餘進程的數據。事務系統提供的隔 離對於任何給定的操做,Subversion版本庫代碼看到的只是數據庫的靜態視圖-而不是一個在其餘進程影響不斷變化的數據庫-並可以根據該視圖做出決 定。若是該決定正好同其餘進程所作操做衝突,整個操做會回滾,就像什麼都沒有發生同樣,而且Subversion會優雅的再次對更新的靜態視圖進行操做。

Berkeley DB另外一個強大的特性是熱備份-沒必要「脫機」就能夠備份數據庫環境的能力。咱們將會在「版本庫備份」一節討論如何備份你的版本庫,可以不中止系統對版本庫作全面備份的好處是顯而易見的。

Berkeley DB同時是一個可信賴的數據庫系統。Subversion利用了Berkeley DB能夠記日誌的便利,這意味着數據庫先在磁盤上寫一個日誌文件,描述它將要作的修改,而後再作這些修改。這是爲了確保若是若是任何地方出了差錯,數據庫 系統能恢復到先前的檢查點—一個日誌文件認爲沒有錯誤的位置,從新開始事務直到數據恢復爲一個可用的狀態。關於Berkeley DB日誌文件的更多信息請查看「管理磁盤空間」一節

但 是每朵玫瑰都有刺,咱們也必須記錄一些Berkeley DB已知的缺陷。首先,Berkeley DB環境不是跨平臺的。你不能簡單的拷貝一個在Unix上建立的Subversion版本庫到一個Windows系統並指望它可以正常工做。儘管 Berkeley DB數據庫的大部分格式是不受架構約束的,但環境仍是有一些方面沒有獨立出來。其次,使用Berkeley DB的Subversion不能在95/98系統上運行—若是你須要將版本庫建在一個Windows機器上,請裝到Windows2000或 WindowsXP上。另外,Berkeley DB版本庫不能放在網絡共享文件夾中,儘管Berkeley DB承諾若是按照一套特定規範的話,能夠在網絡共享上正常運行,但實際上已知的共享類型幾乎都不知足這套規範。

最 後,由於Berkeley DB的庫直接連接到了Subversion中,它對於中斷比典型的關係型數據庫系統更爲敏感。大多數SQL系統,舉例來講,有一個主服務進程來協調對數據 庫表的訪問。若是一個訪問數據庫的程序由於某種緣由出現問題,數據庫守護進程察覺到鏈接中斷會作一些清理。由於數據庫守護進程是惟一訪問數據庫表的進程, 應用程序不須要擔憂訪問許可的衝突。可是,這些狀況與Berkeley DB不一樣。Subversion(和使用Subversion庫的程序)直接訪問數據庫的表,這意味着若是有一個程序崩潰,就會使數據庫處於一個暫時的不 一致、不可訪問的狀態。當這種狀況發生時,管理員須要讓Berkeley DB恢復到一個檢查點,這的確有點討厭。除了崩潰的進程,還有一些狀況能讓版本庫出現異常,好比程序在數據庫文件的全部權或訪問權限上發生衝突。由於 Berkeley DB版本庫很是快,而且能夠擴展,很是適合使用一個單獨的服務進程,經過一個用戶來訪問—好比Apache的httpdsvnserve(參見第 6 章 配置服務器)—而不是多用戶經過file:///svn+ssh://URL的方式多用戶訪問。若是將Berkeley DB版本庫直接用做多用戶訪問,請先閱讀「支持多種版本庫訪問方法」一節

在2004 年中期,另外一種版本庫存儲系統慢慢造成了:一種不須要數據庫的存儲系統。FSFS版本庫在單一文件中存儲修訂版本樹,因此版本庫中全部的修訂版本都在一個 子文件夾中有限的幾個文件裏。事務在單獨的子目錄中被建立,建立完成後,一個單獨的事務文件被建立並移動到修訂版本目錄,這保證提交是原子性的。由於一個 修訂版本文件是持久不可改變的,版本庫也能夠作到熱備份,就象Berkeley DB版本庫同樣。

修 訂版本文件格式表明了一個修訂版本的目錄結構,文件內容,和其它修訂版本樹中相關信息。不像Berkeley DB數據庫,這種存儲格式可跨平臺而且與CPU架構無關。由於沒有日誌或用到共享內存的文件,數據庫能被網絡文件系統安全的訪問和在只讀環境下檢查。缺乏 數據庫花消同時也意味着版本庫的整體體積能夠稍小一點。

FSFS 也有一種不一樣的性能特性。當提交大量文件時,FSFS使用O(N)算法來追加條目,而Berkeley DB則用(N^2)算法來重寫整個目錄。另外一方面,FSFS經過寫入與上一個版本比較的變化來記錄新版本,這也意味着獲取最新修訂版本時會比 Berkeley DB慢一點,提交時FSFS也會有一個更長的延遲,在某些極端狀況下會致使客護端在等待迴應時超時。

最重要的區別是當出現錯誤時FSFS不會楔住的能力。若是使用Berkeley DB的進程發生許可錯誤或忽然崩潰,數據庫會一直沒法使用,直到管理員恢復。假如在應用FSFS版本庫時發生一樣的狀況,版本庫不會受到任何干擾,最壞狀況下也就是會留下一些事務數據。

惟一真正對FSFS不利的是相對於Berkeley DB的不成熟,缺少足夠的使用和壓力測試,許多關於速度和可擴展性的判斷都是創建在良好的猜想之上。在理論上,它承諾會下降管理員新手的門檻而且更加不容易發生問題。在實踐中,只有時間能夠證實。

建立一個 Subversion 版本庫出乎尋常的簡單。 Subversion 提供的svnadmin 工具,有一個執行這個功能的子命令。要創建一個新的版本庫,只須要運行:

$ svnadmin create /path/to/repos

這個命令在目錄/path/to/repos建立了一個新的版本庫。這個新的版本庫會以修訂版本版本0開始其生命週期,裏面除了最上層的根目錄(/),什麼都沒有。剛開始,修訂版本0有一個修訂版本屬性svn:date,設置爲版本庫建立的時間。

在 Subversion 1.2中,版本庫默認使用FSFS後端存儲方式來建立(見「版本庫數據存儲」一節)。無論怎樣,存儲類型能夠使用--fs-type參數明確說明:

$ svnadmin create --fs-type fsfs /path/to/repos
$ svnadmin create --fs-type bdb /path/to/other/repos

警告

不 要在網絡共享上建立Berkeley DB版本庫—它不能存在於諸如NFS, AFS或Windows SMB的遠程文件系統中,Berkeley 數據要求底層文件系統實現嚴格的POSIX鎖定語義,幾乎沒有任何網絡文件系統提供這些特性,假如你在網絡共享上使用Berkeley DB,結果是不可預知的——許多錯誤可能會馬上發現,也有可能在幾個月以後才能發現

假如你須要多臺計算機來訪問,你須要在網絡共享上建立FSFS版本庫,而不是Berkeley DB的版本庫。或者更好的辦法,你創建一個真正的服務進程(例如Apache或svnserve),把版本庫放在服務器能訪問到的本地文件系統中,以便能經過網絡訪問。詳情請參看linkend="svn.serverconfig"/>。

你可能已經注意到了,svnadmin命令的路徑參數只是一個普通的文件系統路徑,而不是一個svn客戶端程序訪問版本庫時使用的URL。svnadminsvnlook都被認爲是服務器端工具—它們在版本庫所在的機器上使用,用來檢查或修改版本庫,不能經過網絡來執行任務。一個Subversion的新手一般會犯的錯誤,就是試圖將URL(甚至「本地file:路徑)傳給這兩個程序。

因此,當你運行svnadmin create命令後,就會在運行目錄建立一個嶄新的Subversion版本庫,讓咱們看一下在這個目錄建立中建立了什麼。

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

除了README.txtformat文件,版本庫目錄就是一些子目錄了。就像Subversion其它部分的設計同樣,模塊化是一個很重要的原則,並且層次化的組織要比雜亂無章好。下面是對新的版本庫目錄中各個項目的簡要介紹:

conf

一個存儲版本庫配置文件的目錄。

dav

提供給Apache和mod_dav_svn的目錄,讓它們存儲本身的數據。

db

你全部的受版本控制數據的所在之處。這個目錄或者是個Berkeley DB環境(盡是數據表和其餘東西),或者是一個包含修訂版本文件的FSFS環境。

format

包含了用來表示版本庫佈局版本號的整數。

hooks

一個存儲鉤子腳本模版的目錄(還有鉤子腳本自己, 若是你安裝了的話)。

locks

一個存儲Subversion版本庫鎖定數據的目錄,被用來追蹤對版本庫的訪問。

README.txt

這個文件只是用來告訴它的閱讀者,他如今看的是 Subversion 的版本庫。

通常來講,你不須要手動干預版本庫。svnadmin工具應該足以用來處理對版本庫的任何修改,或者你也能夠使用第三方工具(好比Berkeley DB的工具包)來調整部分版本庫。不過仍是會有些例外狀況,咱們會在這裏提到。

所謂鉤子就是與一些版本庫事件觸發的程序,例如新修訂版本的建立,或是未版本化屬性的修改。每一個鉤子都會被告滿足夠多的信息,包括那是什麼事件,所操做的對象,和觸發事件的用戶名。經過鉤子的輸出或返回狀態,鉤子程序能讓工做繼續、中止或是以某種方式掛起。

默認狀況下,鉤子的子目錄中包含各類版本庫鉤子模板。

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

對 每種Subversion版本庫支持的鉤子的都有一個模板,經過查看這些腳本的內容,你能看到是什麼事件觸發了腳本及如何給傳腳本傳遞數據。同時,這些模 版也是如何使用這些腳本,結合Subversion支持的工具來完成有用任務的例子。要實際安裝一個可用的鉤子,你須要在repos/hooks目錄下安裝一些與鉤子同名(如 start-commit或者post-commit)的可執行程序或腳本。

在Unix 平臺上,這意味着要提供一個與鉤子同名的腳本或程序(多是shell 腳本,Python 程序,編譯過的c語言二進制文件或其餘東西)。固然,腳本模板文件不只僅是展現了一些信息—在Unix下安裝鉤子最簡單的辦法就是拷貝這些模板,而且去 掉.tmpl擴展名,而後自定義鉤子的內容,肯定腳本是可運行的。Windows用文件的擴展名來決定一個程序是否可運行,因此你要使程序的基本名與鉤子 同名,同時,它的擴展名是Windows系統所能辨認的,例如execom和批處理的bat

目前Subversion有已實現了九種鉤子:

start-commit

它在提交事務產生前已運行,一般用來斷定一個用戶是否有權提交。版本庫傳給該程序兩個參數:到版本庫的路徑,和要進行提交的用戶名。若是程序返回一個非零值,會在事務產生前中止該提交操做。若是鉤子程序要在stderr中寫入數據,它將排隊送至客戶端。

pre-commit

在 事務完成提交以前運行,一般這個鉤子是用來保護由於內容或位置(例如,你要求全部到一個特定分支的提交必須包括一個bug追蹤的ticket號,或者是要 求日誌信息不爲空)而不容許的提交。版本庫傳遞兩個參數到程序:版本庫的路徑和正在提交的事務名稱,若是程序返回非零值,提交會失敗,事務也會刪除。若是 鉤子程序在stderr中寫入了數據,也會傳遞到客戶端。

Subversion的分發版本包括了一些訪問控制腳本(在Subversion源文件目錄樹的tools/hook-scripts目錄),能夠用來被pre-commit調用來實現精密的寫訪問控制。另外一個選擇是使用Apache的httpd模塊mod_authz_svn,能夠對單個目錄進行讀寫訪問控制(見「每目錄訪問控制」一節)。在將來的Subversion版本中,咱們計劃直接在文件系統中實現訪問控制列表(ACLs)。

post-commit

它在事務完成後運行,建立一個新的修訂版本。大多數人用這個鉤子來發送關於提交的描述性電子郵件,或者做爲版本庫的備份。版本庫傳給程序兩個參數:到版本庫的路徑和被建立的新的修訂版本號。退出程序會被忽略。

Subversion分發版本中包括mailer.pycommit-email.pl腳本(存於Subversion源代碼樹中的tools/hook-scripts/目錄中)能夠用來發送描述給定提交的email(而且或只是追加到一個日誌文件),這個mail包含變化的路徑清單,提交的日誌信息、日期和做者以及修改文件的GNU區別樣式輸出。

Subversion提供的另外一個有用的工具是hot-backup.py腳本(在Subversion源代碼樹中的tools/backup/目錄中)。這個腳本能夠爲Subversion版本庫進行熱備份(Berkeley DB數據庫後端支持的一種特性),能夠製做版本庫每次提交的快照做爲歸檔和緊急狀況的備份。

pre-revprop-change

由於Subversion的修訂版本屬性不是版本化的,對這類屬性的修改(例如提交日誌屬性svn:log)將會永久覆蓋之前的屬性值。由於數據在此可能丟失,因此Subversion提供了這種鉤子(及與之對應的post-revprop-change),所以版本庫管理員可用一些外部方法記錄變化。做爲對丟失未版本化屬性數據的防範,Subversion客戶端不能遠程修改修訂版本屬性,除非爲你的版本庫實現這個鉤子。

這個鉤子在對版本庫進行這種修改時纔會運行,版本庫給鉤子傳遞四個參數:到版本庫的路徑,要修改屬性的修訂版本,通過認證的用戶名和屬性自身的名字。

post-revprop-change

咱們在前面提到過,這個鉤子與pre-revprop-change對應。事實上,由於多疑的緣由,只有存在pre-revprop-change時這個腳本纔會執行。當這兩個鉤子都存在時,post-revprop-change在修訂版本屬性被改變以後運行,一般用來發送包含新屬性的email。版本庫傳遞四個參數給該鉤子:到版本庫的路徑,屬性存在的修訂版本,通過校驗的產生變化的用戶名,和屬性自身的名字。

Subversion分發版本中包含propchange-email.pl腳本(在Subversion源代碼樹中的tools/hook-scripts/目錄中),能夠用來發送修訂版本屬性修改細節的email(而且或只是追加到一個日誌文件)。這個email包含修訂版本和發生變化的屬性名,做出修改的用戶和新屬性值。

pre-lock

這個鉤子會在每次有人嘗試鎖定文件時執行,能夠防止徹底的鎖定,或者用來制定控制哪些用戶能夠鎖定特定路徑的複雜策略,若是鉤子發現已存在的鉤子,也能夠決定是否「竊取」這個鉤子。版本庫傳遞三個參數到鉤子:到版本庫的路徑、鎖定的路徑和企圖執行鎖定的用戶。若是程序返回非零值,鎖定動做會退出,而且全部的標準輸出返回到客戶端。

post-lock

這個鉤子在一個路徑被鎖定後執行,鎖定的路徑傳遞給鉤子的標準輸入,這個鉤子也接受兩個參數:到版本庫的路徑和企圖執行鎖定的用戶。能夠用這個鉤子發送通知郵件來記錄這種鎖定事件,由於鎖定已經發生,輸出會被鉤子忽略。

pre-unlock

這 個鉤子在某人企圖刪除一個文件上的鉤子時發生,能夠用來制定哪些用戶能夠解除文件鎖定的策略。制定破壞鎖定的策略很是重要,若是一個用戶A鎖定了一個文 件,容許用戶B打開這個鎖?若是這個鎖已經一週了呢?這種事情能夠經過鉤子決定並執行。版本庫傳遞三個參數到鉤子:到版本庫的路徑、將要解鎖的路徑和企圖 解鎖的用戶。若是程序返回非零值,解鎖操做退出並會將標準錯誤傳輸到客戶端。

post-unlock

鉤子在一個路徑被解鎖後執行,被解鎖的路徑會傳遞到鉤子的標準輸入,鉤子也會獲得兩個參數:到版本庫的路徑和刪除鎖定的用戶。能夠用鉤子發送記錄這些事件的郵件。由於刪除已經發生,鉤子的輸出被忽略。

警告

不要嘗試用鉤子腳本修改事務。一個常見的例子就是在提交時自動設置svn:eol-stylesvn:mime-type這類屬性。這看起來是個好主意,但它會引發問題。主要的問題是客戶並不知道由鉤子腳本進行的修改,同時沒有辦法通告客戶它的數據是過期的,這種矛盾會致使出人意料和不能預測的行爲。

做爲嘗試修改事務的替代,咱們經過檢查pre-commit鉤子的事務,在不知足要求時拒絕提交。

Subversion 會試圖以當前訪問版本庫的用戶身份執行鉤子。一般,對版本庫的訪問老是經過Apache HTTP服務器和mod_dav_svn進行,所以,執行鉤子的用戶就是運行Apache的用戶。鉤子自己須要具備操做系統級的訪問許可,用戶能夠運行 它。另外,其它被鉤子直接或間接使用的文件或程序(包括Subversion版本庫自己)也要被同一個用戶訪問。換句話說,要注意潛在的訪問控制問題,它 可能會讓你的鉤子沒法按照你的目的順利執行。

維 護一個Subversion版本庫是一項使人沮喪的工做,主要由於有數據庫後端與生俱來的複雜性。作好這項工做須要知道一些工具——它們是什麼,何時 用以及如何使用。這一節將會向你介紹Subversion自帶的版本庫管理工具,以及如何使用它們來完成諸如版本庫移植、升級、備份和整理之類的任務。

Subversion提供了一些用來建立、查看、修改和修復版本庫的工具。讓咱們首先詳細瞭解一下每一個工具,而後,咱們再看一下僅在Berkeley DB後端分發版本中提供的版本數據庫工具。

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子命令的輸出定義以下:

這種輸出是人可閱讀的,像是時間戳這種有意義的條目,使用文本表示,而不是其餘比較晦澀的方式(例如許多無聊的人推薦的十億分之一秒的數量)。這種輸出也是機器可讀的—由於日誌信息能夠有多行,沒有長度的限制,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 catsvnlook propget, 和svnlook proplist命令來查看這些目錄和文件的細節。

svnlook還能夠作不少別的查詢,顯示咱們先前提到的信息的一些子集,報告指定的修訂版本或事務中哪些路徑曾經被修改過,顯示對文件和目錄作過的文本和屬性的修改,等等。下面是svnlook命令能接受的子命令的介紹,以及這些子命令的輸出:

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)

咱們已經提過svnadmincreate子命令(參照「版本庫的建立和配置」一節)。本章中咱們會詳細講解大多數其餘的命令。如今,咱們來簡單的看一下每一個可用的子命令提供了什麼功能。

create

建立一個新的Subversion版本庫。

deltify

在指定的修訂版本範圍內,對其中修改過的路徑作增量化操做。若是沒有指定修訂版本,這條命令會修改HEAD修訂版本。

dump

導出版本庫修訂必定版本範圍內的內容,使用可移植轉儲格式。

hotcopy

對版本庫作熱拷貝,用這個方法你能任什麼時候候安全的備份版本庫而無需考慮是否正在使用。

list-dblogs

(Berkeley DB版本庫專有)列出Berkeley DB中與版本庫有關的日誌文件清單。這個清單包括全部的日誌文件—仍然被版本庫使用的和再也不使用的。

list-unused-dblogs

(Berkeley DB版本庫專有)列出Berkeley DB版本庫有關的不在使用日誌文件路徑清單。你能安全的從版本庫中刪除那些日誌文件,也能夠將它們存檔以用來在災難事件後版本庫的恢復。

load

導入由dump子命令導出的可移植轉儲格式的一組修訂版本。

lslocks

List and describe any locks that exist in the repository.

lstxns

列出剛剛在版本庫的沒有提交的Subversion事務清單。

recover

恢復版本庫,一般在版本庫發生了致命錯誤的時候,例如阻礙進程乾淨的關閉同版本庫的鏈接的錯誤。

rmlocks

無條件刪除所列路徑裏的鎖定。

rmtxns

從版本庫中清除Subversion事務(經過加工lstxns子命令的輸出便可)。

setlog

替換給定修訂版本的svn:log(提交日誌信息)屬性值。

verify

驗證版本庫的內容,包括校驗比較本地版本化數據和版本庫。

由於Subversion使用底層的數據庫儲存各種數據,手工調整是不明智的,即便這樣作並不困難。況且,一旦你的數據存進了版本庫,一般很難再將它們從版本庫中刪除。[15]但 是不可避免的,總會有些時候你須要處理版本庫的歷史數據。你也許想把一個不該該出現的文件從版本庫中完全清除。或者,你曾經用一個版本庫管理多個工程,現 在又想把它們分開。要完成這樣的工做,管理員們須要更易於管理和擴展的方法表示版本庫中的數據,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

將指定路徑的數據添加到轉儲數據流中。

如今我來演示如何使用這個命令。咱們會在其它章節(參見 「選擇一種版本庫佈局」一節) 討論關於如何選擇設定版本庫佈局的問題,好比應該使用一個版本庫管理多個項目仍是使用一個版本庫管理一個項目,或者如何在版本庫中安排數據等等。不過,有 些時候,即便在項目已經展開之後,你仍是但願對版本庫的佈局作一些調整。最多見的狀況是,把原來存放在同一個版本庫中的幾個項目分開,各自成家。

假設有一個包含三個項目的版本庫: calccalendar,和 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的頂級目錄。若是但願trunktagsbranches這三個目錄直接位於版本庫的根路徑下,你可能須要編輯轉儲文件,調整Node-pathCopyfrom-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-pathCopyfrom-path這兩個頭參數對你有些幫助。


Node-path: spreadsheet/Makefile

若是這些路徑以斜線開頭,那麼你傳遞給svndumpfilter includesvndumpfilter exclude的路徑也必須以斜線開頭(反之亦然)。若是由於某些緣由轉儲文件中的路徑沒有統一使用或不使用斜線開頭,[16]也許須要修正這些路徑,統一使用斜線開頭或不使用斜線開頭。

此外,複製操做生成的路徑也會帶來麻煩。Subversion支持在版本庫中進行復制操做,也就是複製一個存在的路徑,生成一個新的路徑。問題是,svndumpfilter保留的某個文件或目錄多是由某個svndumpfilter排除的文件或目錄複製而來的。也就是說,爲了確保轉儲數據的完整性,svndumpfilter需 要切斷這些複製自被排除路徑的文件與源文件的關係,還要將這些文件的內容以新建的方式添加到轉儲數據中。可是因爲Subversion版本庫轉儲文件格式 中僅包含了修訂版本的更改信息,所以源文件的內容基本上沒法得到。若是你不能肯定版本庫中是否存在相似的狀況,最好從新考慮一下到底保留/排除哪些路徑。

若是你使用Berkeley DB版本庫,那麼全部歸入版本控制的文件系統結構和數據都儲存在一系列數據庫的表中,而這個位於版本庫的db子目錄下。這個子目錄是一個標準的Berkeley DB環境目錄,能夠應用任何Berkeley數據庫工具進行操做(參考SleepyCat網站http://www.sleepycat.com/上關於這些工具的介紹)。

對於Subversion的平常使用來講,這些工具並無什麼用處。大多數Subversion版本庫必須的數據庫操做都集成到svnadmin工具中。好比,svnadmin list-unused-dblogssvnadmin 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-changepost-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選項跳過鉤子。

警告

不過須要注意的是,一旦跳過鉤子也就跳過了鉤子所提供的全部功能,好比郵件通知(通知屬性有改動)、系統備份(能夠用來跟蹤非版本化的屬性變動)等等。換句話說,要留心你所做出的修改,以及你做出修改的方式。

svnadmin的 另外一個常見用途是查詢異常的—多是已經死亡的—Subversion事務。一般提交操做失敗時,與之相關的事務就會被清除。也就是說,事務自己及全部與 該事務相關(且僅與該事務相關)的數據會從版本庫中刪除。不過偶爾也會出現操做失敗而事務沒有被清除的狀況。出現這種狀況可能有如下緣由:客戶端的用戶粗 暴的結束了操做,操做過程當中出現網絡故障,等等。無論是什麼緣由,死亡的事務老是有可能會出現。這類事務不會產生什麼負面影響,僅僅是消耗了一點點磁盤空 間。不過,嚴厲的管理員老是但願可以將它們清除出去。

能夠使用svnadminlstxns 命令列出當前的異常事務名。

$ svnadmin lstxns myrepos
19
3a1
a45
$

將輸出的結果條目做爲svnlook(設置--transaction選項)的參數,就能夠得到事務的詳細信息,如事務的建立者、建立時間,事務已做出的更改類型,由這些信息能夠判斷出是否能夠將這個事務安全的刪除。若是能夠安全刪除,那麼只需將事務名做爲參數輸入到svnadmin rmtxns,就能夠將事務清除掉了。其實rmtxns子命令能夠直接以lstxns的輸出做爲輸入進行清理。

$ svnadmin rmtxns myrepos `svnadmin lstxns myrepos`
$

在按照上面例子中的方法清理版本庫以前,你或許應該暫時關閉版本庫和客戶端的鏈接。這樣在你開始清理以前,不會有正常的事務進入版本庫。下面例子中的shell腳本能夠用來迅速得到版本庫中異常事務的信息:

能夠用下面的命令使用上例中腳本: /path/to/txn-info.sh /path/to/repos。該命令的輸出主要由多個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提交日誌等等—均可以做 爲決策的參考。管理員還能夠直接和那些彷佛已經死亡事務的提交者直接交流(好比經過郵件),來確認該事務確實已經死亡了。

雖 然存儲器的價格在過去的幾年裏以讓人難以至信的速度滑落,可是對於那些須要對大量數據進行版本管理的管理員們來講,磁盤空間的消耗依然是一個重要的因素。 版本庫每增長一個字節都意味着須要多一個字節的磁盤空間進行備份,對於多重備份來講,就須要消耗更多的磁盤空間。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配置」一節得到更多關於數據庫配置的幫助信息。

若是不自動刪除日誌文件,那麼日誌文件會隨着版本庫的使用逐漸增長。這多少應該算是數據庫系統的特性,經過這些日誌文件能夠在數據庫嚴重損壞時恢復整個數據庫的內容。可是通常狀況下,最好是可以將無用的日誌文件收集起來並刪除,這樣就能夠節省磁盤空間。使用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使用一種鎖機制保護版本庫中的數據。鎖機制確保數據庫不會同時被多個訪問進程修改,也就保證了從數據庫中讀取到的數據始終是穩定並且正確的。當一個進 程須要修改數據庫中的數據時,首先必須檢查目標數據是否已經上鎖。若是目標數據沒有上鎖,進程就將它鎖上,而後做出修改,最後再將鎖解除。而其它進程則必 須等待鎖解除後才能繼續訪問數據庫中的相關內容。

在操做Subversion版本庫的過程當中,致命錯誤(如內存或硬盤空間不足)或異常中斷可能會致使某個進程沒能及時將鎖解除。結果就是後端的數據庫系統被「塞住」了。一旦發生這種狀況,任何訪問版本庫的進程都會掛起(每一個訪問進程都在等待鎖被解除,可是鎖已經沒法解除了)。

首先,若是你的版本庫出現這種狀況,沒什麼好驚慌的。Berkeley DB的文件系統採用了數據庫事務、檢查點以及預寫入日誌等技術來取保只有災難性的事件[17]才能永久性的破壞數據庫環境。因此雖然一個過於穩重的版本庫管理員一般都會按照某種方案進行大量的版本庫離線備份,不過不要急着通知你的管理員進行恢復。

而後,使用下面的方法試着「恢復」你的版本庫:

  1. 確保沒有其它進程訪問(或者試圖訪問)版本庫。對於網絡版本庫,關閉Apache HTTP服務器是個好辦法。

  2. 成爲版本庫的擁有者和管理員。這一點很重要,若是以其它用戶的身份恢復版本庫,可能會改變版本庫文件的訪問權限,致使在版本庫「恢復」後依舊沒法訪問。

  3. 運行命令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.

    此命令可能須要數分鐘才能完成。

  4. 從新啓動Subversion服務器。

這個方法能修復幾乎全部版本庫鎖住的問題。記住,要以數據庫的擁有者和管理員的身份運行這個命令,而不必定是root用戶。恢復過程當中可能會使用其它數據存儲區(例如共享內存區)重建一些數據庫文件。若是以root用戶身份恢復版本庫,這些重建的文件擁有者將變成root用戶,也就是說,即便恢復了到版本庫的鏈接,通常的用戶也無權訪問這些文件。

若是由於某些緣由,上面的方法沒能成功的恢復版本庫,那麼你能夠作兩件事。首先,將破損的版本庫保存到其它地方,而後從最新的備份中恢復版本庫。而後,發送一封郵件到Subversion用戶列表(地址是:),寫清你所遇到的問題。對於Subversion的開發者來講,數據安全是最重要的問題。

Subversion文件系統將數據保存在許多數據庫表中,而這些表的結構只有Subversion開發者們才瞭解(也只有他們才感興趣)不過,有些時候咱們會想到把全部的數據(或者一部分數據)保存在一個獨立的、可移植的、普通格式的文件中。Subversion經過svnadmin的兩個子命令dumpload提供了相似的功能。

對 版本庫的轉儲和裝載的需求主要仍是因爲Subversion自身處於變化之中。在Subversion的成長期,後端數據庫的設計屢次發生變化,這些變化 致使以前的版本庫出現兼容性問題。固然,將Berkeley DB版本庫移植到不一樣的操做系統或者CPU架構上,或者在Berkeley DB和FSFS後端之間進行轉化也須要轉儲和裝載功能。按照下面的介紹,只需簡單幾步就能夠完成數據庫的移植:

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 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鉤子在每次新的修訂版本提交後將其轉儲到文件中。或者,能夠編寫一個腳本,在天天夜裏將全部新增的修訂版本轉儲到文件中。這樣,svnadmindumpload命令就變成了很好的版本庫備份工具,萬一出現系統崩潰或其它災難性事件,它的價值就體現出來了。

轉儲還能夠用來將幾個獨立的版本庫合併爲一個版本庫。使用svnadmin load--parent-dir選項,能夠在裝載的時候指定根目錄。也就是說,若是有三個不一樣版本庫的轉儲文件,好比calc-dumpfilecal-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版本庫轉儲數據的最後一種用途——在不一樣的存儲機制或版本控制系統之間轉換。由於轉儲數據的格式的大部分是能夠閱讀的,[18]因此使用這種格式描述變動集(每一個變動集對應一個新的修訂版本)會相對容易一些。事實上,cvs2svn工具(參見 「轉化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的鉤子。 [19] 並且由於你能夠改變修訂版本的屬性,而不須要遵守時間順序—你可在任什麼時候刻修改任何修訂版本的屬性—所以最新版本的增量備份不會捕捉到之前特定修訂版本的屬性修改。

通 常說來,在每次提交時,只有妄想狂纔會備份整個版本庫,然而,假設一個給定的版本庫擁有一些恰當粒度的冗餘機制(如每次提交的郵件)。版本庫管理員也許會 但願將版本庫的熱備份引入到系統級的每夜備份,對大多數版本庫,歸檔的提交郵件爲保存資源提供了足夠的冗餘措施,至少對於最近的提交。可是它是你的數據— 你喜歡怎樣保護均可以。

一般狀況下,最好的版本庫備份方式是 混合的,你能夠平衡徹底和增量備份,另外配合提交郵件的歸檔,Subversion開發者,舉個例子,在每一個新的修訂版本創建時備份Subversion 的源代碼版本庫,而且保留全部的提交和屬性修改通知文件。你的解決方案相似,必須迎合你的須要,平衡便利和你的偏執。然而這些不會改變你的硬件來自鋼鐵的 命運。[20] 這必定會幫助你減小嚐試的時間。

一旦你的版本庫已經創建而且配置好了,剩下的就是使用了。若是你已經準備好了須要版本控制的數據,那麼能夠使用客戶端軟件svnimport子命令來實現你的指望。不過在這樣作以前,你最好對版本庫仔細的做一個長遠的規劃。本節,咱們會給你一些好的建議,這些建議能夠幫助你設計版本庫的文件佈局,以及如何在特定的佈局中安排你的數據。

在Subversion版本庫中,移動版本化的文件和目錄不會損失任何信息,可是這樣一來那些常常訪問版本庫而且覺得文件老是在同一個路徑的用戶可能會受到干擾。爲未來着想,最好預先對你的版本庫佈局進行規劃。以一種高效的「佈局」開始項目,能夠減小未來不少沒必要要的麻煩。

在 創建Subversion版本庫以前,有不少事情須要考慮。假如你是一個版本庫管理員,須要向多個項目提供版本控制支持。那麼,你首先要決定的是,用一個 版本庫支持多個項目,仍是爲每一個項目創建一個版本庫,仍是爲其中的某些項目提供獨立的版本庫支持,而將另一些項目分佈在幾個版本庫中。

使 用一個版本庫支持多個項目有不少好處,最明顯的無過於不須要維護好幾個版本庫。單一版本庫就意味着只有一個鉤子集,只須要備份一個數據庫,當 Subversion進行不兼容升級時,只須要一次轉儲和裝載操做,等等。還有,你能夠輕易的在項目之間移動數據,還不會損失任何歷史版本信息。

單 一版本庫的缺點是,不一樣的項目一般都有不一樣的提交郵件列表或者不一樣的權限認證和權限要求。還有,別忘了Subversion的修訂版本號是針對整個版本庫 的。即便最近沒有對某個項目做出修改,版本庫的修訂版本號仍是會由於其它項目的修改而不停的提高,許多人並不喜歡這樣的事實。

能夠採用折中的辦法。好比,能夠把許多項目按照彼此之間的關聯程度劃分爲幾個組合,而後爲每個項目組合創建一個版本庫。這樣,在相關項目之間共享數據依舊很簡單,而若是修訂版本號有了變化,至少開發人員知道,改變的東西多少和他們有些關係。

在決定了如何用版本庫組織項目之後,就該決定如何設置版本庫的目錄層次了。因爲Subversion按普通的目錄複製方式完成分支和標籤操做(參見第 4 章 分支與合併),Subversion社區建議爲每個項目創建一個項目根目錄—項目的「頂級」目錄—而後在根目錄下創建三個子目錄:trunk,保存項目的開發主線;branches,保存項目的各類開發分支;tags,保存項目的標籤,也就是建立後永遠不會修改的分支(可能會刪除)。 [21]

舉個例子,一個版本庫可能會有以下的佈局:

/
calc/
trunk/
tags/
branches/
calendar/
trunk/
tags/
branches/
spreadsheet/
trunk/
tags/
branches/

項目在版本庫中的根目錄地址並不重要。若是每一個版本庫中 只有一個項目,那麼就能夠認爲項目的根目錄就是版本庫的根目錄。若是版本庫中包含多個項目,那麼能夠將這些項目劃分紅不一樣的組合(按照項目的目標或者是否 須要共享代碼甚至是字母順序)保存在不一樣子目錄中,下面的例子給出了一個相似的佈局:

/
utils/
calc/
trunk/
tags/
branches/
calendar/
trunk/
tags/
branches/

office/
spreadsheet/
trunk/
tags/
branches/

按照你由於合適方式安排版本庫的佈局。Subversion自身並不強制或者偏好某一種佈局形式,對於Subversion來講,目錄就是目錄。最後,在設計版本庫佈局的時候,不要忘了考慮一下項目參與者們的意見。

設計好版本庫的佈局後,就該在版本庫中實現佈局和導入初始數據了。在Subversion中,有不少種方法完成這項工做。能夠使用svn mkdir命令(參見第 9 章 Subversion徹底參考)在版本庫中逐個建立須要的目錄。更快捷的方法是使用svn import命令(參見svn import」一節)。首先,在硬盤上建立一個臨時目錄,並按照設計好的佈局在其中建立子目錄,而後經過導入命令一次性的提交整個佈局到版本庫中:

$ mkdir tmpdir
$ cd tmpdir
$ mkdir projectA
$ mkdir projectA/trunk
$ mkdir projectA/branches
$ mkdir projectA/tags
$ mkdir projectB
$ mkdir projectB/trunk
$ mkdir projectB/branches
$ mkdir projectB/tags

$ svn import . file:///path/to/repos --message 'Initial repository layout'
Adding projectA
Adding projectA/trunk
Adding projectA/branches
Adding projectA/tags
Adding projectB
Adding projectB/trunk
Adding projectB/branches
Adding projectB/tags

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

而後能夠使用svn list命令確認導入的結果是否正確::

$ svn list --verbose file:///path/to/repos
1 harry May 08 21:48 projectA/
1 harry May 08 21:48 projectB/

$

建立了版本庫佈局之後,若是有項目的初始數據,那麼能夠將這些數據導入到版本庫中。一樣有不少種方法完成這項工做。首先,能夠使用svn import命令。也能夠先從版本庫中取出工做副本,將已有的項目數據複製到工做副本中,再使用svn addsvn commit命令提交修改。不過這些工做就不屬於版本庫管理方面的內容了。若是對svn 客戶端程序還不熟悉,請閱讀第 3 章 指導教程



[13] 這可能聽起來很崇高, 但咱們所指的只是那些對管理別人工做拷貝數據以外的神祕領域感興趣的人。

[14] 讀做「fuzz-fuzz」, 若是Jack Repenning提及這個問題。

[15] 順便說一句,這是Subversion的特性,而不是bug。

[16] 儘管svnadmin dump對是否以斜線做爲路徑的開頭有統一的規定——這個規定就是不以斜線做爲路徑的開頭——其它生成轉儲文件的程序不必定會遵照這個規定。

[17] 好比:硬盤 + 大號電磁鐵 = 毀滅。

[18] Subversion版本庫的轉儲文件格式相似於RFC-822格式,後者普遍的應用於電子郵件系統中。

[19] svnadmin setlog能夠被繞過鉤子程序被調用。

[20] 你知道的—只是對各類變化莫測的問題的統稱。

[21] trunktagsbranches能夠使用「TTB目錄」來表示。

一個Subversion的版本庫能夠和客戶端同時運行在同一個機器上,使用file:///訪問,可是一個典型的Subversion設置應該包括一個單獨的服務器,能夠被辦公室的全部客戶端訪問—或者有多是整個世界。

本小節描述了怎樣將一個Subversion的版本庫暴露給遠程客戶端,咱們會覆蓋Subversion已存在的服務器機制,討論各類方式的配置和使用。通過閱讀本小節,你能夠決定你須要哪一種網絡設置,而且明白怎樣在你的主機上進行配置。

Subversion的設計包括一個抽象的網絡層,這意味着版本庫能夠經過各類服務器進程訪問,並且客戶端「版本庫訪問」的API容許程序員寫出相關協議的插件,理論上講,Subversion能夠使用無限數量的網絡協議實現,目前實踐中存在着兩種服務器。

Apache是最流行的web服務器,經過使用mod_dav_svn模塊,Apache能夠訪問版本庫,而且能夠使客戶端使用HTTP的擴展協議WebDAV/DeltaV進行訪問,另外一個是svnserve:一個小的,獨立服務器,使用本身定義的協議和客戶端,表格6-1比較了這兩種服務器。

須要注意到Subversion做爲一個開源的項目,並無官方的指定何種服務器是「主要的」或者是「官方的」,並無那種網絡實現被視做二等公民,每種服務器都有本身的優勢和缺點,事實上,不一樣的服務器能夠並行工做,分別經過本身的方式訪問版本庫,它們之間不會互相阻礙(見「支持多種版本庫訪問方法」一節)。表 6.1 「網絡服務器比較」是對兩種存在的Subversion服務器的比較—做爲一個管理員,你更加勝任給你和你的用戶挑選服務器的任務。

表 6.1. 網絡服務器比較

特性 Apache + mod_dav_svn svnserve
認證選項 HTTP(S) basic auth、X.509 certificates、LDAP、NTLM或任何Apache httpd已經具有的方式 CRAM-MD5或SSH
用戶賬號選項 私有的'users'文件 私有的'users'文件,或存在的系統(SSH)賬戶
受權選項 總體的讀/寫訪問,或者是每目錄的讀/寫訪問 總體的讀/寫訪問,或者是使用pre-commit鉤子的每目錄寫訪問(但不是讀)
加密 經過選擇SSL 經過選擇SSH通道
交互性 能夠部分的被其餘WebDAV客戶端使用 不能被其餘客戶端使用
Web瀏覽能力 有限的內置支持,或者經過第三方工具,如ViewCVS 經過第三方工具,如ViewCVS
速度 有些慢 快一點
初始化配置 有些複雜 至關簡單

這部分是討論了Subversion客戶端和服務器怎樣互相交流,不考慮具體使用的網絡實現,經過閱讀,你會很好的理解服務器的行爲方式和多種客戶端與之響應的配置方式。

Subversion客戶端花費大量的時間來管理工做拷貝,當它須要版本庫信息,它會作一個網絡請求,而後服務器給一個恰當的回答,具體的網絡協議細節對用戶不可見,客戶端嘗試去訪問一個URL,根據URL模式的不一樣,會使用特定的協議與服務器聯繫(見版本庫的URL),用戶能夠運行svn --version來查看客戶端能夠使用的URL模式和協議。

當服務器處理一個客戶端請求,它一般會要求客戶端肯定它本身的身份,它會發出一個認證請求給客戶端,而客戶端經過提供憑證給服務器做爲響應,一旦認證結束,服務器會響應客戶端最初請求的信息。注意這個系統與CVS之類的系統不同,它們會在請求以前,預先提供憑證(「logs in」)給服務器,在Subversion裏,服務器經過請求客戶端適時地「拖入」憑證,而不是客戶端「」出。這使得這種操做更加的優雅,例如,若是一個服務器配置爲世界上的任何人均可以讀取版本庫,在客戶使用svn checkout時,服務器永遠不會發起一個認證請求。

若是客戶端請求往版本庫寫入新的數據(例如svn commit),這會創建新的修訂版本樹,若是客戶端的請求是通過認證的,認證過的用戶的用戶名就會做爲svn:author屬性的值保存到新的修訂本裏(見「未受版本控制的屬性」一節)。若是客戶端沒有通過認證(換句話說,服務器沒有發起過認證請求),這時修訂本的svn:author的值是空的。[22]

許多服務器配置爲在每次請求時要求認證,這對一次次輸入用戶名和密碼的用戶來講是很是惱人的事情。

使人高興的是,Subversion客戶端對此有一個修補:存在一個在磁盤上保存認證憑證緩存的系統,缺省狀況下,當一個命令行客戶端成功的在服務器上獲得認證,它會保存一個認證文件到用戶的私有運行配置區—類Unix系統下會在~/.subversion/auth/,Windows下在%APPDATA%/Subversion/auth/(運行區在「運行配置區」一節會有更多細節描述)。成功的憑證會緩存在磁盤,以主機名、端口和認證域的組合做爲惟一性區別。

當客戶端接收到一個認證請求,它會首先查找磁盤中的認證憑證緩存,若是沒有發現,或者是緩存的憑證認證失敗,客戶端會提示用戶須要這些信息。

十分關心安全的人們必定會想「把密碼緩存在磁盤?太可怕了,永遠不要這樣作!」可是請保持冷靜,並無你想象得那麼可怕。

  • auth/緩存區只有用戶(擁有者)能夠訪問,而不是全世界均可以,操做系統的訪問許可能夠保護密碼文件。

  • 在Windows 2000或更新的系統上,Subversion客戶端使用標準Windows加密服務來加密磁盤上的密碼。由於加密密鑰是Windows管理的,與用戶的 登錄憑證相關,只有用戶能夠解密密碼。(注意:若是用戶的Windows帳戶密碼修改了,全部的緩存密碼就不能夠解密了,此時Subversion客戶端 就會當它們根本不存在,在須要時繼續詢問密碼。)

  • 真正的偏執狂纔會犧牲全部的便利,能夠徹底的關閉憑證緩存。

你能夠關閉憑證緩存,只須要一個簡單的命令,使用參數--no-auth-cache

$ svn commit -F log_msg.txt --no-auth-cache
Authentication realm: <svn://host.example.com:3690> example realm
Username: joe
Password for 'joe':

Adding newfile
Transmitting file data .
Committed revision 2324.

# password was not cached, so a second commit still prompts us

$ svn delete newfile
$ svn commit -F new_msg.txt
Authentication realm: <svn://host.example.com:3690> example realm
Username: joe

或許,你但願永遠關閉憑證緩存,你能夠編輯你的運行配置文件(坐落在auth/目錄),只須要把store-auth-creds設置爲no,這樣就不會有憑證緩存在磁盤。

[auth]
store-auth-creds = no

有時候,用戶但願從磁盤緩存刪除特定的憑證,爲此你能夠瀏覽到auth/區域,刪除特定的緩存文件,憑證都是做爲一個單獨的文件緩存,若是你打開每個文件,你會看到鍵和值,svn:realmstring描述了這個文件關聯的特定服務器的域:

$ ls ~/.subversion/auth/svn.simple/
5671adf2865e267db74f09ba6f872c28
3893ed123b39500bca8a0b382839198e
5c3c22968347b390f349ff340196ed39

$ cat ~/.subversion/auth/svn.simple/5671adf2865e267db74f09ba6f872c28

K 8
username
V 3
joe
K 8
password
V 4
blah
K 15
svn:realmstring
V 45
<https://svn.domain.com:443> Joe's repository
END

一旦你定位了正確的緩存文件,只須要刪除它。

客戶端認證的行爲的最後一點:對使用--username--password選項的一點說明,許多客戶端和子命令接受這個選項,可是要明白使用這個選項不會主動地發送憑證信息到服務器,就像前面討論過的,服務器會在須要的時候纔會從客戶端「」入憑證,客戶端不會隨意「」出。若是一個用戶名和/或者密碼做爲選項傳入,它們只會在服務器須要時展示給服務器。[23]一般,只有在以下狀況下才會使用這些選項:

  • 用戶但願使用與登錄系統不一樣的名字認證,或者

  • 一段不但願使用緩存憑證但須要認證的腳本

這裏是Subversion客戶端在收到認證請求的時候的行爲方式:

  1. 檢查用戶是否經過--username和/或--password命令選項指定了任何憑證信息,若是沒有,或者這些選項沒有認證成功,而後

  2. 查找運行中的auth/區域保存的服務器域信息,來肯定用戶是否已經有了恰當的認證緩存,若是沒有,或者緩存憑證認證失敗,而後

  3. 提示用戶輸入。

若是客戶端經過以上的任何一種方式成功認證,它會嘗試在磁盤緩存憑證(除非用戶已經關閉了這種行爲方式,在前面提到過。)

svnserve是一個輕型的服務器,能夠同客戶端經過在TCP/IP基礎上的自定義有狀態協議通信,客戶端經過使用開頭爲svn://或者svn+ssh://svnserve的URL來訪問一個svnserve服務器。這一小節將會解釋運行svnserve的不一樣方式,客戶端怎樣實現服務器的認證,怎樣配置版本庫恰當的訪問控制。

有許多調用svnserve的方式,若是調用時沒有參數,你只會看到一些幫助信息,然而,若是你計劃使用inetd啓動進程,你能夠傳遞-i--inetd)選項:

$ svnserve -i
( success ( 1 2 ( ANONYMOUS ) ( edit-pipeline ) ) )

當用參數--inetd調用時,svnserve會嘗試使用自定義協議經過stdinstdout來與Subversion客戶端通話,這是使用inetd工做的標準方式,IANA爲Subversion協議保留3690端口,因此在類Unix系統你能夠在/etc/services添加以下的幾行(若是他們還不存在):

svn           3690/tcp   # Subversion
svn 3690/udp # Subversion

若是系統是使用經典的類Unix的inetd守護進程,你能夠在/etc/inetd.conf添加這幾行:

svn stream tcp nowait svnowner /usr/bin/svnserve svnserve -i

肯定「svnowner」用戶擁有訪問版本庫的適當權限,如今若是一個客戶鏈接來到你的服務器的端口3690,inetd會產生一個svnserve進程來作服務。

在一個Windows系統,有第三方工具能夠將svnserve做爲服務運行,請看Subversion的網站的工具列表。

svnserve的第二個選項是做爲獨立「守護」進程,爲此要使用-d選項:

$ svnserve -d
$ # svnserve is now running, listening on port 3690

當以守護模式運行svnserve時,你能夠使用--listen-port=--listen-host=選項來自定義「綁定」的端口和主機名。

也一直有第三種方式,使用-t選項的「管道模式」,這個模式假定一個分佈式服務程序如RSHSSH已經驗證了一個用戶,而且以這個用戶調用了一個私有svnserve進程,svnserve運做如常(經過stdinstdout通信),而且能夠設想通信是自動轉向到一種通道傳遞迴客戶端,當svnserve被這樣的通道代理調用,肯定認證用戶對版本數據庫有徹底的讀寫權限,(見服務器和訪問許可:一個警告。)這與本地用戶經過file:///URl訪問版本庫一樣重要。

一旦svnserve已經運行,它會將你係統中全部版本庫發佈到網絡,一個客戶端須要指定版本庫在URL中的絕對路徑,舉個例子,若是一個版本庫是位於/usr/local/repositories/project1,則一個客戶端能夠使用svn://host.example.com/usr/local/repositories/project1 來進行訪問,爲了提升安全性,你能夠使用svnserve-r選項,這樣會限制只輸出指定路徑下的版本庫:

$ svnserve -d -r /usr/local/repositories

使用-r能夠有效地改變文件系統的根位置,客戶端能夠使用去掉前半部分的路徑,留下的要短一些的(更加有提示性)URL:

$ svn checkout svn://host.example.com/project1

若是一個客戶端鏈接到svnserve進程,以下事情會發生:

  • 客戶端選擇特定的版本庫。

  • 服務器處理版本庫的conf/svnserve.conf文件,而且執行裏面定義的全部認證和受權政策。

  • 依賴於位置和受權政策,

    • 若是沒有收到認證請求,客戶端可能被容許匿名訪問,或者

    • 客戶端收到認證請求,或者

    • 若是操做在「通道模式」,客戶端會宣佈本身已經在外部獲得認證。

在撰寫本文時,服務器還只知道怎樣發送CRAM-MD5[24]認證請求,本質上講,就是服務器發送一些數據到客戶端,客戶端使用MD5哈希算法建立這些數據組合密碼的指紋,而後返回指紋,服務器執行一樣的計算而且來計算結果的一致性,真正的密碼並無在互聯網上傳遞。

固然也有可能,若是客戶端在外部經過通道代理認證,如SSH,在那種狀況下,服務器簡單的檢驗做爲那個用戶的運行,而後使用它做爲認證用戶名,更多信息請看「SSH認證和受權」一節

像你已經猜想到的,版本庫的svnserve.conf文件是控制認證和受權政策的中央機構,這文件與其它配置文件格式相同(見「運行配置區」一節):小節名稱使用方括號標記([]),註釋以井號(#)開始,每一小節都有一些參數能夠設置(variable = value),讓咱們瀏覽這個文件而且學習怎樣使用它們。

此時,svnserve.conf文件的[general]部分包括全部你須要的變量,開始先定義一個保存用戶名和密碼的文件和一個認證域:

[general]
password-db = userfile
realm = example realm

realm是你定義的名稱,這告訴客戶端鏈接的「認證命名空間」,Subversion會在認證提示裏顯示,而且做爲憑證緩存(見「客戶端憑證緩存」一節。)的關鍵字(還有服務器的主機名和端口),password-db參數指出了保存用戶和密碼列表文件,這個文件使用一樣熟悉的格式,舉個例子:

[users]
harry = foopassword
sally = barpassword

password-db的值能夠是用戶文件的絕對或相對路徑,對許多管理員來講,把文件保存在版本庫conf/下的svnserve.conf旁邊是一個簡單的方法。另外一方面,可能你的多個版本庫使用同一個用戶文件,此時,這個文件應該在更公開的地方,版本庫分享用戶文件時必須配置爲相同的域,由於用戶列表本質上定義了一個認證域,不管這個文件在哪裏,必須設置好文件的讀寫權限,若是你知道運行svnserve的用戶,限定這個用戶對這個文件有讀權限是必須的。

svnserve.conf有兩個或多個參數須要設置:它們肯定未認證(匿名)和認證用戶能夠作的事情,參數anon-accessauth-access能夠設置爲noneread或者write,設置爲none會限制全部方式的訪問,read容許只讀訪問,而write容許對版本庫徹底的讀/寫權限:

[general]
password-db = userfile
realm = example realm

# anonymous users can only read the repository
anon-access = read

# authenticated users can both read and write
auth-access = write

實例中的設置其實是參數的缺省值,你必定不要忘了設置它們,若是你但願更保守一點,你能夠徹底封鎖匿名訪問:

[general]
password-db = userfile
realm = example realm

# anonymous users aren't allowed
anon-access = none

# authenticated users can both read and write
auth-access = write

注意svnserve只能識別「總體」的訪問控制,一個用戶能夠有全體的讀/寫權限,或者只讀權限,或沒有訪問權限,沒有對版本庫具體路徑訪問的細節控制,不少項目和站點,這種 訪問控制已經徹底足夠了,然而,若是你但願單個目錄訪問控制,你會須要使用包括mod_authz_svn(見「每目錄訪問控制」一節)的Apache,或者是使用pre-commit鉤子腳原本控制寫訪問(見「鉤子腳本」一節),Subversion的分發版本包含一個commit-access-control.pl和一個更加複雜的svnperms.py腳本能夠做爲pre-commit腳本使用。

svnserve的內置認證會很是容易獲得,由於它避免了建立真實的系統賬號,另外一方面,一些管理員已經建立好了SSH認證框架,在這種狀況下,全部的項目用戶已經擁有了系統賬號和有能力「SSH到」服務器。

SSH與svnserve結合很簡單,客戶端只須要使用svn+ssh://的URL模式來鏈接:

$ whoami
harry

$ svn list svn+ssh://host.example.com/repos/project
harry@host.example.com's password: *****

foo
bar
baz

在這個例子裏,Subversion客戶端會調用一個ssh進程,鏈接到host.example.com,使用用戶harry認證,而後會有一個svnserve私有進程以用戶harry運行。svnserve是以管道模式調用的(-t),它的網絡協議是經過ssh封裝的」,被管道代理的svnserve會知道程序是以用戶harry運行的,若是客戶執行一個提交,認證的用戶名會做爲版本的參數保存到新的修訂本。

這裏要理解的最重要的事情是Subversion客戶端是鏈接到運行中的svnserve守護進程,這種訪問方法不須要一個運行的守護進程,也不須要在必要時喚醒一個,它依賴於ssh來發起一個svnserve進程,而後網絡斷開後終止進程。

當使用svn+ssh://的URL訪問版本庫時,記住是ssh提示請求認證,而svn客戶端程序。這意味着密碼不會有自動緩存(見「客戶端憑證緩存」一節),Subversion客戶端一般會創建多個版本庫的鏈接,但用戶一般會由於密碼緩存特性而沒有注意到這一點,當使用svn+ssh://的URL時,用戶會爲ssh在每次創建鏈接時重複的詢問密碼感到討厭,解決方案是用一個獨立的SSH密碼緩存工具,像類Unix系統的ssh-agent或者是Windows下的pageant

當在一個管道上運行時,認證一般是基於操做系統對版本庫數據庫文件的訪問控制,這同Harry直接經過file:///的URL直接訪問版本庫很是相似,若是有多個系統用戶要直接訪問版本庫,你會但願將他們放到一個常見的組裏,你應該當心的使用umasks。(肯定要閱讀「支持多種版本庫訪問方法」一節)可是即便是在管道模式時,文件svnserve.conf仍是能夠阻止用戶訪問,如auth-access = read或者auth-access = none

你會認爲SSH管道的故事該結束了,但還不是,Subversion容許你在運行配置文件config(見「運行配置區」一節)建立一個自定義的管道行爲方式,舉個例子,假定你但願使用RSH而不是SSH,在config文件的[tunnels]部分做以下定義:

[tunnels]
rsh = rsh

如今你能夠經過指定與定義匹配的URL模式來使用新的管道定義:svn+rsh://host/path。當使用新的URL模式時,Subversion客戶端實際上會在後臺運行rsh host svnserve -t這個命令,若是你在URL中包括一個用戶名(例如,svn+rsh://username@host/path),客戶端也會在本身的命令中包含這部分(rsh username@host svnserve -t),可是你能夠定義比這個更加智能的新的管道模式:

[tunnels]
joessh = $JOESSH /opt/alternate/ssh -p 29934

這個例子裏論證了一些事情,首先,它展示瞭如何讓Subversion客戶端啓動一個特定的管道程序(這個在/opt/alternate/ssh),在這個例子裏,使用svn+joessh://的URL會以-p 29934參數調用特定的SSH程序—對鏈接到非標準端口的程序很是有用。

第二點,它展現了怎樣定義一個自定義的環境變量來覆蓋管道程序中的名字,設置SVN_SSH環境變量是覆蓋缺省的SSH管道的一種簡便方法,可是若是你須要爲多個服務器作出多個不一樣的覆蓋,或許每個都聯繫不一樣的端口或傳遞不一樣的SSH選項,你能夠使用本例論述的機制。如今若是咱們設置JOESSH環境變量,它的值會覆蓋管道中的變量值—會執行$JOESSH而不是/opt/alternate/ssh -p 29934

不只僅是能夠控制客戶端調用ssh方式,也能夠控制服務器中的sshd的行爲方式,在本小節,咱們會展現怎樣控制sshd執行svnserve,包括如何讓多個用戶分享同一個系統賬戶。

做爲開始,定位到你啓動svnserve的賬號的主目錄,肯定這個帳戶已經安裝了一套SSH公開/私有密鑰對,用戶能夠經過公開密鑰認證,由於全部以下的技巧圍繞着使用SSHauthorized_keys文件,密碼認證在這裏不會工做。

若是這個文件還不存在,建立一個authorized_keys文件(在UNIX下一般是~/.ssh/authorized_keys),這個文件的每一行描述了一個容許鏈接的公鑰,這些行一般是下面的形式:

  ssh-dsa AAAABtce9euch.... user@example.com

第一個字段描述了密鑰的類型,第二個字段是未加密的密鑰自己,第三個字段是註釋。然而,這是一個不多人知道的事實,能夠使用一個command來處理整行:

  command="program" ssh-dsa AAAABtce9euch.... user@example.com

command字段設置後,SSH守護進程運行命名的程序而不是一般Subversion客戶端詢問的svnserve -t。這爲實施許多服務器端技巧開啓了大門,在下面的例子裏,咱們簡寫了文件的這些行:

  command="program" TYPE KEY COMMENT

由於咱們能夠指定服務器端執行的命令,咱們很容易來選擇運行一個特定的svnserve程序來而且傳遞給它額外的參數:

  command="/path/to/svnserve -t -r /virtual/root" TYPE KEY COMMENT

在這個例子裏,/path/to/svnserve也許會是一個svnserve程序的包裹腳本,會來設置umask(見「支持多種版本庫訪問方法」一節)。它也展現了怎樣在虛擬根目錄定位一個svnserve,就像咱們常常在使用守護進程模式下運行svnserve同樣。這樣作不只能夠把訪問限制在系統的一部分,也能夠使用戶不須要在svn+ssh://URL裏輸入絕對路徑。

多個用戶也能夠共享同一個賬號,做爲爲每一個用戶建立系統賬戶的替代,咱們建立一個公開/私有密鑰對,而後在authorized_users文件裏放置各自的公鑰,一個用戶一行,使用--tunnel-user選項:

  command="svnserve -t --tunnel-user=harry" TYPE1 KEY1 harry@example.com
command="svnserve -t --tunnel-user=sally" TYPE2 KEY2 sally@example.com

這個例子容許Harry和Sally經過公鑰認證鏈接同一個的帳戶,每一個人自定義的命令將會執行。--tunnel-user選項告訴svnserve -t命令採用命名的參數做爲通過認證的用戶,若是沒有--tunnel-user,全部的提交會做爲共享的系統賬戶提交。

最後要當心:設定經過公鑰共享帳戶進行用戶訪問時還會容許其它形式的SSH訪問,即便你設置了authorized_keyscommand值,舉個例子,用戶仍然能夠經過SSH獲得shell訪問,或者是經過服務器執行X11或者是端口轉發。爲了給用戶儘量少的訪問權限,你或許但願在command命令以後指定一些限制選項:

  command="svnserve -t --tunnel-user=harry",no-port-forwarding,/
no-agent-forwarding,no-X11-forwarding,no-pty /
TYPE1 KEY1 harry@example.com

Apache的HTTP服務器是一個Subversion能夠利用的「重型」網絡服務器,經過一個自定義模塊,httpd可讓Subversion版本庫經過WebDAV/DeltaV協議在客戶端前可見,WebDAV/DeltaV協議是HTTP 1.1的擴展(見http://www.webdav.org/來 查看詳細信息)。這個協議利用了無處不在的HTTP協議是廣域網的核心這一點,添加了寫能力—更明確一點,版本化的寫—能力。結果就是這樣一個標準化的健 壯的系統,做爲Apache 2.0軟件的一部分打包,被許多操做系統和第三方產品支持,網絡管理員也不須要打開另外一個自定義端口。 [25]這樣一個Apache-Subversion服務器具有了許多svnserve沒有的特性,可是也有一點難於配置,靈活一般會帶來複雜性。

下面的討論包括了對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網絡,你基本上須要兩個包裏的四個部分。你須要Apache httpd 2.0和包括的mod_dav DAV模塊,Subversion和與之一同分發的mod_dav_svn文件系統提供者模塊,若是你有了這些組件,網絡化你的版本庫將很是簡單,如:

  • 配置好httpd 2.0,而且使用mod_dav啓動,

  • 爲mod_dav安裝mod_dav_svn插件,它會使用Subversion的庫訪問版本庫,而且

  • 配置你的httpd.conf來輸出(或者說暴露)版本庫。

你能夠經過從源代碼編譯httpd和Subversion來完成前兩個項目,也能夠經過你的系統上的已經編譯好的二進制包來安裝。最新的使用Apache HTTP的Subversion的編譯方法和Apache的配置方式能夠看Subversion源代碼樹根目錄的INSTALL文件。

一旦你安裝了必須的組件,剩下的工做就是在httpd.conf裏配置Apache,使用LoadModule來加載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是做爲共享對象編譯(而不是靜態連接到httpd程序),你須要爲它使用使用LoadModule語句,必定肯定它在mod_dav_svn以前:

LoadModule dav_module         modules/mod_dav.so
LoadModule dav_svn_module modules/mod_dav_svn.so

在你的配置文件後面的位置,你須要告訴Apache你在什麼地方保存Subversion版本庫(也許是多個),位置指示有一個很像XML的符號,開始於一個開始標籤,以一個結束標籤結束,配合中間許多的其它配置。Location指 示的目的是告訴Apache在特定的URL以及子URL下須要特殊的處理,若是是爲Subversion準備的,你但願能夠經過告訴Apache特定 URL是指向版本化的資源,從而把支持轉交給DAV層,你能夠告訴Apache將全部路徑部分(URL中服務器名稱和端口以後的部分)以/repos/開頭的URL交由DAV服務提供者處理。一個DAV服務提供者的版本庫位於/absolute/path/to/repository,能夠使用以下的httpd.conf語法:

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

若是你計劃支持多個具有相同父目錄的Subversion版本庫,你有另外的選擇,SVNParentPath指示,來表示共同的父目錄。舉個例子,若是你知道你會在/usr/local/svn下建立多個Subversion版本庫,而且經過相似http://my.server.com/svn/repos1http://my.server.com/svn/repos2的URL訪問,你能夠用後面例子中的httpd.conf配置語法:

<Location /svn>
DAV svn

# any "/svn/foo" URL will map to a repository /usr/local/svn/foo
SVNParentPath /usr/local/svn
</Location>

使用上面的語法,Apache會代理全部URL路徑部分爲/svn/的請求到Subversion的DAV提供者,Subversion會認爲SVNParentPath指定的目錄下的全部項目是真實的Subversion版本庫,這一般是一個便利的語法,不像是用SVNPath指示,咱們在此沒必要爲建立新的版本庫而重啓Apache。

請肯定當你定義新的位置,不會與其它輸出的位置重疊,例如你的主要DocumentRoot/www,不要把Subversion版本庫輸出到<Location /www/repos>,若是一個請求的URI是/www/repos/foo.c,Apache不知道是直接到repos/foo.c訪問這個文件仍是讓mod_dav_svn代理從Subversion版本庫返回foo.c

在 本階段,你必定要考慮訪問權限問題,若是你已經做爲普通的web服務器運行過Apache,你必定有了一些內容—網頁、腳本和其餘。這些項目已經配置了許 多在Apache下能夠工做的訪問許可,或者更準確一點,容許Apache與這些文件一塊兒工做。Apache看成爲Subversion服務器運行時,同 樣須要正確的訪問許可來讀寫你的Subversion版本庫。(見服務器和訪問許可:一個警告。)

你會須要檢驗權限系統的設置知足Subversion的需求,同時不會把之前的頁面和腳本搞亂。這或許意味着修改Subversion的訪問許可來配合Apache服務器已經使用的工具,或者可能意味着須要使用httpd.confUserGroup指示來指定Apache做爲運行的用戶和Subversion版本庫的組。並非只有一條正確的方式來設置許可,每一個管理員都有不一樣的緣由來以特定的方式操做,只須要意識到許可關聯的問題常常在爲Apache配置Subversion版本庫的過程當中被疏忽。

此時,若是你配置的httpd.conf保存以下的內容

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

這樣你的版本庫對全世界是能夠「匿名」訪問的,直到你配置了一些認證受權政策,你經過Location指示來使Subversion版本庫能夠被任何人訪問,換句話說,

  • 任何人能夠使用Subversion客戶端來從版本庫URL取出一個工做拷貝(或者是它的子目錄),

  • 任何人能夠在瀏覽器輸入版本庫URL交互瀏覽的方式來查看版本庫的最新修訂版本,而且

  • 任何人能夠提交到版本庫。

最簡單的客戶端認證方式是經過HTTP基本認證機制,簡單的使用用戶名和密碼來驗證一個用戶所自稱的身份,Apache提供了一個htpasswd工具來管理可接受的用戶名和密碼,這些就是你但願賦予Subversion特別權限的用戶,讓咱們給Sally和Harry賦予提交權限,首先,咱們須要添加他們到密碼文件。

$ ### First time: use -c to create the file
$ ### Use -m to use MD5 encryption of the password, which is more secure
$ htpasswd -cm /etc/svn-auth-file harry
New password: *****
Re-type new password: *****
Adding password for user harry
$ htpasswd -m /etc/svn-auth-file sally
New password: *******
Re-type new password: *******
Adding password for user sally
$

下一步,你須要在httpd.confLocation區裏添加一些指示來告訴Apache如何來使用這些密碼文件,AuthType指示指定系統使用的認證類型,這種狀況下,咱們須要指定Basic認證系統,AuthName是你提供給認證域一個任意名稱,大多數瀏覽器會在向用戶詢問名稱和密碼的彈出窗口裏顯示這個名稱,最終,使用AuthUserFile指示來指定使用htpasswd建立的密碼文件的位置。

添加完這三個指示,你的<Location>區塊必定像這個樣子:

<Location /svn>
DAV svn
SVNParentPath /usr/local/svn
AuthType Basic
AuthName "Subversion repository"
AuthUserFile /etc/svn-auth-file
</Location>

這個<Location>區塊尚未結束,還不能作任何有用的事情,它只是告訴Apache當須要受權時,要去向Subversion客戶端索要用戶名和密碼。咱們這裏遺漏的,是一些告訴Apache什麼樣客戶端須要受權的指示。哪裏須要受權,Apache就會在哪裏要求認證,最簡單的方式是保護全部的請求,添加Require valid-user來告訴Apache任何請求須要認證的用戶:

<Location /svn>
DAV svn
SVNParentPath /usr/local/svn
AuthType Basic
AuthName "Subversion repository"
AuthUserFile /etc/svn-auth-file
Require valid-user
</Location>

必定要閱讀後面的部分(「受權選項」一節)來獲得Require的細節,和受權政策的其餘設置方法。

須要警戒:HTTP基本認證的密碼是用明文傳輸,所以很是不可靠的,若是你擔憂密碼偷窺,最好是使用某種SSL加密,因此客戶端認證使用https://而不是http://,爲了方便,你能夠配置Apache爲自簽名認證。 [26] 參考Apache的文檔(和OpenSSL文檔)來查看怎樣作。

商業應用須要越過公司防火牆的版本庫訪問,防火牆須要當心的考慮非認證用戶「吸收」他們的網絡流量的狀況,SSL讓那種形式的關注更不容易致使敏感數據泄露。

若是Subversion使用OpenSSL編譯,它就會具有與Subversion服務器使用https://的URL通信的能力,Subversion客戶端使用的Neon庫不只僅能夠用來驗證服務器證書,也能夠必要時提供客戶端證書,若是客戶端和服務器交換了SSL證書而且成功地互相認證,全部剩下的交流都會經過一個會話關鍵字加密。

怎樣產生客戶端和服務器端證書以及怎樣使用它們已經超出了本書的範圍,許多書籍,包括Apache本身的文檔,描述這個任務,如今咱們能夠覆蓋的是普通的客戶端怎樣來管理服務器與客戶端證書。

當經過https://與Apache通信時,一個Subversion客戶端能夠接收兩種類型的信息:

  • 一個服務器證書

  • 一個客戶端證書的要求

若是客戶端接收了一個服務器證書,它須要去驗證它是能夠相信的:這個服務器是它自稱的那一個嗎?OpenSSL庫會去檢驗服務器證書的簽名人或者是核證機構(CA)。若是OpenSSL不能夠自動信任這個CA,或者是一些其餘的問題(如證書過時或者是主機名不匹配),Subversion命令行客戶端會詢問你是否願意仍然信任這個證書:

$ svn list https://host.example.com/repos/project

Error validating server certificate for 'https://host.example.com:443':
- The certificate is not issued by a trusted authority. Use the
fingerprint to validate the certificate manually!
Certificate information:
- Hostname: host.example.com
- Valid: from Jan 30 19:23:56 2004 GMT until Jan 30 19:23:56 2006 GMT
- Issuer: CA, example.com, Sometown, California, US
- Fingerprint: 7d:e1:a9:34:33:39:ba:6a:e9:a5:c4:22:98:7b:76:5c:92:a0:9c:7b

(R)eject, accept (t)emporarily or accept (p)ermanently?

這個對話看起來很熟悉,這是你會在web瀏覽器(另外一種HTTP客戶端,就像Subversion)常常看到的問題,若是你選擇(p)ermanent選項,服務器證書會存放在你存放那個用戶名和密碼緩存(見「客戶端憑證緩存」一節。)的私有運行區auth/中,緩存後,Subversion會自動記住在之後的交流中信任這個證書。

你的運行中servers文件也會給你能力可讓Subversion客戶端自動信任特定的CA,包括全局的或是每主機爲基礎的,只須要設置ssl-authority-files爲一組逗號隔開的PEM加密的CA證書列表:

[global]
ssl-authority-files = /path/to/CAcert1.pem;/path/to/CAcert2.pem

許多OpenSSL安裝包括一些預先定義好的能夠廣泛信任的「缺省的」CA,爲了讓Subversion客戶端自動信任這些標準權威,設置ssl-trust-default-catrue

當 與Apache通話時,Subversion客戶端也會收到一個證書的要求,Apache是詢問客戶端來證實本身的身份:這個客戶端是不是他所說的那一 個?若是一切正常,Subversion客戶端會發送回一個經過Apache信任的CA簽名的私有證書,一個客戶端證書一般會以加密方式存放在磁盤,使用 本地密碼保護,當Subversion收到這個要求,它會詢問你證書的路徑和保護用的密碼:

$ svn list https://host.example.com/repos/project

Authentication realm: https://host.example.com:443
Client certificate filename: /path/to/my/cert.p12
Passphrase for '/path/to/my/cert.p12': ********

注意這個客戶端證書是一個「p12」文件,爲了讓Subversion使用客戶端證書,它必須是運輸標準的PKCS#12格式,大多數瀏覽器能夠導入和導出這種格式的證書,另外一個選擇是用OpenSSL命令行工具來轉化存在的證書爲PKCS#12格式。

再次,運行中servers文件容許你爲每一個主機自動響應這種要求,單個或兩條信息能夠用運行參數來描述:

[groups]
examplehost = host.example.com

[examplehost]
ssl-client-cert-file = /path/to/my/cert.p12
ssl-client-cert-password = somepassword

一旦你設置了ssl-client-cert-filessl-client-cert-password參數,Subversion客戶端能夠自動響應客戶端證書請求而不會打擾你。 [27]

此刻,你已經配置了認證,可是沒有配置受權,Apache能夠要求用戶認證而且肯定身份,可是並無說明這個身份的怎樣容許和限制,這個部分描述了兩種控制訪問版本庫的策略。

最簡單的訪問控制形式是受權特定用戶爲只讀版本庫訪問或者是讀/寫訪問版本庫。

你能夠經過在<Location>區塊添加Require valid-user指示來限制全部的版本庫操做,使用咱們前面的例子,這意味着只有客戶端只能夠是harry或者sally,並且他們必須提供正確的用戶名及對應密碼,這樣容許對Subversion版本庫作任何事:

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

# how to authenticate a user
AuthType Basic
AuthName "Subversion repository"
AuthUserFile /path/to/users/file

# only authenticated users may access the repository
Require valid-user
</Location>

有時候,你不須要這樣嚴密,舉個例子,Subversion本身在http://svn.collab.net/repos/svn的源代碼容許全世界的人執行版本庫的只讀操做(例如檢出咱們的工做拷貝和使用瀏覽器瀏覽版本庫),可是限定只有認證用戶能夠執行寫操做。爲了執行特定的限制,你能夠使用LimitLimitExcept配置指示,就像Location指示,這個區塊有開始和結束標籤,你須要在<Location>中添加這個指示。

LimitLimitExcept中使用的參數是能夠被這個區塊影響的HTTP請求類型,舉個例子,若是你但願禁止全部的版本庫訪問,只是保留當前支持的只讀操做,你能夠使用LimitExcept指示,而且使用GETPROPFINDOPTIONSREPORT請求類型參數,而後前面提到過的Require valid-user指示將會在<LimitExcept>區塊中而不是在<Location>區塊。

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

# how to authenticate a user
AuthType Basic
AuthName "Subversion repository"
AuthUserFile /path/to/users/file

# For any operations other than these, require an authenticated user.
<LimitExcept GET PROPFIND OPTIONS REPORT>
Require valid-user
</LimitExcept>
</Location>

這裏只是一些簡單的例子,想看關於Apache訪問控制Require指示的更深刻信息,能夠查看Apache文檔中的教程集http://httpd.apache.org/docs-2.0/misc/tutorials.html中的Security部分。

也能夠使用Apache的httpd模塊mod_authz_svn更加細緻的設置訪問權限,這個模塊收集客戶端傳遞過來的不一樣的晦澀的URL信息,詢問mod_dav_svn來解碼,而後根據在配置文件定義的訪問政策來裁決請求。

若是你從源代碼建立Subversion,mod_authz_svn會自動附加到mod_dav_svn,許多二進制分發版本也會自動安裝,爲了驗證它是安裝正確,肯定它是在httpd.confLoadModule指示中的mod_dav_svn後面:

LoadModule dav_module         modules/mod_dav.so
LoadModule dav_svn_module modules/mod_dav_svn.so
LoadModule authz_svn_module modules/mod_authz_svn.so

爲了激活這個模塊,你須要配置你的Location區塊的AuthzSVNAccessFile指示,指定保存路徑中的版本庫訪問政策的文件。(一下子咱們將會討論這個文件的格式。)

Apache很是的靈活,你能夠從三種模式裏選擇一種來配置你的區塊,做爲開始,你選擇一種基本的配置模式。(下面的例子很是簡單;見Apache本身的文檔中的認證和受權選項來查看更多的細節。)

最簡單的區塊是容許任何人能夠訪問,在這個場景裏,Apache決不會發送認證請求,全部的用戶做爲「匿名」對待。

例 6.1. 匿名訪問的配置實例。

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

# our access control policy
AuthzSVNAccessFile /path/to/access/file
</Location>

在另外一個極端,你能夠配置爲拒絕全部人的認證,全部客戶端必須提供證實本身身份的證書,你經過Require valid-user指示來阻止無條件的認證,而且定義一種認證的手段。

例 6.2. 一個認證訪問的配置實例。

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

# our access control policy
AuthzSVNAccessFile /path/to/access/file

# only authenticated users may access the repository
Require valid-user

# how to authenticate a user
AuthType Basic
AuthName "Subversion repository"
AuthUserFile /path/to/users/file
</Location>

第 三種流行的模式是容許認證和匿名用戶的組合,舉個例子,許多管理員但願容許匿名用戶讀取特定的版本庫路徑,但但願只有認證用戶能夠讀(或者寫)更多敏感的 區域,在這個設置裏,全部的用戶開始時用匿名用戶訪問版本庫,若是你的訪問控制策略在任什麼時候候要求一個真實的用戶名,Apache將會要求認證客戶端,爲 ­¤,你能夠同時使用Satisfy AnyRequire valid-user指示。

例 6.3. 一個混合認證/匿名訪問的配置實例。

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

# our access control policy
AuthzSVNAccessFile /path/to/access/file

# try anonymous access first, resort to real
# authentication if necessary.
Satisfy Any
Require valid-user

# how to authenticate a user
AuthType Basic
AuthName "Subversion repository"
AuthUserFile /path/to/users/file
</Location>

一旦你的基本Location區塊已經配置了,你能夠建立一個定義一些受權規則的訪問文件。

訪問文件的語法與svnserve.conf和運行中配置文件很是類似,以(#)開頭的行會被忽略,在它的簡單形式裏,每一小節命名一個版本庫和一個裏面的路徑,認證用戶名是在每一個小節中的選項名,每一個選項的值描述了用戶訪問版本庫的級別:r(只讀)或者rw(讀寫),若是用戶沒有提到,訪問是不容許的。

具體一點:這個小節的名稱是[repos-name:path]或者[path]的形式,若是你使用SVNParentPath指示,指定版本庫的名字是很重要的,若是你漏掉了他們,[/some/dir]部分就會與/some/dir的全部版本庫匹配,若是你使用SVNPath指示,所以在你的小節中只是定義路徑也很好—畢竟只有一個版本庫。

[calc:/branches/calc/bug-142]
harry = rw
sally = r

在第一個例子裏,用戶harrycalc版本庫中/branches/calc/bug-142具有徹底的讀寫權利,可是用戶sally只有讀權利,任何其餘用戶禁止訪問這個目錄。

固然,訪問控制是父目錄傳遞給子目錄的,這意味着咱們能夠爲Sally指定一個子目錄的不一樣訪問策略:

[calc:/branches/calc/bug-142]
harry = rw
sally = r

# give sally write access only to the 'testing' subdir
[calc:/branches/calc/bug-142/testing]
sally = rw

如今Sally能夠讀取分支的testing子目錄,但對其餘部分仍是隻能夠讀,同時,Harry對整個分支還繼續有徹底的讀寫權限。

也能夠經過繼承規則明確的的拒絕某人的訪問,只須要設置用戶名參數爲空:

[calc:/branches/calc/bug-142]
harry = rw
sally = r

[calc:/branches/calc/bug-142/secret]
harry =

在這個例子裏,Harry對bug-142目錄樹有徹底的讀寫權限,可是對secret子目錄沒有任何訪問權利。

有一件事須要記住的是須要找到最匹配的目錄,mod_authz_svn模塊首先找到匹配本身的目錄,而後父目錄,而後父目錄的父目錄,就這樣繼續下去,更具體的路徑控制會覆蓋全部繼承下來的訪問控制。

缺省狀況下,沒有人對版本庫有任何訪問,這意味着若是你已經從一個空文件開始,你會但願給全部用戶對版本庫根目錄具有讀權限,你能夠使用*實現,用來表明「全部用戶」:

[/]
* = r

這是一個普通的設置;注意在小節名中沒有提到版本庫名稱,這讓全部版本庫對全部的用戶可讀,無論你是使用SVNPath或是SVNParentPath。當全部用戶對版本庫有了讀權利,你能夠賦予特定用戶對特定子目錄的rw權限。

星號(*)參數須要在這裏詳細強調:這是匹配匿名用戶的惟一模式,若是你已經配置了你的Location區塊容許匿名和認證用戶的混合訪問,全部用戶做爲Apache匿名用戶開始訪問,mod_authz_svn會在要訪問路徑的定義中查找*值;若是找不到,Apache就會要求真實的客戶端認證。

訪問文件也容許你定義一組的用戶,很像Unix的/etc/group文件:

[groups]
calc-developers = harry, sally, joe
paint-developers = frank, sally, jane
everyone = harry, sally, joe, frank, sally, jane

組能夠被賦予通用戶同樣的訪問權限,使用「at」(@)前綴來加以區別:

[calc:/projects/calc]
@calc-developers = rw

[paint:/projects/paint]
@paint-developers = rw
jane = r

組中也能夠定義爲包含其它的組:

[groups]
calc-developers = harry, sally, joe
paint-developers = frank, sally, jane
everyone = @calc-developers, @paint-developers

...而且很是接近。

mod_dav_svn模塊作了許多工做來肯定你標記爲「不可讀」的數據不會因意外而泄露,這意味着須要緊密監控經過svn checkout或是svn update返回的路徑和文件內容,若是這些命令遇到一些根據認證策略不是可讀的路徑,這個路徑一般會被一塊兒忽略,在歷史或者重命名操做時—例如運行一個相似svn cat -r OLD foo.c的命令來操做一個好久之前改過名字的文件 — 若是一個對象的之前的名字檢測到是隻讀的,重命令追蹤就會終止。

全部的路徑檢查在有時會很是昂貴,特別是svn log的狀況。當檢索一列修訂版本時,服務器會查看全部修訂版本修改的路徑,而且檢查可讀性,若是發現了一個不可讀路徑,它會從修訂版本的修改路徑中忽略(能夠查看--verbose選項),而且整個的日誌信息會被禁止,沒必要多說,這種影響大量文件修訂版本的操做會很是耗時。這是安全的代價:即便你並無配置mod_authz_svn模塊,mod_dav_svn仍是會詢問httpd來對全部路徑運行認證檢查,mod_dav_svn模塊沒有辦法知道那個認證模塊被安裝,因此只有詢問Apache來調用所提供的模塊。

在另外一方面,也有一個安全艙門容許你用安全特性來交換速度,若是你不是堅持要求有每目錄受權(如不使用 mod_authz_svn和相似的模塊),你就能夠關閉全部的路徑檢查,在你的httpd.conf文件,使用SVNPathAuthz指示:

例 6.4. 關閉全部的路經檢查

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

SVNPathAuthz off
</Location>

SVNPathAuthz指示缺省是「on」,當設置爲「off」時,全部的路徑爲基礎的受權都會關閉;mod_dav_svn中止對每一個目錄調用受權檢查。

咱們已經覆蓋了關於認證和受權的Apache和mod_dav_svn的大多數選項,可是Apache還提供了許多很好的特性。

一 個很是有用的好處是使用Apache/WebDAV配置Subversion版本庫時能夠用普通的瀏覽器察看最新的版本庫文件,由於Subversion 使用URL來鑑別版本庫版本化的資源,版本庫使用的HTTP爲基礎的URL也能夠直接輸入到Web瀏覽器中,你的瀏覽器會發送一個GET請求到URL,根據訪問的URL是指向一個版本化的目錄仍是文件,mod_dav_svn會負責列出目錄列表或者是文件內容。

因 爲URL不能肯定你所但願看到的資源的版本,mod_dav_svn會一直返回最新的版本,這樣會有一些美妙的反作用,你能夠直接把Subversion 的URL傳遞給文檔做爲引用,這些URL會一直指向文檔最新的材料,固然,你也能夠在別的網站做爲超鏈使用這些URL。

你 一般會在版本化的文件的URL以外獲得更多地用處—畢竟那裏是有趣的內容存在的地方,可是你會偶爾瀏覽一個Subversion的目錄列表,你會很快發現 展現列表生成的HTML很是基本,而且必定沒有在外觀上(或者是有趣上)下功夫,爲了自定義這些目錄顯示,Subversion提供了一個XML目錄特 性,一個單獨的SVNIndexXSLT指示在你的httpd.conf文件版本庫的Location塊裏,它將會指導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做爲一個健壯的Web服務器的許多特性也能夠用來增長Subversion的功能性和安全性,Subversion使用Neon與Apache通信,這是一種通常的HTTP/WebDAV庫,能夠支持SSL和Deflate壓縮(是gzipPKZIP程序用來「壓縮」文件爲數據塊的同樣的算法)之類的機制。你只須要編譯你但願Subversion和Apache須要的特性,而且正確的配置程序來使用這些特性。

Deflate壓縮給服務器和客戶端帶來了更多地負擔,壓縮和解壓縮減小了網絡傳輸的實際文件的大小,若是網絡帶寬比較緊缺,這種方法會大大提升服務器和客戶端之間發送數據的速度,在極端狀況下,這種最小化的傳輸會形成超時和成功的區別。

不 怎麼有趣,但一樣重要,是Apache和Subversion關係的一些特性,像能夠指定自定義的端口(而不是缺省的HTTP的80)或者是一個 Subversion能夠被訪問的虛擬主機名,或者是經過代理服務器訪問的能力,這些特性都是Neon所支持的,因此Subversion輕易獲得這些支 持。

最後,由於mod_dav_svn是使用一個半完成的WebDAV/DeltaV方言,因此經過第三方的DAV客戶端訪問也是可能的,幾乎全部的現代操做系統(Win3二、OS X和Linux)都有把DAV服務器影射爲普通的網絡「共享」的內置能力,這是一個複雜的主題;察看附錄 B, WebDAV和自動版本化來獲得更多細節。

你已經看到了一個版本庫能夠用多種方式訪問,可是能夠—或者說安全的—用幾種方式同時並行的訪問你的版本庫嗎?回答是能夠,假若你有一些深謀遠慮的使用。

在任何給定的時間,這些進程會要求讀或者寫訪問你的版本庫:

  • 常規的系統用戶使用Subversion客戶端(客戶端程序自己)經過file:///URL直接訪問版本庫;

  • 常規的系統用戶鏈接使用SSH調用的訪問版本庫的svnserve進程(以它們本身運行);

  • 一個svnserve進程—是一個守護進程或是經過inetd啓動的—做爲一個固定的用戶運行;

  • 一個Apache httpd進程,以一個固定用戶運行。

最一般的一個問題是管理進入到版本庫的全部權和訪問許可,是前面例子的全部進程 (或者說是用戶)都有讀寫Berkeley DB的權限?假定你有一個類Unix的操做系統,一個直接的辦法是在新的svn組添加全部潛在的用戶,而後讓這個組徹底擁有版本庫,但這樣還不足夠,由於一個進程會使用不友好的umask來寫數據庫文件—用來防止別的用戶的訪問。

因此下一步咱們不選擇爲每一個版本庫用戶設置一個共同的組的方法,而是強制每一個版本庫訪問進程使用一個健全的umask。對直接訪問版本庫的用戶,你能夠使用svn的包裹腳原本首先設置umask 002,而後運行真實的svn客戶端程序,你能夠爲svnserve寫相同的腳本,而且增長umask 002命令到Apache本身的啓動腳本apachectl中。例如:

$ cat /usr/bin/svn

#!/bin/sh

umask 002
/usr/bin/svn-real "$@"

另外一個在類Unix系統下常見的問題是,當版本庫在使用時,BerkeleyDB有時候建立一個新的日誌文件來記錄它的東西,即便這個版本庫是徹底由svn組擁有,這個新建立的文件不是必須被同一個組擁有,這給你的用戶形成了更多地許可問題。一個好的工做區應該設置組的SUID字節到版本庫的db目錄,這會致使全部新建立的日誌文件擁有同父目錄相同的組擁有者。

一旦你跳過了這些障礙,你的版本庫必定是能夠經過各類可能的手段訪問了,這看起來有點凌亂和複雜,可是這個讓多個用戶分享對一個文件的寫權限的問題是一個經典問題,而且常常是沒有優雅的解決。

幸運的是,大多數版本庫管理員不須要這樣複雜的配置,用戶若是但願訪問本機的版本庫,並非必定要經過file://的URL—他們能夠用localhost機器名聯繫Apache的HTTP服務器或者是svnserve,協議分別是http://svn://。爲你的Subversion版本庫維護多個服務器進程,版本庫會變得超出須要的頭痛,咱們建議你選擇最符合你的須要的版本庫,而且堅持使用!



[22] 這個問題其實是一個FAQ,源自錯誤的服務器配置。

[23] 再次重申,一個常見的錯誤是把服務器配置爲從不會請求認證,當用戶傳遞--username--password給客戶端時,他們驚奇的發現它們沒有被使用,如新的修訂版本看起來始終是由匿名用戶提交的!

[24] 見RFC 2195。

[25] 他們討厭這樣作。

[

相關文章
相關標籤/搜索