Git and GitHubgit 2.54 · GitHub · macOS, Linux or Windows
Dashboard
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
26 small wins to finish your pathNext lesson →

Pushing and pulling

git push sends your commits to a remote, and git pull brings other people's commits down. They are the only two commands that move work between the copies.

Last updated: 12 Oct, 2026 · git 2.54

GitHub authentication settled how GitHub lets your machine in. With that done, the two commands below are the whole of working with other people.

Pushing the first commit · from the Git and GitHub series · 12:56 to 13:33

The video runs git push origin main and the commit appears on the repository page. origin is the remote's name and main is the branch being sent.

Why a push is rejected and what a pull does about it. Commit A is followed by commit B, the last commit both sides had. The history then splits: commit C is yours on your main, and commit D is theirs, already on origin/main on GitHub. Because the history split at B, your commit cannot be added on top of theirs, so git push is answered with the rejected message main to main fetch first. A git pull, which is a git fetch followed by a git merge of origin slash main, joins C and D with the merge commit M, and the push then works.

Syntax: the four commands this lesson uses.

bash
git push -u origin main    # send branch main to origin, and remember the pairing
git push                   # after -u once, this is enough
git fetch origin           # download what the remote has, change nothing you are working on
git pull                   # git fetch, then merge what came down into your branch

The repository this pushes to is a bare repository in a folder on the same machine, standing in for the GitHub URL so the commands below are real runs. On GitHub the only difference is the URL.

ExampleThe stand-in, and a repository pointed at it
export GIT_AUTHOR_NAME=Reader GIT_AUTHOR_EMAIL=reader@example.com GIT_AUTHOR_DATE=2026-10-12T10:00:00
export GIT_COMMITTER_NAME=Reader GIT_COMMITTER_EMAIL=reader@example.com GIT_COMMITTER_DATE=2026-10-12T10:00:00
git init --bare -q ask-docs.git            # the stand-in for the repository on GitHub
mkdir ask-docs && cd ask-docs && git init -q -b main
printf 'Answer only from the notes.\n' > prompt.txt
git add . && git commit -q -m "Add the prompt"
git remote add origin ../ask-docs.git
git remote -v

A bare repository holds commits and no working tree, which is what a repository on a server looks like. One commit sits in your copy, and nothing has crossed to the stand-in yet.

Sending a branch for the first time

-u is short for --set-upstream. It does the push and also records that this branch belongs with that branch on that remote. After it, git push and git pull with no arguments know where to go.

bash
git push -u origin main   # the first push of a branch
git push                  # every push after that
ExampleThe first push, and what -u recorded
git push -u origin main
echo "--- what the branch knows now"
git branch -vv
git status -sb
  • A new branch was created on the other side. * [new branch] main -> main says the branch did not exist there and now does.
  • The pairing was saved. The line about tracking is what -u added. git branch -vv shows it in brackets as [origin/main].
  • git status -sb now has a second half. ## main...origin/main means git is comparing your branch against the remote one every time you ask for status.

Bringing down what someone else pushed

When the remote has a commit you do not have and you have nothing new, git pull moves your branch forward on to it. Git calls that a fast-forward: no merge is needed because nothing of yours is in the way.

ExampleA second copy, with a commit yours has not seen
cd ~ && git clone -q "$HOME/ask-docs.git" teammate && cd teammate     # someone else's copy
printf 'chunk_size = 400\n' > settings.py
git add . && git commit -q -m "Add a settings file" && git push -q
git log --oneline

That is a third repository in the picture: the stand-in in the middle, your copy, and a copy belonging to somebody else. Their commit is in the stand-in now, and your copy still has one commit.

Pulling their commit into your copy

ExampleA pull that only has to move forward
cd ~/ask-docs                 # back in your own copy
git pull
git log --oneline
  • The first lines are the download. From ../ask-docs and the two hashes with a range between them are git saying which commits arrived.
  • Fast-forward means no merge commit. Your branch had nothing on top of the old commit, so git moved the branch pointer and stopped.
  • The file is now on disk. settings.py was written by someone else's commit and the pull put it in your working tree.

The push that gets rejected, and the way out

This is the first thing that goes wrong for almost everyone. Both sides committed on top of the same commit, so neither history is the other with more commits on the end. Git refuses the push, because accepting it would drop the other commit.

ExampleBoth sides committed, so the push is refused
cd ~/ask-docs                 # back in your own copy
printf 'loader\n' > loader.py && git add . && git commit -q -m "Add the loader"
git push origin main

Read the hint rather than the word error. Git says the remote has work you do not have, names the usual cause, and tells you to pull first. Nothing is broken and nothing is lost: your commit is still here and theirs is still there.

The pull that refuses to guess

ExampleA pull with commits on both sides, and no instruction
git pull

Git downloaded the commit, saw that the two branches had split, and stopped with Need to specify how to reconcile divergent branches. It listed the three answers rather than picking one: a merge, a rebase, or a refusal.

Choosing the merge and pulling again

ExampleThe pull that joins the two histories
git config pull.rebase false      # join them with a merge commit
git pull
git log --oneline --graph
  • pull.rebase false picks the merge. The pull then made a merge commit joining your commit and theirs. --rebase would have replayed yours on top instead, and --ff-only would have refused.
  • The graph shows the join. The two lines leave the first commit, run side by side and come back together at the merge commit.

Pushing once the histories are joined

ExampleThe same push, accepted
git push origin main

The push is accepted now. The remote's commit is an ancestor of what you are sending, so nothing on the other side would be dropped.

git fetch against git pull

git pull is git fetch followed by a merge. Fetching on its own is the careful version: it downloads everything and touches nothing you are working on, so you can look before you join.

ExampleFetch downloads and changes nothing
cd ~/ask-docs
git fetch origin
git status -sb
ls                                     # the new file is not here yet
git log --oneline origin/main          # but the commit can already be read
  • After the fetch, status says behind 1. Git knows about a commit you do not have. Your files are untouched, and ls shows the one file you had.
  • origin/main is readable straight away. It is a branch name like any other, kept up to date by fetch, pointing at what the remote had when you last fetched.

Merging what the fetch brought down

ExampleThe merge is what writes the files
git merge origin/main
ls

Only now did settings.py appear in ls. git pull is these two commands in one, which is the whole of the difference between them.

git fetchgit pull
Downloads commitsyesyes
Changes your filesnoyes
Can cause a conflictnoyes
Leaves you to decideyes, merge when you are readyno, it merges at once
Use it whenyou want to see what arrived firstyou are up to date with your own work and want theirs

Where push and pull belong in a day

  • Pull before you start. One pull at the beginning avoids most rejected pushes, because you begin from what everyone else already has.
  • Push when a piece works. A commit on your laptop protects you from nothing. A pushed commit survives the laptop.
  • Fetch when you are in the middle of something. It tells you what is coming without disturbing the work in front of you.
Watch out. git push --force makes the rejection go away by replacing the remote branch with yours, which deletes commits other people have. If you must rewrite a pushed branch, use --force-with-lease, which refuses when the remote has moved since you last fetched, and tell the people who share the branch first.
Try it yourself
  • Change git config pull.rebase false to true in the divergent example and read the graph again. There is no merge commit: your work sits on top.
  • Set it to git config pull.ff only instead and run the pull. Git refuses, which is what you want when you never meant to have your own commits.
  • Delete the -u from the first push and then run git push with no arguments in the next example.

Every expert started right here.