open atlas
↑ Back to track
Git, from zero to senior GIT · 05 · 02

Push and pull

git push sends local commits to the remote; git pull is fetch plus integrate. Set the upstream once with push -u origin branch, then bare push and pull work. git branch -vv shows tracking. A push is rejected as non-fast-forward when the remote has commits you lack — pull first.

GIT Junior ◷ 16 min
Level
FoundationsJuniorMiddleSenior

You commit your work, type git push, and git snaps back with fatal: The current branch feature has no upstream branch. You add -u origin feature, it works, and from then on a bare git push is enough. Later a push fails with ! [rejected] ... (non-fast-forward) and you are told to pull first. These two moments — setting an upstream and getting rejected — are the whole daily rhythm of working with a remote.

By the end of this lesson you will be able to push and pull confidently, set up branch tracking, and explain exactly why a push gets rejected and what pulling does about it.

Goal

After this lesson you can use git push and git pull, set an upstream with git push -u, read tracking with git branch -vv, and explain why a non-fast-forward push is rejected and how pulling first resolves it.

1

git push uploads commits from your local branch to a branch on the remote. It takes commits that exist only in your repository and copies them to the server, then advances the remote’s branch (and your local origin/main) to match. The full form names a remote and a branch:

git push origin main      # push local main to origin's main

Push is the mirror image of fetch: fetch pulls the server’s commits down, push sends yours up. Neither touches your working files.

2

Set the upstream once with -u, then push and pull need no arguments. Typing origin main every time is noise. The first push of a branch can record a permanent link — its upstream (also called the tracking relationship):

git push -u origin feature   # push AND remember origin/feature as upstream
git push                     # from now on this is enough
git pull                     # and so is this

-u is short for --set-upstream. After it, your local feature tracks origin/feature, and git knows where bare push/pull should go.

3

git branch -vv shows what each branch tracks and how far it has drifted. When push and pull stop behaving, this is the first command to reach for:

git branch -vv
# * feature  9af1c2b [origin/feature: ahead 2] add retry logic
#   main     1d4e7f0 [origin/main] release prep

[origin/feature: ahead 2] means feature tracks origin/feature and has 2 commits not yet pushed. A branch with no [...] has no upstream — that is exactly the state that triggers the “no upstream branch” error.

4

git pull is git fetch followed by an integrate step — by default a merge. Pull is not a primitive; it is two operations bundled:

git pull origin main
# = git fetch origin        (update origin/main)
#   then git merge origin/main into your current branch

So a pull both refreshes your remote-tracking branch and brings the remote’s new commits into your local branch. The default integration is a merge; the next lesson shows the alternatives and why you might want them.

Why this works

Why is a fresh push rejected as non-fast-forward? Git only lets a branch on the remote move forward — to a commit that has the current tip as an ancestor (a “fast-forward”). If someone else pushed in the meantime, the remote’s main has a commit your history does not contain, so replacing it with your tip would drop their commit. Git refuses. The fix is to pull (fetch their commit and merge or rebase it into yours) so your branch now builds on top of theirs — then your push is a clean fast-forward.

5

Pushing a brand-new branch just means creating it on the remote. The remote does not need the branch to exist first; your push creates it:

git switch -c hotfix      # new local branch
# ...commit work...
git push -u origin hotfix # creates origin/hotfix and sets tracking

This new-branch push is always a fast-forward (the remote had nothing there to conflict with), so it is never rejected for non-fast-forward reasons.

Worked example

A rejected push, walked through.

You and a teammate both start from origin/main at commit c10. You commit c11 locally. Meanwhile your teammate commits c12 and pushes it, so the server’s main is now at c12. You run git push:

! [rejected]        main -> main (non-fast-forward)
error: failed to push some refs to 'origin'
hint: Updates were rejected because the remote contains work that you
hint: do not have locally. ... integrate the remote changes (e.g. 'git pull')

Git refuses because moving the server’s main from c12 to your c11 would silently erase c12. You run git pull: it fetches c12 (advancing origin/main) and merges it with your c11, producing a merge commit c13 that has both as parents. Now your local main (c13) contains c12 as an ancestor, so git push is a fast-forward and succeeds. The teammate’s work is preserved, and so is yours.

Common mistake

When a push is rejected, the dangerous reflex is to reach for git push --force to “make it go”. Force overwrites the remote and really does delete the teammate’s c12. On a shared branch that is data loss. The correct response to a non-fast-forward rejection is almost always to pull (or fetch + rebase) and push again. Force-pushing has legitimate uses — covered two lessons from now — but reacting to a normal rejection is not one of them.

Check yourself
Quiz

Your git push is rejected with '(non-fast-forward)'. What does that tell you about the remote, and what is the right next step?

Recap

git push sends your local commits up to a branch on the remote; git pull is git fetch plus an integrate step (a merge by default) that brings the remote’s commits down into your branch. The first push of a branch should use git push -u origin <branch> to record the upstream/tracking link, after which bare push and pull know where to go; git branch -vv shows that tracking and how far ahead or behind you are. A push is rejected as non-fast-forward when the remote contains commits you lack — the cure is to pull first, never to force. Next you will separate fetching from integrating, and choose between merge and rebase deliberately.

Practice

Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.

recallapplystretch0 of 4 done

Something unclear?

Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.

shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources2
expand
  1. 01
  2. 02

Trademarks belong to their respective owners. Editorial reference only.