最近將某項目代碼庫從SVN遷移到了Git。網上有介紹使用git svn來進行遷移的,我試過最終因git svn clone無法正常執行完成失敗了。我也注意到有一款採用Ruby開發的svn2git工具,原理應該是基於git svn進行二次開發。因爲之前用git svn方法沒成功並且每次嘗試消耗的時間過長,就沒有再試這個工具。
我最終採用了svn-all-fast-export工具。這個工具基於libsvn-dev開發,是直接對SVN庫目錄進行操作。svn-all-fast-export需要Linux環境運行,我採用VirtualBox+Debian;Synaptic新立得包管理器已包含這個軟件,直接安裝即可。
先建個目錄svn2git,保存SVN庫目錄(Svn_CQClient和Svn_CQServer)及對應的轉換規則配置文件(cqclient.rules和cqserver.rules)。目錄結構如下:
~/svn2git
|-Svn_CQClient/
|-Svn_CQServer/
|-cqclient.rules
`-cqserver.rules
在製作rules文件之前,需要先梳理一下SVN庫的內容並規劃將來的Git庫。以CQServer庫爲例說明,先建立一個表格,有幾列:Git、branches、based on branch、tags和labels(因爲CQServer最早期用VSS做版本管理,所以VSS轉SVN時保留了labels;如果你的SVN庫沒有/labels就不需要labels列)。
1. 規劃Git庫名和分支名
打開TortoiseSVN Repository Browser,逐個查看/trunk和/branch目錄下每個項目的“Show log”輸出(設置“Stop on copy/rename”標記,點“Show All”顯示完整日誌),看看該項目最早的代碼是從其他項目派生的,還是直接添加入庫的,將項目路徑寫入branches列,將它的源項目路徑(如有)寫入base on branch。還可以使用TortoiseSVN的Revision Graph來查看,已刪除的項目路徑也要進行處理並特別標註。
不同源的項目之間無法跨項目應用merge操作,如果放在同一個Git庫會產生根節點不同的分支樹。所以不同源項目一般應歸屬不同的Git庫。
有時項目是從它的父目錄或子目錄複製過來的,比如某個項目之前在/trunk/保存了開發版本,後來把項目整個移到/trunk/Develop/目錄下,然後刪除了原父目錄下的文件。這樣的話可以將父目錄/trunk/和子目錄/trunk/Develop/規劃爲Git中的不同分支,並利用max revision規則(見下文)保留/trunk/刪除版本文件之前的樣子。
爲每一組同源項目分配一個Git庫名(第一列“Git”)。爲每個branches項分配一個Git分支名。
2. 規劃Git庫標籤
和branches類似,但是不需要確定分支名,可以使用通配符來標記。例如:
Git | tags | labels |
---|---|---|
CQServer | CQServer/([^/]+)/ CQServerRelease/([^/]+)/ | ([^/]+)/(Develop|Release)/ ([^/]+)/ |
CQModule | CQModule/([^/]+)/ | ([^/]+)/CQModule/ |
CQServer-x64 | CO3Server/([^/]+)/ CO3ServerCn/([^/]+)/ CQServerIpad/([^/]+)/ CQServerRelease-x64/([^/]+)/ CQServer-x64/([^/]+)/ |
這裏只是粗略地分一下,實際轉換時可能會發現某些tag跨不同Git庫導致錯誤的情況,就要編寫更細緻的規則把這些tag分配到不同Git庫或者直接忽略掉對應的commit記錄。
3. 編寫建庫規則
轉換配置文件的例子可參考https://github.com/svn-all-fast-export/svn2git/tree/master/samples。
create repository CQServer.git
end repository
把”CQServer.git”依次替換爲其他Git庫名(第一列“Git”)。
4. 編寫分支規則
根據第二列“branches”和取好的分支名轉換爲Git庫中的分支,例如:
match /trunk/Develop/repository CQServer.git
branch master
end match
將紅字部分依次替換爲之前規劃好的配置。注意:目錄名末尾必須加”/”字符。
分支規則的書寫順序很重要。轉換程序會逐條處理SVN庫的commit記錄,將commit記錄中每個文件路徑按match規則(”match”之後的正則表達式,採用Qt4 QRegExp)的順序逐條匹配。如果匹配成功就按照match/end match之間的配置進行處理(repository指定接收的Git庫,branch指定接收或新建的分支名)。如果匹配失敗則繼續嘗試下一條match規則。
這相當於將SVN的commit記錄的文件修改按照match規則去修改對應的Git庫和分支,並進行commit和push。
如果match匹配全部失敗,轉換程序會報告錯誤並退出,因此全部匹配失敗是不允許的。如果確實打算忽略某些commit記錄,可以在match/end match之間不寫任何語句。例如,在所有其他/trunk/的match規則的後面加上:
match /trunk/
end match
就可以忽略之前未被匹配到的/trunk/下的commit記錄。
5. 編寫labels和tags規則
例如:
match /labels/([^/]+)/(Develop|Release)/
repository CQServer.git
branch refs/tags/\1
annotated true
end match
根據規劃表格中的“tags”和“labels”列編寫,替換上面紅字的部分。“\1”和“\2”表示match規則中被圓括號對包圍的那部分內容,第一對圓括號內的內容對應\1,第二對對應\2。這個寫法也適用於之前處理/trunk/和/branches/的branch語句。
注意:如果tag不是從SVN庫中某個版本直接導出的,而是從外部複製版本目錄到/tags/下(類似於新建一個項目),這樣的tag無法在Git庫中溯源,只能忽略掉。例如:
match /tags/CQServerRelease/
min revision 1930
max revision 3220
end match
這樣的異常tag記錄有一個特徵:TortoiseSVN中顯示commit message的字體是黑色,與正常tag的藍色字體不同。
“min revision”和“max revision”除了一般的match匹配規則還針對SVN的revision編號進行匹配,如果匹配成功才處理,匹配不成功則繼續嘗試下一條match規則。“min revision”和“max revision”可以同時出現也可以單獨出現。min revision的編號不能大於max revision,否則可能會出現非預期的結果卻不報錯。這個辦法也可以用於處理SVN記錄中已刪除的項目路徑。
6. 逐條檢查SVN commit記錄中所有包含”Deleted”的記錄
主要注意刪除或重命名項目分支目錄或版本標籤的情況,這類情況一般需要在轉換配置文件中進行特殊處理。
在svn-all-fast-export處理後期會對tags進行Finalising處理,即使tag創建後被刪除。如果tag刪除之後沒有被重新創建,Finalising就會異常報錯,因此對於這樣的tag要編寫對應的match規則進行處理(比如忽略刪除tag的記錄,或同時忽略tag創建和刪除記錄)。Tag改名也有類似問題,改名以後相當於舊名被刪除了,但是轉換工具仍會試圖對舊名進行處理。
7. 用轉換命令處理SVN庫
export SVN2GITDIR=~/svn2git; cd $SVN2GITDIR; svn-all-fast-export --identity-domain nd --rules cqserver.rules Svn_CQServer 1>out.log 2>err.log
要看看err.log和log-*.git的日誌信息是否正常。如果程序正常結束,log-*.git日誌末尾都應該有一些程序運行統計信息,非正常結束的情況部分日誌文件沒有這部分信息。
有些特別大的SVN庫可能會出錯,可以加“--commit-interval=1”參數看看能否解決。
8. 後期檢查
對比TortoiseGit和TortoiseSVN版本分支圖(Revision Graph),檢查分支版本文件及日誌,看看是否有異常或非預期的結果。如果轉換結果有錯,可用以下命令刪除Git庫和轉換日誌,修改轉換配置文件後再重新轉換:
export SVN2GITDIR=~/svn2git; cd $SVN2GITDIR; rm -rf *.git *.log