<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>开发实战 on DK Blog</title><link>/tags/%E5%BC%80%E5%8F%91%E5%AE%9E%E6%88%98/</link><description>Recent content in 开发实战 on DK Blog</description><generator>Hugo -- gohugo.io</generator><language>zh-CN</language><lastBuildDate>Thu, 25 Jun 2026 20:25:00 +0800</lastBuildDate><atom:link href="/tags/%E5%BC%80%E5%8F%91%E5%AE%9E%E6%88%98/index.xml" rel="self" type="application/rss+xml"/><item><title>Git 子模块的核心：不是复制代码，而是固定引用一个仓库</title><link>/dkblog/038-git-submodule-core/</link><pubDate>Mon, 15 Jun 2026 00:00:00 +0000</pubDate><guid>/dkblog/038-git-submodule-core/</guid><description>&lt;img src="https://images.unsplash.com/photo-1618401471353-b98afee0b2eb?auto=format&amp;fit=crop&amp;w=900&amp;q=60" alt="Featured image of post Git 子模块的核心：不是复制代码，而是固定引用一个仓库" /&gt;&lt;h1 id="git-子模块的核心不是复制代码而是固定引用一个仓库"&gt;&lt;a href="#git-%e5%ad%90%e6%a8%a1%e5%9d%97%e7%9a%84%e6%a0%b8%e5%bf%83%e4%b8%8d%e6%98%af%e5%a4%8d%e5%88%b6%e4%bb%a3%e7%a0%81%e8%80%8c%e6%98%af%e5%9b%ba%e5%ae%9a%e5%bc%95%e7%94%a8%e4%b8%80%e4%b8%aa%e4%bb%93%e5%ba%93" class="header-anchor"&gt;&lt;/a&gt;Git 子模块的核心：不是复制代码，而是固定引用一个仓库
&lt;/h1&gt;&lt;p&gt;我第一次真正注意到 Git 子模块，是在看当前这个博客项目的时候。&lt;/p&gt;
&lt;p&gt;项目根目录里有一个 &lt;code&gt;.gitmodules&lt;/code&gt; 文件。刚看到它时，很容易以为这只是 Git 的某个额外配置。但继续看目录结构就会发现，它其实说明了一件更重要的事：这个项目里有一部分代码，并不属于主仓库本身，而是来自另一个 Git 仓库。&lt;/p&gt;
&lt;p&gt;以博客项目为例，主仓库通常负责这些内容：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;文章内容&lt;/li&gt;
&lt;li&gt;站点配置&lt;/li&gt;
&lt;li&gt;部署脚本&lt;/li&gt;
&lt;li&gt;自己写的少量定制代码&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但主题、模板或者某个公共组件，可能来自另一个开源仓库。这个时候就会出现一个问题：主项目需要使用那份代码，但又不想真的把那份代码复制进来。&lt;/p&gt;
&lt;p&gt;Git 子模块就是为了解决这个问题。&lt;/p&gt;
&lt;h2 id="先看一个具体场景"&gt;&lt;a href="#%e5%85%88%e7%9c%8b%e4%b8%80%e4%b8%aa%e5%85%b7%e4%bd%93%e5%9c%ba%e6%99%af" class="header-anchor"&gt;&lt;/a&gt;先看一个具体场景
&lt;/h2&gt;&lt;p&gt;假设我有一个博客项目，仓库叫 &lt;code&gt;myblog&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;它的目录大概是这样：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-0-1"&gt;&lt;a class="lnlinks" href="#hl-0-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-0-2"&gt;&lt;a class="lnlinks" href="#hl-0-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-0-3"&gt;&lt;a class="lnlinks" href="#hl-0-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-0-4"&gt;&lt;a class="lnlinks" href="#hl-0-4"&gt;4&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-0-5"&gt;&lt;a class="lnlinks" href="#hl-0-5"&gt;5&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;myblog/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; content/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; config.toml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; themes/blog-theme/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; .gitmodules
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;其中 &lt;code&gt;content/&lt;/code&gt; 和 &lt;code&gt;config.toml&lt;/code&gt; 是我自己的内容，应该直接提交到 &lt;code&gt;myblog&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;但 &lt;code&gt;themes/blog-theme/&lt;/code&gt; 不是我从零写的，它来自另一个仓库：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-1-1"&gt;&lt;a class="lnlinks" href="#hl-1-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;https://github.com/example/blog-theme.git
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这里有三种处理方式。&lt;/p&gt;
&lt;p&gt;第一种是直接复制主题代码到 &lt;code&gt;themes/blog-theme/&lt;/code&gt;。这看起来最简单，但后面会很难维护。因为复制之后，我就很难知道这些代码最初来自哪里，也很难继续合并原作者后续更新。&lt;/p&gt;
&lt;p&gt;第二种是每次构建时临时下载主题。这对一些项目可以，但对于博客主题这种需要本地改配置、看源码、偶尔还要定制的东西，不一定方便。&lt;/p&gt;
&lt;p&gt;第三种就是 Git 子模块：主项目保留 &lt;code&gt;themes/blog-theme/&lt;/code&gt; 这个目录，但这个目录本身由另一个 Git 仓库提供。&lt;/p&gt;
&lt;p&gt;这就是子模块最核心的作用：&lt;strong&gt;让一个 Git 仓库把另一个 Git 仓库当作子目录来使用，同时又保留两个仓库各自的边界。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="gitmodules-到底记录了什么"&gt;&lt;a href="#gitmodules-%e5%88%b0%e5%ba%95%e8%ae%b0%e5%bd%95%e4%ba%86%e4%bb%80%e4%b9%88" class="header-anchor"&gt;&lt;/a&gt;&lt;code&gt;.gitmodules&lt;/code&gt; 到底记录了什么
&lt;/h2&gt;&lt;p&gt;当项目使用子模块时，主仓库里会多一个 &lt;code&gt;.gitmodules&lt;/code&gt; 文件。&lt;/p&gt;
&lt;p&gt;它大概长这样：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-2-1"&gt;&lt;a class="lnlinks" href="#hl-2-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-2-2"&gt;&lt;a class="lnlinks" href="#hl-2-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-2-3"&gt;&lt;a class="lnlinks" href="#hl-2-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[submodule &amp;#34;themes/blog-theme&amp;#34;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="na"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;themes/blog-theme
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;	url = https://github.com/example/blog-theme.git&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这个文件只说明两件事：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;子模块放在哪个目录&lt;/li&gt;
&lt;li&gt;子模块应该从哪个远程仓库拉取&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;也就是说，&lt;code&gt;.gitmodules&lt;/code&gt; 记录的是“地址映射关系”。&lt;/p&gt;
&lt;p&gt;但这还不是全部。主仓库还会额外记录这个子模块当前指向哪个提交。&lt;/p&gt;
&lt;p&gt;这点很关键。&lt;/p&gt;
&lt;p&gt;主仓库并不会把 &lt;code&gt;themes/blog-theme/&lt;/code&gt; 里的每个文件都当成自己的文件来跟踪。它只会记录一句话：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-3-1"&gt;&lt;a class="lnlinks" href="#hl-3-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;themes/blog-theme 当前使用的是 blog-theme 仓库里的某个 commit
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;你可以把它理解成一个固定版本号。&lt;/p&gt;
&lt;p&gt;比如：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-4-1"&gt;&lt;a class="lnlinks" href="#hl-4-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;myblog 使用 blog-theme@a1b2c3d
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;所以 Git 子模块不是“把别人的代码复制进来”，而是“在主仓库里固定引用另一个仓库的某一次提交”。&lt;/p&gt;
&lt;p&gt;这就是它的核心。&lt;/p&gt;
&lt;h2 id="为什么这件事有价值"&gt;&lt;a href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e8%bf%99%e4%bb%b6%e4%ba%8b%e6%9c%89%e4%bb%b7%e5%80%bc" class="header-anchor"&gt;&lt;/a&gt;为什么这件事有价值
&lt;/h2&gt;&lt;p&gt;还是用博客主题举例。&lt;/p&gt;
&lt;p&gt;如果主题仓库今天更新了一个版本，但这个版本有 bug，我不希望我的博客项目自动跟着坏掉。&lt;/p&gt;
&lt;p&gt;子模块不会自动追最新版本。它会停在主仓库记录的那个提交上。&lt;/p&gt;
&lt;p&gt;这带来了一个很重要的好处：可复现。&lt;/p&gt;
&lt;p&gt;别人 clone 我的博客项目时，只要把子模块也初始化下来，他拿到的主题代码就应该和我提交时使用的是同一个版本。&lt;/p&gt;
&lt;p&gt;这比“文档里写一句请安装某某主题最新版”可靠得多。因为最新版一直在变，但提交号不会变。&lt;/p&gt;
&lt;h2 id="为什么刚-clone-下来时子模块可能是空的"&gt;&lt;a href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e5%88%9a-clone-%e4%b8%8b%e6%9d%a5%e6%97%b6%e5%ad%90%e6%a8%a1%e5%9d%97%e5%8f%af%e8%83%bd%e6%98%af%e7%a9%ba%e7%9a%84" class="header-anchor"&gt;&lt;/a&gt;为什么刚 clone 下来时子模块可能是空的
&lt;/h2&gt;&lt;p&gt;很多人第一次接触子模块时，会遇到一个现象：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-5-1"&gt;&lt;a class="lnlinks" href="#hl-5-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git clone &amp;lt;repo-url&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;clone 完之后，子模块目录存在，但里面可能是空的，或者没有真正的内容。&lt;/p&gt;
&lt;p&gt;这是正常的。&lt;/p&gt;
&lt;p&gt;因为主仓库只先拿到了 &lt;code&gt;.gitmodules&lt;/code&gt; 和子模块指向的提交号，还没有真正把子模块仓库拉下来。&lt;/p&gt;
&lt;p&gt;这时需要执行：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-6-1"&gt;&lt;a class="lnlinks" href="#hl-6-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git submodule update --init --recursive
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;如果从一开始就知道项目有子模块，也可以直接：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-7-1"&gt;&lt;a class="lnlinks" href="#hl-7-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git clone --recurse-submodules &amp;lt;repo-url&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这条命令的意思是：clone 主仓库时，把它引用的子模块也一起拉下来。&lt;/p&gt;
&lt;h2 id="日常更新子模块时真正更新的是什么"&gt;&lt;a href="#%e6%97%a5%e5%b8%b8%e6%9b%b4%e6%96%b0%e5%ad%90%e6%a8%a1%e5%9d%97%e6%97%b6%e7%9c%9f%e6%ad%a3%e6%9b%b4%e6%96%b0%e7%9a%84%e6%98%af%e4%bb%80%e4%b9%88" class="header-anchor"&gt;&lt;/a&gt;日常更新子模块时，真正更新的是什么
&lt;/h2&gt;&lt;p&gt;假设主题仓库修了一个 bug，我希望博客项目使用新版本。&lt;/p&gt;
&lt;p&gt;直觉上会觉得：进入 &lt;code&gt;themes/blog-theme/&lt;/code&gt;，执行 &lt;code&gt;git pull&lt;/code&gt; 就完了。&lt;/p&gt;
&lt;p&gt;但这只完成了一半。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-8-1"&gt;&lt;a class="lnlinks" href="#hl-8-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-8-2"&gt;&lt;a class="lnlinks" href="#hl-8-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; themes/blog-theme
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git pull
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这一步只是让子模块目录里的代码更新到了新提交。&lt;/p&gt;
&lt;p&gt;接下来必须回到主仓库，再提交一次：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-9-1"&gt;&lt;a class="lnlinks" href="#hl-9-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-9-2"&gt;&lt;a class="lnlinks" href="#hl-9-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-9-3"&gt;&lt;a class="lnlinks" href="#hl-9-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ../..
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git add themes/blog-theme
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git commit -m &lt;span class="s2"&gt;&amp;#34;chore: update blog theme&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;为什么还要提交主仓库？&lt;/p&gt;
&lt;p&gt;因为主仓库记录的是“子模块指向哪个提交”。当子模块从 &lt;code&gt;a1b2c3d&lt;/code&gt; 更新到 &lt;code&gt;e4f5g6h&lt;/code&gt; 时，主仓库也要把这个指针变化记录下来。&lt;/p&gt;
&lt;p&gt;所以更新子模块，本质上不是把一堆主题文件提交到主仓库，而是修改主仓库里的这条引用：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-10-1"&gt;&lt;a class="lnlinks" href="#hl-10-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;blog-theme@a1b2c3d -&amp;gt; blog-theme@e4f5g6h
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这也是很多人对 Git 子模块困惑的地方：你看到的是一个目录，但 Git 看到的是另一个仓库的提交号。&lt;/p&gt;
&lt;h2 id="如果我想魔改这个主题应该怎么办"&gt;&lt;a href="#%e5%a6%82%e6%9e%9c%e6%88%91%e6%83%b3%e9%ad%94%e6%94%b9%e8%bf%99%e4%b8%aa%e4%b8%bb%e9%a2%98%e5%ba%94%e8%af%a5%e6%80%8e%e4%b9%88%e5%8a%9e" class="header-anchor"&gt;&lt;/a&gt;如果我想魔改这个主题，应该怎么办
&lt;/h2&gt;&lt;p&gt;这才是子模块在真实项目里最容易遇到的问题。&lt;/p&gt;
&lt;p&gt;一开始我只是引用别人的主题。后来我想改样式、改布局、加自己的逻辑。这时应该怎么处理？&lt;/p&gt;
&lt;p&gt;这里有两个方向。&lt;/p&gt;
&lt;h2 id="方案一fork-原仓库然后让子模块指向自己的-fork"&gt;&lt;a href="#%e6%96%b9%e6%a1%88%e4%b8%80fork-%e5%8e%9f%e4%bb%93%e5%ba%93%e7%84%b6%e5%90%8e%e8%ae%a9%e5%ad%90%e6%a8%a1%e5%9d%97%e6%8c%87%e5%90%91%e8%87%aa%e5%b7%b1%e7%9a%84-fork" class="header-anchor"&gt;&lt;/a&gt;方案一：fork 原仓库，然后让子模块指向自己的 fork
&lt;/h2&gt;&lt;p&gt;这是我更推荐的方式，尤其适合“我想改，但还希望保留上游更新能力”的情况。&lt;/p&gt;
&lt;p&gt;原因很简单：原来的主题仓库不是我的，我没有权限直接往里面推代码。即使我在本地子模块目录里改了代码，这些改动也需要一个地方保存。&lt;/p&gt;
&lt;p&gt;最合理的地方就是自己的 fork。&lt;/p&gt;
&lt;p&gt;流程大概是这样。&lt;/p&gt;
&lt;p&gt;先在 GitHub 或 Gitee 上 fork 原主题仓库。&lt;/p&gt;
&lt;p&gt;原来子模块可能指向这里：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-11-1"&gt;&lt;a class="lnlinks" href="#hl-11-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-11-2"&gt;&lt;a class="lnlinks" href="#hl-11-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-11-3"&gt;&lt;a class="lnlinks" href="#hl-11-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[submodule &amp;#34;themes/blog-theme&amp;#34;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="na"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;themes/blog-theme
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;	url = https://github.com/original/blog-theme.git&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;fork 之后，把它改成自己的仓库：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-12-1"&gt;&lt;a class="lnlinks" href="#hl-12-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-12-2"&gt;&lt;a class="lnlinks" href="#hl-12-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-12-3"&gt;&lt;a class="lnlinks" href="#hl-12-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[submodule &amp;#34;themes/blog-theme&amp;#34;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="na"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;themes/blog-theme
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;	url = https://github.com/yourname/blog-theme.git&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;然后同步子模块配置：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-13-1"&gt;&lt;a class="lnlinks" href="#hl-13-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git submodule sync themes/blog-theme
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;进入子模块目录，确认远程地址：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-14-1"&gt;&lt;a class="lnlinks" href="#hl-14-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-14-2"&gt;&lt;a class="lnlinks" href="#hl-14-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; themes/blog-theme
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git remote -v
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;如果地址还是旧的，可以改掉：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-15-1"&gt;&lt;a class="lnlinks" href="#hl-15-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git remote set-url origin https://github.com/yourname/blog-theme.git
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;之后，你对子模块的修改就提交到自己的 fork：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-16-1"&gt;&lt;a class="lnlinks" href="#hl-16-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-16-2"&gt;&lt;a class="lnlinks" href="#hl-16-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-16-3"&gt;&lt;a class="lnlinks" href="#hl-16-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git add .
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git commit -m &lt;span class="s2"&gt;&amp;#34;feat: customize blog theme&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git push
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;最后回到主仓库，把 &lt;code&gt;.gitmodules&lt;/code&gt; 的变化和子模块指针一起提交：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-17-1"&gt;&lt;a class="lnlinks" href="#hl-17-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-17-2"&gt;&lt;a class="lnlinks" href="#hl-17-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-17-3"&gt;&lt;a class="lnlinks" href="#hl-17-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ../..
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git add .gitmodules themes/blog-theme
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git commit -m &lt;span class="s2"&gt;&amp;#34;chore: use forked blog theme&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这样处理之后，关系就变得很清楚：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;myblog&lt;/code&gt; 仍然是主仓库&lt;/li&gt;
&lt;li&gt;&lt;code&gt;blog-theme&lt;/code&gt; 仍然是独立仓库&lt;/li&gt;
&lt;li&gt;只是主仓库引用的主题仓库，从原作者版本变成了我的 fork&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个方案的好处是，后续还可以同步上游。&lt;/p&gt;
&lt;p&gt;比如在自己的 fork 里保留原作者仓库地址：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-18-1"&gt;&lt;a class="lnlinks" href="#hl-18-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-18-2"&gt;&lt;a class="lnlinks" href="#hl-18-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git remote add upstream https://github.com/original/blog-theme.git
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git fetch upstream
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;以后原作者修了 bug，我可以选择性合并：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-19-1"&gt;&lt;a class="lnlinks" href="#hl-19-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git merge upstream/main
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;所以方案一的本质是：&lt;strong&gt;我接管了这个依赖，但没有抹掉它和上游的关系。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这适合大多数“基于开源项目做定制”的情况。&lt;/p&gt;
&lt;h2 id="方案二把子模块彻底并入主仓库"&gt;&lt;a href="#%e6%96%b9%e6%a1%88%e4%ba%8c%e6%8a%8a%e5%ad%90%e6%a8%a1%e5%9d%97%e5%bd%bb%e5%ba%95%e5%b9%b6%e5%85%a5%e4%b8%bb%e4%bb%93%e5%ba%93" class="header-anchor"&gt;&lt;/a&gt;方案二：把子模块彻底并入主仓库
&lt;/h2&gt;&lt;p&gt;另一个方向是：不要子模块了，直接把它变成主仓库里的普通目录。&lt;/p&gt;
&lt;p&gt;这个方案适合另一种情况：我已经不关心上游更新了，或者这份代码已经被我改得和原项目差异很大，再保留子模块只会增加理解成本。&lt;/p&gt;
&lt;p&gt;比如一个主题原本来自开源仓库，但我后来把目录结构、样式、组件、构建方式都改了一遍。这个时候它已经不太像原来的主题了。继续把它当成外部依赖，意义就不大。&lt;/p&gt;
&lt;p&gt;这时可以考虑把它并入主仓库。&lt;/p&gt;
&lt;p&gt;但要注意，这不是简单地删除 &lt;code&gt;.gitmodules&lt;/code&gt; 就结束了。&lt;/p&gt;
&lt;p&gt;子模块在 Git 里有自己的记录。要把它变成普通目录，需要做几件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;确认子模块代码已经完整拉下来&lt;/li&gt;
&lt;li&gt;从主仓库索引里移除子模块引用&lt;/li&gt;
&lt;li&gt;删除 &lt;code&gt;.gitmodules&lt;/code&gt; 里的对应配置&lt;/li&gt;
&lt;li&gt;去掉子模块目录里的 Git 元信息&lt;/li&gt;
&lt;li&gt;重新把这些文件作为普通文件加入主仓库&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;以 &lt;code&gt;themes/blog-theme&lt;/code&gt; 为例，先确认代码完整：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-20-1"&gt;&lt;a class="lnlinks" href="#hl-20-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git submodule update --init --recursive themes/blog-theme
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;然后从主仓库索引里移除它的子模块身份，但尽量保留工作区文件：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-21-1"&gt;&lt;a class="lnlinks" href="#hl-21-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git rm --cached themes/blog-theme
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;接着删除 &lt;code&gt;.gitmodules&lt;/code&gt; 里对应的这一段：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-22-1"&gt;&lt;a class="lnlinks" href="#hl-22-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-22-2"&gt;&lt;a class="lnlinks" href="#hl-22-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-22-3"&gt;&lt;a class="lnlinks" href="#hl-22-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[submodule &amp;#34;themes/blog-theme&amp;#34;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="na"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;themes/blog-theme
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;	url = https://github.com/example/blog-theme.git&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;然后检查 &lt;code&gt;themes/blog-theme/&lt;/code&gt; 里是否还有 &lt;code&gt;.git&lt;/code&gt; 文件或目录。如果有，就删除它。否则 Git 仍然会把它当成一个嵌套仓库，而不是普通目录。&lt;/p&gt;
&lt;p&gt;最后重新加入主仓库：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-23-1"&gt;&lt;a class="lnlinks" href="#hl-23-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-23-2"&gt;&lt;a class="lnlinks" href="#hl-23-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git add .gitmodules themes/blog-theme
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git commit -m &lt;span class="s2"&gt;&amp;#34;chore: vendor blog theme into repository&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;完成之后，&lt;code&gt;themes/blog-theme/&lt;/code&gt; 就和 &lt;code&gt;content/&lt;/code&gt; 一样，成为主仓库直接管理的普通目录。&lt;/p&gt;
&lt;p&gt;这个方案的好处是简单：以后 clone 项目不用再初始化子模块，改主题也不用在两个仓库之间来回切。&lt;/p&gt;
&lt;p&gt;但代价也很明确：你基本放弃了和原主题仓库的自然同步能力。以后原作者更新了代码，你不能再像子模块那样直接拉取某个上游提交，只能手工对比和合并。&lt;/p&gt;
&lt;p&gt;所以方案二的本质是：&lt;strong&gt;我不再把它当外部依赖，而是正式把它收进当前项目。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="两个方案怎么选"&gt;&lt;a href="#%e4%b8%a4%e4%b8%aa%e6%96%b9%e6%a1%88%e6%80%8e%e4%b9%88%e9%80%89" class="header-anchor"&gt;&lt;/a&gt;两个方案怎么选
&lt;/h2&gt;&lt;p&gt;我会按这个标准判断。&lt;/p&gt;
&lt;p&gt;如果这份代码还像一个独立项目，只是我需要在它基础上做一些定制，选方案一。&lt;/p&gt;
&lt;p&gt;比如主题还是主题，组件库还是组件库，只是我要改几个样式、加几个功能。这时 fork 一份，然后继续作为子模块引用，最稳。&lt;/p&gt;
&lt;p&gt;如果这份代码已经变成当前项目的一部分，不再准备跟随上游，选方案二。&lt;/p&gt;
&lt;p&gt;比如主题已经被改得面目全非，继续保留上游关系反而没有价值。那就把它并入主仓库，降低维护复杂度。&lt;/p&gt;
&lt;p&gt;如果暂时不确定，优先选方案一。&lt;/p&gt;
&lt;p&gt;因为 fork + 子模块保留了退路。后面如果真的不需要上游了，再并入主仓库也不迟。反过来，如果一开始就并入主仓库，后面再想恢复清晰的上游同步关系，会麻烦很多。&lt;/p&gt;
&lt;h2 id="回到当前项目"&gt;&lt;a href="#%e5%9b%9e%e5%88%b0%e5%bd%93%e5%89%8d%e9%a1%b9%e7%9b%ae" class="header-anchor"&gt;&lt;/a&gt;回到当前项目
&lt;/h2&gt;&lt;p&gt;所以，当前项目里出现 &lt;code&gt;.gitmodules&lt;/code&gt;，我现在会这样理解：&lt;/p&gt;
&lt;p&gt;它不是一个可有可无的配置文件，而是在告诉我：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt" id="hl-24-1"&gt;&lt;a class="lnlinks" href="#hl-24-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-24-2"&gt;&lt;a class="lnlinks" href="#hl-24-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-24-3"&gt;&lt;a class="lnlinks" href="#hl-24-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;这个项目有一部分内容来自另一个仓库。
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;主仓库不直接管理那部分文件。
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;主仓库只记录它应该使用那个仓库的哪一次提交。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这就是 Git 子模块真正要表达的东西。&lt;/p&gt;
&lt;p&gt;它解决的不是“怎么把目录嵌套起来”的问题，而是“主项目如何稳定引用另一个独立项目”的问题。&lt;/p&gt;
&lt;p&gt;对于博客项目来说，这个独立项目常常是主题。&lt;/p&gt;
&lt;p&gt;对于其他工程来说，它可能是 SDK、公共组件、算法库、插件、文档模板，或者任何需要独立维护、但又必须放进主项目一起使用的代码。&lt;/p&gt;
&lt;h2 id="总结"&gt;&lt;a href="#%e6%80%bb%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;总结
&lt;/h2&gt;&lt;p&gt;Git 子模块的核心可以压缩成一句话：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;主仓库不保存子模块的全部代码历史，只保存子模块的来源、路径，以及当前指向的提交。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;理解了这句话，很多现象就都说得通了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;为什么 clone 后还要初始化子模块&lt;/li&gt;
&lt;li&gt;为什么更新子模块后主仓库也有变化&lt;/li&gt;
&lt;li&gt;为什么直接在子模块里改代码不等于主仓库已经保存了这些改动&lt;/li&gt;
&lt;li&gt;为什么 fork 后再改，是更适合二次开发的方式&lt;/li&gt;
&lt;li&gt;为什么彻底并入主仓库，会失去和上游自然同步的能力&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以看到 &lt;code&gt;.gitmodules&lt;/code&gt; 时，不要只把它当成一个 Git 配置文件。&lt;/p&gt;
&lt;p&gt;它其实是在说明这个项目的依赖边界。&lt;/p&gt;
&lt;h2 id="参考资料"&gt;&lt;a href="#%e5%8f%82%e8%80%83%e8%b5%84%e6%96%99" class="header-anchor"&gt;&lt;/a&gt;参考资料
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://git-scm.com/book/en/v2/Git-Tools-Submodules" target="_blank" rel="noopener"
 &gt;Git Book: Submodules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://git-scm.com/docs/git-submodule" target="_blank" rel="noopener"
 &gt;git-submodule 官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.atlassian.com/git/tutorials/git-submodule" target="_blank" rel="noopener"
 &gt;Atlassian Git submodule tutorial&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>