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!