<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Bisect on heyaohua's Blog</title><link>https://blog.heyaohua.com/tags/bisect/</link><description>Recent content in Bisect on heyaohua's Blog</description><image><title>heyaohua's Blog</title><url>https://blog.heyaohua.com/og-image.png</url><link>https://blog.heyaohua.com/og-image.png</link></image><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Sat, 22 Aug 2026 23:23:00 +0800</lastBuildDate><atom:link href="https://blog.heyaohua.com/tags/bisect/index.xml" rel="self" type="application/rss+xml"/><item><title>Git 使用手册 · 高级篇：原理、调试与仓库工程</title><link>https://blog.heyaohua.com/posts/2026/08/git-handbook-advanced/</link><pubDate>Sat, 22 Aug 2026 23:23:00 +0800</pubDate><guid>https://blog.heyaohua.com/posts/2026/08/git-handbook-advanced/</guid><description>Git 高级手册，深入对象模型与引用、bisect 二分排错、worktree 多工作区、submodule 与 subtree、hooks 自动化、大文件 LFS 与仓库瘦身。</description><content:encoded><![CDATA[<p>这是 Git 手册系列的第三篇。前两篇讲&quot;怎么用&quot;，本篇讲&quot;为什么&quot;：理解了 Git 的内部模型，所有命令都会变成推理题而非背诵题；再配上一批工程化工具，解决&quot;什么时候出的 bug&quot;、&ldquo;仓库太大&rdquo;、&ldquo;多仓库依赖&quot;这类真实难题。</p>
<hr>
<h2 id="目录">目录</h2>
<ul>
<li>Git 对象模型：blob、tree、commit、tag</li>
<li>引用：分支、HEAD、tag 都只是指针</li>
<li>彻底理解 merge / rebase / cherry-pick</li>
<li>bisect：二分查找&quot;哪次提交引入了 bug&rdquo;</li>
<li>worktree：一个仓库多个工作区</li>
<li>submodule 与 subtree：仓库套仓库</li>
<li>hooks：在关键时刻自动执行脚本</li>
<li>Git LFS 与大文件</li>
<li>仓库瘦身：清理历史中的大文件</li>
<li>高级调试与诊断</li>
</ul>
<hr>
<h2 id="git-对象模型blobtreecommittag">Git 对象模型：blob、tree、commit、tag</h2>
<p>Git 本质是一个<strong>内容寻址的文件系统</strong> + 一个版本控制外壳。<code>.git/objects/</code> 下只有四种对象：</p>
<table>
	<thead>
			<tr>
					<th>对象</th>
					<th>内容</th>
					<th>类比</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><strong>blob</strong></td>
					<td>文件的内容（不含文件名）</td>
					<td>文件快照</td>
			</tr>
			<tr>
					<td><strong>tree</strong></td>
					<td>一个目录：文件名 → blob/tree 的指针列表</td>
					<td>文件夹快照</td>
			</tr>
			<tr>
					<td><strong>commit</strong></td>
					<td>指向一个 tree + 父 commit 指针 + 作者/时间/信息</td>
					<td>一次版本</td>
			</tr>
			<tr>
					<td><strong>tag</strong>（附注）</td>
					<td>指向一个 commit + 标签信息</td>
					<td>里程碑</td>
			</tr>
	</tbody>
</table>
<p>所有对象用内容算 SHA-1（新版支持 SHA-256）哈希作为 id：<strong>内容相同 → id 相同</strong>，这就是 Git 去重和完整性校验的原理。</p>
<p>一次提交的结构示意：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>commit a1b2c3d
</span></span><span style="display:flex;"><span>├── tree     e4f5g6h          ← 项目根目录快照
</span></span><span style="display:flex;"><span>│   ├── src/        → tree ...
</span></span><span style="display:flex;"><span>│   ├── README.md   → blob ...
</span></span><span style="display:flex;"><span>│   └── package.json→ blob ...
</span></span><span style="display:flex;"><span>├── parent   9i0j1k2          ← 上一次提交
</span></span><span style="display:flex;"><span>├── author   heyaohua &lt;...&gt; 1700000000 +0800
</span></span><span style="display:flex;"><span>└── message  修复登录页按钮错位
</span></span></code></pre></div><p>自己动手看一眼（plumbing 命令）：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git cat-file -t a1b2c3d    <span style="color:#6272a4"># 对象类型：commit</span>
</span></span><span style="display:flex;"><span>git cat-file -p a1b2c3d    <span style="color:#6272a4"># 打印内容</span>
</span></span><span style="display:flex;"><span>git cat-file -p HEAD^<span style="color:#ff79c6">{</span>tree<span style="color:#ff79c6">}</span> <span style="color:#6272a4"># 当前提交的根 tree</span>
</span></span></code></pre></div><p>关键推论：</p>
<ul>
<li><strong>&ldquo;快照&quot;其实很省空间</strong>：没改的文件，新 tree 直接复用旧 blob 指针。</li>
<li><strong>commit 是不可变的</strong>：任何修改历史（amend/rebase）都是生成新对象，旧的靠 reflog 留着。</li>
</ul>
<hr>
<h2 id="引用分支headtag-都只是指针">引用：分支、HEAD、tag 都只是指针</h2>
<p><code>.git/refs/</code> 和 <code>.git/HEAD</code> 揭开了分支的全部秘密：</p>
<ul>
<li><strong>分支</strong>：一个文件，内容就是某个 commit 的哈希。<code>git switch -c foo</code> 就是写了一个 41 字节的文件——所以 Git 开分支快到零成本。</li>
<li><strong>HEAD</strong>：一个文件，内容是&quot;当前指向哪个分支&rdquo;（如 <code>ref: refs/heads/main</code>）。</li>
<li><strong>detached HEAD</strong>：HEAD 直接指向 commit 而不是分支（如 <code>git switch a1b2c3d</code>）。此时提交的改动没有分支名挂着，切走后会变成&quot;孤儿&quot;，解法是立刻 <code>git switch -c rescue</code> 给它安个分支名。</li>
<li><strong>远程跟踪分支</strong> <code>origin/main</code>：也是本地的一个指针，记录&quot;上次和远程同步时远程在哪&quot;，只有 <code>fetch/pull/push</code> 时才更新。</li>
</ul>
<p>合并提交 = 有<strong>两个 parent</strong> 的 commit。fast-forward 合并 = 只是把分支指针往前挪，不产生新 commit。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git log --oneline --graph --all    <span style="color:#6272a4"># 现在你应该能完全读懂这张图了</span>
</span></span><span style="display:flex;"><span>git rev-parse HEAD main origin/main  <span style="color:#6272a4"># 看各引用实际指向的哈希</span>
</span></span></code></pre></div><hr>
<h2 id="彻底理解-merge--rebase--cherry-pick">彻底理解 merge / rebase / cherry-pick</h2>
<p>有了对象模型，三者的区别一句话说清：</p>
<ul>
<li><strong>merge</strong>：新建一个双 parent 的 commit，tree 取三方合并（base、我方、对方）的结果。</li>
<li><strong>rebase</strong>：把每条提交的 diff 依次&quot;重放&quot;到新 base 上，生成一串<strong>新 commit</strong>（旧提交还在，靠 reflog 可见）。</li>
<li><strong>cherry-pick</strong>：等价于对单条提交做一次 rebase——算出它的 diff，重放到当前 HEAD。</li>
</ul>
<p>所以&quot;rebase 会改历史&quot;的准确说法是：<strong>commit id（哈希）会变，因为 parent 变了 → 内容变了 → 哈希变了</strong>。这也是为什么不能 rebase 公共分支：别人手里还是旧 id，你们的对象图从此分叉。</p>
<hr>
<h2 id="bisect二分查找哪次提交引入了-bug">bisect：二分查找&quot;哪次提交引入了 bug&quot;</h2>
<p>场景：线上报 bug，上次发版还是好的，中间隔了 200 条提交，不知道哪次改坏了。人肉翻要半天，bisect 用二分查找只要约 8 步（log₂200 ≈ 7.6）。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git bisect start
</span></span><span style="display:flex;"><span>git bisect bad                 <span style="color:#6272a4"># 当前版本是坏的</span>
</span></span><span style="display:flex;"><span>git bisect good v1.2.0         <span style="color:#6272a4"># 上次发版是好的</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># Git 自动 checkout 到中间的提交，你测试后回答：</span>
</span></span><span style="display:flex;"><span>git bisect good   <span style="color:#6272a4"># 或 git bisect bad</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># …重复几轮…</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>git bisect reset   <span style="color:#6272a4"># 结束后回到原分支</span>
</span></span></code></pre></div><p>最后 Git 会告诉你：<code>a1b2c3d is the first bad commit</code>。</p>
<p><strong>自动化 bisect</strong>——有现成测试脚本时全程不用动手：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git bisect start HEAD v1.2.0
</span></span><span style="display:flex;"><span>git bisect run npm <span style="color:#8be9fd;font-style:italic">test</span>        <span style="color:#6272a4"># 退出码非 0 即 bad</span>
</span></span><span style="display:flex;"><span>git bisect reset
</span></span></code></pre></div><hr>
<h2 id="worktree一个仓库多个工作区">worktree：一个仓库多个工作区</h2>
<p>场景：正在 feature 分支开发到一半（一堆未提交改动），需要紧急在 main 上验证一个问题。stash 来回倒腾很烦，worktree 直接给仓库开第二个目录：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git worktree add ../my-project-hotfix main   <span style="color:#6272a4"># 在旁边目录开一个 main 的工作区</span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">cd</span> ../my-project-hotfix                      <span style="color:#6272a4"># 独立目录，独立工作区，共享 .git</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># …修 bug、提交、push…</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">cd</span> ../my-project
</span></span><span style="display:flex;"><span>git worktree remove ../my-project-hotfix     <span style="color:#6272a4"># 用完删掉</span>
</span></span><span style="display:flex;"><span>git worktree list
</span></span></code></pre></div><p>注意：同一分支不能同时被两个 worktree 检出。这也是 Code Review 时快速跑别人 PR 的利器。</p>
<hr>
<h2 id="submodule-与-subtree仓库套仓库">submodule 与 subtree：仓库套仓库</h2>
<h3 id="submodule嵌一个独立仓库的固定版本">submodule：嵌一个独立仓库的固定版本</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git submodule add https://github.com/org/shared-lib.git vendor/shared-lib
</span></span><span style="display:flex;"><span>git commit -m <span style="color:#f1fa8c">&#34;引入 shared-lib 子模块&#34;</span>
</span></span></code></pre></div><p>主仓库存的只是&quot;子模块的仓库地址 + commit id&quot;。克隆含子模块的项目：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git clone --recurse-submodules &lt;url&gt;
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># 或事后补：</span>
</span></span><span style="display:flex;"><span>git submodule update --init --recursive
</span></span></code></pre></div><p>更新子模块：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">cd</span> vendor/shared-lib <span style="color:#ff79c6">&amp;&amp;</span> git pull origin main <span style="color:#ff79c6">&amp;&amp;</span> <span style="color:#8be9fd;font-style:italic">cd</span> -
</span></span><span style="display:flex;"><span>git add vendor/shared-lib <span style="color:#ff79c6">&amp;&amp;</span> git commit -m <span style="color:#f1fa8c">&#34;升级 shared-lib&#34;</span>
</span></span></code></pre></div><p>痛点：每个协作者都要记得 <code>--recurse-submodules</code>，子模块内部的修改提交流程也绕。<strong>适合：外部依赖、发布节奏完全独立的组件。</strong></p>
<h3 id="subtree把另一个仓库的内容直接并入">subtree：把另一个仓库的内容直接并入</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git subtree add --prefix vendor/shared-lib https://github.com/org/shared-lib.git main --squash
</span></span><span style="display:flex;"><span>git subtree pull --prefix vendor/shared-lib https://github.com/org/shared-lib.git main --squash
</span></span></code></pre></div><p>对协作者完全透明（就是一个普通目录），但拉取更新稍慢、命令冗长。<strong>适合：想 vendoring 一份代码且偶尔同步上游。</strong></p>
<p>选择建议：依赖关系强、需要锁版本 → submodule；只想拷一份进来偶尔同步 → subtree；能走包管理器（npm/pip/go mod）的一律别用这两个。</p>
<hr>
<h2 id="hooks在关键时刻自动执行脚本">hooks：在关键时刻自动执行脚本</h2>
<p><code>.git/hooks/</code> 里的可执行脚本会在对应时机触发。常用：</p>
<table>
	<thead>
			<tr>
					<th>hook</th>
					<th>时机</th>
					<th>典型用途</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>pre-commit</code></td>
					<td>提交前</td>
					<td>lint、格式化、跑单测</td>
			</tr>
			<tr>
					<td><code>commit-msg</code></td>
					<td>提交信息生成后</td>
					<td>校验 message 格式（conventional commits）</td>
			</tr>
			<tr>
					<td><code>pre-push</code></td>
					<td>推送前</td>
					<td>跑完整测试套件</td>
			</tr>
			<tr>
					<td><code>post-merge</code></td>
					<td>合并后</td>
					<td>检测 package.json 变了就提醒装依赖</td>
			</tr>
	</tbody>
</table>
<p>示例 <code>pre-commit</code>：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#ff79c6">#!/bin/sh
</span></span></span><span style="display:flex;"><span>npm run lint-staged <span style="color:#ff79c6">||</span> <span style="color:#8be9fd;font-style:italic">exit</span> <span style="color:#bd93f9">1</span>
</span></span></code></pre></div><p>实践中不要手写裸 hook，用 <strong>husky</strong>（Node 项目）或 <strong>pre-commit 框架</strong>（Python/多语言）管理，它们把 hooks 纳入版本库、团队共享：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>pnpm add -D husky lint-staged
</span></span><span style="display:flex;"><span>npx husky init
</span></span><span style="display:flex;"><span><span style="color:#8be9fd;font-style:italic">echo</span> <span style="color:#f1fa8c">&#34;pnpm lint-staged&#34;</span> &gt; .husky/pre-commit
</span></span></code></pre></div><hr>
<h2 id="git-lfs-与大文件">Git LFS 与大文件</h2>
<p>Git 对二进制的差量存储很差，一个 100MB 的 PSD 改 10 次，仓库就胖 1GB。Git LFS（Large File Storage）把大文件内容存到专门服务器，仓库里只留指针：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git lfs install
</span></span><span style="display:flex;"><span>git lfs track <span style="color:#f1fa8c">&#34;*.psd&#34;</span> <span style="color:#f1fa8c">&#34;*.zip&#34;</span> <span style="color:#f1fa8c">&#34;assets/videos/**&#34;</span>
</span></span><span style="display:flex;"><span>git add .gitattributes        <span style="color:#6272a4"># 必须提交，团队才会共享规则</span>
</span></span><span style="display:flex;"><span>git add design.psd <span style="color:#ff79c6">&amp;&amp;</span> git commit -m <span style="color:#f1fa8c">&#34;添加设计稿&#34;</span>
</span></span></code></pre></div><p>注意：LFS 只对<strong>新提交</strong>生效；历史里已有的大家伙要用下节的方法清理。另外 LFS 服务器（GitHub 等）有流量配额，重度使用前先算账。</p>
<hr>
<h2 id="仓库瘦身清理历史中的大文件">仓库瘦身：清理历史中的大文件</h2>
<p>场景：早期误提交了构建产物 / 视频，仓库 clone 要 10 分钟。目标是<strong>改写全部历史</strong>，把大文件从每个 commit 里抠掉。</p>
<p>推荐工具 <strong>git-filter-repo</strong>（官方推荐的第三方工具，替代已废弃的 <code>filter-branch</code>）：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>pip install git-filter-repo
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># 按路径删除（历史上所有提交）</span>
</span></span><span style="display:flex;"><span>git filter-repo --path dist/ --invert-paths
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># 按大小：干掉所有超过 50M 的 blob</span>
</span></span><span style="display:flex;"><span>git filter-repo --strip-blobs-bigger-than 50M
</span></span></code></pre></div><p>⚠️ 这是<strong>破坏性重写历史</strong>：</p>
<ol>
<li>操作前全量备份（<code>cp -r</code> 或推一个备份 remote）。</li>
<li>所有协作者必须重新克隆，旧本地仓库作废。</li>
<li>推送用 <code>git push --force</code> 全分支全标签。</li>
<li>如果只是为了去掉误提交的<strong>密钥</strong>，改完历史后<strong>密钥照样要换</strong>——它可能已经泄露。</li>
</ol>
<p>查一下仓库里最占空间的历史对象：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git rev-list --objects --all <span style="color:#f1fa8c">\
</span></span></span><span style="display:flex;"><span>  | git cat-file --batch-check<span style="color:#ff79c6">=</span><span style="color:#f1fa8c">&#39;%(objecttype) %(objectname) %(objectsize) %(rest)&#39;</span> <span style="color:#f1fa8c">\
</span></span></span><span style="display:flex;"><span>  | awk <span style="color:#f1fa8c">&#39;/^blob/ {print substr($0,6)}&#39;</span> | sort -k2 -n | tail -20
</span></span></code></pre></div><hr>
<h2 id="高级调试与诊断">高级调试与诊断</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#6272a4"># 仓库统计与磁盘占用</span>
</span></span><span style="display:flex;"><span>git count-objects -vH
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># 某行代码的演化史：谁在什么时候为什么改的</span>
</span></span><span style="display:flex;"><span>git log -L 10,20:src/app.js        <span style="color:#6272a4"># 跟踪文件第 10-20 行的变更历史</span>
</span></span><span style="display:flex;"><span>git log -S <span style="color:#f1fa8c">&#34;functionName&#34;</span>           <span style="color:#6272a4"># 找&#34;某段字符串被增删&#34;的提交（pickaxe）</span>
</span></span><span style="display:flex;"><span>git log -G <span style="color:#f1fa8c">&#34;regex&#34;</span>                  <span style="color:#6272a4"># 按正则找 diff 内容变化的提交</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># 分支拓扑</span>
</span></span><span style="display:flex;"><span>git log --graph --oneline --all --decorate
</span></span><span style="display:flex;"><span>git branch --contains &lt;commit&gt;      <span style="color:#6272a4"># 哪些分支包含这个提交（修没修进发布版？）</span>
</span></span><span style="display:flex;"><span>git branch --no-merged main         <span style="color:#6272a4"># 还没合并进 main 的分支</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># 比较</span>
</span></span><span style="display:flex;"><span>git diff main...feature/xxx         <span style="color:#6272a4"># 三点：feature 从 main 分叉后引入的改动</span>
</span></span><span style="display:flex;"><span>git range-diff main..old main..new  <span style="color:#6272a4"># 比较 rebase 前后两个提交序列的差异</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># 数据完整性</span>
</span></span><span style="display:flex;"><span>git fsck                            <span style="color:#6272a4"># 检查对象库损坏 / 悬空对象</span>
</span></span></code></pre></div><p><code>-S</code>（pickaxe）是排查&quot;这行代码什么时候没的&quot;的终极武器，配合 <code>--reverse</code> 还能看一段逻辑从出生到现在的完整演化。</p>
<hr>
<h2 id="本篇速查表">本篇速查表</h2>
<table>
	<thead>
			<tr>
					<th>目的</th>
					<th>命令</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>看对象内容</td>
					<td><code>git cat-file -p &lt;id&gt;</code></td>
			</tr>
			<tr>
					<td>看引用指向</td>
					<td><code>git rev-parse &lt;ref&gt;</code></td>
			</tr>
			<tr>
					<td>二分排错</td>
					<td><code>git bisect start/bad/good/run</code></td>
			</tr>
			<tr>
					<td>多工作区</td>
					<td><code>git worktree add &lt;path&gt; &lt;branch&gt;</code></td>
			</tr>
			<tr>
					<td>子模块</td>
					<td><code>git submodule update --init --recursive</code></td>
			</tr>
			<tr>
					<td>并入外部仓库</td>
					<td><code>git subtree pull --prefix=... --squash</code></td>
			</tr>
			<tr>
					<td>提交前自动化</td>
					<td>husky / pre-commit 框架</td>
			</tr>
			<tr>
					<td>大文件</td>
					<td><code>git lfs track &quot;*.bin&quot;</code></td>
			</tr>
			<tr>
					<td>历史瘦身</td>
					<td><code>git filter-repo --invert-paths --path ...</code></td>
			</tr>
			<tr>
					<td>找代码何时被删</td>
					<td><code>git log -S &quot;...&quot;</code></td>
			</tr>
			<tr>
					<td>检查仓库健康</td>
					<td><code>git fsck</code> / <code>git count-objects -vH</code></td>
			</tr>
	</tbody>
</table>
<hr>
<p>到这里，Git 对你已经没有黑盒了。下一篇《终极篇》转向工程之巅：monorepo 策略、安全合规（签名提交、密钥扫描）、CI/CD 中的 Git 技巧、灾难恢复 SOP，以及一套可以直接落地的团队 Git 规范。</p>
]]></content:encoded></item></channel></rss>