有種情況我們經(jīng)常會(huì)遇到:某個(gè)工作中的項(xiàng)目需要包含并使用另一個(gè)項(xiàng)目。 也許是第三方庫(kù),或者你獨(dú)立開(kāi)發(fā)的,用于多個(gè)父項(xiàng)目的庫(kù)。 現(xiàn)在問(wèn)題來(lái)了:你想要把它們當(dāng)做兩個(gè)獨(dú)立的項(xiàng)目,同時(shí)又想在一個(gè)項(xiàng)目中使用另一個(gè)。
我們舉一個(gè)例子。 假設(shè)你正在開(kāi)發(fā)一個(gè)網(wǎng)站然后創(chuàng)建了 Atom 訂閱。 你決定使用一個(gè)庫(kù),而不是寫(xiě)自己的 Atom 生成代碼。 你可能不得不通過(guò) CPAN 安裝或 Ruby gem 來(lái)包含共享庫(kù)中的代碼,或者將源代碼直接拷貝到自己的項(xiàng)目中。 如果將這個(gè)庫(kù)包含進(jìn)來(lái),那么無(wú)論用何種方式都很難定制它,部署則更加困難,因?yàn)槟惚仨毚_保每一個(gè)客戶端都包含該庫(kù)。 如果將代碼復(fù)制到自己的項(xiàng)目中,那么你做的任何自定義修改都會(huì)使合并上游的改動(dòng)變得困難。
Git 通過(guò)子模塊來(lái)解決這個(gè)問(wèn)題。 子模塊允許你將一個(gè) Git 倉(cāng)庫(kù)作為另一個(gè) Git 倉(cāng)庫(kù)的子目錄。 它能讓你將另一個(gè)倉(cāng)庫(kù)克隆到自己的項(xiàng)目中,同時(shí)還保持提交的獨(dú)立。
我們將要演示如何在一個(gè)被分成一個(gè)主項(xiàng)目與幾個(gè)子項(xiàng)目的項(xiàng)目上開(kāi)發(fā)。
我們首先將一個(gè)已存在的 Git 倉(cāng)庫(kù)添加為正在工作的倉(cāng)庫(kù)的子模塊。 你可以通過(guò)在?git submodule add
?命令后面加上想要跟蹤的項(xiàng)目 URL 來(lái)添加新的子模塊。 在本例中,我們將會(huì)添加一個(gè)名為 “DbConnector” 的庫(kù)。
$ 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.
默認(rèn)情況下,子模塊會(huì)將子項(xiàng)目放到一個(gè)與倉(cāng)庫(kù)同名的目錄中,本例中是 “DbConnector”。 如果你想要放到其他地方,那么可以在命令結(jié)尾添加一個(gè)不同的路徑。
如果這時(shí)運(yùn)行?git status
,你會(huì)注意到幾件事。
$ 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)當(dāng)注意到新的?.gitmodules
?文件。 該置文件保存了項(xiàng)目 URL 與已經(jīng)拉取的本地目錄之間的映射:
$ cat .gitmodules
[submodule "DbConnector"]
path = DbConnector
url = https://github.com/chaconinc/DbConnector
如果有多個(gè)子模塊,該文件中就會(huì)有多條記錄。 要重點(diǎn)注意的是,該文件也像?.gitignore
?文件一樣受到(通過(guò))版本控制。 它會(huì)和該項(xiàng)目的其他部分一同被拉取推送。 這就是克隆該項(xiàng)目的人知道去哪獲得子模塊的原因。
NOTE
由于 .gitmodules 文件中的 URL 是人們首先嘗試克隆/拉取的地方,因此請(qǐng)盡可能確保你使用的URL 大家都能訪問(wèn)。 例如,若你要使用的推送 URL 與他人的拉取 URL 不同,那么請(qǐng)使用他人能訪問(wèn)到的 URL。 你也可以根據(jù)自己的需要,通過(guò)在本地執(zhí)行?
git config submodule.DbConnector.url <私有URL>
?來(lái)覆蓋這個(gè)選項(xiàng)的值。 如果可行的話,一個(gè)相對(duì)路徑會(huì)很有幫助。
在?git status
?輸出中列出的另一個(gè)是項(xiàng)目文件夾記錄。 如果你運(yùn)行?git diff
,會(huì)看到類似下面的信息:
$ 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
?是工作目錄中的一個(gè)子目錄,但 Git 還是會(huì)將它視作一個(gè)子模塊。當(dāng)你不在那個(gè)目錄中時(shí),Git 并不會(huì)跟蹤它的內(nèi)容, 而是將它看作該倉(cāng)庫(kù)中的一個(gè)特殊提交。
如果你想看到更漂亮的差異輸出,可以給?git diff
?傳遞?--submodule
?選項(xiàng)。
$ 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)
當(dāng)你提交時(shí),會(huì)看到類似下面的信息:
$ 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ì)上意味著你是將一次提交記作一項(xiàng)目錄記錄的,而非將它記錄成一個(gè)子目錄或者一個(gè)文件。
接下來(lái)我們將會(huì)克隆一個(gè)含有子模塊的項(xiàng)目。 當(dāng)你在克隆這樣的項(xiàng)目時(shí),默認(rèn)會(huì)包含該子模塊目錄,但其中還沒(méi)有任何文件:
$ 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
?目錄,不過(guò)是空的。 你必須運(yùn)行兩個(gè)命令:git submodule init
?用來(lái)初始化本地配置文件,而?git submodule update
?則從該項(xiàng)目中抓取所有數(shù)據(jù)并檢出父項(xiàng)目中列出的合適的提交。
$ 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
?子目錄是處在和之前提交時(shí)相同的狀態(tài)了。
不過(guò)還有更簡(jiǎn)單一點(diǎn)的方式。 如果給?git clone
?命令傳遞?--recursive
?選項(xiàng),它就會(huì)自動(dòng)初始化并更新倉(cāng)庫(kù)中的每一個(gè)子模塊。
$ 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àng)目副本,我們將會(huì)同時(shí)在主項(xiàng)目和子模塊項(xiàng)目上與隊(duì)員協(xié)作。
在項(xiàng)目中使用子模塊的最簡(jiǎn)模型,就是只使用子項(xiàng)目并不時(shí)地獲取更新,而并不在你的檢出中進(jìn)行任何更改。 我們來(lái)看一個(gè)簡(jiǎn)單的例子。
如果想要在子模塊中查看新工作,可以進(jìn)入到目錄中運(yùn)行?git fetch
?與?git merge
,合并上游分支來(lái)更新本地代碼。
$ 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)在返回到主項(xiàng)目并運(yùn)行?git diff --submodule
,就會(huì)看到子模塊被更新的同時(shí)獲得了一個(gè)包含新添加提交的列表。 如果你不想每次運(yùn)行?git diff
?時(shí)都輸入?--submodle
,那么可以將?diff.submodule
?設(shè)置為 “l(fā)og” 來(lái)將其作為默認(rèn)行為。
$ git config --global diff.submodule log
$ git diff
Submodule DbConnector c3f01dc..d0354fc:
> more efficient db routine
> better connection routine
如果在此時(shí)提交,那么你會(huì)將子模塊鎖定為其他人更新時(shí)的新代碼。
如果你不想在子目錄中手動(dòng)抓取與合并,那么還有種更容易的方式。 運(yùn)行?git submodule update --remote
,Git 將會(huì)進(jìn)入子模塊然后抓取并更新。
$ 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'
此命令默認(rèn)會(huì)假定你想要更新并檢出子模塊倉(cāng)庫(kù)的?master
?分支。 不過(guò)你也可以設(shè)置為想要的其他分支。 例如,你想要 DbConnector 子模塊跟蹤倉(cāng)庫(kù)的 “stable” 分支,那么既可以在.gitmodules
?文件中設(shè)置(這樣其他人也可以跟蹤它),也可以只在本地的?.git/config
?文件中設(shè)置。 讓我們?cè)?.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
?選項(xiàng),那么它只會(huì)為你做修改。但是在倉(cāng)庫(kù)中保留跟蹤信息更有意義一些,因?yàn)槠渌艘部梢缘玫酵瑯拥男Ч?/p>
這時(shí)我們運(yùn)行?git status
,Git 會(huì)顯示子模塊中有 “新提交”。
$ 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è)置了配置選項(xiàng)?status.submodulesummary
,Git 也會(huì)顯示你的子模塊的更改摘要:
$ 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
這時(shí)如果運(yùn)行?git diff
,可以看到我們修改了 .gitmodules 文件,同時(shí)還有幾個(gè)已拉取的提交需要提交到我們自己的子模塊項(xiàng)目中。
$ 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
這非常有趣,因?yàn)槲覀兛梢灾苯涌吹綄⒁峤坏阶幽K中的提交日志。 提交之后,你也可以運(yùn)行?git log -p
?查看這個(gè)信息。
$ 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
當(dāng)運(yùn)行?git submodule update --remote
?時(shí),Git 默認(rèn)會(huì)嘗試更新所有子模塊,所以如果有很多子模塊的話,你可以傳遞想要更新的子模塊的名字。
你很有可能正在使用子模塊,因?yàn)槟愦_實(shí)想在子模塊中編寫(xiě)代碼的同時(shí),還想在主項(xiàng)目上編寫(xiě)代碼(或者跨子模塊工作)。 否則你大概只能用簡(jiǎn)單的依賴管理系統(tǒng)(如 Maven 或 Rubygems)來(lái)替代了。
現(xiàn)在我們將通過(guò)一個(gè)例子來(lái)演示如何在子模塊與主項(xiàng)目中同時(shí)做修改,以及如何同時(shí)提交與發(fā)布那些修改。
到目前為止,當(dāng)我們運(yùn)行?git submodule update
?從子模塊倉(cāng)庫(kù)中抓取修改時(shí),Git 將會(huì)獲得這些改動(dòng)并更新子目錄中的文件,但是會(huì)將子倉(cāng)庫(kù)留在一個(gè)稱作 “游離的 HEAD” 的狀態(tài)。 這意味著沒(méi)有本地工作分支(例如 “master”)跟蹤改動(dòng)。 所以你做的任何改動(dòng)都不會(huì)被跟蹤。
為了將子模塊設(shè)置得更容易進(jìn)入并修改,你需要做兩件事。 首先,進(jìn)入每個(gè)子模塊并檢出其相應(yīng)的工作分支。 接著,若你做了更改就需要告訴 Git 它該做什么,然后運(yùn)行?git submodule update --remote
?來(lái)從上游拉取新工作。 你可以選擇將它們合并到你的本地工作中,也可以嘗試將你的工作變基到新的更改上。
首先,讓我們進(jìn)入子模塊目錄然后檢出一個(gè)分支。
$ git checkout stable
Switched to branch 'stable'
然后嘗試用 “merge” 選項(xiàng)。 為了手動(dòng)指定它,我們只需給?update
?添加?--merge
?選項(xiàng)即可。 這時(shí)我們將會(huì)看到服務(wù)器上的這個(gè)子模塊有一個(gè)改動(dòng)并且它被合并了進(jìn)來(lái)。
$ 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'
如果我們進(jìn)入 DbConnector 目錄,可以發(fā)現(xiàn)新的改動(dòng)已經(jīng)合并入本地?stable
?分支。 現(xiàn)在讓我們看看當(dāng)我們對(duì)庫(kù)做一些本地的改動(dòng)而同時(shí)其他人推送另外一個(gè)修改到上游時(shí)會(huì)發(fā)生什么。
$ cd DbConnector/
$ vim src/db.c
$ git commit -am 'unicode support'
[stable f906e16] unicode support
1 file changed, 1 insertion(+)
如果我們現(xiàn)在更新子模塊,就會(huì)看到當(dāng)我們?cè)诒镜刈隽烁臅r(shí)上游也有一個(gè)改動(dòng),我們需要將它并入本地。
$ 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 會(huì)將子模塊更新為服務(wù)器上的狀態(tài)。并且會(huì)將項(xiàng)目重置為一個(gè)游離的 HEAD 狀態(tài)。
$ git submodule update --remote
Submodule path 'DbConnector': checked out '5d60ef9bbebf5a0c1c1050f242ceeb54ad58da94'
即便這真的發(fā)生了也不要緊,你只需回到目錄中再次檢出你的分支(即還包含著你的工作的分支)然后手動(dòng)地合并或變基?origin/stable
(或任何一個(gè)你想要的遠(yuǎn)程分支)就行了。
如果你沒(méi)有提交子模塊的改動(dòng),那么運(yùn)行一個(gè)子模塊更新也不會(huì)出現(xiàn)問(wèn)題,此時(shí) Git 會(huì)只抓取更改而并不會(huì)覆蓋子模塊目錄中未保存的工作。
$ 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'
如果你做了一些與上游改動(dòng)沖突的改動(dòng),當(dāng)運(yùn)行更新時(shí) Git 會(huì)讓你知道。
$ 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'
你可以進(jìn)入子模塊目錄中然后就像平時(shí)那樣修復(fù)沖突。
現(xiàn)在我們的子模塊目錄中有一些改動(dòng)。 其中有一些是我們通過(guò)更新從上游引入的,而另一些是本地生成的,由于我們還沒(méi)有推送它們,所以對(duì)任何其他人都不可用。
$ git diff
Submodule DbConnector c87d55d..82d2ad3:
> Merge from origin/stable
> updated setup script
> unicode support
> remove unnecessary method
> add new option for conn pooling
如果我們?cè)谥黜?xiàng)目中提交并推送但并不推送子模塊上的改動(dòng),其他嘗試檢出我們修改的人會(huì)遇到麻煩,因?yàn)樗麄儫o(wú)法得到依賴的子模塊改動(dòng)。 那些改動(dòng)只存在于我們本地的拷貝中。
為了確保這不會(huì)發(fā)生,你可以讓 Git 在推送到主項(xiàng)目前檢查所有子模塊是否已推送。?git push
?命令接受可以設(shè)置為 “check” 或 “on-demand” 的?--recurse-submodules
?參數(shù)。 如果任何提交的子模塊改動(dòng)沒(méi)有推送那么 “check” 選項(xiàng)會(huì)直接使?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.
如你所見(jiàn),它也給我們了一些有用的建議,指導(dǎo)接下來(lái)該如何做。 最簡(jiǎn)單的選項(xiàng)是進(jìn)入每一個(gè)子模塊中然后手動(dòng)推送到遠(yuǎn)程倉(cāng)庫(kù),確保它們能被外部訪問(wèn)到,之后再次嘗試這次推送。
另一個(gè)選項(xiàng)是使用 “on-demand” 值,它會(huì)嘗試為你這樣做。
$ 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
如你所見(jiàn),Git 進(jìn)入到 DbConnector 模塊中然后在推送主項(xiàng)目前推送了它。 如果那個(gè)子模塊因?yàn)槟承┰蛲扑褪?,主?xiàng)目也會(huì)推送失敗。
如果你其他人同時(shí)改動(dòng)了一個(gè)子模塊引用,那么可能會(huì)遇到一些問(wèn)題。 也就是說(shuō),如果子模塊的歷史已經(jīng)分叉并且在父項(xiàng)目中分別提交到了分叉的分支上,那么你需要做一些工作來(lái)修復(fù)它。
如果一個(gè)提交是另一個(gè)的直接祖先(一個(gè)快進(jìn)式合并),那么 Git 會(huì)簡(jiǎn)單地選擇之后的提交來(lái)合并,這樣沒(méi)什么問(wèn)題。
不過(guò),Git 甚至不會(huì)嘗試去進(jìn)行一次簡(jiǎn)單的合并。 如果子模塊提交已經(jīng)分叉且需要合并,那你會(huì)得到類似下面的信息:
$ 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 在這里指出了子模塊歷史中的兩個(gè)分支記錄點(diǎn)已經(jīng)分叉并且需要合并。 它將其解釋為 “merge following commits not found”(未找到接下來(lái)需要合并的提交),雖然這有點(diǎn)令人困惑,不過(guò)之后我們會(huì)解釋為什么是這樣。
為了解決這個(gè)問(wèn)題,你需要弄清楚子模塊應(yīng)該處于哪種狀態(tài)。 奇怪的是,Git 并不會(huì)給你多少能幫你擺脫困境的信息,甚至連兩邊提交歷史中的 SHA-1 值都沒(méi)有。 幸運(yùn)的是,這很容易解決。 如果你運(yùn)行?git diff
,就會(huì)得到試圖合并的兩個(gè)分支中記錄的提交的 SHA-1 值。
$ git diff
diff --cc DbConnector
index eb41d76,c771610..0000000
--- a/DbConnector
+++ b/DbConnector
所以,在本例中,eb41d76
?是我們的子模塊中大家共有的提交,而?c771610
?是上游擁有的提交。 如果我們進(jìn)入子模塊目錄中,它應(yīng)該已經(jīng)在?eb41d76
?上了,因?yàn)楹喜](méi)有動(dòng)過(guò)它。 如果不是的話,無(wú)論什么原因,你都可以簡(jiǎn)單地創(chuàng)建并檢出一個(gè)指向它的分支。
來(lái)自另一邊的提交的 SHA-1 值比較重要。 它是需要你來(lái)合并解決的。 你可以嘗試直接通過(guò) SHA-1 合并,也可以為它創(chuàng)建一個(gè)分支然后嘗試合并。 我們建議后者,哪怕只是為了一個(gè)更漂亮的合并提交信息。
所以,我們將會(huì)進(jìn)入子模塊目錄,基于?git diff
?的第二個(gè) SHA 創(chuàng)建一個(gè)分支然后手動(dò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.
我們?cè)谶@兒得到了一個(gè)真正的合并沖突,所以如果想要解決并提交它,那么只需簡(jiǎn)單地通過(guò)結(jié)果來(lái)更新主項(xiàng)目。
$ 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
這可能會(huì)讓你有點(diǎn)兒困惑,但它確實(shí)不難。
有趣的是,Git 還能處理另一種情況。 如果子模塊目錄中存在著這樣一個(gè)合并提交,它的歷史中包含了的兩邊的提交,那么 Git 會(huì)建議你將它作為一個(gè)可行的解決方案。 它看到有人在子模塊項(xiàng)目的某一點(diǎn)上合并了包含這兩次提交的分支,所以你可能想要那個(gè)。
這就是為什么前面的錯(cuò)誤信息是 “merge following commits not found”,因?yàn)樗荒?這樣做。 它讓人困惑是因?yàn)?strong>誰(shuí)能想到它會(huì)嘗試這樣做?
如果它找到了一個(gè)可以接受的合并提交,你會(huì)看到類似下面的信息:
$ 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.
它會(huì)建議你更新索引,就像你運(yùn)行了?git add
?那樣,這樣會(huì)清除沖突然后提交。不過(guò)你可能不應(yīng)該這樣做。你可以輕松地進(jìn)入子模塊目錄,查看差異是什么,快進(jìn)到這次提交,恰當(dāng)?shù)販y(cè)試,然后提交它。
$ cd DbConnector/
$ git merge 9fd905e
Updating eb41d76..9fd905e
Fast-forward
$ cd ..
$ git add DbConnector
$ git commit -am 'Fast forwarded to a common submodule child'
這些命令完成了同一件事,但是通過(guò)這種方式你至少可以驗(yàn)證工作是否有效,以及當(dāng)你在完成時(shí)可以確保子模塊目錄中有你的代碼。
你可以做幾件事情來(lái)讓用子模塊工作輕松一點(diǎn)兒。
有一個(gè)?foreach
?子模塊命令,它能在每一個(gè)子模塊中運(yùn)行任意命令。 如果項(xiàng)目中包含了大量子模塊,這會(huì)非常有用。
例如,假設(shè)我們想要開(kāi)始開(kāi)發(fā)一項(xiàng)新功能或者修復(fù)一些錯(cuò)誤,并且需要在幾個(gè)子模塊內(nèi)工作。 我們可以輕松地保存所有子模塊的工作進(jìn)度。
$ 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)建一個(gè)新分支,并將所有子模塊都切換過(guò)去。
$ 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)該明白。 能夠生成一個(gè)主項(xiàng)目與所有子項(xiàng)目的改動(dò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;
在這里,我們看到子模塊中定義了一個(gè)函數(shù)并在主項(xiàng)目中調(diào)用了它。 這明顯是個(gè)簡(jiǎn)化了的例子,但是希望它能讓你明白這種方法的用處。
你可能想為其中一些命令設(shè)置別名,因?yàn)樗鼈兛赡軙?huì)非常長(zhǎng)而你又不能設(shè)置選項(xiàng)作為它們的默認(rèn)選項(xiàng)。 我們?cè)?Git 別名?介紹了設(shè)置 Git 別名,但是如果你計(jì)劃在 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'
這樣當(dāng)你想要更新子模塊時(shí)可以簡(jiǎn)單地運(yùn)行?git supdate
,或?git spush
?檢查子模塊依賴后推送。
然而使用子模塊還是有一些小問(wèn)題。
例如在有子模塊的項(xiàng)目中切換分支可能會(huì)造成麻煩。 如果你創(chuàng)建一個(gè)新分支,在其中添加一個(gè)子模塊,之后切換到?jīng)]有該子模塊的分支上時(shí),你仍然會(huì)有一個(gè)還未跟蹤的子模塊目錄。
$ 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)
移除那個(gè)目錄并不困難,但是有一個(gè)目錄在那兒會(huì)讓人有一點(diǎn)困惑。 如果你移除它然后切換回有那個(gè)子模塊的分支,需要運(yùn)行?submodule update --init
?來(lái)重新建立和填充。
$ 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
再說(shuō)一遍,這真的不難,只是會(huì)讓人有點(diǎn)兒困惑。
另一個(gè)主要的告誡是許多人遇到了將子目錄轉(zhuǎn)換為子模塊的問(wèn)題。 如果你在項(xiàng)目中已經(jīng)跟蹤了一些文件,然后想要將它們移動(dòng)到一個(gè)子模塊中,那么請(qǐng)務(wù)必小心,否則 Git 會(huì)對(duì)你發(fā)脾氣。 假設(shè)項(xiàng)目?jī)?nèi)有一些文件在子目錄中,你想要將其轉(zhuǎn)換為一個(gè)子模塊。 如果刪除子目錄然后運(yùn)行?submodule add
,Git 會(huì)朝你大喊:
$ 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è)你在一個(gè)分支下做了這樣的工作。 如果嘗試切換回的分支中那些文件還在子目錄而非子模塊中時(shí) - 你會(huì)得到這個(gè)錯(cuò)誤:
$ 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
你可以通過(guò)?check -f
?來(lái)強(qiáng)制切換,但是要小心,如果其中還有未保存的修改,這個(gè)命令會(huì)把它們覆蓋掉。
$ git checkout -f master
warning: unable to rmdir CryptoLibrary: Directory not empty
Switched to branch 'master'
當(dāng)你切換回來(lái)之后,因?yàn)槟承┰蚰愕玫搅艘粋€(gè)空的?CryptoLibrary
?目錄,并且?git submodule update
?也無(wú)法修復(fù)它。 你需要進(jìn)入到子模塊目錄中運(yùn)行?git checkout .
?來(lái)找回所有的文件。 你也可以通過(guò)?submodule foreach
?腳本來(lái)為多個(gè)子模塊運(yùn)行它。
要特別注意的是,近來(lái)子模塊會(huì)將它們的所有 Git 數(shù)據(jù)保存在頂級(jí)項(xiàng)目的?.git
?目錄中,所以不像舊版本的 Git,摧毀一個(gè)子模塊目錄并不會(huì)丟失任何提交或分支。
擁有了這些工具,使用子模塊會(huì)成為可以在幾個(gè)相關(guān)但卻分離的項(xiàng)目上同時(shí)開(kāi)發(fā)的相當(dāng)簡(jiǎn)單有效的方法。
更多建議: