Git Push

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 main sends commits that exist locally but not on origin/main to the remote, then fast-forwards the remote branch.
  • -u records 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-lease adds a safety check that plain --force skips 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:

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.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.