<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Version Control on kenji.blog</title><link>http://kenji.blog/en/tags/version-control/</link><description>Recent content in Version Control on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>en</language><copyright>kenjinote</copyright><lastBuildDate>Sun, 13 Sep 2026 09:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/en/tags/version-control/index.xml" rel="self" type="application/rss+xml"/><item><title>[Git Commands] The Difference Between rebase and merge, and How to Use Them Properly in Practice</title><link>http://kenji.blog/en/p/git-rebase-vs-merge-practical-guide/</link><pubDate>Sun, 13 Sep 2026 09:00:00 +0900</pubDate><guid>http://kenji.blog/en/p/git-rebase-vs-merge-practical-guide/</guid><description>&lt;img src="http://kenji.blog/p/git-rebase-vs-merge-practical-guide/img/eyecatch.jpg" alt="Featured image of post [Git Commands] The Difference Between rebase and merge, and How to Use Them Properly in Practice" />&lt;h1 id="1-introduction-why-merge-vs-rebase-is-an-eternal-question">1. Introduction: Why &amp;ldquo;merge vs. rebase&amp;rdquo; is an Eternal Question
&lt;/h1>&lt;p>Git is an essential version control system in modern software development. When multiple developers make changes to a codebase simultaneously, Git&amp;rsquo;s powerful branching model proves its worth. However, in team development, the debate over &amp;ldquo;whether to use &lt;code>merge&lt;/code> or &lt;code>rebase&lt;/code>&amp;rdquo; is a topic that constantly troubles developers, from beginners to experts.&lt;/p>
&lt;p>In this article, we will delve deeply into the mechanical differences between &lt;code>git merge&lt;/code> and &lt;code>git rebase&lt;/code> by unraveling Git&amp;rsquo;s internal structure, such as the DAG (Directed Acyclic Graph) and the mathematical properties of commit hashes. Then, we will thoroughly explain how to use them properly in practice, incorporating specific workflows. By understanding not just the commands but also the calculations Git performs behind the scenes, you will eliminate the fear of conflicts and become able to build a clean, traceable history.&lt;/p>
&lt;hr>
&lt;h1 id="2-gits-internal-structure-commit-hashes-and-the-object-model">2. Git&amp;rsquo;s Internal Structure: Commit Hashes and the Object Model
&lt;/h1>&lt;p>To understand how Git integrates history, you first need to know how Git stores data. Git does not merely save the diffs (patches) of file changes; it saves a snapshot of the entire file system at a given point in time.&lt;/p>
&lt;h2 id="21-cryptographic-properties-of-commit-hashes">2.1 Cryptographic Properties of Commit Hashes
&lt;/h2>&lt;p>Each commit in Git is uniquely identified by a 40-character hexadecimal number generated by the SHA-1 (Secure Hash Algorithm 1) hash function, which is calculated based on its contents. A commit object consists of the following elements:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Pointer to a Tree object&lt;/strong>: A snapshot of the directory structure and files (Blobs) at that time&lt;/li>
&lt;li>&lt;strong>Pointers to parent commits&lt;/strong>: The hash values of one or more parent commits (the initial commit has no parents, while a merge commit has two or more parents)&lt;/li>
&lt;li>&lt;strong>Author information&lt;/strong>: The person who wrote the code and the date/time&lt;/li>
&lt;li>&lt;strong>Committer information&lt;/strong>: The person who created/applied the commit and the date/time&lt;/li>
&lt;li>&lt;strong>Commit message&lt;/strong>: Text explaining the intent of the changes&lt;/li>
&lt;/ol>
&lt;p>Expressed mathematically, the hash value $H(C)$ for a commit object $C$ is defined as follows:&lt;/p>
$$
H(C) = \text{SHA-1}( \text{tree} \parallel \text{parent} \parallel \text{author} \parallel \text{committer} \parallel \text{message} )
$$&lt;p>Here, $\parallel$ represents the concatenation of data. Due to the characteristics of hash functions, even changing a single character in the commit message or having a different parent commit generates an entirely different hash value. In other words, &lt;strong>commits are immutable&lt;/strong>. The reason why &lt;code>rebase&lt;/code> (discussed later) is described as &amp;ldquo;rewriting history&amp;rdquo; is that it actually &amp;ldquo;creates new commits with similar content but different hash values.&amp;rdquo;&lt;/p>
&lt;p>The size of the hash space is $2^{160}$, and the probability $P$ of a collision (different commits having the same hash value) can be approximated using the theory of the Birthday Paradox as follows ($n$ is the number of commits):&lt;/p>
$$
P(\text{collision}) \approx 1 - \exp\left(-\frac{n^2}{2 \times 2^{160}}\right)
$$&lt;p>This probability is extremely low, and practically speaking, it is almost impossible for Git commit hashes to collide.&lt;/p>
&lt;hr>
&lt;h1 id="3-graph-theory-and-dag-the-mathematical-model-of-git-history">3. Graph Theory and DAG: The Mathematical Model of Git History
&lt;/h1>&lt;p>Git&amp;rsquo;s commit history is modeled as a &amp;ldquo;Directed Acyclic Graph (DAG)&amp;rdquo; in graph theory.&lt;/p>
&lt;h2 id="31-what-is-a-dag-directed-acyclic-graph">3.1 What is a DAG (Directed Acyclic Graph)?
&lt;/h2>&lt;p>In a graph $G = (V, E)$, $V$ is a set of commits (vertices), and $E$ is a set of directed edges indicating the parent-child relationship between commits. In Git, the direction of the edges points from the &amp;ldquo;child commit to the parent commit.&amp;rdquo; This is because new commits hold a pointer to past commits.&lt;/p>
&lt;pre class="mermaid">
graph BT
A[&amp;#34;Commit A (Initial)&amp;#34;]
B[&amp;#34;Commit B&amp;#34;]
C[&amp;#34;Commit C (Main)&amp;#34;]
D[&amp;#34;Commit D (Feature)&amp;#34;]
E[&amp;#34;Commit E (Merge)&amp;#34;]
B --&amp;gt; A
C --&amp;gt; B
D --&amp;gt; B
E --&amp;gt; C
E --&amp;gt; D
&lt;/pre>
&lt;p>The most significant feature of a DAG is that &amp;ldquo;there are no cycles.&amp;rdquo; This ensures that the algorithm tracing the commit history never falls into an infinite loop and can reliably reach the end (the initial commit).&lt;/p>
&lt;h2 id="32-topological-sort-and-the-order-of-history">3.2 Topological Sort and the Order of History
&lt;/h2>&lt;p>When displaying history with commands like &lt;code>git log&lt;/code>, the DAG is ordered as a one-dimensional list using a Topological Sort algorithm. For any directed edge $u \to v$ in the DAG ($u$ is a child of $v$), it rearranges them so that $u$ comes before $v$ in the list.&lt;/p>
&lt;hr>
&lt;h1 id="4-the-mechanisms-and-types-of-git-merge">4. The Mechanisms and Types of git merge
&lt;/h1>&lt;p>The most basic command to integrate changes from a branch is &lt;code>git merge&lt;/code>. However, Git automatically selects different merge strategies depending on the current state.&lt;/p>
&lt;h2 id="41-fast-forward-merge---ff">4.1 Fast-Forward Merge (&amp;ndash;ff)
&lt;/h2>&lt;p>If the branch you are merging into (e.g., &lt;code>main&lt;/code>) is a direct ancestor of the branch you are merging from (e.g., &lt;code>feature&lt;/code>), Git performs a &amp;ldquo;Fast-Forward&amp;rdquo; merge. This is an operation that simply moves the branch pointer forward without creating a new commit.&lt;/p>
&lt;pre class="mermaid">
gitGraph
commit id: &amp;#34;A&amp;#34;
commit id: &amp;#34;B&amp;#34;
branch feature
checkout feature
commit id: &amp;#34;C&amp;#34;
commit id: &amp;#34;D&amp;#34;
checkout main
merge feature
&lt;/pre>
&lt;p>A Fast-Forward merge keeps the history straight, but it has the disadvantage of losing the context of &amp;ldquo;which group of commits were grouped together as a single feature development (feature).&amp;rdquo;&lt;/p>
&lt;h2 id="42-non-fast-forward-merge---no-ff">4.2 Non-Fast-Forward Merge (&amp;ndash;no-ff)
&lt;/h2>&lt;p>If you explicitly specify &lt;code>git merge --no-ff&lt;/code>, it will always create a new &amp;ldquo;merge commit,&amp;rdquo; even in a situation where a Fast-Forward is possible. A merge commit is a special commit that has two parents.&lt;/p>
&lt;pre class="mermaid">
gitGraph
commit id: &amp;#34;A&amp;#34;
commit id: &amp;#34;B&amp;#34;
branch feature
checkout feature
commit id: &amp;#34;C&amp;#34;
commit id: &amp;#34;D&amp;#34;
checkout main
commit id: &amp;#34;Main Work 1&amp;#34;
merge feature type: NORMAL
&lt;/pre>
&lt;p>The advantage of this method is that the existence and history of the feature branch clearly remain on the DAG. If an issue occurs, you can safely undo (revert) the entire feature at once by running &lt;code>git revert -m 1 &amp;lt;merge commit hash&amp;gt;&lt;/code>.&lt;/p>
&lt;h2 id="43-3-way-merge-algorithm">4.3 3-Way Merge Algorithm
&lt;/h2>&lt;p>When both the target and source branches have their own unique commits, Git performs a 3-way merge. At this time, Git traverses the DAG and finds the &amp;ldquo;Lowest Common Ancestor (LCA)&amp;rdquo; of the two branches.&lt;/p>
&lt;p>The time complexity $T_{\text{LCA}}$ of the algorithm to find the LCA can be executed in linear time relative to the number of vertices $|V|$ and edges $|E|$:&lt;/p>
$$
T_{\text{LCA}} = \mathcal{O}(|V| + |E|)
$$&lt;p>Git compares three states: the &amp;ldquo;state of the LCA,&amp;rdquo; the &amp;ldquo;state of the current branch,&amp;rdquo; and the &amp;ldquo;state of the other branch.&amp;rdquo; If the changes do not conflict, it automatically generates a merge commit.&lt;/p>
&lt;hr>
&lt;h1 id="5-the-mechanism-of-git-rebase-and-reconstructing-history">5. The Mechanism of git rebase and Reconstructing History
&lt;/h1>&lt;p>While &lt;code>git merge&lt;/code> &amp;ldquo;integrates&amp;rdquo; history, &lt;code>git rebase&lt;/code> &amp;ldquo;reconstructs (re-attaches)&amp;rdquo; history.&lt;/p>
&lt;h2 id="51-the-movements-behind-rebase">5.1 The Movements Behind Rebase
&lt;/h2>&lt;p>The internal operation when rebasing the &lt;code>feature&lt;/code> branch onto the &lt;code>main&lt;/code> branch (&lt;code>git rebase main&lt;/code>) is as follows:&lt;/p>
&lt;ol>
&lt;li>Find the lowest common ancestor (LCA) of the &lt;code>feature&lt;/code> branch and the &lt;code>main&lt;/code> branch.&lt;/li>
&lt;li>Save the diffs of the commits from the LCA to the tip of the &lt;code>feature&lt;/code> branch in a temporary area.&lt;/li>
&lt;li>Move the pointer of the &lt;code>feature&lt;/code> branch to the tip of the &lt;code>main&lt;/code> branch.&lt;/li>
&lt;li>Apply the saved diffs one by one sequentially onto the new base (the tip of &lt;code>main&lt;/code>) (Cherry-Pick) to generate new commits.&lt;/li>
&lt;/ol>
&lt;pre class="mermaid">
graph TD
A[&amp;#34;Commit A&amp;#34;] --&amp;gt; B[&amp;#34;Commit B&amp;#34;]
B --&amp;gt; C[&amp;#34;Commit C (Main)&amp;#34;]
B --&amp;gt; D[&amp;#34;Commit D (Old Feature)&amp;#34;]
D -.-&amp;gt; E[&amp;#34;Commit D&amp;#39; (New Feature)&amp;#34;]
C --&amp;gt; E
style D stroke-dasharray: 5 5, fill: #f9f9f9, color: #999
&lt;/pre>
&lt;p>What&amp;rsquo;s important here is that the commit $D'$ generated by the rebase &lt;strong>has a completely different hash value because its parent commit is different&lt;/strong> from the original commit $D$ (refer to the definition of the hash function $H(C)$ mentioned above).&lt;/p>
&lt;h2 id="52-interactive-rebase">5.2 Interactive Rebase
&lt;/h2>&lt;p>Using &lt;code>git rebase -i&lt;/code> (or &lt;code>--interactive&lt;/code>), you can manipulate the commit history as you wish. This is the most powerful tool for organizing local history.&lt;/p>
&lt;ul>
&lt;li>&lt;code>pick&lt;/code>: Use the commit as is&lt;/li>
&lt;li>&lt;code>reword&lt;/code>: Modify only the commit message&lt;/li>
&lt;li>&lt;code>edit&lt;/code>: Pause to amend the contents of the commit&lt;/li>
&lt;li>&lt;code>squash&lt;/code>: Meld this commit into the previous commit, combining their messages&lt;/li>
&lt;li>&lt;code>fixup&lt;/code>: Same as &lt;code>squash&lt;/code>, but discards the message of this commit&lt;/li>
&lt;li>&lt;code>drop&lt;/code>: Completely remove the commit&lt;/li>
&lt;/ul>
&lt;p>Mathematically speaking, if a branch has $N$ commits, the permutations (variations of linear history) $P$ that can be generated by reordering during a rebase are as follows:&lt;/p>
$$
P = N!
$$&lt;p>Git gives developers the freedom of $N!$ ways, allowing them to keep the history in a logical and beautiful state.&lt;/p>
&lt;hr>
&lt;h1 id="6-the-golden-rule-of-rebase">6. The Golden Rule of Rebase
&lt;/h1>&lt;p>&lt;code>rebase&lt;/code> is incredibly powerful, but there is one absolute rule.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>&amp;ldquo;Never rebase public history&amp;rdquo;&lt;/strong>
&lt;em>(Never rebase public history)&lt;/em>&lt;/p>
&lt;/blockquote>
&lt;h2 id="61-why-should-you-not-rebase-public-history">6.1 Why should you not rebase public history?
&lt;/h2>&lt;p>Git is distributed. The commits you pushed to &lt;code>origin/main&lt;/code> are also cloned to the local repositories of other developers. If you rewrite the history by rebasing commits that have already been pushed, and force-overwrite them with &lt;code>git push --force&lt;/code>, what will happen?&lt;/p>
&lt;p>The DAGs on other developers&amp;rsquo; local machines and the remote DAG will fundamentally diverge. When other developers run &lt;code>git pull&lt;/code>, Git will forcefully try to merge the groups of commits with different histories, causing a massive amount of conflicts and duplicate commits (commits with the same changes but different hashes), and the repository will fall into a state of panic.&lt;/p>
&lt;p>The ironclad rule is to perform rebases &lt;strong>&amp;ldquo;only on local branches that you haven&amp;rsquo;t shared with anyone yet.&amp;rdquo;&lt;/strong>&lt;/p>
&lt;hr>
&lt;h1 id="7-resolving-conflicts-and-git-rebase---continue">7. Resolving Conflicts and git rebase &amp;ndash;continue
&lt;/h1>&lt;p>When multiple people change the same part of the same file, a conflict occurs. The process of resolving conflicts differs between &lt;code>merge&lt;/code> and &lt;code>rebase&lt;/code>.&lt;/p>
&lt;h2 id="71-conflict-resolution-in-merge">7.1 Conflict Resolution in Merge
&lt;/h2>&lt;p>In the case of &lt;code>git merge&lt;/code>, conflict resolution happens &lt;strong>only once&lt;/strong>. You fix all the conflicting parts at once right before creating the final merge commit.&lt;/p>
&lt;h2 id="72-conflict-resolution-in-rebase">7.2 Conflict Resolution in Rebase
&lt;/h2>&lt;p>In the case of &lt;code>git rebase&lt;/code>, because commits are re-applied one by one, &lt;strong>conflicts can occur at each commit&lt;/strong>.&lt;/p>
&lt;p>When a conflict occurs during a rebase, Git pauses the process. The flow of resolution is as follows:&lt;/p>
&lt;ol>
&lt;li>Open your editor or IDE (like VS Code) and manually fix the conflict markers (&lt;code>&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&lt;/code>, &lt;code>======&lt;/code>, &lt;code>&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&lt;/code>).&lt;/li>
&lt;li>Add the modified files to the index:
&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 &amp;lt;modified_file&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;/li>
&lt;li>Do not create a commit, just resume the rebase process:
&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 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>If you want to cancel the rebase itself and return to the original state, run the following command:&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-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">git rebase --abort
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>(* If you don&amp;rsquo;t need to resolve the conflict and want to skip that entire commit, use &lt;code>git rebase --skip&lt;/code>)&lt;/p>
&lt;hr>
&lt;h1 id="8-proper-usage-in-practice-workflow-practice">8. Proper Usage in Practice (Workflow Practice)
&lt;/h1>&lt;p>So, how should you properly use &lt;code>merge&lt;/code> and &lt;code>rebase&lt;/code> in an actual development environment? Here we introduce the most standard and safe approach.&lt;/p>
&lt;h2 id="81-scenario-1-organizing-local-working-history-using-rebase">8.1 [Scenario 1] Organizing Local Working History (Using Rebase)
&lt;/h2>&lt;p>Suppose you are developing on a feature branch, and many minor commits (&amp;ldquo;typo fix&amp;rdquo;, &amp;ldquo;temporary save&amp;rdquo;, etc.) have piled up. Before submitting a Pull Request (PR), you use interactive rebase to organize these into meaningful units.&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">&lt;span class="c1"># Execute while on the feature branch&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git rebase -i HEAD~5
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># (The editor opens, and you clean up the history using squash and fixup)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>By doing this, you can create a beautiful commit history whose intent is easy for reviewers to understand.&lt;/p>
&lt;h2 id="82-scenario-2-catching-up-with-the-latest-main-branch-using-rebase">8.2 [Scenario 2] Catching Up with the Latest main Branch (Using Rebase)
&lt;/h2>&lt;p>If development drags on and other people&amp;rsquo;s changes keep getting merged into the &lt;code>main&lt;/code> branch, your &lt;code>feature&lt;/code> branch will become outdated. In this case, you rebase your &lt;code>feature&lt;/code> branch onto the latest &lt;code>main&lt;/code> to catch up.&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-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Fetch the latest information from main&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git fetch origin
&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"># Rebase the feature branch onto the latest main&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git rebase origin/main
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>This keeps the history linear and prevents conflicts during subsequent merges. It also prevents the generation of unnecessary merge commits (&amp;ldquo;Merge branch &amp;lsquo;main&amp;rsquo; into feature&amp;rdquo;).&lt;/p>
&lt;h2 id="83-scenario-3-integrating-completed-features-using-merge">8.3 [Scenario 3] Integrating Completed Features (Using Merge)
&lt;/h2>&lt;p>Development on the &lt;code>feature&lt;/code> branch is complete, and it is finally the phase to integrate it into the &lt;code>main&lt;/code> branch. Here, we use &lt;strong>&lt;code>git merge --no-ff&lt;/code>&lt;/strong> (This is the same as choosing &amp;ldquo;Create a merge commit&amp;rdquo; in a Pull Request on GitHub, etc.).&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 checkout main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git merge --no-ff feature -m &lt;span class="s2">&amp;#34;Merge feature: Implement user login functionality&amp;#34;&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;p>By doing this, a node in history (a merge commit) saying &amp;ldquo;a single feature was merged here&amp;rdquo; is left on the DAG of the &lt;code>main&lt;/code> branch. When looking back at the history later, it becomes easier to trace the code by feature units.&lt;/p>
&lt;hr>
&lt;h1 id="9-conclusion-summary">9. Conclusion (Summary)
&lt;/h1>&lt;p>In Git operations, extreme approaches like &amp;ldquo;doing everything with Merge&amp;rdquo; or &amp;ldquo;making everything linear with Rebase&amp;rdquo; each have their pros and cons.&lt;/p>
&lt;p>The best practice in real-world environments is a hybrid approach: &lt;strong>&amp;ldquo;Cleanly organize local private history with rebase, and preserve context in public integration history with merge &amp;ndash;no-ff.&amp;rdquo;&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Local (Personal workspace)&lt;/strong>: Use &lt;code>rebase&lt;/code> to eliminate useless commits, catch up with the latest mainline, and maintain a linear history.&lt;/li>
&lt;li>&lt;strong>Global (Shared workspace)&lt;/strong>: Use &lt;code>merge --no-ff&lt;/code> to record the existence of a feature branch as a merge commit on the DAG, making reverts and tracking easier.&lt;/li>
&lt;/ul>
&lt;p>By understanding the mathematical and architectural backgrounds, such as the structure of the DAG and how hash functions work, Git commands are elevated from mere memorization to &amp;ldquo;designing history with intent.&amp;rdquo; While adhering to the golden rule of rebase, let&amp;rsquo;s select the optimal commands depending on the situation and build a clean commit history that is easy to read and maintain for the entire team.&lt;/p></description></item><item><title>A Collection of Common Mistakes and Solutions for Git Beginners (Including Conflict Resolution)</title><link>http://kenji.blog/en/p/git-beginners-mistakes-and-solutions/</link><pubDate>Sat, 12 Sep 2026 17:00:00 +0900</pubDate><guid>http://kenji.blog/en/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 A Collection of Common Mistakes and Solutions for Git Beginners (Including Conflict Resolution)" />&lt;h1 id="a-collection-of-common-mistakes-and-solutions-for-git-beginners-including-conflict-resolution">A Collection of Common Mistakes and Solutions for Git Beginners (Including Conflict Resolution)
&lt;/h1>&lt;h2 id="1-introduction-why-do-we-make-mistakes-in-git">1. Introduction: Why Do We Make Mistakes in Git?
&lt;/h2>&lt;p>In software development, Git has become as essential as air and water. However, for many beginners (and sometimes even experts), Git can feel like a &amp;ldquo;terrifying magical black box&amp;rdquo;. Commits disappear, massive amounts of changes are pushed into the wrong branch, or unfamiliar conflict error messages fill the screen&amp;hellip; Falling into these &amp;ldquo;Git traps&amp;rdquo; can completely halt your work progress and make you fear that you might destroy the source code in the worst-case scenario.&lt;/p>
&lt;p>Why is Git so difficult and prone to inducing mistakes? The biggest reason is that people use it by simply memorizing superficial commands without understanding what is happening inside Git. Git is based on a robust design philosophy as a Distributed Version Control System (DVCS), but its interface (CLI) is not always intuitive.&lt;/p>
&lt;p>This article categorizes many &amp;ldquo;screw-ups&amp;rdquo; (common mistakes) that Git beginners frequently encounter in the field into numerous cases and presents specific commands as solutions for each. However, it will not just be a simple list of commands (a cheat sheet). We will thoroughly dive deep into &amp;ldquo;why that mistake happens&amp;rdquo; and &amp;ldquo;how the data moves inside Git when you execute that command&amp;rdquo;, exploring the structure of the &lt;code>.git&lt;/code> directory, the mathematical background of the Diff algorithm working behind the scenes, and Mermaid diagrams in a volume exceeding 10,000 characters.&lt;/p>
&lt;p>By the time you finish reading this article, you should be free from the feeling that &amp;ldquo;Git is scary&amp;rdquo; and instead be convinced that &amp;ldquo;there is no more reliable partner than Git.&amp;rdquo; Now, let&amp;rsquo;s dive into the profound world of Git.&lt;/p>
&lt;hr>
&lt;h2 id="2-the-abyss-of-git-understanding-the-internal-structure-of-the-git-directory">2. The Abyss of Git: Understanding the Internal Structure of the &lt;code>.git&lt;/code> Directory
&lt;/h2>&lt;p>The first step to making many troubleshooting tasks easier is knowing how Git stores data. The hidden folder &lt;code>.git&lt;/code> that exists in the root directory of your project is exactly the heart of Git. Git is not a system that merely records file differences (patches) sequentially, but it manages data as a &lt;strong>stream of snapshots&lt;/strong>.&lt;/p>
&lt;h3 id="21-object-model-blob-tree-commit">2.1 Object Model: Blob, Tree, Commit
&lt;/h3>&lt;p>Git mainly uses three objects to represent the state of a repository. These objects are saved in &lt;code>.git/objects&lt;/code>.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Blob (Binary Large Object)&lt;/strong>
An object that stores the file content itself. File names and permission information are not included here. Pure byte sequences are compressed with zlib and identified by a SHA-1 hash value (a 40-character hexadecimal number).&lt;/li>
&lt;li>&lt;strong>Tree&lt;/strong>
An object that represents the structure of a directory. A Tree object contains pointers (SHA-1 hash values) to other Tree objects (subdirectories) and Blob objects (files), as well as their file names and access permissions. It plays a role similar to a UNIX directory.&lt;/li>
&lt;li>&lt;strong>Commit&lt;/strong>
Holds a pointer to the top-level Tree object of the entire repository at a given point in time, metadata (author, commit date and time, commit message), and a pointer to the immediately preceding commit (parent commit).&lt;/li>
&lt;/ol>
&lt;pre class="mermaid">
graph TD
Commit1[&amp;#34;Commit (Hash: 9f8a)&amp;#34;] --&amp;gt; Tree1[&amp;#34;Tree (Hash: 4b82)&amp;#34;]
Tree1 --&amp;gt; Blob1[&amp;#34;Blob (Hash: 8d7e) : index.js&amp;#34;]
Tree1 --&amp;gt; Tree2[&amp;#34;Tree (Hash: 3a2c) : src/&amp;#34;]
Tree2 --&amp;gt; Blob2[&amp;#34;Blob (Hash: 5f1b) : app.js&amp;#34;]
&lt;/pre>
&lt;h3 id="22-the-true-identity-of-head-and-references-refs">2.2 The True Identity of HEAD and References (Refs)
&lt;/h3>&lt;p>When working with Git, you frequently see the word &lt;code>HEAD&lt;/code>. This is a &lt;strong>Symbolic Reference&lt;/strong> that points to the currently checked-out branch (or commit).
If you open the &lt;code>.git/HEAD&lt;/code> file in a text editor, you will see a string like the following:&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>This means that &amp;ldquo;the current state is at the tip of the &lt;code>main&lt;/code> branch.&amp;rdquo; And when you open &lt;code>.git/refs/heads/main&lt;/code>, there is a 40-character SHA-1 hash written there, which points to the latest Commit object.
A branch in Git is simply a lightweight pointer (file) that points to a specific commit. Just knowing this fact removes the fear that &amp;ldquo;if I delete a branch, will all the files be deleted?&amp;rdquo;.&lt;/p>
&lt;hr>
&lt;h2 id="3-deciphering-git-with-mathematics-diff-algorithms-and-hash-functions">3. Deciphering Git with Mathematics: Diff Algorithms and Hash Functions
&lt;/h2>&lt;p>When Git detects conflicts or displays file differences, sophisticated algorithms are running internally.&lt;/p>
&lt;h3 id="31-myers-diff-algorithm">3.1 Myers&amp;rsquo; Diff Algorithm
&lt;/h3>&lt;p>Git&amp;rsquo;s default difference detection algorithm is the one devised by Eugene W. Myers. When there are two text files $A$ and $B$, the problem of finding the &amp;ldquo;minimum edit script (insertions and deletions)&amp;rdquo; to transform $A$ into $B$ can be modeled as a shortest path problem in graph theory.&lt;/p>
&lt;p>Let the lengths of the strings be $N$ and $M$ respectively, and the sum be $V = N + M$. Myers&amp;rsquo; algorithm searches for the Edit Distance $D$. The time complexity of this algorithm is expressed by the following formula:&lt;/p>
$$ \mathcal{O}(V \cdot D) $$&lt;p>Here, if the difference between the files is small (that is, $D$ is small), the algorithm operates very fast at $\mathcal{O}(V)$. However, if the files are completely different, $D \approx V$, and the worst-case time complexity becomes $\mathcal{O}(V^2)$.&lt;/p>
&lt;h3 id="32-patience-diff-and-histogram-diff">3.2 Patience Diff and Histogram Diff
&lt;/h3>&lt;p>While Myers&amp;rsquo; algorithm is excellent, it can sometimes generate differences that are not intuitive (do not make sense) to humans, such as when the order of functions or classes is significantly rearranged. To solve this, Git implements &lt;code>Patience Diff&lt;/code> and &lt;code>Histogram Diff&lt;/code>.&lt;/p>
&lt;p>Patience Diff focuses on &amp;ldquo;unique lines that appear exactly once in both files&amp;rdquo; and finds their Longest Common Subsequence (LCS). Letting the number of unique elements be $U$, the computation of the LCS can be solved with the following time complexity:&lt;/p>
$$ \mathcal{O}(U \log U) $$&lt;p>When you feel that conflict resolution is difficult, using &lt;code>git diff --histogram&lt;/code> or specifying this algorithm in the merge strategy (&lt;code>git merge -s recursive -X histogram&lt;/code>) is one solution.&lt;/p>
&lt;h3 id="33-sha-1-and-collision-probability">3.3 SHA-1 and Collision Probability
&lt;/h3>&lt;p>Git manages all objects with SHA-1 hash values. The size of the hash space is $2^{160}$. Approximating the probability of hash collision (different contents having the same hash value) using the Birthday Paradox, the number of objects $k$ required for the collision probability $p$ to reach 50% is as follows:&lt;/p>
$$ k \approx \sqrt{2 \ln(2)} \cdot 2^{80} \approx 1.2 \times 2^{80} $$&lt;p>This is an astronomical number, and the probability of an unintended collision occurring in normal software development is practically zero. Therefore, Git operates by trusting the hash value as an &amp;ldquo;absolute unique ID&amp;rdquo;.&lt;/p>
&lt;hr>
&lt;h2 id="4-case-study-1-i-committed-to-the-wrong-branch">4. Case Study 1: I Committed to the Wrong Branch!
&lt;/h2>&lt;p>&lt;strong>[Situation]&lt;/strong>
Without realizing that I was working on the &lt;code>main&lt;/code> branch, I actively wrote code for a new feature and even went as far as doing a &lt;code>git commit&lt;/code>. I was supposed to create a &lt;code>feature/login&lt;/code> branch and work there!&lt;/p>
&lt;h3 id="solution-git-reset-and-creating-a-branch">Solution: &lt;code>git reset&lt;/code> and Creating a Branch
&lt;/h3>&lt;p>In Git, a commit is an independent object, and a branch is just a pointer. Therefore, you can instantly solve this with the operation: &amp;ldquo;create a new branch and then rewind the pointer of the current branch.&amp;rdquo;&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. Create a new branch pointing to the current commit (the mistakenly created commit)&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. Rewind the pointer of the main branch to the previous commit (HEAD~1)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Using --keep allows you to safely reset while maintaining uncommitted changes in the working directory.&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. Switch to the correct branch&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="diagram-what-happened-internally">Diagram: What Happened Internally?
&lt;/h3>&lt;p>Let&amp;rsquo;s visualize the movement of branch pointers at this time using a Mermaid &lt;code>gitGraph&lt;/code>.&lt;/p>
&lt;pre class="mermaid">
gitGraph
commit id: &amp;#34;Initial commit&amp;#34;
commit id: &amp;#34;Bugfix&amp;#34;
commit id: &amp;#34;Mistaken Commit&amp;#34; type: HIGHLIGHT
branch feature/login
checkout feature/login
checkout main
&lt;/pre>
&lt;p>Initially, &lt;code>main&lt;/code> and &lt;code>HEAD&lt;/code> were pointing to &amp;ldquo;Mistaken Commit&amp;rdquo;, but with &lt;code>git branch feature/login&lt;/code>, a new pointer is created there. After that, by using &lt;code>git reset&lt;/code>, only the &lt;code>main&lt;/code> pointer returns to the &amp;ldquo;Bugfix&amp;rdquo; position. The objects themselves are not deleted at all.&lt;/p>
&lt;hr>
&lt;h2 id="5-case-study-2-i-want-to-cancel-an-already-pushed-commit">5. Case Study 2: I Want to Cancel an Already Pushed Commit!
&lt;/h2>&lt;p>&lt;strong>[Situation]&lt;/strong>
I committed bug-ridden code written in a late-night frenzy, and what&amp;rsquo;s more, I published it to the remote repository with &lt;code>git push origin main&lt;/code>. I noticed a critical bug and turned pale.&lt;/p>
&lt;h3 id="solution-1-deny-history-with-git-revert-recommended--safe">Solution 1: Deny History with &lt;code>git revert&lt;/code> (Recommended / Safe)
&lt;/h3>&lt;p>In team development, tampering with the history of already pushed commits using &lt;code>git reset&lt;/code> or similar is strictly prohibited. It causes inconsistencies with the local repositories of other developers. The correct approach is to &lt;strong>&amp;ldquo;create a new commit that does the exact opposite, completely neutralizing the changes of the mistaken commit.&amp;rdquo;&lt;/strong> This is &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"># Create a commit that neutralizes the latest commit&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;Commit message of the mistaken commit&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"># Push to remote&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;Commit A&amp;#34;
commit id: &amp;#34;Commit B (Mistake)&amp;#34;
commit id: &amp;#34;Revert Commit B&amp;#34; type: REVERSE
&lt;/pre>
&lt;p>History continues to move forward, and only the state of the code reverts to its original form.&lt;/p>
&lt;h3 id="solution-2-falsify-history-with-git-push---force-with-lease">Solution 2: Falsify History with &lt;code>git push --force-with-lease&lt;/code>
&lt;/h3>&lt;p>If you have just pushed to a branch that only you are using, rewriting history is acceptable.&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"># Reset the commit locally and fix it&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"># Forcefully overwrite the remote history&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> is a safe forced push to prevent accidents where you mistakenly overwrite someone else&amp;rsquo;s work.&lt;/p>
&lt;hr>
&lt;h2 id="6-case-study-3-i-want-to-switch-to-another-branch-mid-work-the-magic-of-stash">6. Case Study 3: I Want to Switch to Another Branch Mid-Work (The Magic of Stash)
&lt;/h2>&lt;p>&lt;strong>[Situation]&lt;/strong>
While implementing a new feature on the &lt;code>feature/A&lt;/code> branch, the source code is in a half-finished state where it doesn&amp;rsquo;t even compile yet. Suddenly, an order flies in from my boss: &amp;ldquo;There is an urgent bug in the production environment of the &lt;code>main&lt;/code> branch, so fix it right now!&amp;rdquo;&lt;/p>
&lt;h3 id="solution-shelve-with-git-stash">Solution: Shelve with &lt;code>git stash&lt;/code>
&lt;/h3>&lt;p>&lt;code>git stash&lt;/code> is a command to temporarily shelve uncommitted changes to a temporary area.&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. Shelve the changes being worked on&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. Make it possible to switch to the main branch&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"># ... (perform emergency bug fix work, commit, and push) ...&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. Return to the original branch when work is finished&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. Restore the shelved changes&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>When you execute &lt;code>git stash&lt;/code>, Git internally generates two special commit objects and saves them to a reference called &lt;code>refs/stash&lt;/code>. In other words, Stash is also ultimately an &amp;ldquo;unnamed temporary commit&amp;rdquo;.&lt;/p>
&lt;hr>
&lt;h2 id="7-case-study-4-the-terrifying-detached-head-state">7. Case Study 4: The Terrifying &amp;ldquo;Detached HEAD&amp;rdquo; State
&lt;/h2>&lt;p>&lt;strong>[Situation]&lt;/strong>
I wanted to check the code at a specific point in the past, so I executed &lt;code>git checkout 9f8a7b6&lt;/code>. Then, &lt;code>You are in 'detached HEAD' state.&lt;/code> was displayed. I committed as it was, but when I switched branches, the commit disappeared!&lt;/p>
&lt;h3 id="mechanism-of-detached-head">Mechanism of Detached HEAD
&lt;/h3>&lt;p>Normally, &lt;code>HEAD&lt;/code> points to a branch like &lt;code>refs/heads/main&lt;/code>. However, if you directly check out a specific commit, &lt;code>HEAD&lt;/code> points directly to the commit object. This is called a &lt;strong>Detached HEAD&lt;/strong>.&lt;/p>
&lt;pre class="mermaid">
graph TD
A[&amp;#34;Commit A&amp;#34;] --&amp;gt; B[&amp;#34;Commit B&amp;#34;]
B --&amp;gt; C[&amp;#34;Commit C&amp;#34;]
C --&amp;gt; D[&amp;#34;Commit D&amp;#34;]
BranchMain[&amp;#34;Branch: 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>Even if you stack commits in this state, no branch will track those new commits. The moment you switch to another branch, the new commits become lost.&lt;/p>
&lt;h3 id="solution-save-as-a-new-branch">Solution: Save as a New Branch
&lt;/h3>&lt;p>It will be resolved if you create a new branch at your current location.&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"># Create a new branch at the current HEAD position and switch to it&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-case-study-5-conflict-resolution-in-merge-and-rebase">8. Case Study 5: Conflict Resolution in Merge and Rebase
&lt;/h2>&lt;p>&lt;strong>[Situation]&lt;/strong>
When I executed &lt;code>git merge&lt;/code> or &lt;code>git rebase&lt;/code>, &lt;code>CONFLICT (content)&lt;/code> was displayed, and the process was interrupted.&lt;/p>
&lt;h3 id="difference-between-merge-and-rebase">Difference Between Merge and Rebase
&lt;/h3>&lt;ol>
&lt;li>&lt;strong>Merge&lt;/strong>
Performs a 3-way merge using the latest commits of the two branches and their common ancestor, creating a merge commit.&lt;/li>
&lt;li>&lt;strong>Rebase&lt;/strong>
Temporarily saves the commits of the current branch and reapplies them to the tip of the target. History becomes a straight line.&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="conflict-resolution-method">Conflict Resolution Method
&lt;/h3>&lt;p>Markers like the following are inserted into files where conflicts have occurred.&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>The resolution procedure is extremely simple.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Delete the markers and correct the code to the right one.&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>Add the resolved file to staging.&lt;/strong>
&lt;code>git add&lt;/code> has the role of &amp;ldquo;telling Git that the conflict has been resolved&amp;rdquo;.
&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>Complete the process.&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"># In the case of merge&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"># In the case of rebase&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>If you panic, you can abort at any time with &lt;code>$ git merge --abort&lt;/code> or &lt;code>$ git rebase --abort&lt;/code>.&lt;/p>
&lt;hr>
&lt;h2 id="9-case-study-6-the-commit-history-is-a-mess-git-rebase--i">9. Case Study 6: The Commit History is a Mess! &lt;code>git rebase -i&lt;/code>
&lt;/h2>&lt;p>&lt;strong>[Situation]&lt;/strong>
A large number of minor commits like &amp;ldquo;Fix typo&amp;rdquo;, &amp;ldquo;Fix again&amp;rdquo;, and &amp;ldquo;Add test&amp;rdquo; have occurred. If merged into &lt;code>main&lt;/code> like this, the history will be messy.&lt;/p>
&lt;h3 id="solution-interactive-rebase">Solution: Interactive Rebase
&lt;/h3>&lt;p>Using &lt;code>git rebase -i&lt;/code> (interactive), you can rearrange the order of past commits, combine multiple commits into one (squash), or modify commit messages.&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"># Organize the last 3 commits&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>An editor opens, and it displays as follows:&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 Fix typo
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pick 2b3c4d5 Fix again
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pick 3c4d5e6 Add test
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Rewrite this as follows:&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 Implement feature X
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">squash 2b3c4d5 Fix again
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">squash 3c4d5e6 Add test
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>When you save and close, these three commits are beautifully integrated into one.&lt;/p>
&lt;hr>
&lt;h2 id="10-case-study-7-i-dont-know-when-the-bug-was-introduced-git-bisect">10. Case Study 7: I Don&amp;rsquo;t Know When the Bug Was Introduced! &lt;code>git bisect&lt;/code>
&lt;/h2>&lt;p>&lt;strong>[Situation]&lt;/strong>
There is a bug in the current &lt;code>main&lt;/code> branch, but it was normal when released a month ago. I want to identify which commit introduced the bug, but there are over 100 commits, making it impossible to do manually!&lt;/p>
&lt;h3 id="solution-identifying-bugs-via-binary-search">Solution: Identifying Bugs via Binary Search
&lt;/h3>&lt;p>Git has a built-in tool that finds the commit where a bug was introduced through a mathematical Binary Search. Since the time complexity is $\mathcal{O}(\log N)$, even with 1000 commits, it can be identified in about 10 tests.&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"># Start the search&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"># The current commit has a 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"># It was normal 1 month ago (for example, hash 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 will automatically checkout a middle commit, so run your test&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># If the test succeeds:&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"># If the test fails:&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>By simply repeating this, Git will accurately tell you, &amp;ldquo;This commit is the first bad commit.&amp;rdquo; When finished, use &lt;code>$ git bisect reset&lt;/code> to return to the original state.&lt;/p>
&lt;hr>
&lt;h2 id="11-the-ultimate-safety-net-git-reflog">11. The Ultimate Safety Net: &lt;code>git reflog&lt;/code>
&lt;/h2>&lt;p>The ultimate secret technique against any &amp;ldquo;screw-up&amp;rdquo; in Git is &lt;code>git reflog&lt;/code>. Git records all local operation history (movements of HEAD) for a certain period of time. Even if you mistakenly delete a branch or perform an incorrect reset, you can recover just by finding the past hash with &lt;code>git reflog&lt;/code> and doing a &lt;code>git reset --hard&lt;/code> to it.&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-conclusion">12. Conclusion
&lt;/h2>&lt;p>We have explained in great detail the mistakes that Git beginners easily fall into, the mechanisms of Git behind them, and how to solve them. Committing to the wrong branch, reverting pushed commits, utilizing Stash, surviving from a Detached HEAD, and resolving conflicts. What is important in all of these is to imagine &amp;ldquo;what objects and pointers Git is manipulating behind the scenes.&amp;rdquo;&lt;/p>
&lt;p>File differences are calculated by strict Diff algorithms expressed in mathematical formulas, and the consistency of history is guaranteed by cryptographic hash functions. If you understand this beautiful design philosophy, you should realize that Git is by no means a &amp;ldquo;mysterious black box,&amp;rdquo; but the strongest shield that firmly protects your source code.&lt;/p>
&lt;p>Next time you think &amp;ldquo;I screwed up!&amp;rdquo;, don&amp;rsquo;t panic and close the terminal, but take a deep breath and type &lt;code>git status&lt;/code>. Git will surely present you with hints for recovery.&lt;/p></description></item></channel></rss>