<?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>Rebase on heyaohua's Blog</title><link>https://blog.heyaohua.com/tags/rebase/</link><description>Recent content in Rebase 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:22:00 +0800</lastBuildDate><atom:link href="https://blog.heyaohua.com/tags/rebase/index.xml" rel="self" type="application/rss+xml"/><item><title>Git 使用手册 · 中级篇：分支协作、冲突与历史修改</title><link>https://blog.heyaohua.com/posts/2026/08/git-handbook-intermediate/</link><pubDate>Sat, 22 Aug 2026 23:22:00 +0800</pubDate><guid>https://blog.heyaohua.com/posts/2026/08/git-handbook-intermediate/</guid><description>Git 中级手册，讲清分支协作模型、合并冲突的正确解法、reset 三种模式、stash、rebase 与 cherry-pick，让你从会用 Git 到用得对。</description><content:encoded><![CDATA[<p>这是 Git 手册系列的第二篇。初级篇解决了&quot;能用&quot;，本篇解决&quot;用得对&quot;：多人协作不打架、冲突不慌、历史能改得干净。</p>
<hr>
<h2 id="目录">目录</h2>
<ul>
<li>分支模型：选一个然后坚持</li>
<li>merge 与 rebase：到底用哪个</li>
<li>冲突的正确解法</li>
<li>reset 的三种模式（soft / mixed / hard）</li>
<li>stash：临时收摊子</li>
<li>cherry-pick：挑一个提交带走</li>
<li>修改历史：amend 与交互式 rebase</li>
<li>reflog：你的后悔药</li>
<li>tag 与发布</li>
<li>中级常见错误</li>
</ul>
<hr>
<h2 id="分支模型选一个然后坚持">分支模型：选一个然后坚持</h2>
<h3 id="个人--小团队trunk-based主干开发">个人 / 小团队：Trunk-Based（主干开发）</h3>
<ul>
<li><code>main</code> 是唯一长期分支，随时可发布</li>
<li>功能分支从 main 切出，活 1~3 天，合并后立即删除</li>
<li>配合 PR/MR 做代码评审</li>
</ul>
<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 switch -c feature/xxx main
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># 开发、提交</span>
</span></span><span style="display:flex;"><span>git push -u origin feature/xxx   <span style="color:#6272a4"># 提 PR，评审通过后合并</span>
</span></span><span style="display:flex;"><span>git switch main <span style="color:#ff79c6">&amp;&amp;</span> git pull
</span></span><span style="display:flex;"><span>git branch -d feature/xxx
</span></span></code></pre></div><h3 id="需要同时维护多个发布版本git-flow">需要同时维护多个发布版本：Git Flow</h3>
<ul>
<li><code>main</code>：线上版本；<code>develop</code>：集成分支</li>
<li><code>feature/*</code>、<code>release/*</code>、<code>hotfix/*</code> 各司其职</li>
<li>重，只有确实需要多版本并行（如 SaaS + 私有化部署）才用</li>
</ul>
<p><strong>结论</strong>：90% 的团队用 Trunk-Based 就够了。分支模型越简单，协作成本越低。</p>
<hr>
<h2 id="merge-与-rebase到底用哪个">merge 与 rebase：到底用哪个</h2>
<p>两者都用来&quot;把 B 分支的改动合进 A 分支&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-text" data-lang="text"><span style="display:flex;"><span>merge（保留真实历史，产生合并提交）：
</span></span><span style="display:flex;"><span>main:   A---B-------M
</span></span><span style="display:flex;"><span>              \   /
</span></span><span style="display:flex;"><span>feature:       C---D
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>rebase（把 feature 的提交&#34;搬家&#34;到 main 最新处，历史成一条直线）：
</span></span><span style="display:flex;"><span>main:   A---B
</span></span><span style="display:flex;"><span>feature:         C&#39;--D&#39;   （C、D 被重放为新提交）
</span></span></code></pre></div><p>经验法则：</p>
<ul>
<li><strong>公共分支（main、develop）上用 merge</strong>，保留协作痕迹。</li>
<li><strong>自己的功能分支同步 main 最新代码时用 rebase</strong>，保持历史干净：</li>
</ul>
<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 switch feature/xxx
</span></span><span style="display:flex;"><span>git fetch origin
</span></span><span style="display:flex;"><span>git rebase origin/main      <span style="color:#6272a4"># 解决冲突后 git rebase --continue</span>
</span></span></code></pre></div><p>⚠️ <strong>黄金规则：永远不要 rebase 已经 push 且别人在用的分支</strong>。rebase 会改写提交 id，别人的本地历史会对不上。</p>
<hr>
<h2 id="冲突的正确解法">冲突的正确解法</h2>
<p>冲突的本质：同一文件的同一区域，两边都改了，Git 不知道该听谁的。</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 merge feature/xxx
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># CONFLICT (content): Merge conflict in src/app.js</span>
</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>git status                 <span style="color:#6272a4"># 1. 看哪些文件冲突</span>
</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-text" data-lang="text"><span style="display:flex;"><span>&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD
</span></span><span style="display:flex;"><span>const timeout = 3000;
</span></span><span style="display:flex;"><span>=======
</span></span><span style="display:flex;"><span>const timeout = 5000;
</span></span><span style="display:flex;"><span>&gt;&gt;&gt;&gt;&gt;&gt;&gt; feature/xxx
</span></span></code></pre></div><p><code>&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD</code> 到 <code>=======</code> 是当前分支的内容，<code>=======</code> 到 <code>&gt;&gt;&gt;&gt;&gt;&gt;&gt;</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-text" data-lang="text"><span style="display:flex;"><span>const timeout = 5000;
</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>git add src/app.js         <span style="color:#6272a4"># 2. 标记为已解决</span>
</span></span><span style="display:flex;"><span>git commit                 <span style="color:#6272a4"># 3. 完成合并提交</span>
</span></span></code></pre></div><p>中途想放弃：<code>git merge --abort</code>（rebase 对应 <code>git rebase --abort</code>），一切回到合并前。</p>
<p>降低冲突概率的三个习惯：</p>
<ol>
<li>功能分支短命，尽快合并</li>
<li>开始干活前先 <code>git pull</code> / <code>git rebase origin/main</code></li>
<li>公共文件（配置文件、路由表）的改动尽早沟通</li>
</ol>
<hr>
<h2 id="reset-的三种模式soft--mixed--hard">reset 的三种模式（soft / mixed / hard）</h2>
<p><code>git reset</code> 把 HEAD 移动到某个提交，三种模式决定&quot;工作区和暂存区怎么办&quot;：</p>
<table>
	<thead>
			<tr>
					<th>模式</th>
					<th>提交历史</th>
					<th>暂存区</th>
					<th>工作区</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>--soft</code></td>
					<td>回退</td>
					<td><strong>保留</strong></td>
					<td>保留</td>
			</tr>
			<tr>
					<td><code>--mixed</code>（默认）</td>
					<td>回退</td>
					<td>清空</td>
					<td><strong>保留</strong></td>
			</tr>
			<tr>
					<td><code>--hard</code></td>
					<td>回退</td>
					<td>清空</td>
					<td><strong>清空</strong></td>
			</tr>
	</tbody>
</table>
<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 reset --soft HEAD~1      <span style="color:#6272a4"># 撤销上次提交，改动还在暂存区（改提交内容用）</span>
</span></span><span style="display:flex;"><span>git reset HEAD~1             <span style="color:#6272a4"># 撤销上次提交，改动回到工作区（重新组织提交用）</span>
</span></span><span style="display:flex;"><span>git reset --hard HEAD~1      <span style="color:#6272a4"># 撤销上次提交，改动全部丢弃（危险！）</span>
</span></span></code></pre></div><p><code>HEAD~1</code> 表示&quot;上一条提交&quot;，<code>HEAD~3</code> 表示往前数 3 条。</p>
<p><strong>安全准则</strong>：<code>--hard</code> 之前先 <code>git stash</code> 或确认改动有备份；只 reset 还没 push 的提交。</p>
<hr>
<h2 id="stash临时收摊子">stash：临时收摊子</h2>
<p>场景：正在 feature 分支改一半，线上突然有 bug 要你切分支修。</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 stash                    <span style="color:#6272a4"># 把未提交的改动收起来，工作区变干净</span>
</span></span><span style="display:flex;"><span>git switch hotfix/urgent     <span style="color:#6272a4"># 去修 bug</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># …修完、提交…</span>
</span></span><span style="display:flex;"><span>git switch feature/xxx       <span style="color:#6272a4"># 回来</span>
</span></span><span style="display:flex;"><span>git stash pop                <span style="color:#6272a4"># 恢复刚才收起的改动</span>
</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>git stash list               <span style="color:#6272a4"># 看有几个 stash</span>
</span></span><span style="display:flex;"><span>git stash show -p            <span style="color:#6272a4"># 看最近一次 stash 的内容</span>
</span></span><span style="display:flex;"><span>git stash push -m <span style="color:#f1fa8c">&#34;登录页改了一半&#34;</span>   <span style="color:#6272a4"># 带备注存</span>
</span></span><span style="display:flex;"><span>git stash apply stash@<span style="color:#ff79c6">{</span>1<span style="color:#ff79c6">}</span>    <span style="color:#6272a4"># 恢复指定的（pop 会删掉，apply 保留）</span>
</span></span><span style="display:flex;"><span>git stash drop stash@<span style="color:#ff79c6">{</span>0<span style="color:#ff79c6">}</span>     <span style="color:#6272a4"># 删除</span>
</span></span></code></pre></div><hr>
<h2 id="cherry-pick挑一个提交带走">cherry-pick：挑一个提交带走</h2>
<p>场景：在 develop 上修的一个 bug，main 上也需要，但不想合并整个 develop。</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 develop        <span style="color:#6272a4"># 找到那个提交的 id，比如 a1b2c3d</span>
</span></span><span style="display:flex;"><span>git switch main
</span></span><span style="display:flex;"><span>git cherry-pick a1b2c3d          <span style="color:#6272a4"># 把它复制过来（生成新提交 id）</span>
</span></span></code></pre></div><p>可以连续挑多个：<code>git cherry-pick A B C</code>，或挑一个区间 <code>git cherry-pick A..B</code>（不含 A）。</p>
<hr>
<h2 id="修改历史amend-与交互式-rebase">修改历史：amend 与交互式 rebase</h2>
<h3 id="amend修补最近一次提交">amend：修补最近一次提交</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><span style="color:#6272a4"># 刚提交完发现漏了个文件 / 提交信息写错了</span>
</span></span><span style="display:flex;"><span>git add forgotten.js
</span></span><span style="display:flex;"><span>git commit --amend               <span style="color:#6272a4"># 编辑器里顺便改 message</span>
</span></span><span style="display:flex;"><span>git commit --amend --no-edit     <span style="color:#6272a4"># 只补内容不改 message</span>
</span></span></code></pre></div><h3 id="交互式-rebase整理最近-n-条提交">交互式 rebase：整理最近 N 条提交</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 rebase -i HEAD~4
</span></span></code></pre></div><p>编辑器里列出最近 4 条提交，把每行开头的 <code>pick</code> 改成：</p>
<table>
	<thead>
			<tr>
					<th>指令</th>
					<th>作用</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>pick</code></td>
					<td>保留这条提交</td>
			</tr>
			<tr>
					<td><code>reword</code></td>
					<td>保留，但改提交信息</td>
			</tr>
			<tr>
					<td><code>squash</code></td>
					<td>合并进上一条（保留两条的信息供编辑）</td>
			</tr>
			<tr>
					<td><code>fixup</code></td>
					<td>合并进上一条（丢弃本条信息）</td>
			</tr>
			<tr>
					<td><code>drop</code></td>
					<td>删除这条提交</td>
			</tr>
	</tbody>
</table>
<p>典型场景：PR 之前把 <code>修复 typo</code>、<code>再改一下</code>、<code>真的改好了</code> 这类碎提交压成一条干净的。</p>
<p>⚠️ 同样的黄金规则：<strong>只改写未 push 的历史</strong>。如果已 push 且确认只有自己在用，改写后需要 <code>git push --force-with-lease</code>（比 <code>--force</code> 安全：远程有别人新提交时会拒绝）。</p>
<hr>
<h2 id="reflog你的后悔药">reflog：你的后悔药</h2>
<p>几乎所有&quot;我完了&quot;的瞬间，<code>git reflog</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>git reflog
</span></span></code></pre></div><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>a1b2c3d HEAD@{0}: reset: moving to HEAD~1      ← 手滑的操作
</span></span><span style="display:flex;"><span>e4f5g6h HEAD@{1}: commit: 重要的提交            ← 以为丢了的提交
</span></span></code></pre></div><p>reflog 记录 HEAD 的每一次移动（commit、reset、rebase、切换分支……），保留约 90 天。找回方式：</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 reset --hard e4f5g6h     <span style="color:#6272a4"># 直接回到那个状态</span>
</span></span><span style="display:flex;"><span><span style="color:#6272a4"># 或</span>
</span></span><span style="display:flex;"><span>git branch rescue e4f5g6h    <span style="color:#6272a4"># 开个分支把那个提交捞回来</span>
</span></span></code></pre></div><p><strong>只要不是 <code>git clean</code> 删掉的未跟踪新文件，Git 里几乎没有真正丢失的提交。</strong></p>
<hr>
<h2 id="tag-与发布">tag 与发布</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>git tag v1.2.0                   <span style="color:#6272a4"># 轻量标签</span>
</span></span><span style="display:flex;"><span>git tag -a v1.2.0 -m <span style="color:#f1fa8c">&#34;发布 1.2.0：新增支付模块&#34;</span>   <span style="color:#6272a4"># 附注标签（推荐）</span>
</span></span><span style="display:flex;"><span>git push origin v1.2.0           <span style="color:#6272a4"># 推送单个标签</span>
</span></span><span style="display:flex;"><span>git push --tags                  <span style="color:#6272a4"># 推送所有标签</span>
</span></span><span style="display:flex;"><span>git tag -d v1.2.0                <span style="color:#6272a4"># 删本地</span>
</span></span><span style="display:flex;"><span>git push origin :v1.2.0          <span style="color:#6272a4"># 删远程</span>
</span></span></code></pre></div><p>版本号遵循语义化版本 <code>主.次.修</code>（semver）：破坏性变更升主版本，新功能升次版本，修 bug 升修订号。</p>
<hr>
<h2 id="中级常见错误">中级常见错误</h2>
<p><strong>1. 在公共分支上 rebase / amend 了已 push 的提交</strong>
队友 pull 时会历史错乱。解：公共分支只用 merge；改写前先确认没有别人基于它工作。</p>
<p><strong>2. 解决冲突时直接用&quot;接受我方/接受对方&quot;整文件覆盖</strong>
可能把别人无关的改动一起吞掉。逐块（hunk）检查。</p>
<p><strong>3. <code>git reset --hard</code> 之后发现误删了改动</strong>
先别慌，<code>git reflog</code> 找回。养成 hard 之前 <code>git stash</code> 的习惯。</p>
<p><strong>4. stash 堆成山从来不清理</strong>
<code>git stash list</code> 定期看一眼，用完就 pop/drop。</p>
<p><strong>5. cherry-pick 之后又 merge 原分支，出现重复改动</strong>
偶尔会发生，合并时 Git 通常能识别；冲突了耐心解一次即可。</p>
<hr>
<h2 id="本篇速查表">本篇速查表</h2>
<table>
	<thead>
			<tr>
					<th>目的</th>
					<th>命令</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>同步 main 到功能分支</td>
					<td><code>git rebase origin/main</code></td>
			</tr>
			<tr>
					<td>放弃合并</td>
					<td><code>git merge --abort</code></td>
			</tr>
			<tr>
					<td>撤销提交保留改动</td>
					<td><code>git reset --soft/--mixed HEAD~1</code></td>
			</tr>
			<tr>
					<td>彻底回退（危险）</td>
					<td><code>git reset --hard &lt;id&gt;</code></td>
			</tr>
			<tr>
					<td>暂存半成品</td>
					<td><code>git stash</code> / <code>git stash pop</code></td>
			</tr>
			<tr>
					<td>复制单个提交</td>
					<td><code>git cherry-pick &lt;id&gt;</code></td>
			</tr>
			<tr>
					<td>改最近提交</td>
					<td><code>git commit --amend</code></td>
			</tr>
			<tr>
					<td>整理历史</td>
					<td><code>git rebase -i HEAD~n</code></td>
			</tr>
			<tr>
					<td>找回丢失提交</td>
					<td><code>git reflog</code></td>
			</tr>
			<tr>
					<td>安全强推</td>
					<td><code>git push --force-with-lease</code></td>
			</tr>
			<tr>
					<td>打标签</td>
					<td><code>git tag -a v1.0.0 -m &quot;...&quot;</code></td>
			</tr>
	</tbody>
</table>
<hr>
<p>到这一层，你已经能应付团队协作的全部日常。下一篇《高级篇》进入 Git 的内部世界：对象模型、引用、HEAD 的本质、bisect 二分排错、worktree、submodule/subtree 与钩子——理解了&quot;为什么&quot;，命令就再也背不丢。</p>
]]></content:encoded></item></channel></rss>