子树
子树(subtree)会把另一个仓库复制进你自己仓库的某个目录里。此后那些文件真的就在那里:一次普通的 git clone 就能拿到它们,git checkout 像对待其他任何文件一样移动它们,而且没有人需要知道这个目录来自别处。
这就是它与子模块的全部区别——子模块只存一个指针,需要 --recurse-submodules、需要自己的检出,还需要理清它自己的分离 HEAD。
⌘K → 子树。

没人提起的那个坑
git 不为子树记录任何清单文件。 子模块有 .gitmodules,里面列着每一个 url 和路径。子树什么都没有——只有当初做导入的那个提交上的一行 git-subtree-dir: trailer。
所以一个仓库可以包含一棵子树,却不给你任何办法查出它从哪儿来。Gitcito 尽其所能:
- 这份列表是通过读取那些 trailer 从历史中发现的。任何人、用任何工具加入的子树都会出现。
- 来源仓库和引用由 Gitcito 记住,存放在本仓库的 git 配置里。从历史中发现的子树,这些字段一开始是空的——填一次,从此拉取和推送就都能用了。
被记住的值位于 .git/config 中的 gitcito.subtree.* 之下,因此它们会留在这个仓库里,但不会随克隆一起走。忘记会清空它们,除此之外什么都不动。
添加一棵
| 字段 | 含义 |
|---|---|
| 目录 | 它落到哪里,例如 vendor/parser。必须尚不存在 |
| 来源仓库 | 一个 URL 或者磁盘上的一个路径 |
| 分支或标签 | 要导入什么 |
| 压缩 | 把它作为一个提交引入,而不是它的整部历史 |
除非你有理由,否则请保持「压缩」为开启。不开的话,这个库的每一个提交都会永远交织进你的日志里,git log 也就不再是关于你自己项目的了。
与它共处
| 操作 | 它运行什么 |
|---|---|
| 拉取 | git subtree pull——上游的改动以一次合并的形式落进你的目录 |
| 推送 | git subtree push——你在那个目录下的本地改动回到来源仓库 |
| 拆分 | git subtree split -b <branch>——把这个目录自己的历史抽取成一个分支,文件位于其根目录 |
拆分是值得知道的那一个:它把一个 vendored 目录重新变回一个独立仓库的历史,而这正是一棵子树不再是子树的方式。
值得知道的限制
- 推送很慢。 它每一次都从零重新计算这个目录的历史。在一个大仓库上,这是几秒到几分钟,而不是一瞬间,Gitcito 也只能等着。
- 一次拉取就是一次合并,所以它会像任何合并一样冲突——你会落进冲突解决器。
git subtree是一个 contrib 脚本,不是 git 内建命令。一个被精简过的 git 安装可能没有它;Gitcito 会明白地说出来,而不是把 "'subtree' is not a git command" 原样转达给你。- 被压缩过的历史事后无法还原成未压缩的。 那些提交从来就没有被导入过。
- Gitcito 不会把子模块转换成子树,反过来也不会。