Submodule 主子仓协作开发指南

在主子仓协作开发的过程中,时常遇到以下问题:

  • 作为开发者,修改了子仓,却不知如何让主仓记住当前子仓的版本,以便更新能被其他协作者访问。

  • 作为协作者,总是不能正确签出自己想要的代码,误打误撞提交了错误的内容,引发乌龙。

"为什么有个奇怪的 diff 文件怎么删都删不掉?为什么我推送了子仓的代码,别人却拉不到?为什么仓库初始化的时候拉到的版本不对?为什么总有人把我刚更新的子仓代码回退掉?为什么老天如此不公bushi......"

由于以上这些"奇怪的问题"出现频率过高,因此我决定好好聊聊「主子仓的协作原理」,以节省往后的答疑成本。

子仓配置

子仓配置存放在主仓根目录下的 .gitmodules 文件中,记录了子仓的存放路径remote 地址分支,以我们的主仓为例:

properties 复制代码
[submodule "packages/everCloudCore"]
  path = packages/everCloudCore
  url = git@[此处省略].git
  branch = release/oversea

上面的配置代表了:

  1. path:仓库配置下来之后存放在 packages/everCloudCore 目录
  2. url:该子模块的远程仓库地址
  3. branch:该子模块克隆到本地时切出的分支,也是主仓对子仓的基础跟踪分支

当执行git submodule update --init时,会根据.gitmodules文件中的配置信息,在当前目录下创建一个新的子目录,并克隆相应的子模块到目录中。这个过程等价于下面两个操作:

shell 复制代码
git submodule init # 先初始化子仓
git submodule update # 更新子仓代码,会顺便 clone 仓库

Diff 文件介绍

某次修改、或更新代码之后 ,发现仓库多出了一个奇怪的 diff 文件,删又删不掉

这是主仓用于标记子仓 commit hash 版本的内部文件,在子仓初始化或更新时会用到

出现这个文件主要有三个原因:

  1. 修改了子仓代码,但还未提交,你会发现 diff 文件的末尾有一个 -dirty 后缀。
  2. 切换了子仓的分支,导致子仓当前 commit hash 与主仓记录的 commit hash 产生 diff。
  3. 合并了代码,主仓记录的 commit hash 产生变化,而子仓还停留在合并前的 commit hash,产生 diff。

总的来说,只要子仓签出的 commit hash 与主仓所记录的不符,就会产生 diff 文件

子仓 hash 与主仓记录的 hash 产生 diff

子仓因存在未提交的变更而产生 dirty diff

为了简化表述,下文中,我们将对以下称呼进行替换

  • 「子仓签出的 commit hash」→「子仓 hash」
  • 「主仓记录的子仓 commit hash」→「主仓 hash」

开发者视角------提交 Diff 文件

这一小节,我们主要讲述,作为子仓代码的变更者,你需要注意的内容

在主仓中开发子仓

在主仓库中开发子仓时,主仓会产生 diff 文件(原因见上文),开发时不用过度关注主子仓概念,但开发完成准备提交代码时,我们需要注意提交顺序

  1. 首先切到子仓,提交所有变更,推送到远程。
  2. 然后切回主仓,将主仓下生成的 diff 文件,连同主仓代码变更一并提交,然后推送到远程。

Diff 文件好比一个锚点,记录了子仓在正常情况下,应该签出的 commit hash。若子仓未将对应的变更提交到远程,那么主仓相当于,记录了一个不存在的 hash。那么后续其他同学更新代码时,将会遇到报错------大致意思是说 commit hash 不存在。

子仓独立开发

子仓独立开发时,一切流程如旧,该提交提交,该推送推送。我们主要关注,主仓如何获取子仓的变更,并更新指针。假设现在子仓开发完毕,一些流程就绪,我们有以下方法更新主仓 hash:

  • 手动签出:cd 到子仓,像平时一样,fetch 远程,然后手动签出分支或 commit hash。

  • --remote** 参数**:通过运行 git submodule update --remote,我们可以将子仓 hash 更新为配置分支(本文代指 release/oversea)的远程最新 hash。

以上操作之后,若更新成功,主仓将产生 diff 文件,提交推送即可。

协作者视角------消除 Diff 文件

这一小节,我们将探讨,作为子仓的使用者,需要注意的内容。

当我们在主仓中拉取、合并远程代码时,主仓 hash 可能因需求迭代产生变更。又或者本地切换不同分支时,不同分支记录的主仓 hash 不同。而在 Git 的工作流中,子仓 hash 并不会随着主仓代码更新而自动更新,导致子仓 hash 落后或领先于主仓 hash,主子仓 hash 记录不符,从而产生 diff 文件。

意外提交 diff 文件,将会让主仓 hash 指向不符合预期的版本,这不是我们所希望的。

A:"我刚提了个 commit 更新了主仓 hash,你怎么又给我回退了?"

B:"啊?我没改过啊,那个不是自动生成的吗?提交的时候文件太多了,也没注意。"

我:"......还是看下文的「补救措施」吧 😅"

变更主仓 hash 是开发者的事情。作为协作者,在遇到 diff 文件时,我们应该首先考虑,通过一些方式消除它

更正子仓 hash 值

最常用的方法是执行 git submodule update,令子仓签出主仓 hash,消除 diff,执行效果如下:

直接在主仓重置这个 diff 文件是没用的 。因为这个文件存在的核心原因,是子仓 hash 和主仓 hash 的不统一。diff 文件只是这种关系的记录和展现,并不能实际左右子仓行为。在子仓未正确签出 hash 值的情况下,即便在主仓的 Git 面板上丢弃(Discard)这个 diff 文件,也不会生效。

综上所述,消除 diff 文件的核心思路是,需要令子仓正确签出主仓 hash

明白原理后,你应该意识到,我们通过手动的方式进入子仓签出主仓 hash 也是可以消除 diff 的,只要子仓当前的 HEAD 指针对应的 commit hash 值和主仓保持一致即可。

假设当前主仓 hash 值为 <current-hash-recorded-in-main-repo>

shell 复制代码
cd packages/everCloudCore # 进入子仓
git checkout <current-hash-recorded-in-main-repo> # 签出对应 hash

另外补充, 在 Git 中签出分支的本质也就是签出对应的 commit hash。若 <branch> 分支对应的最新 hash 也为 <current-hash-recorded-in-main-repo> ,那么 git checkout <branch> 效果是一样的。

补救措施------更正主仓 hash 值

凡事总有意外,当其他同学在本应消除 diff 文件的时候,误将其提交,通常会导致其他「协作者」们切错版本,引发一些副作用,例如编译报错。

此时你挺身而出,作为拯救这一切的锅灰勇士,天选之子,你应该怎么办?下述流程可做参考:

  1. 通过 commit 历史,找到错误的提交,复制出变更之前的 hash 值,然后 cd 到子仓签出该值。
  2. 如果找不到 commit 记录,则需要明确当前分支期望的子仓 hash 是多少,涉及到业务需求,不多赘述。
    • 如果希望将子仓更新到配置分支(release/oversea)最新的远程 commit hash,运行 git submodule update --remote 即可。

更新完成后,主仓会产生 diff 文件,我们将这个文件提交,然后推送到远程即可。

关于 --remote 参数

我们知道,git submodule update 命令,操作子仓,签出主仓 hash。而 --remote 参数将改变这个行为,令子仓不再对齐主仓 ,转而对齐子仓配置分支的远程最新 commit hash。

我们通常在子仓远程分支有更新,并且希望在主仓拉取子仓更新时使用,详见上文「子仓独立开发」模块。

相关推荐
f89790707026 分钟前
layui动态表格出现 横竖间隔线
前端·javascript·layui
鱼跃鹰飞32 分钟前
Leecode热题100-295.数据流中的中位数
java·服务器·开发语言·前端·算法·leetcode·面试
云端奇趣35 分钟前
探索 3 个有趣的 GitHub 学习资源库
经验分享·git·学习·github
二十雨辰1 小时前
[uni-app]小兔鲜-04推荐+分类+详情
前端·javascript·uni-app
霸王蟹2 小时前
Vue3 项目中为啥不需要根标签了?
前端·javascript·vue.js·笔记·学习
小白求学12 小时前
CSS计数器
前端·css
Anita_Sun2 小时前
🌈 Git 全攻略 - Git 的初始设置 ✨
前端
lucifer3112 小时前
深入解析 React 组件封装 —— 从业务需求到性能优化
前端·react.js
等什么君!2 小时前
复习HTML(进阶)
前端·html
儒雅的烤地瓜2 小时前
JS | 如何解决ajax无法后退的问题?
前端·javascript·ajax·pushstate·popstate事件·replacestate