Git 為開發(fā)者提供了如此優(yōu)秀的體驗(yàn),許多人已經(jīng)找到了在他們的工作站上使用 Git 的方法,即使他們團(tuán)隊(duì)其余的人使用的是完全不同的 VCS。 有許多這種可用的適配器,它們被叫做 “橋接”。 下面我們將要介紹幾個很可能會在實(shí)際中用到的橋接。
很大一部分開源項(xiàng)目與相當(dāng)多的企業(yè)項(xiàng)目使用 Subversion 來管理它們的源代碼。 而且在大多數(shù)時間里,它已經(jīng)是開源項(xiàng)目VCS選擇的?事實(shí)標(biāo)準(zhǔn)。 它在很多方面都與曾經(jīng)是源代碼管理世界的大人物的 CVS 相似。
Git 中最棒的特性就是有一個與 Subversion 的雙向橋接,它被稱作?git svn
。 這個工具允許你使用 Git 作為連接到 Subversion 有效的客戶端,這樣你可以使用 Git 所有本地的功能然后如同正在本地使用 Subversion 一樣推送到 Subversion 服務(wù)器。 這意味著你可以在本地做新建分支與合并分支、使用暫存區(qū)、使用變基與揀選等等的事情,同時協(xié)作者還在繼續(xù)使用他們黑暗又古老的方式。 當(dāng)你試圖游說公司將基礎(chǔ)設(shè)施修改為完全支持 Git 的過程中,一個好方法是將 Git 偷偷帶入到公司環(huán)境,并幫助周圍的開發(fā)者提升效率。 Subversion 橋接就是進(jìn)入 DVCS 世界的誘餌。
git svn
在 Git 中所有 Subversion 橋接命令的基礎(chǔ)命令是?git svn
。 它可以跟很多命令,所以我們會通過幾個簡單的工作流程來為你演示最常用的命令。
需要特別注意的是當(dāng)你使用?git svn
?時,就是在與 Subversion 打交道,一個與 Git 完全不同的系統(tǒng)。 盡管?可以?在本地新建分支與合并分支,但是你最好還是通過變基你的工作來保證你的歷史盡可能是直線,并且避免做類似同時與 Git 遠(yuǎn)程服務(wù)器交互的事情。
不要重寫你的歷史然后嘗試再次推送,同時也不要推送到一個平行的 Git 倉庫來與其他使用 Git 的開發(fā)者協(xié)作。 Subversion 只能有一個線性的歷史,弄亂它很容易。 如果你在一個團(tuán)隊(duì)中工作,其中有一些人使用 SVN 而另一些人使用 Git,你需要確保每個人都使用 SVN 服務(wù)器來協(xié)作 - 這樣做會省去很多麻煩。
為了演示這個功能,需要一個有寫入權(quán)限的典型 SVN 倉庫。 如果想要拷貝這些例子,你必須獲得一份我的測試倉庫的可寫拷貝。 為了輕松地拷貝,可以使用 Subversion 自帶的一個名為?svnsync
?的工具。 為了這些測試,我們在 Google Code 上創(chuàng)建了一個?protobuf
?項(xiàng)目部分拷貝的新 Subversion 倉庫。protobuf
?是一個將結(jié)構(gòu)性數(shù)據(jù)編碼用于網(wǎng)絡(luò)傳輸?shù)墓ぞ摺?/p>
接下來,你需要先創(chuàng)建一個新的本地 Subversion 倉庫:
$ mkdir /tmp/test-svn
$ svnadmin create /tmp/test-svn
然后,允許所有用戶改變版本屬性 - 最容易的方式是添加一個返回值為 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)用加入目標(biāo)與來源倉庫參數(shù)的?svnsync init
?命令同步這個項(xiàng)目到本地的機(jī)器。
$ svnsync init file:///tmp/test-svn \
http://progit-example.googlecode.com/svn/
這樣就設(shè)置好了同步所使用的屬性。 可以通過運(yùn)行下面的命令來克隆代碼:
$ 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.
[…]
雖然這個操作可能只會花費(fèi)幾分鐘,但如果你嘗試拷貝原始的倉庫到另一個非本地的遠(yuǎn)程倉庫時,即使只有不到 100 個的提交,這個過程也可能會花費(fèi)將近一個小時。 Subversion 必須一次復(fù)制一個版本然后推送回另一個倉庫 - 這低效得可笑,但卻是做這件事唯一簡單的方式。
既然已經(jīng)有了一個有寫入權(quán)限的 Subversion 倉庫,那么你可以開始一個典型的工作流程。 可以從git svn clone
?命令開始,它會將整個 Subversion 倉庫導(dǎo)入到一個本地 Git 倉庫。 需要牢記的一點(diǎn)是如果是從一個真正托管的 Subversion 倉庫中導(dǎo)入,需要將?file:///tmp/test-svn
替換為你的 Subversion 倉庫的 URL:
$ 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
A java/src/test/java/com/google/protobuf/UnknownFieldSetTest.java
A java/src/test/java/com/google/protobuf/WireFormatTest.java
…
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
這相當(dāng)于運(yùn)行了兩個命令 -?git svn init
?以及緊接著的?git svn fetch
?- 你提供的 URL 。 這會花費(fèi)一些時間。 測試項(xiàng)目只有 75 個左右的提交并且代碼庫并不是很大,但是 Git 必須一次一個地檢出一個版本同時單獨(dú)地提交它。 對于有成百上千個提交的項(xiàng)目,這真的可能會花費(fèi)幾小時甚至幾天來完成。
-T trunk -b branches -t tags
?部分告訴 Git Subversion 倉庫遵循基本的分支與標(biāo)簽慣例。 如果你命名了不同的主干、分支或標(biāo)簽,可以修改這些參數(shù)。 因?yàn)檫@是如此地常見,所以能用-s
?來替代整個這部分,這表示標(biāo)準(zhǔn)布局并且指代所有那些選項(xiàng)。 下面的命令是相同的:
$ git svn clone file:///tmp/test-svn -s
至此,應(yīng)該得到了一個已經(jīng)導(dǎo)入了分支與標(biāo)簽的有效的 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
注意這個工具是如何將 Subversion 標(biāo)簽作為遠(yuǎn)程引用來管理的。?讓我們近距離看一下 Git 的底層命令?show-ref
:
$ 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 服務(wù)器克隆時并不這樣做;下面是在剛剛克隆完成的有標(biāo)簽的倉庫的樣子:
$ 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
Git 直接將標(biāo)簽抓取至?refs/tags
,而不是將它們看作分支。
現(xiàn)在你有了一個工作倉庫,你可以在項(xiàng)目上做一些改動,然后高效地使用 Git 作為 SVN 客戶端將你的提交推送到上游。 一旦編輯了一個文件并提交它,你就有了一個存在于本地 Git 倉庫的提交,這提交在 Subversion 服務(wù)器上并不存在:
$ git commit -am 'Adding git-svn instructions to the README'
[master 4af61fd] Adding git-svn instructions to the README
1 file changed, 5 insertions(+)
接下來,你需要將改動推送到上游。 注意這會怎樣改變你使用 Subversion 的方式 - 你可以離線做幾次提交然后一次性將它們推送到 Subversion 服務(wù)器。 要推送到一個 Subversion 服務(wù)器,運(yùn)行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
這會拿走你在 Subversion 服務(wù)器代碼之上所做的所有提交,針對每一個做一個 Subversion 提交,然后重寫你本地的 Git 提交來包含一個唯一的標(biāo)識符。 這很重要因?yàn)檫@意味著所有你的提交的 SHA-1 校驗(yàn)和都改變了。 部分由于這個原因,同時使用一個基于 Git 的項(xiàng)目遠(yuǎn)程版本和一個 Subversion 服務(wù)器并不是一個好主意。 如果你查看最后一次提交,有新的?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
注意你原來提交的 SHA-1 校驗(yàn)和原來是以?4af61fd
?開頭,而現(xiàn)在是以?95e0222
?開頭。 如果想要既推送到一個 Git 服務(wù)器又推送到一個 Subversion 服務(wù)器,必須先推送(dcommit
)到 Subversion 服務(wù)器,因?yàn)檫@個操作會改變你的提交數(shù)據(jù)。
如果你和其他開發(fā)者一起工作,當(dāng)在某一時刻你們其中之一推送時,另一人嘗試推送修改會導(dǎo)致沖突。 那次修改會被拒絕直到你合并他們的工作。 在?git svn
?中,它看起來是這樣的:
$ 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.
為了解決這種情況,可以運(yùn)行?git svn rebase
,它會從服務(wù)器拉取任何你本地還沒有的改動,并將你所有的工作變基到服務(wù)器的內(nèi)容之上:
$ 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.
現(xiàn)在,所有你的工作都已經(jīng)在 Subversion 服務(wù)器的內(nèi)容之上了,你就可以順利地?dcommit
:
$ 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
?只會在修改發(fā)生沖突時要求你那樣做(更像是 Subversion 工作的行為)。 如果其他人推送一個文件的修改然后你推送了另一個文件的修改,你的?dcommit
?命令會正常工作:
$ 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...
記住這一點(diǎn)很重要,因?yàn)榻Y(jié)果是當(dāng)你推送后項(xiàng)目的狀態(tài)并不存在于你的電腦中。 如果修改并未沖突但卻是不兼容的,可能會引起一些難以診斷的問題。 這與使用 Git 服務(wù)器并不同 - 在 Git 中,可以在發(fā)布前完全測試客戶端系統(tǒng)的狀態(tài),然而在 SVN 中,你甚至不能立即確定在提交前與提交后的狀態(tài)是相同的。
你也應(yīng)該運(yùn)行這個命令從 Subversion 服務(wù)器上拉取修改,即使你自己并不準(zhǔn)備提交。 可以運(yùn)行git svn fetch
?來抓取新數(shù)據(jù),但是?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.
每隔一會兒運(yùn)行?git svn rebase
?確保你的代碼始終是最新的。 雖然需要保證當(dāng)運(yùn)行這個命令時工作目錄是干凈的。 如果有本地的修改,在運(yùn)行?git svn rebase
?之前要么儲藏你的工作要么做一次臨時的提交,不然,當(dāng)變基會導(dǎo)致合并沖突時,命令會終止。
當(dāng)適應(yīng)了 Git 的工作流程,你大概會想要創(chuàng)建特性分支,在上面做一些工作,然后將它們合并入主分支。 如果你正通過?git svn
?推送到一個 Subversion 服務(wù)器,你可能想要把你的工作變基到一個單獨(dú)的分支上,而不是將分支合并到一起。 比較喜歡變基的原因是因?yàn)?Subversion 有一個線性的歷史并且無法像 Git 一樣處理合并,所以?git svn
?在將快照轉(zhuǎn)換成 Subversion 提交時,只會保留第一父提交。
假設(shè)你的歷史像下面這樣:創(chuàng)建了一個?experiment
?分支,做了兩次提交,然后將它們合并回master
。 當(dāng)?dcommit
?時,你看到輸出是這樣的:
$ 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
在一個合并過歷史提交的分支上?dcommit
?命令工作得很好,除了當(dāng)你查看你的 Git 項(xiàng)目歷史時,它并沒有重寫所有你在?experiment
?分支上所做的任意提交 - 相反,所有這些修改顯示一個單獨(dú)合并提交的 SVN 版本中。
當(dāng)其他人克隆那些工作時,他們只會看到一個被塞入了所有改動的合并提交,就像運(yùn)行了?git merge --squash
;他們無法看到修改從哪來或何時提交的信息。
在 Subversion 中新建分支與在 Git 中新建分支并不相同;如果你能不用它,那最好就不要用。 然而,你可以使用 git svn 在 Subversion 中創(chuàng)建分支并在分支上做提交。
要在 Subversion 中創(chuàng)建一個新分支,運(yùn)行?git svn branch [branchname]
:
$ 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)
這與 Subversion 中的?svn copy trunk branches/opera
?命令作用相同并且是在 Subversion 服務(wù)器中操作。 需要重點(diǎn)注意的是它并不會檢出到那個分支;如果你在這時提交,提交會進(jìn)入服務(wù)器的?trunk
?分支,而不是?opera
?分支。
Git 通過查找在歷史中 Subversion 分支的頭部來指出你的提交將會到哪一個分支 - 應(yīng)該只有一個,并且它應(yīng)該是在當(dāng)前分支歷史中最后一個有?git-svn-id
?的。
如果想要同時在不止一個分支上工作,可以通過在導(dǎo)入的那個分支的 Subversion 提交開始來設(shè)置本地分支?dcommit
?到特定的 Subversion 分支。 如果想要一個可以單獨(dú)在上面工作的?opera
?分支,可以運(yùn)行
$ git branch opera remotes/origin/opera
現(xiàn)在,如果想要將你的?opera
?分支合并入?trunk
(你的?master
?分支),可以用一個正常的git merge
?來這樣做。 但是你需要通過?-m
?來提供一個描述性的提交信息,否則合并信息會是沒有用的 “Merge branch opera”。
記住盡管使用的是?git merge
?來做這個操作,而且合并可能會比在 Subversion 中更容易一些(因?yàn)?Git 會為你自動地檢測合適的合并基礎(chǔ)),但這并不是一個普通的 Git 合并提交。 你不得不將這個數(shù)據(jù)推送回一個 Subversion 服務(wù)器,Subversion 服務(wù)器不支持那些跟蹤多個父結(jié)點(diǎn)的提交;所以,當(dāng)推送完成后,它看起來會是一個將其他分支的所有提交壓縮在一起的單獨(dú)提交。 在合并一個分支到另一個分支后,你并不能像 Git 中那樣輕松地回到原來的分支繼續(xù)工作。 你運(yùn)行的dcommit
?命令會將哪個分支被合并進(jìn)來的信息抹掉,所以后續(xù)的合并基礎(chǔ)計(jì)算會是錯的 - dcommit 會使你的?git merge
?結(jié)果看起來像是運(yùn)行了?git merge --squash
。 不幸的是,沒有一個好的方式來避免這種情形 - Subversion 無法存儲這個信息,所以當(dāng)使用它做為服務(wù)器時你總是會被它的限制打垮。 為了避免這些問題,應(yīng)該在合并到主干后刪除本地分支(本例中是?opera
)。
git svn
?工具集通過提供很多功能與 Subversion 中那些相似的命令來幫助簡化轉(zhuǎn)移到 Git 的過程。 下面是一些提供了 Subversion 中常用功能的命令。
如果你習(xí)慣于使用 Subversion 并且想要看 SVN 輸出風(fēng)格的提交歷史,可以運(yùn)行?git svn log
來查看 SVN 格式的提交歷史:
$ 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
關(guān)于?git svn log
,有兩件重要的事你應(yīng)該知道。 首先,它是離線工作的,并不像真正的?svn log
?命令,會向 Subversion 服務(wù)器詢問數(shù)據(jù)。 其次,它只會顯示已經(jīng)提交到 Subversion 服務(wù)器上的提交。 還未 dcommit 的本地 Git 提交并不會顯示;同樣也不會顯示這段時間中其他人推送到 Subversion 服務(wù)器上的提交。 它更像是最后獲取到的 Subversion 服務(wù)器上的提交狀態(tài)。
類似?git svn log
?命令離線模擬了?svn log
?命令,你可以認(rèn)為?git svn blame [FILE]
離線模擬了?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
重復(fù)一次,它并不顯示你在 Git 中的本地提交,也不顯示同一時間被推送到 Subversion 的其他提交。
可以通過運(yùn)行?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)
這就像是在你上一次和 Subversion 服務(wù)器通訊時同步了之后,離線運(yùn)行的?blame
?與?log
?命令。
如果克隆一個在任意一處設(shè)置?svn:ignore
?屬性的 Subversion 倉庫時,你也許會想要設(shè)置對應(yīng)的.gitignore
?文件,這樣就不會意外的提交那些不該提交的文件。?git svn
?有兩個命令來幫助解決這個問題。 第一個是?git svn create-ignore
,它會為你自動地創(chuàng)建對應(yīng)的?.gitignore
文件,這樣你的下次提交就能包含它們。
第二個命令是?git svn show-ignore
,它會將你需要放在?.gitignore
?文件中的每行內(nèi)容打印到標(biāo)準(zhǔn)輸出,這樣就可以將輸出內(nèi)容重定向到項(xiàng)目的例外文件中:
$ git svn show-ignore > .git/info/exclude
這樣,你就不會由于?.gitignore
?文件而把項(xiàng)目弄亂。 當(dāng)你是 Subversion 團(tuán)隊(duì)中唯一的 Git 用戶時這是一個好的選項(xiàng),并且你的隊(duì)友并不想要項(xiàng)目內(nèi)存在?.gitignore
?文件。
當(dāng)你不得不使用 Subversion 服務(wù)器或者其他必須運(yùn)行一個 Subversion 服務(wù)器的開發(fā)環(huán)境時,git svn
?工具很有用。 你應(yīng)該把它當(dāng)做一個不完全的 Git,然而,你要是不用它的話,就會在做轉(zhuǎn)換的過程中遇到很多麻煩的問題。 為了不惹麻煩,盡量遵守這些準(zhǔn)則:
保持一個線性的 Git 歷史,其中不能有?git merge
?生成的合并提交。 把你在主線分支外開發(fā)的全部工作變基到主線分支;而不要合并入主線分支。
git-svn-id
?條目的東西。 你可能會需要增加一個?pre-receive
?鉤子來檢查每一個提交信息是否包含?git-svn-id
?并且拒絕任何未包含的提交。如果你遵守了那些準(zhǔn)則,忍受用一個 Subversion 服務(wù)器來工作可以更容易些。 然而,如果有可能遷移到一個真正的 Git 服務(wù)器,那么遷移過去能使你的團(tuán)隊(duì)獲得更多好處。
DVCS 的宇宙里不只有 Git。 實(shí)際上,在這個空間里有許多其他的系統(tǒng)。對于如何正確地進(jìn)行分布式版本管理,每一個系統(tǒng)都有自己的視角。 除了 Git,最流行的就是 Mercurial,并且它們兩個在很多方面都很相似。
好消息是,如果你更喜歡 Git 的客戶端行為但是工作在源代碼由 Mercurial 控制的項(xiàng)目中,有一種使用 Git 作為 Mercurial 托管倉庫的客戶端的方法。 由于 Git 與服務(wù)器倉庫是使用遠(yuǎn)程交互的,那么由遠(yuǎn)程助手實(shí)現(xiàn)的橋接方法就不會讓人很驚訝。 這個項(xiàng)目的名字是 git-remote-hg,可以在?找到。
首先,需要安裝 git-remote-hg。 實(shí)際上需要將它的文件放在 PATH 變量的某個目錄中,像這樣:
$ curl -o ~/bin/git-remote-hg \
https://raw.githubusercontent.com/felipec/git-remote-hg/master/git-remote-hg
$ chmod +x ~/bin/git-remote-hg
假定?~/bin
?在?$PATH
?變量中。 Git-remote-hg 有一個其他的依賴:mercurial
?Python 庫。 如果已經(jīng)安裝了 Python,安裝它就像這樣簡單:
$ pip install mercurial
需要做的最后一件事是安裝 Mercurial 客戶端。 如果還沒有安裝的話請?jiān)L問?來安裝。
現(xiàn)在已經(jīng)準(zhǔn)備好搖滾了。 你所需要的一切就是一個你可以推送的 Mercurial 倉庫。 很幸運(yùn),每一個 Mercurial 倉庫都可以這樣做,所以我們只需要使用大家用來學(xué)習(xí) Mercurial 的“hello world”倉庫就可以了:
$ hg clone http://selenic.com/repo/hello /tmp/hello
既然有一個可用的 “server-side” 倉庫,我們可以通過一個典型的工作流來了解。 你將會看到,這兩種系統(tǒng)非常相似,沒有太多的出入。
和 Git 一樣,首先我們克隆:
$ 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
你會注意到與 Mercurial 倉庫工作時使用了標(biāo)準(zhǔn)的?git clone
?命令。 那是因?yàn)?git-remote-hg 工作在相當(dāng)?shù)偷牡讓?,使用類似?Git HTTP/S 協(xié)議的機(jī)制實(shí)現(xiàn)的(遠(yuǎn)程助手)。 由于 Git 與 Mercurial 都設(shè)計(jì)為每一個客戶端有一個倉庫的完整歷史,所以這個命令做了一次完整的克隆,包括所有的項(xiàng)目歷史,并且相當(dāng)快地完成。
log 命令顯示了兩次提交,最后一次提交指向了一大堆引用。 那說明這其中的一部分實(shí)際上并沒有在那兒。 讓我們看看?.git
?目錄中實(shí)際上有什么:
$ 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 嘗試讓結(jié)構(gòu)更有 Git 風(fēng)格,但是在隱藏在下面的是它管理兩個輕微不同系統(tǒng)之間概念的映射。?refs/hg
?目錄中存儲了實(shí)際的遠(yuǎn)程引用。 例如,refs/hg/origin/branches/default
?是一個包含以“ac7955c”開始的 SHA-1 值的 Git 引用文件,是?master
?所指向的提交。 所以?refs/hg
?目錄是一種類似refs/remotes/origin
?的替代品,但是它引入了書簽與分支的區(qū)別。
notes/hg
?文件是 git-remote-hg 如何在 Git 的提交散列與 Mercurial 變更集 ID 之間建立映射的起點(diǎn)。 讓我們來探索一下:
$ 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 對象數(shù)據(jù)庫中的一個有其他對象名字的列表。git ls-tree
?輸出 tree 對象中所有項(xiàng)目的模式、類型、對象哈希與文件名。 如果深入挖掘 tree 對象中的一個項(xiàng)目,我們會發(fā)現(xiàn)在其中是一個名字為 “ac9117f” 的 blob 對象(master
?所指向提交的 SHA-1 散列值),包含內(nèi)容 “0a04b98”(是?default
?分支指向的 Mercurial 變更集的 ID)。
好消息是大多數(shù)情況下我們不需要關(guān)心以上這些。 典型的工作流程與使用 Git 遠(yuǎn)程倉庫并沒有什么不同。
在我們繼續(xù)之前,這里還有一件需要注意的事情:忽略。 Mercurial 與 Git 使用非常類似的機(jī)制實(shí)現(xiàn)這個功能,但是一般來說你不會想要把一個?.gitignore
?文件提交到 Mercurial 倉庫中。 幸運(yùn)的是,Git 有一種方式可以忽略本地磁盤倉庫的文件,而且 Mercurial 格式是與 Git 兼容的,所以你只需將這個文件拷貝過去:
$ cp .hgignore .git/info/exclude
.git/info/exclude
?文件的作用像是一個?.gitignore
,但是它不包含在提交中。
假設(shè)我們已經(jīng)做了一些工作并且在?master
?分支做了幾次提交,而且已經(jīng)準(zhǔn)備將它們推送到遠(yuǎn)程倉庫。 這是我們倉庫現(xiàn)在的樣子:
$ 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
我們的?master
?分支領(lǐng)先?origin/master
?分支兩個提交,但是那兩個提交只存在于我們的本地機(jī)器中。 讓我們看看在同一時間有沒有其他人做過什么重要的工作:
$ 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
|/
因?yàn)槭褂昧?--all
?標(biāo)記,我們看到被 git-remote-hg 內(nèi)部使用的 “notes” 引用,但是可以忽略它們。 剩下的部分是我們期望的;origin/master
?已經(jīng)前進(jìn)了一次提交,同時我們的歷史現(xiàn)在分叉了。 Mercurial 和我們本章中討論的其他系統(tǒng)不一樣,它能夠處理合并,所以我們不需要做任何其他事情。
$ 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
完美。 運(yùn)行測試然后所有測試都通過了,所以我們準(zhǔn)備將工作共享給團(tuán)隊(duì)的其他成員。
$ git push
To hg::/tmp/hello
df85e87..0c64627 master -> master
就是這樣! 如果你現(xiàn)在查看一下 Mercurial 倉庫,你會發(fā)現(xiàn)這樣實(shí)現(xiàn)了我們所期望的:
$ 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 只有一種類型的分支:當(dāng)提交生成時移動的一個引用。 在 Mercurial 中,這種類型的引用叫作 “bookmark”,它的行為非常類似于 Git 分支。
Mercurial 的 “branch” 概念則更重量級一些。 變更集生成時的分支會記錄?在變更集中,意味著它會永遠(yuǎn)地存在于倉庫歷史中。 這個例子描述了一個在?develop
?分支上的提交:
$ hg log -l 1
changeset: 6:8f65e5e02793
branch: develop
tag: tip
user: Ben Straub <ben@straub.cc>
date: Thu Aug 14 20:06:38 2014 -0700
summary: More documentation
注意開頭為 “branch” 的那行。 Git 無法真正地模擬這種行為(并且也不需要這樣做;兩種類型的分支都可以表達(dá)為 Git 的一個引用),但是 git-remote-hg 需要了解其中的區(qū)別,因?yàn)?Mercurial 關(guān)心。
創(chuàng)建 Mercurial 書簽與創(chuàng)建 Git 分支一樣容易。 在 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]
?標(biāo)簽。 在 Git 這邊這些看起來像是 Git 分支,除了一點(diǎn):不能從 Git 這邊刪除書簽(這是遠(yuǎn)程助手的一個限制)。
你也可以工作在一個 “重量級” 的 Mercurial branch:只需要在?branches
?命名空間內(nèi)創(chuàng)建一個分支:
$ 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 <ben@straub.cc>
| date: Thu Aug 14 20:21:09 2014 -0700
| summary: A permanent change
|
| @ changeset: 6:8f65e5e02793
|/ branch: develop
| user: Ben Straub <ben@straub.cc>
| 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 <ben@straub.cc>
| | date: Thu Aug 14 20:02:21 2014 -0700
| | summary: Merge remote-tracking branch 'origin/master'
[...]
分支名字 “permanent” 記錄在序號?7?的變更集中。
在 Git 這邊,對于其中任何一種風(fēng)格的分支的工作都是相同的:僅僅是正常做的檢出、提交、抓取、合并、拉取與推送。 還有需要知道的一件事情是 Mercurial 不支持重寫歷史,只允許添加歷史。 下面是我們的 Mercurial 倉庫在交互式的變基與強(qiáng)制推送后的樣子:
$ hg log --style compact -G
o 10[tip] 99611176cbc9 2014-08-14 20:21 -0700 ben
| A permanent change
|
o 9 f23e12f939c3 2014-08-14 20:01 -0700 ben
| Add some documentation
|
o 8:1 c16971d33922 2014-08-14 20:00 -0700 ben
| goodbye
|
| o 7:5 a4529d07aad4 2014-08-14 20:21 -0700 ben
| | A permanent change
| |
| | @ 6 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
變更集?8、9?與?10?已經(jīng)被創(chuàng)建出來并且屬于?permanent
?分支,但是舊的變更集依然在那里。 這會讓使用 Mercurial 的團(tuán)隊(duì)成員非常困惑,所以要避免這種行為。
Git 與 Mercurial 如此相似,以至于跨這兩個系統(tǒng)進(jìn)行工作十分流暢。 如果能注意避免改變在你機(jī)器上的歷史(就像通常建議的那樣),你甚至并不會察覺到另一端是 Mercurial。
在企業(yè)環(huán)境中 Perforce 是非常流行的版本管理系統(tǒng)。 它大概起始于 1995 年,這使它成為了本章中介紹的最古老的系統(tǒng)。 就其本身而言,它設(shè)計(jì)時帶有當(dāng)時時代的局限性;它假定你始終連接到一個單獨(dú)的中央服務(wù)器,本地磁盤只保存一個版本。 誠然,它的功能與限制適合幾個特定的問題,但實(shí)際上,在很多情況下,將使用 Perforce 的項(xiàng)目換做使用 Git 會更好。
如果你決定混合使用 Perforce 與 Git 這里有兩種選擇。 第一個我們要介紹的是 Perforce 官方制作的 “Git Fusion” 橋接,它可以將 Perforce 倉庫中的子樹表示為一個可讀寫的 Git 倉庫。 第二個是 git-p4,一個客戶端橋接允許你將 Git 作為 Perforce 的客戶端使用,而不用在 Perforce 服務(wù)器上做任何重新的配置。
Perforce 提供了一個叫作 Git Fusion 的產(chǎn)品(可在??獲得),它將會在服務(wù)器這邊同步 Perforce 服務(wù)器與 Git 倉庫。
針對我們的例子,我們將會使用最簡單的方式安裝 Git Fusion:下載一個虛擬機(jī)來運(yùn)行 Perforce 守護(hù)進(jìn)程與 Git Fusion。 可以從??獲得虛擬機(jī)鏡像,下載完成后將它導(dǎo)入到你最愛的虛擬機(jī)軟件中(我們將會使用 VirtualBox)。
在第一次啟動機(jī)器后,它會詢問你自定義三個 Linux 用戶(root
、perforce
?與?git
)的密碼,并且提供一個實(shí)例名字來區(qū)分在同一網(wǎng)絡(luò)下不同的安裝。 當(dāng)那些都完成后,將會看到這樣:
更多建議: