鍍金池/ 教程/ Java/ Git 與其他系統(tǒng)
起步
Git 分支
自定義 Git
Git 基礎(chǔ)
Git 工具
Git 與其他系統(tǒng)
服務(wù)器上的 Git
Git 內(nèi)部原理
分布式 Git

Git 與其他系統(tǒng)

世界不是完美的。大多數(shù)時(shí)候,將所有接觸到的項(xiàng)目全部轉(zhuǎn)向 Git 是不可能的。有時(shí)我們不得不為某個(gè)項(xiàng)目使用其他的版本控制系統(tǒng)(VCS, Version Control System ),其中比較常見的是 Subversion 。你將在本章的第一部分學(xué)習(xí)使用 git svn ,Git 為 Subversion 附帶的雙向橋接工具。

或許現(xiàn)在你已經(jīng)在考慮將先前的項(xiàng)目轉(zhuǎn)向 Git 。本章的第二部分將介紹如何將項(xiàng)目遷移到 Git:先介紹從 Subversion 的遷移,然后是 Perforce,最后介紹如何使用自定義的腳本進(jìn)行非標(biāo)準(zhǔn)的導(dǎo)入。

Git 與 Subversion

當(dāng)前,大多數(shù)開發(fā)中的開源項(xiàng)目以及大量的商業(yè)項(xiàng)目都使用 Subversion 來管理源碼。作為最流行的開源版本控制系統(tǒng),Subversion 已經(jīng)存在了接近十年的時(shí)間。它在許多方面與 CVS 十分類似,后者是前者出現(xiàn)之前代碼控制世界的霸主。

Git 最為重要的特性之一是名為 git svn 的 Subversion 雙向橋接工具。該工具把 Git 變成了 Subversion 服務(wù)的客戶端,從而讓你在本地享受到 Git 所有的功能,而后直接向 Subversion 服務(wù)器推送內(nèi)容,仿佛在本地使用了 Subversion 客戶端。也就是說,在其他人忍受古董的同時(shí),你可以在本地享受分支合并,使暫存區(qū)域,衍合以及 單項(xiàng)挑揀等等。這是個(gè)讓 Git 偷偷潛入合作開發(fā)環(huán)境的好東西,在幫助你的開發(fā)同伴們提高效率的同時(shí),它還能幫你勸說團(tuán)隊(duì)讓整個(gè)項(xiàng)目框架轉(zhuǎn)向?qū)?Git 的支持。這個(gè) Subversion 之橋是通向分布式版本控制系統(tǒng)(DVCS, Distributed VCS )世界的神奇隧道。

git svn

Git 中所有 Subversion 橋接命令的基礎(chǔ)是 git svn 。所有的命令都從它開始。相關(guān)的命令數(shù)目不少,你將通過幾個(gè)簡單的工作流程了解到其中常見的一些。

值得警戒的是,在使用 git svn 的時(shí)候,你實(shí)際是在與 Subversion 交互,Git 比它要高級(jí)復(fù)雜的多。盡管可以在本地隨意的進(jìn)行分支和合并,最好還是通過衍合保持線性的提交歷史,盡量避免類似與遠(yuǎn)程 Git 倉庫動(dòng)態(tài)交互這樣的操作。

避免修改歷史再重新推送的做法,也不要同時(shí)推送到并行的 Git 倉庫來試圖與其他 Git 用戶合作。Subersion 只能保存單一的線性提交歷史,一不小心就會(huì)被搞糊涂。合作團(tuán)隊(duì)中同時(shí)有人用 SVN 和 Git,一定要確保所有人都使用 SVN 服務(wù)來協(xié)作——這會(huì)讓生活輕松很多。

初始設(shè)定

為了展示功能,先要一個(gè)具有寫權(quán)限的 SVN 倉庫。如果想嘗試這個(gè)范例,你必須復(fù)制一份其中的測(cè)試倉庫。比較簡單的做法是使用一個(gè)名為 svnsync 的工具。較新的 Subversion 版本中都帶有該工具,它將數(shù)據(jù)編碼為用于網(wǎng)絡(luò)傳輸?shù)母袷健?/p>

要嘗試本例,先在本地新建一個(gè) Subversion 倉庫:

$ mkdir /tmp/test-svn
$ svnadmin create /tmp/test-svn

然后,允許所有用戶修改 revprop —— 簡單的做法是添加一個(gè)總是以 0 作為返回值的 pre-revprop-change 腳本:

$ 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)倉庫,再加源倉庫的格式來把該項(xiàng)目同步到本地了:

$ svnsync init file:///tmp/test-svn http://progit-example.googlecode.com/svn/

這將建立進(jìn)行同步所需的屬性??梢酝ㄟ^運(yùn)行以下命令來克隆代碼:

$ 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è)操作只花掉幾分鐘,要是你想把源倉庫復(fù)制到另一個(gè)遠(yuǎn)程倉庫,而不是本地倉庫,那將花掉接近一個(gè)小時(shí),盡管項(xiàng)目中只有不到 100 次的提交。 Subversion 每次只復(fù)制一次修改,把它推送到另一個(gè)倉庫里,然后周而復(fù)始——驚人的低效,但是我們別無選擇。

入門

有了可以寫入的 Subversion 倉庫以后,就可以嘗試一下典型的工作流程了。我們從 git svn clone 命令開始,它會(huì)把整個(gè) Subversion 倉庫導(dǎo)入到一個(gè)本地的 Git 倉庫中。提醒一下,這里導(dǎo)入的是一個(gè)貨真價(jià)實(shí)的 Subversion 倉庫,所以應(yīng)該把下面的 file:///tmp/test-svn 換成你所用的 Subversion 倉庫的 URL:

$ 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 加上 git svn fetch ??赡軙?huì)花上一段時(shí)間。我們所用的測(cè)試項(xiàng)目僅僅包含 75 次提交并且它的代碼量不算大,所以只有幾分鐘而已。不過,Git 仍然需要提取每一個(gè)版本,每次一個(gè),再逐個(gè)提交。對(duì)于一個(gè)包含成百上千次提交的項(xiàng)目,花掉的時(shí)間則可能是幾小時(shí)甚至數(shù)天。

-T trunk -b branches -t tags 告訴 Git 該 Subversion 倉庫遵循了基本的分支和標(biāo)簽命名法則。如果你的主干(譯注:trunk,相當(dāng)于非分布式版本控制里的master分支,代表開發(fā)的主線),分支或者標(biāo)簽以不同的方式命名,則應(yīng)做出相應(yīng)改變。由于該法則的常見性,可以使用 -s 來代替整條命令,它意味著標(biāo)準(zhǔn)布局(s 是 Standard layout 的首字母),也就是前面選項(xiàng)的內(nèi)容。下面的命令有相同的效果:

$ git svn clone file:///tmp/test-svn -s

現(xiàn)在,你有了一個(gè)有效的 Git 倉庫,包含著導(dǎo)入的分支和標(biāo)簽:

$ 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 倉庫時(shí),可以以 origin/[branch] 的形式獲取遠(yuǎn)程服務(wù)器上所有可用的分支——分配到遠(yuǎn)程服務(wù)的名稱下。然而 git svn 假定不存在多個(gè)遠(yuǎn)程服務(wù)器,所以把所有指向遠(yuǎn)程服務(wù)的引用不加區(qū)分的保存下來??梢杂?Git 探測(cè)命令 show-ref 來查看所有引用的全名。

$ 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 倉庫應(yīng)該是這個(gè)模樣:

$ 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,具有 mastertesting 兩個(gè)分支。

注意本例中通過 git svn 導(dǎo)入的遠(yuǎn)程引用,(Subversion 的)標(biāo)簽是當(dāng)作遠(yuǎn)程分支添加的,而不是真正的 Git 標(biāo)簽。導(dǎo)入的 Subversion 倉庫仿佛是有一個(gè)帶有不同分支的 tags 遠(yuǎn)程服務(wù)器。

提交到 Subversion

有了可以開展工作的(本地)倉庫以后,你可以開始對(duì)該項(xiàng)目做出貢獻(xiàn)并向上游倉庫提交內(nèi)容了,Git 這時(shí)相當(dāng)于一個(gè) SVN 客戶端。假如編輯了一個(gè)文件并進(jìn)行提交,那么這次提交僅存在于本地的 Git 而非 Subversion 服務(wù)器上。

$ 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(-)

接下來,可以將作出的修改推送到上游。值得注意的是,Subversion 的使用流程也因此改變了——你可以在離線狀態(tài)下進(jìn)行多次提交然后一次性的推送到 Subversion 的服務(wù)器上。向 Subversion 服務(wù)器推送的命令是 git svn dcommit

$ 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 將被重寫,加入一個(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 (譯注:即本段開頭所說的特別標(biāo)識(shí)):

$ 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 開頭的 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)展

如果要與其他開發(fā)者協(xié)作,總有那么一天你推送完畢之后,其他人發(fā)現(xiàn)他們推送自己修改的時(shí)候(與你推送的內(nèi)容)產(chǎn)生沖突。這些修改在你合并之前將一直被拒絕。在 git svn 里這種情況形似:

$ 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

為了解決該問題,可以運(yùn)行 git svn rebase ,它會(huì)拉取服務(wù)器上所有最新的改變,再次基礎(chǔ)上衍合你的修改:

$ 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

$ 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è)谕扑椭跋群喜⑸嫌蝹}庫中最新的內(nèi)容,而 git svn 只要求存在沖突的時(shí)候才這樣做。假如有人向一個(gè)文件推送了一些修改,這時(shí)你要向另一個(gè)文件推送一些修改,那么 dcommit 將正常工作:

$ 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)。如果做出的修改無法兼容但沒有產(chǎn)生沖突,則可能造成一些很難確診的難題。這和使用 Git 服務(wù)器是不同的——在 Git 世界里,發(fā)布之前,你可以在客戶端系統(tǒng)里完整的測(cè)試項(xiàng)目的狀態(tài),而在 SVN 永遠(yuǎn)都沒法確保提交前后項(xiàng)目的狀態(tài)完全一樣。

即使還沒打算進(jìn)行提交,你也應(yīng)該用這個(gè)命令從 Subversion 服務(wù)器拉取最新修改。sit svn fetch 能獲取最新的數(shù)據(jù),不過 git svn rebase 才會(huì)在獲取之后在本地進(jìn)行更新 。

$ 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 可以確保你的代碼沒有過時(shí)。不過,運(yùn)行該命令時(shí)需要確保工作目錄的整潔。如果在本地做了修改,則必須在運(yùn)行 git svn rebase 之前或暫存工作,或暫時(shí)提交內(nèi)容——否則,該命令會(huì)發(fā)現(xiàn)衍合的結(jié)果包含著沖突因而終止。

Git 分支問題

習(xí)慣了 Git 的工作流程以后,你可能會(huì)創(chuàng)建一些特性分支,完成相關(guān)的開發(fā)工作,然后合并他們。如果要用 git svn 向 Subversion 推送內(nèi)容,那么最好是每次用衍合來并入一個(gè)單一分支,而不是直接合并。使用衍合的原因是 Subversion 只有一個(gè)線性的歷史而不像 Git 那樣處理合并,所以 git svn 在把快照轉(zhuǎn)換為 Subversion 的 commit 時(shí)只能包含第一個(gè)祖先。

假設(shè)分支歷史如下:創(chuàng)建一個(gè) experiment 分支,進(jìn)行兩次提交,然后合并到 master 。在 dcommit 的時(shí)候會(huì)得到如下輸出:

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
       M      CHANGES.txt
Committed r85
       M      CHANGES.txt
r85 = 4bfebeec434d156c36f2bcd18f4e3d97dc3269a2 (trunk)
No changes between current HEAD and refs/remotes/trunk
Resetting to the latest refs/remotes/trunk
COPYING.txt: locally modified
INSTALL.txt: locally modified
       M      COPYING.txt
       M      INSTALL.txt
Committed r86
       M      INSTALL.txt
       M      COPYING.txt
r86 = 2647f6b86ccfcaad4ec58c520e369ec81f7c283c (trunk)
No changes between current HEAD and refs/remotes/trunk
Resetting to the latest refs/remotes/trunk

在一個(gè)包含了合并歷史的分支上使用 dcommit 可以成功運(yùn)行,不過在 Git 項(xiàng)目的歷史中,它沒有重寫你在 experiment 分支中的兩個(gè) commit ——另一方面,這些改變卻出現(xiàn)在了 SVN 版本中同一個(gè)合并 commit 中。

在別人克隆該項(xiàng)目的時(shí)候,只能看到這個(gè)合并 commit 包含了所有發(fā)生過的修改;他們無法獲知修改的作者和時(shí)間等提交信息。

Subversion 分支

Subversion 的分支和 Git 中的不盡相同;避免過多的使用可能是最好方案。不過,用 git svn 創(chuàng)建和提交不同的 Subversion 分支仍是可行的。

創(chuàng)建新的 SVN 分支

要在 Subversion 中建立一個(gè)新分支,需要運(yùn)行 git svn branch [分支名]

$ 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 命令,并會(huì)對(duì) Subversion 服務(wù)器進(jìn)行相關(guān)操作。值得注意的是它沒有檢出和轉(zhuǎn)換到那個(gè)分支;如果現(xiàn)在進(jìn)行提交,將提交到服務(wù)器上的 trunk, 而非 opera。

切換當(dāng)前分支

Git 通過搜尋提交歷史中 Subversion 分支的頭部來決定 dcommit 的目的地——而它應(yīng)該只有一個(gè),那就是當(dāng)前分支歷史中最近一次包含 git-svn-id 的提交。

如果需要同時(shí)在多個(gè)分支上提交,可以通過導(dǎo)入 Subversion 上某個(gè)其他分支的 commit 來建立以該分支為 dcommit 目的地的本地分支。比如你想擁有一個(gè)并行維護(hù)的 opera 分支,可以運(yùn)行

$ git branch opera remotes/opera

然后,如果要把 opera 分支并入 trunk (本地的 master 分支),可以使用普通的 git merge。不過最好提供一條描述提交的信息(通過 -m),否則這次合并的記錄是 Merge branch opera ,而不是任何有用的東西。

記住,雖然使用了 git merge 來進(jìn)行這次操作,并且合并過程可能比使用 Subversion 簡單一些(因?yàn)?Git 會(huì)自動(dòng)找到適合的合并基礎(chǔ)),這并不是一次普通的 Git 合并提交。最終它將被推送回 commit 無法包含多個(gè)祖先的 Subversion 服務(wù)器上;因而在推送之后,它將變成一個(gè)包含了所有在其他分支上做出的改變的單一 commit。把一個(gè)分支合并到另一個(gè)分支以后,你沒法像在 Git 中那樣輕易的回到那個(gè)分支上繼續(xù)工作。提交時(shí)運(yùn)行的 dcommit 命令擦除了全部有關(guān)哪個(gè)分支被并入的信息,因而以后的合并基礎(chǔ)計(jì)算將是不正確的—— dcommit 讓 git merge 的結(jié)果變得類似于 git merge --squash。不幸的是,我們沒有什么好辦法來避免該情況—— Subversion 無法儲(chǔ)存這個(gè)信息,所以在使用它作為服務(wù)器的時(shí)候你將永遠(yuǎn)為這個(gè)缺陷所困。為了不出現(xiàn)這種問題,在把本地分支(本例中的 opera)并入 trunk 以后應(yīng)該立即將其刪除。

對(duì)應(yīng) Subversion 的命令

git svn 工具集合了若干個(gè)與 Subversion 類似的功能,對(duì)應(yīng)的命令可以簡化向 Git 的轉(zhuǎn)化過程。下面這些命令能實(shí)現(xiàn) Subversion 的這些功能。

SVN 風(fēng)格的歷史

習(xí)慣了 Subversion 的人可能想以 SVN 的風(fēng)格顯示歷史,運(yùn)行 git svn log 可以讓提交歷史顯示為 SVN 格式:

$ 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)需要注意。首先,它可以離線工作,不像 svn log 命令,需要向 Subversion 服務(wù)器索取數(shù)據(jù)。其次,它僅僅顯示已經(jīng)提交到 Subversion 服務(wù)器上的 commit。在本地尚未 dcommit 的 Git 數(shù)據(jù)不會(huì)出現(xiàn)在這里;其他人向 Subversion 服務(wù)器新提交的數(shù)據(jù)也不會(huì)顯示。等于說是顯示了最近已知 Subversion 服務(wù)器上的狀態(tài)。

SVN 日志

類似 git svn log 對(duì) git log 的模擬,svn annotate 的等效命令是 git svn blame [文件名]。其輸出如下:

$ 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 上后來更新的內(nèi)容。

SVN 服務(wù)器信息

還可以使用 git svn info 來獲取與運(yùn)行 svn info 類似的信息:

$ 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)

它與 blamelog 的相同點(diǎn)在于離線運(yùn)行以及只更新到最后一次與 Subversion 服務(wù)器通信的狀態(tài)。

略 Subversion 之所略

假如克隆了一個(gè)包含了 svn:ignore 屬性的 Subversion 倉庫,就有必要建立對(duì)應(yīng)的 .gitignore 文件來防止意外提交一些不應(yīng)該提交的文件。git svn 有兩個(gè)有益于改善該問題的命令。第一個(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)目的黑名單文件:

$ git svn show-ignore > .git/info/exclude

這樣一來,避免了 .gitignore 對(duì)項(xiàng)目的干擾。如果你是一個(gè) Subversion 團(tuán)隊(duì)里唯一的 Git 用戶,而其他隊(duì)友不喜歡項(xiàng)目包含 .gitignore,該方法是你的不二之選。

Git-Svn 總結(jié)

git svn 工具集在當(dāng)前不得不使用 Subversion 服務(wù)器或者開發(fā)環(huán)境要求使用 Subversion 服務(wù)器的時(shí)候格外有用。不妨把它看成一個(gè)跛腳的 Git,然而,你還是有可能在轉(zhuǎn)換過程中碰到一些困惑你和合作者們的迷題。為了避免麻煩,試著遵守如下守則:

  • 保持一個(gè)不包含由 git merge 生成的 commit 的線性提交歷史。將在主線分支外進(jìn)行的開發(fā)通通衍合回主線;避免直接合并。
  • 不要單獨(dú)建立和使用一個(gè) Git 服務(wù)來搞合作??梢詾榱思铀傩麻_發(fā)者的克隆進(jìn)程建立一個(gè),但是不要向它提供任何不包含 git-svn-id 條目的內(nèi)容。甚至可以添加一個(gè) pre-receive 掛鉤來在每一個(gè)提交信息中查找 git-svn-id 并拒絕提交那些不包含它的 commit。

如果遵循這些守則,在 Subversion 上工作還可以接受。然而,如果能遷徙到真正的 Git 服務(wù)器,則能為團(tuán)隊(duì)帶來更多好處。

遷移到 Git

如果在其他版本控制系統(tǒng)中保存了某項(xiàng)目的代碼而后決定轉(zhuǎn)而使用 Git,那么該項(xiàng)目必須經(jīng)歷某種形式的遷移。本節(jié)將介紹 Git 中包含的一些針對(duì)常見系統(tǒng)的導(dǎo)入腳本,并將展示編寫自定義的導(dǎo)入腳本的方法。

導(dǎo)入

你將學(xué)習(xí)到如何從專業(yè)重量級(jí)的版本控制系統(tǒng)中導(dǎo)入數(shù)據(jù)—— Subversion 和 Perforce —— 因?yàn)閾?jù)我所知這二者的用戶是(向 Git)轉(zhuǎn)換的主要群體,而且 Git 為此二者附帶了高質(zhì)量的轉(zhuǎn)換工具。

Subversion

讀過前一節(jié)有關(guān) git svn 的內(nèi)容以后,你應(yīng)該能輕而易舉的根據(jù)其中的指導(dǎo)來 git svn clone 一個(gè)倉庫了;然后,停止 Subversion 的使用,向一個(gè)新 Git server 推送,并開始使用它。想保留歷史記錄,所花的時(shí)間應(yīng)該不過就是從 Subversion 服務(wù)器拉取數(shù)據(jù)的時(shí)間(可能要等上好一會(huì)就是了)。

然而,這樣的導(dǎo)入并不完美;而且還要花那么多時(shí)間,不如干脆一次把它做對(duì)!首當(dāng)其沖的任務(wù)是作者信息。在 Subversion,每個(gè)提交者在都在主機(jī)上有一個(gè)用戶名,記錄在提交信息中。上節(jié)例子中多處顯示了 schacon ,比如 blame 的輸出以及 git svn log。如果想讓這條信息更好的映射到 Git 作者數(shù)據(jù)里,則需要 從 Subversion 用戶名到 Git 作者的一個(gè)映射關(guān)系。建立一個(gè)叫做 user.txt 的文件,用如下格式表示映射關(guān)系:

schacon = Scott Chacon <schacon@geemail.com>
selse = Someo Nelse <selse@geemail.com>

通過該命令可以獲得 SVN 作者的列表:

$ svn log ^/ --xml | grep -P "^<author" | sort -u | \
      perl -pe 's/<author>(.*?)<\/author>/$1 = /' > users.txt

它將輸出 XML 格式的日志——你可以找到作者,建立一個(gè)單獨(dú)的列表,然后從 XML 中抽取出需要的信息。(顯而易見,本方法要求主機(jī)上安裝了grep,sortperl.)然后把輸出重定向到 user.txt 文件,然后就可以在每一項(xiàng)的后面添加相應(yīng)的 Git 用戶數(shù)據(jù)。

git svn 提供該文件可以然它更精確的映射作者數(shù)據(jù)。你還可以在 clone 或者 init后面添加 --no-metadata 來阻止 git svn 包含那些 Subversion 的附加信息。這樣 import 命令就變成了:

$ 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)該比原來整潔多了。原來的 commit 看上去是這樣:

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)在是這樣:

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)行

$ git for-each-ref refs/remotes/tags | cut -d / -f 4- | grep -v @ | while read tagname; do git tag "$tagname" "tags/$tagname"; git branch -r -d "tags/$tagname"; done

該命令將原本以 tag/ 開頭的遠(yuǎn)程分支的索引變成真正的(輕巧的)標(biāo)簽。

接下來,把 refs/remotes 下面剩下的索引變成本地分支:

$ git for-each-ref refs/remotes | cut -d / -f 3- | grep -v @ | while read branchname; do git branch "$branchname" "refs/remotes/$branchname"; git branch -r -d "$branchname"; done

現(xiàn)在所有的舊分支都變成真正的 Git 分支,所有的舊標(biāo)簽也變成真正的 Git 標(biāo)簽。最后一項(xiàng)工作就是把新建的 Git 服務(wù)器添加為遠(yuǎn)程服務(wù)器并且向它推送。下面是新增遠(yuǎn)程服務(wù)器的例子:

$ git remote add origin git@my-git-server:myrepository.git

為了讓所有的分支和標(biāo)簽都得到上傳,我們使用這條命令:

$ git push origin --all
$ git push origin --tags

所有的分支和標(biāo)簽現(xiàn)在都應(yīng)該整齊干凈的躺在新的 Git 服務(wù)器里了。

Perforce

你將了解到的下一個(gè)被導(dǎo)入的系統(tǒng)是 Perforce. Git 發(fā)行的時(shí)候同時(shí)也附帶了一個(gè) Perforce 導(dǎo)入腳本,不過它是包含在源碼的 contrib 部分——而不像 git svn 那樣默認(rèn)可用。運(yùn)行它之前必須獲取 Git 的源碼,可以在 git.kernel.org 下載:

$ 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 公共代碼倉庫(譯注: Perforce Public Depot,Perforce 官方提供的代碼寄存服務(wù))導(dǎo)入 Jam 工程。為了設(shè)定客戶端,我們要把 P4PORT 環(huán)境變量 export 到 Perforce 倉庫:

$ export P4PORT=public.perforce.com:1666

運(yùn)行 git-p4 clone 命令將從 Perforce 服務(wù)器導(dǎo)入 Jam 項(xiàng)目,我們需要給出倉庫和項(xiàng)目的路徑以及導(dǎo)入的目標(biāo)路徑:

$ 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)入的成果:

$ 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í)候——在開啟新倉庫之前。可以通過 git filter-branch 來批量刪除這些標(biāo)識(shí)符:

$ 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 字串則從提交信息里消失了:

$ 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)完成,可以開始向新的 Git 服務(wù)器推送了。

自定導(dǎo)入腳本

如果先前的系統(tǒng)不是 Subversion 或 Perforce 之一,先上網(wǎng)找一下有沒有與之對(duì)應(yīng)的導(dǎo)入腳本——導(dǎo)入 CVS,Clear Case,Visual Source Safe,甚至存檔目錄的導(dǎo)入腳本已經(jīng)存在。假如這些工具都不適用,或者使用的工具很少見,抑或你需要導(dǎo)入過程具有更多可制定性,則應(yīng)該使用 git fast-import。該命令從標(biāo)準(zhǔn)輸入讀取簡單的指令來寫入具體的 Git 數(shù)據(jù)。這樣創(chuàng)建 Git 對(duì)象比運(yùn)行純 Git 命令或者手動(dòng)寫對(duì)象要簡單的多(更多相關(guān)內(nèi)容見第九章)。通過它,你可以編寫一個(gè)導(dǎo)入腳本來從導(dǎo)入源讀取必要的信息,同時(shí)在標(biāo)準(zhǔn)輸出直接輸出相關(guān)指示。你可以運(yùn)行該腳本并把它的輸出管道連接到 git fast-import。

下面演示一下如何編寫一個(gè)簡單的導(dǎo)入腳本。假設(shè)你在進(jìn)行一項(xiàng)工作,并且按時(shí)通過把工作目錄復(fù)制為以時(shí)間戳 back_YY_MM_DD 命名的目錄來進(jìn)行備份,現(xiàn)在你需要把它們導(dǎo)入 Git 。目錄結(jié)構(gòu)如下:

$ 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 來編寫這個(gè)腳本,因?yàn)樗俏胰粘J褂玫恼Z言而且閱讀起來簡單一些。你可以用任何其他熟悉的語言來重寫這個(gè)例子——它僅需要把必要的信息打印到標(biāo)準(zhǔn)輸出而已。同時(shí),如果你在使用 Windows,這意味著你要特別留意不要在換行的時(shí)候引入回車符(譯注:carriage returns,Windows 換行時(shí)加入的符號(hào),通常說的 \r )—— git fast-import 對(duì)僅使用換行符(LF)而非 Windows 的回車符(CRLF)要求非常嚴(yán)格。

首先,進(jìn)入目標(biāo)目錄并且找到所有子目錄,每一個(gè)子目錄將作為一個(gè)快照被導(dǎo)入為一個(gè) commit。我們將依次進(jìn)入每一個(gè)子目錄并打印所需的命令來導(dǎo)出它們。腳本的主循環(huán)大致是這樣:

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)記;由此我們就可以正確的把二者連接起來。"標(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)記:

mark = convert_dir_to_mark(dir)

實(shí)現(xiàn)該函數(shù)的方法是建立一個(gè)目錄的數(shù)組序列并使用數(shù)組的索引值作為標(biāo)記,因?yàn)闃?biāo)記必須是一個(gè)整數(shù)。這個(gè)方法大致是這樣的:

$marks = []
def convert_dir_to_mark(dir)
  if !$marks.include?(dir)
    $marks << dir
  end
  ($marks.index(dir) + 1).to_s
end

有了整數(shù)來代表每個(gè) commit,我們現(xiàn)在需要提交附加信息中的日期。由于日期是用目錄名表示的,我們就從中解析出來。print_export 文件的下一行將是:

date = convert_dir_to_date(dir)

convert_dir_to_date 則定義為

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è)全局變量中直接定義之:

$author = 'Scott Chacon <schacon@example.com>'

我們差不多可以開始為導(dǎo)入腳本輸出提交數(shù)據(jù)了。第一項(xiàng)信息指明我們定義的是一個(gè) commit 對(duì)象以及它所在的分支,隨后是我們生成的標(biāo)記,提交者信息以及提交備注,然后是前一個(gè) commit 的索引,如果有的話。代碼大致這樣:

# 打印導(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)處于簡化目的使用硬編碼。如果是從其他版本控制系統(tǒng)導(dǎo)入,則必須以變量的形式指明時(shí)區(qū)。 提交備注必須以特定格式給出:

data (size)\n(contents)

該格式包含了單詞 data,所讀取數(shù)據(jù)的大小,一個(gè)換行符,最后是數(shù)據(jù)本身。由于隨后指明文件內(nèi)容的時(shí)候要用到相同的格式,我們寫一個(gè)輔助方法,export_data

def export_data(string)
  print "data #{string.size}\n#{string}"
end

唯一剩下的就是每一個(gè)快照的內(nèi)容了。這簡單的很,因?yàn)樗鼈兎謩e處于一個(gè)目錄——你可以輸出 deleeall 命令,隨后是目錄中每個(gè)文件的內(nèi)容。Git 會(huì)正確的記錄每一個(gè)快照:

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è)命令來指出哪些文件被添加,刪除或者修改過,以及修改的內(nèi)容。我們將需要計(jì)算快照之間的差別并且僅僅給出這項(xiàng)數(shù)據(jù),不過該做法要復(fù)雜很多——還如不直接把所有數(shù)據(jù)丟給 Git 然它自己搞清楚。假如前面這個(gè)方法更適用于你的數(shù)據(jù),參考 fast-import 的 man 幫助頁面來了解如何以這種方式提供數(shù)據(jù)。

列舉新文件內(nèi)容或者指明帶有新內(nèi)容的已修改文件的格式如下:

M 644 inline path/to/file
data (size)
(file contents)

這里,644 是權(quán)限模式(加入有可執(zhí)行文件,則需要探測(cè)之并設(shè)定為 755),而 inline 說明我們?cè)诒拘薪Y(jié)束之后立即列出文件的內(nèi)容。我們的 inline_data 方法大致是:

def inline_data(file, code = 'M', mode = '644')
  content = File.read(file)
  puts "#{code} #{mode} inline #{file}"
  export_data(content)
end

我們重用了前面定義過的 export_data,因?yàn)檫@里和指明提交注釋的格式如出一轍。

最后一項(xiàng)工作是返回當(dāng)前的標(biāo)記以便下次循環(huán)的使用。

return mark

注意:如果你在用 Windows,一定記得添加一項(xiàng)額外的步驟。前面提過,Windows 使用 CRLF 作為換行字符而 git fast-import 只接受 LF。為了繞開這個(gè)問題來滿足 git fast-import,你需要讓 ruby 用 LF 取代 CRLF:

$stdout.binmode

搞定了。現(xiàn)在運(yùn)行該腳本,你將得到如下內(nèi)容:

$ 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 作為開頭,然后運(yùn)行該腳本:

$ 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ì)象?,F(xiàn)在可以運(yùn)行 git log 來檢視新的歷史:

$ 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 倉庫。需要注意的是此時(shí)沒有任何內(nèi)容被檢出——?jiǎng)傞_始當(dāng)前目錄里沒有任何文件。要獲取它們,你得轉(zhuǎn)到 master 分支的所在:

$ 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 目錄里找到;其中較為出眾的是前面提過的 git-p4 腳本。

總結(jié)

現(xiàn)在的你應(yīng)該掌握了在 Subversion 上使用 Git 以及把幾乎任何先存?zhèn)}庫無損失的導(dǎo)入為 Git 倉庫。下一章將介紹 Git 內(nèi)部的原始數(shù)據(jù)格式,從而是使你能親手鍛造其中的每一個(gè)字節(jié),如果必要的話。

上一篇:自定義 Git下一篇:Git 分支