1. 程式人生 > >子模組 submodule

子模組 submodule

7.11 Git 工具 - 子模組
子模組
有種情況我們經常會遇到:某個工作中的專案需要包含並使用另一個專案。 也許是第三方庫,或者你獨立開發的,用於多個父專案的庫。 現在問題來了:你想要把它們當做兩個獨立的專案,同時又想在一個專案中使用另一個。

我們舉一個例子。 假設你正在開發一個網站然後建立了 Atom 訂閱。 你決定使用一個庫,而不是寫自己的 Atom 生成程式碼。 你可能不得不通過 CPAN 安裝或 Ruby gem 來包含共享庫中的程式碼,或者將原始碼直接拷貝到自己的專案中。 如果將這個庫包含進來,那麼無論用何種方式都很難定製它,部署則更加困難,因為你必須確保每一個客戶端都包含該庫。 如果將程式碼複製到自己的專案中,那麼你做的任何自定義修改都會使合併上游的改動變得困難。

Git 通過子模組來解決這個問題。 子模組允許你將一個 Git 倉庫作為另一個 Git 倉庫的子目錄。 它能讓你將另一個倉庫克隆到自己的專案中,同時還保持提交的獨立。

開始使用子模組
我們將要演示如何在一個被分成一個主專案與幾個子專案的專案上開發。

我們首先將一個已存在的 Git 倉庫新增為正在工作的倉庫的子模組。 你可以通過在 git submodule add 命令後面加上想要跟蹤的專案 URL 來新增新的子模組。 在本例中,我們將會新增一個名為 “DbConnector” 的庫。

$ git submodule add https://github.com/chaconinc/DbConnector


Cloning into ‘DbConnector’…
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity… done.
預設情況下,子模組會將子專案放到一個與倉庫同名的目錄中,本例中是 “DbConnector”。 如果你想要放到其他地方,那麼可以在命令結尾新增一個不同的路徑。

如果這時執行 git status,你會注意到幾件事。

$ git status
On branch master
Your branch is up-to-date with ‘origin/master’.

Changes to be committed:
(use “git reset HEAD …” to unstage)

new file:   .gitmodules
new file:   DbConnector

首先應當注意到新的 .gitmodules 檔案。 該置檔案儲存了專案 URL 與已經拉取的本地目錄之間的對映:

$ cat .gitmodules
[submodule “DbConnector”]
path = DbConnector
url = https://github.com/chaconinc/DbConnector
如果有多個子模組,該檔案中就會有多條記錄。 要重點注意的是,該檔案也像 .gitignore 檔案一樣受到(通過)版本控制。 它會和該專案的其他部分一同被拉取推送。 這就是克隆該專案的人知道去哪獲得子模組的原因。

Note
由於 .gitmodules 檔案中的 URL 是人們首先嚐試克隆/拉取的地方,因此請儘可能確保你使用的URL 大家都能訪問。 例如,若你要使用的推送 URL 與他人的拉取 URL 不同,那麼請使用他人能訪問到的 URL。 你也可以根據自己的需要,通過在本地執行 git config submodule.DbConnector.url <私有URL> 來覆蓋這個選項的值。 如果可行的話,一個相對路徑會很有幫助。

在 git status 輸出中列出的另一個是專案資料夾記錄。 如果你執行 git diff,會看到類似下面的資訊:

$ git diff –cached DbConnector
diff –git a/DbConnector b/DbConnector
new file mode 160000
index 0000000..c3f01dc
— /dev/null
+++ b/DbConnector
@@ -0,0 +1 @@
+Subproject commit c3f01dc8862123d317dd46284b05b6892c7b29bc
雖然 DbConnector 是工作目錄中的一個子目錄,但 Git 還是會將它視作一個子模組。當你不在那個目錄中時,Git 並不會跟蹤它的內容, 而是將它看作該倉庫中的一個特殊提交。

如果你想看到更漂亮的差異輸出,可以給 git diff 傳遞 –submodule 選項。

$ git diff –cached –submodule
diff –git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..71fc376
— /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule “DbConnector”]
+ path = DbConnector
+ url = https://github.com/chaconinc/DbConnector
Submodule DbConnector 0000000…c3f01dc (new submodule)
當你提交時,會看到類似下面的資訊:

$ git commit -am ‘added DbConnector module’
[master fb9093c] added DbConnector module
2 files changed, 4 insertions(+)
create mode 100644 .gitmodules
create mode 160000 DbConnector
注意 DbConnector 記錄的 160000 模式。 這是 Git 中的一種特殊模式,它本質上意味著你是將一次提交記作一項目錄記錄的,而非將它記錄成一個子目錄或者一個檔案。

克隆含有子模組的專案
接下來我們將會克隆一個含有子模組的專案。 當你在克隆這樣的專案時,預設會包含該子模組目錄,但其中還沒有任何檔案:

$ git clone https://github.com/chaconinc/MainProject
Cloning into ‘MainProject’…
remote: Counting objects: 14, done.
remote: Compressing objects: 100% (13/13), done.
remote: Total 14 (delta 1), reused 13 (delta 0)
Unpacking objects: 100% (14/14), done.
Checking connectivity… done.
cdMainProject ls -la
total 16
drwxr-xr-x 9 schacon staff 306 Sep 17 15:21 .
drwxr-xr-x 7 schacon staff 238 Sep 17 15:21 ..
drwxr-xr-x 13 schacon staff 442 Sep 17 15:21 .git
-rw-r–r– 1 schacon staff 92 Sep 17 15:21 .gitmodules
drwxr-xr-x 2 schacon staff 68 Sep 17 15:21 DbConnector
-rw-r–r– 1 schacon staff 756 Sep 17 15:21 Makefile
drwxr-xr-x 3 schacon staff 102 Sep 17 15:21 includes
drwxr-xr-x 4 schacon staff 136 Sep 17 15:21 scripts
drwxr-xr-x 4 schacon staff 136 Sep 17 15:21 src
cdDbConnector/ ls
$
其中有 DbConnector 目錄,不過是空的。 你必須執行兩個命令:git submodule init 用來初始化本地配置檔案,而 git submodule update 則從該專案中抓取所有資料並檢出父專案中列出的合適的提交。

$ git submodule init
Submodule ‘DbConnector’ (https://github.com/chaconinc/DbConnector) registered for path ‘DbConnector’
$ git submodule update
Cloning into ‘DbConnector’…
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity… done.
Submodule path ‘DbConnector’: checked out ‘c3f01dc8862123d317dd46284b05b6892c7b29bc’
現在 DbConnector 子目錄是處在和之前提交時相同的狀態了。

不過還有更簡單一點的方式。 如果給 git clone 命令傳遞 –recursive 選項,它就會自動初始化並更新倉庫中的每一個子模組。

$ git clone –recursive https://github.com/chaconinc/MainProject
Cloning into ‘MainProject’…
remote: Counting objects: 14, done.
remote: Compressing objects: 100% (13/13), done.
remote: Total 14 (delta 1), reused 13 (delta 0)
Unpacking objects: 100% (14/14), done.
Checking connectivity… done.
Submodule ‘DbConnector’ (https://github.com/chaconinc/DbConnector) registered for path ‘DbConnector’
Cloning into ‘DbConnector’…
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity… done.
Submodule path ‘DbConnector’: checked out ‘c3f01dc8862123d317dd46284b05b6892c7b29bc’
在包含子模組的專案上工作
現在我們有一份包含子模組的專案副本,我們將會同時在主專案和子模組專案上與隊員協作。

拉取上游修改
在專案中使用子模組的最簡模型,就是隻使用子專案並不時地獲取更新,而並不在你的檢出中進行任何更改。 我們來看一個簡單的例子。

如果想要在子模組中檢視新工作,可以進入到目錄中執行 git fetch 與 git merge,合併上游分支來更新原生代碼。

$ git fetch
From https://github.com/chaconinc/DbConnector
c3f01dc..d0354fc master -> origin/master
$ git merge origin/master
Updating c3f01dc..d0354fc
Fast-forward
scripts/connect.sh | 1 +
src/db.c | 1 +
2 files changed, 2 insertions(+)
如果你現在返回到主專案並執行 git diff –submodule,就會看到子模組被更新的同時獲得了一個包含新新增提交的列表。 如果你不想每次執行 git diff 時都輸入 –submodle,那麼可以將 diff.submodule 設定為 “log” 來將其作為預設行為。

gitconfigglobaldiff.submodulelog git diff
Submodule DbConnector c3f01dc..d0354fc:

more efficient db routine
better connection routine
如果在此時提交,那麼你會將子模組鎖定為其他人更新時的新程式碼。

如果你不想在子目錄中手動抓取與合併,那麼還有種更容易的方式。 執行 git submodule update –remote,Git 將會進入子模組然後抓取並更新。

$ git submodule update –remote DbConnector
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 2), reused 4 (delta 2)
Unpacking objects: 100% (4/4), done.
From https://github.com/chaconinc/DbConnector
3f19983..d0354fc master -> origin/master
Submodule path ‘DbConnector’: checked out ‘d0354fc054692d3906c85c3af05ddce39a1c0644’
此命令預設會假定你想要更新並檢出子模組倉庫的 master 分支。 不過你也可以設定為想要的其他分支。 例如,你想要 DbConnector 子模組跟蹤倉庫的 “stable” 分支,那麼既可以在 .gitmodules 檔案中設定(這樣其他人也可以跟蹤它),也可以只在本地的 .git/config 檔案中設定。 讓我們在 .gitmodules 檔案中設定它:

$ git config -f .gitmodules submodule.DbConnector.branch stable

$ git submodule update –remote
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 2), reused 4 (delta 2)
Unpacking objects: 100% (4/4), done.
From https://github.com/chaconinc/DbConnector
27cf5d3..c87d55d stable -> origin/stable
Submodule path ‘DbConnector’: checked out ‘c87d55d4c6d4b05ee34fbc8cb6f7bf4585ae6687’
如果不用 -f .gitmodules 選項,那麼它只會為你做修改。但是在倉庫中保留跟蹤資訊更有意義一些,因為其他人也可以得到同樣的效果。

這時我們執行 git status,Git 會顯示子模組中有 “新提交”。

$ git status
On branch master
Your branch is up-to-date with ‘origin/master’.

Changes not staged for commit:
(use “git add …” to update what will be committed)
(use “git checkout – …” to discard changes in working directory)

modified: .gitmodules
modified: DbConnector (new commits)

no changes added to commit (use “git add” and/or “git commit -a”)
如果你設定了配置選項 status.submodulesummary,Git 也會顯示你的子模組的更改摘要:

$ git config status.submodulesummary 1

$ git status
On branch master
Your branch is up-to-date with ‘origin/master’.

Changes not staged for commit:
(use “git add …” to update what will be committed)
(use “git checkout – …” to discard changes in working directory)

modified:   .gitmodules
modified:   DbConnector (new commits)

Submodules changed but not updated:

  • DbConnector c3f01dc…c87d55d (4):

    catch non-null terminated lines
    這時如果執行 git diff,可以看到我們修改了 .gitmodules 檔案,同時還有幾個已拉取的提交需要提交到我們自己的子模組專案中。


$ git diff
diff –git a/.gitmodules b/.gitmodules
index 6fc0b3d..fd1cc29 100644
— a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,4 @@
[submodule “DbConnector”]
path = DbConnector
url = https://github.com/chaconinc/DbConnector
+ branch = stable
Submodule DbConnector c3f01dc..c87d55d:
catch non-null terminated lines
more robust error handling
more efficient db routine
better connection routine
這非常有趣,因為我們可以直接看到將要提交到子模組中的提交日誌。 提交之後,你也可以執行 git log -p 檢視這個資訊。 $ git log -p –submodule commit 0a24cfc121a8a3c118e0105ae4ae4c00281cf7ae Author: Scott Chacon [email protected] Date: Wed Sep 17 16:37:02 2014 +0200
updating DbConnector for bug fixes
diff –git a/.gitmodules b/.gitmodules index 6fc0b3d..fd1cc29 100644 — a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,4 @@ [submodule “DbConnector”] path = DbConnector url = https://github.com/chaconinc/DbConnector + branch = stable Submodule DbConnector c3f01dc..c87d55d:

catch non-null terminated lines
more robust error handling
more efficient db routine
better connection routine
當執行 git submodule update –remote 時,Git 預設會嘗試更新所有子模組,所以如果有很多子模組的話,你可以傳遞想要更新的子模組的名字。

在子模組上工作 你很有可能正在使用子模組,因為你確實想在子模組中編寫程式碼的同時,還想在主專案上編寫程式碼(或者跨子模組工作)。 否則你大概只能用簡單的依賴管理系統(如 Maven 或 Rubygems)來替代了。 現在我們將通過一個例子來演示如何在子模組與主專案中同時做修改,以及如何同時提交與釋出那些修改。 到目前為止,當我們執行 git submodule update 從子模組倉庫中抓取修改時,Git 將會獲得這些改動並更新子目錄中的檔案,但是會將子倉庫留在一個稱作 “遊離的 HEAD” 的狀態。 這意味著沒有本地工作分支(例如 “master”)跟蹤改動。 所以你做的任何改動都不會被跟蹤。 為了將子模組設定得更容易進入並修改,你需要做兩件事。 首先,進入每個子模組並檢出其相應的工作分支。 接著,若你做了更改就需要告訴 Git 它該做什麼,然後執行 git submodule update –remote 來從上游拉取新工作。 你可以選擇將它們合併到你的本地工作中,也可以嘗試將你的工作變基到新的更改上。 首先,讓我們進入子模組目錄然後檢出一個分支。 gitcheckoutstableSwitchedtobranchstablemergeupdatemerge git submodule update –remote –merge remote: Counting objects: 4, done. remote: Compressing objects: 100% (2/2), done. remote: Total 4 (delta 2), reused 4 (delta 2) Unpacking objects: 100% (4/4), done. From https://github.com/chaconinc/DbConnector c87d55d..92c7337 stable -> origin/stable Updating c87d55d..92c7337 Fast-forward src/main.c | 1 + 1 file changed, 1 insertion(+) Submodule path ‘DbConnector’: merged in ‘92c7337b30ef9e0893e758dac2459d07362ab5ea’ 如果我們進入 DbConnector 目錄,可以發現新的改動已經合併入本地 stable 分支。 現在讓我們看看當我們對庫做一些本地的改動而同時其他人推送另外一個修改到上游時會發生什麼。 cdDbConnector/ vim src/db.c gitcommitamunicodesupport[stablef906e16]unicodesupport1filechanged,1insertion(+)