9.2 遷移到 Git

2018-02-24 15:22 更新

遷移到 Git

如果你現(xiàn)在有一個(gè)正在使用其他 VCS 的代碼庫,但是你已經(jīng)決定開始使用 Git,必須通過某種方式將你的項(xiàng)目遷移至 Git。 這一部分會(huì)介紹一些通用系統(tǒng)的導(dǎo)入器,然后演示如何開發(fā)你自己定制的導(dǎo)入器。 你將會(huì)學(xué)習(xí)如何從幾個(gè)大型專業(yè)應(yīng)用的 SCM 系統(tǒng)中導(dǎo)入數(shù)據(jù),不僅因?yàn)樗鼈兪谴蠖鄶?shù)想要轉(zhuǎn)換的用戶正在使用的系統(tǒng),也因?yàn)楂@取針對(duì)它們的高質(zhì)量工具很容易。

Subversion

如果你閱讀過前面關(guān)于?git svn?的章節(jié),可以輕松地使用那些指令來?git svn clone?一個(gè)倉庫,停止使用 Subversion 服務(wù)器,推送到一個(gè)新的 Git 服務(wù)器,然后就可以開始使用了。 如果你想要?dú)v史,可以從 Subversion 服務(wù)器上盡可能快地拉取數(shù)據(jù)來完成這件事(這可能會(huì)花費(fèi)一些時(shí)間)。

然而,導(dǎo)入并不完美;因?yàn)榛ㄙM(fèi)太長(zhǎng)時(shí)間了,你可能早已用其他方法完成導(dǎo)入操作。 導(dǎo)入產(chǎn)生的第一個(gè)問題就是作者信息。 在 Subversion 中,每一個(gè)人提交時(shí)都需要在系統(tǒng)中有一個(gè)用戶,它會(huì)被記錄在提交信息內(nèi)。 在之前章節(jié)的例子中幾個(gè)地方顯示了?schacon,比如?blame?輸出與?git svn log。 如果想要將上面的 Subversion 用戶映射到一個(gè)更好的 Git 作者數(shù)據(jù)中,你需要一個(gè) Subversion 用戶到 Git 用戶的映射。 創(chuàng)建一個(gè)?users.txt?的文件包含像下面這種格式的映射:

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

為了獲得 SVN 使用的作者名字列表,可以運(yùn)行這個(gè):

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

這會(huì)將日志輸出為 XML 格式,然后保留作者信息行、去除重復(fù)、去除 XML 標(biāo)記。 (很顯然這只會(huì)在安裝了?grep、sort?與?perl?的機(jī)器上運(yùn)行。) 然后,將輸出重定向到你的 users.txt 文件中,這樣就可以在每一個(gè)記錄后面加入對(duì)應(yīng)的 Git 用戶數(shù)據(jù)。

你可以將此文件提供給?git svn?來幫助它更加精確地映射作者數(shù)據(jù)。 也可以通過傳遞?--no-metadata?給?clone?與?init?命令,告訴?git svn?不要包括 Subversion 通常會(huì)導(dǎo)入的元數(shù)據(jù)。 這會(huì)使你的?import?命令看起來像這樣:

$ git svn clone http://my-project.googlecode.com/svn/ \
      --authors-file=users.txt --no-metadata -s my_project

現(xiàn)在在?my_project?目錄中應(yīng)當(dāng)有了一個(gè)更好的 Subversion 導(dǎo)入。 并不像是下面這樣的提交:

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

反而它們看起來像是這樣:

commit 03a8785f44c8ea5cdb0e8834b7c8e6c469be2ff2
Author: Scott Chacon <schacon@geemail.com>
Date:   Sun May 3 00:12:22 2009 +0000

    fixed install - go to trunk

不僅是 Author 字段更好看了,git-svn-id?也不在了。

之后,你應(yīng)當(dāng)做一些導(dǎo)入后的清理工作。 第一步,你應(yīng)當(dāng)清理?git svn?設(shè)置的奇怪的引用。 首先移動(dòng)標(biāo)簽,這樣它們就是標(biāo)簽而不是奇怪的遠(yuǎn)程引用,然后你會(huì)移動(dòng)剩余的分支這樣它們就是本地的了。

為了將標(biāo)簽變?yōu)楹线m的 Git 標(biāo)簽,運(yùn)行

$ cp -Rf .git/refs/remotes/origin/tags/* .git/refs/tags/
$ rm -Rf .git/refs/remotes/origin/tags

這會(huì)使原來在?remotes/origin/tags/?里的遠(yuǎn)程分支引用變成真正的(輕量)標(biāo)簽。

接下來,將?refs/remotes?下剩余的引用移動(dòng)為本地分支:

$ cp -Rf .git/refs/remotes/* .git/refs/heads/
$ rm -Rf .git/refs/remotes

現(xiàn)在所有的舊分支都是真正的 Git 分支,并且所有的舊標(biāo)簽都是真正的 Git 標(biāo)簽。 最后一件要做的事情是,將你的新 Git 服務(wù)器添加為遠(yuǎn)程倉庫并推送到上面。 下面是一個(gè)將你的服務(wù)器添加為遠(yuǎn)程倉庫的例子:

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

因?yàn)橄胍蟼魉蟹种c標(biāo)簽,你現(xiàn)在可以運(yùn)行:

$ git push origin --all

通過以上漂亮、干凈地導(dǎo)入操作,你的所有分支與標(biāo)簽都應(yīng)該在新 Git 服務(wù)器上。

Mercurial

因?yàn)?Mercurial 與 Git 在表示版本時(shí)有著非常相似的模型,也因?yàn)?Git 擁有更加強(qiáng)大的靈活性,將一個(gè)倉庫從 Mercurial 轉(zhuǎn)換到 Git 是相當(dāng)直接的,使用一個(gè)叫作“hg-fast-export”的工具,需要從這里拷貝一份:

$ git clone http://repo.or.cz/r/fast-export.git /tmp/fast-export

轉(zhuǎn)換的第一步就是要先得到想要轉(zhuǎn)換的 Mercurial 倉庫的完整克?。?/p>

$ hg clone <remote repo URL> /tmp/hg-repo

下一步就是創(chuàng)建一個(gè)作者映射文件。 Mercurial 對(duì)放入到變更集作者字段的內(nèi)容比 Git 更寬容一些,所以這是一個(gè)清理的好機(jī)會(huì)。 只需要用到?bash?終端下的一行命令:

$ cd /tmp/hg-repo
$ hg log | grep user: | sort | uniq | sed 's/user: *//' > ../authors

這會(huì)花費(fèi)幾秒鐘,具體要看項(xiàng)目提交歷史有多少,最終?/tmp/authors?文件看起來會(huì)像這樣:

bob
bob@localhost
bob <bob@company.com>
bob jones <bob <AT> company <DOT> com>
Bob Jones <bob@company.com>
Joe Smith <joe@company.com>

在這個(gè)例子中,同一個(gè)人(Bob)使用不同的名字創(chuàng)建變更集,其中一個(gè)實(shí)際上是正確的,另一個(gè)完全不符合 Git 提交的規(guī)范。 Hg-fast-export 通過向我們想要修改的行尾添加?={new name and email address}?來修正這個(gè)問題,移除任何我們想要保留的用戶名所在的行。 如果所有的用戶名看起來都是正確的,那我們根本就不需要這個(gè)文件。 在本例中,我們會(huì)使文件看起來像這樣:

bob=Bob Jones <bob@company.com>
bob@localhost=Bob Jones <bob@company.com>
bob jones <bob <AT> company <DOT> com>=Bob Jones <bob@company.com>
bob <bob@company.com>=Bob Jones <bob@company.com>

下一步是創(chuàng)建一個(gè)新的 Git 倉庫,然后運(yùn)行導(dǎo)出腳本:

$ git init /tmp/converted
$ cd /tmp/converted
$ /tmp/fast-export/hg-fast-export.sh -r /tmp/hg-repo -A /tmp/authors

-r?選項(xiàng)告訴 hg-fast-export 去哪里尋找我們想要轉(zhuǎn)換的 Mercurial 倉庫,-A?標(biāo)記告訴它在哪找到作者映射文件。 這個(gè)腳本會(huì)分析 Mercurial 變更集然后將它們轉(zhuǎn)換成 Git“fast-import”功能(我們將在之后詳細(xì)討論)需要的腳本。 這會(huì)花一點(diǎn)時(shí)間(盡管它比通過網(wǎng)格??快),輸出相當(dāng)?shù)娜唛L(zhǎng):

$ /tmp/fast-export/hg-fast-export.sh -r /tmp/hg-repo -A /tmp/authors
Loaded 4 authors
master: Exporting full revision 1/22208 with 13/0/0 added/changed/removed files
master: Exporting simple delta revision 2/22208 with 1/1/0 added/changed/removed files
master: Exporting simple delta revision 3/22208 with 0/1/0 added/changed/removed files
[…]
master: Exporting simple delta revision 22206/22208 with 0/4/0 added/changed/removed files
master: Exporting simple delta revision 22207/22208 with 0/2/0 added/changed/removed files
master: Exporting thorough delta revision 22208/22208 with 3/213/0 added/changed/removed files
Exporting tag [0.4c] at [hg r9] [git :10]
Exporting tag [0.4d] at [hg r16] [git :17]
[…]
Exporting tag [3.1-rc] at [hg r21926] [git :21927]
Exporting tag [3.1] at [hg r21973] [git :21974]
Issued 22315 commands
git-fast-import statistics:
---------------------------------------------------------------------
Alloc'd objects:     120000
Total objects:       115032 (    208171 duplicates                  )
      blobs  :        40504 (    205320 duplicates      26117 deltas of      39602 attempts)
      trees  :        52320 (      2851 duplicates      47467 deltas of      47599 attempts)
      commits:        22208 (         0 duplicates          0 deltas of          0 attempts)
      tags   :            0 (         0 duplicates          0 deltas of          0 attempts)
Total branches:         109 (         2 loads     )
      marks:        1048576 (     22208 unique    )
      atoms:           1952
Memory total:          7860 KiB
       pools:          2235 KiB
     objects:          5625 KiB
---------------------------------------------------------------------
pack_report: getpagesize()            =       4096
pack_report: core.packedGitWindowSize = 1073741824
pack_report: core.packedGitLimit      = 8589934592
pack_report: pack_used_ctr            =      90430
pack_report: pack_mmap_calls          =      46771
pack_report: pack_open_windows        =          1 /          1
pack_report: pack_mapped              =  340852700 /  340852700
---------------------------------------------------------------------

$ git shortlog -sn
   369  Bob Jones
   365  Joe Smith

那看起來非常好。 所有 Mercurial 標(biāo)簽都已被轉(zhuǎn)換成 Git 標(biāo)簽,Mercurial 分支與書簽都被轉(zhuǎn)換成 Git 分支。 現(xiàn)在已經(jīng)準(zhǔn)備好將倉庫推送到新的服務(wù)器那邊:

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

Perforce

下一個(gè)將要看到導(dǎo)入的系統(tǒng)是 Perforce。 就像我們之前討論過的,有兩種方式讓 Git 與 Perforce 互相通信:git-p4 與 Perforce Git Fusion。

Perforce Git Fusion

Git Fusion 使這個(gè)過程毫無痛苦。 只需要使用在?Git Fusion?中討論過的配置文件來配置你的項(xiàng)目設(shè)置、用戶映射與分支,然后克隆整個(gè)倉庫。 Git Fusion 讓你處在一個(gè)看起來像是原生 Git 倉庫的環(huán)境中,如果愿意的話你可以隨時(shí)將它推送到一個(gè)原生 Git 托管中。 如果你喜歡的話甚至可以使用 Perforce 作為你的 Git 托管。

Git-p4

Git-p4 也可以作為一個(gè)導(dǎo)入工具。 作為例子,我們將從 Perforce 公開倉庫中導(dǎo)入 Jam 項(xiàng)目。 為了設(shè)置客戶端,必須導(dǎo)出 P4PORT 環(huán)境變量指向 Perforce 倉庫:

$ export P4PORT=public.perforce.com:1666
NOTE

為了繼續(xù)后續(xù)步驟,需要連接到 Perforce 倉庫。 在我們的例子中將會(huì)使用在 public.perforce.com 的公開倉庫,但是你可以使用任何你有權(quán)限的倉庫。

運(yùn)行?git p4 clone?命令從 Perforce 服務(wù)器導(dǎo)入 Jam 項(xiàng)目,提供倉庫、項(xiàng)目路徑與你想要存放導(dǎo)入項(xiàng)目的路徑:

$ git-p4 clone //guest/perforce_software/jam@all p4import
Importing from //guest/perforce_software/jam@all into p4import
Initialized empty Git repository in /private/tmp/p4import/.git/
Import destination: refs/remotes/p4/master
Importing revision 9957 (100%)

這個(gè)特定的項(xiàng)目只有一個(gè)分支,但是如果你在分支視圖(或者說一些目錄)中配置了一些分支,你可以將?--detect-branches?選項(xiàng)傳遞給?git p4 clone?來導(dǎo)入項(xiàng)目的所有分支。 查看?分支?來了解關(guān)于這點(diǎn)的更多信息。

此時(shí)你幾乎已經(jīng)完成了。 如果進(jìn)入?p4import?目錄中并運(yùn)行?git log,可以看到你的導(dǎo)入工作:

$ git log -2
commit e5da1c909e5db3036475419f6379f2c73710c4e6
Author: giles <giles@giles@perforce.com>
Date:   Wed Feb 8 03:13:27 2012 -0800

    Correction to line 355; change </UL> to </OL>.

    [git-p4: depot-paths = "http://public/jam/src/": change = 8068]

commit aa21359a0a135dda85c50a7f7cf249e4f7b8fd98
Author: kwirth <kwirth@perforce.com>
Date:   Tue Jul 7 01:35:51 2009 -0800

    Fix spelling error on Jam doc page (cummulative -> cumulative).

    [git-p4: depot-paths = "http://public/jam/src/": change = 7304]

你可以看到?git-p4在每一個(gè)提交里都留下了一個(gè)標(biāo)識(shí)符。 如果之后想要引用 Perforce 的修改序號(hào)的話,標(biāo)識(shí)符保留在那里也是可以的。 然而,如果想要移除標(biāo)識(shí)符,現(xiàn)在正是這么做的時(shí)候 - 在你開始在新倉庫中工作之前。?可以使用?git filter-branch?將全部標(biāo)識(shí)符移除。

$ git filter-branch --msg-filter 'sed -e "/^\[git-p4:/d"'
Rewrite e5da1c909e5db3036475419f6379f2c73710c4e6 (125/125)
Ref 'refs/heads/master' was rewritten

如果運(yùn)行?git log,你會(huì)看到所有提交的 SHA-1 校驗(yàn)和都改變了,但是提交信息中不再有?git-p4?字符串了:

$ git log -2
commit b17341801ed838d97f7800a54a6f9b95750839b7
Author: giles <giles@giles@perforce.com>
Date:   Wed Feb 8 03:13:27 2012 -0800

    Correction to line 355; change </UL> to </OL>.

commit 3e68c2e26cd89cb983eb52c024ecdfba1d6b3fff
Author: kwirth <kwirth@perforce.com>
Date:   Tue Jul 7 01:35:51 2009 -0800

    Fix spelling error on Jam doc page (cummulative -> cumulative).

現(xiàn)在導(dǎo)入已經(jīng)準(zhǔn)備好推送到你的新 Git 服務(wù)器上了。

TFS

如果你的團(tuán)隊(duì)正在將他們的源代碼管理從 TFVC 轉(zhuǎn)換為 Git,你們會(huì)想要最高程度的無損轉(zhuǎn)換。 這意味著,雖然我們?cè)谥暗慕换フ鹿?jié)介紹了 git-tfs 與 git-tf 兩種工具,但是我們?cè)诒静糠种荒芙榻B git-tfs,因?yàn)?git-tfs 支持分支,而使用 git-tf 代價(jià)太大。

NOTE

這是一個(gè)單向轉(zhuǎn)換。 這意味著 Git 倉庫無法連接到原始的 TFVC 項(xiàng)目。

第一件事是映射用戶名。 TFVC 對(duì)待變更集作者字段的內(nèi)容相當(dāng)寬容,但是 Git 需要人類可讀的名字與郵箱地址。 可以通過?tf?命令行客戶端來獲取這個(gè)信息,像這樣:

PS> tf history $/myproject -recursive > AUTHORS_TMP

這會(huì)將歷史中的所有變更集抓取下來并放到 AUTHORS_TMP 文件中,然后我們將會(huì)將?User?列(第二個(gè))取出來。 打開文件找到列開始與結(jié)束的字符并替換,在下面的命令行中,cut?命令的參數(shù)?11-20?就是我們找到的:

PS> cat AUTHORS_TMP | cut -b 11-20 | tail -n+3 | uniq | sort > AUTHORS

cut?命令只會(huì)保留每行中第 11 個(gè)到第 22 個(gè)字符。?tail?命令會(huì)跳過前兩行,就是字段表頭與 ASCII 風(fēng)格的下劃線。 所有這些的結(jié)果通過管道送到?uniq?來去除重復(fù),然后保存到?AUTOHRS文件中。 下一步是手動(dòng)的;為了讓 git-tfs 有效地使用這個(gè)文件,每一行必須是這種格式:

DOMAIN\username = User Name <email@address.com>

左邊的部分是 TFVC 中的 “User” 字段,等號(hào)右邊的部分是將被用作 Git 提交的用戶名。

一旦有了這個(gè)文件,下一件事就是生成一個(gè)你需要的 TFVC 項(xiàng)目的完整克?。?/p>

PS> git tfs clone --with-branches --authors=AUTHORS https://username.visualstudio.com/DefaultCollection $/Trunk project_git

接下來要從提交信息底部清理?git-tfs-id?區(qū)塊。 下面的命令會(huì)完成這個(gè)任務(wù):

PS> git filter-branch -f --msg-filter 'sed "s/^git-tfs-id:.*$//g"' -- --all

那會(huì)使用 Git 終端環(huán)境中的?sed?命令來將所有以 “git-tfs-id:” 開頭的行替換為 Git 會(huì)忽略的空白。

全部完成后,你就已經(jīng)準(zhǔn)備好去增加一個(gè)新的遠(yuǎn)程倉庫,推送你所有的分支上去,然后你的團(tuán)隊(duì)就可以開始用 Git 工作了。

一個(gè)自定義的導(dǎo)入器

如果你的系統(tǒng)不是上述中的任何一個(gè),你需要在線查找一個(gè)導(dǎo)入器 - 針對(duì)許多其他系統(tǒng)有很多高質(zhì)量的導(dǎo)入器,包括 CVS、Clear Case、Visual Source Safe,甚至是一個(gè)檔案目錄。 如果沒有一個(gè)工具適合你,需要一個(gè)不知名的工具,或者需要更大自由度的自定義導(dǎo)入過程,應(yīng)當(dāng)使用?git fast-import。 這個(gè)命令從標(biāo)準(zhǔn)輸入中讀取簡(jiǎn)單指令來寫入特定的 Git 數(shù)據(jù)。 通過這種方式創(chuàng)建 Git 對(duì)象比運(yùn)行原始 Git 命令或直接寫入原始對(duì)象(查看?Git 內(nèi)部原理?了解更多內(nèi)容)更容易些。 通過這種方式你可以編寫導(dǎo)入腳本,從你要導(dǎo)入的系統(tǒng)中讀取必要數(shù)據(jù),然后直接打印指令到標(biāo)準(zhǔn)輸出。 然后可以運(yùn)行這個(gè)程序并通過?git fast-import?重定向管道輸出。

為了快速演示,我們會(huì)寫一個(gè)簡(jiǎn)單的導(dǎo)入器。 假設(shè)你在?current?工作,有時(shí)候會(huì)備份你的項(xiàng)目到時(shí)間標(biāo)簽?back_YYYY_MM_DD?備份目錄中,你想要將這些導(dǎo)入到 Git 中。 目錄結(jié)構(gòu)看起來是這樣:

$ ls /opt/import_from
back_2014_01_02
back_2014_01_04
back_2014_01_14
back_2014_02_03
current

為了導(dǎo)入一個(gè) Git 目錄,需要了解 Git 如何存儲(chǔ)它的數(shù)據(jù)。 你可能記得,Git 在底層存儲(chǔ)指向內(nèi)容快照的提交對(duì)象的鏈表。 所有要做的就是告訴?fast-import?哪些內(nèi)容是快照,哪個(gè)提交數(shù)據(jù)指向它們,以及它們進(jìn)入的順序。 你的策略是一次訪問一個(gè)快照,然后用每個(gè)目錄中的內(nèi)容創(chuàng)建提交,并且將每一個(gè)提交與前一個(gè)連接起來。

如同我們?cè)?使用強(qiáng)制策略的一個(gè)例子?里做的,我們將會(huì)使用 Ruby 寫這個(gè),因?yàn)樗俏覀兤匠9ぷ髦惺褂玫牟⑶宜苋菀鬃x懂。 可以使用任何你熟悉的東西來非常輕松地寫這個(gè)例子 - 它只需要將合適的信息打印到?標(biāo)準(zhǔn)輸出。 然而,如果你在 Windows 上,這意味著需要特別注意不要引入回車符到行尾 - git fast-import 非常特別地只接受換行符(LF)而不是 Windows 使用的回車換行符(CRLF)。

現(xiàn)在開始,需要進(jìn)入目標(biāo)目錄中并識(shí)別每一個(gè)子目錄,每一個(gè)都是你要導(dǎo)入為提交的快照。 要進(jìn)入到每個(gè)子目錄中并為導(dǎo)出它打印必要的命令。 基本主循環(huán)像這個(gè)樣子:

last_mark = nil

# loop through the directories
Dir.chdir(ARGV[0]) do
  Dir.glob("*").each do |dir|
    next if File.file?(dir)

    # move into the target directory
    Dir.chdir(dir) do
      last_mark = print_export(dir, last_mark)
    end
  end
end

在每個(gè)目錄內(nèi)運(yùn)行?print_export,將會(huì)拿到清單并標(biāo)記之前的快照,然后返回清單并標(biāo)記現(xiàn)在的快照;通過這種方式,可以將它們合適地連接在一起。 “標(biāo)記” 是一個(gè)給提交標(biāo)識(shí)符的?fast-import?術(shù)語;當(dāng)你創(chuàng)建提交,為每一個(gè)提交賦予一個(gè)標(biāo)記來將它與其他提交連接在一起。 這樣,在你的?print_export?方法中第一件要做的事就是從目錄名字生成一個(gè)標(biāo)記:

mark = convert_dir_to_mark(dir)

可以創(chuàng)建一個(gè)目錄的數(shù)組并使用索引做為標(biāo)記,因?yàn)闃?biāo)記必須是一個(gè)整數(shù)。 方法類似這樣:

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

既然有一個(gè)整數(shù)代表你的提交,那還要給提交元數(shù)據(jù)一個(gè)日期。 因?yàn)槟夸浢直磉_(dá)了日期,所以你將會(huì)從中解析出日期。 你的?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

那會(huì)返回每一個(gè)目錄日期的整數(shù)。 最后一項(xiàng)每個(gè)提交需要的元數(shù)據(jù)是提交者信息,它將會(huì)被硬編碼在全局變量中:

$author = 'John Doe <john@example.com>'

現(xiàn)在準(zhǔn)備開始為你的導(dǎo)入器打印出提交數(shù)據(jù)。 初始信息聲明定義了一個(gè)提交對(duì)象與它所在的分支,緊接著一個(gè)你生成的標(biāo)記、提交者信息與提交信息、然后是一個(gè)之前的提交,如果它存在的話。 代碼看起來像這樣:

# print the import information
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),因?yàn)檫@樣很容易。 如果從其他系統(tǒng)導(dǎo)入,必須指定為一個(gè)偏移的時(shí)區(qū)。 提交信息必須指定為特殊的格式:

data (size)\n(contents)

這個(gè)格式包括文本數(shù)據(jù)、將要讀取數(shù)據(jù)的大小、一個(gè)換行符、最終的數(shù)據(jù)。 因?yàn)橹筮€需要為文件內(nèi)容指定相同的數(shù)據(jù)格式,你需要?jiǎng)?chuàng)建一個(gè)幫助函數(shù),export_data

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

剩下的工作就是指定每一個(gè)快照的文件內(nèi)容。 這很輕松,因?yàn)槊恳粋€(gè)目錄都是一個(gè)快照 - 可以在目錄中的每一個(gè)文件內(nèi)容后打印?deleteall?命令。 Git 將會(huì)適當(dāng)?shù)赜涗浢恳粋€(gè)快照:

puts 'deleteall'
Dir.glob("**/*").each do |file|
  next if !File.file?(file)
  inline_data(file)
end

注意:因?yàn)榇蠖鄶?shù)系統(tǒng)認(rèn)為他們的版本是從一個(gè)提交變化到另一個(gè)提交,fast-import 也可以為每一個(gè)提交執(zhí)行命令來指定哪些文件是添加的、刪除的或修改的與新內(nèi)容是哪些。 可以計(jì)算快照間的不同并只提供這些數(shù)據(jù),但是這樣做會(huì)很復(fù)雜 - 也可以把所有數(shù)據(jù)給 Git 然后讓它為你指出來。 如果這更適合你的數(shù)據(jù),查閱?fast-import?man 幫助頁來了解如何以這種方式提供你的數(shù)據(jù)。

這種列出新文件內(nèi)容或用新內(nèi)容指定修改文件的格式如同下面的內(nèi)容:

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

這里,644 是模式(如果你有可執(zhí)行文件,反而你需要檢測(cè)并指定 755),inline 表示將會(huì)立即把內(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)樗c你定義的提交信息數(shù)據(jù)的方法一樣。

最后一件你需要做的是返回當(dāng)前的標(biāo)記以便它可以傳給下一個(gè)迭代:

return mark
NOTE

如果在 Windows 上還需要確保增加一個(gè)額外步驟。 正如之前提到的,Windows 使用 CRLF 作為換行符而 git fast-import 只接受 LF。 為了修正這個(gè)問題使 git fast-import 正常工作,你需要告訴 ruby 使用 LF 代替 CRLF:

$stdout.binmode

就是這樣。 這是全部的腳本:

#!/usr/bin/env ruby

$stdout.binmode
$author = "John Doe <john@example.com>"

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

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

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

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

def print_export(dir, last_mark)
    date = convert_dir_to_date(dir)
    mark = convert_dir_to_mark(dir)

    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

    puts 'deleteall'
    Dir.glob("**/*").each do |file|
        next if !File.file?(file)
        inline_data(file)
    end
    mark
end

# Loop through the directories
last_mark = nil
Dir.chdir(ARGV[0]) do
    Dir.glob("*").each do |dir|
        next if File.file?(dir)

        # move into the target directory
        Dir.chdir(dir) do
            last_mark = print_export(dir, last_mark)
        end
    end
end

如果運(yùn)行這個(gè)腳本,你會(huì)得到類似下面的內(nèi)容:

$ ruby import.rb /opt/import_from
commit refs/heads/master
mark :1
committer John Doe <john@example.com> 1388649600 -0700
data 29
imported from back_2014_01_02deleteall
M 644 inline README.md
data 28
# Hello

This is my readme.
commit refs/heads/master
mark :2
committer John Doe <john@example.com> 1388822400 -0700
data 29
imported from back_2014_01_04from :1
deleteall
M 644 inline main.rb
data 34
#!/bin/env ruby

puts "Hey there"
M 644 inline README.md
(...)

為了運(yùn)行導(dǎo)入器,將這些輸出用管道重定向到你想要導(dǎo)入的 Git 目錄中的?git fast-import。 可以創(chuàng)建一個(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:           13 (         6 duplicates                  )
      blobs  :            5 (         4 duplicates          3 deltas of          5 attempts)
      trees  :            4 (         1 duplicates          0 deltas of          4 attempts)
      commits:            4 (         1 duplicates          0 deltas of          0 attempts)
      tags   :            0 (         0 duplicates          0 deltas of          0 attempts)
Total branches:           1 (         1 loads     )
      marks:           1024 (         5 unique    )
      atoms:              2
Memory total:          2344 KiB
       pools:          2110 KiB
     objects:           234 KiB
---------------------------------------------------------------------
pack_report: getpagesize()            =       4096
pack_report: core.packedGitWindowSize = 1073741824
pack_report: core.packedGitLimit      = 8589934592
pack_report: pack_used_ctr            =         10
pack_report: pack_mmap_calls          =          5
pack_report: pack_open_windows        =          2 /          2
pack_report: pack_mapped              =       1457 /       1457
---------------------------------------------------------------------

正如你所看到的,當(dāng)它成功完成時(shí),它會(huì)給你一串關(guān)于它完成內(nèi)容的統(tǒng)計(jì)。 這本例中,一共導(dǎo)入了 13 個(gè)對(duì)象、4 次提交到 1 個(gè)分支。 現(xiàn)在,可以運(yùn)行?git log?來看一下你的新歷史:

$ git log -2
commit 3caa046d4aac682a55867132ccdfbe0d3fdee498
Author: John Doe <john@example.com>
Date:   Tue Jul 29 19:39:04 2014 -0700

    imported from current

commit 4afc2b945d0d3c8cd00556fbe2e8224569dc9def
Author: John Doe <john@example.com>
Date:   Mon Feb 3 01:00:00 2014 -0700

    imported from back_2014_02_03

做得很好 - 一個(gè)漂亮、干凈的 Git 倉庫。 要注意的一點(diǎn)是并沒有檢出任何東西 - 一開始你的工作目錄內(nèi)并沒有任何文件。 為了得到他們,你必須將分支重置到?master?所在的地方:

$ ls
$ git reset --hard master
HEAD is now at 3caa046 imported from current
$ ls
README.md main.rb

可以通過?fast-import?工具做很多事情 - 處理不同模式、二進(jìn)制數(shù)據(jù)、多個(gè)分支與合并、標(biāo)簽、進(jìn)度指示等等。 一些更復(fù)雜情形下的例子可以在 Git 源代碼目錄中的?contrib/fast-import目錄中找到。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)