长篇分享

为什么我不推荐你使用 git submodule

最近,笔者在某些项目的开发中碰到了 git submodule 并结结实实地被它恶心到了,从奇怪的版本管理到与父项目的尴尬关系——总之处处透漏着不舒服,本以为只有我是这么觉得,结果随手一搜发现与我有同样想法的人不在少数,这次就让我结合@dizlet的一篇博文,来谈谈为什么 git submodule 那么令人生厌?又有什么别的方式可以替换掉它?

太长不读版

永远别用 git submodule,即便你觉得现在碰到的情况很适合用它,也别用。

下面我会详细解释一下为啥不要用,以及各种替代方案。

Git Submodule 的问题

它的问题主要体现在两点:

  • 底层设计上就有问题。它破坏了 git 的数据模型,包括但不限于以下几个方面:
    1. 你的仓库中的 git 对象不再一定能解析为有意义的数据。(浅克隆也有类似的问题,但那只是对于历史记录来说。而 git submodule 对树也一样有问题。)
    2. 它不符合 git 的一般规定。子模块中的 URL 和主机名都是由 git 配置文件决定的,而不是通常的 git 仓库本身。
    3. 它会导致 git 树处于奇怪的状态,而要排除起来则非常痛苦。
  • 细节实现上也问题多多。有的是从设计根上带出来的,但更多的是实现上的问题,即便你压根没用git submodule init初始化各个子模块,它还是会影响到你的仓库,暴露出的问题如下:
    1. 用于切换分支的git checkout命令不再可靠。
    2. 编辑和提交会变得非常痛苦
    3. 从主分支拉取代码会变麻烦
    4. git ls-files的输出会和git loggit cat-file产生冲突
    5. .gitmodules中的 URL 可能包含恶意链接,它甚至能被缓存在本地的.git/config文件里。
      总的来说,很多非常常见的 git 操作,诸如git checkoutgit pull这样的命令就会导致子模块处于非常怪异的状态,你必须要运行某个与子模块相关的命令才能回到正常的状态。对绝大部分人来说,他们可能更愿意直接把仓库删了,然后重新克隆——我也是这么干的,除非你是某位 git 绝地大师,否则千万别试图排除奇怪的子模块问题,纯属浪费时间。

所以,对我们开发者(库的维护者)而言,就只剩两个方案了:

  1. 干脆别用,跟各位开发者讲清楚子模块的各种问题,并让他们放弃。
  2. 捏着鼻子用,并准备好应对它给你带来的各种麻烦核问题,并浪费大量的时间和精力处理这些问题。

有什么替代品吗?

但是,如果真的有类似需求,又不想在子模块上浪费人生要怎么办呢?我推荐下面的这些解决方案,你可以根据自己的需求选择合适的方案。

Git Subtree

Git Subtree 可以解决很多 git submodule 能解决的问题,同时不会破坏 git 的数据模型。
如果你的项目符合这些特点,可以考虑使用 subtree:

  • 你想要在你的代码树里跟踪并使用另一个项目,但又想保存它的独立性。
  • 与你的项目相比,子项目的大小相对合理。

如果你使用 git subtree,大部分开发者都不需要与子树中的项目交互,他们甚至不会注意到这是一个子树,可以随便提交、切换分支,怎么搞都没问题。git subtree 可以自动从下游分离出对下游分支的更改,以应用于(或提交给)上游分支。

我之前在自己的项目中使用过 git subtree,并发现它强大、方便,且非常简单直接,因此我推荐你也尝试一下!

干脆就用一个仓库

如果你想引入的项目本来维护人就是你,那么选择用一个仓库也是一个不错的选项,因为你可以直接把另一个项目的 git 历史合并进来。

如果你的项目符合这些特点,可以考虑直接使用单个仓库:

  • 你仓库中使用的各个项目能够共享同样的 git 历史记录
  • 整个项目的大小在合理的范围内
  • 你项目中长期存在的分支只是为了维护发版,而不是为了让某个内部项目维持在不同的版本。(例如 PCRE2 与仓库中的 sljit)

使用包管理系统和显式的依赖

与其自己维护依赖,还不如直接用别人打包好的!这个方案的精髓就在于将你的项目与依赖分开,并使用它提供的各种 API 取代之前在树中代码的作用,然后用一个包管理器安装它!(如有必要的话,你可以自己维护一个特殊版本的下游包)

可供选用的包管理系统包括:

  1. 给发行版用的包管理系统:比如 Debian 的 apt + dpkg + sbuild 的组合。
  2. 语言专属的包管理系统,比如 pip 或者 cargo。

如果你的项目满足以下要求,可以考虑这个方案:

  • 你正在使用、或者很熟悉一个合适的包管理器。
  • 这个包管理器可以将你需要的 API 以合理的方式暴露出来。

使用多仓库工具:MR

mr 是一个可以让你方便地管理多个代码树的工具,通常这些树之间是并列关系。

我虽然没有亲自用过这个工具,但它看起来还不错,你可以把它跟我下面要介绍的基于..的依赖解决方案结合起来。如果你的项目里有很多外部项目的话,可以考虑这个方案。

把依赖放在 ../dependency 里

这个解决方案非常轻量,不需要任何工具,只需要把你项目的依赖放到../dependency里就好了。并让用户手动选择正确版本的依赖。

如果你的项目符合这些条件,可以考虑这个方案:

  • 你的项目处于初期起步阶段,你不想过多操心依赖和构建的问题。
  • 依赖默认是被禁用的,并且几乎不会被启用。

每个需要依赖项的程序或人都需要明白如何克隆依赖项并更新,这可能会带来一些麻烦,如果你正在使用 CI(持续集成),则需要编写一些自定义的 CI 脚本。但这还是比 git submodule 强,至少对于大部分人来说,究竟发生了什么、以及如何对依赖项进行更改等都是完全可见的。

提供一个临时的内部脚本来下载依赖项

作为最后的手段,你可以在顶层软件包的构建系统中嵌入用于查找依赖项的 URL 和下载指令。这种方法很笨、也很麻烦,但可能让你惊讶的是,它还是比 git submodule 要强得多。

如果你的项目符合这些特点,可以考虑这个方案:

  • 大多数使用/构建你的软件的人根本不需要依赖项。
  • 大多数人不需要编辑依赖项。

任何其他的情况下,都不要考虑这个方案。

通常情况下,下游构建过程应该使用 git clone 命令克隆依赖项,下游代码树应该指定所需的准确提交 ID。

尽量避免使用这个方案,这并不是一个优秀的解决方案。但是:

真的,git submodule 还不如 Makefile

临时的 shell 脚本看起来很不让人满意。但是与 git submodule 相比,它还是有一些优点的。比如,与 git submodule 不同,这种方法(就像我提出的大多数其他方法)意味着:

  • 所有期望克隆你的存储库、进行更改、构建、跟踪更改等的工具都能正常工作。
  • 你可以精确控制下载发生的时间/条件:也就是说,你可以安排在需要依赖项时精确下载它。
  • 你可以精确控制依赖项的版本管理和检查:你的脚本控制着使用的依赖项版本以及是否“固定”或动态更新。

再次重申一遍,我并不喜欢临时脚本,也不认为它是一个好主意,只是因为 git submodule 实在是太烂了,总之一句话,千万别用!

参考文章:
https://diziet.dreamwidth.org/14666.html

对这篇文章感觉如何?

太棒了
0
不错
0
爱死了
0
不太好
0
感觉很糟
1

You may also like

Leave a reply

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据

More in:长篇分享

教程

安卓逆向:签名校验对抗

本文介绍了应用签名校验的原理和常见的签名校验特征,以及签名校验对抗的方法。其中包括了核心破解插件、一键过签名工具、手撕签名校验、PM代理、IO重定向和模拟器检测、反调试检测以及Frida检测等。本文为逆向爱好者提供了一些反制签名校验的思路和方法,并介绍了一些常见的签名校验技术。标签:安卓逆向、签名校验、反调试检测、IO重定向、模拟器检测、Frida检测。
开源项目

Pandora:一个让你呼吸顺畅的ChatGPT

本文介绍了一款名为Pandora的开源项目,它是一款用于解决国内用户使用ChatGPT时遇到的种种问题的客户端。本文详细介绍了Pandora的功能特性、安装方法、部署方式以及Docker部署方法。
长篇分享

Debian 打包入门指南

最近笔者忙于为一些 Debian 衍生发行版打包软件包,这些发行版往往都会使用 deb 包作为默认的软件包,作为 Linux 世界两大软件包格式之一(另一个是 RedHat 系的 rpm 包),deb […]
教程

安卓逆向:动态调试指南

本文详细介绍了安卓逆向工程中的动态调试过程。通过阐述各种工具的使用、动态调试环境的配置、获取debug权限、端口转发以及开启adb调试权限、下断点和使用Jeb附加调试进程等步骤,为您提供了一份详尽的动态调试教程。