git subtree 最佳实践

705 阅读6分钟

目录

1背景

1.1 痛点

目前业务主要有A端和B端两个系统,这两个系统技术栈是完全相同的,许多功能也相同。所以在日常的开发过程中,产生了大量的重复工作,一个需求在A端完成后,还需要复制到B端,这样往往容易出现疏漏。

1.2 解决思路

实现代码复用目前,有下面两种方法:

  • 抽象成NPM包进行复用

  • 使用Git的子仓库对代码进行复用

由于本项目要实现业务代码复用,抽成 npm 包的方式就不太合适。

1.3 什么是git子仓库

通俗上的理解, 一个Git仓库下面放了多个其他的Git仓库,其他的Git仓库就是我们父级仓库的子仓库。

通过使用git子仓库将公共的组件抽离出来,实现在一端更改后,另一端通过git去合并代码,将我们从繁重的复制粘贴中解放出来。同时,可以在后续的需求中放入公共组件,通过增量的方式去应用这个技术,不会影响以前的代码。

1.4 git的两种子仓库方案

目前git实现子仓库有下面两种方案:

  1. git submodule。 tdesign 使用的就是这种方案。

  2. git subtree

两种方案的对比如下:

维度subtreesubmodule优劣对比
空间占用subtree 在初始化 add 时,会将子仓库 copy 到父仓库中,并产生至少一次 merge 记录。所以会占用大量父仓库空间submodule 在初始化 add 时,会在父仓库新建一个 .gitmodules 文件,用于保存子仓库的 commit hash 引用。所以不会占用父仓库空间submodule 更优
clonesubtree add 至父仓库之后,后续的 clone 操作与单一仓库操作相同后续 clone 时 submodule 还需要 init/update 操作,且 submodule 子仓库有自己的分支。 流水线部署时需要更改配置。subtree 更优
update子仓库更新后,父仓库需要 subtree pull 操作,且命令行略长,需要指定 --prefix 参数。由于无法感知子仓库的存在,可能会产生 merge 冲突需要处理子仓库更新后,父仓库需要 submodule update 操作。父仓库只需变动子仓库 hash 引用,不会出现冲突submodule 更优
commit父仓库直接提交父子仓库目录里的变动。若修改了子仓库的文件,则需要执行 subtree push父子仓库的变动需要单独分别提交。且注意先提交子仓库再提交父仓库subtree 更优

用一句话来描述 Git Subtree 的优势就是:

经由 Git Subtree 来维护的子项目代码,对于父项目来说是透明的,所有的开发人员看到的就是一个普通的目录,原来怎么做现在依旧那么做,只需要维护这个 Subtree 的人在合适的时候去做同步代码的操作。

1.5 git subtree 对现有项目的影响

使用git subtree 无需改变现有工程结构,可以只在新需求中使用它去复用代码,相当于它只是一个复制粘贴的工具。

2方案设计

2.1 创建子仓库

建立一个单独的git仓库命名为 common , 可以创建如下的目录结构:

-common
  -utils 公共的工具函数
  -services 接口
  -components 公共的组件
  -hooks 公共的hooks

2.2 关联子仓库

然后在A端和B端添加common的远程仓库:

 git remote add common [common仓库地址]

建立父仓库和子仓库的依赖关系:

git subtree add --prefix=src/common common master

将common远程仓库的master分支拷贝到父仓库的 src/common 目录下, 这时在两个项目的src目录多一个 common 的文件夹,我们可以像一个本地目录一样去使用里面的代码。

--prefix 可以用 -P 来代替,见下文。

2.3 拉取子仓库更新

git subtree pull -P src/common common master

2.4 推送更改到子仓库

方法一 直接提交

git subtree push -P src/common common master

subtree push实际上是遍历本工程每一次提交,把提交文件涉及到subtree目录的挑出来,同步到subtree工程,如果提交有很多,速度会非常慢。

方法二 拆分代码再push[推荐]

git subtree split --rejoin -P src/common
git subtree push -P src/common common master

如果想要split成功,一定要去除 commit msg 的校验。

方法三 拆分代码到单独分支

git subtree split --rejoin -P src/common -b split-common
git push common split-common

首先将 common 拆分到父仓库的 split-common 分支,可以通过 checkout 到这个分支查看内容。

2.5 删除子仓库

git rm -r src/common

2.5 细节

在开发一个需求的时候, A端更改了 common 后,其他人只需要向以前一样在父仓库拉取代码。而当想在B端使用 common 代码,则需要将A端的代码同步到common 仓库,B拉取一下就行。

问题

git subtree split 无效

我们项目是基于 umi 脚手架开发的项目,这个脚手架自带了一个 gitHooks 会对 commit 的msg进行校验,而git subtree split 的原理就是通过 msg 进行判断。 解决方法:去掉 package.json 中的 commit 校验

{
  "gitHooks": {
  }
}

修改后没有同步

问题描述

修改一个后,没有push代码,慢慢导致后面两端的子仓库出现差异, 出现代码冲突。

解决方法

每次修改公共的代码都要 push 和 pull, 手动保持一致。

git subtree pull 冲突

错误信息如下

fatal: refusing to merge unrelated histories

解决方法, 在 git subtree pull 时添加 --squash 参数, 类似于 git push 的 --allow-unrelated-historie参数。

git subtree push 不上去

git push using:  common feature/20221214
cache for f1156335aca1314ff75ba328a850cbdd13affb5a already exists!

stackoverflow.com/questions/6…

暂时无法解决

参考文章

# 为什么你的公司不应该使用git submodule # Git subtree用法与常见问题分析 # 用 Git Subtree 在多个 Git 项目间双向同步子项目 # Git subtree 要不要使用 –squash 参数 # 掌握Git的subtree[译]