git push uploads your local commits to a remote repository, updating the remote’s branch to match yours. It’s the command that turns your local work into something your team — or your deploy pipeline — can actually see. This is the ninth post in this site’s Git command guide, following Git Checkout.
Basic Usage
If you’ve added a remote already (git remote add origin <url>), the first push to a new branch needs the full form so Git knows which remote branch to update:
$ git push origin main
This uploads your local main branch’s new commits to the origin remote’s main branch.
Setting Upstream Tracking with -u
Typing origin main every time gets old fast. The -u (or --set-upstream) flag tells Git to remember the link between your local branch and that remote branch, so future pushes and pulls on this branch just need git push or git pull with no arguments:
$ git push -u origin main
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Writing objects: 100% (3/3), 327 bytes | 327.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To github.com:username/repo.git
3f6a2c1..a92d44e main -> main
Branch 'main' set up to track remote branch 'main' from 'origin'.
That last line is the part that matters — it confirms the tracking relationship is set. After this, plain git push on this branch always goes to origin/main without you specifying it again.
–force vs –force-with-lease
After rewriting history locally — an interactive rebase, or amending a pushed commit — a normal push will be rejected because your branch and the remote’s branch have diverged. --force overwrites the remote branch with your local one unconditionally, which means it will silently discard any commits someone else pushed in the meantime that you haven’t fetched. --force-with-lease does the same overwrite, but first checks that the remote branch is still exactly where your local copy of it last saw it — if someone else pushed since your last fetch, it refuses instead of clobbering their work.
$ git push --force-with-lease origin main
# succeeds only if origin/main still matches what you last fetched
I treat plain --force as something to use only on a branch I’m certain nobody else is touching — a personal feature branch, not main. On anything shared, --force-with-lease is the safer default because it fails loudly instead of destroying a coworker’s commit.
A Real Rejected Push
If someone else has pushed to the branch since you last pulled, a normal push fails with this:
$ git push origin main
To github.com:username/repo.git
! [rejected] main -> main (fetch first)
error: failed to push some refs to 'github.com:username/repo.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
This is Git refusing to overwrite commits it doesn’t know about — it’s not an error in your work, it’s a sign someone else’s push landed first. Fix it by pulling first (see Git Pull for the reconciliation strategies), resolving any conflicts, then pushing again:
$ git pull origin main
$ git push origin main
How the Code Works
git push origin mainsends commits that exist locally but not onorigin/mainto the remote, then fast-forwards the remote branch.-urecords a permanent tracking link between your local branch and a remote branch, simplifying future invocations.- A push is rejected, not merged or overwritten, if the remote has commits your local branch hasn’t incorporated — Git always prefers to fail loudly over guessing.
--force-with-leaseadds a safety check that plain--forceskips entirely.
FAQs
What’s the difference between git push and git push origin main?
None, once upstream tracking is set with -u. Without tracking set, plain git push may fail or behave according to your push.default config — being explicit with origin main avoids any ambiguity.
Is it ever safe to force-push to main?
Generally no, on a shared branch with collaborators. Most teams configure branch protection on main specifically to block force pushes for this reason. Reserve force pushes for your own feature branches.
Why did my push succeed but I don’t see it on GitHub?
Check you pushed to the branch you think you did — git push origin some-other-branch by habit is a common slip. git branch -vv shows you which remote branch each local branch tracks.
See Also: This Site’s Git Command Guide
This post is part of a 13-part Git command guide on this site:
- Git Init — starting a brand-new repository
- Git Clone — copying an existing repository
- Git Add — staging specific files for a commit
- Git Add . (All) — staging all changes at once
- Git Commit — saving staged changes to the repository
- Git Status — checking what’s changed before you commit
- Git Branch — creating, listing, and renaming branches
- Git Checkout — switching branches and restoring files
- Git Pull — fetching and merging remote changes
- Git Log — reading commit history
- Git: Delete Last Commit — undoing a commit safely
- Git – Autocorrect Spelling — typo recovery and productivity config
Conclusion
The rejected-push message looks alarming the first time you see it, but it’s actually Git doing exactly its job — protecting commits you haven’t seen yet from being silently erased. Pull, resolve, push again, and you’re fine. Once your changes are on the remote, the natural next step — especially on a team — is pulling everyone else’s changes too, covered in Git Pull.