If main hasn't moved at all since feature branched off it, git merge feature doesn't need a merge commit at all — Git can simply slide main's pointer forward to feature's tip, since feature's history already contains everything main has. This is a fast-forward merge, and it's why some merges leave no trace of a merge commit while others do.
git switch main
git merge feature
# If main hasn't changed since feature branched: fast-forward, no merge commit created
# If main HAS changed since: a real merge commit is created, joining both histories
git merge --no-ff feature forces a merge commit even when a fast-forward is possible — useful when a team wants every feature's boundaries visible in history regardless of whether main happened to stay still while it was in progress. git merge --ff-only does the opposite: it refuses to merge unless a fast-forward is possible, failing loudly instead of silently creating a merge commit — a useful guard in scripts or CI where an unexpected merge commit would be surprising.