<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Git on DK Blog</title><link>/tags/git/</link><description>Recent content in Git 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/git/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><item><title>已经 push 到远程的文件，再加入 .gitignore 还会生效吗？</title><link>/dkblog/021-gitignore-tracked-file/</link><pubDate>Mon, 08 Jun 2026 20:54:07 +0800</pubDate><guid>/dkblog/021-gitignore-tracked-file/</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 已经 push 到远程的文件，再加入 .gitignore 还会生效吗？" /&gt;&lt;h2 id="问题背景"&gt;&lt;a href="#%e9%97%ae%e9%a2%98%e8%83%8c%e6%99%af" class="header-anchor"&gt;&lt;/a&gt;问题背景
&lt;/h2&gt;&lt;p&gt;有时候我们会把某个本来不应该提交的文件 commit 并 push 到远程仓库，比如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;本地配置文件：&lt;code&gt;application-local.yml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;IDE 配置文件：&lt;code&gt;.idea/workspace.xml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;临时生成文件：&lt;code&gt;debug.log&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;编译产物：&lt;code&gt;dist/&lt;/code&gt;、&lt;code&gt;target/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;机器相关文件：&lt;code&gt;.env.local&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;等发现问题时，第一反应通常是：把这个文件写进 &lt;code&gt;.gitignore&lt;/code&gt;，然后再提交一次 &lt;code&gt;.gitignore&lt;/code&gt;，是不是远程仓库里的那个文件就不会有了？&lt;/p&gt;
&lt;p&gt;答案是：&lt;strong&gt;不会。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.gitignore&lt;/code&gt; 只对还没有被 Git 跟踪的文件生效。只要某个文件已经被 commit 过，它就是 tracked file。即使后来把它加入 &lt;code&gt;.gitignore&lt;/code&gt;，Git 仍然会继续跟踪它。&lt;/p&gt;
&lt;h2 id="直接结论"&gt;&lt;a href="#%e7%9b%b4%e6%8e%a5%e7%bb%93%e8%ae%ba" class="header-anchor"&gt;&lt;/a&gt;直接结论
&lt;/h2&gt;&lt;p&gt;如果文件已经被 commit 并 push 到远程：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;只修改 &lt;code&gt;.gitignore&lt;/code&gt;，远程仓库里的文件不会自动删除&lt;/li&gt;
&lt;li&gt;只修改 &lt;code&gt;.gitignore&lt;/code&gt;，本地文件也不会停止被 Git 跟踪&lt;/li&gt;
&lt;li&gt;后续继续修改这个文件，&lt;code&gt;git status&lt;/code&gt; 里仍然可能看到它&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;想让 Git 以后不再跟踪它，常见做法是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;把文件路径加入 &lt;code&gt;.gitignore&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;git rm --cached&lt;/code&gt; 把它从 Git 索引中移除，但保留本地文件&lt;/li&gt;
&lt;li&gt;commit 并 push 这次变更&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="正确处理方式远程删除本地保留"&gt;&lt;a href="#%e6%ad%a3%e7%a1%ae%e5%a4%84%e7%90%86%e6%96%b9%e5%bc%8f%e8%bf%9c%e7%a8%8b%e5%88%a0%e9%99%a4%e6%9c%ac%e5%9c%b0%e4%bf%9d%e7%95%99" class="header-anchor"&gt;&lt;/a&gt;正确处理方式：远程删除，本地保留
&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-0-1"&gt;&lt;a class="lnlinks" href="#hl-0-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;config/local.yml
&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;.gitignore&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-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;config/local.yml
&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-2-1"&gt;&lt;a class="lnlinks" href="#hl-2-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 config/local.yml
&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;从 Git 索引中删除这个文件&lt;/li&gt;
&lt;li&gt;不删除你本地工作区里的真实文件&lt;/li&gt;
&lt;/ul&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-3-1"&gt;&lt;a class="lnlinks" href="#hl-3-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-3-2"&gt;&lt;a class="lnlinks" href="#hl-3-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-3-3"&gt;&lt;a class="lnlinks" href="#hl-3-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 .gitignore
&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;stop tracking local config file&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;/p&gt;
&lt;ul&gt;
&lt;li&gt;远程仓库中这个文件会在新提交里被删除&lt;/li&gt;
&lt;li&gt;本地的 &lt;code&gt;config/local.yml&lt;/code&gt; 仍然存在&lt;/li&gt;
&lt;li&gt;因为 &lt;code&gt;.gitignore&lt;/code&gt; 已经包含它，后续 Git 不会再把它作为未跟踪文件提示出来&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="如果是目录怎么办"&gt;&lt;a href="#%e5%a6%82%e6%9e%9c%e6%98%af%e7%9b%ae%e5%bd%95%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;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-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;logs/
&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;.gitignore&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-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-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;logs/
&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-6-1"&gt;&lt;a class="lnlinks" href="#hl-6-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-6-2"&gt;&lt;a class="lnlinks" href="#hl-6-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-6-3"&gt;&lt;a class="lnlinks" href="#hl-6-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-6-4"&gt;&lt;a class="lnlinks" href="#hl-6-4"&gt;4&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 -r logs/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git add .gitignore
&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;stop tracking logs directory&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;-r&lt;/code&gt;，因为 &lt;code&gt;logs/&lt;/code&gt; 是目录。&lt;/p&gt;
&lt;h2 id="为什么-gitignore-不会影响已跟踪文件"&gt;&lt;a href="#%e4%b8%ba%e4%bb%80%e4%b9%88-gitignore-%e4%b8%8d%e4%bc%9a%e5%bd%b1%e5%93%8d%e5%b7%b2%e8%b7%9f%e8%b8%aa%e6%96%87%e4%bb%b6" class="header-anchor"&gt;&lt;/a&gt;为什么 .gitignore 不会影响已跟踪文件？
&lt;/h2&gt;&lt;p&gt;Git 判断一个文件是否要纳入版本管理，大致可以分成两类：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;已经被 Git 跟踪的文件&lt;/li&gt;
&lt;li&gt;还没有被 Git 跟踪的文件&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;.gitignore&lt;/code&gt; 管的是第二类，也就是 untracked file。&lt;/p&gt;
&lt;p&gt;比如你新建了一个 &lt;code&gt;debug.log&lt;/code&gt;，它还没有被 &lt;code&gt;git add&lt;/code&gt;，这时 &lt;code&gt;.gitignore&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-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-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;debug.log
&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;git status&lt;/code&gt; 就不会再提示这个文件。&lt;/p&gt;
&lt;p&gt;但如果 &lt;code&gt;debug.log&lt;/code&gt; 之前已经被提交过，Git 的索引里已经有它了。此时 &lt;code&gt;.gitignore&lt;/code&gt; 不会让 Git 自动忘记它。你必须显式告诉 Git：&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;/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 debug.log
&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;h2 id="如何判断文件是否已经被-git-跟踪"&gt;&lt;a href="#%e5%a6%82%e4%bd%95%e5%88%a4%e6%96%ad%e6%96%87%e4%bb%b6%e6%98%af%e5%90%a6%e5%b7%b2%e7%bb%8f%e8%a2%ab-git-%e8%b7%9f%e8%b8%aa" class="header-anchor"&gt;&lt;/a&gt;如何判断文件是否已经被 Git 跟踪？
&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-9-1"&gt;&lt;a class="lnlinks" href="#hl-9-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 ls-files config/local.yml
&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;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-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git status
&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;Changes not staged for commit&lt;/code&gt; 里，通常说明它已经是 tracked file。&lt;/p&gt;
&lt;p&gt;如果显示在 &lt;code&gt;Untracked files&lt;/code&gt; 里，说明它还没被跟踪，这时 &lt;code&gt;.gitignore&lt;/code&gt; 才能直接让它消失在状态列表中。&lt;/p&gt;
&lt;h2 id="常见误区"&gt;&lt;a href="#%e5%b8%b8%e8%a7%81%e8%af%af%e5%8c%ba" class="header-anchor"&gt;&lt;/a&gt;常见误区
&lt;/h2&gt;&lt;h3 id="误区一提交-gitignore-后远程文件会自动消失"&gt;&lt;a href="#%e8%af%af%e5%8c%ba%e4%b8%80%e6%8f%90%e4%ba%a4-gitignore-%e5%90%8e%e8%bf%9c%e7%a8%8b%e6%96%87%e4%bb%b6%e4%bc%9a%e8%87%aa%e5%8a%a8%e6%b6%88%e5%a4%b1" class="header-anchor"&gt;&lt;/a&gt;误区一：提交 .gitignore 后，远程文件会自动消失
&lt;/h3&gt;&lt;p&gt;不会。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.gitignore&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-11-1"&gt;&lt;a class="lnlinks" href="#hl-11-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 文件路径
&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;h3 id="误区二git-rm-cached-会删除本地文件"&gt;&lt;a href="#%e8%af%af%e5%8c%ba%e4%ba%8cgit-rm-cached-%e4%bc%9a%e5%88%a0%e9%99%a4%e6%9c%ac%e5%9c%b0%e6%96%87%e4%bb%b6" class="header-anchor"&gt;&lt;/a&gt;误区二：git rm &amp;ndash;cached 会删除本地文件
&lt;/h3&gt;&lt;p&gt;不会。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;git rm 文件路径&lt;/code&gt; 会删除本地文件，并把删除动作加入索引。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;git rm --cached 文件路径&lt;/code&gt; 只从索引中移除文件，本地工作区文件会保留。&lt;/p&gt;
&lt;p&gt;这也是处理本地配置文件时最常用的方式。&lt;/p&gt;
&lt;h3 id="误区三gitignore-能阻止所有人提交这个文件"&gt;&lt;a href="#%e8%af%af%e5%8c%ba%e4%b8%89gitignore-%e8%83%bd%e9%98%bb%e6%ad%a2%e6%89%80%e6%9c%89%e4%ba%ba%e6%8f%90%e4%ba%a4%e8%bf%99%e4%b8%aa%e6%96%87%e4%bb%b6" class="header-anchor"&gt;&lt;/a&gt;误区三：.gitignore 能阻止所有人提交这个文件
&lt;/h3&gt;&lt;p&gt;不完全能。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.gitignore&lt;/code&gt; 能让正常情况下的 &lt;code&gt;git add .&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-12-1"&gt;&lt;a class="lnlinks" href="#hl-12-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 add -f config/local.yml
&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;所以如果是敏感文件，比如密钥、token、数据库密码，不能只依赖 &lt;code&gt;.gitignore&lt;/code&gt;，还要配合权限控制、密钥扫描、CI 检查等手段。&lt;/p&gt;
&lt;h2 id="如果已经把敏感信息-push-出去了怎么办"&gt;&lt;a href="#%e5%a6%82%e6%9e%9c%e5%b7%b2%e7%bb%8f%e6%8a%8a%e6%95%8f%e6%84%9f%e4%bf%a1%e6%81%af-push-%e5%87%ba%e5%8e%bb%e4%ba%86%e6%80%8e%e4%b9%88%e5%8a%9e" class="header-anchor"&gt;&lt;/a&gt;如果已经把敏感信息 push 出去了怎么办？
&lt;/h2&gt;&lt;p&gt;如果误提交的是普通文件，比如日志、编译产物、本地配置模板，用 &lt;code&gt;git rm --cached&lt;/code&gt; 通常就够了。&lt;/p&gt;
&lt;p&gt;但如果误提交的是敏感信息，比如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;API token&lt;/li&gt;
&lt;li&gt;SSH 私钥&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;那就不能只删除文件了，因为敏感内容仍然存在于 Git 历史提交中。&lt;/p&gt;
&lt;p&gt;正确处理顺序应该是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;立刻废弃或轮换已经泄露的密钥&lt;/li&gt;
&lt;li&gt;从当前版本中删除敏感文件&lt;/li&gt;
&lt;li&gt;把敏感文件加入 &lt;code&gt;.gitignore&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;如有必要，使用 &lt;code&gt;git filter-repo&lt;/code&gt; 或 BFG 清理历史记录&lt;/li&gt;
&lt;li&gt;强制推送清理后的历史&lt;/li&gt;
&lt;li&gt;通知所有协作者重新拉取或重新 clone 仓库&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;也就是说：&lt;strong&gt;删除当前版本里的文件，不等于删除历史里的泄露内容。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="如果我想远程保留本地以后不提交改动呢"&gt;&lt;a href="#%e5%a6%82%e6%9e%9c%e6%88%91%e6%83%b3%e8%bf%9c%e7%a8%8b%e4%bf%9d%e7%95%99%e6%9c%ac%e5%9c%b0%e4%bb%a5%e5%90%8e%e4%b8%8d%e6%8f%90%e4%ba%a4%e6%94%b9%e5%8a%a8%e5%91%a2" class="header-anchor"&gt;&lt;/a&gt;如果我想远程保留，本地以后不提交改动呢？
&lt;/h2&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-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;config/dev.yml
&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;git status&lt;/code&gt; 里看到它。&lt;/p&gt;
&lt;p&gt;这种情况不适合靠 &lt;code&gt;.gitignore&lt;/code&gt;，因为文件已经是 tracked file。&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-14-1"&gt;&lt;a class="lnlinks" href="#hl-14-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 update-index --skip-worktree config/dev.yml
&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 update-index --no-skip-worktree config/dev.yml
&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-16-1"&gt;&lt;a class="lnlinks" href="#hl-16-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 update-index --assume-unchanged config/dev.yml
&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;assume-unchanged&lt;/code&gt; 更偏性能优化，告诉 Git 暂时假设这个文件没变&lt;/li&gt;
&lt;li&gt;&lt;code&gt;skip-worktree&lt;/code&gt; 更偏本地覆盖场景，告诉 Git 尽量不要关心工作区里的这个文件&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;日常如果是“仓库保留默认配置，本地想长期改成自己的配置”，一般优先考虑 &lt;code&gt;skip-worktree&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;不过这个方案只影响你本地，不会影响其他人，也不会改变远程仓库。&lt;/p&gt;
&lt;h2 id="推荐实践"&gt;&lt;a href="#%e6%8e%a8%e8%8d%90%e5%ae%9e%e8%b7%b5" class="header-anchor"&gt;&lt;/a&gt;推荐实践
&lt;/h2&gt;&lt;p&gt;对于不应该提交的本地文件，推荐一开始就做好这几件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;把真实本地配置加入 &lt;code&gt;.gitignore&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;提供一个可提交的模板文件，比如 &lt;code&gt;.env.example&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;文档里说明如何复制模板并填写本地值&lt;/li&gt;
&lt;/ol&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-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;/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;.env
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.env.local
&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-18-1"&gt;&lt;a class="lnlinks" href="#hl-18-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;.env.example
&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;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;已经 commit 或 push 过的文件，再加入 &lt;code&gt;.gitignore&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-19-1"&gt;&lt;a class="lnlinks" href="#hl-19-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-19-2"&gt;&lt;a class="lnlinks" href="#hl-19-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-19-3"&gt;&lt;a class="lnlinks" href="#hl-19-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-19-4"&gt;&lt;a class="lnlinks" href="#hl-19-4"&gt;4&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 文件路径
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git add .gitignore
&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;stop tracking ignored file&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;-r&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 rm --cached -r 目录路径
&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;strong&gt;&lt;code&gt;.gitignore&lt;/code&gt; 只负责忽略未跟踪文件，不能让 Git 自动忘记已经跟踪过的文件。&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Git rebase 和 merge：我在项目里怎么取舍</title><link>/dkblog/030-git-rebase-merge-workflow/</link><pubDate>Tue, 03 Feb 2026 22:16:00 +0800</pubDate><guid>/dkblog/030-git-rebase-merge-workflow/</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 rebase 和 merge：我在项目里怎么取舍" /&gt;&lt;p&gt;我以前对 &lt;code&gt;rebase&lt;/code&gt; 和 &lt;code&gt;merge&lt;/code&gt; 的态度比较摇摆。&lt;/p&gt;
&lt;p&gt;刚开始写项目的时候，看到提交历史里一堆 &lt;code&gt;Merge branch 'main' into feature/xxx&lt;/code&gt; 会觉得很乱，于是就偏向 &lt;code&gt;rebase&lt;/code&gt;。后来在团队分支上踩过几次坑，又开始觉得 &lt;code&gt;merge&lt;/code&gt; 稳一点。&lt;/p&gt;
&lt;p&gt;现在我的习惯比较简单：&lt;strong&gt;自己的开发分支可以 rebase，已经给别人用了的分支尽量不要 rebase；合入主干时按项目约定来，不强行追求一种方式。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这件事我现在不再按“哪个更高级”来判断。rebase 和 merge 的区别不是水平高低，而是你愿不愿意改写历史、你需不需要保留分支关系、团队能不能接受这种历史形态。&lt;/p&gt;
&lt;h2 id="merge-保留真实合并关系"&gt;&lt;a href="#merge-%e4%bf%9d%e7%95%99%e7%9c%9f%e5%ae%9e%e5%90%88%e5%b9%b6%e5%85%b3%e7%b3%bb" class="header-anchor"&gt;&lt;/a&gt;merge 保留真实合并关系
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;merge&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-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;span class="lnt" id="hl-0-6"&gt;&lt;a class="lnlinks" href="#hl-0-6"&gt;6&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 checkout main
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git pull origin main
&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;git checkout -b feature/login
&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;add login api&amp;#34;&lt;/span&gt;
&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;add login middleware&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;main&lt;/code&gt; 上别人也提交了东西，我想把最新 &lt;code&gt;main&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;span class="lnt" id="hl-1-2"&gt;&lt;a class="lnlinks" href="#hl-1-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-1-3"&gt;&lt;a class="lnlinks" href="#hl-1-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 checkout feature/login
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git fetch origin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git merge origin/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;如果两个分支都有新提交，通常会产生一个 merge commit。这个 merge commit 有时候看起来碍眼，但它也有价值：它明确记录了“这个分支是在这里和 main 合过一次”。&lt;/p&gt;
&lt;p&gt;我一般会在这些场景用 &lt;code&gt;merge&lt;/code&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;公共分支，比如 &lt;code&gt;main&lt;/code&gt;、&lt;code&gt;develop&lt;/code&gt;、&lt;code&gt;release/*&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;多个人一起开发的长期 feature 分支。&lt;/li&gt;
&lt;li&gt;需要保留上下文的功能分支。&lt;/li&gt;
&lt;li&gt;项目本身要求使用 merge commit 保留 PR 合入记录。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;缺点也明显：如果一个分支开发时间很长，又频繁 &lt;code&gt;merge main&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-2-1"&gt;&lt;a class="lnlinks" href="#hl-2-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 log --oneline --graph --decorate --all
&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;所以我现在不会无脑 merge。短分支没必要反复 merge main，真有需要再同步。&lt;/p&gt;
&lt;h2 id="rebase-适合整理自己的分支"&gt;&lt;a href="#rebase-%e9%80%82%e5%90%88%e6%95%b4%e7%90%86%e8%87%aa%e5%b7%b1%e7%9a%84%e5%88%86%e6%94%af" class="header-anchor"&gt;&lt;/a&gt;rebase 适合整理自己的分支
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;rebase&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;span class="lnt" id="hl-3-2"&gt;&lt;a class="lnlinks" href="#hl-3-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-3-3"&gt;&lt;a class="lnlinks" href="#hl-3-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 checkout feature/login
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git fetch origin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git rebase origin/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;/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;span class="lnt" id="hl-4-2"&gt;&lt;a class="lnlinks" href="#hl-4-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-4-3"&gt;&lt;a class="lnlinks" href="#hl-4-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;main: A---B---C
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;feature before: \---D---E
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;feature after: \---D&amp;#39;---E&amp;#39;
&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;D'&lt;/code&gt;、&lt;code&gt;E'&lt;/code&gt; 不是原来的提交了。内容可能一样，但提交哈希会变，因为父提交变了。&lt;/p&gt;
&lt;p&gt;我一般会在这些场景用 &lt;code&gt;rebase&lt;/code&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;只有我自己在用的本地分支。&lt;/li&gt;
&lt;li&gt;PR 提交前整理提交历史。&lt;/li&gt;
&lt;li&gt;把多个临时提交 squash 成更容易 review 的提交。&lt;/li&gt;
&lt;li&gt;避免 feature 分支里出现一堆没意义的同步主干 merge commit。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;比如整理最近 3 个提交：&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 rebase -i HEAD~3
&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;pick&lt;/code&gt; 改成 &lt;code&gt;squash&lt;/code&gt; 或 &lt;code&gt;fixup&lt;/code&gt;。这对代码评审会友好很多。&lt;/p&gt;
&lt;p&gt;但 rebase 的代价也在这里：它让历史更好看，同时也改变了提交身份。只要这个分支已经被别人拉走，改写历史就不再只是你自己的事。&lt;/p&gt;
&lt;h2 id="我的实际取舍"&gt;&lt;a href="#%e6%88%91%e7%9a%84%e5%ae%9e%e9%99%85%e5%8f%96%e8%88%8d" class="header-anchor"&gt;&lt;/a&gt;我的实际取舍
&lt;/h2&gt;&lt;p&gt;第一，&lt;strong&gt;本地分支优先 rebase&lt;/strong&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;span class="lnt" id="hl-6-2"&gt;&lt;a class="lnlinks" href="#hl-6-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-6-3"&gt;&lt;a class="lnlinks" href="#hl-6-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 checkout feature/foo
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git fetch origin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git rebase origin/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;如果冲突不多，rebase 后历史干净，后面提 PR 也舒服。&lt;/p&gt;
&lt;p&gt;第二，&lt;strong&gt;公共分支不要随便 rebase&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;比如一个分支已经推上去了，而且别人也基于它提交了代码，这时候 rebase 会改写历史。你本地看着清爽了，别人那边可能就开始分叉、冲突、重复提交。&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-7-1"&gt;&lt;a class="lnlinks" href="#hl-7-1"&gt;1&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-7-2"&gt;&lt;a class="lnlinks" href="#hl-7-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-7-3"&gt;&lt;a class="lnlinks" href="#hl-7-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 checkout shared/feature
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git fetch origin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git merge origin/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;有的项目喜欢 merge commit，因为能看出每个 PR 的边界；有的项目喜欢 squash merge，让主干历史每个提交都对应一个完整变更；也有项目要求 rebase 后 fast-forward。&lt;/p&gt;
&lt;p&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-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;span class="lnt" id="hl-8-3"&gt;&lt;a class="lnlinks" href="#hl-8-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-8-4"&gt;&lt;a class="lnlinks" href="#hl-8-4"&gt;4&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;个人 feature 分支：允许 rebase，提 PR 前整理
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;多人共享分支：使用 merge，同步主干时不改写历史
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;主干合入：小 PR squash，大 PR 保留 merge commit
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;release 分支：尽量保留真实历史，方便追溯
&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;这不是标准答案，但比“所有情况都 rebase”或“所有情况都 merge”更接近实际协作。&lt;/p&gt;
&lt;h2 id="冲突和-force-push"&gt;&lt;a href="#%e5%86%b2%e7%aa%81%e5%92%8c-force-push" class="header-anchor"&gt;&lt;/a&gt;冲突和 force push
&lt;/h2&gt;&lt;p&gt;merge 冲突时：&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;span class="lnt" id="hl-9-4"&gt;&lt;a class="lnlinks" href="#hl-9-4"&gt;4&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-9-5"&gt;&lt;a class="lnlinks" href="#hl-9-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;git merge origin/main
&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;git status
&lt;/span&gt;&lt;/span&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
&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-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-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git merge --abort
&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;rebase 冲突时：&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;span class="lnt" id="hl-11-4"&gt;&lt;a class="lnlinks" href="#hl-11-4"&gt;4&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-11-5"&gt;&lt;a class="lnlinks" href="#hl-11-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;git rebase origin/main
&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;git status
&lt;/span&gt;&lt;/span&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 rebase --continue
&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-12-1"&gt;&lt;a class="lnlinks" href="#hl-12-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 rebase --abort
&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;rebase 可能会让你连续处理多次冲突，因为它是按提交逐个重放。merge 通常是在一次合并里处理最终结果。&lt;/p&gt;
&lt;p&gt;rebase 之后，如果这个分支之前已经 push 到远端，再 push 通常会失败：&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 push origin feature/login
&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;/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 push --force-with-lease origin feature/login
&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;git push --force&lt;/code&gt;。&lt;code&gt;--force-with-lease&lt;/code&gt; 会多做一层检查：如果远端分支已经被别人更新过，它不会直接覆盖。&lt;/p&gt;
&lt;p&gt;风险提醒写直白一点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不要 rebase &lt;code&gt;main&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;不要 rebase 别人正在基于它开发的分支。&lt;/li&gt;
&lt;li&gt;不要在没看 &lt;code&gt;git log&lt;/code&gt;、&lt;code&gt;git status&lt;/code&gt; 的情况下 force push。&lt;/li&gt;
&lt;li&gt;rebase 前如果心里没底，可以先打个临时备份分支。&lt;/li&gt;
&lt;/ul&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 branch backup/feature-login-before-rebase
&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;merge&lt;/code&gt;；想整理自己的提交历史，用 &lt;code&gt;rebase&lt;/code&gt;；分支已经共享出去，就谨慎 rebase。&lt;/p&gt;
&lt;p&gt;最后再补一句：Git 历史清爽当然好，但它首先要可恢复。每次准备 rebase 已经 push 过的分支前，我都会先确认 &lt;code&gt;git status&lt;/code&gt; 是干净的，再看一眼 &lt;code&gt;git log --oneline --graph&lt;/code&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/docs/git-merge" target="_blank" rel="noopener"
 &gt;git merge 官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://git-scm.com/docs/git-rebase" target="_blank" rel="noopener"
 &gt;git rebase 官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://git-scm.com/docs/git-push" target="_blank" rel="noopener"
 &gt;git push 官方文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://git-scm.com/book/en/v2/Git-Branching-Rebasing" target="_blank" rel="noopener"
 &gt;Pro Git: Rebasing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Git LFS 大文件管理指南</title><link>/dkblog/015-git-lfs/</link><pubDate>Tue, 15 Jul 2025 10:29:29 +0800</pubDate><guid>/dkblog/015-git-lfs/</guid><description>&lt;img src="https://images.unsplash.com/photo-1556075798-4825dfaaf498?auto=format&amp;fit=crop&amp;w=900&amp;q=60" alt="Featured image of post Git LFS 大文件管理指南" /&gt;&lt;h1 id="git-lfs-完全指南轻松管理-git-仓库中的大文件"&gt;&lt;a href="#git-lfs-%e5%ae%8c%e5%85%a8%e6%8c%87%e5%8d%97%e8%bd%bb%e6%9d%be%e7%ae%a1%e7%90%86-git-%e4%bb%93%e5%ba%93%e4%b8%ad%e7%9a%84%e5%a4%a7%e6%96%87%e4%bb%b6" class="header-anchor"&gt;&lt;/a&gt;Git LFS 完全指南：轻松管理 Git 仓库中的大文件
&lt;/h1&gt;&lt;p&gt;在日常开发中，你是否遇到过这样的问题：Git 仓库因为几张设计图、一个安装包而体积暴增，克隆代码需要几十分钟，&lt;code&gt;git status&lt;/code&gt; 命令卡顿严重？如果你正在被大文件拖累 Git 仓库性能，那么 Git LFS 就是你的救星。&lt;/p&gt;
&lt;h2 id="什么是-git-lfs"&gt;&lt;a href="#%e4%bb%80%e4%b9%88%e6%98%af-git-lfs" class="header-anchor"&gt;&lt;/a&gt;什么是 Git LFS？
&lt;/h2&gt;&lt;p&gt;Git LFS（Large File Storage，大文件存储）是 Git 的一个扩展工具，专门为解决 Git 对大文件支持不足的问题而设计。它的核心思路是：&lt;strong&gt;用轻量的「指针文件」替代实际大文件存储在 Git 仓库中，而真正的大文件则单独存储在 LFS 服务器&lt;/strong&gt;，从而避免 Git 仓库体积膨胀，提升操作效率。&lt;/p&gt;
&lt;h2 id="为什么需要-git-lfs"&gt;&lt;a href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e9%9c%80%e8%a6%81-git-lfs" class="header-anchor"&gt;&lt;/a&gt;为什么需要 Git LFS？
&lt;/h2&gt;&lt;p&gt;Git 本身的设计更适合管理文本文件（如代码、配置文件），因为文本文件体积小，且 Git 能通过「差异对比」（只记录修改部分）高效存储版本历史。但面对图片、视频、安装包等大文件时，Git 会暴露明显缺陷：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;仓库体积爆炸&lt;/strong&gt;：Git 会完整保存大文件的每一个版本，几次修改后仓库可能从 MB 级膨胀到 GB 级，克隆/拉取速度极慢。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;操作卡顿&lt;/strong&gt;：&lt;code&gt;git status&lt;/code&gt;、&lt;code&gt;git commit&lt;/code&gt; 等命令需要扫描文件，大文件会显著拖慢这些操作。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;存储浪费&lt;/strong&gt;：重复存储大文件的多个版本，占用大量本地和远程存储空间。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;而 Git LFS 正是为解决这些问题而生——它让 Git 既能管理大文件的版本，又不用承受大文件带来的性能负担。&lt;/p&gt;
&lt;h2 id="git-lfs-工作原理"&gt;&lt;a href="#git-lfs-%e5%b7%a5%e4%bd%9c%e5%8e%9f%e7%90%86" class="header-anchor"&gt;&lt;/a&gt;Git LFS 工作原理
&lt;/h2&gt;&lt;p&gt;Git LFS 的核心是「分离存储」，简单来说分为三步：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;用指针替代大文件&lt;/strong&gt;&lt;br&gt;
当你用 LFS 跟踪大文件时，Git 不会把实际文件存入仓库，而是生成一个几百字节的「指针文件」（包含大文件的唯一哈希和大小），指针文件会被正常提交到 Git 仓库。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;大文件单独存储&lt;/strong&gt;&lt;br&gt;
实际的大文件会被上传到 LFS 服务器（如 GitHub、GitLab 的 LFS 服务），并通过哈希值与指针文件关联。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;按需下载大文件&lt;/strong&gt;&lt;br&gt;
其他人克隆仓库时，先下载轻量的指针文件，再通过 LFS 工具自动从服务器下载对应的实际大文件（仅在需要时下载）。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="git-lfs-安装与初始化"&gt;&lt;a href="#git-lfs-%e5%ae%89%e8%a3%85%e4%b8%8e%e5%88%9d%e5%a7%8b%e5%8c%96" class="header-anchor"&gt;&lt;/a&gt;Git LFS 安装与初始化
&lt;/h2&gt;&lt;h3 id="1-安装-git-lfs"&gt;&lt;a href="#1-%e5%ae%89%e8%a3%85-git-lfs" class="header-anchor"&gt;&lt;/a&gt;1. 安装 Git LFS
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Windows&lt;/strong&gt;：从 &lt;a class="link" href="https://git-lfs.com/" target="_blank" rel="noopener"
 &gt;Git LFS 官网&lt;/a&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;/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;choco install git-lfs &lt;span class="c1"&gt;# Chocolatey&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;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;macOS&lt;/strong&gt;：使用 Homebrew：&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;brew install git-lfs
&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;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Linux&lt;/strong&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;span class="lnt" id="hl-2-4"&gt;&lt;a class="lnlinks" href="#hl-2-4"&gt;4&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="c1"&gt;# Debian/Ubuntu&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo apt-get install git-lfs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# CentOS/RHEL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo yum install git-lfs
&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;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-初始化-git-lfs"&gt;&lt;a href="#2-%e5%88%9d%e5%a7%8b%e5%8c%96-git-lfs" class="header-anchor"&gt;&lt;/a&gt;2. 初始化 Git LFS
&lt;/h3&gt;&lt;p&gt;安装后，需要在仓库中初始化 LFS（每个仓库只需执行一次）：&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-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git lfs install
&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;br&gt;
&lt;code&gt;Git LFS initialized.&lt;/code&gt;&lt;/p&gt;
&lt;h2 id="git-lfs-核心操作指南"&gt;&lt;a href="#git-lfs-%e6%a0%b8%e5%bf%83%e6%93%8d%e4%bd%9c%e6%8c%87%e5%8d%97" class="header-anchor"&gt;&lt;/a&gt;Git LFS 核心操作指南
&lt;/h2&gt;&lt;h3 id="1-跟踪大文件"&gt;&lt;a href="#1-%e8%b7%9f%e8%b8%aa%e5%a4%a7%e6%96%87%e4%bb%b6" class="header-anchor"&gt;&lt;/a&gt;1. 跟踪大文件
&lt;/h3&gt;&lt;p&gt;Git LFS 不会自动识别大文件，需要手动配置「跟踪规则」（通过文件类型或路径匹配）。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;跟踪特定类型的文件（如所有 &lt;code&gt;.zip&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-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-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git lfs track &lt;span class="s2"&gt;&amp;#34;*.zip&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;/li&gt;
&lt;li&gt;
&lt;p&gt;跟踪指定目录下的文件（如 &lt;code&gt;assets/&lt;/code&gt; 目录下的所有 &lt;code&gt;.png&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-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 lfs track &lt;span class="s2"&gt;&amp;#34;assets/*.png&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;/li&gt;
&lt;li&gt;
&lt;p&gt;跟踪单个特定文件（如 &lt;code&gt;installer.exe&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-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 lfs track &lt;span class="s2"&gt;&amp;#34;installer.exe&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;/li&gt;
&lt;/ul&gt;
&lt;p&gt;执行上述命令后，仓库会生成/更新 &lt;code&gt;.gitattributes&lt;/code&gt; 文件，其中记录了 LFS 跟踪规则（需提交到仓库，确保团队成员同步配置）。&lt;/p&gt;
&lt;h3 id="2-查看跟踪规则"&gt;&lt;a href="#2-%e6%9f%a5%e7%9c%8b%e8%b7%9f%e8%b8%aa%e8%a7%84%e5%88%99" class="header-anchor"&gt;&lt;/a&gt;2. 查看跟踪规则
&lt;/h3&gt;&lt;p&gt;检查当前 LFS 跟踪的文件类型：&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 lfs track
&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-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;span class="lnt" id="hl-8-3"&gt;&lt;a class="lnlinks" href="#hl-8-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-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;assets/*.png (.gitattributes)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;*.zip (.gitattributes)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;installer.exe (.gitattributes)
&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;h3 id="3-取消跟踪文件"&gt;&lt;a href="#3-%e5%8f%96%e6%b6%88%e8%b7%9f%e8%b8%aa%e6%96%87%e4%bb%b6" class="header-anchor"&gt;&lt;/a&gt;3. 取消跟踪文件
&lt;/h3&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;/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 lfs untrack &lt;span class="s2"&gt;&amp;#34;*.zip&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;h3 id="4-正常提交与推送"&gt;&lt;a href="#4-%e6%ad%a3%e5%b8%b8%e6%8f%90%e4%ba%a4%e4%b8%8e%e6%8e%a8%e9%80%81" class="header-anchor"&gt;&lt;/a&gt;4. 正常提交与推送
&lt;/h3&gt;&lt;p&gt;配置跟踪规则后，日常操作与普通 Git 完全一致：&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;span class="lnt" id="hl-10-2"&gt;&lt;a class="lnlinks" href="#hl-10-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-10-3"&gt;&lt;a class="lnlinks" href="#hl-10-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-10-4"&gt;&lt;a class="lnlinks" href="#hl-10-4"&gt;4&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-10-5"&gt;&lt;a class="lnlinks" href="#hl-10-5"&gt;5&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-10-6"&gt;&lt;a class="lnlinks" href="#hl-10-6"&gt;6&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="c1"&gt;# 添加文件（LFS 会自动处理跟踪的大文件）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git add 大文件.zip
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 提交&lt;/span&gt;
&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;添加大文件&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 推送（LFS 会自动上传实际大文件到 LFS 服务器）&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;h3 id="5-拉取-lfs-跟踪的文件"&gt;&lt;a href="#5-%e6%8b%89%e5%8f%96-lfs-%e8%b7%9f%e8%b8%aa%e7%9a%84%e6%96%87%e4%bb%b6" class="header-anchor"&gt;&lt;/a&gt;5. 拉取 LFS 跟踪的文件
&lt;/h3&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;span class="lnt" id="hl-11-4"&gt;&lt;a class="lnlinks" href="#hl-11-4"&gt;4&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-11-5"&gt;&lt;a class="lnlinks" href="#hl-11-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;&lt;span class="c1"&gt;# 克隆仓库&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git clone &amp;lt;仓库地址&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; &amp;lt;仓库目录&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 拉取 LFS 跟踪的实际文件&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git lfs 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;如果希望克隆时自动拉取 LFS 文件，可以直接使用：&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;/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 lfs clone &amp;lt;仓库地址&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;h3 id="6-查看-lfs-跟踪的文件列表"&gt;&lt;a href="#6-%e6%9f%a5%e7%9c%8b-lfs-%e8%b7%9f%e8%b8%aa%e7%9a%84%e6%96%87%e4%bb%b6%e5%88%97%e8%a1%a8" class="header-anchor"&gt;&lt;/a&gt;6. 查看 LFS 跟踪的文件列表
&lt;/h3&gt;&lt;p&gt;检查当前仓库中被 LFS 跟踪的文件：&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 lfs ls-files
&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-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;abc12345 * 大文件.zip
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;def67890 * assets/image.png
&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;h2 id="常见问题与解决方案"&gt;&lt;a href="#%e5%b8%b8%e8%a7%81%e9%97%ae%e9%a2%98%e4%b8%8e%e8%a7%a3%e5%86%b3%e6%96%b9%e6%a1%88" class="header-anchor"&gt;&lt;/a&gt;常见问题与解决方案
&lt;/h2&gt;&lt;h3 id="1-拉取后大文件变成-100-200b-的小文件"&gt;&lt;a href="#1-%e6%8b%89%e5%8f%96%e5%90%8e%e5%a4%a7%e6%96%87%e4%bb%b6%e5%8f%98%e6%88%90-100-200b-%e7%9a%84%e5%b0%8f%e6%96%87%e4%bb%b6" class="header-anchor"&gt;&lt;/a&gt;1. 拉取后大文件变成 100-200B 的小文件？
&lt;/h3&gt;&lt;p&gt;这是因为拉取的是 LFS 指针文件（而非实际文件），执行 &lt;code&gt;git lfs pull&lt;/code&gt; 即可下载实际内容。&lt;/p&gt;
&lt;h3 id="2-推送时提示-lfs-文件未上传"&gt;&lt;a href="#2-%e6%8e%a8%e9%80%81%e6%97%b6%e6%8f%90%e7%a4%ba-lfs-%e6%96%87%e4%bb%b6%e6%9c%aa%e4%b8%8a%e4%bc%a0" class="header-anchor"&gt;&lt;/a&gt;2. 推送时提示 LFS 文件未上传？
&lt;/h3&gt;&lt;p&gt;可能是本地 LFS 未初始化，或推送时网络问题。解决方案：&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;span class="lnt" id="hl-15-2"&gt;&lt;a class="lnlinks" href="#hl-15-2"&gt;2&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-15-3"&gt;&lt;a class="lnlinks" href="#hl-15-3"&gt;3&lt;/a&gt;
&lt;/span&gt;&lt;span class="lnt" id="hl-15-4"&gt;&lt;a class="lnlinks" href="#hl-15-4"&gt;4&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="c1"&gt;# 重新初始化 LFS&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git lfs install
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 手动上传 LFS 文件&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git lfs push --all origin 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;h3 id="3-如何迁移已存在的大文件到-lfs"&gt;&lt;a href="#3-%e5%a6%82%e4%bd%95%e8%bf%81%e7%a7%bb%e5%b7%b2%e5%ad%98%e5%9c%a8%e7%9a%84%e5%a4%a7%e6%96%87%e4%bb%b6%e5%88%b0-lfs" class="header-anchor"&gt;&lt;/a&gt;3. 如何迁移已存在的大文件到 LFS？
&lt;/h3&gt;&lt;p&gt;如果仓库历史中已有大文件，可以用 &lt;code&gt;git lfs migrate&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-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;/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="c1"&gt;# 将所有 .zip 文件迁移到 LFS（针对 master 分支）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git lfs migrate import --include&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;*.zip&amp;#34;&lt;/span&gt; --everything
&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;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 LFS 是管理大文件的利器，它通过「指针-实际文件分离存储」的设计，解决了 Git 仓库因大文件导致的体积膨胀、操作卡顿等问题。只需简单配置跟踪规则，就能在不改变原有 Git 工作流的前提下，高效管理图片、视频、安装包等大文件。&lt;/p&gt;
&lt;p&gt;如果你经常在项目中处理大文件，不妨尝试 Git LFS——让你的 Git 仓库保持轻盈，操作如丝般顺滑。&lt;/p&gt;</description></item></channel></rss>