Git refuses the push because the remote branch contains commits your local copy doesn't have — someone (teammate, you on another machine, a web edit) pushed first. Git won't silently overwrite their work, so it stops with “failed to push some refs” and hints like “non-fast-forward” or “fetch first.” Your commits are safe; they just need to be reconciled with the remote's.
git pull --rebase origin mainRebase replays your local commits on top of the remote's — the history reads as if you'd started from the latest. (Use your branch name;
git pull without --rebase also works and creates a merge commit instead — both are correct, teams pick one style.)git add them, git rebase --continue. Conflicts mean both sides touched the same lines — they're a decision, not damage.git push origin main — now your branch contains the remote's commits plus yours, and the push is a clean fast-forward.git branch spelling.git push --force rewrites the remote to match you and discards the commits that blocked the push — on a shared branch, that's a teammate's work deleted. It's appropriate mainly for your own solo feature branch after a rebase, and even then --force-with-lease is the safer form (it refuses if the remote moved since you last looked).
Look at the actual divergence: git fetch then git log --oneline --graph --all -15 shows both lines of history. If the remote's new commits came from you (another laptop, a GitHub web edit), the rebase is painless. If a rebase goes sideways, git rebase --abort restores the pre-pull state exactly — nothing about this error can lose committed work unless force gets involved.
Did I lose my commits when the push failed? No. A rejected push changes nothing on either side. Your commits sit in your local branch waiting to be reconciled.
Rebase or merge — which should be used? Both produce a pushable branch. Rebase gives a linear history; merge preserves the true branching. Follow the team's convention; for a solo repo, rebase reads cleaner.