NO IMAGE
推薦的版本庫佈局
 
儘管Subversion的靈活性允許你自由佈局版本庫,但我們有一套推薦的方式,建立一個trunk目錄來儲存開發的“主線”,一個branches目錄存放分支拷貝,tags目錄儲存標籤拷貝,例如:
$ svn list file:///usr/local/svn/repos
/trunk
/branches
/tags
 
 
 
因為你的工作拷貝“同你係統上的檔案和目錄沒有任何區別”,你可以隨意修改檔案,但是你必須告訴Subversion你做的其他任何事。例如,你希望拷貝或移動工作拷貝的一個檔案,你應該使用svn copy或者 svn move而不要使用作業系統的拷貝移動命令
 
 
.svn目錄包含什麼?
工作拷貝中的任何一個目錄包括一個名為.svn管理區域,通常列表操作不顯示這個目錄,但它仍然是一個非常重要的目錄,無論你做什麼?不要刪除或是更改這個管理區域的任何東西,Subversion使用它來管理工作拷貝。如果你不小心刪除了子目錄.svn,最簡單的解決辦法是刪除包含的目錄(普通的檔案系統刪除,而不是svn delete),然後在父目錄執行svn update,Subversion客戶端會重新下載你刪除的目錄,幷包含新的.svn。
 
 
禁用密碼快取
當你執行的Subversion命令需要認證時,預設情況下Subversion會在磁碟快取認證資訊,這樣做出於便利,在接下來的操作中你就可以不必輸入密碼,但如果你很在乎密碼快取,[3]你可以永久關閉快取或每次執行命令時說明。在某次命令關閉密碼快取可以在命令中使用–no-auth-cache選項,如果希望永久關閉快取,可以在本機的Subversion配置檔案中新增store-passwords = no這一行。
 
 
用其它身份認證
因為Subversion認證快取是預設設定(包含使用者名稱和密碼),用來記住上一次修改工作拷貝的人非常方便。但是有時候會不好用—特別是如果你使用的是共享工作拷貝,在這種情況下,你只需要為命令列傳遞–username選項,Subversion就會嘗試使用該使用者認證,如果需要也提示你輸入密碼。
 
 
典型的工作週期是這樣的:
更新你的工作拷貝
svn update
做出修改
svn add
svn delete
svn copy
svn move
檢驗修改
svn status
svn diff
可能會取消一些修改
svn revert
解決衝突(合併別人的修改)
svn update
svn resolved
提交你的修改
svn commit
 
 
 
修改你的工作拷貝
svn add foo
預定將檔案、目錄或者符號鏈foo新增到版本庫,當你下次提交後,foo會成為其父目錄的一個子物件。注意,如果foo是目錄,所有foo中的內容也會預定新增進去,如果你只想新增foo本身,請使用–non-recursive (-N)引數。
 
svn delete foo
預定將檔案、目錄或者符號鏈foo從版本庫中刪除,如果foo是檔案,它馬上從工作拷貝中刪除,如果是目錄,不會被刪除,但是Subversion準備好刪除了,當你提交你的修改,foo就會在你的工作拷貝和版本庫中被刪除。
 
svn copy foo bar
建立一個新的專案bar作為foo的複製品,會自動預定將bar新增,當在下次提交時會將bar新增到版本庫,這種拷貝歷史會記錄下來(按照來自foo的方式記錄),svn copy並不建立中介目錄。
 
svn move foo bar
這個命令與與執行svn copy foo bar;svn delete foo完全相同,bar作為foo的拷貝準備新增,foo已經預定被刪除,svn move不建立中介的目錄。
 
svn mkdir blort
這個命令同執行 mkdir blort; svn add blort相同,也就是建立一個叫做blort的檔案,並且預定新增到版本庫。
 
 
 
 
檢視你的修改概況
svn status列印6列字元,緊跟一些空格,接著是檔案或者目錄名。第一列告訴一個檔案或目錄的狀態或它的內容,返回程式碼如下:
A item
預定加入到版本庫的檔案、目錄或符號鏈的item。
C item
檔案item發生衝突,在從伺服器更新時與本地版本發生交迭,在你提交到版本庫前,必須手工的解決衝突。
D item
檔案、目錄或是符號鏈item預定從版本庫中刪除。
M item
檔案item的內容被修改了。
svn status也有一個–verbose (-v)選項,它可以顯示工作拷貝中的所有專案,即使沒有改變過的
上面所有的svn status呼叫並沒有聯絡版本庫,只是與.svn中的原始資料進行比較的結果,最後,是–show-updates (-u)選項,它將會聯絡版本庫為已經過時的資料新增新資訊
$ svn status -u -v
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精確的找出你所做的修改
svn diff命令通過比較你的檔案和.svn的“原始”檔案來輸出資訊,預定要增加的檔案會顯示所有增加的文字,要刪除的檔案會顯示所有要刪除的文字。輸出的格式為統一區別格式(unified diff format),刪除的行前面加一個-,新增的行前面有一個 ,svn diff命令也列印檔名和打補丁需要的資訊,所以你可以通過重定向一個區別檔案來生成“補丁”:
$ svn diff > patchfile
舉個例子,你可以把補丁檔案傳送郵件到其他開發者,在提交之前稽核和測試。
Subversion使用內建區別引擎,預設情況下輸出為統一區別格式。如果你期望不同的輸出格式,你可以使用–diff-cmd指定外接的區別程式,並且通過–extensions傳遞其他引數,舉個例子,察看本地檔案foo.c的區別,同時忽略大小寫差異,你可以執行svn diff –diff-cmd /usr/bin/diff –extensions ‘-bc’ foo.c。
 
 
 
 
取消本地修改
假定我們在看svn diff的輸出,你發現對某個檔案的所有修改都是錯誤的,或許你根本不應該修改這個檔案,或者是從開頭重新修改會更加容易。
這是使用svn revert的好機會:
$ svn revert README
Reverted ‘README’
Subversion把檔案恢復到未修改的狀態,叫做.svn目錄的“原始”拷貝,應該知道svn revert可以恢復任何預定要做的操作
svn revert ITEM的效果與刪除ITEM然後執行svn update -r BASEITEM完全一樣,但是,如果你使用svn revert它不必通知版本庫就可以恢復檔案。
 
 
 
 
 
解決衝突(合併別人的修改)
我們可以使用svn status -u來預測衝突,當你執行svn update一些有趣的事情發生了:
$ svn update
U  INSTALL
G  README
C  bar.c
Updated to revision 46.
U和G沒必要關心,檔案乾淨的接受了版本庫的變化,檔案標示為U表明本地沒有修改,檔案已經根據版本庫更新。G標示合併,標示本地已經修改過,與版本庫沒有重迭的地方,已經合併。
但是C表示衝突,說明伺服器上的改動同你的改動衝突了,你需要自己手工去解決。
 
當衝突發生了,有三件事可以幫助你注意到這種情況和解決問題:
Subversion在更新時列印C標記,並且標記這個檔案已衝突。
如果Subversion認為這個檔案是可合併的,它會置入衝突標記—特殊的橫線分開衝突的“兩面”—在檔案裡視覺化的描述重疊的部分(Subversion使用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 -m “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就不會認為這個檔案是在衝突狀態了。
$ svn resolved sandwich.txt
Resolved conflicted state of ‘sandwich.txt’
 
手工合併衝突
第一次嘗試解決衝突讓人感覺很害怕,但經過一點訓練,它簡單的像是騎著車子下坡。
這裡一個簡單的例子,由於不良的交流,你和同事Sally,同時編輯了sandwich.txt。Sally提交了修改,當你準備更新你的工作拷貝,衝突發生了,我們不得不去修改sandwich.txt來解決這個問題。首先,看一下這個檔案:
$ cat sandwich.txt
Top piece of bread
Mayonnaise
Lettuce
Tomato
Provolone
<<<<<<< .mine
Salami
Mortadella
Prosciutto
=======
Sauerkraut
Grilled Chicken
>>>>>>> .r2
Creole Mustard
Bottom piece of bread
小於號、等於號和大於號串是衝突標記,並不是衝突的資料,你一定要確定這些內容在下次提交之前得到刪除,前兩組標誌中間的內容是你在衝突區所做的修改:
<<<<<<< .mine
Salami
Mortadella
Prosciutto
=======
後兩組之間的是Sally提交的修改衝突:
=======
Sauerkraut
Grilled Chicken
>>>>>>> .r2
通常你並不希望只是刪除衝突標誌和Sally的修改—當她收到三明治時,會非常的吃驚。所以你應該走到她的辦公室或是拿起電話告訴Sally,你沒辦法從從義大利熟食店得到想要的泡菜。[7]一旦你們確認了提交內容後,修改檔案並且刪除衝突標誌。
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.”
現在我們準備好提交修改了,注意svn resolved不像我們本章學過的其他命令一樣需要引數,在任何你認為解決了衝突的時候,只需要小心執行svn resolved,—一旦刪除了臨時檔案,Subversion會讓你提交這檔案,即使檔案中還存在衝突標記。
記住,如果你修改衝突時感到混亂,你可以參考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 revert
如果你得到衝突,經過檢查你決定取消自己的修改並且重新編輯,你可以恢復你的修改:
$ svn revert sandwich.txt
Reverted ‘sandwich.txt’
$ ls sandwich.*
sandwich.txt
注意,當你恢復一個衝突的檔案時,不需要再執行svn resolved。
 
 
 
 
提交你的修改
svn commit命令傳送所有的修改到版本庫,當你提交修改時,你需要提供一些描述修改的日誌資訊,你的資訊會附到這個修訂版本上,如果資訊很簡短,你可以在命令列中使用–message(或-m)選項:
$ svn commit -m “Corrected number of cheese slices.”
Sending        sandwich.txt
Transmitting file data .
Committed revision 3.
然而,如果你把寫日誌資訊當作工作的一部分,你也許會希望告訴Subversion通過一個檔名得到日誌資訊,使用–file選項:
$ svn commit -F logmsg
Sending        sandwich.txt
Transmitting file data .
Committed revision 4.
如果你沒有指定–message或者–file選項,Subversion會自動地啟動你最喜歡的編輯器來編輯日誌資訊。
 
 
檢驗歷史
你的版本庫就像是一臺時間機器,它記錄了所有提交的修改,允許你檢查檔案或目錄以及相關後設資料的歷史。通過一個Subversion命令你可以根據時間或修訂號取出一個過去的版本(或者恢復現在的工作拷貝),然而,有時候我們只是想看看歷史而不想回到歷史。
有許多命令可以為你提供版本庫歷史:
svn log
展示給你主要資訊:每個版本附加在版本上的作者與日期資訊和所有路徑修改。
svn diff
顯示特定修改的行級詳細資訊。
svn cat
取得在特定版本的某一個檔案顯示在當前螢幕。
svn list
顯示一個目錄在某一版本存在的檔案。
 
比較本地修改:像我們看到的,不使用任何引數呼叫時,svn diff將會比較你的工作檔案與快取在.svn的“原始”拷貝 
比較工作拷貝和版本庫:如果傳遞一個–revision(-r)引數,你的工作拷貝會與指定的版本比較 : svn diff -r 3 rules.txt
比較版本庫與版本庫:如果通過–revision (-r)傳遞兩個通過冒號分開的版本號,這兩個版本會進行比較 : svn diff -r 2:3 rules.txt 。與前一個修訂版本比較更方便的辦法是使用–change (-c) : svn diff -c 3 rules.txt
 
 
 
 
瀏覽版本庫
通過svn cat和svn list,你可以在未修改工作修訂版本的情況下檢視檔案和目錄的內容,實際上,你甚至也不需要有一個工作拷貝。
svn cat
如果你只是希望檢查一個過去的版本而不希望察看它們的區別,使用svn cat
svn list
可以在不下載檔案到本地目錄的情況下來察看目錄中的檔案,如果你希望察看詳細資訊,你可以使用–verbose(-v) 引數
沒有任何引數的svn list命令預設使用當前工作拷貝的版本庫URL,而不是本地工作拷貝的目錄。畢竟,如果你希望列出本地目錄,你只需要使用ls(或任何合理的非UNIX等價物)。
 
 
 
 
獲得舊的版本庫快照
除了以上的命令,你可以使用帶引數–revision的svn update和svn checkout來使整個工作拷貝“回到過去”:
$ svn checkout -r 1729 # Checks out a new working copy at r1729
$ svn update -r 1729 # Updates an existing working copy to r1729
 
 
最後,如果你構建了一個版本,並且希望從Subversion打包檔案,但是你不希望有討厭的.svn目錄,這時你可以匯出版本庫的一部分檔案而沒有.svn目錄。就像svn update和svn checkout,你也可以傳遞–revision選項給svn export:
$ svn export http://svn.example.com/svn/repos1 # Exports latest revision
$ svn export http://svn.example.com/svn/repos1 -r 1729 # Exports revision r1729
 
 
 
有時你只需要清理
當Subversion改變你的工作拷貝(或是.svn中的任何資訊),它會儘可能的小心,在修改任何事情之前,它把意圖寫到日誌檔案中去,然後執行log檔案中的命令,並且執行過程中在工作拷貝的相關部分儲存一個鎖— 防止Subversion客戶端在變更過程中訪問工作拷貝。然後刪掉日誌檔案,這與記帳試的檔案系統架構類似。如果Subversion的操作中斷了(舉個例子:程序被殺死了,機器死掉了),日誌檔案會儲存在硬碟上,通過重新執行日誌檔案,Subversion可以完成上一次開始的操作,你的工作拷貝可以回到一致的狀態。
這就是svn cleanup所作的:它查詢工作拷貝中的所有遺留的日誌檔案,刪除程序中工作拷貝的鎖。如果Subversion告訴你工作拷貝中的一部分已經“鎖定”了,你就需要執行這個命令了。同樣,svn status將會使用L 標示鎖定的專案:
$ svn status
  L    somedir
M      somedir/foo.c
$ svn cleanup
$ svn status
M      somedir/foo.c
不要將工作拷貝鎖與Subversion使用者使用併發版本控制的“鎖定-修改-解鎖”模型建立的鎖混淆
 
 
 
 
 
 
 
 
 
 
高階主題
 
 
修訂版本關鍵字
Subversion客戶端可以理解一些修訂版本關鍵字,這些關鍵字可以用來代替–revision (r)的數字引數,這會被Subversion解釋到特定修訂版本號:
HEAD
版本庫中最新的(或者是“最年輕的”)版本。
BASE
工作拷貝中一個條目的修訂版本號,如果這個版本在本地修改了,則“BASE版本”就是這個條目在本地未修改的版本。
COMMITTED
專案最近修改的修訂版本,與BASE相同或更早。
PREV
一個專案最後修改版本之前的那個版本,技術上可以認為是COMMITTED -1。
因為可以從描述中得到,關鍵字PREV,BASE和COMMITTED只在引用工作拷貝路徑時使用,而不能用於版本庫URL,而關鍵字HEAD則可以用於兩種路徑型別。
下面是一些修訂版本關鍵字的例子:
$ svn diff -r PREV:COMMITTED foo.c # shows the last change committed to foo.c
$ svn log -r HEAD # shows log message for the latest repository commit
$ svn diff -r HEAD # compares your working copy (with all of its local changes) to the latest version of that tree in the repository
$ svn diff -r BASE:HEAD foo.c # compares the unmodified version of foo.c with the latest version of foo.c in the repository
$ svn log -r BASE:HEAD # shows all commit logs for the current versioned directory since you last updated
$ svn update -r PREV foo.c # rewinds the last change on foo.c, decreasing foo.c’s working revision
$ svn diff -r BASE:14 foo.c # compares the unmodified version of foo.c with the way foo.c looked in revision 14
 
 
 
版本日期
在版本控制系統以外,修訂版本號碼是沒有意義的,但是有時候你需要將時間和歷史修訂版本號關聯。為此,–revision (-r)選項接受使用花括號({和})包裹的日期輸入,Subversion支援標準ISO-8601日期和時間格式,也支援一些其他的。下面是一些例子。(記住使用引號括起所有包含空格的日期。)
$ svn checkout -r {2006-02-17}
$ svn checkout -r {15:30}
$ svn checkout -r {15:30:00.200000}
$ svn checkout -r {“2006-02-17 15:30”}
$ svn checkout -r {“2006-02-17 15:30 0230”}
$ svn checkout -r {2006-02-17T15:30}
$ svn checkout -r {2006-02-17T15:30Z}
$ svn checkout -r {2006-02-17T15:30-04:00}
$ svn checkout -r {20060217T1530}
$ svn checkout -r {20060217T1530Z}
$ svn checkout -r {20060217T1530-0500}
當你指定一個日期,Subversion會在版本庫找到接近這個日期的最近版本,並且對這個版本繼續操作
 
Subversion 會早一天嗎?
如果你只是指定了日期而沒有時間(舉個例子2006-11-27),你也許會以為Subversion會給你11-27號最後的版本,相反,你會得到一個26號版本,甚至更早。記住Subversion會根據你的日期找到最新的版本,如果你給一個日期,而沒有給時間,像2006-11-27,Subversion會假定時間是00:00:00,所以在27號找不到任何版本。
如果你希望查詢包括27號,你既可以使用({“2006-11-27 23:59”}),或是直接使用第二天({2006-11-28})。
你可以使用時間段,Subversion會找到這段時間的所有版本:
$ svn log -r {2006-11-20}:{2006-11-29}
 
因為一個版本的時間戳是作為一個屬性儲存的—不是版本化的,而是可以編輯的屬性—版本號的時間戳可以被修改,從而建立一個虛假的年代表,也可以被完全刪除。Subversion正確轉化修訂版本日期到修訂版本的能力依賴於修訂版本時間戳順序排列—修訂版本越年輕,則時間戳越年輕。如果順序沒有被維護,你會發現使用日期指定修訂版本不會返回你期望的資料。
 
 
 
 
忽略未版本控制的條目
在任何工作拷貝,將版本化檔案和目錄與沒有也不準備版本化的檔案分開會是非常常見的情況。文字編輯器的備份檔案會將目錄搞亂,程式碼編譯過程中生成的中間檔案,甚至最終檔案也不是你希望版本化的,使用者在見到這些檔案和目錄(經常是版本控制工作拷貝中)的任何時候都會將他們刪除。
期望讓Subversion的工作拷貝擺脫混亂保持乾淨是可笑的,實際上Subversion將工作拷貝是普通目錄作為它的一項特性。但是這些沒有版本化的檔案和目錄會給Subversion使用者帶來一些煩惱,例如,因為svn add和svn import命令都是會遞迴執行的,並不知道哪些檔案你不希望版本化,很容易意外的新增一些檔案。因為svn status會報告工作拷貝中包括未版本化檔案和目錄的資訊,如果這種檔案很多,它的輸出會變得非常嘈雜。
所以Subversion提供了兩種方法讓你指明哪些檔案可以被漠視,一種方法需要你修改Subversion的執行配置系統(見“執行配置區”一節),這樣會使所有的Subversion操作都利用這個配置,通常來說,這是在某一個計算機上的操作,或者是某個計算機某個使用者的操作。另一種方法利用了Subversion目錄屬性支援,與版本化的目錄樹緊密結合,因而會影響所有擁有這個目錄樹工作拷貝的人。兩種機制都使用檔案模式。
Subversion執行配置系統提供一個global-ignores選項,其中的值是空格分開的檔名模式(或glob)。這些模式會應用到可以新增到版本控制的候選者,也就是svn status顯示出來的未版本化檔案。如果檔名與其中的某個模式匹配,Subversion會當這個檔案不存在。這個檔案模式最好是全域性不期望版本化的模式,例如編輯器Emacs的備份檔案*~和.*~。
如果是在版本化目錄上發現svn:ignore屬性,其內容是一列以行分割的檔案模式,Subversion用來判斷在這個目錄下物件是否被忽略。這些模式不會覆蓋在執行配置設定的全域性忽略,而是向其新增忽略模式。不像全域性忽略選項,在svn:ignore屬性中設定的值只會應用到其設定的目錄,而不會應用到其子目錄。svn:ignore屬性是告訴Subversion在每個使用者的工作拷貝對應目錄忽略相同的檔案的好方法,例如編譯輸出或—使用一個本書相關的例子—本書從DocBook XML檔案生成的HTML、PDF或PostScript。
Subversion對於忽略檔案模式的支援僅限於將未版本化檔案和目錄新增到版本控制時,如果一個檔案已經在Subversion控制下,忽略模式機制不會再有效果,不要期望Subversion會阻止你提交一個符合忽略條件的修改—Subversion一直認為它是版本化的物件。
 
全域性忽略模式只是一種個人喜好,可能更接近於使用者的特定工具鏈,而不是特定工作拷貝的需要,所以餘下的小節將關注svn:ignore屬性和它的使用。
假定你的svn status有如下輸出:
$ svn status calc
 M     calc/button.c
?      calc/calculator
?      calc/data.c
?      calc/debug_log
?      calc/debug_log.1
?      calc/debug_log.2.gz
?      calc/debug_log.3.gz
在這個例子裡,你對button.c檔案作了一些屬性修改,但是你的工作拷貝也有一些未版本化的檔案:你從原始碼編譯的最新計算器程式,一系列除錯輸出日誌檔案,現在你知道你的編譯系統一直會編譯生成計算器程式。 [13]而且你知道你的測試元件總是會留下這些除錯日誌,這對所有的工作拷貝都是一樣的,不僅僅是你的。你也知道你不會有興趣在svn status命令中顯示這些資訊,所以使用svn propedit svn:ignore calc來為calc目錄增加一些忽略模式,舉個例子,你或許會新增如下的值作為svn:ignore的屬性:
calculator
debug_log*
當你新增完這些屬性,你會在calc目錄有一個本地修改,但是注意你的svn status輸出有什麼其他的不同:
$ svn status
 M     calc
 M     calc/button.c
?      calc/data.c
現在,所有多餘的輸出不見了!當然,你的計算器程式和所有的日誌檔案還在工作拷貝中,Subversion僅僅是不再提醒你它們的存在和未版本化。現在所有討厭的噪音都已經不再顯示,只留下了你感興趣的條目—如你忘記新增到版本控制的原始碼檔案data.c。
當然,不僅僅只有這種簡略的工作拷貝狀態輸出,如果想檢視被忽略的檔案,可以使用Subversion的–no-ignore選項:
$ svn status –no-ignore
 M     calc
 M     calc/button.c
I      calc/calculator
?      calc/data.c
I      calc/debug_log
I      calc/debug_log.1
I      calc/debug_log.2.gz
I      calc/debug_log.3.gz
我們在前面提到過,svn add和svn import也會使用這個忽略模式列表,這兩個操作都包括了詢問Subversion來開始管理一組檔案和目錄。比強制使用者挑揀目錄樹中那個檔案要納入版本控制的方式更好,Subversion使用忽略模式來檢測那個檔案不應該在大的迭代新增和匯入操作中進入版本控制系統。再次說明,操作Subversion檔案和目錄時你可以使用–no-ignore選項忽略這個忽略列表。
 
 
 
 
 
 
關鍵字替換
Subversion具備新增關鍵字的能力—一些有用的,關於版本化的檔案動態資訊的片斷—不必直接新增到檔案本身。關鍵字通常會用來描述檔案最後一次修改的一些資訊,因為這些資訊每次都有改變,更重要的一點,這是在檔案修改之後,除了版本控制系統,對於任何企圖保持資料最新的過程都是一場混亂,
舉個例子,你有一個文件希望顯示最後修改的日期,你需要麻煩每個作者提交之前做這件事情,也要修改文件的一部分來描述何時作的修改,但是遲早會有人忘記做這件事,不選擇簡單的告訴Subversion來執行替換LastChangedDate關鍵字的操作,你通過在目標位置放置一個keyword anchor來控制關鍵字插入的位置,這個anchor只是一個格式為$KeywordName$字串。
所有作為anchor出現在檔案裡的關鍵字是大小寫敏感的:為了關鍵字的擴充套件,你必須使用正確的大寫,你必須考慮svn:keywords的屬性值也是大小寫敏感—特定的關鍵字名會忽略大小寫,但是這個特性已經被廢棄了。
Subversion定義了用來替換的關鍵字列表,這個列表儲存瞭如下五個關鍵字,有一些也包括了可用的別名:
Date
這個關鍵字儲存了檔案最後一次在版本庫修改的日期,看起來類似於$Date: 2006-07-22 21:42:37 -0700 (Sat, 22 Jul 2006) $,它也可以用LastChangedDate來指定。
Revision
這個關鍵字描述了這個檔案最後一次修改的修訂版本,看起來像$Revision: 144 $,也可以通過LastChangedRevision或者Rev引用。
Author
這個關鍵字描述了最後一個修改這個檔案的使用者,看起來類似$Author: harry $,也可以用LastChangedBy來指定。
HeadURL
這個關鍵字描述了這個檔案在版本庫最新版本的完全URL,看起來類似$HeadURL: http://svn.collab.net/repos/trunk/README $,可以縮寫為URL。
Id
這個關鍵字是其他關鍵字一個壓縮組合,它看起來就像$Id: calc.c 148 2006-07-28 21:30:43Z sally $,可以解釋為檔案calc.c上一次修改的修訂版本號是148,時間是2006年7月28日,作者是sally。
前面的一些描述使用了類似“最後已知的”短語,請記住關鍵字擴充套件是客戶端操作,你的客戶端只“知道”在你更新工作拷貝時版本庫發生的修改,如果你從不更新工作拷貝,即使檔案在版本庫裡有規律的修改,這些關鍵字也不會擴充套件為不同的值。只在你的檔案增加關鍵字anchor不會做什麼特別的事情,Subversion不會嘗試對你的檔案內容執行文字替換,除非明確的被告知這樣做,畢竟,你可以撰寫一個關於如何使用關鍵字的文件,你不希望Subversion會替換你漂亮的關於不需要替換的關鍵字anchor例項!
為了告訴Subversion是否替代某個檔案的關鍵字,我們要再次求助於屬性相關的子命令,當svn:keywords屬性設定到一個版本化的檔案,這些屬性控制了哪些關鍵字將會替換到這個檔案,這個屬性的值是空格分隔的前面列表的名稱或是別名列表。
舉個例子,假定你有一個版本化的檔案weather.txt,內容如下:
Here is the latest report from the front lines.
$LastChangedDate$
$Rev$
Cumulus clouds are appearing more frequently as summer approaches.
當沒有svn:keywords屬性設定到這個檔案,Subversion不會有任何特別操作,現在讓我們允許LastChangedDate關鍵字的替換。
$ svn propset svn:keywords “Date Author” weather.txt
property ‘svn:keywords’ set on ‘weather.txt’
$
現在你已經對weather.txt的屬性作了修改,你會看到檔案的內容沒有改變(除非你之前做了一些屬性設定),注意這個檔案包含了Rev的關鍵字anchor,但我們沒有在屬性值中包括這個關鍵字,Subversion會高興的忽略替換這個檔案中的關鍵字,也不會替換svn:keywords屬性中沒有出現的關鍵字。
在你提交了屬性修改後,Subversion會立刻更新你的工作檔案為新的替代文字,你將無法找到$LastChangedDate$的關鍵字anchor,你會看到替換的結果,這個結果也儲存了關鍵字的名字,與美元符號($)繫結在一起,而且我們預測的,Rev關鍵字不會被替換,因為我們沒有要求這樣做。
注意我們設定svn:keywords屬性為“Date Author”,關鍵字anchor使用別名$LastChangedDate$並且正確的擴充套件。
Here is the latest report from the front lines.
$LastChangedDate: 2006-07-22 21:42:37 -0700 (Sat, 22 Jul 2006) $
$Rev$
Cumulus clouds are appearing more frequently as summer approaches.
如果有其他人提交了weather.txt的修改,你的此檔案的拷貝還會顯示同樣的替換關鍵字值—直到你更新你的工作拷貝,此時你的weather.txt重的關鍵字將會被替換來反映最新的提交資訊。
 
Subversion 1.2引入了另一種關鍵字的語法,提供了額外和有用的,儘管是非典型的功能。你現在可以告訴Subversion為替代的關鍵字維護一個固定長度(從消耗位元組的觀點),通過在關鍵字名後使用雙冒號(::),然後緊跟一組空格,你就定義了固定寬度。當Subversion使用替代值代替你的關鍵字,只會替換這些空白字元,保持關鍵字欄位長度保持不變,如果替代值比定義的欄位短,會有替代欄位後保留空格;如果替代值太長,就會在最後的美元符號終止符前用井號(#)截斷。
$Rev::               $:  Revision of last commit
$Author::            $:  Author of last commit
$Date::              $:  Date of last commit
需要意識到,因為關鍵字欄位的長度是以位元組為單位,可能會破壞多位元組值,例如一個使用者名稱包含多位元組的UTF-8字元,可能會遭遇從某個字元中間截斷的情況,從位元組角度看僅僅是一種截斷,但是從UTF-8字串角度看可能是錯誤和曲解的,當載入檔案時,破壞的UTF-8文字可能導致整個檔案的破壞,整個檔案無法操作。所以,當限制關鍵字為固定大小時,需要選擇一個可以擴充套件的大小。
 
 
 
 
 
鎖定
Subversion 的鎖定特性為兩個主要目的服務:
1.順序訪問資源。允許使用者得到一個排他的修改檔案權,這個使用者可以確定不可合併的修改不會被浪費—他對這個修改的提交會成功。
2.輔助交流。通過要求使用者對某個版本化物件序列工作,使用者可以知道物件正在被別人修改,這樣可以防止浪費精力和時間去修改一個不可合併和提交的物件。
“鎖定”的三種含義
在本小節,和幾乎本書的每一個地方“lock”和“locking”描述了一種避免使用者之間衝突提交的排他機制,但是佷不幸,Subversion中還有另外兩種鎖,因此需要在本書格外關心。
第一種是工作拷貝鎖,Subversion內部用來防止不同客戶端同時操作同一份工作拷貝的鎖,這種鎖使用svn status輸出中第三列出現的L表示,可以使用svn cleanup刪除,“有時你只需要清理”一節有介紹。
第二種,資料庫鎖,在Berkeley DB後端內部使用,防止多個程式訪問資料庫發生衝突,一個導致版本庫“楔住”的錯誤發生後產生,“Berkeley DB 恢復”一節有描述。
在發生問題之前你完全可以忘記上面兩種鎖,在本書,“鎖定”意味著第一種鎖,除非是在從上下文中十分明確或明確指出的。
 
建立鎖定
為了防止其他使用者此時提交這個檔案的修改(也是警告別人他正在修改它),使用svn lock命令鎖定了版本庫中的這個檔案: $ svn lock banana.jpg -m “Editing file for tomorrow’s release.”
令牌的一個重要事實—它們快取在工作拷貝。有鎖定令牌是非常重要的,這給了工作拷貝權利利用這個鎖的能力。svn status會在檔案後面顯示一個K(locKed的縮寫),表明了擁有鎖定令牌。
 
關於鎖定令牌
一個鎖不是一個認證令牌,而是一個授權令牌,這個令牌不是一個受保護的祕密,事實上,任何人都可以通過svn info URL發現這個唯一令牌。一個鎖定令牌只有在工作拷貝中才有特別的意義,它是鎖定建立在這個工作拷貝的證據,而不是其它使用者在其他地方,僅僅檢驗鎖定擁有者還不能防止出現意外。
例如,你在辦公室電腦上鎖定了一個檔案,或許修改正在進行中。很有可能在你的家用計算機上的一個工作拷貝(或別的Subversion客戶端)裡你又不小心修改了同一個檔案,僅僅因為檢驗了你就是鎖定的擁有者。換句話說,鎖定令牌防止你通過一個Subversion相關軟體的工作破壞另一個的工作。(在我們的例子裡,如果你真的需要在另一個工作拷貝修改這個檔案,你必須打破鎖定再重新鎖定檔案。)
 
需要注意到提交之後,svn status顯示工作拷貝已經沒有鎖定令牌了,這是svn commit的標準行為方式—它會遍歷工作拷貝(或者從目標列表,如果有列表的話),並且作為提交的一部分傳送所有遇到的鎖定令牌到伺服器。當提交完全成功,前面用到的所有版本庫鎖定都會被釋放—即使是沒有提交的檔案。這樣的原因是不鼓勵使用者濫用鎖定,或者是長時間的保持鎖定。
自動釋放鎖定的特性可以通過svn commit的–no-unlock選項關閉,當你要提交檔案,同時期望繼續修改而必須保留鎖定時非常有用。這個特性也可以半永久性的設定,方法是設定執行中config檔案(見“執行配置區”一節)的no-unlock = yes。
當然,鎖定一個檔案不會強制一個人要提交修改,任何時候都可以通過執行svn unlock命令釋放鎖定
 
 
 
發現鎖定
最明顯的方式就是因為鎖定而不能提交一個檔案,最簡單的方式是svn status –show-updates
$ svn status -u
M              23   bar.c
M    O         32   raisin.jpg
       *       72   foo.h
Status against revision:     105
在這個例子裡,Sally可以見到不僅她的foo.h是過期的,而且發現兩個計劃要提交的檔案被鎖定了。O符號表示其他人所訂了檔案。如果她嘗試提交,raisin.jpg的鎖定會阻止她,Sally會納悶誰鎖定了檔案,什麼時候,為什麼。再一次,svn info擁有答案:
$ svn info http://svn.example.com/repos/project/raisin.jpg
Path: raisin.jpg
Name: raisin.jpg
URL: http://svn.example.com/repos/project/raisin.jpg
Repository UUID: edb2f264-5ef2-0310-a47a-87b0ce17a8ec
Revision: 105
Node Kind: file
Last Changed Author: sally
Last Changed Rev: 32
Last Changed Date: 2006-01-25 12:43:04 -0600 (Sun, 25 Jan 2006)
Lock Token: opaquelocktoken:fc2b4dee-98f9-0310-abf3-653ff3226e6b
Lock Owner: harry
Lock Created: 2006-02-16 13:29:18 -0500 (Thu, 16 Feb 2006)
Lock Comment (1 line):
Need to make a quick tweak to this image.
 
 
 
解除和偷竊鎖定
版本庫鎖定並不是神聖不可侵犯的,在Subversion的預設配置狀態,不只是建立者可以釋放鎖定,任何人都可以。當有其他人期望消滅鎖定時,我們稱之為打破鎖定。
從管理員的位子上很容易打破鎖定,svnlook和svnadmin程式都有能力從版本庫直接顯示和刪除鎖定。
svnadmin lslocks /usr/local/svn/repos
svnadmin rmlocks /usr/local/svn/repos /project/raisin.jpg
更有趣的選項是允許使用者互相打破鎖定,為此,Sally只需要使用unlock命令的–force選項:
$ svn status -u
M              23   bar.c
M    O         32   raisin.jpg
       *       72   foo.h
Status against revision:     105
$ svn unlock raisin.jpg
svn: ‘raisin.jpg’ is not locked in this working copy
$ svn info raisin.jpg | grep URL
URL: http://svn.example.com/repos/project/raisin.jpg
$ svn unlock http://svn.example.com/repos/project/raisin.jpg
svn: Unlock request failed: 403 Forbidden (http://svn.example.com)
$ svn unlock –force http://svn.example.com/repos/project/raisin.jpg
‘raisin.jpg’ unlocked.
當然,簡單的打破鎖定也許還不夠,在這個例子裡,Sally不僅想要打破Harry遺忘的鎖定,她也希望自己重新鎖定。她可以通過執行svn unlock –force緊接著svn lock,但是有可能有人在這兩次命令之間鎖定了檔案,最簡單的方式是竊取這個鎖定,將打破和重新鎖定變成一種原子操作,為此需要執行svn lock加–force選項
如果版本庫鎖定被打破了,svn status –show-updates會在檔案旁邊顯示一個B (Broken)。如果有一個新的鎖,就會顯示一個T (sTolen)符號。最終,svn update會注意到所有死掉的鎖定並且把它們從工作拷貝中刪除掉。
 
 
鎖定交流
利用svn lock和svn unlock來建立、釋放、打破和竊取鎖定,這就滿足了順序訪問檔案的要求,但是浪費時間
例如,假定Harry鎖定了一個圖片,並開始編輯。同時,幾英里之外的Sally希望做同樣的工作,她沒想到執行svn status –show-updates,她不知道Harry已經鎖定了檔案。她花費了數小時來修改檔案,當她真被提交時發現檔案已經被鎖定或者是她的檔案已經過期了。她的修改不能和Harry的合併,他們中的一人需要拋棄自己的工作,許多時間被浪費了。
Subversion針對此問題的解決方案是提供一種機制,提醒使用者在開始編輯以前必須鎖定這個檔案,這個機制就是提供一種特別的屬性–svn:needs-lock。當有這個值時,除非使用者鎖定這個檔案,否則檔案一直是隻讀的。當得到一個鎖定令牌(執行svn lock的結果),檔案變成可讀寫,當釋放這個鎖後,檔案又變成只讀。
$ /usr/local/bin/gimp raisin.jpg
gimp: error: file is read-only!
$ ls -l raisin.jpg
-r–r–r–   1 sally   sally   215589 Jun  8 19:23 raisin.jpg
$ svn lock raisin.jpg
svn: Lock request failed: 423 Locked (http://svn.example.com)
$ svn info http://svn.example.com/repos/project/raisin.jpg | grep Lock
Lock Token: opaquelocktoken:fc2b4dee-98f9-0310-abf3-653ff3226e6b
Lock Owner: harry
Lock Created: 2006-06-08 07:29:18 -0500 (Thu, 08 June 2006)
Lock Comment (1 line):
Making some tweaks.  Locking for the next two hours.
我們鼓勵使用者和管理員都應該給不能根據上下文的檔案新增svn:needs-lock屬性,這是鼓勵好的鎖定習慣和防止浪費的主要技術手段。
需要注意到這個屬性是依賴於鎖定系統的交流工具,不管是否有這個屬性,檔案都可以鎖定。相反的,無論有沒有這個屬性,並不會要求提交需要首先鎖定檔案。
這個系統並不是毫無瑕疵,即使有這個屬性,只讀提醒也有可能失效。有些程式“偷偷的篡改了”檔案的只讀屬性,悄無聲息的允許使用者編輯和儲存檔案,不幸的是,Subversion對此無能為力—即使到了現今,還是沒有任何工具能夠代替人與人的良好交流。
 
 
 
 
 
 
 
 
 
 
 
 
分支與合併
 
建立分支
建立分支非常的簡單—使用svn copy命令給你的工程做個拷貝,Subversion不僅可以拷貝單個檔案,也可以拷貝整個目錄,在目前情況下,你希望作/calc/trunk的拷貝,新的拷貝應該在哪裡?在你希望的任何地方—它只是在於專案的政策,我們假設你們專案的政策是在/calc/branches建立分支,並且你希望把你的分支叫做my-calc-branch,你希望建立一個新的目錄/calc/branches/my-calc-branch,作為/calc/trunk的拷貝開始它的生命週期。
有兩個方法作拷貝,我們先介紹一個混亂的方法,只是讓概念更清楚,首先取出一個專案的根目錄,/calc:
$ svn checkout http://svn.example.com/repos/calc bigwc
A  bigwc/trunk/
A  bigwc/trunk/Makefile
A  bigwc/trunk/integer.c
A  bigwc/trunk/button.c
A  bigwc/branches/
Checked out revision 340.
建立一個備份只是傳遞兩個目錄引數到svn copy命令:
$ cd bigwc
$ svn copy trunk branches/my-calc-branch
$ svn status
A     branches/my-calc-branch
在這個情況下,svn copy命令迭代的將trunk工作目錄拷貝到一個新的目錄branhes/my-calc-branch,像你從svn status看到的,新的目錄是準備新增到版本庫的,但是也要注意A後面的“ ”號,這表明這個準備新增的東西是一份備份,而不是新的東西。當你提交修改,Subversion會通過拷貝/calc/trunk建立/calc/branches/my-calc-branch目錄,而不是通過網路傳遞所有資料:
$ svn commit -m “Creating a private branch of /calc/trunk.”
Adding         branches/my-calc-branch
Committed revision 341.
 
現在,我們必須告訴你建立分支最簡單的方法:svn copy可以直接對兩個URL操作。
$ svn copy http://svn.example.com/repos/calc/trunk \
           http://svn.example.com/repos/calc/branches/my-calc-branch \
      -m “Creating a private branch of /calc/trunk.”
Committed revision 341.
從版本庫的視點來看,其實這兩種方法沒有什麼區別,兩個過程都在版本341建立了一個新目錄作為/calc/trunk的一個備份,注意第二種方法,只是執行了一個立即提交。 這是一個簡單的過程,因為你不需要取出版本庫一個龐大的映象,事實上,這個技術不需要你有工作拷貝,這是大多數使用者建立分支的方式。
 
廉價複製
Subversion的版本庫有特殊的設計,當你複製一個目錄,你不需要擔心版本庫會變得十分巨大—Subversion並不是拷貝所有的資料,相反,它建立了一個已存在目錄樹的入口,如果你是Unix使用者,可以把它理解成硬連結,在對拷貝的檔案和目錄操作之前,Subversion還僅僅把它當作硬連結,只有為了區分不同版本的物件時才會複製資料。
這就是為什麼經常聽到Subversion使用者談論“廉價的拷貝”,與目錄的大小無關—這個操作會使用很少的時間,事實上,這個特性是Subversion提交工作的基礎:每一次版本都是前一個版本的一個“廉價的拷貝”,只有少數專案修改了。(要閱讀更多關於這部分的內容,訪問Subversion網站並且閱讀設計文件中的“bubble up”方法)。
當然,拷貝與分享的內部機制對使用者來講是不可見的,使用者只是看到拷貝樹,這裡的要點是拷貝的時間與空間代價很小。如果你完全在版本庫裡建立分支(通過執行svn copy URL1 URL2),這是一個快速的,時間基本固定的操作,只要你希望,可以隨意建立分支。
 
分支背後的關鍵概念
在本節,你需要記住兩件重要的課程。首先,Subversion並沒有內在的分支概念—只有拷貝,當你拷貝一個目錄,這個結果目錄就是一個“分支”,只是因為你給了它這樣一個含義而已。你可以換一種角度考慮,或者特別對待,但是對於Subversion它只是一個普通的拷貝,只不過碰巧包含了一些額外的歷史資訊。第二,因為拷貝機制,Subversion的分支是以普通檔案系統目錄存在的,這與其他版本控制系統不同,它們都為分支定義了另一維度的“標籤”。
 
 
保持分支與主幹同步
Keeping a Branch in Sync
Subversion is aware of the history of your branch and knows when it divided away from thetrunk. To replicate the latest, greatest trunk changes to your branch, first make sure your working copy of the branch is “clean”—that it has no local modifications
reported by svn status. Then simply run:
$ pwd
/home/user/my-calc-branch
$ svn merge http://svn.example.com/repos/calc/trunk
— Merging r345 through r356 into ‘.’:
U button.c
U integer.c
This basic syntax—svn merge URL—tells Subversion to merge all recent changes from the URL to the current working directory (which is typically the root of your working copy). After running the prior example, your branch working copy now contains new local
modifications, and these edits are duplications of all of the changes that have happened on the trunk since you first created your branch:
$ svn status
 M .
M button.c
M integer.c
At this point, the wise thing to do is look at the changes carefully with svn diff, and then build and test your branch. Notice that the current working directory (“.”) has also been modified; the svn diff will show that its svn:mergeinfo property has
been either created or modified. This is important merge-related metadata that you should not touch, since it will be needed by future svn merge commands.
 
After performing the merge, you might also need to resolve some conflicts (just as you do with svn update) or possibly make some small edits to get things working properly. (Remember, just because there are no syntactic conflicts doesn’t mean there aren’t
any semantic conflicts!) If you encounter serious problems, you can always abort the local changes by running svn revert . -R (which will undo all local modifications) and start a long “what’s going on?” discussion with your collaborators. If things look good,
however, you can submit these changes into the repository:
$ svn commit -m “Merged latest trunk changes to my-calc-branch.”
Sending .
Sending button.c
Sending integer.c
Transmitting file data ..
Committed revision 357.
At this point, your private branch is now “in sync” with the trunk, so you can rest easier knowing that as you continue to work in isolation, you’re not drifting too far away from what everyone else is doing.
 
 
 
合併分支到主幹上
What happens when you finally finish your work, though? Your new feature is done, and you’re ready to merge your branch changes back to the trunk (so your team can enjoy the bounty of your labor). The process is simple. First, bring your branch in sync
with the trunk again, just as you’ve been doing all along:
$ svn merge http://svn.example.com/repos/calc/trunk
— Merging r381 through r385 into ‘.’:
U button.c
U README
$ # build, test, …
$ svn commit -m “Final merge of trunk changes to my-calc-branch.”
Sending .
Sending button.c
Sending README
Transmitting file data ..
Committed revision 390.
Now, you use svn merge to replicate your branch changes back into the trunk. You’ll need an up-to-date working copy of /trunk. You can do this by either doing an svn checkout, dredging up an old trunk working copy from somewhere on your disk, or using
svn switch (see the section called “Traversing Branches”.) However you get a trunk working copy, remember that it’s a best practice to do your merge into a working copy that has no local edits and has been recently updated (i.e., is not a mixture of local
revisions). If your working copy isn’t “clean” in these ways, you can run into some unnecessary conflict-related headaches and svn merge will likely return an error. Once you have a clean working copy of the trunk, you’re ready to merge your branch back into
it:
$ pwd
/home/user/calc-trunk
$ svn update # (make sure the working copy is up to date)
At revision 390.
$ svn merge –reintegrate http://svn.example.com/repos/calc/branches/my-calc-bra
— Merging differences between repository URLs into ‘.’:
U button.c
U integer.c
U Makefile
U .
$ # build, test, verify, …
$ svn commit -m “Merge my-calc-branch back into trunk!”
Sending .
Sending button.c
Sending integer.c
Sending Makefile
Transmitting file data ..
Committed revision 391.
Congratulations, your branch has now been remerged back into the main line of development. Notice our use of the –reintegrate option this time around. The option is critical for reintegrating changes from a branch back into its original line of development—don’t
forget it! It’s needed because this sort of “merge back” is a different sort of work than what you’ve been doing up until now. Previously, we had been asking svn merge to grab the “next set” of changes from one line of development (the trunk) and duplicate
them to another (your branch). This is fairly straightforward, and each time Subversion knows how to pick up where it left off. In our prior examples, you can see that first it merges the ranges Branching and Merging 345:356 from trunk to branch; later on,
it continues by merging the next contiguously available range, 356:380. When doing the final sync, it merges the range 380:385.
 
When merging your branch back to the trunk, however, the underlying mathematics is quite different. Your feature branch is now a mishmosh of both duplicated trunk changes and private branch changes, so there’s no simple contiguous range of revisions to
copy over. By specifying the –reintegrate option, you’re asking Subversion to carefully replicate only those changes unique to your branch. (And in fact, it does this by comparing the latest
trunk tree with the latest branch tree: the resulting difference is exactly your branch changes!) Now that your private branch is merged to trunk, you may wish to remove it from the repository:
$ svn delete http://svn.example.com/repos/calc/branches/my-calc-branch \
-m “Remove my-calc-branch.”
Committed revision 392.
 
But wait! Isn’t the history of that branch valuable? What if somebody wants to audit the evolution of your feature someday and look at all of your branch changes? No need to worry. Remember that even though your branch is no longer visible in the /branches
directory, its existence is still an immutable part of the repository’s history. A simple svn log command on the /branches URL will show the entire history of your branch. Your branch can even be resurrected at some point, should you desire (see the section
called “Resurrecting Deleted Items”).
In Subversion 1.5, once a –reintegrate merge is done from branch to trunk, the branch is no longer usable for further work. It’s not able to correctly absorb new trunk changes, nor can it be properly reintegrated to trunk again. For this reason, if you
want to keep working on your feature branch, we recommend destroying it and then re-creating it from the trunk:
$ svn delete http://svn.example.com/repos/calc/branches/my-calc-branch \
-m “Remove my-calc-branch.”
Committed revision 392.
$ svn copy http://svn.example.com/repos/calc/trunk \
http://svn.example.com/repos/calc/branches/new-branch
-m “Create a new branch from trunk.”
Committed revision 393.
$ cd my-calc-branch
$ svn switch http://svn.example.com/repos/calc/branches/new-branch
Updated to revision 393.
 
The final command in the prior example—svn switch—is a way of updating an existing working copy to reflect a different repository directory. We’ll discuss this more in the section called “Traversing Branches”.
 
While it’s perfectly fine to experiment with merges by running svn merge and svn revert over and over, you may run into some annoying (but easily bypassed) roadblocks. For example, if the merge operation adds a new file (i.e., schedules it for addition),
svn revert won’t actually remove the file; it simply
unschedules the addition. You’re left with an unversioned file. If you then attempt to run the merge again, you may get conflicts due to the unversioned file “being in the way.” Solution? After performing a revert, be sure to clean up the working copy
and remove unversioned files and directories. The output of svn status should be as clean as possible, ideally showing no output.
 
 
 
 
取消改變
Undoing Changes
An extremely common use for svn merge is to roll back a change that has already been committed. Suppose you’re working away happily on a working copy of /calc/trunk, and you discover that the change made way back in revision 303, which changed integer.c,
is completely wrong. It never should have been committed. You can use svn merge to “undo” the change in your working copy, and then commit the local modification to the repository. All you need to do is to specify a reverse difference. (You can do this by
specifying –revision 303:302, or by an equivalent –change -303.) 
$ svn merge -c -303 http://svn.example.com/repos/calc/trunk
— Reverse-merging r303 into ‘integer.c’:
U integer.c
$ svn status
M .
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.
 
As we mentioned earlier, one way to think about a repository revision is as a specific changeset. By using the -r option, you can ask svn merge to apply a changeset, or a whole range of changesets, to your working copy. In our case of undoing a change,
we’re asking svn merge to apply changeset #303 to our working copy backward. Keep in mind that rolling back a change like this is just like any other svn merge operation, so you should use svn status and svn diff to confirm that your work is in the state you
want it to be in, and then use svn commit to send the final version to the repository. After committing, this particular changeset is no longer reflected in the HEAD revision. Again, you may be thinking: well, that really didn’t undo the commit, did it? The
change still exists in revision 303. If somebody checks out a version of the calc project between revisions 303 and 349, she’ll still see the bad change, right? 
Yes, that’s true. When we talk about “removing” a change, we’re really talking about removing it from the HEAD revision. The original change still exists in the repository’s history. For most situations, this is good enough. Most people are only interested
in tracking the HEAD of a project anyway. There are special cases, however, where you really might want to destroy all evidence of the commit. (Perhaps somebody accidentally committed a confidential document.) This isn’t so easy, it turns out, because Subversion
was deliberately designed to never lose information. Revisions are immutable trees that build upon one another. Removing a revision from history would cause a domino effect, creating chaos in all subsequent revisions and possibly invalidating all working copies. 
 
 
 
恢復已刪內容
Resurrecting Deleted Items
The great thing about version control systems is that information is never lost. Even when you delete a file or directory, it may be gone from the HEAD revision, but the object still exists in earlier revisions. One of the most common questions new users
ask is, “How do I get my old file or directory back?”
The first step is to define exactly which item you’re trying to resurrect. Here’s a useful metaphor: you can think of every object in the repository as existing in a sort of twodimensional coordinate system. The first coordinate is a particular revision
tree, and the second coordinate is a path within that tree. So every version of your file or directory can be defined by a specific coordinate pair. (Remember the “peg revision” [email protected]—mentioned back in the section called “Peg and Operative Revisions”.) 
First, you might need to use svn log to discover the exact coordinate pair you wish to resurrect. A good strategy is to run svn log –verbose in a directory that used to contain your deleted item. The –verbose (-v) option shows a list of all changed items
in each revision; all you need to do is find the revision in which you deleted the file or directory. You can do this visually, or by using another tool to examine the log output (via grep, or
perhaps via an incremental search in an editor).
$ cd parent-dir
$ svn log -v
————————————————————————
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.
In the example, we’re assuming that you’re looking for a deleted file real.c. By looking through the logs of a parent directory, you’ve spotted that this file was deleted in revision 808. Therefore, the last version of the file to exist was in the revision
right before that. Conclusion: you want to resurrect the path /calc/trunk/real.c from revision 807. That was the hard part—the research. Now that you know what you want to restore, you have two different choices.
One option is to use svn merge to apply revision 808 “in reverse.” (We already discussed how to undo changes in the section called “Undoing Changes”.) This would have the effect of re-adding real.c as a local modification. The file would be scheduled for
addition, and after a commit, the file would again exist in HEAD. 
In this particular example, however, this is probably not the best strategy. Reverse-applying revision 808 would not only schedule real.c for addition, but the log message indicates that it would also undo certain changes to integer.c, which you don’t
want. Certainly, you could reverse-merge revision 808 and then svn revert the local modifications to integer.c, but this technique doesn’t scale well. What if 90 files were changed in revision 808?
A second, more targeted strategy is not to use svn merge at all, but rather to use the svn copy command. Simply copy the exact revision and path “coordinate pair” from the repository to your working copy: 
$ svn copy http://svn.example.com/repos/calc/trunk/[email protected] ./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.
The plus sign in the status output indicates that the item isn’t merely scheduled for addition, but scheduled for addition “with history.” Subversion remembers where it was copied from. In the future, running svn log on this file will traverse back through
the file’s resurrection and through all the history it had prior to revision 807. In other words, this new real.c isn’t really new; it’s a direct descendant of the original, deleted file. This is usually considered a good and useful thing.
 
Although our example shows us resurrecting a file, note that these same techniques work just as well for resurrecting deleted directories. Also note that a resurrection doesn’t have to happen in your working copy—it can happen entirely in the repository.
 
 
 
合併某一個特定分支的內容
Cherrypicking
Just as the term “changeset” is often used in version control systems, so is the term cherrypicking. This word refers to the act of choosing one specific changeset from a branch and replicating it to another. Cherrypicking may also refer to the act of
duplicating a particular set of (not necessarily contiguous!) changesets from one branch to another. This is in contrast to more typical merging scenarios, where the “next” contiguous range of revisions
is duplicated automatically.
Why would people want to replicate just a single change? It comes up more often than you’d think. For example, let’s go back in time and imagine that you haven’t yet merged your private feature branch back to the trunk. At the water cooler, you get word
that Sally made an interesting change to integer.c on the trunk. Looking over the history of commits to the trunk, you see that in revision 355 she fixed a critical bug that directly impacts the feature you’re working on. You might not be ready to merge all
the trunk changes to your branch just yet, but you certainly need that particular bug fix in order to continue your work.
$ svn diff -c 355 http://svn.example.com/repos/calc/trunk
Index: integer.c
===================================================================
— integer.c (revision 354)
integer.c (revision 355)
@@ -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, “CP/MM”);
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;
 
Just as you used svn diff in the prior example to examine revision 355, you can pass the same option to svn merge:
$ svn merge -c 355 http://svn.example.com/repos/calc/trunk
U integer.c
$ svn status
M integer.c
 
You can now go through the usual testing procedures before committing this change to your branch. After the commit, Subversion marks r355 as having been merged to the branch so that future “magic” merges that synchronize your branch with the trunk know
to skip over r355. (Merging the same change to the same branch almost always results in a conflict!)
$ cd my-calc-branch
$ svn propget svn:mergeinfo .
/trunk:341-349,355
# Notice that r355 isn’t listed as “eligible” to merge, because
# it’s already been merged.
$ svn mergeinfo http://svn.example.com/repos/calc/trunk –show-revs eligible
r350
r351
r352
r353
r354
r356
r357
r358
r359
r360
$ svn merge http://svn.example.com/repos/calc/trunk
— Merging r350 through r354 into ‘.’:
U .
U integer.c
U Makefile
— Merging r356 through r360 into ‘.’:
U .
U integer.c
U button.c
This use case of replicating (or backporting) bug fixes from one branch to another is perhaps the most popular reason for cherrypicking changes; it comes up all the time, for example, when a team is maintaining a “release branch” of software. (We discuss
this pattern in the section called “Release Branches”.)
 
Did you notice how, in the last example, the merge invocation caused two distinct ranges of merges to be applied? The svn merge command applied two
independent patches to your working copy to skip over changeset 355, which your branch already contained. There’s nothing inherently wrong with this, except
that it has the potential to make conflict resolution trickier. If the first range of changes creates conflicts, you must resolve them interactively for the merge process to continue and apply the second range of changes. If you postpone a conflict from
the first wave of changes, the whole merge command will bail out with an error message.
 
 
 
 
遍歷分支,使用 svn switch 命令,可達到不同版本的檔案組合成一個工程
Traversing Branches
The svn switch command transforms an existing working copy to reflect a different branch. While this command isn’t strictly necessary for working with branches, it provides a nice shortcut. In our earlier example, after creating your private branch, you
checked out a fresh working copy of the new repository directory. Instead, you can simply ask Subversion to change your working copy of /calc/trunk to mirror the new branch location: 
$ 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
“Switching” a working copy that has no local modifications to a different branch results in the working copy looking just as it would if you’d done a fresh checkout of the directory. It’s usually more efficient to use this command, because often branches
differ by only a small degree. The server sends only the minimal set of changes necessary to make your working copy reflect the branch directory.
The svn switch command also takes a –revision (-r) option, so you need not always move your working copy to the HEAD of the branch. Of course, most projects are more complicated than our calc example, and contain multiple subdirectories. Subversion users
often follow a specific algorithm when using
branches:
1. Copy the project’s entire “trunk” to a new branch directory.
2. Switch only part of the trunk working copy to mirror the branch.
In other words, if a user knows that the branch work needs to happen on only a specific subdirectory, she uses svn switch to move only that subdirectory to the branch. (Or sometimes users will switch just a single working file to the branch!) That way,
the user can continue to receive normal “trunk” updates to most of her working copy, but the switched portions will remain immune (unless someone commits a change to her branch). This feature adds a whole new dimension to the concept of a “mixed working copy”—not
only can working copies contain a mixture of working revisions, but they can also contain a mixture of repository locations as well.
 
If your working copy contains a number of switched subtrees from different repository locations, it continues to function as normal. When you update, you’ll receive patches to each subtree as appropriate. When you commit, your local changes will still
be applied as a single, atomic change to the repository. 
Note that while it’s okay for your working copy to reflect a mixture of repository locations, these locations must all be within the same repository. Subversion repositories aren’t yet able to communicate with one another; that feature is planned for the
future.
 
Have you ever found yourself making some complex edits (in your /trunk working copy) and suddenly realized, “Hey, these changes ought to be in their
own branch?” A great technique to do this can be summarized in two steps:
$ svn copy http://svn.example.com/repos/calc/trunk \
http://svn.example.com/repos/calc/branches/newbranch \
-m “Create branch ‘newbranch’.”
Committed revision 353.
$ svn switch http://svn.example.com/repos/calc/branches/newbranch
At revision 353.
 
 
 
 
 
 
 
 
 
 
 
標籤
 
另一個常見的版本控制系統概念是標­¾(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命令得到,與分支一樣,一個目錄之所以是標籤只是人們決定這樣使用它,只要沒有人提交這個目錄,它永遠是一個快照,但如果人們開始提交,它就變成了分支。
如果你管理一個版本庫,你有兩種方式管理標籤,第一種方法是禁止命令:作為專案的政策,我們要決定標籤所在的位置,確定所有使用者知道如何處理拷貝的目錄(也就是確保他們不會提交他們),第二種方法看來很過分:使用訪問控制指令碼來阻止任何想對標籤目錄做的非拷貝的操作這種方法通常是不必要的,如果一個人不小心提交了到標籤目錄一個修改,你可以簡單的取消,畢竟這是版本控制啊。
 
 
建立複雜標籤
有時候你希望你的“快照”能夠很複雜,而不只是一個單獨修訂版本的一個單獨目錄。
舉個例子,假定你的專案比我們的的例子calc大的多:假設它儲存了一組子目錄和許多檔案,在你工作時,你或許決定建立一個包括特定特性和Bug修正的工作拷貝,你可以通過選擇性的回溯檔案和目錄到特定修訂版本(使用svn update -r)來實現,或者轉換檔案和目錄到特定分支(使用svn switch),這樣做之後,你的工作拷貝成為版本庫不同版本和分支的司令部,但是經過測試,你會知道這是你需要的一種精確資料組合。
是時候進行快照了,拷貝URL在這裡不能工作,在這個例子裡,你希望把本地拷貝的佈局做映象並且儲存到版本庫中,幸運的是,svn copy包括四種不同的使用方式(在第 9 章 Subversion 完全參考可以詳細閱讀),包括拷貝工作拷貝到版本庫:
$ ls
my-working-copy/
$ svn copy my-working-copy http://svn.example.com/repos/calc/tags/mytag
Committed revision 352.
現在在版本庫有一個新的目錄/calc/tags/mytag,這是你的本地拷貝的一個快照—混合了修訂版本,URL等等。
一些人也發現這一特性一些有趣的使用方式,有些時候本地拷貝有一組本地修改,你希望你的協作者看到這些,不使用svn diff併傳送一個補定檔案(不會捕捉到目錄、符號鏈和屬性的修改),而是使用svn copy來“上傳”你的工作拷貝到一個版本庫的私有區域,你的協作者可以選擇完整的取出你的工作拷貝,或使用svn merge來接受你的精確修改。
雖然這是上傳快速工作拷貝快照的一個好方法,但這不是初始建立分支的好方法。分支建立必須是它本身的事件,而這個方法建立的分支包含了額外修改,都包含在一個單獨修訂版本里。這讓我們很難識別分支點的單個修訂版本號碼。
你是否發現你做出了複雜的修改(在/trunk的工作拷貝),並突然發現,“這些修改必須在它們自己的分支?”處理這個問題的技術可以總結為兩步:
$ svn copy http://svn.example.com/repos/calc/trunk \
           http://svn.example.com/repos/calc/branches/newbranch
Committed revision 353.
$ svn switch http://svn.example.com/repos/calc/branches/newbranch
At revision 353.
就像svn update命令,svn switch會保留工作拷貝的本地修改,此刻,你的工作拷貝反映到新建的分支上,而你的下一次svn commit會傳送修改到伺服器。
 
 
 
供方分支(第三方庫的管理)
當開發軟體時有這樣一個情況,你版本控制的資料可能關聯於或者是依賴於其他人的資料,通常來講,你的專案的需要會要求你自己的專案對外部實體提供的資料保持儘可能最新的版本,同時不會犧牲穩定性,這種情況總是會出現—只要某個小組的資訊對另一個小組的資訊有直接的影響。
舉個例子,軟體開發者會工作在一個使用第三方庫的應用,Subversion恰好是和Apache的Portable Runtime library(見“Apache可移植執行庫”一節)有這樣一個關係。Subversion原始碼依賴於APR庫來實現可移植需求。在Subversion的早期開發階段,專案緊密地追蹤APR的API修改,經常在庫程式碼的“流血的邊緣”粘住,現在APR和Subversion都已經成熟了,Subversion只嘗試同步APR的經過良好測試的,穩定的API庫。
現在,如果你的專案依賴於其他人的資訊,有許多方法可以用來嘗試同步你的資訊,最痛苦的,你可以為專案所有的貢獻者釋出口頭或書寫的指導,告訴他們確信他們擁有你們的專案需要的特定版本的第三方資訊。如果第三方資訊是用Subversion版本庫維護,你可以使用Subversion的外部定義來有效的“強制”特定的版本的資訊在你的工作拷貝的的位置(見“外部定義”一節)。
但是有時候,你希望在你自己的版本控制系統維護一個針對第三方資料的自定義修改,回到軟體開發的例子,程式設計師為了他們自己的目的會需要修改第三方庫,這些修改會包括新的功能和bug修正,在成為第三方工具官方釋出之前,只是內部維護。或者這些修改永遠不會傳給庫的維護者,只是作為滿足軟體開發需要的單獨的自定義修改存在。
現在你會面對一個有趣的情形,你的專案可以用某種脫節的樣式保持它關於第三方資料自己的修改,如使用補丁檔案或者是完全的可選版本的檔案和目錄。但是這很快會成為維護的頭痛的事情,需要一種機制來應用你對第三方資料的自定義修改,並且迫使在第三方資料的後續版本重建這些修改。
這個問題的解決方案是使用供方分支,一個供方分支是一個目錄樹儲存了第三方實體或供應方的資訊,每一個供應方資料的版本吸收到你的專案叫做供方drop。
供方分支提供了兩個關鍵的益處,第一,通過在我們的版本控制系統儲存現在支援的供方drop,你專案的成員不需要指導他們是否有了正確版本的供方資料,他們只需要作為不同工作拷貝更新的一部份,簡單的接受正確的版本就可以了。第二,因為資料存在於你自己的Subversion版本庫,你可以在恰當的位置儲存你的自定義修改—你不需要一個自動的(或者是更壞,手工的)方法來交換你的自定義行為。
常規的供方分支管理過程
管理供方分支通常會像這個樣子,你建立一個頂級的目錄(如/vendor)來儲存供方分支,然後你匯入第三方的程式碼到你的子目錄。然後你將拷貝這個子目錄到主要的開發分支(例如/trunk)的適當位置。你一直在你的主要開發分支上做本地修改,當你的追蹤的程式碼有了新版本,你會把帶到供方分支並且把它合併到你的/trunk,解決任何你的本地修改和他們的修改的衝突。
也許一個例子有助於我們闡述這個演算法,我們會使用這樣一個場景,我們的開發團隊正在開發一個計算器程式,與一個第三方的複雜數字運算庫libcomplex關聯。我們從供方分支的初始建立開始,並且匯入供方drop,我們會把每株分支目錄叫做libcomplex,我們的程式碼drop會進入到供方分支的子目錄current,並且因為svn import建立所有的需要的中間父目錄,我們可以使用一個命令完成這一步。
$ svn import /path/to/libcomplex-1.0 \
             http://svn.example.com/repos/vendor/libcomplex/current \
             -m ‘importing initial 1.0 vendor drop’
我們現在在/vendor/libcomplex/current有了libcomplex當前版本的程式碼,現在我們為那個版本作標籤(見“標籤”一節),然後拷貝它到主要開發分支,我們的拷貝會在calc專案目錄建立一個新的目錄libcomplex,它是這個我們將要進行自定義的供方資料的拷貝版本。
$ svn copy http://svn.example.com/repos/vendor/libcomplex/current  \
           http://svn.example.com/repos/vendor/libcomplex/1.0      \
           -m ‘tagging libcomplex-1.0’
$ svn copy http://svn.example.com/repos/vendor/libcomplex/1.0  \
           http://svn.example.com/repos/calc/libcomplex        \
           -m ‘bringing libcomplex-1.0 into the main branch’
我們取出我們專案的主分支—現在包括了第一個供方釋放的拷貝—我們開始自定義libcomplex的程式碼,在我們知道之前,我們的libcomplex修改版本是已經與我們的計算器程式完全整合了。 [23]
幾周之後,libcomplex得開發者釋出了一個新的版本—版本1.1—包括了我們很需要的一些特性和功能。我們很希望升級到這個版本,但不希望失去在當前版本所作的修改。我們本質上會希望把我們當前基線版本是的libcomplex1.0的拷貝替換為libcomplex 1.1,然後把前面自定義的修改應用到新的版本。但是實際上我們通過一個相反的方向解決這個問題,應用libcomplex從版本1.0到1.1的修改到我們修改的拷貝。
為了執行這個升級,我們取出一個我們供方分支的拷貝,替換current目錄為新的libcomplex 1.1的程式碼,我們只是拷貝新檔案到存在的檔案上,或者是解壓縮libcomplex 1.1的打包檔案到我們存在的檔案和目錄。此時的目標是讓我們的current目錄只保留libcomplex 1.1的程式碼,並且保證所有的程式碼在版本控制之下,哦,我們希望在最小的版本控制歷史擾動下完成這件事。
完成了這個從1.0到1.1的程式碼替換,svn status會顯示檔案的本地修改,或許也包括了一些未版本化或者丟失的檔案,如果我們做了我們應該做的事情,未版本化的檔案應該都是libcomplex在1.1新引入的檔案—我們執行svn add來將它們加入到版本控制。丟失的檔案是存在於1.1但是不是在1.1,在這些路徑我們執行svn delete。最終一旦我們的current工作拷貝只是包括了libcomplex1.1的程式碼,我們可以提交這些改變目錄和檔案的修改。
我們的current分支現在儲存了新的供方drop,我們為這個新的版本建立一個新的標籤(就像我們為1.0版本drop所作的),然後合併這從個標籤前一個版本的區別到主要開發分支。
 
$ cd working-copies/calc
$ svn merge http://svn.example.com/repos/vendor/libcomplex/1.0      \
            http://svn.example.com/repos/vendor/libcomplex/current  \
            libcomplex
… # resolve all the conflicts between their changes and our changes
$ svn commit -m ‘merging libcomplex-1.1 into the main branch’
在這個瑣碎的用例裡,第三方工具的新版本會從一個檔案和目錄的角度來看,就像前一個版本。沒有任何libcomplex原始檔會被刪除、被改名或是移動到別的位置—新的版本只會儲存針對上一個版本的文字修改。在完美世界,我們對呢修改會乾淨得應用到庫的新版本,不會產生任何併發和衝突。
 
但是事情總不是這樣簡單,實際上原始檔在不同的版本間的移動是很常見的,這種過程複雜性可以確保我們的修改會一直對新的版本程式碼有效,可以很快使形勢退化到我們需要在新版本手工的重新建立我們的自定義修改。一旦Subversion知道了給定檔案的歷史—包括了所有以前的位置—合併到新版本的程序就會很簡單,但是我們需要負責告訴Subversion供方drop之間原始檔佈局的改變。
svn_load_dirs.pl
不僅僅包含一些刪除、新增和移動的供方drops使得升級第三方資料後續版本的過程變得複雜,所以Subversion提供了一個svn_load_dirs.pl指令碼來輔助這個過程,這個指令碼自動進行我們前面提到的常規供方分支管理過程的匯入步驟,從而使得錯誤最小化。你仍要負責使用合併命令合併第三方的新 版本資料合併到主要開發分支,但是svn_load_dirs.pl幫助你快速到達這一步驟。
一句話,svn_load_dirs.pl是一個增強的svn import,具備了許多重要的特性:
它可以在任何有一個存在的版本庫目錄與一個外部的目錄匹配時執行,會執行所有必要的新增和刪除並且可以選則執行移動。
它可以用來操作一系列複雜的操作,如那些需要一箇中間媒介的提交—如在操作之前重新命名一個檔案或者目錄兩次。
它可以隨意的為新匯入目錄打上標籤。
它可以隨意為符合正規表示式的檔案和目錄新增任意的屬性。
svn_load_dirs.pl利用三個強制的引數,第一個引數是Subversion工作的基本目錄URL,第二個引數在URL之後—相對於第一個引數—指向當前的供方分支將會匯入的目錄,最後,第三個引數是一個需要匯入的本地目錄,使用前面的例子,一個典型的svn_load_dirs.pl呼叫看起來如下:
$ svn_load_dirs.pl http://svn.example.com/repos/vendor/libcomplex \
                   current                                        \
                   /path/to/libcomplex-1.1
你可以說明你會希望svn_load_dirs.pl同時打上標籤,這使用-t命令列選項,需要指定一個標籤名,這個標籤是第一個引數的一個相對URL。
$ svn_load_dirs.pl -t libcomplex-1.1                              \
                   http://svn.example.com/repos/vendor/libcomplex \
                   current                                        \
                   /path/to/libcomplex-1.1
當你執行svn_load_dirs.pl,它會檢驗你的存在的“current”供方drop,並且與提議的新供方drop比較,在這個瑣碎的例子裡,沒有檔案只出現在一個版本里,指令碼執行新的匯入而不會發生意外。然而如果版本之間有了檔案佈局的區別,svn_load_dirs.pl會詢問你如何解決這個區別,例如你會有機會告訴指令碼libcomplex版本1.0的math.c檔案在1.1已經重新命名為arithmetic.c,任何沒有解釋為移動的差異都會被看作是常規的新增和刪除。
這個指令碼也接受單獨配置檔案用來為新增到版本庫的檔案和目錄設定匹配正規表示式的屬性。配置檔案通過svn_load_dirs.pl的-p命令列選項指定,這個配置檔案的每一行都是一個空白分割的兩列或者四列值:一個Perl樣式的正規表示式來匹配新增的路徑、一個控制關鍵字(break或者是cont)和可選的屬性名和值。
\.png$              break   svn:mime-type   image/png
\.jpe?g$            break   svn:mime-type   image/jpeg
\.m3u$              cont    svn:mime-type   audio/x-mpegurl
\.m3u$              break   svn:eol-style   LF
.*                  break   svn:eol-style   native
對每一個新增的路徑,會按照順序為匹配正規表示式的檔案配置屬性,除非控制標誌是break(意味著不需要更多的路徑匹配應用到這個路徑)。如果控制說明是cont—continue的縮寫—然後匹配工作會繼續到配置檔案的下一行。
任何正規表示式,屬性名或者屬性值的空格必須使用單引號或者雙引號環繞,你可以使用反斜槓(\)換碼符來回避引號,反斜槓只會在解析配置檔案時迴避引號,所以不能保護必要正規表示式字元之外的的其它字元。
而且完全沒有bug,當然!