背景:学习 Hugo + GitHub Pages 搭建博客,在「安装 PaperMod 主题」这一步遇到了
git submodule命令。
⚠️ 注意:文中的 PaperMod 主题仓库地址务必以官方为准,使用前请先核对最新地址:
- 官方仓库:https://github.com/adityatelange/hugo-PaperMod
- SSH 地址:
git@github.com:adityatelange/hugo-PaperMod.git- 曾误写过
adnanhob/PaperMod(该仓库不存在),已更正。仓库地址可能随时间迁移,动手前建议到 GitHub 搜索hugo-PaperMod确认。
一、命令逐词拆解
git submodule add --depth=1 git@github.com:adityatelange/hugo-PaperMod.git themes/PaperMod
| 片段 | 含义 |
|---|---|
git submodule add | git 的核心命令:添加一个子模块 |
--depth=1 | 只拉取最近 1 次提交(浅克隆) |
git@github.com:adityatelange/hugo-PaperMod.git | 主题的远程仓库地址(SSH 形式) |
themes/PaperMod | 主题下载到本地的位置 |
二、核心概念:什么是「子模块」(submodule)
要解决的场景冲突
你的博客站点本身是个 git 仓库(
my-blog),而 PaperMod 主题也是一个独立的 git 仓库。现在你想把主题代码放进博客的themes/目录里。
如果直接 git clone 主题进去,就会形成「仓库套仓库」的混乱局面——博客的 git 会看到 themes/PaperMod 里还有个 .git,管理起来一团糟。
子模块的机制
子模块就是 git 专门用来解决「仓库里套仓库」的机制。做法很聪明:
- 你的博客仓库不保存主题的实际代码,只保存一句话:「
themes/PaperMod这个位置,指向adityatelange/hugo-PaperMod仓库的某个特定版本」 - 主题的代码仍然留在它自己的仓库里,独立更新、独立管理
原理图
┌─────────────────────────────┐ ┌─────────────────────────────┐
│ 你的博客仓库 my-blog │ │ PaperMod 主题仓库 │
│ │ │ │
│ content/ 文章 │ │ (独立存在,独立更新) │
│ hugo.toml 配置 │ │ │
│ themes/PaperMod │ 引用 │ 真正存代码的地方 │
│ ↳ 只存一个「引用指针」 │ ──────▶ │ │
│ .gitmodules 记录指向 │ │ │
└─────────────────────────────┘ └─────────────────────────────┘
好处:主题升级 = 单独 pull,互不干扰
三、--depth=1 是什么:浅克隆(shallow clone)
正常情况下,git clone 会把仓库的全部历史记录(每一次提交)都下载下来。但 PaperMod 发展了几年、有上千次提交,全部下载既慢又占空间——而我们其实只需要当前最新的代码。
--depth=1 的意思就是:只下载最新的一次提交,不要历史。
- 速度快、体积小
- 适合「要用主题」而非「研究主题历史」的场景
--depth=1是传给子模块内部那个git clone的参数(git 允许这样把参数「穿透」进去)
四、执行这个命令后会发生什么
git 会输出类似:
Cloning into 'themes/PaperMod'...
Submodule path 'themes/PaperMod': checked out 'xxxxxxx'
博客目录里会多出两个东西:
themes/PaperMod/—— 主题代码被下载到这里.gitmodules—— 新文件,记录「themes/PaperMod指向哪个仓库」,相当于子模块的「登记表」
以后任何人(包括未来的自己)拿到这个博客仓库,只要执行一条命令就能把主题代码重新拉下来(因为主题代码本身没存进仓库,只存了引用指针):
git submodule update --init
五、submodule vs Hugo module(两种装主题方式)
参考博客 blog.xiaohuangyu.space 的作者用的是 Hugo module 方式,和我们学的 submodule 不同。
先纠正一个误解
作者说"submodule 是把主题代码塞进你的仓库"。这句话技术上不完全准确:
- submodule 其实也没有把主题代码"塞"进仓库——它同样只存一个引用指针(
.gitmodules里那行记录),主题代码仍留在主题自己的仓库里。 - 两者的真正区别在于:“引用"发生在哪个环节、由谁来拉取主题代码。
核心区别对比
| 对比项 | submodule(我们学的) | Hugo module(他用的) |
|---|---|---|
| 引用记录 | .gitmodules | go.mod / module.toml |
| 主题代码 | 下载到 themes/ 目录 | 不进仓库 |
| 拉取方式 | 手动 git submodule update | Hugo 构建时自动拉 |
| 前提 | 只需 git | 需要额外装 Go |
| 难度 | 简单直观,新手友好 | 稍复杂,偏进阶 |
| 优点 | 直观、依赖少、出错好排查 | 仓库更干净、升级省心 |
作者的判断放在他的语境里
作者说"submodule 改起来重”,对他成立,原因有两点:
- 他用了多个主题、长期折腾,submodule 每次换/升级主题都要手动操作,是负担。
- 他本身有 Go 环境,装 Go 零成本,module 几乎无门槛。
这两个前提,新手目前都不具备。
Hugo module 的真实优劣
优势:
- 仓库更干净:主题代码完全不进仓库,仓库里只有自己的文章和配置。
- 升级省心:
hugo mod get -u一条命令升级,不用碰 git submodule 命令。
代价:
- 必须额外装 Go(新工具、新环境)。
- 构建时依赖网络拉取主题(离线/网络不好时,本地可能构建不了)。
- 出问题时排查链路更长(涉及 Go 模块缓存、版本解析)。
结论
现在用 submodule,先别切。 理由:
- 已经
git init了,submodule 是自然顺下来的下一步,零额外依赖。 - 正在学 git,submodule 能看得见主题代码在
themes/PaperMod/里,对理解"Hugo 怎么找到主题"帮助更大。 - 单一主题、稳定使用,“升级麻烦"这个缺点短期内根本遇不到。
等遇到以下场景再切 module 不迟:想同时用多个主题 / 频繁换主题、嫌弃仓库里多出的主题目录、已装 Go 想统一用 Hugo 生态。
一句话:作者的 module 方案是"进阶后的选择”,不是"新手的起点"。
六、相关知识点速记
- 子模块(submodule):git 处理「仓库里套仓库」的机制,父仓库只存引用指针,不存子仓库代码。
- 浅克隆(
--depth=1):只拉最新一次提交,不拉历史,省时省空间。 .gitmodules:子模块的登记表,记录子模块路径和对应仓库地址。git submodule update --init:克隆仓库后,重新拉取所有子模块代码。