本書使用創做共用署名許可證,能夠經過訪問http://creativecommons.org/licenses/by/2.0/或者發送郵件到Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA來查看本許可證的內容。python
(TBA)linux
目錄git
插圖清單程序員
表格清單web
範例清單正則表達式
.svn/entries
文件內容
一轉眼到了2005年,Subversion 1.2發佈了,個人注意力又轉到了這個領域,正好我有了作一個網站的念頭,因此就有了Subversion中文站(http://www.subversion.org.cn),而同時我也開始申請成爲這本書的中文官方翻譯。數據庫
這 本書的官方翻譯要求我必須使用DocBook,要求我必須有一個團隊,因而我在這兩方面進行了努力,因而有人開始與我並肩工做了。在這段翻譯的時間裏陸續 有人加入進來,按照時間順序是rocksun、jerry、nashwang、gxio、MichaelDuan、viv、lifengliu2000、 genedna、luyongshou、leasun和nannan。可是必需要說明這不是對翻譯貢獻大小的排序,你們都在本身的能力範圍內爲這個翻譯作 出了本身的貢獻,感謝咱們成員的努力,也感謝許多對咱們提出建議的朋友。
開始的時候並無以爲作好這件事有多難,但當看到翻譯的東西本身都讀不懂的時候,我感到了一種壓力。若是這翻譯還不如英文,咱們還有沒有必要繼續。好在在你們的支持下,我愈來愈喜歡這本書了,漸漸的發現本身能夠把這本書看成本身的參考材料了。
但 是,我也有過許多疑惑,在中國人們彷佛只是把版本控制工具當作一個代碼分享的工具,而沒有把它融入到整個軟件開發的生命週期當中,這也難怪,大多數中國軟 件的壽命彷佛並不長,不須要那麼多複雜的配置管理。因此咱們的這些翻譯可以給你們帶來多大的幫助要由中國軟件的發展決定,但願咱們的工做可以伴隨着中國軟 件的騰飛不斷成長。
讓咱們一塊兒努力吧!
—
,青島,2005年11月29日一個不太好的常見問題列表(FAQ),經常並非由人們實際上的問題組成,而常常是由做者期待的問題組成。或許你曾經見過這種類型的問題:
固然,沒人能夠承諾這本書能夠回答全部問題。儘管有時候一些前人提問的驚人一致性讓你感受是心靈感應;你仍有可能在社區的知識庫裏摔跤,空手而歸。若是有這種狀況,最好的辦法是寫明問題發送email到<
users@subversion.tigris.org>, 做者還在那裏關注着社區,不只僅封面提到的三位,還包括許多曾經做出修正與提供原始材料的人。從社區的視角,幫你解決問題只是逐步的調整這本書,進一步調 整Subversion自己以更合理的適合用戶使用這樣一個大工程的一個有趣的額外效用。他們渴望你的信息,不只僅能夠幫助你,也由於能夠幫助他們。與 Subversion這樣活躍的自由軟件項目一塊兒,你並不孤單。
讓這本書將成爲你的第一個夥伴。
—
,芝加哥,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徹底參考包括了全部命令的參考,這個附錄包括了許多有用的主題,當你完成了本書的閱讀,你會常常去看這個章節。
記述了Subversion的歷史與特性、架構、組件和安裝方法,還包括一個快速入門指南。
解釋了版本控制的基礎知識,介紹了不一樣的版本模型,隨同講述了Subversion的版本庫,工做拷貝和修訂版本的概念。
帶領你做爲一個Subversion用戶開始工做,示範了怎樣使用Subversion得到、修改和提交數據。
討論分支、合併與標籤,包括一個最佳實踐,一般的用例,怎樣取消修改,以及怎樣從一個分支轉到另外一個分支。
講述Subversion版本庫的基本概念,怎樣創建、配置和維護版本庫,以及你能夠使用的工具。
解釋了怎樣配置Subversion服務器,以及三種訪問版本庫的方式,HTTP
、svn
協議和本地訪問。這裏也介紹了認證的細節,以及受權與匿名訪問方式。
研究Subversion客戶配置文件,文件和目錄屬性,怎樣忽略
工做拷貝中的文件,怎樣引入外部版本樹到工做拷貝,最後介紹瞭如何掌握賣主分支。
介紹了Subversion的核心,Subversion文件系統,以及從程序員的角度如何看待工做拷貝的管理區域,介紹瞭如何使用公共APIs寫程序使用Subversion,最重要的是,怎樣投身到Subversion的開發當中去。
深刻研究研究全部的命令,包括 svn、svnadmin、和svnlook以及大量的相關實例
比較Subversion與CVS的異同點,消除多年使用CVS養出的壞習慣的建議,包括subversion版本號、標記版本的目錄、離線操做、update與status、分支、標籤、元數據、衝突和認證。
描述了WebDAV與DeltaV的細節,和怎樣將你的Subversion版本庫做爲可讀/寫的DAV共享裝載。
討論一些支持和使用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社區。
感謝個人妻子Frances,在幾個月裏,我一直在對你說,「可是親愛的,我還在爲這本書工做」,非比尋常,「可是親愛的,我還在處理郵件」。我不知道她爲何會如此耐心!她是我完美的平衡點。
感謝個人家人對個人鼓勵,不管是否對個人題目感興趣。(你知道的,一我的說 「哇,你正在寫一本書?」,而後當他知道你是寫一本計算機書時,那種驚訝就變得沒有那麼多了。)
很是很是感謝個人妻子Marie的理解,支持和最重要的耐心。感謝引導我學會UNIX編程的兄弟Eric,感謝個人母親和外祖母的支持,對我在聖誕夜裏埋頭工做的理解。
Mike和Ben:與大家一塊兒工做很是快樂,Heck,咱們在一塊兒工做很愉快!
版本控制是管理信息變化的藝術,它很早就成爲了程序員重要的工具,程序員常常會花時間作一點小修改而後次日又把它改回來。可是版本控制的做用不只在軟件開發領域,任何須要管理頻繁信息改變的地方都須要它,這就是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很是容易維護,能夠輕易的用其餘語言操做。
圖 1.1 「Subversion的架構」從高處「俯視」Subersion的設計。
最簡單的安裝辦法就是下載相應操做系統的二進制包,Subversion的網站(http://subversion.tigris.org)上一般會有志願者提供的包能夠下載,對於微軟操做系統,網站上一般會有圖形化的安裝包,對於類Unix系統,你能夠使用它們自己的打包系統(PRMs、DEBs、ports tree等等)獲得Subversion。
你也能夠選擇從源代碼直接編譯Subversion,從網站下載最新的源代碼,解壓縮,根據INSTALL
文件的指導進行編譯。注意,經過這些源代碼能夠徹底編譯訪問服務器的命令行客戶端工具(一般是apr,apr-util和neno庫)。可是可選部分有許多依賴,如Berkeley DB和Apache httpd。若是你但願作一個徹底的編譯,肯定你有全部INSTALL
文件中記述的包。若是你計劃經過Subversiong自己工做,你能夠使用客戶端程序取得最新的,帶血的源代碼,這部份內容見「取得源代碼」一節。
Subversion安裝以後,分爲幾個部分,這是一個快速瀏覽。不要懼怕這些讓你撓頭的簡略描述,本書有足夠的內容來減小這種混亂。
假定你已經將Subversion正確安裝,你已經準備好開始,下兩章將帶領你使用svn,Subversion的客戶端程序。
若是版本控制或者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
(或任何名稱)的目錄裏,你的項目要有branches
、tags
和trunk
三個頂級目錄,後面你就會知道這樣作的緣由。trunk
目錄保存全部的數據,而branches
和tags
都是空的:
/tmp/myproject/branches/
/tmp/myproject/tags/
/tmp/myproject/trunk/
foo.c
bar.c
Makefile
…
branches
、tags
和trunk
子目錄不是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 章 配置服務器,學習使用不一樣的服務器以及配置。
這一章是對Subversion一個簡短和隨意的介紹,若是你對版本控制很陌生,這一章節徹底爲你準備的,咱們從討論基本概念開始,深刻理解Subversion的思想,而後展現許多簡單的實例。
儘管咱們的例子展現了人們如何分享程序源代碼,仍然要記住Subversion能夠控制全部類型的文件—它並無限制在只爲程序員工做。
Subversion是一種集中的分享信息的系統,它的核心是版本庫,它儲存全部的數據,版本庫按照文件樹形式儲存數據—包括文件和目錄。任意數量的客戶端能夠鏈接到版本庫,讀寫這些文件。經過寫,別人能夠看到這些信息,經過讀數據,能夠看到別人的修改。圖 2.1 「一個典型的客戶/服務器系統」描述了這種關係:
版本控制系統的核心任務是提供協做編輯和數據共享,可是不一樣的系統使用不一樣的策略來達到目的。
全部的版本控制系統都須要解決這樣一個基礎問題:怎樣讓系統容許用戶共享信息,而不會讓他們因意外而互相干擾?版本庫裏意外覆蓋別人的更改很是的容易。
考慮圖 2.2 「須要避免的問題」的情景,咱們有兩個共同工做者,Harry和Sally,他們想同時編輯版本庫裏的同一個文件,若是首先Harry保存它的修改,過了一會,Sally可能湊巧用本身的版本覆蓋了這些文件,Harry的更改不會永遠消失(由於系統記錄了每次修改),Harry全部的修改不會出如今Sally的文件中,因此Harry的工做仍是丟失了—至少是從最新的版本中丟失了—並且是意外的,這就是咱們要明確避免的狀況!
許多版本控制系統使用鎖定-修改-解鎖這種機制解決這種問題,在這樣的系統裏,在一個時間段裏版本庫的一個文件只容許被一我的修改。首先在修改以前,Harry要「鎖定」 住這個文件,鎖定很像是從圖書館借一本書,若是Harry鎖住這個文件,Sally不能作任何修改,若是Sally想請求獲得一個鎖,版本庫會拒絕這個請 求。在Harry結束編輯而且放開這個鎖以前,她只能夠閱讀文件。Harry解鎖後,就要換班了,Sally獲得本身的輪換位置,鎖定而且開始編輯這個文 件。圖 2.3 「鎖定-修改-解鎖 方案」描述了這樣的解決方案。
鎖定-修改-解鎖模型有一點問題就是限制太多,常常會成爲用戶的障礙:
鎖定可能致使管理問題。有時候Harry會鎖住文件而後忘了此事,這就是說Sally一直等待解鎖來編輯這些文件,她在這裏僵住了。而後Harry去旅行了,如今Sally只好去找管理員放開鎖,這種狀況會致使沒必要要的耽擱和時間浪費。
鎖定可能致使沒必要要的線性化開發。若是Harry編輯一個文件的開始,Sally想編輯同一個文件的結尾,這種修改不會衝突,設想修改能夠正確的合併到一塊兒,他們能夠輕鬆的並行工做而沒有太多的壞處,沒有必要讓他們輪流工做。
鎖定可能致使錯誤的安全狀態。假 設Harry鎖定和編輯一個文件A,同時Sally鎖定並編輯文件B,若是A和B互相依賴,這種變化是必須同時做的,這樣A和B不能正確的工做了,鎖定機 制對防止此類問題將無能爲力—從而產生了一種處於安全狀態的假相。很容易想象Harry和Sally都覺得本身鎖住了文件,並且從一個安全,孤立的狀況開 始工做,於是沒有儘早發現他們不匹配的修改。
是時候從抽象轉到具體了,在本小節,咱們會展現一個Subversion真實使用的例子。
你已經閱讀過了關於工做拷貝的內容,如今咱們要講一講客戶端怎樣創建和使用它。
一個典型的Subversion的版本庫常常包含許多項目的文件(或者說源代碼),一般每個項目都是版本庫的子目錄,在這種安排下,一個用戶的工做拷貝每每對應版本庫的的一個子目錄。
舉一個例子,你的版本庫包含兩個軟件項目,paint
和calc
。每一個項目在它們各自的頂級子目錄下,見圖 2.6 「版本庫的文件系統」。
$ 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 commit操做不作任何事情,svn update不作任何事情。
在工做目錄已經修改,從基本修訂版本以後沒有修改提交到版本庫。本地修改沒有提交,所以svn commit會成功的提交,svn update不作任何事情。
這個文件在工做目錄沒有修改,但在版本庫中已經修改了。這個文件最終將更新到最新版本,成爲當時的公共修訂版本。svn commit不作任何事情,svn update將會取得最新的版本到工做拷貝。
這個文件在工做目錄和版本庫都獲得修改。一個svn commit將會失敗,這個文件必須首先更新,svn update命令會合並公共和本地修改,若是Subversion不能夠自動完成,將會讓用戶解決衝突。
這看起來須要記錄不少事情,可是svn status命令能夠告訴你工做拷貝中文件的狀態,關於此命令更多的信息,請看「svn status」一節。
事實上,每次運行svn commit,你的工做拷貝都會進入混合多個修訂版本的狀態,剛剛提交的文件會比其餘文件有更高的修訂版本號。通過屢次提交(之間沒有更新),你的工做拷貝會徹底是混合的修訂版本。即便只有你一我的使用版本庫,你依然會見到這個現象。爲了檢驗混合工做修訂版本,能夠使用svn status --verbose命令(詳細信息見「svn status」一節)。
一般,新用戶對於工做拷貝的混合修訂版本一無所知,這會讓人糊塗,由於許多客戶端命令對於所檢驗條目的修訂版本很敏感。例如svn log命令顯示一個文件或目錄的歷史修改信息(見「svn log」一節),當用戶對一個工做拷貝對象調用這個命令,他們但願看到這個對象的整個歷史信息。可是若是這個對象的修訂版本已經至關老了(一般由於很長時間沒有運行svn update),此時會顯示比這個對象更老的歷史。
如今,咱們將要深刻到Subversion到使用細節當中,完成本章,你將學會全部平常使用的Subversion命令,你將從一個初始化檢出開始,作出修改並檢查,你也將會學到如何將別人的修改取到工做拷貝,檢查他們,並解決全部可能發生的衝突。
這一章並非Subversion命令的徹底列表—而是你將會遇到的最經常使用任務的介紹,這一章假定你已經讀過而且理解了第 2 章 基本概念,並且熟悉Subversion的模型,若是想查看全部命令的參考,見第 9 章 Subversion徹底參考。
使用svn import來導入一個新項目到Subversion的版本庫,這恐怕是使用Subversion一定要作的第一步操做,但不是常常發生的事情,詳細介紹能夠看本章後面的「svn import」一節。
在繼續以前你必定要知道如何識別版本庫的一個修訂版本,像你在「修訂版本」一節看到的,一個修訂版本就是版本庫的一個「快照」,當你的版本庫持續擴大,你必須有手段來識別這些快照。
你能夠使用--revision
(-r
)參數來選擇特定修訂版本(svn --revision REV),你也能夠指定在兩個修訂版本之間的一個範圍 (svn --revision REV1:REV2)。你能夠在Subversion中經過修訂版本號、關鍵字或日期指定特定修訂版本。
當你新建了一個Subversion版本庫,從修訂版本號0開始,每一次成功的提交加1,當你提交成功,Subversion告訴客戶端這個新版本號:
$ svn commit --message "Corrected number of cheese slices."
Sending sandwich.txt
Transmitting file data .
Committed revision 3.
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
大多數時候,你會使用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有許多特性、選項和華而不實的高級功能,但平常的工做中你只使用其中的一小部分,有一些只在特殊狀況纔會使用,在這一節裏,咱們會介紹許多你在平常工做中常見的命令。
當你在一個團隊的項目裏工做時,你但願更新你的工做拷貝獲得全部其餘人這段時間做出的修改,使用svn update讓你的工做拷貝與最新的版本同步。
$ svn update
U foo.c
U bar.c
Updated to revision 2.
這種狀況下,其餘人在你上次更新以後提交了對foo.c
和bar.c
的修改,所以Subversion更新你的工做拷貝來引入這些更改。
讓咱們認真檢查svn update的輸出,當服務器發送修改到你的工做拷貝,一個字母顯示在每個項目以前,來讓你知道Subversion對你的工做拷貝作了什麼操做:
修改文件,能夠使用文本編輯器、字處理軟件、圖形程序或任何你經常使用的工具,Subverion處理二進制文件像同文本文件同樣—效率也同樣。
這些是經常使用的能夠修改目錄樹結構的子命令(咱們會在後麪包括svn import和svn mkdir)。
預約將文件、目錄或者符號鏈foo
從版本庫中刪除掉,若是foo是文件,它立刻從工做拷貝中刪除,若是是目錄,不會被刪除,可是Subversion準備好刪除了,當你提交你的修改,foo
就會在你的工做拷貝和版本庫中被刪除。[3]
創建一個新的項目bar
做爲foo
的複製品,當在下次提交時會將bar
添加到版本庫,這種拷貝歷史會記錄下來(按照來自foo
的方式記錄),svn copy並不創建中介目錄。
這個命令與與運行svn copy foo bar; svn delete foo徹底相同,bar
做爲foo
的拷貝準備添加,foo
已經預約要被刪除,svn move不創建中介的目錄。
相對於其餘命令,你會更多地使用這個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
C item
D item
M item
R item
文件、目錄或是符號鏈item
預約將要替換版本庫中的item
,這意味着這個對象首先要被刪除,另一個同名的對象將要被添加,全部的操做發生在一個修訂版本。
X item
? 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 add、svn import和svn status命令忽略這個文件,關於忽略文件,見「svn:ignore
」一節。注意,這個符號只會在使用svn status的參數--no-ignore
時纔會出現—不然這個文件會被忽略且不會顯示!
第二列說明文件或目錄的屬性的狀態(更多細節能夠看「屬性」一節),若是一個M
出如今第二列,說明屬性被修改了,不然顯示空白。
第三列只顯示空白或者L
,L
表示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,你的README
和trout.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
舉個例子,你能夠把補丁文件發送郵件到其餘開發者,在提交以前審覈和測試。
假設你經過上面的diff輸出發現你不當心用編輯器在README
中輸入了一些字符。
$ svn revert README
Reverted 'README'
Subversion把文件恢復到未修改的狀態,叫作.svn
目錄的「原始」拷貝,應該知道svn revert能夠恢復任何預約要作的操做,舉個例子,你再也不想添加一個文件:
$ svn status foo
? foo
$ svn add foo
A foo
$ svn revert foo
Reverted 'foo'
$ svn status foo
? foo
$ svn status README
README
$ svn delete README
D README
$ svn revert README
Reverted 'README'
$ svn status README
README
咱們能夠使用svn status -u來預測衝突,當你運行svn update一些有趣的事情發生了:
$ svn update
U INSTALL
G README
C bar.c
Updated to revision 46.
U
和G
不必關心,文件乾淨的接受了版本庫的變化,文件標示爲U
代表本地沒有修改,文件已經根據版本庫更新。G
標示合併,標示本地已經修改過,與版本庫沒有重迭的地方,已經合併。
可是C
表示衝突,說明服務器上的改動同你的改動衝突了,你須要本身手工去解決。
若是Subversion認爲這個文件是可合併的,它會置入衝突標記—特殊的橫線分開衝突的「兩面」—在文件裏可視化的描述重疊的部分(Subversion使用svn:mime-type
屬性來決定一個文件是否能夠使用上下文的,以行爲基礎合併,更多信息能夠看「svn:mime-type
」一節)。
對於每個衝突的文件,Subversion放置三個額外的未版本化文件到你的工做拷貝:
filename.mine
你更新前的文件,沒有衝突標誌,只是你最新更改的內容。(若是Subversion認爲這個文件不能夠合併,.mine
文件不會建立,由於它和工做文件相同。)
filename.rOLDREV
這是你的作更新操做之前的BASE
版本文件,就是你在上次更新以後未做更改的版本。
filename.rNEWREV
這是你的Subversion客戶端從服務器剛剛收到的版本,這個文件對應版本庫的HEAD
版本。
這裏OLDREV
是你的.svn
目錄中的修訂版本號,NEWREV
是版本庫中HEAD
的版本號。
舉一個例子,Sally修改了sandwich.txt
,Harry剛剛改變了他的本地拷貝中的這個文件而且提交到服務器,Sally在提交以前更新它的工做拷貝獲得了衝突:
$ svn update
C sandwich.txt
Updated to revision 2.
$ ls -1
sandwich.txt
sandwich.txt.mine
sandwich.txt.r1
sandwich.txt.r2
在這種狀況下,Subversion不會容許你提交sandwich.txt
,直到你的三個臨時文件被刪掉。
$ svn commit --message "Add a few more things"
svn: 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'
第一次嘗試解決衝突讓人感受很懼怕,但通過一點訓練,它簡單的像是騎着車子下坡。
$ 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
=======
=======
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生成的三個文件—包括你未做更新的文件。你也能夠使用第三方的合併工具檢驗這三個文件。
若是你只是但願取消你的修改,你能夠僅僅拷貝Subversion爲你生成的文件替換你的工做拷貝:
$ svn update
C sandwich.txt
Updated to revision 2.
$ ls sandwich.*
sandwich.txt sandwich.txt.mine sandwich.txt.r2 sandwich.txt.r1
$ cp sandwich.txt.r2 sandwich.txt
$ svn resolved sandwich.txt
最後!你的修改結束了,你合併了服務器上全部的修改,你準備好提交修改到版本庫。
svn commit命令發送全部的修改到版本庫,當你提交修改時,你須要提供一些描述修改的日誌信息,你的信息會附到這個修訂版本上,若是信息很簡短,你能夠在命令行中使用--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基本的工做週期,還有許多其它特性能夠管理你得版本庫和工做拷貝,可是隻使用前面介紹的命令你就能夠很輕鬆的工做了。
$ 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 -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將會比較你的工做文件與緩存在.svn
的「原始」拷貝:
$ svn diff
Index: rules.txt
===================================================================
--- rules.txt (revision 3)
+++ rules.txt (working copy)
@@ -1,4 +1,5 @@
Be kind to others
Freedom = Responsibility
Everything in moderation
-Chew with your mouth open
+Chew with your mouth closed
+Listen when others are speaking
$
若是傳遞一個--revision
(-r
)參數,你的工做拷貝會與指定的版本比較。
$ svn diff --revision 3 rules.txt
Index: rules.txt
===================================================================
--- rules.txt (revision 3)
+++ rules.txt (working copy)
@@ -1,4 +1,5 @@
Be kind to others
Freedom = Responsibility
Everything in moderation
-Chew with your mouth open
+Chew with your mouth closed
+Listen when others are speaking
$
若是經過--revision
(-r
)傳遞兩個版本號,經過冒號分開,這兩個版本會進行比較。
$ svn diff --revision 2:3 rules.txt
Index: rules.txt
===================================================================
--- rules.txt (revision 2)
+++ rules.txt (revision 3)
@@ -1,4 +1,4 @@
Be kind to others
-Freedom = Chocolate Ice Cream
+Freedom = Responsibility
Everything in moderation
Chew with your mouth open
$
你不只能夠用svn diff比較你工做拷貝中的文件,你甚至能夠經過提供一個URL參數來比較版本庫中兩個文件的的區別,一般在本地機器沒有工做拷貝時很是有用:
$ svn diff --revision 4:5 http://svn.red-bean.com/repos/example/trunk/text/rules.txt
…
$
若是你只是但願檢查一個過去的版本而不但願察看它們的區別,使用svn cat:
$ svn cat --revision 2 rules.txt
Be kind to others
Freedom = Chocolate Ice Cream
Everything in moderation
Chew with your mouth open
$
$ svn cat --revision 2 rules.txt > rules.txt.v2
$
你必定疑惑爲何不僅是使用svn update --revision ,將文件更新到舊的文件,咱們有使用svn cat的緣由。
svn list能夠在不下載文件到本地目錄的狀況下來察看目錄中的文件:
$ svn list http://svn.collab.net/repos/svn
README
branches/
clients/
tags/
trunk/
若是你但願察看詳細信息,你能夠使用--verbose
(-v
)參數:
$ svn list --verbose http://svn.collab.net/repos/svn
2755 harry 1331 Jul 28 02:07 README
2773 sally Jul 29 15:07 branches/
2769 sally Jul 29 12:07 clients/
2698 harry Jul 24 18:07 tags/
2785 sally Jul 29 19:07 trunk/
除了以上的命令,你能夠使用帶參數--revision
的svn update和svn 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
…
不象這章前面討論的那些常常用到的命令,這些命令只是偶爾被用到。
svn import命令是拷貝用戶的一個未被版本化的目錄樹到版本庫最快的方法,若是須要,它也要創建一些中介文件。
$ svnadmin create /usr/local/svn/newrepos
$ svn import mytree file:///usr/local/svn/newrepos/some/project /
-m "Initial import"
Adding mytree/foo.c
Adding mytree/bar.c
Adding mytree/subdir
Adding mytree/subdir/quux.h
Committed revision 1.
在上一個例子裏,將會拷貝目錄mytree
到版本庫的some/project
下:
$ svn list file:///usr/local/svn/newrepos/some/project
bar.c
foo.c
subdir/
注意,在導入以後,原來的目錄樹並沒有轉化成工做拷貝,爲了開始工做,你仍是須要運行svn checkout導出一個工做拷貝。
咱們已經覆蓋了大多數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 章 基本概念)。
假設你的工做是維護本公司一個部門的手冊文檔,一天,另外一個部門問你要相同的手冊,但一些地方會有「區別」,由於他們有不一樣的須要。
這種狀況下你會怎樣作?顯而易見的方法是:做一個版本的拷貝,而後分別維護兩個版本,只要任何一個部門告訴要作一些小修改,你必須選擇在對應的版本進行更改。
你也許但願在兩個版本同時做修改,舉個例子,你在第一個版本發現了一個拼寫錯誤,很顯然這個錯誤也會出如今第二個版本里。兩份文檔幾乎相同,畢竟,只有許多特定的微小區別。
這是分支的基本概念—正如它的名字,開發的一條線獨立於另外一條線,若是回顧歷史,能夠發現兩條線分享共同的歷史,一個分支老是從一個備份開始的,從那裏開始,發展本身獨有的歷史(見 圖 4.1 「分支開發」)。
在這一點上,你必須理解每一次提交是怎樣創建整個新的文件系統樹(叫作「修訂版本」)的,若是沒有,能夠回頭去讀「修訂版本」一節。
對於本章節,咱們會回到第2章的同一個例子,還記得你和你的合做者Sally分享一個包含兩個項目的版本庫,paint
和calc
。注意圖 4.2 「開始規劃版本庫」,然而,如今每一個項目的都有一個trunk
和branches
子目錄,它們存在的理由很快就會清晰起來。
最佳方案是建立你本身的分支,或者是版本庫的開發線。這容許你保存破壞了一半的工做而不打擾別人,儘管你仍能夠選擇性的同你的合做者分享信息,你將會看到這是怎樣工做的。
有兩個方法做拷貝,咱們首先介紹一個混亂的方法,只是讓概念更清楚,做爲開始,取出一個工程的根目錄,/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.
$ cd bigwc
$ svn copy trunk branches/my-calc-branch
$ svn status
A + 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
的歷史。
$ 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.
------------------------------------------------------------------------
$ 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在同一個項目的並行分支上工做:你在私有分支上,而Sally在主幹(trunk)或者叫作開發主線上。
因爲有衆多的人蔘與項目,大多數人擁有主幹拷貝是很正常的,任何人若是進行一個長週期的修改會使得主幹陷入混亂,因此一般的作法是創建一個私有分支,提交修改到本身的分支,直到這階段工做結束。
因此,好消息就是你和Sally不會互相打擾,壞消息是有時候分離會太遠。記住「閉門造車」策略的問題,當你完成你的分支後,可能由於太多衝突,已經沒法輕易合併你的分支和主幹的修改。
$ 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修改—修改從主幹「拷貝」到你的私有分支的工做拷貝,如今做爲一個本地修改,在這種狀況下,要靠你審查本地的修改來肯定它們工做正常。
可是當你審查過你的合併結果後,你能夠使用svn commit提交修改,在那一刻,修改已經合併到你的分支上了,在版本控制術語中,這種在分支之間拷貝修改的行爲叫作搬運修改。
當你提交你的修改時,肯定你的日誌信息中說明你是從某一版本搬運了修改,舉個例子:
$ svn commit -m "integer.c: ported r344 (spelling fixes) from trunk."
Sending integer.c
Transmitting file data .
Committed revision 360.
一個警告:爲何svn diff和svn merge在概念上是很接近,但語法上有許多不一樣,必定閱讀第9章來查看其細節或者使用svn help查看幫助。舉個例子,svn merge須要一個工做拷貝做爲目標,就是一個地方來施展目錄樹修改,若是一個目標都沒有指定,它會假定你要作如下某個普通的操做:
你但願合併目錄修改到工做拷貝的當前目錄。
你但願合併修改到你的當前工做目錄的相同文件名的文件。
若是你合併一個目錄而沒有指定特定的目標,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 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的不一樣版本的簡略寫法,最後一種語法表示工做拷貝是可選的,若是省略,默認是當前目錄。
理想狀況下,你的版本控制系統應該會阻止對一個分支作兩次改變操做,必須自動的記住那一個分支的修改已經接收了,而且能夠顯示出來,用來儘量幫助自動化的合併。
不幸的是,Subversion不是這樣一個系統,相似於CVS,Subversion並不記錄任何合併操做,當你提交本地修改,版本庫並不能判斷出你是經過svn merge仍是手工修改獲得這些文件。
由於合併只是致使本地修改,它不是一個高風險的操做,若是你在第一次操做錯誤,你能夠運行svn revert來再試一次。
有時候你的工做拷貝極可能已經改變了,合併會針對存在的那一個文件,這時運行svn revert不會恢復你在本地做的修改,兩部分的修改沒法識別出來。
在這個狀況下,人們很樂意可以在合併以前預測一下,一個簡單的方法是使用運行svn merge一樣的參數運行svn diff,另外一種方式是傳遞--dry-run
選項給merge命令:
$ svn merge --dry-run -r 343:344 http://svn.example.com/repos/calc/trunk
U integer.c
$ svn status
# nothing printed, working copy is still unchanged.
--dry-run
選項實際上並不修改本地拷貝,它只是顯示實際合併時的狀態信息,對於獲得「總體」的印象,這個命令頗有用,由於svn diff包括太多細節。
就像svn update命令,svn merge會把修改應用到工做拷貝,所以它也會形成衝突,由於svn merge形成的衝突有時候會有些不一樣,本小節會解釋這些區別。
$ 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
$
另外一個svn update和svn merge的小區別是衝突產生的文件的名字不一樣,在「解決衝突(合併別人的修改)」一節,咱們看到過更新產生的文件名字爲filename.mine
、filename.rOLDREV
和filename.rNEWREV
,當svn merge產生衝突時,它產生的三個文件分別爲 filename.working
、filename.left
和filename.right
。在這種狀況下,術語「left」和「right」表示了兩棵樹比較時的兩邊,在兩種狀況下,不一樣的名字會幫助你區分衝突是由於更新形成的仍是合併形成的。
當與Subversion開發者交談時你必定會聽到說起術語祖先,這個詞是用來描述兩個對象的關係:若是他們互相關聯,一個對象就是另外一個的祖先,或者相反。
D foo.c
A foo.c
大多數合併包括比較包括祖先關聯的兩條樹,所以svn merge這樣運做,然而,你也許會但願merge
命令可以比較兩個不相關的目錄樹,舉個例子,你有兩個目錄樹分別表明了賣主軟件項目的不一樣版本(見「賣主分支」一節),若是你使用svn merge進行比較,你會看到第一個目錄樹被刪除,而第二個樹添加上!
在這個狀況下,你只是但願svn merge可以作一個以路徑爲基礎的比較,忽略全部文件和目錄的關係,增長--ignore-ancestry
選項會致使命令象svn diff同樣。(相應的,--notice-ancestry
選項會使svn diff象merge
命令同樣行事。)
分支和svn merge有不少不一樣的用法,這個小節描述了最多見的用法。
爲了完成這個例子,咱們將時間往前推動,假定已通過了幾天,在主幹和你的分支上都有許多更改,假定你完成了分支上的工做,已經完成了特性或bug修正,你想合併全部分支的修改到主幹上,讓別人也能夠使用。
$ svn log --verbose --stop-on-copy /
http://svn.example.com/repos/calc/branches/my-calc-branch
…
------------------------------------------------------------------------
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)
$
$ 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.
繼續,你也許會想:好吧,這不是真的取消提交吧!是吧?版本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
不是新的,而是原先刪除的那一個的後代。
儘管咱們的例子告訴咱們如何找回文件,對於恢復刪除的目錄也是同樣的。
這個主幹被拷貝到「發佈」分支。 當小組認爲軟件已經作好發佈的準備(如,版本1.0)而後/trunk
會被拷貝到/branches/1.0
。
項目組繼續並行工做,一個小組開始對分支進行嚴酷的測試,同時另外一個小組在/trunk
繼續新的工做(如,準備2.0),若是一個bug在任何一個位置被發現,錯誤修正須要來回運送。然而這個過程有時候也會結束,例如分支已經爲發佈前的最終測試「停滯」了。
分支已經做了標籤而且發佈,當測試結束,/branches/1.0
做爲引用快照已經拷貝到/tags/1.0.0
,這個標籤被打包發佈給客戶。
分支屢次維護。當繼續在/trunk
上爲版本2.0工做,bug修正繼續從/trunk
運送到/branches/1.0
,若是積累了足夠的bug修正,管理部門決定發佈1.0.1版本:拷貝/branches/1.0
到/tags/1.0.1
,標籤被打包發佈。
整個過程隨着軟件的成熟不斷重複:當2.0完成,一個新的2.0分支被建立,測試、打標籤和最終發佈,通過許多年,版本庫結束了許多版本發佈,進入了「維護」模式,許多標籤表明了最終的發佈版本。
這種狀況最好經過有規律的將主幹合併到分支來避免,制定這樣一個政策:每週將上週的修改合併到分支,注意這樣作時須要當心,須要手工記錄合併的過程,以免重複的合併(在「手工追蹤合併」一節描述過),你須要當心的撰寫合併的日誌信息,精確的描述合併包括的範圍(在「合併一條分支到另外一支」一節中描述過),這看起來像是脅迫,但是其實是容易作到的。
在一些時候,你已經準備好了將「同步的」特性分支合併回到主幹,爲此,開始作一次將主幹最新修改和分支的最終合併,這樣之後,除了你的分支修改的部分,最新的分支和主幹將會絕對一致,因此在這個特別的例子裏,你會經過直接比較分支和主幹來進行合併:
$ 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用戶一般用以下的法則使用分支:
若是你的工做拷貝包含許多來自不一樣版本庫目錄跳轉的子樹,它會工做如常。當你更新時,你會獲得每個目錄適當的補丁,當你提交時,你的本地修改會一直做爲一個單獨的原子修改提交到版本庫。
注意,由於你的工做拷貝能夠在混合位置的狀況下工做正常,可是全部的位置必須在同一個版本庫,Subversion的版本庫不能互相通訊,這個特性還不在Subversion 1.0的計劃裏。[12]
由於svn switch是svn 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 章 配置服務器)這種方法一般是沒必要要的,若是一我的不當心提交了到標籤目錄一個修改,你能夠簡單的取消,畢竟這是版本控制啊。
/trunk
/branches
/tags
若是一個版本庫保存了多個項目,管理員會經過項目來佈局(見「選擇一種版本庫佈局」一節關於「項目根目錄」):
/paint/trunk
/paint/branches
/paint/tags
/calc/trunk
/calc/branches
/calc/tags
固然,你能夠自由的忽略這些一般的佈局方式,你能夠建立任意的變化,只要是對你和你的項目有益,記住不管你選擇什麼,這不會是一種永久的承諾,你能夠隨時從新組織你的版本庫。由於分支和標籤都是普通的目錄,svn move命令能夠任意的更名和移動它們,從一種佈局到另外一種大概只是一系列服務器端的移動,若是你不喜歡版本庫的組織方式,你能夠任意修改目錄結構。
記住,儘管移動目錄很是容易,你必須體諒你的用戶,你的修改會讓你的用戶感到迷惑,若是一個用戶的擁有一個版本庫目錄的工做拷貝,你的svn move命令也許會刪除最新的版本的這個路徑,當用戶運行svn update,會被告知這個工做拷貝引用的路徑已經再也不存在,用戶須要強制使用svn switch轉到新的位置。
$ svn delete http://svn.example.com/repos/calc/branches/my-calc-branch /
-m "Removing obsolete branch of calc project."
Committed revision 375.
你的分支已經消失了,固然不是真的消失了:這個目錄只是在HEAD
修訂版本里消失了,若是你使用svn checkout、svn 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.
$ 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.
[9] 在未來,Subversion項目將會計劃(或者發明)一種擴展補丁格式來描述目錄樹的結構和屬性變化。
[10] Subversion項目有計劃,無論用什麼方式,總有一天要實現svnadmin obliterate命令來進行永久刪除操做,而此時能夠看「svndumpfilter」一節。
[11] 由於CVS沒有版本樹,它會在每一個版本庫目錄建立一個古典
區域用來保存增量數據。
[12] 當你的服務器位置改變,而你不想放棄存在的本地拷貝,你能夠使用帶選項--relocate
的svn switch命令轉換URL,見第 9 章 Subversion徹底參考的svn switch查看更多信息和例子。
Subversion版本庫是保存任意數量項目版本化數據的中央倉庫,所以,版本庫成爲管理員關注的對象。版本庫的維護通常並不須要太多的關注,但爲了不一些潛在的問題和解決一些實際問題,理解怎樣適當的配置和維護仍是很是重要的。
在這一章裏,咱們將討論如何創建和配置一個Subversion版本庫,還會討論版本庫的維護,包括svnlook和svnadmin工具的使用(它們都包含在Subversion中)。咱們將說明一些常見的問題和錯誤,並提供一些安排版本庫數據的建議。
若是您只是以普通用戶的身份訪問版本庫對數據進行版本控制(就是說經過Subversion客戶端),您徹底能夠跳過本章。可是若是您已是或打算成爲Subversion版本庫的管理員,[13]您必定要關注一下本章的內容。
在Subversion1.1中,版本庫中存儲數據有兩種方式。一種是在Berkeley DB數據庫中存儲數據;另外一種是使用普通的文件,使用自定義格式。由於Subversion的開發者稱版本庫爲(版本化的)文件系統,他們接受了稱後一種存儲方式爲FSFS[14]的習慣,也就是說,使用本地操做系統文件系統來存儲數據的版本化文件的系統。
建 立一個版本庫時,管理員必須決定使用Berkeley DB仍是FSFS。它們各有優缺點,咱們將詳細描述。這兩個中並無一個是更正式的,訪問版本庫的程序與採用哪種實現方式無關。訪問程序並不知道版本庫 如何存儲數據,它們只是從版本庫的API讀取到修訂版本和事務樹。
表 5.1 「版本庫數據存儲對照表」從整體上比較了Berkeley DB和FSFS版本庫,下一部分將會詳細講述細節。
特性 | Berkeley DB | FSFS |
---|---|---|
對操做中斷的敏感 | 很敏感;系統崩潰或者權限問題會致使數據庫「塞住」,須要按期進行恢復。 | 不敏感。 |
可只讀加載 | 不能 | 能夠 |
存儲平臺無關 | 不能 | 能夠 |
可從網絡文件系統訪問 | 不能 | 能夠 |
版本庫大小 | 稍大 | 稍小 |
可擴展性:修訂版本樹的數量 | 數據庫,沒有限制 | 許多古老的本地文件系統在處理單一目錄包含上千個條目時出現問題。 |
可擴展性:文件較多的目錄 | 較慢 | 較快 |
速度:檢出最新的代碼 | 較快 | 較慢 |
速度: 大的提交 | 較慢,可是時間被分配在整個提交操做中 | 較快,可是最後較長的延時可能會致使客戶端操做超時 |
組訪問權處理 | 對於用戶的umask設置十分敏感,最好只由一個用戶訪問。 | 對umask設置不敏感 |
功能成熟時間 | 2001年開始使用 | 2004年開始使用 |
在Subversion的初始設計階段,開發者由於多種緣由而決定採用Berkeley DB,好比它的開源協議、事務支持、可靠性、性能、簡單的API、線程安全、支持遊標等。
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的httpd或svnserve(參見第 6 章 配置服務器)—而不是多用戶經過file:///
或svn+ssh://
URL的方式多用戶訪問。若是將Berkeley DB版本庫直接用做多用戶訪問,請先閱讀「支持多種版本庫訪問方法」一節。
建立一個 Subversion 版本庫出乎尋常的簡單。 Subversion 提供的svnadmin 工具,有一個執行這個功能的子命令。要創建一個新的版本庫,只須要運行:
$ svnadmin create /path/to/repos
在 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。svnadmin和svnlook都被認爲是服務器端工具—它們在版本庫所在的機器上使用,用來檢查或修改版本庫,不能經過網絡來執行任務。一個Subversion的新手一般會犯的錯誤,就是試圖將URL(甚至「本地」file:
路徑)傳給這兩個程序。
因此,當你運行svnadmin create命令後,就會在運行目錄建立一個嶄新的Subversion版本庫,讓咱們看一下在這個目錄建立中建立了什麼。
$ ls repos
conf/ dav/ db/ format hooks/ locks/ README.txt
除了README.txt
和format
文件,版本庫目錄就是一些子目錄了。就像Subversion其它部分的設計同樣,模塊化是一個很重要的原則,並且層次化的組織要比雜亂無章好。下面是對新的版本庫目錄中各個項目的簡要介紹:
一個存儲版本庫配置文件的目錄。
提供給Apache和mod_dav_svn的目錄,讓它們存儲本身的數據。
你全部的受版本控制數據的所在之處。這個目錄或者是個Berkeley DB環境(盡是數據表和其餘東西),或者是一個包含修訂版本文件的FSFS環境。
包含了用來表示版本庫佈局版本號的整數。
一個存儲鉤子腳本模版的目錄(還有鉤子腳本自己, 若是你安裝了的話)。
一個存儲Subversion版本庫鎖定數據的目錄,被用來追蹤對版本庫的訪問。
這個文件只是用來告訴它的閱讀者,他如今看的是 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
start-commit
pre-commit
Subversion的分發版本包括了一些訪問控制腳本(在Subversion源文件目錄樹的tools/hook-scripts
目錄),能夠用來被pre-commit調用來實現精密的寫訪問控制。另外一個選擇是使用Apache的httpd模塊mod_authz_svn,能夠對單個目錄進行讀寫訪問控制(見「每目錄訪問控制」一節)。在將來的Subversion版本中,咱們計劃直接在文件系統中實現訪問控制列表(ACLs)。
post-commit
它在事務完成後運行,建立一個新的修訂版本。大多數人用這個鉤子來發送關於提交的描述性電子郵件,或者做爲版本庫的備份。版本庫傳給程序兩個參數:到版本庫的路徑和被建立的新的修訂版本號。退出程序會被忽略。
Subversion分發版本中包括mailer.py和commit-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-style
或svn:mime-type
這類屬性。這看起來是個好主意,但它會引發問題。主要的問題是客戶並不知道由鉤子腳本進行的修改,同時沒有辦法通告客戶它的數據是過期的,這種矛盾會致使出人意料和不能預測的行爲。
做爲嘗試修改事務的替代,咱們經過檢查pre-commit
鉤子的事務,在不知足要求時拒絕提交。
Subversion 會試圖以當前訪問版本庫的用戶身份執行鉤子。一般,對版本庫的訪問老是經過Apache HTTP服務器和mod_dav_svn進行,所以,執行鉤子的用戶就是運行Apache的用戶。鉤子自己須要具備操做系統級的訪問許可,用戶能夠運行 它。另外,其它被鉤子直接或間接使用的文件或程序(包括Subversion版本庫自己)也要被同一個用戶訪問。換句話說,要注意潛在的訪問控制問題,它 可能會讓你的鉤子沒法按照你的目的順利執行。
Subversion提供了一些用來建立、查看、修改和修復版本庫的工具。讓咱們首先詳細瞭解一下每一個工具,而後,咱們再看一下僅在Berkeley DB後端分發版本中提供的版本數據庫工具。
svnlook是Subversion提供的用來查看版本庫中不一樣的修訂版本和事務。這個程序不會修改版本庫內容-這是個「只讀」的工具。svnlook一般用在版本庫鉤子程序中,用來記錄版本庫即將提交(用在pre-commit鉤子時)或者已經提交的(用在post-commit鉤子時)修改。版本庫管理員能夠將這個工具用於診斷。
$ 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 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.
$ svnlook tree /path/to/repos --show-ids
/ <0.0.1>
A/ <2.0.1>
B/ <4.0.1>
lambda <5.0.1>
E/ <6.0.1>
alpha <7.0.1>
beta <8.0.1>
F/ <9.0.1>
mu <3.0.1>
C/ <a.0.1>
D/ <b.0.1>
gamma <c.0.1>
G/ <d.0.1>
pi <e.0.1>
rho <f.0.1>
tau <g.0.1>
H/ <h.0.1>
chi <i.0.1>
omega <k.0.1>
psi <j.0.1>
iota <1.0.1>
若是你看過樹中目錄和文件的佈局,你能夠使用svnlook cat,svnlook propget, 和svnlook proplist命令來查看這些目錄和文件的細節。
svnlook還能夠作不少別的查詢,顯示咱們先前提到的信息的一些子集,報告指定的修訂版本或事務中哪些路徑曾經被修改過,顯示對文件和目錄作過的文本和屬性的修改,等等。下面是svnlook命令能接受的子命令的介紹,以及這些子命令的輸出:
svnadmin程序是版本庫管理員最好的朋友。除了提供建立Subversion版本庫的功能,這個程序使你能夠維護這些版本庫。svnadmin的語法跟 svnlook相似:
$ svnadmin help
general usage: svnadmin SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]
Type "svnadmin help <subcommand>" for help on a specific subcommand.
Available subcommands:
create
deltify
dump
help (?, h)
…
咱們已經提過svnadmin的create
子命令(參照「版本庫的建立和配置」一節)。本章中咱們會詳細講解大多數其餘的命令。如今,咱們來簡單的看一下每一個可用的子命令提供了什麼功能。
create
建立一個新的Subversion版本庫。
deltify
在指定的修訂版本範圍內,對其中修改過的路徑作增量化操做。若是沒有指定修訂版本,這條命令會修改HEAD
修訂版本。
dump
導出版本庫修訂必定版本範圍內的內容,使用可移植轉儲格式。
hotcopy
對版本庫作熱拷貝,用這個方法你能任什麼時候候安全的備份版本庫而無需考慮是否正在使用。
list-dblogs
(Berkeley DB版本庫專有)列出Berkeley DB中與版本庫有關的日誌文件清單。這個清單包括全部的日誌文件—仍然被版本庫使用的和再也不使用的。
list-unused-dblogs
(Berkeley DB版本庫專有)列出Berkeley DB版本庫有關的不在使用日誌文件路徑清單。你能安全的從版本庫中刪除那些日誌文件,也能夠將它們存檔以用來在災難事件後版本庫的恢復。
load
導入由dump
子命令導出的可移植轉儲格式的一組修訂版本。
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
將指定路徑的數據添加到轉儲數據流中。
如今我來演示如何使用這個命令。咱們會在其它章節(參見 「選擇一種版本庫佈局」一節) 討論關於如何選擇設定版本庫佈局的問題,好比應該使用一個版本庫管理多個項目仍是使用一個版本庫管理一個項目,或者如何在版本庫中安排數據等等。不過,有 些時候,即便在項目已經展開之後,你仍是但願對版本庫的佈局作一些調整。最多見的狀況是,把原來存放在同一個版本庫中的幾個項目分開,各自成家。
假設有一個包含三個項目的版本庫: calc
,calendar
,和 spreadsheet
。它們在版本庫中的佈局以下:
/
calc/
trunk/
branches/
tags/
calendar/
trunk/
branches/
tags/
spreadsheet/
trunk/
branches/
tags/
如今要把這三個項目轉移到三個獨立的版本庫中。首先,轉儲整個版本庫:
$ svnadmin dump /path/to/repos > repos-dumpfile
* Dumped revision 0.
* Dumped revision 1.
* Dumped revision 2.
* Dumped revision 3.
…
$
而後,將轉儲文件三次送入過濾器,每次僅保留一個頂級目錄,就能夠獲得三個轉儲文件:
$ cat repos-dumpfile | svndumpfilter include calc > calc-dumpfile
…
$ cat repos-dumpfile | svndumpfilter include calendar > cal-dumpfile
…
$ cat repos-dumpfile | svndumpfilter include spreadsheet > ss-dumpfile
…
$
如今你必需要做出一個決定了。這三個轉儲文件中,每一個均可以用來建立一個可用的版本庫,不過它們保留了原版本庫的精確路徑結構。也就是說,雖然項目calc
如今獨佔了一個版本庫,但版本庫中還保留着名爲calc
的頂級目錄。若是但願trunk
、tags
和branches
這三個目錄直接位於版本庫的根路徑下,你可能須要編輯轉儲文件,調整Node-path
和Copyfrom-path
頭參數,將路徑calc/
刪除。同時,你還要刪除轉儲數據中建立calc
目錄的部分。通常來講,就是以下的一些內容:
Node-path: calc
Node-action: add
Node-kind: dir
Content-length: 0
若是你打算經過手工編輯轉儲文件來移除一個頂級目錄,注意不要讓你的編輯器將換行符轉換爲本地格式(好比將/r/n轉換爲/n)。不然文件的內容就與所需的格式不相符,這個轉儲文件也就失效了。
剩下的工做就是建立三個新的版本庫,而後將三個轉儲文件分別導入:
$ svnadmin create calc; svnadmin load calc < calc-dumpfile
<<< Started new transaction, based on original revision 1
* adding path : Makefile ... done.
* adding path : button.c ... done.
…
$ svnadmin create calendar; svnadmin load calendar < cal-dumpfile
<<< Started new transaction, based on original revision 1
* adding path : Makefile ... done.
* adding path : cal.c ... done.
…
$ svnadmin create spreadsheet; svnadmin load spreadsheet < ss-dumpfile
<<< Started new transaction, based on original revision 1
* adding path : Makefile ... done.
* adding path : ss.c ... done.
…
$
svndumpfilter的兩個子命令均可以經過選項設定如何處理「空」修訂版本。若是某個指定的修訂版本僅包含路徑的更改,過濾器就會將它刪除,由於當前爲空的修訂版本一般是無用的甚至是讓人討厭的。爲了讓用戶有選擇的處理這些修訂版本,svndumpfilter提供瞭如下命令行選項:
--drop-empty-revs
不生成任何空修訂版本,忽略它們。
--renumber-revs
若是空修訂版本被剔除(經過使用--drop-empty-revs
選項),依次修改其它修訂版本的編號,確保編號序列是連續的。
--preserve-revprops
若是空修訂版本被保留,保持這些空修訂版本的屬性(日誌信息,做者,日期,自定義屬性,等等)。若是不設定這個選項,空修訂版本將僅保留初始時間戳,以及一個自動生成的日誌信息,代表此修訂版本由svndumpfilter處理過。
儘管svndumpfilter十分有用,能節省大量的時間,但它倒是把徹徹底底的雙刃劍。首先,這個工具對路徑語義極爲敏感。仔細檢查轉儲文件中的路徑是否是以斜線開頭。也許Node-path
和Copyfrom-path
這兩個頭參數對你有些幫助。
…
Node-path: spreadsheet/Makefile
…
若是這些路徑以斜線開頭,那麼你傳遞給svndumpfilter include和svndumpfilter exclude的路徑也必須以斜線開頭(反之亦然)。若是由於某些緣由轉儲文件中的路徑沒有統一使用或不使用斜線開頭,[16]也許須要修正這些路徑,統一使用斜線開頭或不使用斜線開頭。
此外,複製操做生成的路徑也會帶來麻煩。Subversion支持在版本庫中進行復制操做,也就是複製一個存在的路徑,生成一個新的路徑。問題是,svndumpfilter保留的某個文件或目錄多是由某個svndumpfilter排除的文件或目錄複製而來的。也就是說,爲了確保轉儲數據的完整性,svndumpfilter需 要切斷這些複製自被排除路徑的文件與源文件的關係,還要將這些文件的內容以新建的方式添加到轉儲數據中。可是因爲Subversion版本庫轉儲文件格式 中僅包含了修訂版本的更改信息,所以源文件的內容基本上沒法得到。若是你不能肯定版本庫中是否存在相似的狀況,最好從新考慮一下到底保留/排除哪些路徑。
若是你使用Berkeley DB版本庫,那麼全部歸入版本控制的文件系統結構和數據都儲存在一系列數據庫的表中,而這個位於版本庫的db
子目錄下。這個子目錄是一個標準的Berkeley DB環境目錄,能夠應用任何Berkeley數據庫工具進行操做(參考SleepyCat網站http://www.sleepycat.com/上關於這些工具的介紹)。
對於Subversion的平常使用來講,這些工具並無什麼用處。大多數Subversion版本庫必須的數據庫操做都集成到svnadmin工具中。好比,svnadmin list-unused-dblogs和svnadmin list-dblogs實現了Berkeley db_archive命令功能的一個子集,而svnadmin recover則起到了 db_recover工具的做用。
固然,還有一些Berkeley DB工具備時是有用的。db_dump將Berkeley DB數據庫中的鍵值對以特定的格式寫入文件中,而db_load則能夠將這些鍵值對注入到數據庫中。Berkeley數據庫自己不支持跨平臺轉移,這兩個工具在這樣的狀況下就能夠實如今平臺間轉移數據庫的功能,而無需關心操做系統或機器架構。此外,db_stat工具可以提供關於Berkeley DB環境的許多有用信息,包括詳細的鎖定和存儲子系統的統計信息。
Subversion版本庫一旦按照須要配置完成,通常狀況下不須要特別的關照。不過有些時候仍是須要管理員手工干預一下。svnadmin工具就可以幫你完成如下這類工做:
svnadmin的子命令中最常常用到的恐怕就是setlog
。用戶在提交時輸入的日誌信息隨着相關事務提交到版本庫並升級成爲修訂版本後,便做爲新修訂版本的非版本化(即沒有進行版本管理)屬性保存下來。換句話說,版本庫只記得最新的屬性值,而忽略之前的。
有時用戶輸入的日誌信息有錯誤(好比拼寫錯誤或者內容錯誤)。若是配置版本庫時設置了(使用pre-revprop-change
和 post-revprop-change
鉤子;參見「鉤子腳本」一節)容許用戶在提交後修改日誌信息的選項,那麼用戶能夠使用svn程序的propset
命令(參見第 9 章 Subversion徹底參考)「修正」日誌信息中的錯誤。不過爲了不永遠丟失信息,Subversion版本庫一般設置爲僅能由管理員修改非版本化屬性(這也是默認的選項)。
若是管理員想要修改日誌信息,那麼能夠使用svnadmin setlog命令。這個命令從指定的文件中讀取信息,取代版本庫中某個修訂版本的日誌信息(svn:log
屬性)。
$ echo "Here is the new, correct log message" > newlog.txt
$ svnadmin setlog myrepos newlog.txt -r 388
即便是svnadmin setlog命令也受到限制。pre-
和 post-revprop-change
鉤子一樣會被觸發,所以必須進行相應的設置才能容許修改非版本化屬性。不過管理員能夠使用svnadmin setlog命令的--bypass-hooks
選項跳過鉤子。
不過須要注意的是,一旦跳過鉤子也就跳過了鉤子所提供的全部功能,好比郵件通知(通知屬性有改動)、系統備份(能夠用來跟蹤非版本化的屬性變動)等等。換句話說,要留心你所做出的修改,以及你做出修改的方式。
svnadmin的 另外一個常見用途是查詢異常的—多是已經死亡的—Subversion事務。一般提交操做失敗時,與之相關的事務就會被清除。也就是說,事務自己及全部與 該事務相關(且僅與該事務相關)的數據會從版本庫中刪除。不過偶爾也會出現操做失敗而事務沒有被清除的狀況。出現這種狀況可能有如下緣由:客戶端的用戶粗 暴的結束了操做,操做過程當中出現網絡故障,等等。無論是什麼緣由,死亡的事務老是有可能會出現。這類事務不會產生什麼負面影響,僅僅是消耗了一點點磁盤空 間。不過,嚴厲的管理員老是但願可以將它們清除出去。
能夠使用svnadmin的lstxns
命令列出當前的異常事務名。
$ svnadmin lstxns myrepos
19
3a1
a45
$
將輸出的結果條目做爲svnlook(設置--transaction
選項)的參數,就能夠得到事務的詳細信息,如事務的建立者、建立時間,事務已做出的更改類型,由這些信息能夠判斷出是否能夠將這個事務安全的刪除。若是能夠安全刪除,那麼只需將事務名做爲參數輸入到svnadmin rmtxns,就能夠將事務清除掉了。其實rmtxns
子命令能夠直接以lstxns
的輸出做爲輸入進行清理。
$ svnadmin rmtxns myrepos `svnadmin lstxns myrepos`
$
在按照上面例子中的方法清理版本庫以前,你或許應該暫時關閉版本庫和客戶端的鏈接。這樣在你開始清理以前,不會有正常的事務進入版本庫。下面例子中的shell腳本能夠用來迅速得到版本庫中異常事務的信息:
能夠用下面的命令使用上例中腳本: /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提交日誌等等—均可以做 爲決策的參考。管理員還能夠直接和那些彷佛已經死亡事務的提交者直接交流(好比經過郵件),來確認該事務確實已經死亡了。
幸運的是,從版本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]才能永久性的破壞數據庫環境。因此雖然一個過於穩重的版本庫管理員一般都會按照某種方案進行大量的版本庫離線備份,不過不要急着通知你的管理員進行恢復。
而後,使用下面的方法試着「恢復」你的版本庫:
確保沒有其它進程訪問(或者試圖訪問)版本庫。對於網絡版本庫,關閉Apache HTTP服務器是個好辦法。
成爲版本庫的擁有者和管理員。這一點很重要,若是以其它用戶的身份恢復版本庫,可能會改變版本庫文件的訪問權限,致使在版本庫「恢復」後依舊沒法訪問。
運行命令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.
此命令可能須要數分鐘才能完成。
從新啓動Subversion服務器。
這個方法能修復幾乎全部版本庫鎖住的問題。記住,要以數據庫的擁有者和管理員的身份運行這個命令,而不必定是root
用戶。恢復過程當中可能會使用其它數據存儲區(例如共享內存區)重建一些數據庫文件。若是以root
用戶身份恢復版本庫,這些重建的文件擁有者將變成root
用戶,也就是說,即便恢復了到版本庫的鏈接,通常的用戶也無權訪問這些文件。
若是由於某些緣由,上面的方法沒能成功的恢復版本庫,那麼你能夠作兩件事。首先,將破損的版本庫保存到其它地方,而後從最新的備份中恢復版本庫。而後,發送一封郵件到Subversion用戶列表(地址是:<users@subversion.tigris.org>
),寫清你所遇到的問題。對於Subversion的開發者來講,數據安全是最重要的問題。
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
鉤子在每次新的修訂版本提交後將其轉儲到文件中。或者,能夠編寫一個腳本,在天天夜裏將全部新增的修訂版本轉儲到文件中。這樣,svnadmin的dump
和load
命令就變成了很好的版本庫備份工具,萬一出現系統崩潰或其它災難性事件,它的價值就體現出來了。
轉儲還能夠用來將幾個獨立的版本庫合併爲一個版本庫。使用svnadmin load的--parent-dir
選項,能夠在裝載的時候指定根目錄。也就是說,若是有三個不一樣版本庫的轉儲文件,好比calc-dumpfile
,cal-dumpfile
,和ss-dumpfile
,能夠在一個新的版本庫中保存全部三個轉儲文件中的數據:
$ svnadmin create /path/to/projects
$
而後在版本庫中建立三個目錄分別保存來自三個不一樣版本庫的數據:
$ svn mkdir -m "Initial project roots" /
file:///path/to/projects/calc /
file:///path/to/projects/calendar /
file:///path/to/projects/spreadsheet
Committed revision 1.
$
最後,將轉儲文件分別裝載到各自的目錄中:
$ svnadmin load /path/to/projects --parent-dir calc < calc-dumpfile
…
$ svnadmin load /path/to/projects --parent-dir calendar < cal-dumpfile
…
$ svnadmin load /path/to/projects --parent-dir spreadsheet < ss-dumpfile
…
$
咱們再介紹一下Subversion版本庫轉儲數據的最後一種用途——在不一樣的存儲機制或版本控制系統之間轉換。由於轉儲數據的格式的大部分是能夠閱讀的,[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] 這必定會幫助你減小嚐試的時間。
在決定了如何用版本庫組織項目之後,就該決定如何設置版本庫的目錄層次了。因爲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 add和svn commit命令提交修改。不過這些工做就不屬於版本庫管理方面的內容了。若是對svn 客戶端程序還不熟悉,請閱讀第 3 章 指導教程。
[14] 讀做「fuzz-fuzz」, 若是Jack Repenning提及這個問題。
[15] 順便說一句,這是Subversion的特性,而不是bug。
[16] 儘管svnadmin dump對是否以斜線做爲路徑的開頭有統一的規定——這個規定就是不以斜線做爲路徑的開頭——其它生成轉儲文件的程序不必定會遵照這個規定。
[17] 好比:硬盤 + 大號電磁鐵 = 毀滅。
[18] Subversion版本庫的轉儲文件格式相似於RFC-822格式,後者普遍的應用於電子郵件系統中。
[19] svnadmin setlog能夠被繞過鉤子程序被調用。
[20] 你知道的—只是對各類變化莫測的問題的統稱。
[21] trunk
、tags
和branches
能夠使用「TTB目錄」來表示。
一個Subversion的版本庫能夠和客戶端同時運行在同一個機器上,使用file:///
訪問,可是一個典型的Subversion設置應該包括一個單獨的服務器,能夠被辦公室的全部客戶端訪問—或者有多是整個世界。
本小節描述了怎樣將一個Subversion的版本庫暴露給遠程客戶端,咱們會覆蓋Subversion已存在的服務器機制,討論各類方式的配置和使用。通過閱讀本小節,你能夠決定你須要哪一種網絡設置,而且明白怎樣在你的主機上進行配置。
須要注意到Subversion做爲一個開源的項目,並無官方的指定何種服務器是「主要的」或者是「官方的」,並無那種網絡實現被視做二等公民,每種服務器都有本身的優勢和缺點,事實上,不一樣的服務器能夠並行工做,分別經過本身的方式訪問版本庫,它們之間不會互相阻礙(見「支持多種版本庫訪問方法」一節)。表 6.1 「網絡服務器比較」是對兩種存在的Subversion服務器的比較—做爲一個管理員,你更加勝任給你和你的用戶挑選服務器的任務。
特性 | 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客戶端在收到認證請求的時候的行爲方式:
檢查用戶是否經過--username
和/或--password
命令選項指定了任何憑證信息,若是沒有,或者這些選項沒有認證成功,而後
查找運行中的auth/
區域保存的服務器域信息,來肯定用戶是否已經有了恰當的認證緩存,若是沒有,或者緩存憑證認證失敗,而後
提示用戶輸入。
若是客戶端經過以上的任何一種方式成功認證,它會嘗試在磁盤緩存憑證(除非用戶已經關閉了這種行爲方式,在前面提到過。)
svnserve是一個輕型的服務器,能夠同客戶端經過在TCP/IP基礎上的自定義有狀態協議通信,客戶端經過使用開頭爲svn://
或者svn+ssh://
svnserve的URL來訪問一個svnserve服務器。這一小節將會解釋運行svnserve的不一樣方式,客戶端怎樣實現服務器的認證,怎樣配置版本庫恰當的訪問控制。
有許多調用svnserve的方式,若是調用時沒有參數,你只會看到一些幫助信息,然而,若是你計劃使用inetd啓動進程,你能夠傳遞-i
(--inetd
)選項:
$ svnserve -i
( success ( 1 2 ( ANONYMOUS ) ( edit-pipeline ) ) )
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
選項的「管道模式」,這個模式假定一個分佈式服務程序如RSH或SSH已經驗證了一個用戶,而且以這個用戶調用了一個私有svnserve進程,svnserve運做如常(經過stdin和stdout通信),而且能夠設想通信是自動轉向到一種通道傳遞迴客戶端,當svnserve被這樣的通道代理調用,肯定認證用戶對版本數據庫有徹底的讀寫權限,(見服務器和訪問許可:一個警告。)這與本地用戶經過file:///
URl訪問版本庫一樣重要。
$ 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-access
和auth-access
能夠設置爲none
、read
或者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_keys
的command
值,舉個例子,用戶仍然能夠經過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生成的訪問和錯誤日誌(CustomLog
和ErrorLog
指示)的磁盤位置。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/repos1
,http://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.conf
的User
和Group
指示來指定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.conf
的Location
區裏添加一些指示來告訴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-ca
爲true
。
當 與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-file
和 ssl-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的源代碼容許全世界的人執行版本庫的只讀操做(例如檢出咱們的工做拷貝和使用瀏覽器瀏覽版本庫),可是限定只有認證用戶能夠執行寫操做。爲了執行特定的限制,你能夠使用Limit
和LimitExcept
配置指示,就像Location
指示,這個區塊有開始和結束標籤,你須要在<Location>
中添加這個指示。
在Limit
和LimitExcept
中使用的參數是能夠被這個區塊影響的HTTP請求類型,舉個例子,若是你但願禁止全部的版本庫訪問,只是保留當前支持的只讀操做,你能夠使用LimitExcept
指示,而且使用GET
,PROPFIND
,OPTIONS
和REPORT
請求類型參數,而後前面提到過的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.conf
的LoadModule
指示中的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 Any
和Require 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
在第一個例子裏,用戶harry
對calc
版本庫中/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壓縮(是gzip和PKZIP程序用來「壓縮」文件爲數據塊的同樣的算法)之類的機制。你只須要編譯你但願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版本庫維護多個服務器進程,版本庫會變得超出須要的頭痛,咱們建議你選擇最符合你的須要的版本庫,而且堅持使用!