Yeah, that error is annoying. You amended a commit or rebased, then git push slaps you with rejected, non-fast-forward. You're not alone—I've had clients lose an afternoon to this. But the fix is one command, and I'll show you the safe version.
The fix: force push with lease
Run this:
git push --force-with-lease
That's it. If you're on a branch that tracks a remote (like main or feature/foo), this will overwrite the remote branch with your local history. The --force-with-lease part is the key—it checks that the remote branch hasn't changed since you last fetched. If someone else pushed to it, the push fails, which is what you want.
If you want to be extra explicit about the branch:
git push --force-with-lease origin feature/foo
Why this works
Here's what happened. When you amend or rebase, you're rewriting commit history. Git creates new commits with new hashes. Your remote still has the old commits. Git sees two different histories—your local and the remote—and refuses to merge them because they've diverged. The --force flag tells git to overwrite the remote branch, but without the -with-lease part, it'll overwrite even if someone else added commits. That's a footgun. The lease prevents that.
Real story: I had a client last month who did a plain git push --force and blew away a colleague's commit that was pushed 10 minutes earlier. Took an hour to recover with reflog. Don't be that guy.
When to use plain --force instead
Sometimes --force-with-lease fails with a message like "stale info" or "remote ref updated since last fetch". That's rare if you've just fetched, but if it happens, you can do:
git fetch origin
git push --force-with-lease
If that still fails, then someone did push. Don't force it—rebase your work onto the remote branch:
git fetch origin
git rebase origin/feature/foo
git push --force-with-lease
That integrates their changes and then force-pushes your rewritten history cleanly.
Less common variations
You're on a protected branch (like main)
GitHub and GitLab often block force pushes to main. You'll get a message like "protected branch hook declined". You have two options: push to a feature branch and open a PR, or temporarily disable protection (if you have admin rights). I'd push to a branch—it's cleaner and keeps the audit trail.
You rebased onto a remote branch that moved
If you rebased onto origin/main but then someone pushed to main, your local main is based on an old origin. The force push will clobber their new commit. Use the fetch + rebase approach above, then force push.
You're working with submodules
You rarely see this, but if you have a submodule and the submodule's commit reference changed, the parent repo push can reject non-fast-forward. Fix the submodule pointer first (git submodule update --remote) and commit that, then push.
Your remote is a bare repo on a server
If you're pushing to a server that has the branch checked out, you'll get "refusing to update checked out branch". That's not a non-fast-forward, but it's similar. You need to set receive.denyCurrentBranch to updateInstead on the server, or push to a non-checked-out branch.
Prevention: stop this from happening again
The real way to avoid this is to not rewrite public history. If you've already pushed a branch and others are using it, don't amend or rebase. Instead, add a new commit with a message like fixup! previous message or just a normal commit.
If you're working solo on a feature branch, you can amend and rebase to your heart's content—just remember you'll need the force push. To make it a habit, use this alias:
git config --global alias.pushf 'push --force-with-lease'
Then git pushf does the safe force push. No more typing the long command.
Also, get in the habit of fetching before you start amending. A quick git fetch will tell you if the remote moved. If it hasn't, you're safe to amend and force push. If it has, rebase first.
That's the whole deal. Next time you see non-fast-forward, you'll know exactly what to type and why it matters. Now go fix that branch.