专栏首页云前端浅析 Git 子模块

浅析 Git 子模块

I. 何为 Git 子模块?

1.1 - 现状和问题

以前端项目为例,通常我们用 npm dependencies 来集成第三方库,或者将自己维护的多个项目中通用的组件抽取出来。

"devDependencies": {
   "babel-eslint": "^8.2.3",
   "base64-img": "^1.0.3",
   "body-parser": "^1.17.2",
   "colors": "^1.3.0",
   "eslint": "^4.19.1",
   "eslint-plugin-babel": "^5.1.0",
   "express": "^4.15.3",
   "fs-extra": "^3.0.1",
   "fs-watch-tree": "^0.2.5",
   "klaw-sync": "^2.1.0",
   "less": "^2.7.2",
   "lodash": "^4.17.4",
   "node-file-eval": "^1.0.0",
   "nodemon": "^1.11.0",
   "postcss": "^6.0.5",
   "precommit-hook": "^3.0.0",
   "ws": "^5.1.1"
 }

这种方式简单方便、支持广泛,适用于大部分情况;但是对于其中某些库来说,也存在一些痛点

  • 需要第三方库编译打包完成,并发布到 npm
  • 如果第三方库有多个编译选项,则组合多个编译选项,分别打包管理,也是一个繁琐的工作
  • 简单方便,但不够灵活。如果是一个庞大的第三方库,即使你只想使用其中的一个小模块,也得把它整个的下载集成
  • 如果第三方库有了更新,需要更新其版本,并验证项目中对其的依赖配置
  • 如果想看看源码,需要手动去 node_modules 中查找

那么,基于以上几点,如果不得不将第三方源码手动拷贝到项目中,又会带来更多的问题:

  • 第三方库将难以和原库保持同步更新
  • 如果对第三方库做出了较通用的更改和补丁等,无法发布到原库中为其他人所用
  • 对第三方库做出的修改,其 git commits 混杂提交到主项目中,难以单独清晰的管理

一个虽然不一定是最好的,但可行的办法是:

1.2 - Git 中的 submodule

子模块(submodule)允许你将一个 Git 仓库作为另一个 Git 仓库的子目录; 它能让你将另一个仓库克隆到自己的项目中,同时还保持提交的独立

简单的说,子模块的解决方案更像是上面两种的融合,类似于一种特区模式:代码既存在于主项目的子文件夹中,可以灵活的为我所用;在 Git 层面又是独立提交管理的,和主项目的 commit 时间线保持了完全的独立性。

如果第三方库发生了变化,那么项目中的子模块可以自由自主的选择 合并、变基、切换分支 等各种操作。比如一个通用组件作为子模块分别被公司中不同项目引用,则各个项目组做出的改进,最后都可以汇总到主组件库中,为大家所共享。

II. 如何用起子模块?

2.1 - 添加子模块

在当前项目中,添加已有的第三方库:

git submodule add 3RD_LIB_GIT_PATH

默认情况下,子模块会自动放入一个与其仓库同名的子目录中;在末尾也可以加一个自定义的路径参数。

同时项目中会出现一个新的 .gitmodules 配置文件,保存了一些映射关系:

[submodule "3RD_LIB_NAME"]
   path = 3RD_LIB_NAME
   url = 3RD_LIB_GIT_PATH
   
......

子模块所在的子目录是被 Git 特殊对待的 – 也就是说,当你不在此目录中时,Git 默认并不跟踪其中的内容,而是将其变动当成一种特殊的提交对待。

2.2 - 克隆含有子模块的项目

克隆含有子模块的项目时,对应的子目录其实默认是空的,需要额外的步骤。

默认做法是:

# 克隆主项目
git clone MAIN_PROJECT_GIT

# 初始化本地配置文件
git submodule init

# 抓取所有数据并检出父项目中列出的合适的提交
git submodule update

更简单一些的做法是在 clone 时加上参数:

git clone --recursive MAIN_PROJECT_GIT

2.3 - 拉取上游变更

在项目中使用子模块的最简单模式,就是只对其更新并享用最新版本,但并不修改之。

更新子模块的命令为:

git submodule update --remote

Git 默认会尝试更新所有子模块;如果子模块数量众多,也可以在以上命令中传入需要更新的子模块名称。

2.4 - 使用子模块

默认情况下,子模块并没有本地分支,而是会停留在一种特殊的 “detached HEAD” 模式下;要对其修改并被 Git 跟踪的话,就要先手动检出分支:

# 检出一个叫 stable 的分支
git checkout stable

然后从上游拉取新的内容,此时有两种选择:

# 选择A:合并
git submodule update --remote --merge

# 选择B:变基
git submodule update --remote --rebase

2.5 - 发布子模块变更

因为主项目并不会跟踪子模块中的变更,也就是说子目录中更改的具体业务文件不会在 push 时被自动发布;所以需要要求 Git 在推送主项目之前检查所有子模块是否已正确提交:

git push --recurse-submodule=check

根据上述检查结果,可以进入每个子模块并手动提交。

还有更简单的做法是自动完成这项操作:

git push --recurse-submodule=on-demand

此时会先推送子模块再推送主项目,如果前者失败整个流程将停止。

2.6 - 解决子模块冲突

会遇到和其他人先后改动了同一个子模块的情况,也就是一个提交是另一个的直接祖先,那么 Git 会简单地选择之后的提交来合并,这样没什么问题。

不过,当两边同时修改,也就是子模块提交已经分叉的情况下,如果尝试合并,Git 会报 “merge following commits not found” 错误。

解决的方法有些麻烦,罗列如下:

# 得到试图合并的两个分支中记录的提交的 SHA-1 值
$ git diff
diff --cc 3RD_LIB_GIT_PATH
index eb41d76,c771610..0000000
--- a/3RD_LIB_GIT_PATH
+++ b/3RD_LIB_GIT_PATH

# 进入子模块目录
$ cd 3RD_LIB_GIT_PATH

# 基于 git diff 的第二个 SHA 创建一个分支
$ git branch my-try-merge-branch c771610
(3RD_LIB_GIT_PATH) $ git merge my-try-merge-branch
Auto-merging src/main.c
CONFLICT (content): Merge conflict in src/main.c
Recorded preimage for 'src/main.c'
Automatic merge failed; fix conflicts and then commit the result.

# 手动解决冲突
$ vim src/main.c

# 返回到主项目目录中
$ cd ..

# 再次检查 SHA-1 值
$ git diff

# 添加解决后的子模块记录
$ git add 3RD_LIB_GIT_PATH

# 提交合并
$ git commit -m "Merge Tom's Changes"

2.7 - 删除子模块

  • 从 .gitmodules 文件中删除相关的行
  • 从 .git/config 中删除相关部分
  • 运行 git rm –cached <子模块名称>
  • 删除 untracked 的子模块文件

III. 子模块有何问题?

  • 需要手动更新子模块代码
  • 第三方库频繁更新时,本项目的 git log 里会生成很多日志
  • 在项目中运行 git status,顶多只能知道子模块有变化,但具体是什么还要到子目录中再去运行一次
  • 正如前面看到的,建立、删除、合并和解决冲突都比较麻烦

IV. 子模块的进化

git subtree 命令,从 git v1.8 后可用,官方推荐使用 subtree 代替 submodule,其并不需要保存 .submodule 这样的元信息。

subtree 用法如下:

4.1 - 第一次添加子目录,建立与 git 项目的关联

# 其中-f意思是在添加远程仓库之后,立即执行fetch
git remote add -f <子仓库名> <子仓库地址>

# --squash意思是把subtree的改动合并成一次commit,这样就不用拉取子项目完整的历史记录。--prefix之后的=等号也可以用空格
git subtree add --prefix=<子目录名> <子仓库名> <分支> --squash

4.2 - 从远程仓库更新子目录

git fetch <远程仓库名> <分支>

git subtree pull --prefix=<子目录名> <远程分支> <分支> --squash

4.3 - 从子目录push到远程仓库

# 需要确认有写权限
git subtree push --prefix=<子目录名> <远程分支名> 分支

V. 总结

  • 子模块适用于需要修改第三方库,或只引用其一部分的场景
  • 子模块能让另一个仓库作为主项目的子目录,同时还保持提交的独立
  • 子模块的若干操作都比较繁琐
  • 应该逐渐用 subtree 代替 submodule 命令,管理、更新都更加方便

VI. 参考资料:

  • https://git-scm.com/book/zh/v2/Git-工具-子模块
  • https://www.atlassian.com/blog/git/git-submodules-workflows-tips
  • https://news.ycombinator.com/item?id=3904932
  • http://slopjong.de/2013/06/04/git-why-submodules-are-evil/
  • https://blog.csdn.net/mountains2001/article/details/72638009
  • https://item.jd.com/12191481.html
  • https://www.slideshare.net/ssusera62527/submodule-subtree
  • https://yihui.name/cn/2017/03/git-submodule/

本文分享自微信公众号 - 云前端(fewelife),作者:lua

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-07-09

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • [译] 请把 .gitattributes 加入你的项目

    原文:https://dev.to/deadlybyte/please-add-gitattributes-to-your-git-repository-1jl...

    江米小枣
  • Chrome Extension

    Chrome插件是一个用Web技术开发、用来增强浏览器功能的软件,它其实就是一个由HTML、CSS、JS、图片等资源组成的一个.crx后缀的压缩包

    江米小枣
  • Vue 3 新特性:在 Composition API 中使用 CSS Modules

    在 Vue 3 Composition API 最近的一次 beta 升级中,无论是 Vue 3 本 3 库 vue-next,还是面向 Vue 2 过渡用的 ...

    江米小枣
  • 7.10 Git 工具 - 使用 Git 调试

    Git 也提供了两个工具来辅助你调试项目中的问题。 由于 Git 被设计成适用于几乎所有类型的项目,这些工具是比较通用的,但它们可以在出现问题的时候帮助你找到...

    shaonbean
  • 使用git lfs追踪仓库中的二进制文件

    使用博客或者给仓库里的代码做注释时,经常会加上图片。 但是单纯地使用git提交会造成每提交一次体积为M的二进制文件, 仓库的体积就会增加M。

    羽翰尘
  • 实用教学!关于playback系

    playback是FreeSWITCH中的一个Application,用于播放声音文件。在FreeSWITCH中有一个用于文件格式的抽象层,我们可以根据格式编写...

    用户1506126
  • 最简单方法将项目上传到github

    二.填写相应信息后点击create即可 Repository name: 仓库名称 Description(可选): 仓库描述介绍 Public, P...

    良月柒
  • sql 之like 和通配符%,_(mysql)

    (。ŏ_ŏ) like模糊查询,啥叫模糊查询? 例如:我们一个数据库里面存在在一个人叫做李二三四。我们忘记了他的名字,只记得他的姓名,那么我们就可以使用...

    公众号 碧油鸡
  • React Native - 开发工具Atom+Nuclide

    Atom 是 Github 开源的文本编辑器,这个编辑器完全是使用Web技术构建的(基于Node-Webkit)。启动速度快,提供很多常用功能的插件和主题,可以...

    赵哥窟
  • 领域驱动设计(DDD)实践之路(一)

    领域驱动设计(Domain Driven Design,DDD)其实并非新理论,大家可以看看 Eric Evans 编著的《领域驱动设计》原稿首版是2003年,...

    2020labs小助手

扫码关注云+社区

领取腾讯云代金券