<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Troubleshooting on kenji.blog</title><link>http://kenji.blog/zh-tw/tags/troubleshooting/</link><description>Recent content in Troubleshooting on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>zh-tw</language><copyright>kenjinote</copyright><lastBuildDate>Sat, 12 Sep 2026 17:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/zh-tw/tags/troubleshooting/index.xml" rel="self" type="application/rss+xml"/><item><title>Git初學者容易犯的錯誤與解決指令集（解決衝突等）</title><link>http://kenji.blog/zh-tw/p/git-beginners-mistakes-and-solutions/</link><pubDate>Sat, 12 Sep 2026 17:00:00 +0900</pubDate><guid>http://kenji.blog/zh-tw/p/git-beginners-mistakes-and-solutions/</guid><description>&lt;img src="http://kenji.blog/p/git-beginners-mistakes-and-solutions/img/eyecatch.jpg" alt="Featured image of post Git初學者容易犯的錯誤與解決指令集（解決衝突等）" />&lt;h1 id="git初學者容易犯的錯誤與解決指令集解決衝突等">Git初學者容易犯的錯誤與解決指令集（解決衝突等）
&lt;/h1>&lt;h2 id="1-前言為什麼會在-git-中犯錯">1. 前言：為什麼會在 Git 中犯錯？
&lt;/h2>&lt;p>在軟體開發中，Git 已經成為像空氣和水一樣不可或缺的存在。然而，對於許多初學者（有時甚至是熟練者）來說，Git 感覺就像是一個「可怕的魔法黑盒子」。提交消失、將大量變更推入錯誤的分支、從未見過的衝突錯誤訊息佔滿螢幕……。當陷入這些「Git 的陷阱」時，工作進度會完全停滯，最糟糕的情況下，甚至會產生原始碼被破壞的恐懼。&lt;/p>
&lt;p>為什麼 Git 會如此困難且容易誘發錯誤呢？最大的原因是「沒有理解 Git 內部發生了什麼，只是死記硬背表面的指令來使用」。Git 是基於分散式版本控制系統（DVCS）的堅固設計理念，但它的介面（CLI）並不總是直觀的。&lt;/p>
&lt;p>在本文中，我們將把 Git 初學者在工作現場經常遇到的「失誤（常見錯誤）」進行分類，並提供每個情況的具體解決方案指令。但這不會只是一份單純的指令清單（備忘錄）。我們將透過 &lt;code>.git&lt;/code> 目錄的結構、背後運行的 Diff 演算法的數學背景，以及 Mermaid 圖解，以超過 10,000 字的篇幅徹底深入探討「為什麼會發生這個錯誤」、「執行該指令時 Git 內部資料是如何移動的」。&lt;/p>
&lt;p>當你讀完這篇文章時，你應該能從「害怕 Git」的情緒中解脫出來，反而會確信「沒有比 Git 更可靠的夥伴了」。那麼，讓我們潛入 Git 的深淵世界吧。&lt;/p>
&lt;hr>
&lt;h2 id="2-git-的深淵理解-git-目錄的內部結構">2. Git 的深淵：理解 &lt;code>.git&lt;/code> 目錄的內部結構
&lt;/h2>&lt;p>許多疑難排解變得容易的第一步，是了解 Git 如何儲存資料。存在於你專案根目錄中的隱藏資料夾 &lt;code>.git&lt;/code>，正是 Git 的心臟地帶。Git 並非只是一個依序記錄檔案差異（patch）的系統，而是將資料作為&lt;strong>快照（snapshot）流&lt;/strong>來管理。&lt;/p>
&lt;h3 id="21-物件模型blob-tree-commit">2.1 物件模型：Blob, Tree, Commit
&lt;/h3>&lt;p>Git 主要使用 3 種物件來表示儲存庫的狀態。這些物件儲存在 &lt;code>.git/objects&lt;/code> 中。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Blob (Binary Large Object)&lt;/strong>
這是儲存檔案內容本身的物件。檔案名稱或權限的資訊不包含在這裡。純粹的位元組陣列會被 zlib 壓縮，並由 SHA-1 雜湊值（40 個字元的十六進位數字）來識別。&lt;/li>
&lt;li>&lt;strong>Tree&lt;/strong>
表示目錄結構的物件。Tree 物件包含指向其他 Tree 物件（子目錄）或 Blob 物件（檔案）的指標（SHA-1 雜湊值），以及它們的檔案名稱、存取權限。它扮演類似 UNIX 目錄的角色。&lt;/li>
&lt;li>&lt;strong>Commit&lt;/strong>
包含指向特定時間點儲存庫整體頂層 Tree 物件的指標、元資料（作者、提交時間、提交訊息），以及指向前一個提交（父提交）的指標。&lt;/li>
&lt;/ol>
&lt;pre class="mermaid">
graph TD
Commit1[&amp;#34;提交 (Hash: 9f8a)&amp;#34;] --&amp;gt; Tree1[&amp;#34;樹 (Hash: 4b82)&amp;#34;]
Tree1 --&amp;gt; Blob1[&amp;#34;Blob (Hash: 8d7e) : index.js&amp;#34;]
Tree1 --&amp;gt; Tree2[&amp;#34;樹 (Hash: 3a2c) : src/&amp;#34;]
Tree2 --&amp;gt; Blob2[&amp;#34;Blob (Hash: 5f1b) : app.js&amp;#34;]
&lt;/pre>
&lt;h3 id="22-head-與參照refs的真面目">2.2 HEAD 與參照（Refs）的真面目
&lt;/h3>&lt;p>在 Git 中工作時，會頻繁看到 &lt;code>HEAD&lt;/code> 這個單字。這是指目前簽出（checkout）的分支（或提交）的&lt;strong>符號參照（Symbolic Reference）&lt;/strong>。
如果用文字編輯器打開 &lt;code>.git/HEAD&lt;/code> 這個檔案，會看到以下字串。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">ref: refs/heads/main
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>這意味著「目前的狀態位於 &lt;code>main&lt;/code> 分支的頂端」。然後，當打開 &lt;code>.git/refs/heads/main&lt;/code> 時，會看到那裡寫著 40 個字元的 SHA-1 雜湊值，這就指向了最新的 Commit 物件。
Git 的分支，不過是一個指向特定提交的輕量級指標（檔案）而已。只要知道這個事實，就能消除「刪除分支是不是所有檔案都會消失？」的恐懼。&lt;/p>
&lt;hr>
&lt;h2 id="3-用數學解讀-gitdiff-演算法與雜湊函數">3. 用數學解讀 Git：Diff 演算法與雜湊函數
&lt;/h2>&lt;p>當 Git 檢測到衝突或顯示檔案差異時，內部正在運行進階的演算法。&lt;/p>
&lt;h3 id="31-myers-的-diff-演算法">3.1 Myers 的 Diff 演算法
&lt;/h3>&lt;p>Git 預設的差異檢測演算法是由 Eugene W. Myers 發明的。當有兩個文字檔 $A$ 和 $B$ 時，找到將 $A$ 轉換為 $B$ 的「最小編輯步驟（插入與刪除）」的問題，可以建模為圖論中的最短路徑問題。&lt;/p>
&lt;p>假設字串的長度分別為 $N, M$，總和為 $V = N + M$。在 Myers 演算法中，會尋找編輯距離（Edit Distance） $D$。這個演算法的時間複雜度可以用以下公式表示：&lt;/p>
$$ \mathcal{O}(V \cdot D) $$&lt;p>這裡，如果檔案之間的差異很小（也就是 $D$ 很小），演算法的執行速度會非常快，達到 $\mathcal{O}(V)$。然而，如果檔案完全不同，則 $D \approx V$，最壞情況下的時間複雜度會變成 $\mathcal{O}(V^2)$。&lt;/p>
&lt;h3 id="32-patience-diff-與-histogram-diff">3.2 Patience Diff 與 Histogram Diff
&lt;/h3>&lt;p>雖然 Myers 演算法很優秀，但當函數或類別的順序發生大幅改變時，它可能會產生對人類來說不直觀（無意義）的差異。為了解決這個問題，Git 實作了 &lt;code>Patience Diff&lt;/code> 和 &lt;code>Histogram Diff&lt;/code>。&lt;/p>
&lt;p>Patience Diff 著眼於「在兩個檔案中只出現過一次的唯一行」，並找出它們的最長共同子序列（Longest Common Subsequence: LCS）。假設唯一元素的數量為 $U$，計算 LCS 的複雜度可以用以下公式來解決：&lt;/p>
$$ \mathcal{O}(U \log U) $$&lt;p>當你覺得解決衝突很困難時，使用 &lt;code>git diff --histogram&lt;/code>，或者在合併策略中指定這個演算法（&lt;code>git merge -s recursive -X histogram&lt;/code>）是一個好方法。&lt;/p>
&lt;h3 id="33-sha-1-與碰撞機率">3.3 SHA-1 與碰撞機率
&lt;/h3>&lt;p>Git 使用 SHA-1 雜湊值來管理所有物件。雜湊空間的大小為 $2^{160}$。關於雜湊碰撞（不同的內容擁有相同的雜湊值）發生的機率，如果我們使用生日悖論（Birthday Paradox）來近似，碰撞機率 $p$ 達到 50% 所需的物件數量 $k$ 如下：&lt;/p>
$$ k \approx \sqrt{2 \ln(2)} \cdot 2^{80} \approx 1.2 \times 2^{80} $$&lt;p>這是一個天文數字，在一般的軟體開發中，無意間發生碰撞的機率幾乎為零。因此，Git 依賴雜湊值作為「絕對唯一 ID」來運作。&lt;/p>
&lt;hr>
&lt;h2 id="4-案例研究-1不小心提交到了錯誤的分支">4. 案例研究 1：不小心提交到了錯誤的分支！
&lt;/h2>&lt;p>&lt;strong>【狀況】&lt;/strong>
沒注意到自己正在 &lt;code>main&lt;/code> 分支上工作，就拼命寫了新功能的程式碼，甚至還執行了 &lt;code>git commit&lt;/code>。原本應該要建立一個名為 &lt;code>feature/login&lt;/code> 的分支並在那裡工作的啊！&lt;/p>
&lt;h3 id="解決方法使用-git-reset-與建立分支">解決方法：使用 &lt;code>git reset&lt;/code> 與建立分支
&lt;/h3>&lt;p>在 Git 中，提交是獨立的物件，而分支只是指標。因此，只要執行「先建立新分支，再將目前分支的指標倒退」的操作，就能瞬間解決。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 1. 建立一個指向目前提交（不小心建立的提交）的新分支&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git branch feature/login
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 2. 將 main 分支的指標倒退到前一個提交（HEAD~1）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 使用 --keep 可以保留工作目錄中未提交的變更並安全地重設。&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git reset --keep HEAD~1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 3. 切換到正確的分支&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git checkout feature/login
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="圖解內部發生了什麼事">圖解：內部發生了什麼事？
&lt;/h3>&lt;p>讓我們使用 Mermaid 的 &lt;code>gitGraph&lt;/code> 來視覺化這時分支指標的移動。&lt;/p>
&lt;pre class="mermaid">
gitGraph
commit id: &amp;#34;初始提交&amp;#34;
commit id: &amp;#34;錯誤修復&amp;#34;
commit id: &amp;#34;誤操作的提交&amp;#34; type: HIGHLIGHT
branch feature/login
checkout feature/login
checkout main
&lt;/pre>
&lt;p>一開始 &lt;code>main&lt;/code> 和 &lt;code>HEAD&lt;/code> 都指向「誤操作的提交」，但透過 &lt;code>git branch feature/login&lt;/code>，在那裡建立了一個新的指標。之後透過 &lt;code>git reset&lt;/code>，只有 &lt;code>main&lt;/code> 指標回到了「錯誤修復」的位置。物件本身並沒有被刪除任何東西。&lt;/p>
&lt;hr>
&lt;h2 id="5-案例研究-2想要取消已經推送的提交">5. 案例研究 2：想要取消已經推送的提交！
&lt;/h2>&lt;p>&lt;strong>【狀況】&lt;/strong>
在深夜情緒高昂時提交了充滿 bug 的程式碼，而且還用 &lt;code>git push origin main&lt;/code> 公開到了遠端儲存庫。發現重大 bug 後臉都綠了。&lt;/p>
&lt;h3 id="解決方案-1抹除歷史的-git-revert推薦安全">解決方案 1：抹除歷史的 &lt;code>git revert&lt;/code>（推薦・安全）
&lt;/h3>&lt;p>在團隊開發中，嚴禁使用 &lt;code>git reset&lt;/code> 等方法去篡改已經推送的提交歷史。這會導致與其他開發者的本機儲存庫失去一致性。正確的方法是，&lt;strong>「建立一個新的提交來完全抵消那個錯誤提交的變更」&lt;/strong>。這就是 &lt;code>git revert&lt;/code>。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 建立一個抵消最新提交的提交&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git revert HEAD
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>main 7f3a8b2&lt;span class="o">]&lt;/span> Revert &lt;span class="s2">&amp;#34;錯誤提交的訊息&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="m">1&lt;/span> file changed, &lt;span class="m">1&lt;/span> insertion&lt;span class="o">(&lt;/span>+&lt;span class="o">)&lt;/span>, &lt;span class="m">10&lt;/span> deletions&lt;span class="o">(&lt;/span>-&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 推送到遠端&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git push origin main
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;pre class="mermaid">
gitGraph
commit id: &amp;#34;提交 A&amp;#34;
commit id: &amp;#34;提交 B (錯誤)&amp;#34;
commit id: &amp;#34;還原提交 B&amp;#34; type: REVERSE
&lt;/pre>
&lt;p>歷史會繼續前進，只有程式碼的狀態會恢復原狀。&lt;/p>
&lt;h3 id="解決方案-2篡改歷史的-git-push---force-with-lease">解決方案 2：篡改歷史的 &lt;code>git push --force-with-lease&lt;/code>
&lt;/h3>&lt;p>如果你是在只有你一個人使用的分支上，在剛推送之後，重寫歷史是可以被容許的。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 在本機重設提交並進行修改&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git reset --hard HEAD~1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git add .
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git commit -m &lt;span class="s2">&amp;#34;Correct implementation&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 強制覆寫遠端的歷史&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git push origin feature/login --force-with-lease
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;code>--force-with-lease&lt;/code> 是一種安全的強制推送，可以防止意外覆寫他人工作的事故。&lt;/p>
&lt;hr>
&lt;h2 id="6-案例研究-3工作到一半想切換到另一個分支stash-的魔法">6. 案例研究 3：工作到一半想切換到另一個分支（Stash 的魔法）
&lt;/h2>&lt;p>&lt;strong>【狀況】&lt;/strong>
在 &lt;code>feature/A&lt;/code> 分支實作新功能時，原始碼還處於連編譯都過不了的半途狀態。突然，主管下達指示：「&lt;code>main&lt;/code> 分支的正式環境出現緊急 bug，現在立刻修好它！」&lt;/p>
&lt;h3 id="解決方法使用-git-stash-進行暫存">解決方法：使用 &lt;code>git stash&lt;/code> 進行暫存
&lt;/h3>&lt;p>&lt;code>git stash&lt;/code> 是一個將尚未提交的變更暫存到臨時區域的指令。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 1. 暫存工作中的變更&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git stash push -m &lt;span class="s2">&amp;#34;WIP: feature A partially implemented&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 2. 現在可以切換到 main 分支了&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git checkout main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ...（進行緊急 bug 修復作業，提交並推送）...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 3. 作業結束後回到原本的分支&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git checkout feature/A
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 4. 恢復暫存的變更&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git stash pop
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>當執行 &lt;code>git stash&lt;/code> 時，Git 內部會產生兩個特殊的提交物件，並保存在 &lt;code>refs/stash&lt;/code> 這個參照中。也就是說，Stash 說到底也只是「沒有名字的臨時提交」而已。&lt;/p>
&lt;hr>
&lt;h2 id="7-案例研究-4恐怖的-detached-head-狀態">7. 案例研究 4：恐怖的 &amp;ldquo;Detached HEAD&amp;rdquo; 狀態
&lt;/h2>&lt;p>&lt;strong>【狀況】&lt;/strong>
為了確認過去某個時間點的程式碼，執行了 &lt;code>git checkout 9f8a7b6&lt;/code>。結果顯示了 &lt;code>You are in 'detached HEAD' state.&lt;/code>。就這樣直接提交了，但切換分支後，提交卻消失了！&lt;/p>
&lt;h3 id="detached-head-的機制">Detached HEAD 的機制
&lt;/h3>&lt;p>通常 &lt;code>HEAD&lt;/code> 會指向 &lt;code>refs/heads/main&lt;/code> 等分支。但是，如果直接簽出特定的提交，&lt;code>HEAD&lt;/code> 就會直接指向該提交物件。這被稱為 &lt;strong>Detached HEAD（分離的 HEAD）&lt;/strong>。&lt;/p>
&lt;pre class="mermaid">
graph TD
A[&amp;#34;提交 A&amp;#34;] --&amp;gt; B[&amp;#34;提交 B&amp;#34;]
B --&amp;gt; C[&amp;#34;提交 C&amp;#34;]
C --&amp;gt; D[&amp;#34;提交 D&amp;#34;]
BranchMain[&amp;#34;分支: main&amp;#34;] --&amp;gt; D
HEAD[&amp;#34;HEAD&amp;#34;] --&amp;gt; B
style HEAD fill:#f9f,stroke:#333,stroke-width:4px
&lt;/pre>
&lt;p>在這種狀態下即使疊加提交，也沒有任何分支會追蹤那個新的提交。切換到其他分支的瞬間，新的提交就會迷路。&lt;/p>
&lt;h3 id="解決方案將其儲存為新的分支">解決方案：將其儲存為新的分支
&lt;/h3>&lt;p>只要在目前所在的位置建立新分支就能解決。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 在目前的 HEAD 位置建立新分支，並切換過去&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git checkout -b feature/recovered-work
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;hr>
&lt;h2 id="8-案例研究-5合併與重定基底時的衝突解決">8. 案例研究 5：合併與重定基底時的衝突解決
&lt;/h2>&lt;p>&lt;strong>【狀況】&lt;/strong>
執行 &lt;code>git merge&lt;/code> 或 &lt;code>git rebase&lt;/code> 時，顯示 &lt;code>CONFLICT (content)&lt;/code>，並且程序被中斷。&lt;/p>
&lt;h3 id="合併merge與重定基底rebase的差異">合併（Merge）與重定基底（Rebase）的差異
&lt;/h3>&lt;ol>
&lt;li>&lt;strong>Merge（合併）&lt;/strong>
使用兩個分支的最新提交與共同的祖先進行三方合併（3-way merge），並建立一個合併提交。&lt;/li>
&lt;li>&lt;strong>Rebase（重定基底）&lt;/strong>
將目前分支的提交暫存，並重新套用在目標分支的頂端。歷史記錄將會變成一條直線。&lt;/li>
&lt;/ol>
&lt;pre class="mermaid">
gitGraph
commit id: &amp;#34;M1&amp;#34;
commit id: &amp;#34;M2&amp;#34;
branch feature
checkout feature
commit id: &amp;#34;F1&amp;#34;
commit id: &amp;#34;F2&amp;#34;
checkout main
commit id: &amp;#34;M3&amp;#34;
merge feature
&lt;/pre>
&lt;h3 id="衝突的解決方法">衝突的解決方法
&lt;/h3>&lt;p>發生衝突的檔案中，會插入如下的標記。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&lt;/span> &lt;span class="nx">HEAD&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">apiUrl&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;https://api.production.example.com&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">=======&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">apiUrl&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;https://api.staging.example.com&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="nx">feature&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">api&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>解決步驟非常簡單。&lt;/p>
&lt;ol>
&lt;li>&lt;strong>刪除標記，並修正為正確的程式碼。&lt;/strong>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">apiUrl&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">env&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">NODE_ENV&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s1">&amp;#39;production&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">?&lt;/span> &lt;span class="s2">&amp;#34;https://api.production.example.com&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;https://api.staging.example.com&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;/li>
&lt;li>&lt;strong>將已解決的檔案加入暫存區（staging）。&lt;/strong>
&lt;code>git add&lt;/code> 具有「告訴 Git 衝突已經解決」的作用。
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ git add index.js
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;/li>
&lt;li>&lt;strong>完成程序。&lt;/strong>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 合併的情況&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git commit -m &lt;span class="s2">&amp;#34;Resolve merge conflict in index.js&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 重定基底的情況&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git rebase --continue
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;/li>
&lt;/ol>
&lt;p>如果感到慌亂，隨時可以使用 &lt;code>$ git merge --abort&lt;/code> 或 &lt;code>$ git rebase --abort&lt;/code> 來中止。&lt;/p>
&lt;hr>
&lt;h2 id="9-案例研究-6提交歷史亂七八糟-git-rebase--i">9. 案例研究 6：提交歷史亂七八糟！ &lt;code>git rebase -i&lt;/code>
&lt;/h2>&lt;p>&lt;strong>【狀況】&lt;/strong>
產生了大量如「修正錯字」、「還是修改一下」、「新增測試」等瑣碎的提交。如果就這樣合併到 &lt;code>main&lt;/code>，歷史記錄會變得非常髒亂。&lt;/p>
&lt;h3 id="解決方案互動式重定基底">解決方案：互動式重定基底
&lt;/h3>&lt;p>使用 &lt;code>git rebase -i&lt;/code>（interactive），可以改變過去提交的順序、將多個提交合併為一個（squash），或是修改提交訊息。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 整理最近 3 個提交&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git rebase -i HEAD~3
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>編輯器會開啟，並顯示如下：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">pick 1a2b3c4 修正錯字
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pick 2b3c4d5 還是修改一下
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pick 3c4d5e6 新增測試
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>將其修改為以下內容：&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">pick 1a2b3c4 實作功能 X
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">squash 2b3c4d5 還是修改一下
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">squash 3c4d5e6 新增測試
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>存檔並關閉後，這 3 個提交就會漂亮地整合成 1 個。&lt;/p>
&lt;hr>
&lt;h2 id="10-案例研究-7不知道什麼時候混入了-bug-git-bisect">10. 案例研究 7：不知道什麼時候混入了 bug！ &lt;code>git bisect&lt;/code>
&lt;/h2>&lt;p>&lt;strong>【狀況】&lt;/strong>
目前的 &lt;code>main&lt;/code> 分支存在 bug，但在 1 個月前發布時是正常的。想要找出是哪個提交混入了 bug，但有超過 100 個提交，靠手動尋找根本不可能！&lt;/p>
&lt;h3 id="解決方案使用二元搜尋來定位-bug">解決方案：使用二元搜尋來定位 bug
&lt;/h3>&lt;p>Git 內建了一個能透過數學上的二元搜尋（Binary Search）來找出混入 bug 的提交的工具。因為時間複雜度是 $\mathcal{O}(\log N)$，即使有 1000 個提交，大約也只需要 10 次測試就能定位出來。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 開始搜尋&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git bisect start
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 目前的提交有 bug（bad）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git bisect bad
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 1 個月前（例如雜湊值是 a1b2c3d）是正常的（good）&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git bisect good a1b2c3d
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Git 會自動簽出中間的提交，所以請執行測試&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 如果測試成功：&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git bisect good
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 如果測試失敗：&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git bisect bad
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>只要重複這個過程，Git 就會準確地告訴你：「這個提交是第一個 Bad 提交」。結束後使用 &lt;code>$ git bisect reset&lt;/code> 即可恢復原狀。&lt;/p>
&lt;hr>
&lt;h2 id="11-終極安全網git-reflog">11. 終極安全網：&lt;code>git reflog&lt;/code>
&lt;/h2>&lt;p>對於 Git 中所有「失誤」的最終奧義就是 &lt;code>git reflog&lt;/code>。Git 會將本機的所有操作歷史（HEAD 的移動歷史）完整記錄一段時間。即使不小心刪除了分支，或是做了錯誤的重設，只要使用 &lt;code>git reflog&lt;/code> 找出過去的雜湊值，然後對其執行 &lt;code>git reset --hard&lt;/code> 就能復原。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ git reflog
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">9f8a7b6 &lt;span class="o">(&lt;/span>HEAD -&amp;gt; main&lt;span class="o">)&lt;/span> HEAD@&lt;span class="o">{&lt;/span>0&lt;span class="o">}&lt;/span>: commit: Add new feature
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1a2b3c4 HEAD@&lt;span class="o">{&lt;/span>1&lt;span class="o">}&lt;/span>: reset: moving to HEAD~1
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="12-結語">12. 結語
&lt;/h2>&lt;p>本文非常詳細地解說了 Git 初學者容易犯的錯誤、其背後的 Git 運作機制，以及解決方法。提交到錯誤的分支、取消已推送的提交、靈活運用 Stash、從 Detached HEAD 狀態生還，以及解決衝突。在所有這些操作中，最重要的是去想像「Git 在背後是如何操作物件和指標的」。&lt;/p>
&lt;p>由數學公式表示的嚴謹 Diff 演算法計算出檔案的差異，並透過密碼學的雜湊函數來確保歷史的一致性。只要理解了這個優美的設計理念，你就會明白 Git 絕對不是一個「莫名其妙的黑盒子」，而是能堅固保護你原始碼的最強盾牌。&lt;/p>
&lt;p>下次當你覺得「搞砸了！」的時候，請不要慌張地關閉終端機，而是深呼吸並輸入 &lt;code>git status&lt;/code>。Git 一定會為你提供復原的提示。&lt;/p></description></item></channel></rss>