有種情況我們經(jīng)常會遇到:某個工作中的項目需要包含并使用另一個項目。 也許是第三方庫,或者你獨立開發(fā)的,用于多個父項目的庫。 現(xiàn)在問題來了:你想要把它們當做兩個獨立的項目,同時又想在一個項目中使用另一個。
我們舉一個例子。 假設(shè)你正在開發(fā)一個網(wǎng)站然后創(chuàng)建了 Atom 訂閱。 你決定使用一個庫,而不是寫自己的 Atom 生成代碼。 你可能不得不通過 CPAN 安裝或 Ruby gem 來包含共享庫中的代碼,或者將源代碼直接拷貝到自己的項目中。 如果將這個庫包含進來,那么無論用何種方式都很難定制它,部署則更加困難,因為你必須確保每一個客戶端都包含該庫。 如果將代碼復制到自己的項目中,那么你做的任何自定義修改都會使合并上游的改動變得困難。
Git 通過子模塊來解決這個問題。 子模塊允許你將一個 Git 倉庫作為另一個 Git 倉庫的子目錄。 它能讓你將另一個倉庫克隆到自己的項目中,同時還保持提交的獨立。
我們將要演示如何在一個被分成一個主項目與幾個子項目的項目上開發(fā)。
我們首先將一個已存在的 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”。 如果你想要放到其他地方,那么可以在命令結(jié)尾添加一個不同的路徑。
如果這時運行 git status,你會注意到幾件事。
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: .gitmodules
new file: DbConnector
首先應(yīng)當注意到新的 .gitmodules 文件。 該置文件保存了項目 URL 與已經(jīng)拉取的本地目錄之間的映射:
$ cat .gitmodules
[submodule "DbConnector"]
path = DbConnector
url = https://github.com/chaconinc/DbConnector
如果有多個子模塊,該文件中就會有多條記錄。 要重點注意的是,該文件也像 .gitignore 文件一樣受到(通過)版本控制。 它會和該項目的其他部分一同被拉取推送。 這就是克隆該項目的人知道去哪獲得子模塊的原因。
Note
由于 .gitmodules 文件中的 URL 是人們首先嘗試克隆/拉取的地方,因此請盡可能確保你使用的URL 大家都能訪問。 例如,若你要使用的推送 URL 與他人的拉取 URL 不同,那么請使用他人能訪問到的 URL。 你也可以根據(jù)自己的需要,通過在本地執(zhí)行 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 并不會跟蹤它的內(nèi)容, 而是將它看作該倉庫中的一個特殊提交。
如果你想看到更漂亮的差異輸出,可以給 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 中的一種特殊模式,它本質(zhì)上意味著你是將一次提交記作一項目錄記錄的,而非將它記錄成一個子目錄或者一個文件。
接下來我們將會克隆一個含有子模塊的項目。 當你在克隆這樣的項目時,默認會包含該子模塊目錄,但其中還沒有任何文件:
$ 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.
$ cd MainProject
$ 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
$ cd DbConnector/
$ ls
$
其中有 DbConnector 目錄,不過是空的。 你必須運行兩個命令:git submodule init 用來初始化本地配置文件,而 git submodule update 則從該項目中抓取所有數(shù)據(jù)并檢出父項目中列出的合適的提交。
$ 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'
現(xiàn)在 DbConnector 子目錄是處在和之前提交時相同的狀態(tài)了。
不過還有更簡單一點的方式。 如果給 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'
現(xiàn)在我們有一份包含子模塊的項目副本,我們將會同時在主項目和子模塊項目上與隊員協(xié)作。
在項目中使用子模塊的最簡模型,就是只使用子項目并不時地獲取更新,而并不在你的檢出中進行任何更改。 我們來看一個簡單的例子。
如果想要在子模塊中查看新工作,可以進入到目錄中運行 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(+)
如果你現(xiàn)在返回到主項目并運行 git diff --submodule,就會看到子模塊被更新的同時獲得了一個包含新添加提交的列表。 如果你不想每次運行 git diff 時都輸入 --submodle,那么可以將 diff.submodule 設(shè)置為 “l(fā)og” 來將其作為默認行為。
$ git config --global diff.submodule log
$ 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 分支。 不過你也可以設(shè)置為想要的其他分支。 例如,你想要 DbConnector 子模塊跟蹤倉庫的 “stable” 分支,那么既可以在 .gitmodules 文件中設(shè)置(這樣其他人也可以跟蹤它),也可以只在本地的 .git/config 文件中設(shè)置。 讓我們在 .gitmodules 文件中設(shè)置它:
$ 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 <file>..." to update what will be committed)
(use "git checkout -- <file>..." 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")
如果你設(shè)置了配置選項 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 <file>..." to update what will be committed)
(use "git checkout -- <file>..." 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 <schacon@gmail.com>
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 默認會嘗試更新所有子模塊,所以如果有很多子模塊的話,你可以傳遞想要更新的子模塊的名字。
你很有可能正在使用子模塊,因為你確實想在子模塊中編寫代碼的同時,還想在主項目上編寫代碼(或者跨子模塊工作)。 否則你大概只能用簡單的依賴管理系統(tǒng)(如 Maven 或 Rubygems)來替代了。
現(xiàn)在我們將通過一個例子來演示如何在子模塊與主項目中同時做修改,以及如何同時提交與發(fā)布那些修改。
到目前為止,當我們運行 git submodule update 從子模塊倉庫中抓取修改時,Git 將會獲得這些改動并更新子目錄中的文件,但是會將子倉庫留在一個稱作 “游離的 HEAD” 的狀態(tài)。 這意味著沒有本地工作分支(例如 “master”)跟蹤改動。 所以你做的任何改動都不會被跟蹤。
為了將子模塊設(shè)置得更容易進入并修改,你需要做兩件事。 首先,進入每個子模塊并檢出其相應(yīng)的工作分支。 接著,若你做了更改就需要告訴 Git 它該做什么,然后運行 git submodule update --remote 來從上游拉取新工作。 你可以選擇將它們合并到你的本地工作中,也可以嘗試將你的工作變基到新的更改上。
首先,讓我們進入子模塊目錄然后檢出一個分支。
$ git checkout stable
Switched to branch 'stable'
然后嘗試用 “merge” 選項。 為了手動指定它,我們只需給 update 添加 --merge 選項即可。 這時我們將會看到服務(wù)器上的這個子模塊有一個改動并且它被合并了進來。
$ 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 目錄,可以發(fā)現(xiàn)新的改動已經(jīng)合并入本地 stable 分支。 現(xiàn)在讓我們看看當我們對庫做一些本地的改動而同時其他人推送另外一個修改到上游時會發(fā)生什么。
$ cd DbConnector/
$ vim src/db.c
$ git commit -am 'unicode support'
[stable f906e16] unicode support
1 file changed, 1 insertion(+)
如果我們現(xiàn)在更新子模塊,就會看到當我們在本地做了更改時上游也有一個改動,我們需要將它并入本地。
$ git submodule update --remote --rebase
First, rewinding head to replay your work on top of it...
Applying: unicode support
Submodule path 'DbConnector': rebased into '5d60ef9bbebf5a0c1c1050f242ceeb54ad58da94'
如果你忘記 --rebase 或 --merge,Git 會將子模塊更新為服務(wù)器上的狀態(tài)。并且會將項目重置為一個游離的 HEAD 狀態(tài)。
$ git submodule update --remote
Submodule path 'DbConnector': checked out '5d60ef9bbebf5a0c1c1050f242ceeb54ad58da94'
即便這真的發(fā)生了也不要緊,你只需回到目錄中再次檢出你的分支(即還包含著你的工作的分支)然后手動地合并或變基 origin/stable(或任何一個你想要的遠程分支)就行了。
如果你沒有提交子模塊的改動,那么運行一個子模塊更新也不會出現(xiàn)問題,此時 Git 會只抓取更改而并不會覆蓋子模塊目錄中未保存的工作。
$ git submodule update --remote
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 4 (delta 0), reused 4 (delta 0)
Unpacking objects: 100% (4/4), done.
From https://github.com/chaconinc/DbConnector
5d60ef9..c75e92a stable -> origin/stable
error: Your local changes to the following files would be overwritten by checkout:
scripts/setup.sh
Please, commit your changes or stash them before you can switch branches.
Aborting
Unable to checkout 'c75e92a2b3855c9e5b66f915308390d9db204aca' in submodule path 'DbConnector'
如果你做了一些與上游改動沖突的改動,當運行更新時 Git 會讓你知道。
$ git submodule update --remote --merge
Auto-merging scripts/setup.sh
CONFLICT (content): Merge conflict in scripts/setup.sh
Recorded preimage for 'scripts/setup.sh'
Automatic merge failed; fix conflicts and then commit the result.
Unable to merge 'c75e92a2b3855c9e5b66f915308390d9db204aca' in submodule path 'DbConnector'
你可以進入子模塊目錄中然后就像平時那樣修復沖突。
現(xiàn)在我們的子模塊目錄中有一些改動。 其中有一些是我們通過更新從上游引入的,而另一些是本地生成的,由于我們還沒有推送它們,所以對任何其他人都不可用。
$ git diff
Submodule DbConnector c87d55d..82d2ad3:
> Merge from origin/stable
> updated setup script
> unicode support
> remove unnecessary method
> add new option for conn pooling
如果我們在主項目中提交并推送但并不推送子模塊上的改動,其他嘗試檢出我們修改的人會遇到麻煩,因為他們無法得到依賴的子模塊改動。 那些改動只存在于我們本地的拷貝中。
為了確保這不會發(fā)生,你可以讓 Git 在推送到主項目前檢查所有子模塊是否已推送。 git push 命令接受可以設(shè)置為 “check” 或 “on-demand” 的 --recurse-submodules 參數(shù)。 如果任何提交的子模塊改動沒有推送那么 “check” 選項會直接使 push 操作失敗。
$ git push --recurse-submodules=check
The following submodule paths contain changes that can
not be found on any remote:
DbConnector
Please try
git push --recurse-submodules=on-demand
or cd to the path and use
git push
to push them to a remote.
如你所見,它也給我們了一些有用的建議,指導接下來該如何做。 最簡單的選項是進入每一個子模塊中然后手動推送到遠程倉庫,確保它們能被外部訪問到,之后再次嘗試這次推送。
另一個選項是使用 “on-demand” 值,它會嘗試為你這樣做。
$ git push --recurse-submodules=on-demand
Pushing submodule 'DbConnector'
Counting objects: 9, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (8/8), done.
Writing objects: 100% (9/9), 917 bytes | 0 bytes/s, done.
Total 9 (delta 3), reused 0 (delta 0)
To https://github.com/chaconinc/DbConnector
c75e92a..82d2ad3 stable -> stable
Counting objects: 2, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 266 bytes | 0 bytes/s, done.
Total 2 (delta 1), reused 0 (delta 0)
To https://github.com/chaconinc/MainProject
3d6d338..9a377d1 master -> master
如你所見,Git 進入到 DbConnector 模塊中然后在推送主項目前推送了它。 如果那個子模塊因為某些原因推送失敗,主項目也會推送失敗。
如果你其他人同時改動了一個子模塊引用,那么可能會遇到一些問題。 也就是說,如果子模塊的歷史已經(jīng)分叉并且在父項目中分別提交到了分叉的分支上,那么你需要做一些工作來修復它。
如果一個提交是另一個的直接祖先(一個快進式合并),那么 Git 會簡單地選擇之后的提交來合并,這樣沒什么問題。
不過,Git 甚至不會嘗試去進行一次簡單的合并。 如果子模塊提交已經(jīng)分叉且需要合并,那你會得到類似下面的信息:
$ git pull
remote: Counting objects: 2, done.
remote: Compressing objects: 100% (1/1), done.
remote: Total 2 (delta 1), reused 2 (delta 1)
Unpacking objects: 100% (2/2), done.
From https://github.com/chaconinc/MainProject
9a377d1..eb974f8 master -> origin/master
Fetching submodule DbConnector
warning: Failed to merge submodule DbConnector (merge following commits not found)
Auto-merging DbConnector
CONFLICT (submodule): Merge conflict in DbConnector
Automatic merge failed; fix conflicts and then commit the result.
所以本質(zhì)上 Git 在這里指出了子模塊歷史中的兩個分支記錄點已經(jīng)分叉并且需要合并。 它將其解釋為 “merge following commits not found”(未找到接下來需要合并的提交),雖然這有點令人困惑,不過之后我們會解釋為什么是這樣。
為了解決這個問題,你需要弄清楚子模塊應(yīng)該處于哪種狀態(tài)。 奇怪的是,Git 并不會給你多少能幫你擺脫困境的信息,甚至連兩邊提交歷史中的 SHA-1 值都沒有。 幸運的是,這很容易解決。 如果你運行 git diff,就會得到試圖合并的兩個分支中記錄的提交的 SHA-1 值。
$ git diff
diff --cc DbConnector
index eb41d76,c771610..0000000
--- a/DbConnector
+++ b/DbConnector
所以,在本例中,eb41d76 是我們的子模塊中大家共有的提交,而 c771610 是上游擁有的提交。 如果我們進入子模塊目錄中,它應(yīng)該已經(jīng)在 eb41d76 上了,因為合并沒有動過它。 如果不是的話,無論什么原因,你都可以簡單地創(chuàng)建并檢出一個指向它的分支。
來自另一邊的提交的 SHA-1 值比較重要。 它是需要你來合并解決的。 你可以嘗試直接通過 SHA-1 合并,也可以為它創(chuàng)建一個分支然后嘗試合并。 我們建議后者,哪怕只是為了一個更漂亮的合并提交信息。
所以,我們將會進入子模塊目錄,基于 git diff 的第二個 SHA 創(chuàng)建一個分支然后手動合并。
$ cd DbConnector
$ git rev-parse HEAD
eb41d764bccf88be77aced643c13a7fa86714135
$ git branch try-merge c771610
(DbConnector) $ git merge try-merge
Auto-merging src/main.c
CONFLICT (content): Merge conflict in src/main.c
Recorded preimage for 'src/main.c'
Automatic merge failed; fix conflicts and then commit the result.
我們在這兒得到了一個真正的合并沖突,所以如果想要解決并提交它,那么只需簡單地通過結(jié)果來更新主項目。
$ vim src/main.c
$ git add src/main.c
$ git commit -am 'merged our changes'
Recorded resolution for 'src/main.c'.
[master 9fd905e] merged our changes
$ cd ..
$ git diff
diff --cc DbConnector
index eb41d76,c771610..0000000
--- a/DbConnector
+++ b/DbConnector
@@@ -1,1 -1,1 +1,1 @@@
- Subproject commit eb41d764bccf88be77aced643c13a7fa86714135
-Subproject commit c77161012afbbe1f58b5053316ead08f4b7e6d1d
++Subproject commit 9fd905e5d7f45a0d4cbc43d1ee550f16a30e825a
$ git add DbConnector
$ git commit -m "Merge Tom's Changes"
[master 10d2c60] Merge Tom's Changes
首先解決沖突
然后返回到主項目目錄中
再次檢查 SHA-1 值
解決沖突的子模塊記錄
提交我們的合并
這可能會讓你有點兒困惑,但它確實不難。
有趣的是,Git 還能處理另一種情況。 如果子模塊目錄中存在著這樣一個合并提交,它的歷史中包含了的兩邊的提交,那么 Git 會建議你將它作為一個可行的解決方案。 它看到有人在子模塊項目的某一點上合并了包含這兩次提交的分支,所以你可能想要那個。
這就是為什么前面的錯誤信息是 “merge following commits not found”,因為它不能 這樣 做。 它讓人困惑是因為誰能想到它會嘗試這樣做?
如果它找到了一個可以接受的合并提交,你會看到類似下面的信息:
$ git merge origin/master
warning: Failed to merge submodule DbConnector (not fast-forward)
Found a possible merge resolution for the submodule:
9fd905e5d7f45a0d4cbc43d1ee550f16a30e825a: > merged our changes
If this is correct simply add it to the index for example
by using:
git update-index --cacheinfo 160000 9fd905e5d7f45a0d4cbc43d1ee550f16a30e825a "DbConnector"
which will accept this suggestion.
Auto-merging DbConnector
CONFLICT (submodule): Merge conflict in DbConnector
Automatic merge failed; fix conflicts and then commit the result.
它會建議你更新索引,就像你運行了 git add 那樣,這樣會清除沖突然后提交。不過你可能不應(yīng)該這樣做。你可以輕松地進入子模塊目錄,查看差異是什么,快進到這次提交,恰當?shù)販y試,然后提交它。
$ cd DbConnector/
$ git merge 9fd905e
Updating eb41d76..9fd905e
Fast-forward
$ cd ..
$ git add DbConnector
$ git commit -am 'Fast forwarded to a common submodule child'
這些命令完成了同一件事,但是通過這種方式你至少可以驗證工作是否有效,以及當你在完成時可以確保子模塊目錄中有你的代碼。
你可以做幾件事情來讓用子模塊工作輕松一點兒。
有一個 foreach 子模塊命令,它能在每一個子模塊中運行任意命令。 如果項目中包含了大量子模塊,這會非常有用。
例如,假設(shè)我們想要開始開發(fā)一項新功能或者修復一些錯誤,并且需要在幾個子模塊內(nèi)工作。 我們可以輕松地保存所有子模塊的工作進度。
$ git submodule foreach 'git stash'
Entering 'CryptoLibrary'
No local changes to save
Entering 'DbConnector'
Saved working directory and index state WIP on stable: 82d2ad3 Merge from origin/stable
HEAD is now at 82d2ad3 Merge from origin/stable
然后我們可以創(chuàng)建一個新分支,并將所有子模塊都切換過去。
$ git submodule foreach 'git checkout -b featureA'
Entering 'CryptoLibrary'
Switched to a new branch 'featureA'
Entering 'DbConnector'
Switched to a new branch 'featureA'
你應(yīng)該明白。 能夠生成一個主項目與所有子項目的改動的統(tǒng)一差異是非常有用的。
$ git diff; git submodule foreach 'git diff'
Submodule DbConnector contains modified content
diff --git a/src/main.c b/src/main.c
index 210f1ae..1f0acdc 100644
--- a/src/main.c
+++ b/src/main.c
@@ -245,6 +245,8 @@ static int handle_alias(int *argcp, const char ***argv)
commit_pager_choice();
+ url = url_decode(url_orig);
+
/* build alias_argv */
alias_argv = xmalloc(sizeof(*alias_argv) * (argc + 1));
alias_argv[0] = alias_string + 1;
Entering 'DbConnector'
diff --git a/src/db.c b/src/db.c
index 1aaefb6..5297645 100644
--- a/src/db.c
+++ b/src/db.c
@@ -93,6 +93,11 @@ char *url_decode_mem(const char *url, int len)
return url_decode_internal(&url, len, NULL, &out, 0);
}
+char *url_decode(const char *url)
+{
+ return url_decode_mem(url, strlen(url));
+}
+
char *url_decode_parameter_name(const char **query)
{
struct strbuf out = STRBUF_INIT;
在這里,我們看到子模塊中定義了一個函數(shù)并在主項目中調(diào)用了它。 這明顯是個簡化了的例子,但是希望它能讓你明白這種方法的用處。
你可能想為其中一些命令設(shè)置別名,因為它們可能會非常長而你又不能設(shè)置選項作為它們的默認選項。 我們在 Git 別名 介紹了設(shè)置 Git 別名,但是如果你計劃在 Git 中大量使用子模塊的話,這里有一些例子。
$ git config alias.sdiff '!'"git diff && git submodule foreach 'git diff'"
$ git config alias.spush 'push --recurse-submodules=on-demand'
$ git config alias.supdate 'submodule update --remote --merge'
這樣當你想要更新子模塊時可以簡單地運行 git supdate,或 git spush 檢查子模塊依賴后推送。
然而使用子模塊還是有一些小問題。
例如在有子模塊的項目中切換分支可能會造成麻煩。 如果你創(chuàng)建一個新分支,在其中添加一個子模塊,之后切換到?jīng)]有該子模塊的分支上時,你仍然會有一個還未跟蹤的子模塊目錄。
$ git checkout -b add-crypto
Switched to a new branch 'add-crypto'
$ git submodule add https://github.com/chaconinc/CryptoLibrary
Cloning into 'CryptoLibrary'...
...
$ git commit -am 'adding crypto library'
[add-crypto 4445836] adding crypto library
2 files changed, 4 insertions(+)
create mode 160000 CryptoLibrary
$ git checkout master
warning: unable to rmdir CryptoLibrary: Directory not empty
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Untracked files:
(use "git add <file>..." to include in what will be committed)
CryptoLibrary/
nothing added to commit but untracked files present (use "git add" to track)
移除那個目錄并不困難,但是有一個目錄在那兒會讓人有一點困惑。 如果你移除它然后切換回有那個子模塊的分支,需要運行 submodule update --init 來重新建立和填充。
$ git clean -fdx
Removing CryptoLibrary/
$ git checkout add-crypto
Switched to branch 'add-crypto'
$ ls CryptoLibrary/
$ git submodule update --init
Submodule path 'CryptoLibrary': checked out 'b8dda6aa182ea4464f3f3264b11e0268545172af'
$ ls CryptoLibrary/
Makefile includes scripts src
再說一遍,這真的不難,只是會讓人有點兒困惑。
另一個主要的告誡是許多人遇到了將子目錄轉(zhuǎn)換為子模塊的問題。 如果你在項目中已經(jīng)跟蹤了一些文件,然后想要將它們移動到一個子模塊中,那么請務(wù)必小心,否則 Git 會對你發(fā)脾氣。 假設(shè)項目內(nèi)有一些文件在子目錄中,你想要將其轉(zhuǎn)換為一個子模塊。 如果刪除子目錄然后運行 submodule add,Git 會朝你大喊:
$ rm -Rf CryptoLibrary/
$ git submodule add https://github.com/chaconinc/CryptoLibrary
'CryptoLibrary' already exists in the index
你必須要先取消暫存 CryptoLibrary 目錄。 然后才可以添加子模塊:
$ git rm -r CryptoLibrary
$ git submodule add https://github.com/chaconinc/CryptoLibrary
Cloning into 'CryptoLibrary'...
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.
現(xiàn)在假設(shè)你在一個分支下做了這樣的工作。 如果嘗試切換回的分支中那些文件還在子目錄而非子模塊中時 - 你會得到這個錯誤:
$ git checkout master
error: The following untracked working tree files would be overwritten by checkout:
CryptoLibrary/Makefile
CryptoLibrary/includes/crypto.h
...
Please move or remove them before you can switch branches.
Aborting
你可以通過 check -f 來強制切換,但是要小心,如果其中還有未保存的修改,這個命令會把它們覆蓋掉。
$ git checkout -f master
warning: unable to rmdir CryptoLibrary: Directory not empty
Switched to branch 'master'
當你切換回來之后,因為某些原因你得到了一個空的 CryptoLibrary 目錄,并且 git submodule update 也無法修復它。 你需要進入到子模塊目錄中運行 git checkout . 來找回所有的文件。 你也可以通過 submodule foreach 腳本來為多個子模塊運行它。
要特別注意的是,近來子模塊會將它們的所有 Git 數(shù)據(jù)保存在頂級項目的 .git 目錄中,所以不像舊版本的 Git,摧毀一個子模塊目錄并不會丟失任何提交或分支。
擁有了這些工具,使用子模塊會成為可以在幾個相關(guān)但卻分離的項目上同時開發(fā)的相當簡單有效的方法。