Git正解 脫水版 【9. 其他VCS系統】

9.1 Git寄生

這個世界還不夠完美,用戶無法在Git中,查看所有的開發項目,但是使用其他VCS系統的開發項目,可以遷移到Git,這裏有兩種方法,其一,將Git作爲客戶端,添加到其他的VCS系統中,其二,將開發項目整體遷移到Git。以下將介紹Git在其他VCS系統中的寄生用法,

Git與Subversion(SVN)

大量的開源項目和一些高質量的企業項目,都選擇了SVN,在過去的十多年時間裏,SVN也一直是開源項目的主流選擇,同時它與之前的王者工具CVS很相似。因此Git構建一個雙向橋接器git svn,用於SVN的協作,如果用戶只使用本地SVN,則可基於Git的本地功能,實現SVN倉庫的提交,這不會影響到SVN系統的協作者,直到用戶將項目的框架逐漸改成Git風格,協作者纔可瞭解高效的含義。

git svn

該命令的選項不多,它最重要的使命,則是完成與SVN的交互,雖然用戶可以使用本地分支,合併,衍合,但是有些功能應當避免使用,比如與Git遠程倉庫的同步交互,以及在修改提交歷史後,不要重新提交,另外,同一時刻下,Git用戶不能再推送並行Git倉庫(與SVN項目並行的Git項目),由於SVN只有單一的線性提交歷史,很容易產生混亂,同時在開發組中,一部分成員使用SVN,另一部分成員使用Git,必須通知所有人,只能在SVN服務器上進行協作,這樣更易於管理。

配置

首先用戶需要配置一個帶寫入權限的SVN倉庫,爲了簡化描述,這裏將使用svnsync工具,生成一個可寫入的SVN倉庫的副本,因此需要新建一個SVN本地倉庫的保存路徑,

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

爲了確保所有用戶都可修改revprop(提交日誌),最簡單的方法,讓pre-revprop-change hook腳本只返回0,這時所有用戶都可獲得提交日誌的修改權限,

$ cat /tmp/test-svn/hooks/pre-revprop-change
#!/bin/sh
exit 0;

$ chmod +x /tmp/test-svn/hooks/pre-revprop-change

調用svnsync init,將SVN遠程倉庫,關聯到本地副本的存儲路徑,

$ svnsync init file:///tmp/test-svn http://your-svn-server.example.org/svn/

克隆SVN遠程倉庫,

$ svnsync sync file:///tmp/test-svn
Committed revision 1.
Copied properties for revision 1.
Transmitting file data .............................[...]
Committed revision 2.
Copied properties for revision 2.
[...]

雖然整個操作只有幾分鐘,如果用戶需要將原始倉庫,複製到其他倉庫,而不是創建本地副本,可能需要一小時,即使遠程倉庫的所有提交不足100個,因爲SVN只能實現基於版本的克隆,每個版本需要完整導出後,將可進行數據傳輸,從上述的輸出信息可知,因而出現了無法忍受的低效,同時這又是最簡單的方法。

應用基礎

此時用戶已擁有了一個帶寫入權限的SVN倉庫,可運行git svn clone命令,將整個SVN倉庫導出到Git本地倉庫,應當注意,這不是一個真實的導出操作,只是將SVN倉庫的URL修改爲本地路徑file:///tmp/test-svn,

$ git svn clone file:///tmp/test-svn -T trunk -b branches -t tags
Initialized empty Git repository in /private/tmp/progit/test-svn/.git/
r1 = dcbfb5891860124cc2e8cc616cded42624897125 (refs/remotes/origin/trunk)
      A    m4/acx_pthread.m4
      A    m4/stl_hash.m4
...
r75 = 556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae (refs/remotes/origin/trunk)
Found possible branch point: file:///tmp/test-svn/trunk => file:///tmp/test-
svn/branches/my-calc-branch, 75
Found branch parent: (refs/remotes/origin/my-calc-branch)
556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae
Following parent with do_switch
Successfully followed parent
r76 = 0fb585761df569eaecd8146c71e58d70147460a2 (refs/remotes/origin/my-calc-branch)
Checked out HEAD:
  file:///tmp/test-svn/trunk r75

上述命令等同於git svn init和git svn fetch的組合,從輸出信息可知,示例項目只有75個提交,代碼庫也不大,Git無需檢查SVN示例項目的每個版本,因爲SVN的提交都是相互獨立的,如果示例項目包含了上百甚至上千個提交,上述操作可能需要數小時甚至數天。

-T trunk -b branches -t tags,表明SVN倉庫中使用了trunk(主分支),branch(分支),tag(標籤)標識,如果用戶需要修改這些默認標識,可在上述命令中,附帶其他的選項,而上述命令附帶的選項,都是常見選項,因此可替換成-s選項,以下命令等同於之前的命令,

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

這時SVN倉庫的分支和標籤,都已導入Git倉庫,

$ git branch -a
* master
  remotes/origin/my-calc-branch
  remotes/origin/tags/2.0.2
  remotes/origin/tags/release-2.0.1
  remotes/origin/tags/release-2.0.2
  remotes/origin/tags/release-2.0.2rc1
  remotes/origin/trunk

使用show-ref命令,從輸出信息可知,SVN標籤已被視爲Git倉庫的引用,

$ git show-ref
556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae refs/heads/master
0fb585761df569eaecd8146c71e58d70147460a2 refs/remotes/origin/my-calc-branch
bfd2d79303166789fc73af4046651a4b35c12f0b refs/remotes/origin/tags/2.0.2
285c2b2e36e467dd4d91c8e3c0c0e1750b3fe8ca refs/remotes/origin/tags/release-2.0.1
cbda99cb45d9abcb9793db1d4f70ae562a969f1e refs/remotes/origin/tags/release-2.0.2
a9f074aa89e826d6f9d30808ce5ae3ffe711feda refs/remotes/origin/tags/release-2.0.2rc1
556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae refs/remotes/origin/trunk

應當注意,這與Git倉庫的克隆副本並不一致,如下,Git標籤將放置在獨立的路徑中,而不是視爲一個遠程分支,

$ git show-ref
c3dcbe8488c6240392e8a5d7553bbffcb0f94ef0 refs/remotes/origin/master
32ef1d1c7cc8c603ab78416262cc421b80a8c2df refs/remotes/origin/branch-1
75f703a3580a9b81ead89fe1138e6da858c5ba18 refs/remotes/origin/branch-2
23f8588dde934e8f33c263c6d8359b2ae095f863 refs/tags/v0.1.0
7064938bd5e7ef47bfd79a685a62c1e2649e2ce7 refs/tags/v0.2.0
6dcb09b5b57875f334f61aebed695e2e4193db5e refs/tags/v1.0.0

推送提交

這時用戶可在SVN項目副本中進行開發,並完成推送,Git倉庫將視爲SVN的客戶端,如果用戶修改了項目文件並提交,應當注意,該提交只存在於Git本地倉庫,並不存在於SVN服務器.

$ git commit -am 'Adding git-svn instructions to the README'
[master 4af61fd] Adding git-svn instructions to the README
 1 file changed, 5 insertions(+)

之後用戶需要推送提交,可以選擇多個提交,一次性推送到SVN服務器,這時可運行git svn dcommit,

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M README.txt
Committed r77
    M README.txt
r77 = 95e0222ba6399739834380eb10afcd73e0670bc5 (refs/remotes/origin/trunk)
No changes between 4af61fd05045e07598c553167e0f31c84fd6ffe1 and
refs/remotes/origin/trunk
Resetting to the latest refs/remotes/origin/trunk

從輸出信息可知,提交已推送到SVN服務器,每個Git提交都將轉換成一個SVN提交,應當注意一個重要的細節,提交的SHA-1校驗碼在提交過程中會被修改,因此建立一個與SVN項目並行的Git倉庫,並不是明智的選擇,如果需要查看SVN項目的最新提交,從輸出信息中可見,已添加了新的git-svn-id,

$ git log -1
commit 95e0222ba6399739834380eb10afcd73e0670bc5
Author: ben <ben@0b684db3-b064-4277-89d1-21af03df0a68>
Date:   Thu Jul 24 03:08:36 2014 +0000

    Adding git-svn instructions to the README
    git-svn-id: file:///tmp/test-svn/trunk@77 0b684db3-b064-4277-89d1-21af03df0a68

注意,提交的原始校驗碼爲4af61fd,提交之後,則變爲95e0222,如果用戶需要同時推送Git倉庫和SVN倉庫,建議首先推送SVN倉庫,因爲dcommit推送操作,將會修改提交數據.

獲取變更

如果存在開發協作者,不可避免會因爲同步問題,而產生推送衝突,除非完成合並操作,如果出現以下衝突,

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...

ERROR from SVN:
Transaction is out of date: File '/trunk/README.txt' is out of date
W: d5837c4b461b7c0e018b49d12398769d2bfc240a and refs/remotes/origin/trunk differ,
using rebase:
:100644 100644 f414c433af0fd6734428cf9d2a9fd8ba00ada145
c80b6127dd04f5fcda218730ddf3a2da4eb39138 M README.txt
Current branch master is up to date.
ERROR: Not all changes have been committed into SVN, however the committed
ones (if any) seem to be successfully integrated into the working tree.
Please see the above messages for details.

爲了解決上述問題,需執行git svn rebase,它可獲取服務器上,未同步的最新變更,並衍合當前用戶的最新變更,

$ git svn rebase
Committing to file:///tmp/test-svn/trunk ...

ERROR from SVN:
Transaction is out of date: File '/trunk/README.txt' is out of date
W: eaa029d99f87c5c822c5c29039d19111ff32ef46 and refs/remotes/origin/trunk differ,
using rebase:
:100644 100644 65536c6e30d263495c17d781962cfff12422693a
b34372b25ccf4945fe5658fa381b075045e7702a M README.txt
First, rewinding head to replay your work on top of it...
Applying: update foo
Using index info to reconstruct a base tree...
M README.txt
Falling back to patching base and 3-way merge...
Auto-merging README.txt
ERROR: Not all changes have been committed into SVN, however the committed
ones (if any) seem to be successfully integrated into the working tree.
Please see the above messages for details.

將衍合結果,提交到SVN倉庫,

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M README.txt
Committed r85
    M README.txt
r85 = 9c29704cc0bbbed7bd58160cfb66cb9191835cd8 (refs/remotes/origin/trunk)
No changes between 5762f56732a958d6cfda681b661d2a239cc53ef5 and
refs/remotes/origin/trunk
Resetting to the latest refs/remotes/origin/trunk

上述工作流不同於Git,只有當出現變更衝突時,才需要合併,如果協作者並未修改同一文件,則不存在變更衝突,

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M configure.ac
Committed r87
    M autogen.sh
r86 = d8450bab8a77228a644b7dc0e95977ffc61adff7 (refs/remotes/origin/trunk)
    M configure.ac
r87 = f3653ea40cb4e26b6281cec102e35dcba1fe17c4 (refs/remotes/origin/trunk)
W: a0253d06732169107aa020390d9fefd2b1d92806 and refs/remotes/origin/trunk differ,
using rebase:
:100755 100755 efa5a59965fbbb5b2b0a12890f1b351bb5493c18
e757b59a9439312d80d5d43bb65d4a7d0389ed6d M autogen.sh
First, rewinding head to replay your work on top of it...

值得注意,當用戶完成提交後,無法查看項目的最終狀態,如果變更之間只是不兼容,而非衝突時,有可能引入一個難以調試的問題,但在Git中,根本不存在這類問題,在提交之前,用戶就可檢查當前倉庫的狀態,而在SVN中,提交前後都無法立即確認倉庫的當前狀態.

用戶可使用git svn fetch命令,從SVN倉庫中獲取最新數據,但使用git svn rebase命令,不僅能獲取最新數據,還能自動更新本地提交.

$ git svn rebase
    M autogen.sh
r88 = c9c5f83c64bd755368784b444bc7a0216cc1e17b (refs/remotes/origin/trunk)
First, rewinding head to replay your work on top of it...
Fast-forwarded master to refs/remotes/origin/trunk.

每當git svn rebase執行完畢,都能獲取到最新的代碼,但運行git svn rebase之前,必須提供一個乾淨的工作區,如果工作區包含了變更文件,可選擇隱藏或臨時提交,否則該命令將中止,如果命令不中止,將會引入合併衝突.

Git分支

只有回到Git工作流,纔可體會輕鬆和愉快,這時用戶可創建特性分支,並工作在特性分支上,在適當的時候合併特性分支,而在SVN中,用戶只能將開發結果,衍合到單一分支,而不是合併分支,因爲SVN只有一個線性提交歷史,無需處理分支合併的問題,所以git svn通常需要將提交,轉換成項目快照,再傳遞給SVN.

假定用戶的提交示例如下,首先創建了一個experimnet分支,並傳入了兩個提交,之後該分支將合併到master分支,如果此時使用dcommit,將Git倉庫的提交,傳遞給SVN倉庫,

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M CHANGES.txt
Committed r89
    M CHANGES.txt
r89 = 89d492c884ea7c834353563d5d913c6adf933981 (refs/remotes/origin/trunk)
    M COPYING.txt
    M INSTALL.txt
Committed r90
    M INSTALL.txt
    M COPYING.txt
r90 = cb522197870e61467473391799148f6721bcf9a0 (refs/remotes/origin/trunk)
No changes between 71af502c214ba13123992338569f4669877f55fd and
refs/remotes/origin/trunk
Resetting to the latest refs/remotes/origin/trunk

這時master分支的合併提交,已傳遞給SVN,可見experiment分支的兩個提交,並未傳遞給SVN,但是master分支的兩個合併提交,傳入SVN後,SHA-1校驗值都發生了變化,如果此時有協作者同步了SVN倉庫,將會看到兩個合併提交所包含的變更,被重組在一起,已經無法分辨這些變更來自哪個提交.

SVN分支

SVN分支不同於Git分支,在大多數情況下,用戶應當避免使用SVN分支,這可能是最好的選擇,當然用戶可使用git svn,生成SVN分支,並實現SVN分支的提交.

新建SVN分支
$ git svn branch opera
Copying file:///tmp/test-svn/trunk at r90 to file:///tmp/test-svn/branches/opera...
Found possible branch point: file:///tmp/test-svn/trunk => file:///tmp/test-svn/branches/opera, 90
Found branch parent: (refs/remotes/origin/opera)
cb522197870e61467473391799148f6721bcf9a0
Following parent with do_switch
Successfully followed parent
r91 = f1b64a3855d3c8dd84ee0ef10fa89d27f1584302 (refs/remotes/origin/opera)

在SVN中,上述命令等同於svn copy trunk branches/opera,從等價命令可知,新建分支只是將主分支trunk,複製到opera,後續的提交依舊會傳遞給主分支trunk.

切換SVN分支

如果用戶需要了解dcommit命令,對應的SVN分支,可查找SVN提交歷史的最新提交,即git-svn-id所提供的信息,注意,SVN只有一條主分支,如果用戶想實現多條分支的並行,可新建一個本地分支,並關聯到特定的SVN分支,比如需要獨立運行之前的opera分支,這時Git就可提交到本地分支,

$ git branch opera remotes/origin/opera

如果用戶需要將opera分支,合併到trunk分支,可執行git merge,但用戶必須使用-m選項,附帶一個提交描述,比如Merge branch opera.

應當注意,雖然用戶可使用git merge完成合並,似乎和純Git系統一樣簡單,因爲Git可自動檢測最佳的合併點,而實際的合併,並不會生成一個合併提交,如果將合併提交,直接傳遞給SVN倉庫,事實上SVN倉庫無法處理,因爲這些提交包含了多個父提交,因此需要額外的操作,一旦合併提交(已處理)傳遞給SVN倉庫後,在SVN倉庫中,只能看到一個提交,該提交將重組來自不同分支的變更,雖然實現了分支合併,但無法切換分支,dcommit會清除掉分支合併的所有信息,因此後續基於合併的命令操作,只能得到錯誤結果,所以dcommit命令更類似於git merge --squash,目前無法避免這類情況的發生,SVN無法記錄合併信息,用戶應當牢記這一點,爲了避免出現問題,當本地分支opera合併到trunk後,應將opera分支刪除.

SVN命令

git svn提供一組子命令,以方便Git和SVN之間的互連,如下,

提交歷史

如果用戶需要查看SVN風格的提交歷史,可執行git svn log,

$ git svn log
------------------------------------------------------------------------
r87 | schacon | 2014-05-02 16:07:37 -0700 (Sat, 02 May 2014) | 2 lines
autogen change
------------------------------------------------------------------------
r86 | schacon | 2014-05-02 16:00:21 -0700 (Sat, 02 May 2014) | 2 lines
Merge branch 'experiment'
------------------------------------------------------------------------
r85 | schacon | 2014-05-02 16:00:09 -0700 (Sat, 02 May 2014) | 2 lines
updated the changelog

有兩點需要注意,其一,SVN倉庫處於離線狀態,上述輸出並不等同svn log命令,svn log會從SVN服務器讀取數據,其二,只顯示當前用戶的提交,也是已保存在SVN倉庫的提交,未傳遞的Git本地提交將不會顯示,

標註

git svn log可視爲svn log的離線版,同樣git svn blame [文件名]也等同於svn annotate,

$ 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 svn info和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)

和blame,log一樣,info也是離線運行,其中包含了連接SVN服務器的最後時間.

可忽略屬性

如果克隆的SVN倉庫包含了svn:ignore屬性,該屬性類似於.gitignore文件,這時git svn提供了兩個子命令,其一,git svn create-ignore可自動創建對應的.gitignore文件,在用戶的下一次提交中,將應用.gitignore文件的設置,其二,git svn show-ignore可將.gitignore文件,輸出到stdout,因此用戶可基於重定向,將stdout接收的數據,轉存到文件中,

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

Git和Mercurial

在DVCS(分佈式版本控制系統)類別中,除了Git之外,還存在大量的其他工具,每個工具的側重點都不同,最流行的工具,即爲Mercurial,在許多功能上,與Git很相似.如果用戶喜歡Git的客戶端管理功能,同時開發項目又使用了Mercurial,這時可在Mercurial倉庫中,使用Git客戶端,基於網絡鏈接,使用git-remote-hg工具,可實現Git與Mercurial服務器倉庫的交互,該工具的獲取地址爲https://github.com/felipec/git-remote-hg.

git-remote-hg

首先需要下載和安裝git-remote-hg,

$ curl -o ~/bin/git-remote-hg https://raw.githubusercontent.com/felipec/git-remote-hg/master/git-remote-hg
$ chmod +x ~/bin/git-remote-hg

假定用戶的$PATH爲~/bin,git-remote-hg依賴於mercurial的python庫,如果用戶已安裝python,可直接下載該支持庫,

$ pip install mercurial

同時用戶還需要mercurial客戶端,從https://www.mercurial-scm.org/頁面可下載客戶端,當然用戶還需要一個供測試使用的mercurial,這裏配置了一個測試倉庫,如下,

$ hg clone http://selenic.com/repo/hello /tmp/hello

應用基礎

以下將基於典型工作流,實現兩個系統的交互,用戶將看到,兩個系統的相似之處,通常不會產生太大的衝突,首先克隆mercurial倉庫,

$ git clone hg::/tmp/hello /tmp/hello-git
$ cd /tmp/hello-git
$ git log --oneline --graph --decorate
* ac7955c (HEAD, origin/master, origin/branches/default, origin/HEAD,
refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master, master) Create a
makefile
* 65bb417 Create a standard "hello, world" program

注意,在mercurial倉庫中,使用了git clone命令,因爲git-remote-hg是一個底層命令,可使用類似的機制,實現Git的HTTP/S協議,由於Git和mercurial都允許所有客戶端,獲取倉庫的完整提交歷史,因此克隆命令可以兼容.

使用log命令,將顯示兩個提交,並會出現大量的引用標識,而這些引用標識大部分並不存在,爲了查看.git目錄,應當使用以下命令,

$ tree .git/refs
.git/refs
├── heads
│ └── master
├── hg
│ └── origin
│   ├── bookmarks
│   │ └── master
│   └── branches
│     └── default
├── notes
│ └── hg
├── remotes
│ └── origin
│   └── HEAD
└── tags

9 directories, 5 files

git-remote-hg會嘗試大多數Git風格的功能,以便在兩個不同的系統之間,關聯兩者的近似功能,refs/hg目錄是遠程倉庫引用的存儲路徑,比如refs/hg/origin/branches/default爲Git引用文件(校驗碼爲ac7955c),即master所指向的提交,同時refs/hg可等同於 refs/remotes/origin,但是bookmarks和branches之間存在差異.

git-remote-hg可使用notes/hg文件,將Git提交,轉換成Mercurial變更集ID號,如下,

$ cat notes/hg
d4c10386...

$ git cat-file -p d4c10386...
tree 1781c96...
author remote-hg <> 1408066400 -0800
committer remote-hg <> 1408066400 -0800

Notes for master

$ git ls-tree 1781c96...
100644 blob ac9117f... 65bb417...
100644 blob 485e178... ac7955c...

$ git cat-file -p ac9117f
0a04b987be5ae354b710cefeba0e2d9de7ad41a9

refs/notes/hg指向了一個文件樹,即Git對象數據庫中其他對象的名稱列表,git ls-tree可輸出該文件樹中所有對象的模式,類型,哈希值和文件名,之後用戶可選擇一個對象,比如ac9117f(master所指向的提交),該對象包含了一串哈希值0a04b98,即Mercurial變更集的ID號,並處於default分支的最新位置.

大多數情況下,上述操作不會引發錯誤,相比於Git遠程倉庫的處理,典型工作流並沒有太大的變動.另一個應當注意的問題,文件忽略的處理,雖然Git和Mercurial使用了類似的機制,但用戶無法向Mercurial倉庫,提交一個.gitignore文件,同時Git提供一種方法,直接在本地倉庫中實現文件忽略,並且Git也兼容Mercurial格式,可直接使用Mercurial文件忽略的配置文件,

$ cp .hgignore .git/info/exclude

.git/info/exclude可等同於.gitignore,但無需提交.

工作流

假定用戶完成了一部分開發,並提交到master分支,準備推送到遠程倉庫,而遠程倉庫的當前狀態如下,

$ git log --oneline --graph --decorate
* ba04a2a (HEAD, master) Update makefile
* d25d16f Goodbye
* ac7955c (origin/master, origin/branches/default, origin/HEAD,
refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Create a makefile
* 65bb417 Create a standard "hello, world" program

Git用戶的master分支,即爲origin/master,出現了兩個提交,同時這些提交都是本地提交,爲了獲得更多細節,可運行

$ git fetch
From hg::/tmp/hello
   ac7955c..df85e87 master -> origin/master
   ac7955c..df85e87 branches/default -> origin/branches/default
   
$ git log --oneline --graph --decorate --all
* 7b07969 (refs/notes/hg) Notes for default
* d4c1038 Notes for master
* df85e87 (origin/master, origin/branches/default, origin/HEAD,
refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Add some
documentation
| * ba04a2a (HEAD, master) Update makefile
| * d25d16f Goodbye
|/
* ac7955c Create a makefile
* 65bb417 Create a standard "hello, world" program

附帶–all選項,可查看git-remote-hg在內部使用的notes引用,其實可以忽略它,測試結果符合預期,origin/master只有一個提交,並在這個提交之後,提交歷史出現分支,雖然Mercurial可處理分支合併,但是用戶也不必過分期待,

$ git merge origin/master
Auto-merging hello.c
Merge made by the 'recursive' strategy.
hello.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

$ git log --oneline --graph --decorate
* 0c64627 (HEAD, master) Merge remote-tracking branch 'origin/master'
|\
| * df85e87 (origin/master, origin/branches/default, origin/HEAD,
refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Add some
documentation
* | ba04a2a Update makefile
* | d25d16f Goodbye
|/
* ac7955c Create a makefile
* 65bb417 Create a standard "hello, world" program

合併成功,當測試通過後,就可分享給其他協作者,

$ git push
To hg::/tmp/hello
   df85e87..0c64627 master -> master

查看Mercurial遠程倉庫,推送結果符合用戶的期望,

$ hg
 log -G --style compact
o   5[tip]:4,2 dc8fa4f932b8 2014-08-14 19:33 -0700  ben
|\    Merge remote-tracking branch 'origin/master'
| |
| o 4  64f27bcefc35 2014-08-14 19:27 -0700  ben
| |   Update makefile
| |
| o 3:1 4256fc29598f 2014-08-14 19:27 -0700  ben
| |   Goodbye
| |
@ | 2 7db0b4848b3c 2014-08-14 19:30 -0700  ben
|/    Add some documentation
|
o 1   82e55d328c8c 2005-08-26 01:21 -0700  mpm
|   Create a makefile
|
o 0   0a04b987be5a 2005-08-26 01:20 -0700  mpm
    Create a standard "hello, world" program

在變更集中,#2爲Mercurial的提交,#3和#4則爲git-remote-hg的提交,並使用Git完成推送.

分支與書籤

Git只有一類分支,當生成新提交時,該分支引用將會移動,但在Mercurial中,這類引用被稱爲書籤(bookmark),功能與Git分支相似,而Mercurial分支則可視爲變更集的記錄,這意味着Mercurial分支將一直保存在提交歷史中,如下例,

$ hg log -l 1
changeset: 6:8f65e5e02793
branch:    develop
tag:       tip
user:      Ben Straub <[email protected]>
date:      Thu Aug 14 20:06:38 2014 -0700
summary:   More documentation

注意branch文本行,Git無法模擬也無需模擬這類分支功能,因爲這兩種Mercurial分支,只需替換成Git引用,但是git-remote-hg必須理解兩者的差異.

創建Mercurial書籤與創建Git分支一樣簡單,如下,

$ git checkout -b featureA
Switched to a new branch 'featureA'

$ git push origin featureA
To hg::/tmp/hello
 * [new branch] featureA -> featureA

在Mercurial系統中,查看創建結果,

$ hg bookmarks
   featureA     5:bd5ac26f11f9
   
$ hg log --style compact -G
@   6[tip] 8f65e5e02793 2014-08-14 20:06 -0700 ben
|     More documentation
|
o   5[featureA]:4,2 bd5ac26f11f9 2014-08-14 20:02 -0700 ben
|\    Merge remote-tracking branch 'origin/master'
| |
| o 4   0434aaa6b91f 2014-08-14 20:01 -0700 ben
| |   update makefile
| |
| o 3:1 318914536c86 2014-08-14 20:00 -0700 ben
| |   goodbye
| |
o | 2   f098c7f45c4f 2014-08-14 20:01 -0700 ben
|/    Add some documentation
|
o  1  82e55d328c8c 2005-08-26 01:21 -0700 mpm
|    Create a makefile
|
o  0  0a04b987be5a 2005-08-26 01:20 -0700 mpm
     Create a standard "hello, world" program

在變更集#5中,出現了[featureA]標記,它與Git分支很相似,但有一點應當注意,在Git系統中,無法刪除書籤,至於創建Mercurial分支,實際上就是將分支,放置於branches名字空間下,

$ git checkout -b branches/permanent
Switched to a new branch 'branches/permanent'

$ vi Makefile
$ git commit -am 'A permanent change'
$ git push origin branches/permanent
To hg::/tmp/hello
 * [new branch] branches/permanent -> branches/permanent

在Mercurial系統中,查看創建結果,

$ hg branches
permanent   7:a4529d07aad4
develop     6:8f65e5e02793
default     5:bd5ac26f11f9 (inactive)

$ hg log -G
o changeset:    7:a4529d07aad4
| branch:       permanent
| tag:          tip
| parent:       5:bd5ac26f11f9
| user:         Ben Straub <[email protected]>
| date:         Thu Aug 14 20:21:09 2014 -0700
| summary:      A permanent change
|
| @ changeset:  6:8f65e5e02793
|/ branch:      develop
| user:         Ben Straub <[email protected]>
| date:         Thu Aug 14 20:06:38 2014 -0700
| summary:      More documentation
|
o changeset:    5:bd5ac26f11f9
|\ bookmark:    featureA
| | parent:     4:0434aaa6b91f
| | parent:     2:f098c7f45c4f
| | user:       Ben Straub <[email protected]>
| | date:       Thu Aug 14 20:02:21 2014 -0700
| | summary:    Merge remote-tracking branch 'origin/master'
[...]

permanent分支記錄了#7變更集,對於Git來說,上述兩條分支的操作是一樣的,但是用戶必須記住,Mercurial不支持提交歷史的修改,只能進行添加,因此上述的Mercurial倉庫很像是,交互式衍合以及強制推送之後的結果.

Git和Bazaar

在DVCS工具中,還有另一種主流工具Bazaar,它是一個免費的開源工具,並且是GNU項目的一部分,但是它的工作流與Git完全不同,同時它給出的命令關鍵字,讓人難以理解,尤其是分支的管理,很容易引發衝突,特別是具備Git使用經驗的人.

很多開源工具都可實現Bazaar的Git客戶端,這裏使用Felipe Contreras的項目,可從https://github.com/felipec/git-remote-bzr-bzr頁面中找到,首先下載和安裝該工具,

$ wget https://raw.github.com/felipec/git-remote-bzr/master/git-remote-bzr -O ~/bin/git-remote-bzr
$ chmod +x ~/bin/git-remote-bzr

新建Git倉庫(基於Bazaar倉庫)

使用bzr::前綴,可克隆Bazaar倉庫,之後需將Git倉庫目錄,直接放置到Bazaar倉庫中,使用ssh地址bzr+ssh://developer@[bazaar服務器名]:[項目名],克隆Bazaar倉庫,

$ git clone bzr::bzr+ssh://developer@mybazaarserver:myproject myProject-Git
$ cd myProject-Git

在克隆過程中,本地Git倉庫也已創建,但是該Git倉庫佔用了過多的磁盤空間,必須首先清理和壓縮新建的Git倉庫,尤其在大型項目中,

$ git gc --aggressive

分支

Bazaar倉庫可包含多條分支,git-remote-baz允許用戶克隆單條或所有分支,比如克隆單條分支,

$ git clone bzr::bzr://bzr.savannah.gnu.org/emacs/trunk emacs-trunk

或克隆整個Bazaar倉庫,

$ git clone bzr::bzr://bzr.savannah.gnu.org/emacs emacs

上述可獲取遠程倉庫的所有分支,但用戶可通過配置,在倉庫克隆中,只克隆指定分支,如下,

$ git config remote-bzr.branches 'trunk, xwindow'

有些遠程Bazaar倉庫,不允許用戶,查詢倉庫的所有分支,這時用戶只能手動設置,

$ git init emacs
$ git remote add origin bzr::bzr://bzr.savannah.gnu.org/emacs
$ git config remote-bzr.branches 'trunk, xwindow'
$ git fetch

可忽略文件

由於當前項目受控於Bazaar,因此無法創建.gitignore文件,因爲它對項目控制並無意義,同時還會干擾到其他協作者,此時需創建一個.git/info/exclude文件,它既可以是符號連接,也可以是一個正常文件.

Bazaar中忽略文件的管控機制,與Git相似,但需要注意兩個功能,

  1. !!符號之後,可附帶一個忽略文件的模板,也可使用!符號
  2. 行首的RE::之後,可指定一個python風格的正則表達式(Git只允許shell風格)

除此之外,還需要考慮兩種不同的應用場景,

  1. 如果分支克隆和倉庫克隆中,都未包含.bzrignore文件,用戶可創建一個符號鏈接,ln -s .bzrignore .git/info/exclude
  2. 另外用戶必須創建.git/info/exclude文件,其中的配置也應當與.bzrignore相同

剩下的問題,保證.git/info/exclude和.bzrignore之間的配置同步,最簡單的方法,創建.git/info/exclude符號鏈接,指向.bzrignore.

獲取變更

爲獲取遠程倉庫的更新,可使用pull命令,假定遠程倉庫的master分支出現更新,用戶可將遠程更新,合併或衍合到本地的origin/master分支,

$ git pull --rebase origin

推送變更

由於Bazaar包含了提交合並的機制,因此推送一個合併提交,並不會引發問題,用戶只需在Git中,將特性分支合併到master分支,再進行提交,

$ git push origin master

警告

應當注意Git的橋接工具,通常存在一些應用限制,比如以下命令將無法工作,
- git push origin 分支將被刪除,因爲Bazaar不接受這種方式的引用刪除
- git push origin old:new 只能推送到old,無法限定new
- git push --dry-run origin branch 只能完成推送,不支持其他功能

Git和Perforce

Perforce在企業級用戶中相當流行,首個版本發佈於1995年,其中包含了日期約束,同時用戶必須一直連接單箇中心服務器,本地磁盤也只能保留一個版本,所以這個版本控制系統只適合於特定問題的修復,但目前依然有大量項目在使用Perforce.

目前有兩種工具,可橋接Git和Perforce,其一爲Git Fusion,它可導出Perforce倉庫的子樹,並視爲一個可讀寫的Git倉庫,其二爲git-p4,可將Git視爲Perforce的客戶端,無需重新配置Perforce服務器.

Git Fusion

可從http://www.perforce.com/git-fusion頁面中,找到Git Fusion,它可以實現Git倉庫與Perforce服務器之間的同步.

創建

首先需要一個虛擬器,這裏採用VirtualBox,在虛擬機中,可運行Perforce後臺和Git Fusion,從http://www.perforce.com/downloads/Perforce/20-Usert頁面,找到虛擬機鏡像,將鏡像添加到虛擬機,之後啓動虛擬機,用戶需要配置三個linux賬號的密碼,分別是root/perforce/git,還需要創建一個新賬號,以區別不同的使用者,一切完成後,將看到以下顯示,
在這裏插入圖片描述
記住界面給出的IP地址,之後需使用它,首先需要創建一個Perforce用戶賬號,點擊下部的Login,使用root賬號登錄系統,使用以下命令,創建一個Perforce用戶賬號,

$ p4 -p localhost:1666 -u super user -f john
$ p4 -p localhost:1666 -u john passwd
$ exit

可自定義用戶的默認屬性,之後需輸入兩次用戶密碼,最後退出當前會話,完成用戶創建.之後需告知Git,無需驗證SSL證書,雖然Git Fusion鏡像中包含了一個證書,但Perforce後臺無法將證書,匹配虛擬機的IP地址,所以Git將拒絕HTTPS連接,因此可選擇重新安裝證書,或者取消Git的SSL驗證,如下,

$ export GIT_SSL_NO_VERIFY=true

測試工作環境是否正常,

$ git clone https://10.0.1.254/Talkhouse
Cloning into 'Talkhouse'...
Username for 'https://10.0.1.254': john
Password for 'https://[email protected]':
remote: Counting objects: 630, done.
remote: Compressing objects: 100% (581/581), done.
remote: Total 630 (delta 172), reused 0 (delta 0)
Receiving objects: 100% (630/630), 1.22 MiB | 0 bytes/s, done.
Resolving deltas: 100% (172/172), done.
Checking connectivity... done.

基於HTTPS連接,克隆示例項目的測試已通過,同時在虛擬機鏡像中,創建了john賬號,Git詢問SSL證書的步驟已被跳過.

配置

Git Fusion已安裝成功,配置過程也很簡單,也就是將Perforce服務器的//.git-fusion目錄,映射到本地工作區,之後可查看對應目錄,

$ tree
.
├── objects
│  ├── repos
│  │  └── [...]
│  └── trees
│     └── [...]
│
├── p4gf_config
├── repos
│  └── Talkhouse
│     └── p4gf_config
└── users
   └── p4gf_usermap
   
498 directories, 287 files

Git Fusion的內部目錄爲objects,它可實現Perforce和Git之間的對象映射,用戶需小心對待該目錄,根目錄下,還包含了全局配置文件p4gf_config,每個Perforce倉庫中都會包含該配置文件,其中可設定Git Fusion的操作模式,如下,

[repo-creation]
charset = utf8

[git-to-perforce]
change-owner = author
enable-git-branch-creation = yes
enable-swarm-reviews = yes
enable-git-merge-commits = yes
enable-git-submodules = yes
preflight-commit = none
ignore-author-permissions = no
read-permission-check = none
git-merge-avoidance-after-change-num = 12107

[perforce-to-git]
http-url = none
ssh-url = none

[@features]
imports = False
chunked-push = False
matrix2 = False
parallel-push = False

[authentication]
email-case-sensitivity = no

用戶不要隨意修改上述配置,同時該文件的格式爲ini,全局配置文件p4gf_config可被倉庫的p4gf_config所覆蓋,比如repos/Talkhouse/p4gf_config,該文件的[@repo]配置段,與全局配置不同,

[Talkhouse-master]
git-branch-name = master
view = //depot/Talkhouse/main-dev/... ...

其中包含了Perforce分支和Git分支的映射,給出的分支名必須是唯一的,git-branch-name可將一個笨重的路徑名,轉換成一個更友好的名稱,view可設定,需映射到Git倉庫的Perforce文件,這裏使用的標準模式,當然也可使用其他的映射模式,如下,

[multi-project-mapping]
git-branch-name = master
view = //depot/project1/main/... project1/...
       //depot/project2/mainline/... project2/...

最後需要注意的文件是users/p4gf_usermap,它可實現Perforce賬號與Git賬號的映射,當然用戶無需手動使用該功能,當一個Perforce變更集轉換成一個Git提交時,Git Fusion將默認查找Perforce賬號,並將賬號信息,充填Git提交的作者名/提交者名和郵箱地址,當反向轉換時,將基於Git提交的作者郵箱地址,查找對應的Perforce賬號,在大多數情況下,該操作不會產生問題,映射文件如下,

john [email protected] "John Doe"
john [email protected] "John Doe"
bob [email protected] "Anon X. Mouse"
joe [email protected] "Anon Y. Mouse"

每行文本都可設定單個賬號的映射,每行起始的賬號名和郵箱地址爲Perforce賬號,如果Git提交中需要多個郵箱地址,可創建多行文本,映射同一個Perforce賬號,注意最後兩行文本,即bob和joe,employee是一個無需公開發布的內部項目,因此可使用虛構的信息,但是郵件地址和Git賬號必須是唯一的.

工作流

之前已介紹過,Perforce Git Fusion可提供雙向的橋接方式,查看Git倉庫的工作流,假定需要克隆一個Jam項目(Perforce),

$ git clone https://10.0.1.254/Jam
Cloning into 'Jam'...
Username for 'https://10.0.1.254': john
Password for 'https://[email protected]':
remote: Counting objects: 2070, done.
remote: Compressing objects: 100% (1704/1704), done.
Receiving objects: 100% (2070/2070), 1.21 MiB | 0 bytes/s, done.
remote: Total 2070 (delta 1242), reused 0 (delta 0)
Resolving deltas: 100% (1242/1242), done.
Checking connectivity... done.

$ git branch -a
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master
  remotes/origin/rel2.1
  
$ git log --oneline --decorate --graph --all
* 0a38c33 (origin/rel2.1) Create Jam 2.1 release branch.
| * d254865 (HEAD, origin/master, origin/HEAD, master) Upgrade to latest metrowerks on
Beos -- the Intel one.
| * bd2f54a Put in fix for jam's NT handle leak.
| * c0f29e7 Fix URL in a jam doc
| * cc644ac Radstone's lynx port.
[...]

首先Git Fusion會將Perforce提交歷史中所有的變更集,轉換成Git提交,這些操作發生在服務端,因此速度較快,當然也取決於變更集的規模,後續的數據傳輸,才取決於Git的傳輸速度.

此時的Perforce倉庫已相當近似於Git倉庫,其中包含了三條分支,並且之前已完成了一部分開發,並生成了一組提交,從git log --oneline --decorate --graph --all命令的輸出可知,因此Git用戶需將這些提交,同步到本地,

$ git fetch
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 2), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From https://10.0.1.254/Jam
   d254865..6afeb15 master -> origin/master
 
$ git log --oneline --decorate --graph --all
* 6afeb15 (origin/master, origin/HEAD) Update copyright
| * cfd46ab (HEAD, master) Add documentation for new feature
| * a730d77 Whitespace
|/
* d254865 Upgrade to latest metrowerks on Beos -- the Intel one.
* bd2f54a Put in fix for jam's NT handle leak.
[...]

實際上6afeb15提交來自於Perforce倉庫,但是與Git提交併無差別,接着可查看Perforce服務端如何處理合並提交,

$ git merge origin/master
Auto-merging README
Merge made by the 'recursive' strategy.
README | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

$ git push
Counting objects: 9, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (9/9), done.
Writing objects: 100% (9/9), 917 bytes | 0 bytes/s, done.
Total 9 (delta 6), reused 0 (delta 0)
remote: Perforce: 100% (3/3) Loading commit tree into memory...
remote: Perforce: 100% (5/5) Finding child commits...
remote: Perforce: Running git fast-export...
remote: Perforce: 100% (3/3) Checking commits...
remote: Processing will continue even if connection is closed.
remote: Perforce: 100% (3/3) Copying changelists...
remote: Perforce: Submitting new Git commit objects to Perforce: 4
To https://10.0.1.254/Jam
   6afeb15..89cba2b master -> master

從上述輸出可知,Git合併提交已推送到Perforce遠程倉庫,使用Perforce環境的工具,可看到合併提交已放置到匿名目錄中,因此Perforce和Git可以協同工作.

Git-p4

Git和Perforce雙向橋接的另一個工具爲Git-p4,它運行在Git倉庫中,因此用戶無法配置Perforce服務端,雖然Git-p4還不如Git Fusion靈活和完善,但是它無需侵入到Perforce系統中,另外p4支持工具,可從http://www.perforce.com/downloads/Perforce/20-User頁面下載.

創建

首先配置git-p4依賴的p4命令行客戶端,所需的環境變量,

$ export P4PORT=10.0.1.254:1666
$ export P4USER=john
配置

運行克隆命令,

$ git p4 clone //depot/www/live www-shallow
Importing from //depot/www/live into www-shallow
Initialized empty Git repository in /private/tmp/www-shallow/.git/
Doing initial import of //depot/www/live/ from revision #head into
refs/remotes/p4/master

上述命令只完成了"淺"克隆,只有最新的Perforce版本,纔會導入Git,這是Perforce的設計理念,用戶具有權限,並不是每個用戶都能獲取所有版本,查看完成克隆的Git倉庫,

$ cd myproject
$ git log --oneline --all --graph --decorate
* 70eaf78 (HEAD, p4/master, p4/HEAD, master) Initial import of //depot/www/live/ from
the state at revision #head

在上述輸出中,p4即爲Perforce遠程倉庫,但事實上,這個遠程倉庫並不存在,

$ git remote -v

不會顯示Perforce遠程倉庫,Git-p4只是創建一些引用,用於描述遠程倉庫的狀態,這使得git log的輸出,看上去很像一個遠程倉庫,由於Perforce遠程倉庫不受Git的管理,因此也無法直接推送.

工作流

假定Git用戶完成了一部分開發,並生成了兩次提交,如下,

$ git log --oneline --all --graph --decorate
* 018467c (HEAD, master) Change page title
* c0fb617 Update link
* 70eaf78 (p4/master, p4/HEAD) Initial import of //depot/www/live/ from the state at revision #head

這時需要將兩次提交,推送到Perforce倉庫,

$ git p4 sync
git p4 sync
Performing incremental import into refs/remotes/p4/master git branch
Depot paths: //depot/www/live/
Import destination: refs/remotes/p4/master
Importing revision 12142 (100%)

$ git log --oneline --all --graph --decorate
* 75cd059 (p4/master, p4/HEAD) Update copyright
| * 018467c (HEAD, master) Change page title
| * c0fb617 Update link
|/
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

從上述信息可知,master和p4/master之間產生了分離點,Perforce的分支不同於Git,推送合併提交併無意義(分支並不會合併),因此Git-p4推薦Git用戶使用衍合,以便得到一個更簡潔的提交歷史,

$ git p4 rebase
Performing incremental import into refs/remotes/p4/master git branch
Depot paths: //depot/www/live/
No changes to import!
Rebasing the current branch onto remotes/p4/master
First, rewinding head to replay your work on top of it...
Applying: Update link
Applying: Change page title
 index.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

git p4 rebase可獲得一個更簡明的提交記錄,並等同於git rebase p4/master,這是更聰明的方法,尤其在多分支的合併中,基本能實現兩個倉庫系統之間的完美近似.這時提交歷史已成線性,可將提交推送到Perforce遠程倉庫,git p4 submit將基於p4/master和master之間的Git提交,創建一個Perforce新版本,同時會打開編輯器,並顯示以下的描述文件,

# A Perforce Change Specification.
#
# Change:      The change number. 'new' on a new changelist.
# Date:        The date this specification was last modified.
# Client:      The client on which the changelist was created. Read-only.
# User:        The user who created the changelist.
# Status:      Either 'pending' or 'submitted'. Read-only.
# Type:        Either 'public' or 'restricted'. Default is 'public'.
# Description: Comments about the changelist. Required.
# Jobs:        What opened jobs are to be closed by this changelist.
#              You may delete jobs from this list. (New changelists only.)
# Files:       What opened files from the default changelist are to be added
#              to this changelist. You may delete files from this list.
#              (New changelists only.)

Change:      new
Client:      john_bens-mbp_8487
User:        john
Status:      new
Description: Update link
Files:       //depot/www/live/index.html   # edit

######## git author [email protected] does not match your p4 account.
######## Use option --preserve-user to modify authorship.
######## Variable git-p4.skipUserNameCheck hides this message.
######## everything below this line is just the diff #######
--- //depot/www/live/index.html 2014-08-31 18:26:05.000000000 0000
+++ /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/index.html  2014-
08-31 18:26:05.000000000 0000
@@ -60,7 +60,7 @@
 </td>
 <td valign=top>
 Source and documentation for
-<a href="http://www.perforce.com/jam/jam.html">
+<a href="jam.html">
 Jam/MR</a>,
 a software build tool.
 </td>

當運行p4 submit時,可看到大致相同的內容,但在文件末尾,由git p4 submit充填的變更信息,則不相同,當需要爲一個提交或變更集,提供一個名稱時,Git-p4將會沿用Git和Perforce的設置,但大多數情況下,用戶都需要進行修改,比如Git提交的作者並無對應的Perforce賬號,則用戶需要在轉換後的變更集中,修改相關信息.當完成相關信息的修改後,推送纔可繼續,如下,

$ git p4 submit
Perforce checkout for depot path //depot/www/live/ located at /Users/ben/john_bens-
mbp_8487/john_bens-mbp_8487/depot/www/live/
Synchronizing p4 checkout...
... - file(s) up-to-date.
Applying dbac45b Update link
//depot/www/live/index.html#4 - opened for edit
Change 12143 created with 1 open file(s).
Submitting change 12143.
Locking 1 files ...
edit //depot/www/live/index.html#5
Change 12143 submitted.
Applying 905ec6a Change page title
//depot/www/live/index.html#5 - opened for edit
Change 12144 created with 1 open file(s).
Submitting change 12144.
Locking 1 files ...
edit //depot/www/live/index.html#6
Change 12144 submitted.
All commits applied!
Performing incremental import into refs/remotes/p4/master git branch
Depot paths: //depot/www/live/
Import destination: refs/remotes/p4/master
Importing revision 12144 (100%)
Rebasing the current branch onto remotes/p4/master
First, rewinding head to replay your work on top of it...

$ git log --oneline --all --graph --decorate
* 775a46f (HEAD, p4/master, p4/HEAD, master) Change page title
* 05f1ade Update link
* 75cd059 Update copyright
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

注意,在Git提交轉換成Perforce變更集的過程中,如果用戶希望將Git提交,重組到單個變更集中,應在執行git p4 submit之前,進行一次衍合,這時所有Git提交的校驗碼都將改變,因爲git-p4會在每個提交文件的末尾,添加一行幫助信息,如下,

$ git log -1
commit 775a46f630d8b46535fc9983cf3ebe6b9aa53145
Author: John Doe <[email protected]>
Date:   Sun Aug 31 10:31:44 2014 -0800

    Change page title
    
    [git-p4: depot-paths = "//depot/www/live/": change = 12144]

如果推送合併提交,Perforce遠程倉庫將會如何處理,以下是Git倉庫和Perforce倉庫的提交歷史,

$ git log --oneline --all --graph --decorate
* 3be6fd8 (HEAD, master) Correct email address
* 1dcbf21 Merge remote-tracking branch 'p4/master'
|\
| * c4689fc (p4/master, p4/HEAD) Grammar fix
* | cbacd0a Table borders: yes please
* | b4959b6 Trademark
|/
* 775a46f Change page title
* 05f1ade Update link
* 75cd059 Update copyright
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

從輸出信息可知,在775a46f提交之後,Git提交歷史和Perforce提交歷史,產生了分離,Git提交歷史中,包含了兩個提交,其中之一爲,已合併到Perforce倉庫的合併提交,如果將Perforce倉庫中最新的單個變更集,推送到Perforce遠程倉庫,如下,

$ git p4 submit -n
Perforce checkout for depot path //depot/www/live/ located at /Users/ben/john_bens-
mbp_8487/john_bens-mbp_8487/depot/www/live/
Would synchronize p4 checkout in /Users/ben/john_bens-mbp_8487/john_bens-
mbp_8487/depot/www/live/
Would apply
  b4959b6 Trademark
  cbacd0a Table borders: yes please
  3be6fd8 Correct email address

-n選項即爲–dry-run的縮寫,它可在推送成功後,顯示命令操作的細節,從輸出信息中可知,首先創建了三個Perforce變更集,分別對應三個非合併提交,以下是推送之後的結果,

$ git log --oneline --all --graph --decorate
* dadbd89 (HEAD, p4/master, p4/HEAD, master) Correct email address
* 1b79a80 Table borders: yes please
* 0097235 Trademark
* c4689fc Grammar fix
* 775a46f Change page title
* 05f1ade Update link
* 75cd059 Update copyright
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

Perforce本地倉庫變爲線性歷史,因爲在推送之前,進行提交的衍合,所以用戶無需擔心Git倉庫的操作,會產生不兼容的Perforce提交,因爲在推送到Perforce遠程倉庫前,都將被衍合.

分支

如果Perforce倉庫包含了多條分支,無需奇怪,只是git-4p進行處理,使得轉換結果更類似於Git倉庫,假定Perforce倉庫如下,

//depot
└── project
  ├── main
  └── dev

Git-p4可自動檢測當前狀態,並進行必要的轉換,

$ git p4 clone --detect-branches //depot/project@all
Importing from //depot/project@all into project
Initialized empty Git repository in /private/tmp/project/.git/
Importing revision 20 (50%)
    Importing new branch project/dev
    Resuming with change 20
Importing revision 22 (100%)
Updated branches: main dev

$ cd project; git log --oneline --all --graph --decorate
* eae77ae (HEAD, p4/master, p4/HEAD, master) main
| * 10d55fb (p4/project/dev) dev
| * a43cfae Populate //depot/project/main/... //depot/project/dev/....
|/
* 2b83451 Project init

@all表示路徑下的所有目錄,並可告知git-4p,不僅克隆最新的變更集,而且將克隆@all下的所有變更集,–detect-branches選項,可告知git-4p,將Perforce分支映射爲Git分支引用,這些映射關係並不存在於Perforce遠程倉庫,

$ git init project
Initialized empty Git repository in /tmp/project/.git/
$ cd project
$ git config git-p4.branchList main:dev
$ git clone --detect-branches //depot/project@all .

將git-p4.branchList配置變量,設爲main:dev,可告知git-4p,Perforce倉庫存在兩條分支main和dev,同時dev爲main的子分支.

使用git checkout -b dev p4/project/dev, 基於p4/project/dev分支,創建一個本地Git分支dev,如果用戶生成了提交,git-4p能夠區分用戶需提交的分支,但是git-4p無法混合使用淺克隆和多分支克隆,如果Perforce倉庫保存了一個巨型項目,幷包含了多條分支,這時獲取每條分支,都必須運行一次git p4 clone.

Git-p4只能同步和推送已存在的分支,每次只能得到一個線性的變更集,如果用戶在Git系統中,合併了兩條分支,再將合併結果,轉換一個新的變更集,並推送到Perforce遠程倉庫時,只能得到一組文件變更的記錄,與分支相關的信息,將全部丟失.

Git和TFS

當Git在Windows開發者中越來越流行,在Windows系統下,用戶還可以選擇TFS(Microsoft’s Team Foundation Server),這是一個協作套件,其中包含缺陷和工作項(work-item)的跟蹤,可使用Scrum(敏捷)和其他類型的軟件開發過程,代碼瀏覽,同時TFS還包含了TFVC(Team Foundation Version Control)系統,Git支持TFS(2013版本)的某些新功能,而TFS的範疇遠遠超出了版本控制的需求,所以Windows開發者在大多數時間裏,一直在使用TFVC.

工具集

目前存在兩種橋接工具git-tf和git-tfs,git-tfs(https://github.com/git-tfs/git-tfs)是一個.NET項目,只能運行在Windows系統下,爲了運行Git倉庫,必須使用.NET綁定庫libgit2,該庫可支持Git的高性能,並保留足夠的靈活性,但是無法完全兼容Git,在一些操作中,需要直接調用Git命令,因此不存在人爲的限制,同時git-tfs對於TFVC的支持相當完善,可使用Visual Studio組件,完成TFS服務器的操作,這也意味着用戶需要安裝Visual Studio(2010版本,以及Express 2012版本)或是VS SDK.

Git-tf(https://gittf.codeplex.com)是一個java項目,可使用JGit(Git的JVM接口),訪問Git倉庫,基本兼容Git命令,但是不支持分支功能.

當然工具始終都存在優點和確定,是否適合,主要取決於應用場景.

git-tf

首先需要克隆TFS倉庫,

$ git tf clone https://tfs.codeplex.com:443/tfs/TFS13 $/myproject/Main project_git

第一個參數爲TFVC倉庫的URL,第二個參數爲用戶的本地保存路徑,第三個參數爲本地Git倉庫名(可選參數),Git-tf每次只能使用一條分支,如果存在多條TFVC分支,則需要克隆多條分支,之後可查看Git倉庫,

$ cd project_git
$ git log --all --oneline --decorate
512e75a (HEAD, tag: TFS_C35190, origin_tfs/tfs, master) Checkin message

同樣這也是一次淺克隆,只會下載最新的變更集,TFVC的設計理念同樣是,並非所有的客戶端,都能獲取完整的提交歷史,git-tf的默認設置,即獲取最新版本,爲了克隆特定分支的完整提交歷史,可使用 --deep選項,

$ git tf clone https://tfs.codeplex.com:443/tfs/TFS13 $/myproject/Main project_git --deep
Username: domain\user
Password:
Connecting to TFS...
Cloning $/myproject into /tmp/project_git: 100%, done.
Cloned 4 changesets. Cloned last changeset 35190 as d44b17a

$ cd project_git
$ git log --all --oneline --decorate
d44b17a (HEAD, tag: TFS_C35190, origin_tfs/tfs, master) Goodbye
126aa7b (tag: TFS_C35189)
8f77431 (tag: TFS_C35178) FIRST
0745a25 (tag: TFS_C35177) Created team project folder $/tfvctest via the Team Project Creation Wizard

類似於TFS_C35189的標記,可幫助用戶瞭解,Git提交所對應的TFVC變更集,當然用戶可配置git config git-tf.tag false,取消上述映射關係的顯示,同時git-tf會在.git/git-tf文件中,保證完整的提交與變更集的映射.

git-tfs

該工具的克隆操作會有點複雜,如下,

PS> git tfs clone --with-branches \
    https://username.visualstudio.com/DefaultCollection \
    $/project/Trunk project_git
Initialized empty Git repository in C:/Users/ben/project_git/.git/
C15 = b75da1aba1ffb359d00e85c52acb261e4586b0c9
C16 = c403405f4989d73a2c3c119e79021cb2104ce44a
Tfs branches found:
- $/tfvc-test/featureA
The name of the local branch will be : featureA
C17 = d202b53f67bde32171d5078968c644e562f1c439
C18 = 44cd729d8df868a8be20438fdeeefb961958b674

–with-branches選項,可生成TVFC分支與Git分支的映射,同時還能將TFVC分支,配置成本地的Git分支,如果用戶需要克隆TFS的所有分支,或是需要合併分支,強烈推薦該選項,但是TFS 2010之前的版本,無法支持該功能,之後可查看已生成的Git倉庫,

PS> git log --oneline --graph --decorate --all
* 44cd729 (tfs/featureA, featureA) Goodbye
* d202b53 Branched from $/tfvc-test/Trunk
* c403405 (HEAD, tfs/default, master) Hello
* b75da1a New project

PS> git log -1
commit c403405f4989d73a2c3c119e79021cb2104ce44a
Author: Ben Straub <[email protected]>
Date:   Fri Aug 1 03:41:59 2014 +0000
    Hello
    git-tfs-id: [https://username.visualstudio.com/DefaultCollection]$/myproject/Trunk;C16

從上述輸出可知,存在兩條本地分支master和featureA,對應TFVC的初始分支Trunk和子分支featureA,同時tfs遠程服務器也包含了一組引用default和featureA,用於表示TFVC分支,而Git-fs已將tfs/default分支和子分支,克隆到本地.

應當注意,git-tfs-id提供的提交信息,git-fs將使用這些信息,標記Git提交與TFVC變更集的對應關係,同時存在一個隱含意義,即Git提交在推送到TFVC的前後,它的校驗碼將發生變化.

工作流

無論使用哪個工具,首先必須完成一組Git配置的設定,以免出現問題,

$ git config set --local core.ignorecase=true
$ git config set --local core.autocrlf=false

之後用戶可繼續項目的開發,但是TFVC和TFS的一些功能,會將更多的複雜性,引入工作流,

  1. TFVC無法描述特性分支,這與Git分支並不一致.
  2. 雖然TFVC允許用戶在服務器上查看文件,但是文件已被鎖定,無法進行編輯,這當然無法阻止用戶對本地倉庫的文件修改,因此除非用戶將文件變更,推送到TFVC服務器,否則服務器的文件不會被修改.
  3. TFS具備門禁功能,當門禁關閉之後,可保證TFS測試周期不被打擾,TFVC shelve支持該功能,因此用戶需要手動處理這類問題,同時git-tfs提供了checkintool命令,用於查看門禁的狀態.
git-tf工作流

當用戶完成了一部分開發,並在master分支上,生成了一組提交,並準備推送到TFVC服務器,首先可查看本地Git倉庫,

$ git log --oneline --graph --decorate --all
* 4178a82 (HEAD, master) update code
* 9df2ae3 update readme
* d44b17a (tag: TFS_C35190, origin_tfs/tfs) Goodbye
* 126aa7b (tag: TFS_C35189)
* 8f77431 (tag: TFS_C35178) FIRST
* 0745a25 (tag: TFS_C35177) Created team project folder $/tfvctest via the Team Project Creation Wizard

用戶決定將4178a82提交,推送到TFVC服務器,首先需查看TFVC服務器上,是否有未同步的最新數據,

$ git tf fetch
Username: domain\user
Password:
Connecting to TFS...
Fetching $/myproject at latest changeset: 100%, done.
Downloaded changeset 35320 as commit 8ef06a8. Updated FETCH_HEAD.

$ git log --oneline --graph --decorate --all
* 8ef06a8 (tag: TFS_C35320, origin_tfs/tfs) just some text
| * 4178a82 (HEAD, master) update code
| * 9df2ae3 update readme
|/
* d44b17a (tag: TFS_C35190) Goodbye
* 126aa7b (tag: TFS_C35189)
* 8f77431 (tag: TFS_C35178) FIRST
* 0745a25 (tag: TFS_C35177) Created team project folder $/tfvctest via the Team Project Creation Wizard

這時已有新提交,同時提交歷史已出現分支,可選擇兩種處理方法,

  1. 創建合併提交,但TFVC無法理解合併提交,如果用戶推送合併提交,將會得到一個與Git完全不同的結果,甚至會引發衝突,只能將所有變更,打包放入一個變更集中,這大概是最簡單的選擇.

  2. 衍合提交,以保證一個線性的提交歷史,這意味着每個Git提交,都將轉換成一個TFVC變更集,這種方法對於大多數功能,都非常友好,推薦使用它,因此git-tf提供了相應的命令git tf pull --rebase.

$ git rebase FETCH_HEAD
First, rewinding head to replay your work on top of it...
Applying: update readme
Applying: update code

$ git log --oneline --graph --decorate --all
* 5a0e25e (HEAD, master) update code
* 6eb3eb5 update readme
* 8ef06a8 (tag: TFS_C35320, origin_tfs/tfs) just some text
* d44b17a (tag: TFS_C35190) Goodbye
* 126aa7b (tag: TFS_C35189)
* 8f77431 (tag: TFS_C35178) FIRST
* 0745a25 (tag: TFS_C35177) Created team project folder $/tfvctest via the Team Project Creation Wizard

git-tf可選擇,生成一個變更集,並放入所有的文件變更(使用默認選項 --shallow),或是爲每個Git提交,生成一個新的變更集(使用 --deep選項),以下將生成一個包含所有文件變更的變更集,

$ git tf checkin -m 'Updating readme and code'
Username: domain\user
Password:
Connecting to TFS...
Checking in to $/myproject: 100%, done.
Checked commit 5a0e25e in as changeset 35348

$ git log --oneline --graph --decorate --all
* 5a0e25e (HEAD, tag: TFS_C35348, origin_tfs/tfs, master) update code
* 6eb3eb5 update readme
* 8ef06a8 (tag: TFS_C35320) just some text
* d44b17a (tag: TFS_C35190) Goodbye
* 126aa7b (tag: TFS_C35189)
* 8f77431 (tag: TFS_C35178) FIRST
* 0745a25 (tag: TFS_C35177) Created team project folder $/tfvctest via the \
          Team Project Creation Wizard

新標記TFS_C35348保存了Git提交5a0e25e,應當注意,並不是所有的Git提交,都需要精準對應到TFVC,比如Git提交6eb3eb5,並未推送到TFVC服務器,以上操作就是典型的工作流,同時用戶還需要考慮其他一些問題,
- Git-tf無法兼容Git的分支概念,每條TFVC分支都只能單獨創建一個Git倉庫.
- 協作開發只能選擇TFVC或Git,不能混用兩者,相同TFVC倉庫的多次克隆,將會導致Git提交出現的不同校驗值.
- 如果開發組選擇了Git協作,並定期同步到TFVC,那麼連接TFVC必須是同一個Git倉庫.

git-tfs工作流

在之前的工作場景中,使用git-tfs,首先Git倉庫的master分支已生成了新提交,

PS> git log --oneline --graph --all --decorate
* c3bd3ae (HEAD, master) update code
* d85e5a2 update readme
| * 44cd729 (tfs/featureA, featureA) Goodbye
| * d202b53 Branched from $/tfvc-test/Trunk
|/
* c403405 (tfs/default) Hello
* b75da1a New project

同時TFVC倉庫中,也出現了需同步的新數據,

PS> git tfs fetch
C19 = aea74a0313de0a391940c999e51c5c15c381d91d

PS> git log --all --oneline --graph --decorate
* aea74a0 (tfs/default) update documentation
| * c3bd3ae (HEAD, master) update code
| * d85e5a2 update readme
|/
| * 44cd729 (tfs/featureA, featureA) Goodbye
| * d202b53 Branched from $/tfvc-test/Trunk
|/
* c403405 Hello
* b75da1a New project

協作者推送的TFVC變更集,已導致tfs/default遠程分支,移動到提交aea74a0,爲了解決提交歷史的分叉,可選擇兩種方法,

  1. 衍合提交
  2. 合併提交

這裏選擇衍合提交,

PS> git rebase tfs/default
First, rewinding head to replay your work on top of it...
Applying: update readme
Applying: update code
PS> git log --all --oneline --graph --decorate
* 10a75ac (HEAD, master) update code
* 5cec4ab update readme
* aea74a0 (tfs/default) update documentation
| * 44cd729 (tfs/featureA, featureA) Goodbye
| * d202b53 Branched from $/tfvc-test/Trunk
|/
* c403405 Hello
* b75da1a New project

此時可將衍合提交推送到TFVC服務器,這裏使用rcheckin命令,可將每個Git衍合提交,轉換成一個TFVC變更集,並推送到TFVC服務器的第一個tfs遠程分支,

PS> git tfs rcheckin
Working with tfs remote: default
Fetching changes from TFS to minimize possibility of late conflict...
Starting checkin of 5cec4ab4 'update readme'
 add README.md
C20 = 71a5ddce274c19f8fdc322b4f165d93d89121017
Done with 5cec4ab4b213c354341f66c80cd650ab98dcf1ed, rebasing tail onto new TFS-commit...
Rebase done successfully.
Starting checkin of b1bf0f99 'update code'
 edit .git\tfs\default\workspace\ConsoleApplication1/ConsoleApplication1/Program.cs
C21 = ff04e7c35dfbe6a8f94e782bf5e0031cee8d103b
Done with b1bf0f9977b2d48bad611ed4a03d3738df05ea5d, rebasing tail onto new TFS-commit...
Rebase done successfully.
No more to rcheckin.

PS> git log --all --oneline --graph --decorate
* ff04e7c (HEAD, tfs/default, master) update code
* 71a5ddc update readme
* aea74a0 update documentation
| * 44cd729 (tfs/featureA, featureA) Goodbye
| * d202b53 Branched from $/tfvc-test/Trunk
|/
* c403405 Hello
* b75da1a New project

每次轉換,都將在提交文件末尾,添加git-tfs-id行,其中包含了轉換信息,同時也將改變Git提交的校驗值,用戶應當該問題,以免產生失誤.

TFS還包含了版本控制的很多集成功能,比如工作項追蹤,設計預覽,門禁檢查等等,如果基於命令行,使用這些功能,將會非常複雜和笨重,因而git-tfs提供了一個圖形化工具checkintool,同時Git-tfs也允許,Git對TFVC分支進行管理,如下,

PS> git tfs branch $/tfvc-test/featureBee
The name of the local branch will be : featureBee
C26 = 1d54865c397608c004a2cadce7296f5edc22a7e5

PS> git log --oneline --graph --decorate --all
* 1d54865 (tfs/featureBee) Creation branch $/myproject/featureBee
* ff04e7c (HEAD, tfs/default, master) update code
* 71a5ddc update readme
* aea74a0 update documentation
| * 44cd729 (tfs/featureA, featureA) Goodbye
| * d202b53 Branched from $/tfvc-test/Trunk
|/
* c403405 Hello
* b75da1a New project

在TFVC中,創建一個分支,等同於添加一個變更集,同時也可視爲一個Git提交,在上述命令中,創建了tfs/featureBee遠程分支,但HEAD一直會指向master分支,如果用戶需要在新分支上進行開發,則需要基於提交1d54865,創建新的提交,或是創建一個特性分支.

9.2 項目遷移

SVN

使用之前介紹的git svn clone,克隆一個SVN倉庫,再停用SVN服務器,之後再將項目,推送到一個Git服務器,項目遷移就完成了,如果用戶需要之前提交的完整歷史,則需要實現SVN服務器的快速獲取.

當然導入過程並不完美,即使遷移操作都正確,也需要耗費大量的時間,第一個問題,就是作者信息,在SVN中,每個提交者都有一個賬號,該賬號將保存在提交信息中,如果用戶需要保留賬號信息,則應當創建Git作者與SVN賬號的映射,即創建users.txt文件,如下,

schacon = Scott Chacon <[email protected]>
selse = Someo Nelse <[email protected]>

使用以下命令,可列出SVN倉庫的所有作者名稱,

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

上述命令可生成一個XML格式的日誌文件,其中包含了作者信息,並丟棄不必要的信息,以及剔除XML標記(事先必須安裝好grep,sort,perl工具),並重定向到users.txt文件,該文件可幫助git svn,正確映射作者信息,如果在clone或init命令中,附帶 --no-metadata選項,可使git svn在正常導入操作中,不包含SVN的元數據(metadata),如果用戶需要保留同步元數據,可忽略該選項,使用以下命令,克隆SVN倉庫,

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

SVN倉庫已導入my_project,而原有的SVN提交描述,與以下內容類似,

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

導入Git倉庫後,SVN提交描述將轉換成以下格式,

commit 03a8785f44c8ea5cdb0e8834b7c8e6c469be2ff2
Author: Scott Chacon <[email protected]>
Date:   Sun May 3 00:12:22 2009 +0000
    fixed install - go to trunk

導入之後,應當進行一些清理操作,需要處理git svn生成的引用標記,首先需要將這些引用標記,修改爲真實的引用標記,而不是指向遠程分支,之後將分支引用指向本地分支,使用以下命令,將遠程倉庫的引用標記(refs/remotes/tags),修改爲正確的Git標記,

$ for t in $(git for-each-ref --format='%(refname:short)' refs/remotes/tags); do git
tag ${t/tags\//} $t && git branch -D -r $t; done

之後將指向refs/remotes的分支引用,修改爲指向本地分支,

$ for b in $(git for-each-ref --format='%(refname:short)' refs/remotes); do git branch
$b refs/remotes/$b && git branch -D -r $b; done

雖然SVN只有一條分支,但是可能會出現一些擴展分支(名稱後綴爲@xxx,xxx爲數字),這些分支是SVN的過期版本(又被稱爲peg revision),用於保存主線分支所棄用的文件,以便得到一個完整的提交歷史,xxx數字即爲過期版本的編號,同時Git並未提供與之對應的功能,如果用戶不需要這些過期版本,可直接刪除,

$ for p in $(git for-each-ref --format='%(refname:short)' | grep @); do git branch -D $p; done

清理操作的最後一步,由於git svn創建了一個擴展分支trunk,並映射了SVN的主線分支,而trunk分支指向的位置,與master分支相同,同時master名稱更符合Git系統的習慣,此時需刪除trunk分支引用,但保留分支內容,之後則可使用替代名master,

$ git branch -d trunk

最後添加Git遠程倉庫,

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

這時需要將本地Git倉庫,全部推送到遠程倉庫,

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

Mercurial

由於Mercurial和Git使用相似的版本描述模型,同時Git更加靈活,因此將Mercurial倉庫轉換成Git倉庫相當簡單,可使用hg-fast-export工具,首先克隆該工具,

$ git clone https://github.com/frej/fast-export.git

轉換的第一步,完整克隆Mercurial倉庫

$ hg clone <Mercurial倉庫URL> /tmp/hg-repo

之後創建提交作者的映射文件,Mercurial並未嚴格管理,變更集包含的作者信息,需要進行必要的處理,

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

此時已生成/tmp/authors文件,如下,

bob
bob@localhost
bob <[email protected]>
bob jones <bob <AT> company <DOT> com>
Bob Jones <[email protected]>
Joe Smith <[email protected]>

創建變更集的同一作者(Bob)使用了五個不同的名字,這時用戶需要手工創建,不同名稱的映射,如下,

"bob"="Bob Jones <[email protected]>"
"bob@localhost"="Bob Jones <[email protected]>"
"bob <[email protected]>"="Bob Jones <[email protected]>"
"bob jones <bob <AT> company <DOT> com>"="Bob Jones <[email protected]>"

如果Mercurial的分支名和標籤名,不兼容Git,則需要修改,完成修改之後,可創建一個新的Git倉庫,

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

-r選項,可指定Mercurial倉庫克隆的存儲路徑,-A選項,可指定作者映射文件的存儲路徑,還可使用-B和-T選項,分別指定分支和標籤的映射文件的存儲路徑,

$ /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標籤,已轉換成Git標籤,所有的Mercurial分支和書籤,都轉換成Git分支,之後需將本地Git倉庫,推送到Git遠程倉庫,

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

Bazaar

Bazaar與Git也很相似,因此轉換操作也相當簡單,可使用bzr-fastimport插件(plugin),但是在Windows和類Unix系統中, fastimport插件的安裝稍有不同,

# Debian
$ sudo apt-get install bzr-fastimport
# RHEL
$ sudo yum install bzr-fastimport
# Fedora,從22版本開始,可使用新的包管理器dnf
$ sudo dnf install bzr-fastimport

也可手動安裝插件,

$ mkdir --parents ~/.bazaar/plugins  # 創建插件所需的默認目錄
$ cd ~/.bazaar/plugins
$ bzr branch lp:bzr-fastimport fastimport  # 導入fastimport插件
$ cd fastimport
$ sudo python setup.py install --record=files.txt  # 安裝插件

當然也可在python環境中直接安裝,

$ python -c "import fastimport"
Traceback (most recent call last):
File "<string>", line 1, in <module>
ImportError: No module named fastimport

$ pip install fastimport

在Windows系統中,可直接下載安裝程序,自動完成插件的安裝.

單分支項目

進入Bazaar倉庫本地副本的目錄,初始化Git倉庫,

$ git init

只需導入Bazaar倉庫,並轉換成Git倉庫,

$ bzr fast-export --plain . | git fast-import
多分支項目

如果Bazaar倉庫包含了多條分支,假定存在兩條分支,一條爲主分支(myProject.trunk),另一條爲工作分支(myProject.work),

$ ls
myProject.trunk myProject.work

創建Git倉庫,

$ git init git-repo
$ cd git-repo

導入Bazaar倉庫的主分支,

$ bzr fast-export --export-marks=../marks.bzr ../myProject.trunk | \
  git fast-import --export-marks=../marks.git

導入Bazaar倉庫的工作分支,

$ bzr fast-export --marks=../marks.bzr --git-branch=work ../myProject.work | \
  git fast-import --import-marks=../marks.git --export-marks=../marks.git

使用git branch可知,Git倉庫的master分支和work分支一模一樣,查看日誌文件,確認Bazaar倉庫包含的文件,已完全導入Git.

同步暫存區

雖然多條Bazaar分支已導入Git,但是暫存區與工作區,並未和HEAD指針同步,使用以下命令,

$ git reset --hard HEAD
可忽略文件

首先將.bzrignore改名爲.gitignore,如果.bzrignore包含了Git不支持的匹配模板,比如!!或RE:,用戶則需要修改,以保證.bzrignore包含的匹配規則能夠完整保留.

再創建一個提交,用於保存遷移記錄,

$ git mv .bzrignore .gitignore
$ # modify .gitignore if needed
$ git commit -am 'Migration from Bazaar to Git'

最後將Git本地倉庫,推送到遠程倉庫,

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

Perforce

如前所述,遷移Perforce也有兩個工具,git-p4和Perforce Git Fusion.

Perforce Git Fusion

Git Fusion的遷移也相當輕鬆,設定好項目配置,賬號名映射,分支所需的配置文件,再克隆Perforce倉庫,即可完成遷移.

git-p4

git-p4也是一種遷移工具,首先用戶必須設定P4Port環境變量,將其指向,需遷移的perforce倉庫,這裏使用了public.perforce.com公共倉庫,

$ export P4PORT=public.perforce.com:1666

使用git p4 clone,克隆perforce公共倉庫的Jam項目,

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

從上述輸出可知,該項目只有一條分支,如果存在多條分支,必須在git p4 clone中,添加–detect-branches選項,可導入所有分支.完成導入後,可進入p4import目錄,查看導入結果,

$ git log -2
commit e5da1c909e5db3036475419f6379f2c73710c4e6
Author: giles <giles@[email protected]>
Date:   Wed Feb 8 03:13:27 2012 -0800
    Correction to line 355; change </UL> to </OL>.
    [git-p4: depot-paths = "//public/jam/src/": change = 8068]

commit aa21359a0a135dda85c50a7f7cf249e4f7b8fd98
Author: kwirth <[email protected]>
Date:   Tue Jul 7 01:35:51 2009 -0800
    Fix spelling error on Jam doc page (cummulative -> cumulative).
    [git-p4: depot-paths = "//public/jam/src/": change = 7304]

從上述輸出中可知,git-p4在每個提交描述中,都添加了一個標記,用於記錄Perforce變更的編號,如果需要刪除該標記,可使用以下命令,

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

運行git log,可知所有提交的校驗碼都已改變,
$ git log -2
commit b17341801ed838d97f7800a54a6f9b95750839b7
Author: giles <giles@[email protected]>
Date:   Wed Feb 8 03:13:27 2012 -0800
    Correction to line 355; change </UL> to </OL>.
    
commit 3e68c2e26cd89cb983eb52c024ecdfba1d6b3fff
Author: kwirth <[email protected]>
Date:   Tue Jul 7 01:35:51 2009 -0800
    Fix spelling error on Jam doc page (cummulative -> cumulative).

最後將本地Git倉庫,推送到遠程倉庫.

TFS

爲了保證TFVC完整遷移到Git,只能使用git-tfs工具,因爲它支持多分支,同時比git-tf更容易實現.

首先需進行用戶名映射,TFVC也未嚴格管理,每個變更集的作者名,而Git倉庫需要在每個提交中,添加作者名和郵箱地址,因此使用以下命令,

PS> tf history $/myproject -recursive > AUTHORS_TMP

上述命令可收集TFVC項目中所有變更集包含的作者信息,並放入AUTHORS_TMP文件,之後需過濾出作者名,再放入AUTHORS文件,

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

手動編輯AUTHORS文件,使用以下格式,創建作者名的映射關係,

DOMAIN\username = User Name <[email protected]>

username即爲TFVC變更集的作者名,User Name即爲Git提交所需的作者信息,完成作者名映射之後,則需要克隆TFVC項目,

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

清除每個Git提交描述,所包含的git-tfs-id文本行,

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

完成之後,添加一個新的Git遠程倉庫,將本地Git倉庫,推送到遠程倉庫.

自定義遷移工具 (略)

感覺意義不大,如果是高級玩家,後續內容太淺顯,如果是入門玩家,不會嘗試這類高難度的課題.

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章