Git Module 与嵌套 Repository
本文大纲:
Git 可将一个 Git repository 作为另外一个 Git repository 的子目录,这允许在你的项目中引用另一个项目,并将两个项目分开维护。假设我们希望将一个现有的 Git repository 作为一个 submodule 添加到当前工作项目上,执行 git submodule add
并跟上绝对或相对 url 作为参数来添加 submodule。
Submodules 至少有两种应用场景:
- 项目依赖一个外部项目,并希望将两者分开维护
- 将一个大项目拆分为多个小项目并将它们黏合在一起
添加一个 Submodule
1 | $ git submodule add https://github.com/chaconinc/DbConnector |
默认情况下,submodules 将使用与 Git repository 一致的名称作为 directory 添加到当前项目的根目录下,在这个例子中为 “DbConnector”。也可以在命令最后指定一个自定义的路径作为该 submodule 的目录。
执行 git status
,看到以下变化:
1 | $ git status |
一个新的文件 .gitmodules
被创建了,该配置文件存储了当前项目与 submodule 之间的关系映射,以下是其内容举例:
1 | [submodule "DbConnector"] |
如果包含多个 submodule,那么该文件会有更多的 entry,值得注意的是,该文件一起受 git
版本控制。
作为 submodule 被管理的 Git repository 将不会受到父级 Git repository 的状态追踪。因此,在当前项目执行 git push origin master
之后,从另一台机器 git clone
父级项目时,将只包含 .gitmodules
文件及对应于各个 submodules 的空的 directory。
1 | $ git clone https://github.com/chaconinc/MainProject |
要获得与提交之前的 submodules,一种方式是执行 git submodule init
指令来初始化本地配置和 git submodule update
从 submodules 对应的地址 clone
所有数据并签出对应的 commit
。
1 | $ git submodule init |
或者:
1 | git submodule update --init --recursive |
另外一种方式是在执行 git clone
父级项目时添加 --recurse-submodules
参数,将一次完成父级项目及所有 submodules 的克隆
1 | $ git clone --recurse-submodules https://github.com/chaconinc/MainProject |
之后,如果希望同父级项目一同获取所有 submodules 的更新,执行
1 | git pull --recurse-submodules |
获取子项目更新
使用 Git Submodules 一个最典型的应用场景是,引用一个由外部维护的 Git repository,仅仅使用它而不做任何修改。
首先导航到指定 submodule 所在目录,执行 git fetch
和 git merge
获取本地更新。
1 | $ git fetch |
回到父级项目目录,执行 git diff --submodule
,可以看到 submodules 已经获得更新并列出一个添加到该项目的 commit 列表。如果不想每次在执行 git diff
时都输入 --submodule
参数,可在 git config
文件中添加该命令的默认参数或执行 git config diff.submodule log
,之后再执行 diff
将会将列出所有子项目更新日志。
1 | $ git config --global diff.submodule log |
此时如果主项目提交至远程 Git repository,之后其他开发人员再获取代码时将会得到与主项目同步后的 submodule。
另外一种方式是直接在主项目目录下执行 git submodule update --remote
命令,git 将自动更新所有 submodules。
1 | $ git submodule update --remote DbConnector |
该命令将默认以所有 submodules 的 master branch 作为更新依据,如何以另外一个 branch name 作为默认更新的依据,参考下文的Git Module
可在 git config 文件中为 status
命令添加默认参数或执行 git config status.submodulesummary 1
,之后执行 git status
显示简短的摘要
1 | $ git config status.submodulesummary 1 |
Git Module
submodule
是嵌套在另一个 repository 中的 repository,submodule
有自己的版本历史,包含子模块的 repository 称为 superproject
.gitmodules
放置在一个 Git 工作树的顶级目录下,该文件是一个匹配 git-config 的文本文件。该文件的每个 subsection 代表一个 submodule 的配置项,subsection 的值为 submodule 的名称,如果不显式指定 name 选项,该名称值将取该 submodule 的路径作为名称。同时,每一个 subsection 包含以下必填值:
- submodule.
<name>
.path: 相对于 Git working tree 顶层目录的路径,默认迁出位置,该值不能以/
结尾,所有 submodule 的路径必须唯一。 - submodule.
<name>
.url: 克隆 submodule 的 url,该值可以是一个绝对 url,也可以./
或../
起头作为 superproject origin repository 的相对 url。
同时,还有以下可选参数:
- submodule.
<name>
.update: 定义 submodue 的默认更新行为,在 superproject 执行git submodule update
指令时如何更新 - submodule.
<name>
.branch: 提供用于检测更新的 branch 名称,如果不指定该值,默认取 master。.
作为特殊值告知 git 取与当前 repository 当前 branch 一致的名称 - submodule.
<name>
.fetchRecurseSubmodules: 该用于控制对 submodule 的递归 fetch,如果在 superproject 的.git/config
中已经设置了该值,那么该值将覆盖在 .gitmodules 中的值。两者均可被git fetch
和git pull
使用--[no-]recurse-submodules
选项覆盖 - submodule.
<name>
.ignore:git status
和比较器如何对已经做出修改的 submodules 进行反应,可取以下值- all: submodule 永远不会被认为已经 modified,但在 staged 后会显示出来
- dirty: 所有对 submodule 工作目录下做出的修改将被忽略,只有其 Head 与 superproject 的记录状态会纳入考虑
- untracked: 只有 untracked 文件会被忽略
- none: 所有修改都不会被忽略
- submodule.
<name>
.shallow: 当设置为 true 时,该 submodule 会执行浅 clone(只包含一层深度的历史信息)
例子:
1 | [submodule "libfoo"] |
文件系统中,一个 submodule
通常
- 在
superproject
的$GID_DIR/modules/
有一个 Git 目录 - 在
superproject
工作目录下有一个对应的子目录作为其工作目录 - 在其工作目录中根目录下包含一个 .git 文件指向 (1) 所在的位置
假设一个 submodule 的 Git 目录位于 $GIT_DIR/modules/foo/
,工作目录位于 path/to/bar/
,superproject
通过一个 path/to/bar/
目录树下的 gitlink
和 .gitmodules
文件中的一个条目 submodule.foo.path = path/to/bar
来追踪这个 submodule
。
参考资料: