有時候建立一個由多個不同檢出得到的工作副本是非常有用的,舉個例子,你或許希望不同的子目錄來自不同的版本庫位置,或者是不同的版本庫。你可以手工設定這樣一個工作副本—使用svn checkout來建立這種你需要的嵌套的工作副本結構。但是如果這個結構對所有的用戶是很重要的,每個用戶需要執行同樣的檢出操作。
很幸運,Subversion提供了外部定義的支持,一個外部定義是一個本地路經到URL的影射—也有可能一個特定的修訂版本—一些版本化的資源。在Subversion你可以使用svn:externals屬性來定義外部定義,你可以用svn propset或svn propedit(見「操作屬性」一節)建立和修改這個屬性。它可以設定到任何版本化的路經,它的值是一個多行的子目錄,可選的修訂版本標記和完全有效的Subversion版本庫URL的列表(相對於設定屬性的版本化目錄)。
$ svn propget svn:externals calc third-party/sounds http://sounds.red-bean.com/repos third-party/skins http://skins.red-bean.com/repositories/skinproj third-party/skins/toolkit -r21 http://svn.red-bean.com/repos/skin-maker
svn:externals的方便之處是這個屬性設定到版本化的路徑後,任何人可以從那個目錄取出一個工作副本,同樣得到外部定義的好處。換句話說,一旦一個人努力來定義這些嵌套的工作副本檢出,其他任何人不需要再麻煩了—Subversion會在原先的工作副本檢出之後,也會檢出外部工作副本。
外部定義的相對目標子目錄不需要存在於你的或其它用戶的系統中—Subversion會在檢出工作副本時建立這些文件。實際上,你一定不要使用外部定義來產生已經在版本控制的路徑。
注意前一個外部定義實例,當有人取出了一個calc目錄的工作副本,Subversion會繼續來取出外部定義的項目。
$ svn checkout http://svn.example.com/repos/calc A calc A calc/Makefile A calc/integer.c A calc/button.c Checked out revision 148. Fetching external item into calc/third-party/sounds A calc/third-party/sounds/ding.ogg A calc/third-party/sounds/dong.ogg A calc/third-party/sounds/clang.ogg … A calc/third-party/sounds/bang.ogg A calc/third-party/sounds/twang.ogg Checked out revision 14. Fetching external item into calc/third-party/skins …
如果你希望修改外部定義,你可以使用普通的屬性修改子命令,當你提交一個svn:externals屬性修改後,當你運行svn update時,Subversion會根據修改的外部定義同步檢出的項目,同樣的事情也會發生在別人更新他們的工作副本接受你的外部定義修改時。
因為svn:externals的值是多行的,所以我們強烈建議使用svn propedit,而不是使用svn propset。
你一定要要考慮在所有的外部定義中使用明確的修訂版本,這樣做意味著你已經決定了何時拖出外部訊息不同的快照,和精確的拖出哪個快照。除了不會受到第三方版本庫的意外修改的影響以外,當你的工作副本回溯到以前的版本庫時,使用明確的修訂版本號會讓外部定義回到以前的那個修訂版本,也意味著外部定義的工作副本更新會匹配以前修訂版本的樣子。對於軟體項目,這可能是編譯複雜代碼基的老快照成功和失敗的區別。
svn status命令也認識外部定義,會為外部定義的子目錄顯示X狀態碼,然後迭代這些子目錄來顯示外部項目的子目錄狀態訊息。
Subversion目前對外部定義的支持可能會引起誤導,首先,一個外部定義只可以指向目錄,而不是文件。第二,外部定義不可以指向相對路徑(如../../skins/myskin)。第三,通過外部定義建立的工作副本與主工作副本沒有連接起來(與設定svn:externals屬性的工作副本的版本庫),所以Subversion會以不關聯的工作副本操作。所以舉個例子,如果你希望提交一個或多個外部定義的拷貝,你必須在這些工作副本顯示的運行svn commit—對主工作副本的提交不會迭代到外部定義的部分。
另外,因為定義本身使用絕對路徑,移動和拷貝路徑他們附著的路徑不會影響他們作為外部的檢出(儘管相對的本地目標子目錄會這樣,當然,根據重命名的目錄改變)。在特定情形下這看起來有些迷惑—甚至讓人沮喪。舉個例子,你的最上層目錄叫作my-project,你在它的子目錄(my-project/some-dir)建立了一個外部定義,而這個外部定義指向的是另一個子目錄(my-project/external-dir)的最新版本。
$ svn checkout http://svn.example.com/projects . A my-project A my-project/some-dir A my-project/external-dir … Fetching external item into 'my-project/some-dir/subdir' Checked out external at revision 11. Checked out revision 11. $ svn propget svn:externals my-project/some-dir subdir http://svn.example.com/projects/my-project/external-dir $
現在你使用svn move將目錄my-project改名,此刻,你的外部定義還是指向my-project目錄,即使這個目錄已經不存在了。
$ svn move -q my-project renamed-project $ svn commit -m "Rename my-project to renamed-project." Deleting my-project Adding my-renamed-project Committed revision 12. $ svn update Fetching external item into 'renamed-project/some-dir/subdir' svn: Target path does not exist $
當然,如果版本庫存在多種URL模式時,使用絕對URL來引用外部定義會導致問題。例如,如果你的Subversion伺服器已經設定為任何用戶可以使用http://或https://檢出,但是只能通過https://提交,你現在有了一個很有趣的問題。如果你的外部定義使用http://形式,則你不能從這個工作副本提交任何內容。另一方面,如果他們使用https://方式的URL,任何因為不支持https://的客戶使用http://檢出的工作副本不能得到外部項目。也需要意識到,如果你需要重定位你的工作副本(使用svn switch --relocate),外部定義不會重新定位。
最後,你或許經常希望svn子命令不會識別或其它作為外部定義處理的結果的外部工作副本上的操作,在這種情況下,你可以對子命令使用--ignore-externals選項。