一個大項目通常由很多較小的, 自完備的模塊組成. 例如, 一個嵌入式Linux發行版的代碼樹會包含每個進行過本地修改的軟件的代碼; 一個電影播放器可能需要基于一個知名解碼庫的特定版本完成編譯; 數個獨立的程序可能會共用同一個創建腳本.
在集中式版本管理系統中, 可以通過把每個模塊放在一個單獨的倉庫中來完成上述的任務. 開發者可以把所有模塊都簽出(checkout), 也可以選擇只簽出他需要的模塊. 在移動文件, 修改API和翻譯時, 他們甚至可以在一個提交中跨多個模塊修改文件.
Git不允許部分簽出(partial checkout), 所以采用上面(集中式版本管理)的方法會強迫開發者們保留一份他們不感興趣的模塊的本地拷貝. 在簽出量巨大時, 提交會慢得超過你的預期, 因為git不得不掃描每一個目錄去尋找修改. 如果模塊有很多本地歷史, 克隆可能永遠不能完成.
從好的方面看來, 分布式版本管理系統可以更好地與外部資源進行整合. 在集中化的模式中, 外部項目的一個快照從它本身的版本控制系統中被分離出來, 然后此快照作為一個提供商分支(vendor branch)導入到本地的版本控制系統中去. 快照的歷史不再可見. 而分布式管理系統中, 你可以把外部項目的歷史一同克隆過來, 從而更好地跟蹤外部項目的開發, 便于合并本地修改.
Git的子模塊(submodule)功能使得一個倉庫可以用子目錄的形式去包含一個外部項目的簽出版本. 子模塊維護它們自己的身份標記(identity); 子模塊功能僅僅儲存子模塊倉庫的位置和提交ID, 因此其他克隆父項目("superproject")的開發者可以輕松克隆所有子模塊的同一版本. 對父項目的部分簽出成為可能: 你可以告訴git去克隆一部分或者所有的子模塊, 也可以一個都不克隆.
Git 1.5.3中加入了[git submodule](http://www.kernel.org/pub/software/scm/git/docs/git-submodule.html)這個命令. Git 1.5.2版本的用戶可以查找倉庫的子模塊并且手工簽出; 更早的版本不支持子模塊功能.
為說明子模塊的使用方法, 創建4個用作子模塊的示例倉庫:
~~~
$ mkdir ~/git
$ cd ~/git
$ for i in a b c d
do
mkdir $i
cd $i
git init
echo "module $i" > $i.txt
git add $i.txt
git commit -m "Initial commit, submodule $i"
cd ..
done
~~~
現在創建父項目, 加入所有的子模塊:
~~~
$ mkdir super
$ cd super
$ git init
$ for i in a b c d
do
git submodule add ~/git/$i $i
done
~~~
注意: 如果你想對外發布你的父項目, 請不要使用本地的地址!
列出`git-submodule`創建文件:
~~~
$ ls -a
. .. .git .gitmodules a b c d
~~~
`git-submodule add`命令進行了如下的操作:
* 它在當前目錄下克隆各個子模塊, 默認簽出master分支.
* 它把子模塊的克隆路徑加入到[gitmodules](http://www.kernel.org/pub/software/scm/git/docs/gitmodules.html)文件中, 然后把這個文件加入到索引, 準備進行提交.
* 它把子模塊的當前提交ID加入到索引中, 準備進行提交.
提交父項目:
~~~
$ git commit -m "Add submodules a, b, c and d."
~~~
現在克隆父項目:
~~~
$ cd ..
$ git clone super cloned
$ cd cloned
~~~
子模塊的目錄創建好了, 但是它們是空的:
~~~
$ ls -a a
. ..
$ git submodule status
-d266b9873ad50488163457f025db7cdd9683d88b a
-e81d457da15309b4fef4249aba9b50187999670d b
-c1536a972b9affea0f16e0680ba87332dc059146 c
-d96249ff5d57de5de093e6baff9e0aafa5276a74 d
~~~
注意: 上面列出的提交對象的名字會和你的項目中看到的有所不同, 但是它們應該和HEAD的提交對象名字一致. 你可以運行`git ls-remote ../git/a`進行檢驗.
拉取子模塊需要進行兩步操作. 首先運行`git submodule init`, 把子模塊的URL加入到`.git/config`:
~~~
$ git submodule init
~~~
現在使用`git-submodule update`去克隆子模塊的倉庫和簽出父項目中指定的那個版本:
~~~
$ git submodule update
$ cd a
$ ls -a
. .. .git a.txt
~~~
`git-submodule update`和`git-submodule add`的一個主要區別就是`git-submodule update`簽出一個指定的提交, 而不是該分支的tip. 它就像簽出一個標簽(tag): 頭指針脫離, 你不在任何一個分支上工作.
~~~
$ git branch
* (no branch)
master
~~~
如何你需要對子模塊進行修改, 同時頭指針又是脫離的狀態, 那么你應該創建或者簽出一個分支, 進行修改, 發布子模塊的修改, 然后更新父項目讓其引用新的提交:
~~~
$ git checkout master
~~~
或者
~~~
$ git checkout -b fix-up
~~~
然后
~~~
$ echo "adding a line again" >> a.txt
$ git commit -a -m "Updated the submodule from within the superproject."
$ git push
$ cd ..
$ git diff
diff --git a/a b/a
index d266b98..261dfac 160000
--- a/a
+++ b/a
@@ -1 +1 @@
-Subproject commit d266b9873ad50488163457f025db7cdd9683d88b
+Subproject commit 261dfac35cb99d380eb966e102c1197139f7fa24
$ git add a
$ git commit -m "Updated submodule a."
$ git push
~~~
如果你想要更新子模塊, 你應該在`git pull`之后運行`git submodule update`.
## 子模塊方式的陷阱
你應該總是在發布父項目的修改之前發布子模塊修改. 如果你忘記發布子模塊的修改, 其他人就無法克隆你的倉庫了:
~~~
$ cd ~/git/super/a
$ echo i added another line to this file >> a.txt
$ git commit -a -m "doing it wrong this time"
$ cd ..
$ git add a
$ git commit -m "Updated submodule a again."
$ git push
$ cd ~/git/cloned
$ git pull
$ git submodule update
error: pathspec '261dfac35cb99d380eb966e102c1197139f7fa24' did not match any file(s) known to git.
Did you forget to 'git add'?
Unable to checkout '261dfac35cb99d380eb966e102c1197139f7fa24' in submodule path 'a'
~~~
如果你暫存了一個更新過的子模塊, 準備進行手工提交, 注意不要在路徑后面加上斜杠. 如果加上了斜杠, git會認為你想要移除那個子模塊然后簽出那個目錄內容到父倉庫.
~~~
$ cd ~/git/super/a
$ echo i added another line to this file >> a.txt
$ git commit -a -m "doing it wrong this time"
$ cd ..
$ git add a/
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# deleted: a
# new file: a/a.txt
#
# Modified submodules:
#
# * a aa5c351...0000000 (1):
# < Initial commit, submodule a
#
~~~
為了修正這個錯誤的操作, 我們應該重置(reset)這個修改, 然后在add的時候不要加上末尾斜杠.
~~~
$ git reset HEAD A
$ git add a
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: a
#
# Modified submodules:
#
# * a aa5c351...8d3ba36 (1):
# > doing it wrong this time
#
~~~
你也不應該把子模塊的分支回退到超出任何父項目中記錄的提交的范圍.
如果你在沒有簽出分支的情況下對子模塊進行了修改并且提交, 運行`git submodule update`將會不安全. 你所進行的修改會在無任何提示的情況下被覆蓋.
~~~
$ cat a.txt
module a
$ echo line added from private2 >> a.txt
$ git commit -a -m "line added inside private2"
$ cd ..
$ git submodule update
Submodule path 'a': checked out 'd266b9873ad50488163457f025db7cdd9683d88b'
$ cd a
$ cat a.txt
module a
~~~
注意: 這些修改在子模塊的reflog中仍然可見.
如果你不想提交你的修改, 那又是另外一種情況了.
- 1. 介紹
- 歡迎使用Git
- GIT對象模型
- Git目錄 與 工作目錄
- Git索引
- 2. 第一步
- 安裝Git
- 安裝與初始化
- 3. 基本用法
- 獲得一個Git倉庫
- 正常的工作流程
- 分支與合并@基礎
- 查看歷史 -Git日志
- 比較提交 - Git Diff
- 分布式的工作流程
- Git標簽
- 4. 中級技能
- 忽略某些文件
- rebase
- 交互式rebase
- 交互式添加
- 儲藏
- Git樹名
- 追蹤分支
- 使用Git Grep進行搜索
- Git的撤消操作 - 重置, 簽出 和 撤消
- 維護Git
- 建立一個公共倉庫
- 建立一個私有倉庫
- 5. 高級技能
- 創建新的空分支
- 修改你的歷史
- 高級分支與合并
- 查找問題的利器 - Git Bisect
- 查找問題的利器 - Git Blame
- Git和Email
- 定制Git
- Git Hooks
- 找回丟失的對象
- 子模塊
- 6. Git生態體系
- Git 與之 Windows
- 使用Git進行系統部署
- 與 Subversion 集成
- 從其他代碼管理工具遷移到Git
- 圖形化的Git
- Git倉庫托管
- Git的其它用法
- Git的腳本支持
- Git 與編輯器
- 7. 原理解析
- Git是如何存儲對象的
- 查看Git對象
- Git引用
- Git索引
- 打包文件
- 更底層的Git
- 傳輸協議
- 術語表