-
1. 起步
-
2. Git 基礎
-
3. Git 分支
-
4. 伺服器上的 Git
- 4.1 協議
- 4.2 在伺服器上部署 Git
- 4.3 生成 SSH 公鑰
- 4.4 架設伺服器
- 4.5 Git Daemon
- 4.6 Smart HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 第三方託管服務
- 4.10 小結
-
5. 分散式 Git
-
A1. 附錄 A: Git 在其他環境
- A1.1 圖形介面
- A1.2 Visual Studio 中的 Git
- A1.3 Visual Studio Code 中的 Git
- A1.4 IntelliJ / PyCharm / WebStorm / PhpStorm / RubyMine 中的 Git
- A1.5 Sublime Text 中的 Git
- A1.6 Bash 中的 Git
- A1.7 Zsh 中的 Git
- A1.8 PowerShell 中的 Git
- A1.9 小結
-
A2. 附錄 B: 在應用程式中嵌入 Git
-
A3. 附錄 C: Git 命令
9.1 Git 和其他系統 - Git 作為客戶端
世界並不完美。通常,您無法立即將遇到的每個專案都切換到 Git。有時您會陷入一個使用其他版本控制系統的專案,並希望它是 Git。我們將花本章的第一部分來學習當您正在處理的專案託管在不同的系統上時,如何將 Git 作為客戶端使用。
在某個時候,您可能希望將現有專案轉換為 Git。本章的第二部分將介紹如何從幾個特定的系統遷移專案到 Git,以及一種適用於沒有預構建匯入工具的情況的方法。
Git 作為客戶端
Git 為開發者提供瞭如此好的體驗,以至於許多人已經找到了在工作站上使用它的方法,即使他們的團隊其他成員正在使用完全不同的版本控制系統。有許多這樣的介面卡,稱為“橋接器”,可用。在這裡,我們將介紹您在實踐中最可能遇到的。
Git 和 Subversion
很大一部分開源開發專案和許多企業專案使用 Subversion 來管理它們的原始碼。它已經存在十多年了,並且在大部分時間裡是開源專案的事實上的版本控制系統選擇。它在許多方面與 CVS 非常相似,而 CVS 在此之前是原始碼控制領域的巨頭。
Git 的一個偉大特性是與 Subversion 的雙向橋接,稱為 `git svn`。這個工具允許您將 Git 作為 Subversion 伺服器的有效客戶端,因此您可以使用 Git 的所有本地功能,然後將更改推送到 Subversion 伺服器,就像您在本地使用 Subversion 一樣。這意味著您可以進行本地分支和合並,使用暫存區,使用 rebase 和 cherry-pick 等等,而您的協作者可以繼續以他們古老的方式工作。這是將 Git 悄悄引入企業環境並幫助您的同事提高效率的好方法,同時您也可以遊說更改基礎設施以完全支援 Git。Subversion 橋是通往分散式版本控制系統世界的一條捷徑。
git svn
Git 中所有 Subversion 橋接命令的基礎命令是 `git svn`。它接受許多命令,因此我們將通過幾個簡單的流程演示最常用的命令。
需要注意的是,當您使用 `git svn` 時,您正在與 Subversion 互動,這是一個與 Git 工作方式非常不同的系統。儘管您可以進行本地分支和合並,但通常最好透過 rebase 您的工作來保持歷史記錄的儘可能線性,並避免同時與 Git 遠端倉庫進行互動。
不要重寫您的歷史記錄並再次嘗試推送,也不要同時推送到並行的 Git 倉庫以與 Git 開發者協作。Subversion 只能有一個單一的線性歷史記錄,並且很容易弄混它。如果您與團隊一起工作,並且有些人使用 SVN 而其他人使用 Git,請確保每個人都使用 SVN 伺服器進行協作——這樣做會使您的生活更輕鬆。
設定
為了演示此功能,您需要一個您擁有寫訪問許可權的典型 SVN 倉庫。如果您想複製這些示例,您必須製作一個可寫的 SVN 測試倉庫副本。為了輕鬆做到這一點,您可以使用一個名為 `svnsync` 的工具,它隨 Subversion 一起提供。
要繼續,您首先需要建立一個新的本地 Subversion 倉庫
$ mkdir /tmp/test-svn
$ svnadmin create /tmp/test-svn
然後,啟用所有使用者更改 revprops——最簡單的方法是新增一個始終退出 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
您現在可以透過呼叫 `svnsync init`,指定目標和源倉庫,來將此專案同步到您的本地計算機。
$ svnsync init file:///tmp/test-svn \
http://your-svn-server.example.org/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 次提交。Subversion 必須一次克隆一個版本,然後將其推送到另一個倉庫——這效率低下得離譜,但它是最容易做到這一點的方法。
入門
現在您有了一個擁有寫訪問許可權的 Subversion 倉庫,您可以進行典型的工作流程。您將從 `git svn clone` 命令開始,該命令將整個 Subversion 倉庫匯入到本地 Git 倉庫。請記住,如果您從實際託管的 Subversion 倉庫匯入,您應該將 `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
這會針對您提供的 URL 執行 `git svn init` 和 `git svn fetch` 這兩個命令。這可能需要一些時間。例如,即使測試專案只有大約 75 次提交,程式碼庫也不是很大,Git 仍然必須一次簽出每個版本並單獨提交。對於有數百甚至數千次提交的專案,這可能需要數小時甚至數天才能完成。
`-T trunk -b branches -t tags` 部分告訴 Git 該 Subversion 倉庫遵循基本的分支和標籤約定。如果您命名您的 trunk、branches 或 tags 不同,您可以更改這些選項。由於這是非常常見的,您可以將整個部分替換為 `-s`,它表示標準佈局並暗示所有這些選項。以下命令是等效的:
$ git svn clone file:///tmp/test-svn -s
此時,您應該擁有一個有效的 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 標籤管理為遠端引用。讓我們使用 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 伺服器克隆時不會這樣做;以下是克隆帶有標籤的倉庫後的樣子:
$ 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 直接將標籤獲取到 `refs/tags` 中,而不是將其視為遠端分支。
提交回 Subversion
現在您有了一個工作目錄,您可以對專案進行一些工作,並將您的提交推送到上游,有效地將 Git 用作 SVN 客戶端。如果您編輯了其中一個檔案並提交了它,那麼您將在 Git 中有一個本地提交,而在 Subversion 伺服器上不存在。
$ 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 伺服器。要推送到 Subversion 伺服器,您需要執行 `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 伺服器程式碼之上進行的所有提交,為每個提交執行一次 Subversion 提交,然後重寫您的本地 Git 提交以包含一個唯一識別符號。這很重要,因為它意味著您的所有提交的 SHA-1 校驗和都會發生變化。部分原因在於,同時與專案的 Git 遠端版本以及 Subversion 伺服器一起工作不是一個好主意。如果您檢視最後一次提交,您可以看到添加了新的 `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` 開頭的 SHA-1 校驗和現在以 `95e0222` 開頭。如果您想同時推送到 Git 伺服器和 Subversion 伺服器,您必須先推送到(`dcommit`)Subversion 伺服器,因為該操作會更改您的提交資料。
拉取新更改
如果您與其他開發者一起工作,那麼總有一天,您中的一個人會推送,然後另一個人會嘗試推送一個衝突的更改。該更改將被拒絕,直到您合併他們的工作。在 `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.
要解決這種情況,您可以執行 `git svn rebase`,它會拉取伺服器上您尚未擁有的任何更改,並將您上面的所有工作 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.
現在,您所有的工作都建立在 Subversion 伺服器上的內容之上,因此您可以成功 `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 要求您在推送之前合併您尚未本地擁有的上游工作,而 `git svn` 只在更改衝突時才要求您這樣做(很像 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...
這一點很重要,因為結果是您在推送時,您的計算機上都不存在一個專案狀態。如果更改不相容但沒有衝突,您可能會遇到難以診斷的問題。這與使用 Git 伺服器不同——在 Git 中,您可以在釋出之前在本地系統上完全測試狀態,而在 SVN 中,您永遠無法確定提交前和提交後的狀態是相同的。
即使您不準備自己提交,也應該執行此命令來拉取 Subversion 伺服器上的更改。您可以執行 `git svn fetch` 來獲取新資料,但 `git svn rebase` 會執行 fetch 並然後更新您的本地提交。
$ 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` 之前將您的工作 stash 或暫時提交——否則,如果命令發現 rebase 會導致合併衝突,它將停止。
Git 分支問題
當您熟悉了 Git 工作流程後,您可能會建立主題分支,在上面進行工作,然後將它們合併。如果您透過 `git svn` 推送到 Subversion 伺服器,您可能希望每次都將您的工作 rebase 到一個單獨的分支上,而不是合併分支。偏好 rebase 的原因是 Subversion 具有線性歷史記錄,並且不像 Git 那樣處理合併,因此 `git svn` 在將快照轉換為 Subversion 提交時只跟隨第一個父級。
假設您的歷史記錄如下:您建立了一個 `experiment` 分支,進行了兩次提交,然後將它們合併回 `master`。當您 `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` 可以正常工作,除了當您檢視您的 Git 專案歷史記錄時,它並沒有重寫您在 `experiment` 分支上進行的任何提交——相反,所有這些更改都出現在單個合併提交的 SVN 版本中。
當其他人克隆該工作時,他們看到的只是一個合併提交,所有工作都壓縮在其中,就好像您運行了 `git merge --squash` 一樣;他們看不到關於它來自何處或何時提交的提交資料。
Subversion 分支
Subversion 中的分支與 Git 中的分支不同;如果您可以避免過多使用它,那可能是最好的。但是,您可以使用 `git svn` 在 Subversion 中建立並提交到分支。
建立新的 SVN 分支
要建立一個新的 Subversion 分支,請執行 `git svn branch [new-branch]`。
$ 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 伺服器上執行。需要注意的是,它不會將您簽出到該分支;如果您此時提交,該提交將傳送到伺服器上的 `trunk`,而不是 `opera`。
切換活動分支
Git 透過查詢您在歷史記錄中任何 Subversion 分支的尖端來確定您的 dcommits 將傳送到哪個分支——您應該只有一個,並且它應該是當前分支歷史記錄中帶有 `git-svn-id` 的最後一個。
如果您想同時處理多個分支,您可以設定本地分支以 `dcommit` 到特定的 Subversion 分支,方法是從該分支的匯入的 Subversion 提交開始。如果您想要一個可以單獨處理的 `opera` 分支,您可以執行:
$ git branch opera remotes/origin/opera
現在,如果您想將您的 `opera` 分支合併到 `trunk`(您的 `master` 分支),您可以使用普通的 `git merge` 來完成。但是您需要提供一個描述性的提交訊息(透過 `-m`),否則合併將顯示“Merge branch opera”而不是有用的資訊。
請記住,儘管您正在使用 `git merge` 來執行此操作,並且合併很可能比在 Subversion 中更容易(因為 Git 會自動為您檢測適當的合併基準),但這並不是一個普通的 Git 合併提交。您需要將這些資料推送到一個無法處理跟蹤多個父項的提交的 Subversion 伺服器;因此,在將其推上去之後,它將看起來像一個合併了另一個分支所有工作的單個提交。在一個分支合併到另一個分支之後,您就無法像在 Git 中那樣輕鬆地返回並繼續處理該分支。您執行的 `dcommit` 命令會刪除任何表明哪個分支被合併的資訊,因此後續的合併基準計算將是錯誤的——`dcommit` 會使您的 `git merge` 結果看起來像是您運行了 `git merge --squash`。不幸的是,沒有好的方法可以避免這種情況——Subversion 無法儲存此資訊,因此當您將其用作伺服器時,您將永遠受其限制的限制。為避免問題,您應該在將分支(在本例中為 `opera`)合併到 trunk 後將其刪除。
Subversion 命令
`git svn` 工具集提供了一系列命令,透過提供一些與您在 Subversion 中擁有的功能類似的功能,來幫助過渡到 Git。以下是一些命令,它們為您提供了 Subversion 曾經提供的功能。
SVN 風格歷史
如果您習慣了 Subversion 並想以 SVN 輸出樣式檢視歷史記錄,您可以執行 `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
關於 `git svn log`,您應該知道兩件重要的事情。第一,它離線工作,不像真正的 `svn log` 命令那樣需要向 Subversion 伺服器請求資料。第二,它只顯示已經提交到 Subversion 伺服器的提交。您尚未 `dcommit` 的本地 Git 提交不會顯示;期間其他人對 Subversion 伺服器進行的提交也不會顯示。它更像是 Subversion 伺服器上提交的最後已知狀態。
SVN 註釋
就像 `git svn log` 命令離線模擬 `svn log` 命令一樣,您可以透過執行 `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
同樣,它不會顯示您在 Git 中本地進行的提交,也不會顯示期間已推送到 Subversion 的提交。
SVN 伺服器資訊
您還可以獲得與 `svn info` 提供的相同型別的資訊,只需執行 `git 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` 類似,它離線執行,並且僅在您上次與 Subversion 伺服器通訊時才更新。
忽略 Subversion 忽略的內容
如果您克隆了一個設定了 `svn:ignore` 屬性的 Subversion 倉庫,您很可能希望設定相應的 `.gitignore` 檔案,以免意外提交不應提交的檔案。`git svn` 有兩個命令可以幫助解決此問題。第一個是 `git svn create-ignore`,它會自動為您建立相應的 `.gitignore` 檔案,以便您的下一次提交可以包含它們。
第二個命令是 `git svn show-ignore`,它將需要放入 `.gitignore` 檔案中的行列印到標準輸出,這樣您就可以將輸出重定向到您的專案排除檔案。
$ git svn show-ignore > .git/info/exclude
這樣,您就不會在專案中散佈 `.gitignore` 檔案。如果您是 Subversion 團隊中唯一的 Git 使用者,而您的隊友不希望專案中有 `.gitignore` 檔案,這是一個不錯的選擇。
Git-Svn 總結
如果您被困於 Subversion 伺服器,或者處於需要執行 Subversion 伺服器的開發環境中,`git svn` 工具集就很有用。但是,您應該將其視為被削弱的 Git,否則您會遇到翻譯上的問題,這些問題可能會讓您和您的協作者感到困惑。為了避免麻煩,請嘗試遵循以下準則:
-
保持線性的 Git 歷史記錄,不包含由 `git merge` 建立的合併提交。將您在主線分支之外所做的任何工作 rebase 回主線分支;不要將其合併進來。
-
不要設定並協作一個獨立的 Git 伺服器。可能有一個來加快新開發者的克隆速度,但不要推任何沒有 `git-svn-id` 條目的內容。您甚至可能想新增一個 `pre-receive` 鉤子,該鉤子檢查每個提交訊息中是否有 `git-svn-id` 並拒絕包含該訊息的推送。
如果您遵循這些準則,與 Subversion 伺服器一起工作可能會更容易忍受。然而,如果有可能遷移到一個真正的 Git 伺服器,這樣做可以為您的團隊帶來更多好處。
Git 和 Mercurial
分散式版本控制系統(DVCS)的世界不僅僅是 Git。事實上,這個領域還有許多其他系統,它們都有自己對如何正確進行分散式版本控制的看法。除了 Git 之外,最受歡迎的是 Mercurial,而兩者在許多方面都非常相似。
好訊息是,如果您更喜歡 Git 的客戶端行為,但正在處理一個由 Mercurial 控制原始碼的專案,那麼有一種方法可以將 Git 用作 Mercurial 託管倉庫的客戶端。由於 Git 透過遠端倉庫與伺服器進行通訊,因此該橋接器實現為遠端助手也就不足為奇了。專案名稱是 `git-remote-hg`,您可以在 https://github.com/felipec/git-remote-hg 找到它。
git-remote-hg
首先,您需要安裝 `git-remote-hg`。這基本上需要將它的檔案放到您的 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` 還有另一個依賴項:Python 的 `mercurial` 庫。如果您安裝了 Python,這很簡單:
$ pip install mercurial
如果您沒有安裝 Python,請訪問 https://python.club.tw/ 並先獲取它。
您需要的最後一件事是 Mercurial 客戶端。前往 https://www.mercurial-scm.org/ 並安裝它(如果您還沒有安裝)。
現在您可以開始使用了。您只需要一個您可以推送的 Mercurial 倉庫。幸運的是,每個 Mercurial 倉庫都可以充當此角色,因此我們將使用每個人都用來學習 Mercurial 的“hello world”倉庫。
$ hg clone http://selenic.com/repo/hello /tmp/hello
入門
現在我們有了一個合適的“伺服器端”倉庫,我們可以進行典型的工作流程。您會發現,這兩個系統足夠相似,因此摩擦很少。
一如既往,我們首先克隆:
$ 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”開頭的 SHA-1,這是 `master` 指向的提交。因此,`refs/hg` 目錄有點像一個假的 `refs/remotes/origin`,但它區分了書籤和分支。
`notes/hg` 檔案是 `git-remote-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` 指向的提交的 SHA-1 雜湊)的 blob,內容為“0a04b98”(這是 `default` 分支尖端的 Mercurial 變更集 ID)。
好訊息是,我們大部分時間都不需要擔心所有這些。典型的工作流程與使用 Git 遠端倉庫的工作流程差別不大。
還有一件事我們應該處理:忽略。Mercurial 和 Git 使用非常相似的機制來實現這一點,但您可能不想真正將 `.gitignore` 檔案提交到 Mercurial 倉庫。幸運的是,Git 有一種僅限於本地磁碟倉庫的忽略檔案的方法,而 Mercurial 格式與 Git 相容,所以您只需將其複製過去即可。
$ 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
我們的 `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
由 Mercurial 製作的變更集編號為 *2*,由 `git-remote-hg` 透過推送 Git 提交建立的變更集編號為 *3* 和 *4*。
分支和書籤
Git 只有一種分支:當進行提交時會移動的引用。在 Mercurial 中,這種引用稱為“書籤”,它的行為與 Git 分支非常相似。
Mercurial 的“分支”概念更重。變更集所在的“分支”是與 *變更集一起* 記錄的,這意味著它將始終存在於倉庫歷史記錄中。這是一個在 `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 無法真正複製這一點(也不需要;兩種型別的分支都可以表示為 Git 引用),但 `git-remote-hg` 需要理解區別,因為 Mercurial 會在意。
在 Git 端建立 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 分支完全相同,有一個例外:您無法從 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 <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 端來看,處理這兩種分支風格是相同的:只需像往常一樣 checkout、commit、fetch、merge、pull 和 push。您應該知道的一件事是,Mercurial 不支援重寫歷史,只支援新增。這是我們 Mercurial 倉庫在互動式 rebase 和 force-push 後的樣子:
$ 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* 已建立並屬於 `permanent` 分支,但舊的變更集仍然存在。這可能會讓使用 Mercurial 的同事感到**非常**困惑,所以儘量避免這樣做。
Mercurial 總結
Git 和 Mercurial 足夠相似,跨界工作相當輕鬆。如果您避免更改已離開您機器的歷史記錄(這通常是推薦的做法),您甚至可能不知道另一端是 Mercurial。
Git 和 Perforce
Perforce 是企業環境中非常流行的版本控制系統。它自 1995 年以來一直存在,這使其成為本章中介紹的最古老的系統。因此,它按照其時代的限制進行設計;它假定您始終連線到單箇中央伺服器,並且本地磁碟上只保留一個版本。當然,它的功能和限制非常適合幾種特定問題,但有許多使用 Perforce 的專案實際上使用 Git 會更好。
如果您想混合使用 Perforce 和 Git,有兩種選擇。我們首先介紹的是 Perforce 製造商提供的“Git Fusion”橋接器,它允許您將 Perforce depot 的子樹暴露為讀寫 Git 倉庫。第二個是 `git-p4`,一個客戶端橋接器,它允許您將 Git 作為 Perforce 客戶端使用,而無需重新配置 Perforce 伺服器。
Git Fusion
Perforce 提供了一個名為 Git Fusion 的產品(可在 https://www.perforce.com/manuals/git-fusion/ 獲取),它在伺服器端同步 Perforce 伺服器和 Git 倉庫。
設定
對於我們的示例,我們將使用 Git Fusion 最簡單的安裝方法,即下載一個執行 Perforce 守護程序和 Git Fusion 的虛擬機器。您可以從 https://www.perforce.com/downloads 下載虛擬機器映象,下載完成後,將其匯入您喜歡的虛擬化軟體(我們將使用 VirtualBox)。
第一次啟動機器時,它會要求您自定義三個 Linux 使用者(`root`、`perforce` 和 `git`)的密碼,並提供一個例項名稱,該名稱可用於區分同一網路上的其他安裝。完成所有操作後,您將看到:
您應該注意這裡顯示的 IP 地址,我們稍後會使用它。接下來,我們將建立一個 Perforce 使用者。選擇底部的“Login”選項並按 Enter(或 SSH 到機器),然後以 `root` 身份登入。然後使用以下命令建立使用者:
$ p4 -p localhost:1666 -u super user -f john
$ p4 -p localhost:1666 -u john passwd
$ exit
第一個命令將開啟 VI 編輯器來定製使用者,但您可以透過輸入 `:wq` 並按 Enter 來接受預設值。第二個命令會提示您輸入兩次密碼。這就是我們需要從 shell 提示符執行的所有操作,因此退出會話。
接下來,您需要告訴 Git 不要驗證 SSL 證書。Git Fusion 映象附帶了一個證書,但它用於一個與您虛擬機器的 IP 地址不匹配的域名,因此 Git 將拒絕 HTTPS 連線。如果這是一個永久安裝,請參閱 Perforce Git Fusion 手冊來安裝不同的證書;就我們的示例而言,這將足夠了。
$ 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://john@10.0.1.254':
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 會要求此連線的憑據,但憑據快取將允許我們跳過後續請求的此步驟。
Fusion 配置
一旦您安裝了 Git Fusion,您將希望調整配置。這實際上很容易使用您喜歡的 Perforce 客戶端完成;只需將 Perforce 伺服器上的 `//.git-fusion` 目錄對映到您的工作區。檔案結構如下:
$ tree
.
├── objects
│ ├── repos
│ │ └── [...]
│ └── trees
│ └── [...]
│
├── p4gf_config
├── repos
│ └── Talkhouse
│ └── p4gf_config
└── users
└── p4gf_usermap
498 directories, 287 files
`objects` 目錄由 Git Fusion 內部用於對映 Perforce 物件到 Git 反之亦然,您無需處理其中的任何內容。該目錄中有一個全域性 `p4gf_config` 檔案,以及每個倉庫的一個檔案——這些是決定 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 格式的文字檔案,就像 Git 用於配置一樣。此檔案指定了全域性選項,這些選項可以被倉庫特定的配置檔案(如 `repos/Talkhouse/p4gf_config`)覆蓋。如果您開啟此檔案,您會看到一個 `[@repo]` 部分,其中包含一些與全域性預設值不同的設定。您還會看到類似以下內容的節:
[Talkhouse-master]
git-branch-name = master
view = //depot/Talkhouse/main-dev/... ...
這是 Perforce 分支和 Git 分支之間的對映。該節的名稱可以隨意命名,只要名稱是唯一的即可。`git-branch-name` 允許您將 Perforce 倉庫路徑轉換為更友好的名稱,這種路徑在 Git 中可能很麻煩。`view` 設定控制如何將 Perforce 檔案對映到 Git 倉庫,使用標準的檢視對映語法。可以指定多個對映,如下例所示:
[multi-project-mapping]
git-branch-name = master
view = //depot/project1/main/... project1/...
//depot/project2/mainline/... project2/...
這樣,如果您的正常工作區對映包含目錄結構的更改,您可以將其與 Git 倉庫一起復制。
我們將討論的最後一個檔案是 `users/p4gf_usermap`,它將 Perforce 使用者對映到 Git 使用者,而您甚至可能不需要它。當從 Perforce 變更集轉換為 Git 提交時,Git Fusion 的預設行為是查詢 Perforce 使用者,並使用其中儲存的電子郵件地址和全名作為 Git 中的 author/committer 欄位。當反向轉換時,預設是查詢具有 Git 提交 author 欄位中儲存的電子郵件地址的 Perforce 使用者,並將變更集作為該使用者提交(應用許可權)。在大多數情況下,此行為就足夠了,但請考慮以下對映檔案:
john john@example.com "John Doe"
john johnny@appleseed.net "John Doe"
bob employeeX@example.com "Anon X. Mouse"
joe employeeY@example.com "Anon Y. Mouse"
每行格式為 `
最後兩行隱藏了 Bob 和 Joe 的實際姓名和電子郵件地址,不會在建立的 Git 提交中顯示。如果您想開源一個內部專案,但又不想將您的員工目錄釋出給全世界,這會很有用。請注意,除非您希望所有 Git 提交都歸屬到一個虛構的作者,否則電子郵件地址和全名應該是唯一的。
工作流程
Perforce Git Fusion 是 Perforce 和 Git 版本控制之間的雙向橋接。讓我們看看從 Git 端工作是什麼感覺。我們假設已經使用如上所示的配置檔案映射了“Jam”專案,我們可以這樣克隆它:
$ git clone https://10.0.1.254/Jam
Cloning into 'Jam'...
Username for 'https://10.0.1.254': john
Password for 'https://john@10.0.1.254':
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 提交。這在伺服器本地進行,所以速度相對較快,但如果您的歷史記錄很多,仍然可能需要一些時間。後續的 fetch 會進行增量轉換,所以感覺上會更像 Git 的原生速度。
正如您所見,我們的倉庫看起來就像您可能使用的任何其他 Git 倉庫一樣。有三個分支,Git 已經很方便地建立了一個本地 `master` 分支來跟蹤 `origin/master`。讓我們做一些工作,並建立幾個新的提交:
# ...
$ git log --oneline --decorate --graph --all
* cfd46ab (HEAD, master) Add documentation for new feature
* a730d77 Whitespace
* d254865 (origin/master, origin/HEAD) Upgrade to latest metrowerks on Beos -- the Intel one.
* bd2f54a Put in fix for jam's NT handle leak.
[...]
我們有兩個新提交。現在讓我們檢查一下是否有人在工作:
$ 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 的角度,使用 p4v 的修訂圖功能,來檢視 README 檔案的歷史記錄。
如果您以前從未見過此檢視,它可能會讓您感到困惑,但它顯示的概念與 Git 歷史記錄的圖形檢視器相同。我們正在檢視 README 檔案的歷史記錄,因此左上角的目錄樹僅顯示該檔案在各種分支中的出現。在右上角,我們有一個視覺化的圖,顯示了檔案不同修訂版本之間的關係,而此圖的全域性檢視則在右下角。檢視的其餘部分用於顯示選定修訂版本(本例中為 2)的詳細資訊。
值得注意的是,該圖與 Git 歷史記錄中的圖看起來完全相同。Perforce 沒有一個命名的分支來儲存 1 和 2 提交,因此它在 .git-fusion 目錄中建立了一個“匿名”分支來儲存它。對於未對應於 Perforce 命名分支的 Git 命名分支,也會發生這種情況(您以後可以使用配置檔案將它們對映到 Perforce 分支)。
其中大部分發生在後臺,但最終結果是,團隊中的一個人可以使用 Git,另一個人可以使用 Perforce,而他們都不會知道對方的選擇。
Git-Fusion 總結
如果您可以(或能夠)訪問您的 Perforce 伺服器,Git Fusion 是讓 Git 和 Perforce 進行通訊的好方法。雖然需要一些配置,但學習曲線並不陡峭。這是本章中為數不多的沒有關於使用 Git 全部強大功能的警告的部分之一。這並不是說 Perforce 會對您扔給它的所有東西都滿意——如果您嘗試重寫已經推送的歷史記錄,Git Fusion 將會拒絕它——但 Git Fusion 非常努力地讓您感覺它像原生工具一樣。您甚至可以使用 Git 子模組(儘管 Perforce 使用者看起來會很奇怪),以及合併分支(這將在 Perforce 端記錄為整合)。
如果您無法說服您的伺服器管理員設定 Git Fusion,仍然有一種方法可以一起使用這些工具。
Git-p4
Git-p4 是 Git 和 Perforce 之間的雙向橋樑。它完全在您的 Git 儲存庫內部執行,因此您不需要任何 Perforce 伺服器的訪問許可權(除了使用者憑據,當然)。Git-p4 不如 Git Fusion 靈活或完整,但它允許您完成大部分您想要做的事情,而不會侵入伺服器環境。
|
注意
|
您需要將 |
設定
為便於說明,我們將如上所示從 Git Fusion OVA 執行 Perforce 伺服器,但我們將繞過 Git Fusion 伺服器,直接連線到 Perforce 版本控制。
為了使用 p4 命令列客戶端(git-p4 依賴於它),您需要設定幾個環境變數。
$ export P4PORT=10.0.1.254:1666
$ export P4USER=john
入門
與 Git 中的任何東西一樣,第一個命令是克隆。
$ 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
這會在 Git 術語中建立一個“淺層”克隆;只有最新的 Perforce 修訂版本會被匯入到 Git 中;請記住,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
請注意,這裡有一個用於 Perforce 伺服器的“p4”遠端,但其他一切看起來都像標準的克隆。實際上,這有點誤導;實際上並沒有遠端。
$ git remote -v
此儲存庫中根本不存在遠端。Git-p4 建立了一些引用來表示伺服器的狀態,它們在 git log 看來就像遠端引用,但它們不受 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 建議您重新調整提交(rebase)您的提交,甚至提供了一個快捷方式來實現這一點。
$ 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 p4 sync 後跟 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 ben@straub.cc 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 已經樂於包含的。當 git-p4 需要為提交或變更集提供名稱時,它會嘗試單獨考慮您的 Git 和 Perforce 設定,但在某些情況下您可能想覆蓋它。例如,如果正在匯入的 Git 提交是由沒有 Perforce 使用者帳戶的貢獻者編寫的,您可能仍希望生成的變更集看起來像是他們編寫的(而不是您)。
Git-p4 已將 Git 提交的訊息作為此 Perforce 變更集的內容匯入,因此我們只需儲存並退出兩次(每次提交一次)。生成的 shell 輸出看起來大致如下。
$ 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 push 一樣,這是實際發生情況的最接近的比喻。
請注意,在此過程中,每個 Git 提交都會轉換為一個 Perforce 變更集;如果您想將它們壓縮成一個變更集,您可以在執行 git p4 submit 之前使用互動式 rebase 來實現。另請注意,所有被提交為變更集的提交的 SHA-1 雜湊值都已更改;這是因為 git-p4 在它轉換的每個提交的末尾添加了一行。
$ git log -1
commit 775a46f630d8b46535fc9983cf3ebe6b9aa53145
Author: John Doe <john@example.com>
Date: Sun Aug 31 10:31:44 2014 -0800
Change page title
[git-p4: depot-paths = "//depot/www/live/": change = 12144]
如果您嘗試提交合並提交,會發生什麼?讓我們來嘗試一下。這是我們讓自己陷入的局面。
$ 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
Git 和 Perforce 的歷史在 775a46f 之後分歧。Git 端有兩個提交,然後是一個帶有 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 變更集,它們對應於尚未在 Perforce 伺服器上存在的三個非合併提交。這聽起來正是我們想要的,讓我們看看結果如何。
$ git p4 submit
[…]
$ 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
我們的歷史記錄已變為線性,就像我們在提交之前進行了 rebase 一樣(事實上,這正是發生的情況)。這意味著您可以自由地在 Git 端建立、處理、丟棄和合並分支,而無需擔心您的歷史記錄會以某種方式與 Perforce 不相容。如果您能 rebase 它,您就可以將其貢獻到 Perforce 伺服器。
分支
如果您的 Perforce 專案有多個分支,您不必絕望;git-p4 可以以一種讓它感覺像 Git 的方式來處理這種情況。假設您的 Perforce 倉庫佈局如下。
//depot
└── project
├── main
└── dev
假設您有一個 dev 分支,其檢視規範如下。
//depot/project/main/... //depot/project/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-p4 不僅克隆該子樹的最新變更集,還克隆所有曾經觸及過這些路徑的變更集。這更接近 Git 的克隆概念,但如果您正在處理一個歷史悠久的專案,這可能需要一段時間。
--detect-branches 標誌告訴 git-p4 使用 Perforce 的分支規範將分支對映到 Git 引用。如果 Perforce 伺服器上不存在這些對映(這是使用 Perforce 的完全有效的方式),您可以告訴 git-p4 分支對映是什麼,您將得到相同的結果。
$ 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-p4,“main”和“dev”都是分支,而第二個是第一個的子分支。
如果我們現在 git checkout -b dev p4/project/dev 並進行一些提交,git-p4 會足夠智慧地在執行 git p4 submit 時定位正確的分支。不幸的是,git-p4 無法混合使用淺層克隆和多個分支;如果您有一個大型專案並想處理多個分支,您將不得不為要提交的每個分支執行一次 git p4 clone。
要建立或整合分支,您必須使用 Perforce 客戶端。Git-p4 只能同步和提交到現有分支,而且它一次只能執行一個線性變更集。如果您在 Git 中合併兩個分支並嘗試提交新的變更集,所有記錄的將是大量的檔案更改;有關哪些分支參與整合的元資料將丟失。
Git 和 Perforce 總結
Git-p4 使在 Perforce 伺服器上使用 Git 工作流程成為可能,並且它在這方面做得相當好。但是,重要的是要記住 Perforce 是原始碼的管理者,而您僅使用 Git 進行本地工作。請務必小心共享 Git 提交;如果您有一個其他人使用的遠端倉庫,請不要推送任何尚未提交到 Perforce 伺服器的提交。
如果您想自由地將 Perforce 和 Git 作為原始碼控制的客戶端混合使用,並且能夠說服伺服器管理員安裝它,Git Fusion 可以讓 Git 成為 Perforce 伺服器的頂級版本控制客戶端。