原文:《Pro Git》
git 與其他系統(tǒng)
世界不是完美的。大多數(shù)時(shí)候,將所有接觸到的項(xiàng)目全部轉(zhuǎn)向 Git 是不可能的。有時(shí)我們不得不為某個(gè)項(xiàng)目使用其他的版本控制系統(tǒng)(VCS, Version Control System ),其中比較常見(jiàn)的是 Subversion 。你將在本章的第一部分學(xué)習(xí)使用git svn
,Git 為 Subversion 附帶的雙向橋接工具。(伯樂(lè)在線(xiàn)注:如果你對(duì)Git還不了解,建議從本Git系列第一篇文章開(kāi)始閱讀)
或許現(xiàn)在你已經(jīng)在考慮將先前的項(xiàng)目轉(zhuǎn)向 Git 。本章的第二部分將介紹如何將項(xiàng)目遷移到 Git:先介紹從 Subversion 的遷移,然后是 Perforce,最后介紹如何使用自定義的腳本進(jìn)行非標(biāo)準(zhǔn)的導(dǎo)入。
8.1 Git 與 Subversion
當(dāng)前,大多數(shù)開(kāi)發(fā)中的開(kāi)源項(xiàng)目以及大量的商業(yè)項(xiàng)目都使用 Subversion 來(lái)管理源碼。作為最流行的開(kāi)源版本控制系統(tǒng),Subversion 已經(jīng)存在了接近十年的時(shí)間。它在許多方面與 CVS 十分類(lèi)似,后者是前者出現(xiàn)之前代碼控制世界的霸主。
Git 最為重要的特性之一是名為 git svn
的 Subversion 雙向橋接工具。該工具把 Git 變成了 Subversion 服務(wù)的客戶(hù)端,從而讓你在本地享受到 Git 所有的功能,而后直接向 Subversion 服務(wù)器推送內(nèi)容,仿佛在本地使用了 Subversion 客戶(hù)端。也就是說(shuō),在其他人忍受古董的同時(shí),你可以在本地享受分支合并,使暫存區(qū)域,衍合以及 單項(xiàng)挑揀等等。這是個(gè)讓 Git 偷偷潛入合作開(kāi)發(fā)環(huán)境的好東西,在幫助你的開(kāi)發(fā)同伴們提高效率的同時(shí),它還能幫你勸說(shuō)團(tuán)隊(duì)讓整個(gè)項(xiàng)目框架轉(zhuǎn)向?qū)?Git 的支持。這個(gè) Subversion 之橋是通向分布式版本控制系統(tǒng)(DVCS, Distributed VCS )世界的神奇隧道。
git svn
Git 中所有 Subversion 橋接命令的基礎(chǔ)是 git svn
。所有的命令都從它開(kāi)始。相關(guān)的命令數(shù)目不少,你將通過(guò)幾個(gè)簡(jiǎn)單的工作流程了解到其中常見(jiàn)的一些。
值得警戒的是,在使用 git svn
的時(shí)候,你實(shí)際是在與 Subversion 交互,Git 比它要高級(jí)復(fù)雜的多。盡管可以在本地隨意的進(jìn)行分支和合并,最好還是通過(guò)衍合保持線(xiàn)性的提交歷史,盡量避免類(lèi)似與遠(yuǎn)程 Git 倉(cāng)庫(kù)動(dòng)態(tài)交互這樣的操作。
避免修改歷史再重新推送的做法,也不要同時(shí)推送到并行的 Git 倉(cāng)庫(kù)來(lái)試圖與其他 Git 用戶(hù)合作。Subersion 只能保存單一的線(xiàn)性提交歷史,一不小心就會(huì)被搞糊涂。合作團(tuán)隊(duì)中同時(shí)有人用 SVN 和 Git,一定要確保所有人都使用 SVN 服務(wù)來(lái)協(xié)作——這會(huì)讓生活輕松很多。
初始設(shè)定
為了展示功能,先要一個(gè)具有寫(xiě)權(quán)限的 SVN 倉(cāng)庫(kù)。如果想嘗試這個(gè)范例,你必須復(fù)制一份其中的測(cè)試倉(cāng)庫(kù)。比較簡(jiǎn)單的做法是使用一個(gè)名為 svnsync
的工具。較新的 Subversion 版本中都帶有該工具,它將數(shù)據(jù)編碼為用于網(wǎng)絡(luò)傳輸?shù)母袷健?/p>
要嘗試本例,先在本地新建一個(gè) Subversion 倉(cāng)庫(kù):
1 2 | $ mkdir /tmp/test-svn $ svnadmin create /tmp/test-svn |
然后,允許所有用戶(hù)修改 revprop —— 簡(jiǎn)單的做法是添加一個(gè)總是以 0 作為返回值的 pre-revprop-change 腳本:
1 2 3 4 | $ cat /tmp/test-svn/hooks/pre-revprop-change #!/bin/sh exit 0; $ chmod +x /tmp/test-svn/hooks/pre-revprop-change |
現(xiàn)在可以調(diào)用 svnsync init
加目標(biāo)倉(cāng)庫(kù),再加源倉(cāng)庫(kù)的格式來(lái)把該項(xiàng)目同步到本地了:
1 | $ svnsync init file : ///tmp/test-svn http: //progit-example .googlecode.com /svn/ |
這將建立進(jìn)行同步所需的屬性。可以通過(guò)運(yùn)行以下命令來(lái)克隆代碼:
1 2 3 4 5 6 7 | $ svnsync sync file : ///tmp/test-svn Committed revision 1. Copied properties for revision 1. Committed revision 2. Copied properties for revision 2. Committed revision 3. ... |
別看這個(gè)操作只花掉幾分鐘,要是你想把源倉(cāng)庫(kù)復(fù)制到另一個(gè)遠(yuǎn)程倉(cāng)庫(kù),而不是本地倉(cāng)庫(kù),那將花掉接近一個(gè)小時(shí),盡管項(xiàng)目中只有不到 100 次的提交。 Subversion 每次只復(fù)制一次修改,把它推送到另一個(gè)倉(cāng)庫(kù)里,然后周而復(fù)始——驚人的低效,但是我們別無(wú)選擇。
入門(mén)
有了可以寫(xiě)入的 Subversion 倉(cāng)庫(kù)以后,就可以嘗試一下典型的工作流程了。我們從 git svn clone
命令開(kāi)始,它會(huì)把整個(gè) Subversion 倉(cāng)庫(kù)導(dǎo)入到一個(gè)本地的 Git 倉(cāng)庫(kù)中。提醒一下,這里導(dǎo)入的是一個(gè)貨真價(jià)實(shí)的 Subversion 倉(cāng)庫(kù),所以應(yīng)該把下面的file:///tmp/test-svn
換成你所用的 Subversion 倉(cāng)庫(kù)的 URL:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | $ git svn clone file : ///tmp/test-svn -T trunk -b branches -t tags Initialized empty Git repository in /Users/schacon/projects/testsvnsync/svn/ .git/ r1 = b4e387bc68740b5af56c2a5faf4003ae42bd135c (trunk) A m4 /acx_pthread .m4 A m4 /stl_hash .m4 ... r75 = d1957f3b307922124eec6314e15bcda59e3d9610 (trunk) Found possible branch point: file : ///tmp/test-svn/trunk => \ file : ///tmp/test-svn /branches/my-calc-branch , 75 Found branch parent: (my-calc-branch) d1957f3b307922124eec6314e15bcda59e3d9610 Following parent with do_switch Successfully followed parent r76 = 8624824ecc0badd73f40ea2f01fce51894189b01 (my-calc-branch) Checked out HEAD: file : ///tmp/test-svn/branches/my-calc-branch r76 |
這相當(dāng)于針對(duì)所提供的 URL 運(yùn)行了兩條命令—— git svn init
加上 gitsvn fetch
。可能會(huì)花上一段時(shí)間。我們所用的測(cè)試項(xiàng)目?jī)H僅包含 75 次提交并且它的代碼量不算大,所以只有幾分鐘而已。不過(guò),Git 仍然需要提取每一個(gè)版本,每次一個(gè),再逐個(gè)提交。對(duì)于一個(gè)包含成百上千次提交的項(xiàng)目,花掉的時(shí)間則可能是幾小時(shí)甚至數(shù)天。
-T trunk -b branches -t tags
告訴 Git 該 Subversion 倉(cāng)庫(kù)遵循了基本的分支和標(biāo)簽命名法則。如果你的主干(譯注:trunk,相當(dāng)于非分布式版本控制里的master分支,代表開(kāi)發(fā)的主線(xiàn)),分支或者標(biāo)簽以不同的方式命名,則應(yīng)做出相應(yīng)改變。由于該法則的常見(jiàn)性,可以使用-s
來(lái)代替整條命令,它意味著標(biāo)準(zhǔn)布局(s 是 Standard layout 的首字母),也就是前面選項(xiàng)的內(nèi)容。下面的命令有相同的效果:
1 | $ git svn clone file : ///tmp/test-svn -s |
現(xiàn)在,你有了一個(gè)有效的 Git 倉(cāng)庫(kù),包含著導(dǎo)入的分支和標(biāo)簽:
1 2 3 4 5 6 7 8 | $ git branch -a * master my-calc-branch tags /2 .0.2 tags /release-2 .0.1 tags /release-2 .0.2 tags /release-2 .0.2rc1 trunk |
值得注意的是,該工具分配命名空間時(shí)和遠(yuǎn)程引用的方式不盡相同??寺∑胀ǖ?Git 倉(cāng)庫(kù)時(shí),可以以 origin/[branch]
的形式獲取遠(yuǎn)程服務(wù)器上所有可用的分支——分配到遠(yuǎn)程服務(wù)的名稱(chēng)下。然而git svn
假定不存在多個(gè)遠(yuǎn)程服務(wù)器,所以把所有指向遠(yuǎn)程服務(wù)的引用不加區(qū)分的保存下來(lái)。可以用 Git 探測(cè)命令 show-ref
來(lái)查看所有引用的全名。
1 2 3 4 5 6 7 8 | $ git show-ref 1cbd4904d9982f386d87f88fce1c24ad7c0f0471 refs /heads/master aee1ecc26318164f355a883f5d99cff0c852d3c4 refs /remotes/my-calc-branch 03d09b0e2aad427e34a6d50ff147128e76c0e0f5 refs /remotes/tags/2 .0.2 50d02cc0adc9da4319eeba0900430ba219b9c376 refs /remotes/tags/release-2 .0.1 4caaa711a50c77879a91b8b90380060f672745cb refs /remotes/tags/release-2 .0.2 1c4cb508144c513ff1214c3488abe66dcb92916f refs /remotes/tags/release-2 .0.2rc1 1cbd4904d9982f386d87f88fce1c24ad7c0f0471 refs /remotes/trunk |
而普通的 Git 倉(cāng)庫(kù)應(yīng)該是這個(gè)模樣:
1 2 3 4 5 | $ git show-ref 83e38c7a0af325a9722f2fdc56b10188806d83a1 refs /heads/master 3e15e38c198baac84223acfc6224bb8b99ff2281 refs /remotes/gitserver/master 0a30dd3b0c795b80212ae723640d4e5d48cabdff refs /remotes/origin/master 25812380387fdd55f916652be4881c6f11600d6f refs /remotes/origin/testing |
這里有兩個(gè)遠(yuǎn)程服務(wù)器:一個(gè)名為 gitserver
,具有一個(gè) master
分支;另一個(gè)叫 origin
,具有 master
和 testing
兩個(gè)分支。
注意本例中通過(guò) git svn
導(dǎo)入的遠(yuǎn)程引用,(Subversion 的)標(biāo)簽是當(dāng)作遠(yuǎn)程分支添加的,而不是真正的 Git 標(biāo)簽。導(dǎo)入的 Subversion 倉(cāng)庫(kù)仿佛是有一個(gè)帶有不同分支的 tags 遠(yuǎn)程服務(wù)器。
提交到 Subversion
有了可以開(kāi)展工作的(本地)倉(cāng)庫(kù)以后,你可以開(kāi)始對(duì)該項(xiàng)目做出貢獻(xiàn)并向上游倉(cāng)庫(kù)提交內(nèi)容了,Git 這時(shí)相當(dāng)于一個(gè) SVN 客戶(hù)端。假如編輯了一個(gè)文件并進(jìn)行提交,那么這次提交僅存在于本地的 Git 而非 Subversion 服務(wù)器上。
1 2 3 | $ git commit -am 'Adding git-svn instructions to the README' [master 97031e5] Adding git-svn instructions to the README 1 files changed, 1 insertions(+), 1 deletions(-) |
接下來(lái),可以將作出的修改推送到上游。值得注意的是,Subversion 的使用流程也因此改變了——你可以在離線(xiàn)狀態(tài)下進(jìn)行多次提交然后一次性的推送到 Subversion 的服務(wù)器上。向 Subversion 服務(wù)器推送的命令是git svn dcommit
:
1 2 3 4 5 6 7 8 | $ git svn dcommit Committing to file : ///tmp/test-svn/trunk ... M README.txt Committed r79 M README.txt r79 = 938b1a547c2cc92033b74d32030e86468294a5c8 (trunk) No changes between current HEAD and refs /remotes/trunk Resetting to the latest refs /remotes/trunk |
所有在原 Subversion 數(shù)據(jù)基礎(chǔ)上提交的 commit 會(huì)一一提交到 Subversion,然后你本地 Git 的 commit 將被重寫(xiě),加入一個(gè)特別標(biāo)識(shí)。這一步很重要,因?yàn)樗馕吨?commit 的 SHA-1 指都會(huì)發(fā)生變化。這也是同時(shí)使用 Git 和 Subversion 兩種服務(wù)作為遠(yuǎn)程服務(wù)不是個(gè)好主意的原因之一。檢視以下最后一個(gè) commit,你會(huì)找到新添加的git-svn-id
(譯注:即本段開(kāi)頭所說(shuō)的特別標(biāo)識(shí)):
1 2 3 4 5 6 7 8 | $ git log -1 commit 938b1a547c2cc92033b74d32030e86468294a5c8 Author: schacon <schacon@4c93b258-373f-11de-be05-5f7a86268029> Date: Sat May 2 22:06:44 2009 +0000 Adding git-svn instructions to the README git-svn- id : file : ///tmp/test-svn/trunk @79 4c93b258-373f-11de-be05-5f7a86268029 |
注意看,原本以 97031e5
開(kāi)頭的 SHA-1 校驗(yàn)值在提交完成以后變成了 938b1a5
。如果既要向 Git 遠(yuǎn)程服務(wù)器推送內(nèi)容,又要推送到 Subversion 遠(yuǎn)程服務(wù)器,則必須先向 Subversion 推送(dcommit
),因?yàn)樵摬僮鲿?huì)改變所提交的數(shù)據(jù)內(nèi)容。
拉取最新進(jìn)展
如果要與其他開(kāi)發(fā)者協(xié)作,總有那么一天你推送完畢之后,其他人發(fā)現(xiàn)他們推送自己修改的時(shí)候(與你推送的內(nèi)容)產(chǎn)生沖突。這些修改在你合并之前將一直被拒絕。在 git svn
里這種情況形似:
1 2 3 4 5 | $ git svn dcommit Committing to file : ///tmp/test-svn/trunk ... Merge conflict during commit: Your file or directory 'README.txt' is probably \ out-of- date : resource out of date ; try updating at /Users/schacon/libexec/git- \ core /git-svn line 482 |
為了解決該問(wèn)題,可以運(yùn)行 git svn rebase
,它會(huì)拉取服務(wù)器上所有最新的改變,再次基礎(chǔ)上衍合你的修改:
1 2 3 4 5 | $ git svn rebase M README.txt r80 = ff829ab914e8775c7c025d741beb3d523ee30bc4 (trunk) First, rewinding head to replay your work on top of it... Applying: first user change |
現(xiàn)在,你做出的修改都發(fā)生在服務(wù)器內(nèi)容之后,所以可以順利的運(yùn)行 dcommit
:
1 2 3 4 5 6 7 8 | $ git svn dcommit Committing to file : ///tmp/test-svn/trunk ... M README.txt Committed r81 M README.txt r81 = 456cbe6337abe49154db70106d1836bc1332deed (trunk) No changes between current HEAD and refs /remotes/trunk Resetting to the latest refs /remotes/trunk |
需要牢記的一點(diǎn)是,Git 要求我們?cè)谕扑椭跋群喜⑸嫌蝹}(cāng)庫(kù)中最新的內(nèi)容,而 git svn
只要求存在沖突的時(shí)候才這樣做。假如有人向一個(gè)文件推送了一些修改,這時(shí)你要向另一個(gè)文件推送一些修改,那么dcommit
將正常工作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | $ git svn dcommit Committing to file : ///tmp/test-svn/trunk ... M configure.ac Committed r84 M autogen.sh r83 = 8aa54a74d452f82eee10076ab2584c1fc424853b (trunk) M configure.ac r84 = cdbac939211ccb18aa744e581e46563af5d962d0 (trunk) W: d2f23b80f67aaaa1f6f5aaef48fce3263ac71a92 and refs /remotes/trunk differ, \ using rebase: :100755 100755 efa5a59965fbbb5b2b0a12890f1b351bb5493c18 \ 015e4c98c482f0fa71e4d5434338014530b37fa6 M autogen.sh First, rewinding head to replay your work on top of it... Nothing to do . |
這一點(diǎn)需要牢記,因?yàn)樗慕Y(jié)果是推送之后項(xiàng)目處于一個(gè)不完整存在與任何主機(jī)上的狀態(tài)。如果做出的修改無(wú)法兼容但沒(méi)有產(chǎn)生沖突,則可能造成一些很難確診的難題。這和使用 Git 服務(wù)器是不同的——在 Git 世界里,發(fā)布之前,你可以在客戶(hù)端系統(tǒng)里完整的測(cè)試項(xiàng)目的狀態(tài),而在 SVN 永遠(yuǎn)都沒(méi)法確保提交前后項(xiàng)目的狀態(tài)完全一樣。
及時(shí)還沒(méi)打算進(jìn)行提交,你也應(yīng)該用這個(gè)命令從 Subversion 服務(wù)器拉取最新修改。sit svn fetch
能獲取最新的數(shù)據(jù),不過(guò)git svn rebase
才會(huì)在獲取之后在本地進(jìn)行更新 。
1 2 3 4 5 | $ git svn rebase M generate_descriptor_proto.sh r82 = bd16df9173e424c6f52c337ab6efa7f7643282f1 (trunk) First, rewinding head to replay your work on top of it... Fast-forwarded master to refs /remotes/trunk . |
不時(shí)地運(yùn)行一下 git svn rebase
可以確保你的代碼沒(méi)有過(guò)時(shí)。不過(guò),運(yùn)行該命令時(shí)需要確保工作目錄的整潔。如果在本地做了修改,則必須在運(yùn)行git svn rebase
之前或暫存工作,或暫時(shí)提交內(nèi)容——否則,該命令會(huì)發(fā)現(xiàn)衍合的結(jié)果包含著沖突因而終止。
Git 分支問(wèn)題
習(xí)慣了 Git 的工作流程以后,你可能會(huì)創(chuàng)建一些特性分支,完成相關(guān)的開(kāi)發(fā)工作,然后合并他們。如果要用 git svn 向 Subversion 推送內(nèi)容,那么最好是每次用衍合來(lái)并入一個(gè)單一分支,而不是直接合并。使用衍合的原因是 Subversion 只有一個(gè)線(xiàn)性的歷史而不像 Git 那樣處理合并,所以 Git svn 在把快照轉(zhuǎn)換為 Subversion 的 commit 時(shí)只能包含第一個(gè)祖先。
假設(shè)分支歷史如下:創(chuàng)建一個(gè) experiment
分支,進(jìn)行兩次提交,然后合并到 master
。在 dcommit
的時(shí)候會(huì)得到如下輸出:
1 2 3 4 5 | $ git svn rebase M generate_descriptor_proto.sh r82 = bd16df9173e424c6f52c337ab6efa7f7643282f1 (trunk) First, rewinding head to replay your work on top of it... Fast-forwarded master to refs /remotes/trunk . |
在一個(gè)包含了合并歷史的分支上使用 dcommit
可以成功運(yùn)行,不過(guò)在 Git 項(xiàng)目的歷史中,它沒(méi)有重寫(xiě)你在 experiment
分支中的兩個(gè) commit ——另一方面,這些改變卻出現(xiàn)在了 SVN 版本中同一個(gè)合并 commit 中。
在別人克隆該項(xiàng)目的時(shí)候,只能看到這個(gè)合并 commit 包含了所有發(fā)生過(guò)的修改;他們無(wú)法獲知修改的作者和時(shí)間等提交信息。
Subversion 分支
Subversion 的分支和 Git 中的不盡相同;避免過(guò)多的使用可能是最好方案。不過(guò),用 git svn 創(chuàng)建和提交不同的 Subversion 分支仍是可行的。
創(chuàng)建新的 SVN 分支
要在 Subversion 中建立一個(gè)新分支,需要運(yùn)行 git svn branch [分支名]
To create a new branch in Subversion, you rungit svn branch [branchname]
:
1 2 3 4 5 6 7 8 | $ git svn branch opera Copying file : ///tmp/test-svn/trunk at r87 to file : ///tmp/test-svn/branches/opera ... Found possible branch point: file : ///tmp/test-svn/trunk => \ file : ///tmp/test-svn/branches/opera , 87 Found branch parent: (opera) 1f6bfe471083cbca06ac8d4176f7ad4de0d62e5f Following parent with do_switch Successfully followed parent r89 = 9b6fe0b90c5c9adf9165f700897518dbc54a7cbf (opera) |
相當(dāng)于在 Subversion 中的 svn copy trunk branches/opera
命令并且對(duì) Subversion 服務(wù)器進(jìn)行了相關(guān)操作。值得提醒的是它沒(méi)有檢出和轉(zhuǎn)換到那個(gè)分支;如果現(xiàn)在進(jìn)行提交,將提交到服務(wù)器上的trunk
, 而非 opera
。
切換當(dāng)前分支
Git 通過(guò)搜尋提交歷史中 Subversion 分支的頭部來(lái)決定 dcommit 的目的地——而它應(yīng)該只有一個(gè),那就是當(dāng)前分支歷史中最近一次包含 git-svn-id
的提交。
如果需要同時(shí)在多個(gè)分支上提交,可以通過(guò)導(dǎo)入 Subversion 上某個(gè)其他分支的 commit 來(lái)建立以該分支為 dcommit
目的地的本地分支。比如你想擁有一個(gè)并行維護(hù)的opera
分支,可以運(yùn)行
1 | $ git branch opera remotes /opera |
然后,如果要把 opera
分支并入 trunk
(本地的 master
分支),可以使用普通的git merge
。不過(guò)最好提供一條描述提交的信息(通過(guò) -m
),否則這次合并的記錄是 Merge branch opera
,而不是任何有用的東西。
記住,雖然使用了 git merge
來(lái)進(jìn)行這次操作,并且合并過(guò)程可能比使用 Subversion 簡(jiǎn)單一些(因?yàn)?Git 會(huì)自動(dòng)找到適合的合并基礎(chǔ)),這并不是一次普通的 Git 合并提交。最終它將被推送回 commit 無(wú)法包含多個(gè)祖先的 Subversion 服務(wù)器上;因而在推送之后,它將變成一個(gè)包含了所有在其他分支上做出的改變的單一 commit。把一個(gè)分支合并到另一個(gè)分支以后,你沒(méi)法像在 Git 中那樣輕易的回到那個(gè)分支上繼續(xù)工作。提交時(shí)運(yùn)行的dcommit
命令擦除了全部有關(guān)哪個(gè)分支被并入的信息,因而以后的合并基礎(chǔ)計(jì)算將是不正確的—— dcommit 讓 git merge
的結(jié)果變得類(lèi)似于git merge --squash
。不幸的是,我們沒(méi)有什么好辦法來(lái)避免該情況—— Subversion 無(wú)法儲(chǔ)存這個(gè)信息,所以在使用它作為服務(wù)器的時(shí)候你將永遠(yuǎn)為這個(gè)缺陷所困。為了不出現(xiàn)這種問(wèn)題,在把本地分支(本例中的opera
)并入 trunk 以后應(yīng)該立即將其刪除。
對(duì)應(yīng) Subversion 的命令
git svn
工具集合了若干個(gè)與 Subversion 類(lèi)似的功能,對(duì)應(yīng)的命令可以簡(jiǎn)化向 Git 的轉(zhuǎn)化過(guò)程。下面這些命令能實(shí)現(xiàn) Subversion 的這些功能。
SVN 風(fēng)格的歷史
習(xí)慣了 Subversion 的人可能想以 SVN 的風(fēng)格顯示歷史,運(yùn)行 git svn log
可以讓提交歷史顯示為 SVN 格式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | $ git svn log ------------------------------------------------------------------------ r87 | schacon | 2009-05-02 16:07:37 -0700 (Sat, 02 May 2009) | 2 lines autogen change ------------------------------------------------------------------------ r86 | schacon | 2009-05-02 16:00:21 -0700 (Sat, 02 May 2009) | 2 lines Merge branch 'experiment' ------------------------------------------------------------------------ r85 | schacon | 2009-05-02 16:00:09 -0700 (Sat, 02 May 2009) | 2 lines updated the changelog |
關(guān)于 git svn log
,有兩點(diǎn)需要注意。首先,它可以離線(xiàn)工作,不像 svn log
命令,需要向 Subversion 服務(wù)器索取數(shù)據(jù)。其次,它僅僅顯示已經(jīng)提交到 Subversion 服務(wù)器上的 commit。在本地尚未 dcommit 的 Git 數(shù)據(jù)不會(huì)出現(xiàn)在這里;其他人向 Subversion 服務(wù)器新提交的數(shù)據(jù)也不會(huì)顯示。等于說(shuō)是顯示了最近已知 Subversion 服務(wù)器上的狀態(tài)。
SVN 日志
類(lèi)似 git svn log
對(duì) git log
的模擬,svn annotate
的等效命令是git svn blame [文件名]
。其輸出如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | $ git svn blame README.txt 2 temporal Protocol Buffers - Google's data interchange format 2 temporal Copyright 2008 Google Inc. 2 temporal http: //code .google.com /apis/protocolbuffers/ 2 temporal 22 temporal C++ Installation - Unix 22 temporal ======================= 2 temporal 79 schacon Committing in git-svn. 78 schacon 2 temporal To build and install the C++ Protocol Buffer runtime and the Protocol 2 temporal Buffer compiler (protoc) execute the following: 2 temporal |
同樣,它不顯示本地的 Git 提交以及 Subversion 上后來(lái)更新的內(nèi)容。
SVN 服務(wù)器信息
還可以使用 git svn info
來(lái)獲取與運(yùn)行 svn info
類(lèi)似的信息:
1 2 3 4 5 6 7 8 9 10 11 | $ git svn info Path: . URL: https: //schacon-test .googlecode.com /svn/trunk Repository Root: https: //schacon-test .googlecode.com /svn Repository UUID: 4c93b258-373f-11de-be05-5f7a86268029 Revision: 87 Node Kind: directory Schedule: normal Last Changed Author: schacon Last Changed Rev: 87 Last Changed Date: 2009-05-02 16:07:37 -0700 (Sat, 02 May 2009) |
它與 blame
和 log
的相同點(diǎn)在于離線(xiàn)運(yùn)行以及只更新到最后一次與 Subversion 服務(wù)器通信的狀態(tài)。
略 Subversion 之所略
假如克隆了一個(gè)包含了 svn:ignore
屬性的 Subversion 倉(cāng)庫(kù),就有必要建立對(duì)應(yīng)的 .gitignore
文件來(lái)防止意外提交一些不應(yīng)該提交的文件。git svn
有兩個(gè)有益于改善該問(wèn)題的命令。第一個(gè)是git svn create-ignore
,它自動(dòng)建立對(duì)應(yīng)的.gitignore
文件,以便下次提交的時(shí)候可以包含它。
第二個(gè)命令是 git svn show-ignore
,它把需要放進(jìn) .gitignore
文件中的內(nèi)容打印到標(biāo)準(zhǔn)輸出,方便我們把輸出重定向到項(xiàng)目的黑名單文件:
1 | $ git svn show-ignore > .git /info/exclude |
這樣一來(lái),避免了 .gitignore
對(duì)項(xiàng)目的干擾。如果你是一個(gè) Subversion 團(tuán)隊(duì)里唯一的 Git 用戶(hù),而其他隊(duì)友不喜歡項(xiàng)目包含.gitignore
,該方法是你的不二之選。
Git-Svn 總結(jié)
git svn
工具集在當(dāng)前不得不使用 Subversion 服務(wù)器或者開(kāi)發(fā)環(huán)境要求使用 Subversion 服務(wù)器的時(shí)候格外有用。不妨把它看成一個(gè)跛腳的 Git,然而,你還是有可能在轉(zhuǎn)換過(guò)程中碰到一些困惑你和合作者們的迷題。為了避免麻煩,試著遵守如下守則:
● 保持一個(gè)不包含由 git merge
生成的 commit 的線(xiàn)性提交歷史。將在主線(xiàn)分支外進(jìn)行的開(kāi)發(fā)通通衍合回主線(xiàn);避免直接合并。
● 不要單獨(dú)建立和使用一個(gè) Git 服務(wù)來(lái)搞合作。可以為了加速新開(kāi)發(fā)者的克隆進(jìn)程建立一個(gè),但是不要向它提供任何不包含 git-svn-id
條目的內(nèi)容。甚至可以添加一個(gè)pre-receive
掛鉤來(lái)在每一個(gè)提交信息中查找 git-svn-id
并拒絕提交那些不包含它的 commit。
如果遵循這些守則,在 Subversion 上工作還可以接受。然而,如果能遷徙到真正的 Git 服務(wù)器,則能為團(tuán)隊(duì)帶來(lái)更多好處。
8.2 遷移到 Git
如果在其他版本控制系統(tǒng)中保存了某項(xiàng)目的代碼而后決定轉(zhuǎn)而使用 Git,那么該項(xiàng)目必須經(jīng)歷某種形式的遷移。本節(jié)將介紹 Git 中包含的一些針對(duì)常見(jiàn)系統(tǒng)的導(dǎo)入腳本,并將展示編寫(xiě)自定義的導(dǎo)入腳本的方法。
導(dǎo)入
你將學(xué)習(xí)到如何從專(zhuān)業(yè)重量級(jí)的版本控制系統(tǒng)中導(dǎo)入數(shù)據(jù)—— Subversion 和 Perforce —— 因?yàn)閾?jù)我所知這二者的用戶(hù)是(向 Git)轉(zhuǎn)換的主要群體,而且 Git 為此二者附帶了高質(zhì)量的轉(zhuǎn)換工具。
Subversion
讀過(guò)前一節(jié)有關(guān) git svn
的內(nèi)容以后,你應(yīng)該能輕而易舉的根據(jù)其中的指導(dǎo)來(lái) git svn clone
一個(gè)倉(cāng)庫(kù)了;然后,停止 Subversion 的使用,向一個(gè)新 Git server 推送,并開(kāi)始使用它。想保留歷史記錄,所花的時(shí)間應(yīng)該不過(guò)就是從 Subversion 服務(wù)器拉取數(shù)據(jù)的時(shí)間(可能要等上好一會(huì)就是了)。
然而,這樣的導(dǎo)入并不完美;而且還要花那么多時(shí)間,不如干脆一次把它做對(duì)!首當(dāng)其沖的任務(wù)是作者信息。在 Subversion,每個(gè)提交者在都在主機(jī)上有一個(gè)用戶(hù)名,記錄在提交信息中。上節(jié)例子中多處顯示了schacon
,比如 blame
的輸出以及 git svn log
。如果想讓這條信息更好的映射到 Git 作者數(shù)據(jù)里,則需要 從 Subversion 用戶(hù)名到 Git 作者的一個(gè)映射關(guān)系。建立一個(gè)叫做user.txt
的文件,用如下格式表示映射關(guān)系:
1 2 | schacon = Scott Chacon <schacon@ geemail.com> selse = Someo Nelse <selse@ geemail.com> |
通過(guò)該命令可以獲得 SVN 作者的列表:
1 | $ svn log --xml | grep author | sort -u | perl -pe 's/.>(.?)<./$1 = /' |
它將輸出 XML 格式的日志——你可以找到作者,建立一個(gè)單獨(dú)的列表,然后從 XML 中抽取出需要的信息。(顯而易見(jiàn),本方法要求主機(jī)上安裝了grep
,sort
和perl
.)然后把輸出重定向到 user.txt 文件,然后就可以在每一項(xiàng)的后面添加相應(yīng)的 Git 用戶(hù)數(shù)據(jù)。
為 git svn
提供該文件可以然它更精確的映射作者數(shù)據(jù)。你還可以在 clone
或者 init
后面添加--no-metadata
來(lái)阻止 git svn
包含那些 Subversion 的附加信息。這樣 import
命令就變成了:
1 2 | $ git-svn clone http: //my-project .googlecode.com /svn/ \ --authors- file = users .txt --no-metadata -s my_project |
現(xiàn)在 my_project
目錄下導(dǎo)入的 Subversion 應(yīng)該比原來(lái)整潔多了。原來(lái)的 commit 看上去是這樣:
1 2 3 4 5 6 7 8 | commit 37efa680e8473b615de980fa935944215428a35a Author: schacon <schacon@4c93b258-373f-11de-be05-5f7a86268029> Date: Sun May 3 00:12:22 2009 +0000 fixed install - go to trunk git-svn- id : https: //my-project .googlecode.com /svn/trunk @94 4c93b258-373f-11de- be05-5f7a86268029 |
現(xiàn)在是這樣:
1 2 3 4 5 | commit 03a8785f44c8ea5cdb0e8834b7c8e6c469be2ff2 Author: Scott Chacon <schacon@ geemail.com> Date: Sun May 3 00:12:22 2009 +0000 fixed install - go to trunk |
不僅作者一項(xiàng)干凈了不少,git-svn-id
也就此消失了。
你還需要一點(diǎn) post-import(導(dǎo)入后)
清理工作。最起碼的,應(yīng)該清理一下 git svn
創(chuàng)建的那些怪異的索引結(jié)構(gòu)。首先要移動(dòng)標(biāo)簽,把它們從奇怪的遠(yuǎn)程分支變成實(shí)際的標(biāo)簽,然后把剩下的分支移動(dòng)到本地。
要把標(biāo)簽變成合適的 Git 標(biāo)簽,運(yùn)行
1 2 | $ cp -Rf .git /refs/remotes/tags/ * .git /refs/tags/ $ rm -Rf .git /refs/remotes/tags |
該命令將原本以 tag/
開(kāi)頭的遠(yuǎn)程分支的索引變成真正的(輕巧的)標(biāo)簽。
接下來(lái),把 refs/remotes
下面剩下的索引變成本地分支:
1 2 | $ cp -Rf .git /refs/remotes/ * .git /refs/heads/ $ rm -Rf .git /refs/remotes |
現(xiàn)在所有的舊分支都變成真正的 Git 分支,所有的舊標(biāo)簽也變成真正的 Git 標(biāo)簽。最后一項(xiàng)工作就是把新建的 Git 服務(wù)器添加為遠(yuǎn)程服務(wù)器并且向它推送。下面是新增遠(yuǎn)程服務(wù)器的例子:
1 | $ git remote add origin git@my-git-server:myrepository.git |
為了讓所有的分支和標(biāo)簽都得到上傳,我們使用這條命令:
1 | $ git push origin --all |
所有的分支和標(biāo)簽現(xiàn)在都應(yīng)該整齊干凈的躺在新的 Git 服務(wù)器里了。
Perforce
你將了解到的下一個(gè)被導(dǎo)入的系統(tǒng)是 Perforce. Git 發(fā)行的時(shí)候同時(shí)也附帶了一個(gè) Perforce 導(dǎo)入腳本,不過(guò)它是包含在源碼的 contrib
部分——而不像git svn
那樣默認(rèn)可用。運(yùn)行它之前必須獲取 Git 的源碼,可以在 git.kernel.org 下載:
1 2 | $ git clone git: //git .kernel.org /pub/scm/git/git .git $ cd git /contrib/fast-import |
在這個(gè) fast-import
目錄下,應(yīng)該有一個(gè)叫做 git-p4
的 Python 可執(zhí)行腳本。主機(jī)上必須裝有 Python 和p4
工具該導(dǎo)入才能正常進(jìn)行。例如,你要從 Perforce 公共代碼倉(cāng)庫(kù)(譯注: Perforce Public Depot,Perforce 官方提供的代碼寄存服務(wù))導(dǎo)入 Jam 工程。為了設(shè)定客戶(hù)端,我們要把 P4PORT 環(huán)境變量 export 到 Perforce 倉(cāng)庫(kù):
1 | $ export P4PORT=public.perforce.com:1666 |
運(yùn)行 git-p4 clone
命令將從 Perforce 服務(wù)器導(dǎo)入 Jam 項(xiàng)目,我們需要給出倉(cāng)庫(kù)和項(xiàng)目的路徑以及導(dǎo)入的目標(biāo)路徑:
1 2 3 4 5 | $ git-p4 clone //public/jam/src @all /opt/p4import Importing from //public/jam/src @all into /opt/p4import Reinitialized existing Git repository in /opt/p4import/ .git/ Import destination: refs /remotes/p4/master Importing revision 4409 (100%) |
現(xiàn)在去 /opt/p4import
目錄運(yùn)行一下 git log
,就能看到導(dǎo)入的成果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | $ git log -2 commit 1fd4ec126171790efd2db83548b85b1bbbc07dc2 Author: Perforce staff <support@ perforce.com> Date: Thu Aug 19 10:18:45 2004 -0800 Drop 'rc3' moniker of jam-2.5. Folded rc2 and rc3 RELNOTES into the main part of the document. Built new tar /zip balls. Only 16 months later. [git-p4: depot-paths = "http://public/jam/src/" : change = 4409] commit ca8870db541a23ed867f38847eda65bf4363371d Author: Richard Geiger <rmg@ perforce.com> Date: Tue Apr 22 20:51:34 2003 -0800 Update derived jamgram.c [git-p4: depot-paths = "http://public/jam/src/" : change = 3108] |
每一個(gè) commit 里都有一個(gè) git-p4
標(biāo)識(shí)符。這個(gè)標(biāo)識(shí)符可以保留,以防以后需要引用 Perforce 的修改版本號(hào)。然而,如果想刪除這些標(biāo)識(shí)符,現(xiàn)在正是時(shí)候——在開(kāi)啟新倉(cāng)庫(kù)之前。可以通過(guò)git filter-branch
來(lái)批量刪除這些標(biāo)識(shí)符:
1 2 3 4 5 | $ git filter-branch --msg-filter ' sed -e "/^\[git-p4:/d" ' Rewrite 1fd4ec126171790efd2db83548b85b1bbbc07dc2 ( 123 / 123 ) Ref 'refs/heads/master' was rewritten |
現(xiàn)在運(yùn)行一下 git log
,你會(huì)發(fā)現(xiàn)這些 commit 的 SHA-1 校驗(yàn)值都發(fā)生了改變,而那些 git-p4
字串則從提交信息里消失了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | $ git log - 2 commit 10a16d60cffca14d454a15c6164378f4082bc5b0 Author: Perforce staff <support@ perforce.com> Date : Thu Aug 19 10 : 18 : 45 2004 - 0800 Drop 'rc3' moniker of jam- 2.5 . Folded rc2 and rc3 RELNOTES into the main part of the document. Built new tar/zip balls. Only 16 months later. commit 2b6c6db311dd76c34c66ec1c40a49405e6b527b2 Author: Richard Geiger <rmg@ perforce.com> Date : Tue Apr 22 20 : 51 : 34 2003 - 0800 Update derived jamgram.c |
至此導(dǎo)入已經(jīng)完成,可以開(kāi)始向新的 Git 服務(wù)器推送了。
自定導(dǎo)入腳本
如果先前的系統(tǒng)不是 Subversion 或 Perforce 之一,先上網(wǎng)找一下有沒(méi)有與之對(duì)應(yīng)的導(dǎo)入腳本——導(dǎo)入 CVS,Clear Case,Visual Source Safe,甚至存檔目錄的導(dǎo)入腳本已經(jīng)存在。假如這些工具都不適用,或者使用的工具很少見(jiàn),抑或你需要導(dǎo)入過(guò)程具有更多可制定性,則應(yīng)該使用git fast-import
。該命令從標(biāo)準(zhǔn)輸入讀取簡(jiǎn)單的指令來(lái)寫(xiě)入具體的 Git 數(shù)據(jù)。這樣創(chuàng)建 Git 對(duì)象比運(yùn)行純 Git 命令或者手動(dòng)寫(xiě)對(duì)象要簡(jiǎn)單的多(更多相關(guān)內(nèi)容見(jiàn)第九章)。通過(guò)它,你可以編寫(xiě)一個(gè)導(dǎo)入腳本來(lái)從導(dǎo)入源讀取必要的信息,同時(shí)在標(biāo)準(zhǔn)輸出直接輸出相關(guān)指示。你可以運(yùn)行該腳本并把它的輸出管道連接到git fast-import
。
下面演示一下如何編寫(xiě)一個(gè)簡(jiǎn)單的導(dǎo)入腳本。假設(shè)你在進(jìn)行一項(xiàng)工作,并且按時(shí)通過(guò)把工作目錄復(fù)制為以時(shí)間戳back_YY_MM_DD
命名的目錄來(lái)進(jìn)行備份,現(xiàn)在你需要把它們導(dǎo)入 Git 。目錄結(jié)構(gòu)如下:
1 2 3 4 5 6 | $ ls /opt/import_from back_2009_01_02 back_2009_01_04 back_2009_01_14 back_2009_02_03 current |
為了導(dǎo)入到一個(gè) Git 目錄,我們首先回顧一下 Git 儲(chǔ)存數(shù)據(jù)的方式。你可能還記得,Git 本質(zhì)上是一個(gè) commit 對(duì)象的鏈表,每一個(gè)對(duì)象指向一個(gè)內(nèi)容的快照。而這里需要做的工作就是告訴fast-import
內(nèi)容快照的位置,什么樣的 commit 數(shù)據(jù)指向它們,以及它們的順序。我們采取一次處理一個(gè)快照的策略,為每一個(gè)內(nèi)容目錄建立對(duì)應(yīng)的 commit ,每一個(gè) commit 與之前的建立鏈接。
正如在第七章 “Git 執(zhí)行策略一例” 一節(jié)中一樣,我們將使用 Ruby 來(lái)編寫(xiě)這個(gè)腳本,因?yàn)樗俏胰粘J褂玫恼Z(yǔ)言而且閱讀起來(lái)簡(jiǎn)單一些。你可以用任何其他熟悉的語(yǔ)言來(lái)重寫(xiě)這個(gè)例子——它僅需要把必要的信息打印到標(biāo)準(zhǔn)輸出而已。同時(shí),如果你在使用 Windows,這意味著你要特別留意不要在換行的時(shí)候引入回車(chē)符(譯注:carriage returns,Windows 換行時(shí)加入的符號(hào),通常說(shuō)的\r
)—— Git 的 fast-import 對(duì)僅使用換行符(LF)而非 Windows 的回車(chē)符(CRLF)要求非常嚴(yán)格。
首先,進(jìn)入目標(biāo)目錄并且找到所有子目錄,每一個(gè)子目錄將作為一個(gè)快照被導(dǎo)入為一個(gè) commit。我們將依次進(jìn)入每一個(gè)子目錄并打印所需的命令來(lái)導(dǎo)出它們。腳本的主循環(huán)大致是這樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 | last_mark = nil # 循環(huán)遍歷所有目錄 Dir.chdir(ARGV[ 0 ]) do Dir.glob( "*" ). each do |dir| next if File.file?(dir) # 進(jìn)入目標(biāo)目錄 Dir.chdir(dir) do last_mark = print_export(dir, last_mark) end end end |
我們?cè)诿恳粋€(gè)目錄里運(yùn)行 print_export ,它會(huì)取出上一個(gè)快照的索引和標(biāo)記并返回本次快照的索引和標(biāo)記;由此我們就可以正確的把二者連接起來(lái)。”標(biāo)記(mark)” 是fast-import<中對(duì) commit 標(biāo)識(shí)符的叫法;在創(chuàng)建 commit 的同時(shí),我們逐一賦予一個(gè)標(biāo)記以便以后在把它連接到其他 commit 時(shí)使用。因此,在print_export方法中要做的第一件事就是根據(jù)目錄名生成一個(gè)標(biāo)記:
1 | mark = convert_dir_to_mark(dir) |
實(shí)現(xiàn)該函數(shù)的方法是建立一個(gè)目錄的數(shù)組序列并使用數(shù)組的索引值作為標(biāo)記,因?yàn)闃?biāo)記必須是一個(gè)整數(shù)。這個(gè)方法大致是這樣的:
1 2 3 4 5 6 7 | $marks = [] def convert_dir_to_mark(dir) if !$marks. include ?(dir) $marks << dir end ($marks.index(dir) + 1 ).to_s end |
有了整數(shù)來(lái)代表每個(gè) commit,我們現(xiàn)在需要提交附加信息中的日期。由于日期是用目錄名表示的,我們就從中解析出來(lái)。print_export
文件的下一行將是:
1 | date = convert_dir_to_date ( dir ) |
而 convert_dir_to_date
則定義為
1 2 3 4 5 6 7 8 9 | def convert_dir_to_date( dir ) if dir == 'current' return Time.now().to_i else dir = dir .gsub( 'back_' , '' ) (year, month, day) = dir . split ( '_' ) return Time. local (year, month, day).to_i end end |
它為每個(gè)目錄返回一個(gè)整型值。提交附加信息里最后一項(xiàng)所需的是提交者數(shù)據(jù),我們?cè)谝粋€(gè)全局變量中直接定義之:
1 | $author = 'Scott Chacon <schacon@ example.com>' |
我們差不多可以開(kāi)始為導(dǎo)入腳本輸出提交數(shù)據(jù)了。第一項(xiàng)信息指明我們定義的是一個(gè) commit 對(duì)象以及它所在的分支,隨后是我們生成的標(biāo)記,提交者信息以及提交備注,然后是前一個(gè) commit 的索引,如果有的話(huà)。代碼大致這樣:
1 2 3 4 5 6 | # 打印導(dǎo)入所需的信息 puts 'commit refs/heads/master' puts 'mark :' + mark puts "committer #{$author} #{date} -0700" export_data( 'imported from ' + dir) puts 'from :' + last_mark if last_mark |
時(shí)區(qū)(-0700)處于簡(jiǎn)化目的使用硬編碼。如果是從其他版本控制系統(tǒng)導(dǎo)入,則必須以變量的形式指明時(shí)區(qū)。 提交備注必須以特定格式給出:
1 | data (size)\n(contents) |
該格式包含了單詞 data,所讀取數(shù)據(jù)的大小,一個(gè)換行符,最后是數(shù)據(jù)本身。由于隨后指明文件內(nèi)容的時(shí)候要用到相同的格式,我們寫(xiě)一個(gè)輔助方法,export_data
:
1 2 3 | def export_data ( string ) print "data #{string.size}\n#{string}" end |
唯一剩下的就是每一個(gè)快照的內(nèi)容了。這簡(jiǎn)單的很,因?yàn)樗鼈兎謩e處于一個(gè)目錄——你可以輸出 deleeall
命令,隨后是目錄中每個(gè)文件的內(nèi)容。Git 會(huì)正確的記錄每一個(gè)快照:
1 2 3 4 | puts 'deleteall' Dir.glob( "**/*" ).each do | file | next if !File. file ?( file ) inline_data( file ) end |
注意:由于很多系統(tǒng)把每次修訂看作一個(gè) commit 到另一個(gè) commit 的變化量,fast-import 也可以依據(jù)每次提交獲取一個(gè)命令來(lái)指出哪些文件被添加,刪除或者修改過(guò),以及修改的內(nèi)容。我們將需要計(jì)算快照之間的差別并且僅僅給出這項(xiàng)數(shù)據(jù),不過(guò)該做法要復(fù)雜很多——還如不直接把所有數(shù)據(jù)丟給 Git 然它自己搞清楚。假如前面這個(gè)方法更適用于你的數(shù)據(jù),參考fast-import
的 man 幫助頁(yè)面來(lái)了解如何以這種方式提供數(shù)據(jù)。
列舉新文件內(nèi)容或者指明帶有新內(nèi)容的已修改文件的格式如下:
1 2 3 | M 644 inline path/to/file data (size) (file contents) |
這里,644 是權(quán)限模式(加入有可執(zhí)行文件,則需要探測(cè)之并設(shè)定為 755),而 inline 說(shuō)明我們?cè)诒拘薪Y(jié)束之后立即列出文件的內(nèi)容。我們的 inline_data
方法大致是:
1 2 3 4 5 | def inline_data(file, code = 'M' , mode = '644' ) content = File.read(file) puts "#{code} #{mode} inline #{file}" export_data(content) end |
我們重用了前面定義過(guò)的 export_data
,因?yàn)檫@里和指明提交注釋的格式如出一轍。
最后一項(xiàng)工作是返回當(dāng)前的標(biāo)記以便下次循環(huán)的使用。
1 | return mark |
注意:如果你在用 Windows,一定記得添加一項(xiàng)額外的步驟。前面提過(guò),Windows 使用 CRLF 作為換行字符而 Git fast-import 只接受 LF。為了繞開(kāi)這個(gè)問(wèn)題來(lái)滿(mǎn)足 git fast-import,你需要讓 ruby 用 LF 取代 CRLF:
1 | $stdout.binmode |
搞定了?,F(xiàn)在運(yùn)行該腳本,你將得到如下內(nèi)容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | $ ruby import .rb /opt/import_from commit refs/heads/master mark : 1 committer Scott Chacon <schacon@ geemail.com> 1230883200 - 0700 data 29 imported from back_2009_01_02deleteall M 644 inline file.rb data 12 version two commit refs/heads/master mark : 2 committer Scott Chacon <schacon@ geemail.com> 1231056000 - 0700 data 29 imported from back_2009_01_04from : 1 deleteall M 644 inline file.rb data 14 version three M 644 inline new .rb data 16 new version one (...) |
要運(yùn)行導(dǎo)入腳本,在需要導(dǎo)入的目錄把該內(nèi)容用管道定向到 git fast-import
。你可以建立一個(gè)空目錄然后運(yùn)行 git init
作為開(kāi)頭,然后運(yùn)行該腳本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | $ git init Initialized empty Git repository in /opt/import_to/.git/ $ ruby import .rb /opt/import_from | git fast- import git-fast- import statistics: --------------------------------------------------------------------- Alloc'd objects: 5000 Total objects: 18 ( 1 duplicates ) blobs : 7 ( 1 duplicates 0 deltas) trees : 6 ( 0 duplicates 1 deltas) commits: 5 ( 0 duplicates 0 deltas) tags : 0 ( 0 duplicates 0 deltas) Total branches: 1 ( 1 loads ) marks: 1024 ( 5 unique ) atoms: 3 Memory total: 2255 KiB pools: 2098 KiB objects: 156 KiB --------------------------------------------------------------------- pack_report: getpagesize() = 4096 pack_report: core.packedGitWindowSize = 33554432 pack_report: core.packedGitLimit = 268435456 pack_report: pack_used_ctr = 9 pack_report: pack_mmap_calls = 5 pack_report: pack_open_windows = 1 / 1 pack_report: pack_mapped = 1356 / 1356 --------------------------------------------------------------------- |
你會(huì)發(fā)現(xiàn),在它成功執(zhí)行完畢以后,會(huì)給出一堆有關(guān)已完成工作的數(shù)據(jù)。上例在一個(gè)分支導(dǎo)入了5次提交數(shù)據(jù),包含了18個(gè)對(duì)象。現(xiàn)在可以運(yùn)行 git log
來(lái)檢視新的歷史:
1 2 3 4 5 6 7 8 9 10 11 12 | $ git log - 2 commit 10bfe7d22ce15ee25b60a824c8982157ca593d41 Author: Scott Chacon <schacon@ example.com> Date : Sun May 3 12 : 57 : 39 2009 - 0700 imported from current commit 7e519590de754d079dd73b44d695a42c9d2df452 Author: Scott Chacon <schacon@ example.com> Date : Tue Feb 3 01 : 00 : 00 2009 - 0700 imported from back_2009_02_03 |
就它了——一個(gè)干凈整潔的 Git 倉(cāng)庫(kù)。需要注意的是此時(shí)沒(méi)有任何內(nèi)容被檢出——?jiǎng)傞_(kāi)始當(dāng)前目錄里沒(méi)有任何文件。要獲取它們,你得轉(zhuǎn)到 master
分支的所在:
1 2 3 4 5 | $ ls $ git reset --hard master HEAD is now at 10bfe7d imported from current $ ls file .rb lib |
fast-import
還可以做更多——處理不同的文件模式,二進(jìn)制文件,多重分支與合并,標(biāo)簽,進(jìn)展標(biāo)識(shí)等等。一些更加復(fù)雜的實(shí)例可以在 Git 源碼的contib/fast-import
目錄里找到;其中較為出眾的是前面提過(guò)的 git-p4
腳本。
8.3 總結(jié)
現(xiàn)在的你應(yīng)該掌握了在 Subversion 上使用 Git 以及把幾乎任何先存?zhèn)}庫(kù)無(wú)損失的導(dǎo)入為 Git 倉(cāng)庫(kù)。下一章將介紹 Git 內(nèi)部的原始數(shù)據(jù)格式,從而是使你能親手鍛造其中的每一個(gè)字節(jié),如果必要的話(huà)。
聯(lián)系客服