Git is a version control system (VCS) used to track changes across files, especially source code. It helps teams work in parallel inside the same repository by keeping a reliable history of who changed what, when, and why.
One of the most common Git workflows involves merging branches. You will often see two closely related commands:
git merge <branch>git merge --no-ff <branch>
Both merge a branch into your current branch. The difference comes down to whether Git can fast-forward in a given scenario, and whether you want a new commit recorded even when fast-forwarding would work.
The short answer: git merge vs git merge --no-ff
git merge fast-forwards when possible, and creates a merge commit when branches have diverged. git merge --no-ff always creates a merge commit, even when Git could fast-forward.
- Use
git mergewhen you prefer a simpler linear history, especially for small features where a fast-forward keeps things clean. - Use
git merge --no-ffwhen you want a true merge recorded, preserving branch boundaries for auditing, reverts, release tracking, or team visibility. - Use
git merge --ff-onlywhen you want a merge to succeed only if Git can fast-forward.
What the git merge command does
The Git merge command combines (merges) many commit sequences into a single, unified history. The command starts by accepting two commit pointers that are typically the most recent commits in the current branch and the specified branch that you want to merge in.
Next, it starts looking for a common base commit for the two. When found, the merge operation may create a dedicated commit (a merge commit), which combines the changes made by each one of the two queued commit sequences.
A merge commit is unique compared to other commits because it has two parent commits. Git automatically tries to merge the separate histories when creating this new commit. If it finds changes that were made in both histories, user intervention is required to complete the merge process.
You will commonly pair merge with these commands:
git switch <branch>orgit checkout <branch>to move to the branch that will receive the merge (often the main branch).git branch -d <branch>to delete the obsolete target branch locally after it has been merged and is no longer needed.
Typical merge workflow
# move to the receiving branch (often main branch)
git switch main
# confirm state before merging
git status
# update your local view of the remote
git fetch
# bring main branch up to the latest updates (choose one approach)
git pull
# merge a feature branch into the main branch
git merge feature/my-change
# optionally delete the local branch (obsolete target branch)
git branch -d feature/my-change
Preparation before merging
- Confirm the receiving branch: run the git status command and confirm HEAD points to the branch receiving the merge. Switch with
git switch <branch>orgit checkout <branch>. - Fetch and update: run
git fetchto retrieve remote updates, then update your local receiving branch (often the main branch) usinggit pullso you are working with the latest updates before starting the merge process.
Fast-forward merges vs 3-way merges
Fast-forward merge
A fast-forward merge occurs when there is a direct, linear path from your current branch tip to the branch you are merging. Git advances the branch pointer forward to the newer commit instead of creating a merge commit.
In this scenario, your history stays linear, and you will not see a merge message because Git did not create a merge commit. If you want to force a merge commit anyway, you would use the ff flag behavior that disables fast-forwarding, --no-ff.

3-way merge
A 3-way merge occurs when branches have diverged. Git uses three commits to produce the merge result: the two branch tips and their common ancestor. In this case Git creates a merge commit as a dedicated commit to tie the histories together, along with a merge message (either your default or one you provide).

git merge vs git merge --no-ff
The default merge behavior fast-forwards when Git can, and creates a merge commit when it must.
When you add --no-ff, Git creates a merge commit even when fast-forwarding would work. This produces a true merge record that can make history easier to understand later because the merge commit marks the integration point for that branch.
# default behavior, fast-forward when possible
git merge feature/my-change
# force a merge commit even when fast-forward is possible
git merge --no-ff feature/my-change
When --no-ff helps
- Reverts become cleaner: you can revert the merge commit to back out a feature as a unit.
- Audit and review become clearer: the merge commit creates a visible boundary for the feature branch.
- Release management: teams using release branches often prefer explicit merge commits with consistent merge messages.
What if you only want fast-forward merges?
If you want the merge to succeed only when fast-forwarding applies, use --ff-only. This is useful when you want to avoid accidental merge commits and keep history linear for small features.
# merge only if Git can fast-forward, otherwise fail
git merge --ff-only feature/my-change
You can also set a default preference via config:
# allow fast-forward when possible (common default)
git config --global merge.ff true
# always create a merge commit for merges that would fast-forward
git config --global merge.ff false
# fast-forward only, fail otherwise
git config --global merge.ff only
Merging more than two branches
Git supports multiple merge strategies. Two that often come up when merging multiple heads are:
1. Octopus
Octopus merges multiple branch heads at once. Git commonly uses it when you merge more than one branch in a single command. It works best when changes combine cleanly and typically stops when conflict resolution would require complex manual work.
2. Ours
The “ours” strategy resolves any number of heads by keeping the current branch’s content as the result while still recording the merge. Teams sometimes use it to end a side branch’s line of development while preserving history.
How this maps to GitHub pull request merge options
If your team merges via pull requests, the button you choose changes the resulting history shape. This matters in modern developer workflows where your Git history is part documentation, part audit trail, and sometimes even part internal media (shared screenshots of a commit graph during reviews and incident retrospectives).
- Create a merge commit: produces an explicit merge commit, similar in spirit to a no-fast-forward merge.
- Squash and merge: combines the PR into a single commit on the base branch.
- Rebase and merge: replays commits onto the base branch for a linear history.
Git merge and git merge --no-ff: comparison
| Parameter | git merge | git merge --no-ff |
|---|---|---|
| What it does | Merges a specified branch into the current branch | Merges a specified branch into the current branch and forces a dedicated commit (merge commit) |
| Fast-forward when possible | Yes | No |
| Creates a merge commit on a linear path | No | Yes (true merge record with a merge message) |
| Best for | Simple integrations where linear history is fine, especially small features | Preserving branch boundaries for reverts, auditing, release tracking, and consistent merge messages |
FAQ
When should I use git merge --no-ff?
Use it when you want an explicit merge commit for every merged branch. This preserves branch boundaries in your repository history and makes it easier to revert or audit changes later.
Does git merge always create a merge commit?
No. When Git can fast-forward, it advances the branch pointer and skips creating a merge commit. When branches have diverged, Git creates a dedicated commit (a merge commit) with a merge message.
What does --ff-only do?
--ff-only makes the merge succeed only when Git can fast-forward. If branches have diverged, the merge fails so you can decide whether to do a true merge, rebase, or another approach.
Should our team use merge, rebase, or squash?
It depends on how you want history to read. Merge commits preserve context, squash simplifies the base branch, and rebase keeps a linear commit timeline. Teams often standardize this through PR settings.
How do I see whether a merge was fast-forwarded?
Run git log --graph --oneline --decorate and look for a merge commit. A merge commit has two parents and appears as a join in the graph.
Conclusion
You now have the practical difference between git merge and git merge --no-ff. Both combine branches, but --no-ff records an explicit merge commit even when Git could fast-forward. Pick the behavior that matches how your team reads history, reviews work, and performs reverts, then keep your main branch aligned with the latest updates before each merge process.
Found something else you want to know? Please tell us in the comments so we can answer. Want to go deeper? Check out our basic Git commands guide and our best Git tutorials.
People are also reading: