Scenario:
- You have a main branch (main).
- You create feature branches (e.g., feature-x) for development.
- You want to keep your history clean and structured.
1️⃣ git merge → Use When You Want to Keep Full History (Default Approach)
📌 What It Does?
- Combines feature branch commits into
main, keeping all commit history. - Creates a new merge commit (
Fast-forwardif no diverging commits). - Easy to understand but can clutter history with many small commits.
📌 When to Use?
✅ If you want a full commit history, including every small commit.
✅ When working in teams where visibility of each commit matters.
📌 Example (Merging feature-x into main):
git checkout main
git pull origin main # Ensure main is up to date
git merge feature-x
git push origin main
2️⃣ git rebase → Use When You Want a Clean, Linear History
📌 What It Does?
- Moves (
reapplies) all commits from the feature branch on top of the latestmain. - No merge commit → keeps history linear and clean.
- Can cause conflicts if multiple people are rebasing.
📌 When to Use?
✅ When working solo and want a clean, linear history.
✅ Before merging a feature branch to avoid unnecessary merge commits.
✅ If you regularly update your feature branch with main.
📌 Example (Rebasing feature-x onto main Before Merging):
git checkout feature-x
git pull origin main # Ensure main is up to date
git rebase main # Moves feature branch commits on top of the latest main
If there are conflicts, resolve them, then:
git rebase --continue
Then merge into main:
git checkout main
git merge feature-x # Fast-forward merge (clean)
git push origin main
3️⃣ git squash → Use When You Want a Single Commit for a Feature
📌 What It Does?
- Combines multiple commits in the feature branch into one commit before merging.
- Keeps history very clean but loses commit granularity.
📌 When to Use?
✅ If a feature branch has many small commits (e.g., fix typo, refactor, debugging).
✅ When you want one clean commit per feature.
✅ If your team follows a “one commit per feature” policy.
📌 Example (Squashing Commits Before Merging into main):
Step 1: Squash commits interactively
git checkout feature-x
git rebase -i main
Step 2: Mark commits to squash
You’ll see:
pick abc123 First commit
pick def456 Second commit
pick ghi789 Third commit
Change all but the first pick to squash (s), like this:
pick abc123 First commit
squash def456 Second commit
squash ghi789 Third commit
Save and exit.
Step 3: Push the squashed commit
git push origin feature-x --force # Required after rewriting history
Step 4: Merge into main
git checkout main
git merge feature-x
git push origin main
Another git squash example. Suppose you have created a branch for adding a feature and you do 2 more commits one for refactoring and other for commenting on the feature. Now you have 3 commits in the feature-x branch.
Now you think better I would commit these 3 commit together and push it as one feature commit. Now what you do? follow the bel0w steps:
# this tells git you want to do some action upon the last 3 commits
git rebase -i HEAD~3
Now you can see 3 commits in your default editor prefixed: pick. Change the last 2 commit pick to squash and save the file. Now Another file popup in the editor to change the commit message for this one squashed commit. Ignore the messages start with # . Keep/modify the commit message which is not starting with # and save the file. This rewrites the history and save as one commit.
Now check the commit history:
git log --oneline
Checkout git HEAD in our post: https://railsdrop.com/2025/04/06/git-best-practices-git-head-useful-commands-commit-message/
Which One to Use in Your Development Process?
| Scenario | Use merge? | Use rebase? | Use squash? |
|---|---|---|---|
| Merging a feature branch | ✅ Yes (keeps history) | ✅ Yes (cleaner history) | ✅ Yes (one commit per feature) |
| Keeping history linear | ❌ No | ✅ Yes | ✅ Yes |
| Keeping every commit | ✅ Yes | ✅ Yes | ❌ No (loses small commits) |
| Team collaboration | ✅ Yes (safer) | ⚠️ Be careful (can rewrite history) | ✅ Yes (if team agrees) |
| Avoiding merge commits | ❌ No | ✅ Yes | ✅ Yes |
Recommendation for Your Workflow 🔀
For daily work, keep your feature branch updated with main:
git checkout feature-x
git pull origin main
git rebase main
Before merging into main, If you want full history:
git merge feature-x
If you want a clean history:
git rebase main
If you want a single commit per feature:
git squash
Merge into main and push:
git checkout main
git merge feature-x
git push origin main

🤔 What’s the Problem with Merge Commits?
A merge commit happens when you run:
git merge feature-x
and Git creates an extra commit to record the merge.
Example merge commit:
commit abc123 (HEAD -> main)
Merge: def456 ghi789
Author: You <you@example.com>
Date: Sat Mar 29 12:00:00 2025 +0000
Merge branch 'feature-x' into 'main'
Why Do Some Developers Avoid Merge Commits?
1️⃣ Cluttered History
- Merge commits pollute history when feature branches have multiple commits.
- Running
git logshows lots of merge commits, making it harder to track actual code changes. - Example of messy history with merge commits:
* Merge branch 'feature-x' (Merge Commit)* Fix typo in error message* Add validation for user input* Implement user authentication* Merge branch 'feature-y' (Merge Commit)* Fix UI issue
2️⃣ Harder to Track Changes
git loggets filled with merge commits instead of meaningful commits.git blamemight point to a merge commit, making debugging harder.
3️⃣ Confusing Graph with Many Branches
- Running
git log --graph --oneline --allshows a complex branching history:
* abc123 Merge branch ‘feature-x’
|\
| * ghi789 Fix authentication bug
| * def456 Add new login method
| 123456 Merge branch ‘feature-y’
|/ - If you rebase instead, the history stays linear.
🛠️ When Do Devs Avoid Merge Commits?
1️⃣ When They Want a Clean, Linear History
✅ Use git rebase instead of git merge to avoid merge commits.
Example:
git checkout feature-x
git rebase main
git checkout main
git merge feature-x # No merge commit (fast-forward merge)
🔹 This way, the commits from feature-x appear directly on top of main.
2️⃣ When They Squash Multiple Commits into One
✅ Use git rebase -i main to combine multiple commits into one before merging.
Example:
git checkout feature-x
git rebase -i main # Squash commits
git checkout main
git merge feature-x # No unnecessary commits
🔹 This results in one clean commit per feature.
3️⃣ When Working on a Team with Many Developers
- If multiple people merge their branches without rebasing, Git history gets full of merge commits.
- Teams using trunk-based development or feature branch workflows often rebase instead of merging.
✅ Solution → Always rebase before merging:
git checkout feature-x
git pull --rebase origin main
git push origin feature-x
When Are Merge Commits Actually Useful?
❌ Merge commits are not always bad! Sometimes, they are necessary:
- Merging Long-Lived Feature Branches
- If a branch exists for weeks or months, it has many commits.
- A merge commit documents when the feature was merged.
- Merging Changes from a Release Branch
- Example: Merging
release-v1.0intomainshould have a merge commit.
- Example: Merging
- Handling Complex Conflicts
- When resolving big merge conflicts, a merge commit shows exactly when conflicts were fixed.
🔑 Final Answer: When to Avoid Merge Commits?
🚫 Avoid merge commits when you want a clean history
✅ Use rebasing (git rebase) before merging to keep history linear.
✅ Use squashing (git rebase -i) to avoid unnecessary commits in a feature branch.
🔹 If you’re working solo or in a small team, rebase before merging to keep history simple.
🔹 If you’re working in a large team with long-lived branches, merge commits may be useful for tracking.
🔥 Finally Best Practices for Your Development Process
Before merging, update your branch without a merge commit:
git checkout feature-x
git pull --rebase origin main
Squash unnecessary commits (optional):
git rebase -i main
Merge the branch (fast-forward, no merge commit):
git checkout main
git merge feature-x # Fast-forward merge, no merge commit
This keeps your history clean and easy to read!