Better

Ethan的博客,欢迎访问交流

git submodule 与 git subtree

之前项目中有使用 git submodule 的方式用来管理通用子仓库,使用过程中总觉得有点变扭,正巧看到相关博客,总结一篇小笔记。

背景

当我们的代码中碰到了业务级别的需要复用的代码,我们一般怎么做呢?一般有两种方案:

  • 抽象成 NPM 包进行复用;
  • 使用 Git 的子仓库对代码进行复用;

Git 两种子仓库使用方案

  • git submodule
  • git subtree

git submodule

添加子仓库

git submodule add <main_project_url>

添加成功后,Git 为我们创建了一个 .gitmodules 文件,这个配置文件中保存了子仓库项目的 URL 和在主仓库目录下的映射关系

直接使用 git clone 拉取仓库时,主仓库虽然包含子仓库对应文件夹,但是里面并不包含任何内容,此时需要运行另外两个命令。

首先运行 git submodule init 来初始化本地配置文件,也就是向 .git/config 文件中写入了子模块的信息。

然后运行 git submodule update 则是从子仓库中抓取所有的数据找到父级仓库对应的那次子仓库的提交 id 并且检出到父项目的目录中。

有没有简单的命令能够直接拉取整个仓库的代码的方式呢? 答案是有的,我们使用 git clone --recursive, Git 会自动帮我们递归去拉取我们所有的父仓库和子仓库的相关内容。

如果 clone 时没有添加参数,也可以通过如下方式,执行如下命令,这过程中会用到提到的字仓库的 url 和 head 索引

git submodule update --init --recursive

在主仓库进行协同开发时,应该首先提交子仓库的修改。Git 不关心我们开发的分支,而只是去拉取子仓库对应的 commit 提交。

子仓库提交完成后,回到主仓库。使用 git status 命令查看,发现本次的 Git 的 status 和以往有些不一样的地方,Git 并没有告诉我们当前到底修改了什么文件,而是说主仓库下有一次新的提交。

使用总结:submodule 本身就是一个大的 Git 仓库下包含了多个子的 Git 仓库,我们修改之后,首先对每个子仓库进行了提交,然后父级仓库就会记录下每个子仓库的提交,然后正常提交父级仓库即可,拉取也是同样的过程,如果是在子仓库的分支上开发,也是先拉取子仓库,随后拉取父级仓库的更新

如果觉得对每个子仓库进行提交繁琐的话, git submodule foreach 就可以解决你这个烦恼。foreach 后面使用的就是你要对子模块使用的 git 命令。

git submodule 的核心原理,Git 在处理 submodule 引用的时候,并不会去扫描子仓库下的文件的变化,而是取子仓库当前的 HEAD 指向的 commit 的 hash 值,当我们对子仓库进行了更改后,Git获取到子模块的 commit 值发生变化,从而记录了这个 Git 指针的变化

git submodule 注意点

  • 当子模块有提交的时候,没有 push 到远程仓库, 父级引用子模块的 commit 更新,并提交到远程仓库, 当别人拉取代码的时候就会报出子模块的 commit 不存在 fatal:reference isn’t a tree
  • 如果你仅仅引用了别人的子模块的游离分支,然后在主仓库修改了子仓库的代码,之后使用 git submodule update 拉取了最新代码,那么你在子仓库游离分支做出的修改会被覆盖掉。

删除 submodule

  1. 删除 .gitmodules 文件
  2. 删除 .git/config 对应部分
  3. 删除 .git/modules/ 对应部分
  4. 执行 git rm --cached path_to_submodule
  5. 提交文件

git subtree

git subtree 则是由第三方开发者贡献的 contrib script, Git 本身并不提供 git subtree 命令,contrib 中包含一些实验性的第三方工具,由各自的作者进行维护。

添加子仓库

git subtree add <main_project_url>

由于使用方式有所不同,举个例子

git subtree add --prefix=lib /path/to/repos/lib.git master

参数部分解释如下

  • --prefix=lib: 指定我们要把子仓库放到哪个文件夹下面
  • /path/to/repos/lib.git: 指定我们子仓库的地址
  • master: 指定我们子仓库需要拉取回代码的分支

执行完成后,发现 lib 仓库已经被放到 main 仓库下的 lib 目录下面了,除了一个 lib 文件夹外, git subtree 并没有额外的标记信息去记录我们子仓库的地址,这也正是 git subtree 命令繁琐的地方, add 命令和其他的命令每次都要指定我们子仓库的远程地址,如果你觉得复杂,可以使用 git remote add lib/path/to/repos/lib.git 去给远程服务器取一个别名。

进入到 lib 文件夹下面,查看文件夹下面的文件,我们发现 git subtree 并没有包含 .git 文件夹,这也是和 git submodule 不一致的地方, lib 在主仓库下就像是一堆子仓库的文件,我们并不能从 lib 下进行子仓库的提交,而是要使用 git subtree 其他的命令进行提交和拉取操作, subtree 表现的就像是所有的代码都在我们的主仓库下,并不存在什么其他的子仓库一样。

协同开发

  • git subtree pull
  • git subtree push

git subtree 原理分析

在主仓库中我们通过 Git 拉取到子仓库下的分支代码到主仓库的另外一根分支中, 通过类似 merge 的操作,将代码合并到我们主仓库的某个目录下,此时会生成的一次提交对象, 这个提交对象 parent 引用了我们主仓库的当前 commit 对象和子仓库的当前 commit 对象, 就完成了子仓库的新增,主仓库也顺便记录了子仓库的所有文件。

来源



留言