Git 子模块(gitsubmodules) - 将一个仓库嵌套到另一个仓库中
Git 子模块(gitsubmodules) - 将一个仓库嵌套到另一个仓库中
名称
gitsubmodules - 将一个仓库嵌套到另一个仓库中
概要
.gitmodules
,$GIT_DIR/config
git submodule
git <command> --recurse-submodules
描述
子模块是嵌套在另一个仓库中的仓库。子模块有自己的历史记录;嵌套它的仓库被称为父项目。
在文件系统上,子模块通常(但不总是 - 参见下文的形式)包括
- 位于其父项目的
$GIT_DIR/modules/
目录下的 Git 目录 - 父项目的工作目录内的一个工作目录,以及指向 1 的子模块工作目录根目录下的
.git
文件。
假设子模块在 $GIT_DIR/modules/foo/
具有 Git 目录,工作目录位于 path/to/bar/
,则父项目通过树中的 path/to/bar
处的 gitlink 条目和其 .gitmodules
文件中的条目(参见 gitmodules(5)
)跟踪子模块,形式为 submodule.foo.path = path/to/bar
。
gitlink 条目包含父项目期望子模块工作目录位于的提交的对象名称。
在 .gitmodules
文件中,submodule.foo.*
部分向 Git 的用户界面层提供了附加提示。例如,submodule.foo.url
设置了获取子模块的位置。
子模块可用于至少两种不同的用例:
在保持独立历史的同时使用另一个项目。子模块允许你在自己的工作树中包含另一个项目的工作树,同时保持两个项目的历史记录分开。而且,由于子模块被固定到任意版本,另一个项目可以独立开发,而不影响父项目,允许父项目仅在需要时固定到新版本。
将(逻辑上的单一)项目拆分为多个仓库并将它们重新绑定在一起。这可以用于克服 Git 实现的当前限制,以实现更精细的访问控制:
Git 仓库的大小:在当前形式下,对于包含未通过树之间的增量计算压缩的大型仓库,Git 的扩展性较差。例如,你可以使用子模块来保存大型二进制资源,而这些仓库可以被浅克隆,以便本地没有大量的历史记录。
传输大小:在当前形式下,Git 需要整个工作树存在。它不允许在提取或克隆中传输部分树。如果你的项目由父项目中的子模块以子模块的形式绑定在一起,你可以避免获取你不感兴趣的仓库的工作树。
访问控制:通过限制用户对子模块的访问,可以实现不同用户的读/写策略。
子模块的配置
子模块操作可以使用以下机制进行配置(从高到低的优先级):
针对支持将子模块作为其路径规范的命令的命令行。大多数命令都有一个布尔标志
--recurse-submodules
,它指定是否递归到子模块中。例如,grep
和checkout
。子模块内部的配置。包括子模块中的
$GIT_DIR/config
,以及在树中的.gitattributes
或.gitignore
文件,它们指定了子模块内部命令的行为。例如,子模块的
.gitignore
文件的效果将在父项目中运行git status --ignore-submodules=none
时观察到。这通过在子模块中运行status
并关注.gitignore
文件来收集子模块的工作目录中的信息。当在父项目中运行
git push --recurse-submodules=check
时,将使用子模块的$GIT_DIR/config
文件,这将检查子模块是否有未发布到任何远程的更改。远程通常在子模块中像在$GIT_DIR/config
文件中一样配置。
父项目的配置文件
$GIT_DIR/config
。Git 仅递归到活动的子模块(参见下面的“活动子模块”部分)。- 如果子模块尚未初始化,则子模块内部的配置不存在,因此在这里配置从哪里获取子模块是必要的。
父项目中的
.gitmodules
文件。项目通常使用此文件为所需的映射提供上游仓库的默认值,该映射对于子模块的名称和路径之间所需的映射很重要。该文件主要用作父项目中子模块的名称和路径之间的映射,以便可以定位子模块的 Git 目录。
如果子模块尚未初始化,则只在此处找到子模块配置。它作为指定从何处获取子模块的最后的后备。
形式
子模块可以采用以下形式:
描述在描述部分的基本形式,具有 Git 目录、工作目录、gitlink 和
.gitmodules
条目“旧形式”子模块:带有嵌入的
.git
目录的工作目录,以及父项目中的跟踪 gitlink 和.gitmodules
条目。这通常在使用旧版本的 Git 生成的仓库中找到。可以手动构造这些旧形式的仓库。
当取消初始化或删除子模块(见下文),子模块的 Git 目录会自动移动到父项目的
$GIT_DIR/modules/<name>/
。
未初始化的子模块:gitlink、
.gitmodules
条目,但没有子模块工作目录。子模块的 Git 目录可能仍然存在,因为取消初始化后,Git 目录会保留在那里。应该是工作目录的目录为空。- 可以通过运行
git submodule deinit
来取消初始化子模块。除了清空工作目录外,此命令仅修改父项目的$GIT_DIR/config
文件,因此不会影响父项目的历史记录。可以使用git submodule init
撤消此操作。
- 可以通过运行
删除的子模块:可以通过运行
git rm <submodule path> && git commit
来删除子模块。可以使用git revert
撤消此操作。删除将删除父项目的跟踪数据,这既是 gitlink 条目,也是
.gitmodules
文件中的条目。子模块的工作目录从文件系统中删除,但 Git 目录保留在那里,以使可以检出过去的提交而不需要从另一个仓库获取。要完全删除子模块,手动删除
$GIT_DIR/modules/<name>/
。
活动子模块
如果满足以下条件之一,子模块被视为活动:
如果
submodule.<name>.active
设置为 true或
如果子模块的路径与 submodule.active 中的 pathspec 匹配
或
如果
submodule.<name>.url
设置。
这些条件按此顺序进行评估。
例如:
1 | [submodule "foo"] |
在上面的配置中,仅子模块 bar
和 baz
是活动的,bar
是因为 (1),而 baz
是因为 (3)。
请注意,(3) 是一个历史工件,如果 (1) 和 (2) 指定子模块不活动,那么将忽略 (3)。换句话说,如果 submodule.<name>.active
设置为 false 或者子模块的路径在 submodule.active
的 pathspec 中被排除,则 url 是否存在都无关紧要。在以下示例中进行了说明。
1 | [submodule "foo"] |
在此配置中,除了 baz
(foo
、bar
、bob
)以外的所有子模块都是活动的。foo
是因为它自己的活动标志,而其他所有子模块都是由于 submodule 活动 pathspec,该 pathspec 指定以 b 开头的任何子模块都是活动的,而不管是否存在 .url 字段。
用于第三方库的工作流程
1 | # 添加一个子模块 |
用于人工拆分仓库的工作流程
1 | # 为相关命令启用递归,使得 |
实现细节
当克隆或拉取包含子模块的仓库时,默认情况下子模块不会被检出;可以指示克隆递归到子模块中。git submodule
的 init 和 update 子命令将保持子模块在你的工作树中被检出,并且处于适当的修订状态。或者,可以设置 submodule.recurse
使 checkout 递归到子模块。