在工作中我們經常遇到一個情況,在一個項目中需要包含並使用到另一個項目,比如開發博客時使用到的主題項目,或者是公司業務中需要在多個項目中使用的庫。那該如何獨立管理這兩個項目,並在一個項目中使用另一個項目呢?
Git 通過子模塊來解決這個問題。 子模塊允許你將一個 Git 倉庫作為另一個 Git 倉庫的子目錄。 它能讓你將另一個倉庫克隆到自己的項目中,同時還保持提交的獨立。
注:如果以下有命令報錯或無法生效,請檢查 Git 的版本。
可使用
git update
或git update-git-for-windows
命令更新 Git 。速度慢的話可以去淘寶鏡像 下載 。
使用子模塊
$ git submodule add [-b <branch>] [-f|--force] [--name <name>] [--reference <repository>] [--depth <depth>] [--] <repository> [<path>]
初始化子模塊
首先,在主項目中執行以下命令:
$ git submodule add --name matery git@github.com:xiamu33/hexo-theme-matery.git themes/xiamu-matery
Cloning into 'blog/themes/xiamu-matery'...
...
Resolving deltas: 100% (8/8), done.
此時,在指定的 themes
目錄下會生成一個名為 xiamu-matery
子模塊。
並且在根目錄生成了新的 .gitmodules
文件,該配置文件保存了項目 URL 取的本地目錄之間的映射:
[submodule "matery"]
path = themes/xiamu-matery
url = git@github.com:xiamu33/hexo-theme-matery.git
如果有多個子模塊,該文件中就會有多條記錄。注意該文件也需要納入 Git 的版本管理並推送至倉庫,這樣克隆該項目的人才知道去哪獲得子模塊。
克隆含有子模塊的項目
當我們克隆一個包含子模塊的項目時,項目中會默認包含該子模塊目錄,但其中沒有任何文件:
$ git clone git@github.com:xiamu33/blog.git
Cloning into 'blog'...
...
Resolving deltas: 100% (90/90), done.
$ ls blog/themes/xiamu-matery/
$
你必須執行 git submodule update --init
來初始化本地配置並拉取子模塊數據(該命令實際上把 git submodule init
和 git submodule update
合並成了一步)。
項目中有許多子模塊的話這樣操作未免有些繁瑣,如果給 git clone
命令傳遞 --recurse-submodules
選項,它就會自動初始化並更新倉庫中的每一個子模塊, 包括可能存在的嵌套子模塊。
$ git clone --recurse-submodules git@github.com:xiamu33/blog.git
Cloning into 'blog'...
...
Resolving deltas: 100% (90/90), done.
Submodule 'themes/hexo-theme-matery' (git@github.com:xiamu33/hexo-theme-matery.git) registered for path 'themes/xiamu-matery'
Cloning into 'blog/themes/xiamu-matery'...
Submodule path 'themes/xiamu-matery': checked out '8017ee19d9f040607b4ca58286eb46596f773e61'
克隆失敗
But,由於國內神奇的網絡問題或者其他詭異的現象,“偶爾”會克隆失敗(如出現 OpenSSL SSL_read: SSL_ERROR_SYSCALL, errno 10054
錯誤時)。可以嘗試以下方法:
- 掛梯子;
- 使用 ssh 克隆:
git clone git@github.com:xiamu33/blog.git
; - 關閉 ssl 驗證:
git config --global http.sslVerify false
。
如果只成功克隆了主項目,可執行 git submodule update --init --recursive
初始化並拉取項目中嵌套的所有子模塊。
如何淺克隆子模塊
當我們的子模塊項目十分龐大且只需要其最近的提交歷史時,可以選擇淺克隆子模塊。只需給 git submodule update
傳遞 --depth <depth>
選項:
$ git submodule add --name matery --depth 1 git@github.com:xiamu33/hexo-theme-matery.git themes/xiamu-matery
已有子模塊的項目中則執行:
$ git submodule update --init --depth 1
此時,你本地項目的子模塊的克隆將作為淺克隆執行(歷史深度為1)。如果你想始終淺克隆該子模塊,可執行以下命令:
$ git config -f .gitmodules submodule.<name>.shallow true
<name>
為git submodule add
命令中的--name
選項,默認為子模塊路徑<path>
這將修改 .gitmodules
文件:
[submodule "matery"]
path = themes/xiamu-matery
url = git@github.com:xiamu33/hexo-theme-matery.git
shallow = true
在包含子模塊的項目上工作
拉取子模塊改動
$ git submodule update --remote [<path>]
Git 默認會嘗試更新 所有 子模塊, 可以傳遞 <path>
更新指定的子模塊。
拉取整個項目的改動
默認情況下,git pull
命令會遞歸抓取子模塊的更改,但並不會更新子模塊,需要再執行:
$ git submodule update --init --recursive
如果想自動化次過程,可以給 git pull
命令傳遞 --recurse-submodules
選項:
$ git pull --recurse-submodules
如果總是想以 --recurse-submodules
拉取,可將 submodule.recurse
設置為 true
。這會讓 Git 為除 clone
外所有支持 --recurse-submodules
的命令使用該選項。
子模塊的URL變動
如果子模塊的 URL 發生變動,即與 .gitmodules
文件中的 URL 不同。此時拉取子模塊會失敗,需執行 git submodule sync
:
$ git submodule sync --recursive
$ git submodule update --init --recursive
發布子模塊改動
如果我們同時在主項目和子模塊中提交了更改,但忘記了推送子模塊的改動,會導致其他開發人員無法更新子模塊。為了確保不發生這種情況,可以讓 Git 在推送主項目前檢查所有子模塊是否已推送。
git push
命令可以傳遞 --recurse-submodules=check|on-demand|only|no
選項,你也可以執行 git config push.recurseSubmodules check|on-demand|only|no
設置默認行為。
check
:Git 會遞歸檢查所有子模塊的改動是否已推送,如果有任何未推送的子模塊改動,那么push
操作會直接失敗;on-demand
:Git 會嘗試在推送主項目前推送所有的子模塊,如果某個子模塊推送失敗,那么主項目也會推送失敗;only
:Git 會僅嘗試推送子模塊,而不會推送主項目;no
:當push
操作不需要遞歸子模塊時,使用no
參數或傳遞--no-recurse-submodules
選項可覆蓋push.recurseSubmodules
配置。
子模塊的技巧
子模塊遍歷
子模塊有個 foreach
命令,它可以在所有子模塊中執行任意命令。如果項目中包含大量子模塊,這將會非常有用。
$ git submodule foreach "git pull"
Entering 'themes/xiamu-matery'
Already up to date.
一些有用的別名
當你不想輸入十分冗長的命令又不想設置默認選項時,可以設置一些有用的 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"
刪除子模塊
rm -rf themes/xiamu-matery
:刪除項目中的子模塊目錄;rm -rf .git/modules/matery
:刪除.git
中的子模塊目錄;vim .gitmodules
:刪除.gitmodules
文件中子模塊的條目;vim .git/config
:刪除項目配置中子模塊的條目。
當你遇到諸如以下報錯時,也可嘗試刪除並重新初始化子模塊。
fatal: 'xxx' already exists in the index
fatal: 'xxx' already exists and is not a valid git repo
fatal: could not get a repository handle for submodule 'xxx'