背景:学习 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 addgit 的核心命令:添加一个子模块
--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'

博客目录里会多出两个东西

  1. themes/PaperMod/ —— 主题代码被下载到这里
  2. .gitmodules —— 新文件,记录「themes/PaperMod 指向哪个仓库」,相当于子模块的「登记表」

以后任何人(包括未来的自己)拿到这个博客仓库,只要执行一条命令就能把主题代码重新拉下来(因为主题代码本身没存进仓库,只存了引用指针):

git submodule update --init

五、submodule vs Hugo module(两种装主题方式)

参考博客 blog.xiaohuangyu.space 的作者用的是 Hugo module 方式,和我们学的 submodule 不同。

先纠正一个误解

作者说"submodule 是把主题代码塞进你的仓库"。这句话技术上不完全准确

  • submodule 其实也没有把主题代码"塞"进仓库——它同样只存一个引用指针.gitmodules 里那行记录),主题代码仍留在主题自己的仓库里。
  • 两者的真正区别在于:“引用"发生在哪个环节、由谁来拉取主题代码

核心区别对比

对比项submodule(我们学的)Hugo module(他用的)
引用记录.gitmodulesgo.mod / module.toml
主题代码下载到 themes/ 目录不进仓库
拉取方式手动 git submodule updateHugo 构建时自动拉
前提只需 git需要额外装 Go
难度简单直观,新手友好稍复杂,偏进阶
优点直观、依赖少、出错好排查仓库更干净、升级省心

作者的判断放在他的语境里

作者说"submodule 改起来重”,对他成立,原因有两点:

  1. 他用了多个主题、长期折腾,submodule 每次换/升级主题都要手动操作,是负担。
  2. 他本身有 Go 环境,装 Go 零成本,module 几乎无门槛。

这两个前提,新手目前都不具备。

Hugo module 的真实优劣

优势:

  • 仓库更干净:主题代码完全不进仓库,仓库里只有自己的文章和配置。
  • 升级省心:hugo mod get -u 一条命令升级,不用碰 git submodule 命令。

代价:

  • 必须额外装 Go(新工具、新环境)。
  • 构建时依赖网络拉取主题(离线/网络不好时,本地可能构建不了)。
  • 出问题时排查链路更长(涉及 Go 模块缓存、版本解析)。

结论

现在用 submodule,先别切。 理由:

  1. 已经 git init 了,submodule 是自然顺下来的下一步,零额外依赖。
  2. 正在学 git,submodule 能看得见主题代码在 themes/PaperMod/ 里,对理解"Hugo 怎么找到主题"帮助更大。
  3. 单一主题、稳定使用,“升级麻烦"这个缺点短期内根本遇不到。

等遇到以下场景再切 module 不迟:想同时用多个主题 / 频繁换主题、嫌弃仓库里多出的主题目录、已装 Go 想统一用 Hugo 生态。

一句话:作者的 module 方案是"进阶后的选择”,不是"新手的起点"。


六、相关知识点速记

  • 子模块(submodule):git 处理「仓库里套仓库」的机制,父仓库只存引用指针,不存子仓库代码。
  • 浅克隆(--depth=1:只拉最新一次提交,不拉历史,省时省空间。
  • .gitmodules:子模块的登记表,记录子模块路径和对应仓库地址。
  • git submodule update --init:克隆仓库后,重新拉取所有子模块代码。