Skip to main content

How to Merge Branches in Git (Without Creating "Spaghetti" History)

· 6 min read
Serhii Hrekov
software engineer, creator, artist, programmer, projects founder

Merging code used to be the most terrifying part of a developer's week. You would type git merge, hold your breath, and pray you didn't accidentally delete your coworker's entire feature.

Today, modern Git workflows (and platforms like GitHub and GitLab) have completely changed the game. The goal in 2026 isn't just to combine code; it's to maintain a clean, readable, and linear history so that when something breaks 6 months from now, you can actually figure out why.

Here is the modern playbook on how to merge your branches without creating a chaotic "spaghetti" history.

🔀 The "Big Three" Merge Strategies

Before you merge, you need to decide how you want your history to look. There are three main ways to combine a feature branch into main.

1. The Squash Merge (The 2026 Industry Standard)

Instead of bringing over all 45 of your tiny "wip", "fix typo", and "forgot a semicolon" commits, a squash merge crushes them all into one single, clean commit on the main branch.

  • Best for: Feature branches and bug fixes.
  • Why we love it: It keeps the main branch incredibly clean. Every commit on main represents one complete, working feature.

2. The True Merge (--no-ff)

This creates a dedicated "Merge Commit" that links the two branches together, preserving every single individual commit you made on your feature branch.

  • Best for: Massive, multi-developer epics where the historical context of how it was built matters.
  • Why it's risky: It creates the classic "Git Spaghetti" graph if overused.

3. The Fast-Forward (--ff-only)

If nothing has changed on main since you started your branch, Git just moves the main pointer forward to your latest commit. No merge commit is created.

  • Best for: Small, local, single-person projects.

💻 How to Merge Locally (The CLI Way)

While most merging happens via Pull Requests online nowadays, you still need to know how to do it in your terminal. Here is the safest workflow for local merging.

# 1. Make sure your local main is perfectly up to date
git checkout main
git pull origin main

# 2. (Optional but recommended) Rebase your feature branch first
# This catches conflicts BEFORE you try to merge
git checkout your-feature-branch
git rebase main

# 3. Switch back to main to perform the merge
git checkout main

# 4. Execute the Merge! (Choose ONE of the following)

# Option A: The Squash Merge (Recommended)
git merge --squash your-feature-branch
git commit -m "feat: added new login dashboard"

# Option B: The True Merge (Creates a merge commit)
git merge --no-ff your-feature-branch

# 5. Push the updated main to the server
git push origin main

📊 Strategy Comparison

StrategyCommandResult on main HistoryWhen to use it?
Squashmerge --squash1 Clean Commit90% of your daily Pull Requests.
True Mergemerge --no-ffAll Commits + 1 Merge CommitLarge collaborative feature branches.
Fast-Forwardmerge --ff-onlyAll Commits (Linear)Quick local updates.

🛡️ The "Shift Left" Rule of Merging

In modern development, the golden rule of merging is: Never fix merge conflicts on the main branch.

If Git tells you there is a conflict, you should abort the merge, go back to your feature branch, merge or rebase main into your feature branch, fix the conflicts there, test it, and then merge back into main.

# If a merge goes wrong, hit the panic button to safely undo it:
git merge --abort

📚 Sources & Technical Refs

  • [1.1] Git SCM Docs: git-merge - Official documentation on merge strategies.
  • [2.1] GitHub Blog: Squash your commits - Why GitHub introduced the "Squash and Merge" button as a default.
  • [3.1] Atlassian Git: Merging vs. Rebasing - The conceptual difference between integrating branches.

Related articles