>> >> >> Reference << << << <<<<<<Ref>>>>>>
Subtree
Modified: 2025-12-31 | Author:ljf12825

Git子树是一种在Git仓库中嵌套和管理另一个独立Git仓库的方法。它允许你将外部项目的外争代码库(包括其历史记录)作为主项目的一个子项目来管理

核心概念

主项目Project-A需要依赖一个第三方库Library-B,有几种选择:

  1. 直接复制粘贴代码:丢失Library-B的所有历史,且难以更新
  2. 使用Git子模块:创建一个链接,但需要仓库使用者额外初始化,增加了复杂性
  3. 使用Git子树:将Library-B的代码和历史记录合并到Project-A的一个子目录(如libs/Library-B)中

子树的核心思想是第3种。对于主仓库来说,外部仓库的文件就是它自己的文件,存在于一个特定的子目录中

子树的关键特性

  1. 单一仓库
  1. 保留历史
  1. 管理双向工作流

read-tree

git read-tree是一个plumbing命令,意味“管道”命令,是为脚本和其他Git命令提供基础操作的底层命令。普通用户在日常使用中很少调用它

它的核心功能是:将指定的树对象(Tree Object)读取到Git的暂存区(Index),替换或合并当前暂存区的内容

# 将某次提交的树读入暂存区,覆盖当前暂存区
git read-tree <commit-hash>

执行后,git status会显示工作区文件和暂存区之间的大量差异,因为暂存区已经被替换成了那个提交的状态

read-tree有几个重要的选项,与子树相关

示例:手动实现“引入一个外部项目”的例子

假设你想把B作为子目录vendor/project-b放入项目A中

# 在项目A的仓库中
# 1. 将项目B的远程仓库添加为一个远程源
git remote add project-b <url-to-project-b>

# 2. 获取项目B的所有数据
git fetch project-b

# 3. 使用 read-tree 将项目B的main分支的树,读入暂存区的 `vendor/project-b/`目录下
git read-tree --prefix=vendor/profect-b/ -u project-b/main 

这就是git read-tree在“子树”操作中扮演的角色:它负责将另一个仓库的完整文件树“嫁接”到当前仓库的一个子目录中

subtree

git subtree是一个porcelain命令,意为“瓷器”命令,是面向用户的、更友好的高级命令。它是一个脚本,封装了包括git read-tree在内的一系列底层操作,让子树管理工作变得非常简单
它解决了手动使用read-tree时的一些复杂问题,比如:

git subtree的核心子命令

git subtree add --prefix=vendor/project-b <url-to-project-b> main --squash 
git subtree pull --prefix=vendor/project-b <url-to-project-b> main --squash
git subtree push --prefix=vendor/project-b <url-to-project-b> feature-branch 
特性git read-tree(底层)git subtree(高层)
角色底层构建块用户友好的工具
用途直接操作暂存区中的树管理项目中的子项目依赖
复杂性高、需要理解Git内部原理低、命令直观易用
历史管理需要手动处理、可能很复杂自动处理,可以保持或压扁历史
工作流常用于脚本或自定义合并策略用于日常的子项目更新和同步

子树的优缺点

优点

缺点

历史融合

当执行git subtree add时,Git的底层机制是

  1. 读取子树仓库的完整对象历史到你的主仓库的对象数据库中
  2. 将子树的历史与主仓库历史合并
  3. 在指定前缀(子目录)下创建文件树

子仓库的.git内容被“消化吸收”进了主仓库的.git目录中,而不是作为一个独立的.git文件夹存在

底层原理

Git通过树对象来管理目录结构。执行git subtree add

  1. 子仓库的所有blob(文件内容)和tree(目录结构)对象被导入主仓库的objects数据库
  2. 在主仓库中创建一个新的commit,将子仓库的树对象链接到指定的前缀路径下
  3. 所有这些对象都存储在同一个.git/objects目录中

Subtree vs Submodule

特性子树子模块
克隆难度简单,git clone一次完成需要git submodule update --init
仓库大小较大(包含所有依赖的历史)较小(只存储连接)
历史记录合并到主仓库历史中完全独立于主仓库
更新依赖需要显示地git subtree pull需要显示地git submodule update
修改依赖直接在主项目中修改需要进入子模块目录修改并提交
适用场景依赖关系紧密,经常共同修改依赖关系松散,希望严格锁定版本