Your local repo and the remote repo are two separate copies of the same project. They drift apart the moment either side commits. Three commands keep them in sync — git push sends your work up, git pull brings teammates' work down, and git fetch peeks at the remote without actually merging anything. This lesson nails down when to use each, the upstream concept that makes the short forms work, and the morning routine every QA engineer runs before they touch a test file.
The mental model
Think of your repo as having two layers:
- Local — what's on your laptop. Your commits land here first.
- Remote — what's on GitHub. Your team's commits land here.
Neither side automatically knows what the other did. Git only syncs when you ask. That's a feature, not a bug — it's why you can keep working offline.
git push — send your local commits up
Once your commits exist locally, push them to GitHub:
git push origin mainEnumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Writing objects: 100% (3/3), 412 bytes | 412.00 KiB/s, done.
To github.com:acme/webapp-tests.git
8f31320..4c48901 main -> main
Two arguments matter:
origin— the remote name. By defaultgit clonecalls the remoteorigin. Confirm withgit remote -v.main— the branch name. You're pushing your localmainto the remote'smain.
For a feature branch:
git push origin feature/search-testsThe first time you push a brand-new branch, add -u (short for --set-upstream):
git push -u origin feature/search-testsBranch 'feature/search-tests' set up to track remote branch 'feature/search-tests' from 'origin'.
-u records "this local branch tracks that remote branch." From then on, plain git push and git pull work without arguments — Git remembers which remote and branch they refer to.
git pull — fetch and merge in one step
To bring down everyone else's commits:
git pull origin mainremote: Counting objects: 100% (8/8), done.
remote: Compressing objects: 100% (4/4), done.
Unpacking objects: 100% (5/5), done.
From github.com:acme/webapp-tests
* branch main -> FETCH_HEAD
Updating 4c48901..9b3d2f1
Fast-forward
tests/checkout.spec.js | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
git pull is two commands rolled into one: git fetch (download remote commits into your local copy of the remote refs) followed by git merge (merge those commits into your current branch). If the merge is clean, you don't notice; if there's a conflict, you handle it the way you learned in Chapter 2.
With upstream set, the short form works:
git pullgit fetch — peek without merging
Sometimes you want to know what's on the remote without changing your local files. git fetch downloads the remote's commits into your local Git database but does not merge them into your branch.
git fetch originremote: Enumerating objects: 6, done.
From github.com:acme/webapp-tests
4c48901..9b3d2f1 main -> origin/main
* [new branch] feature/api-tests -> origin/feature/api-tests
Two things are now visible in your local repo:
origin/mainhas moved forward. Your localmainis unchanged — but you can see what's coming.- A new remote branch
feature/api-testsexists. Your teammate just pushed it.
Inspect the new commits before deciding to merge:
git log main..origin/main --oneline9b3d2f1 Add checkout regression tests
e1a4c80 Update timeout config for slower staging
If you're happy, run git merge origin/main to bring them in. If something looks risky, you can branch off, talk to the author, or wait. Fetch is the "look before you leap" command.
When to use which
| Command | What it does | When to run |
|---|---|---|
git push | Send your local commits up | After you've committed and want teammates / CI to see your work |
git pull | Download AND merge remote commits | At the start of work, before branching off main |
git fetch | Download remote commits but don't merge | When you want to inspect first, or check if anything new is up |
The golden rule: pull before you push
If you push commits and the remote has commits you don't have locally, GitHub will reject the push:
git push origin mainTo github.com:acme/webapp-tests.git
! [rejected] main -> main (fetch first)
error: failed to push some refs to 'github.com:acme/webapp-tests.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally.
The fix: pull first, then push.
git pull
# resolve any merge if needed
git pushBuild the habit of git pull at the start of every work session. It's cheap and prevents 90% of push-rejection moments.
The sync flow at a glance
Step 1 of 6
Start of day — git pull
Bring main up to date. Make sure your starting point matches what the team merged overnight.
A QA morning routine
Every workday for the rest of your career starts something like this:
cd ~/projects/webapp-tests
git switch main
git pull # latest team commits
npx cypress run # smoke test the suite
git switch -c test/add-discount-code-cases # branch for today's work
# ...write tests, commit, repeat...
git pull origin main # before pushing, sync again
git push -u origin test/add-discount-code-cases
# ...open PR on GitHub...Six commands, ten minutes, and you're already shipping work. The patterns become automatic within a week.
What git remote actually is
origin isn't magical — it's just a name pointing at a URL:
git remote -vorigin git@github.com:acme/webapp-tests.git (fetch)
origin git@github.com:acme/webapp-tests.git (push)
You can have multiple remotes. Common pattern: origin (your fork) and upstream (the original repo) — Lesson 4 of this chapter covers exactly that.
⚠️ Common mistakes
- Pulling without committing your in-progress changes. If you have uncommitted edits and
git pulltriggers a merge, Git refuses with "Your local changes would be overwritten by merge." The fix isn't to discard the work —git stash(Chapter 4) parks it safely; pull; pop the stash back. - Using
git pullblindly when your branch is messy. If you've been working on a branch for a week and rungit pullwithout context, you might merge unrelated commits in. On a feature branch,git pull --rebase(or just review whatgit fetchreveals first) is often cleaner. - Forgetting
-uon the first push. Without-u, futuregit pushandgit pullcommands won't know which remote/branch to use, and you'll keep typingorigin <branch-name>. Always push new branches with-u origin <branch>the first time.
🎯 Practice task
A full sync cycle. 20 minutes. Two terminal windows, or two folders, work best — pretend each is a different teammate.
- Clone any small repo of yours into two folders:
webapp-tests-Aandwebapp-tests-B. (Or use one folder and a real teammate's clone.) - In folder A, on
main, edit a file, commit, andgit push origin main. - In folder B, run
git status. Notice nothing changed locally. Thengit fetch originandgit log main..origin/main --oneline. The new commit shows up — but folder B's working files are untouched. - In folder B, run
git pull. The commit now lands in your branch and the file updates. - In folder B, edit a different file and commit. Try to
git pushbefore doing anything in folder A — confirm it succeeds. Then in folder A,git pullto bring it down. - Stretch: in folder A, edit the same file folder B just changed, commit, and try to push without pulling first. Confirm the rejection message. Resolve it:
git pull, fix any conflict,git push.
The next lesson uses every concept so far in service of the most important Git ritual on a team: the pull request.