Dissecting code-merging mechanisms: fast-forward merge, 3-way merge commits, the --no-ff option, the causes of merge conflicts along with the anatomy of their markers, and the steps to resolve conflicts manually then commit the result.

In episode 6 you learned that a branch is just a lightweight pointer to a commit, and you started creating many parallel development paths. But parallelism without bridges is just chaos. The point where two paths meet again is what determines how clean a project's history is.
Code merging is the skill most often tested in the real working world: every Pull Request on GitHub is essentially a merge. In this episode we dissect the two main mechanisms — merge and basic rebase — then defuse the scary myth called the merge conflict, complete with a real example and the steps to resolve it.
The simplest case: you are on main, then create the fitur-nav branch and commit twice on it. Meanwhile main has not moved at all. Because the paths have not diverged, Git can simply slide the pointer main forward to the latest position of fitur-nav — without creating an extra commit.
git switch main
git merge fitur-navThe output mentions "Fast-forward", and the history stays linear like a train running on the same track. No new commit, just the pointer moving.
Now the more common case: main and fitur-nav both moved after the divergence point. They are both ahead of each other, so neither can just slide its pointer.
For this case Git performs a 3-way merge: it compares three points — the commit on our branch (HEAD), the commit of the merged branch, and the common ancestor commit (the last point where both were still on one path). The results are combined into a single new merge commit with two parents.
* f0a2b1c Merge branch 'fitur-nav'
|\
| * 7c3d9e1 feat: tambah navbar
| * 2b8a4f2 feat: tambah komponen nav
* | a1c5e7d fix: perbaiki headerEven when fast-forward is possible, sometimes you want a branch's existence to always be recorded in history. git merge --no-ff <nama-branch> forces a merge commit even when fast-forward is possible. This pattern is common in teams that want features to appear as clear units in history.
git merge --no-ff fitur-navBefore fearing conflicts, first understand when they occur. Git is very smart at merging: if two branches change different lines in the same file, Git merges them automatically without drama. Conflicts only appear when two branches change the same (or adjacent) lines in the same file, or one deletes a file that another branch modified.
Simply put: Git cannot determine which is correct because both claim the same area. At this point Git asks for a human decision.
Note
A conflict is not a sign you are using Git wrong — it is a sign that two legitimate changes intersect. Professional teams resolve dozens of conflicts every week calmly.
When a conflict occurs, Git inserts markers inside the file:
<<<<<<< HEAD
Versi dari branch yang sedang aktif (main)
=======
Versi dari branch yang digabung (fitur-nav)
>>>>>>> fitur-navThree important parts:
<<<<<<< HEAD — start of the block with our active branch's version.======= — separator between the two versions.>>>>>>> fitur-nav — end of the block with the merged branch's version.Let's work through a real example. A project aplikasi-kafe has an app.js file with the line that determines the app name. The main branch changed it to KafeKita, while the fitur-rebrand branch changed the same line to Kafe Kopi Nusantara. When merging:
git switch main
git merge fitur-rebrandGit responds with a conflict status. The resolution steps:
git status — the file will be labeled "both modified".<<<<<<< HEAD
const nama = "KafeKita";
=======
const nama = "Kafe Kopi Nusantara";
>>>>>>> fitur-rebrandconst nama = "Kafe Kopi Nusantara";git add app.js, then finish the merge:git add app.js
git merge --continuegit merge --continue creates the merge commit and opens the editor for the commit message (git commit also works). When done, check with git log --oneline that the merge commit was created.
Tip
In VS Code, the "Accept Current / Accept Incoming / Accept Both" buttons help you choose a version quickly. For complicated conflicts, use a visual diff tool from the editor or git mergetool.
Besides merge, there is rebase: taking the feature branch's commits and reapplying them on top of the tip of the main branch, so the history appears linear, as if the feature were worked on sequentially after main.
git switch fitur-rebrand
git rebase mainThe difference: merge preserves the original divergence and records a merge commit; rebase rewrites commit hashes so the history line is straight. Rebase can also raise the same conflicts, resolved with git add then git rebase --continue.
Warning
Golden rule: never rebase a public branch shared with other people. Rebase rewrites history; if others have already pulled the old commits, the new hashes will break their work. Use rebase only for local/private branches — advanced details are in episode 16.
Key points from this episode:
git merge <nama-branch> merges a branch; --no-ff forces a merge commit.<<<<<<< HEAD, =======, and >>>>>>> nama-branch.git add, then git merge --continue.The local foundation is solid. In episode 8 we take all that code into the real world: working with remote repositories on GitHub — from creating a repository, linking it with origin, to the first push. See you there!