1. 程式人生 > >SVN 學習摘要筆記

SVN 學習摘要筆記

svn學習筆記

推薦的版本庫佈局

儘管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 '.':

Ubutton.c

Uinteger.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.

Mbutton.c

Minteger.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.

Sendingbutton.c

Sendinginteger.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 '.':

Ubutton.c

UREADME

$ # build, test, ...

$ svn commit -m "Final merge of trunk changes to my-calc-branch."

Sending.

Sendingbutton.c

SendingREADME

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 '.':

Ubutton.c

Uinteger.c

UMakefile

U.

$ # build, test, verify, ...

$ svn commit -m "Merge my-calc-branch back into trunk!"

Sending.

Sendingbutton.c

Sendinginteger.c

SendingMakefile

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':

Uinteger.c

$ svn status

M.

Minteger.c

$ svn diff

...

# verify that the change is removed

...

$ svn commit -m "Undoing change committed in r303."

Sendinginteger.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 n