git fetch – Safe Download
git fetch downloads commits, branches, and tags from the remote but does not modify your working directory or current branch. It updates only the remote-tracking branches (e.g., origin/main). This makes it completely safe — you can always fetch without risk.
# Fetch ALL branches from all remotes
git fetch
# Fetch from a specific remote
git fetch origin
# Fetch a specific branch
git fetch origin feature/login
# After fetching, see what changed
git log main..origin/main --oneline
# Shows commits on origin/main that you haven't merged yet
# Check what branches were updated
git remote show origin
Many experienced developers prefer git fetch over git pull because it separates two concerns: downloading remote changes (always safe) and integrating them (requires a decision). After fetching, you can review what changed with git log or git diff before deciding whether to merge or rebase.
git pull – Fetch + Integrate
git pull is shorthand for git fetch followed by either git merge or git rebase. It downloads remote changes AND immediately integrates them into your current branch.
# Pull from the tracked remote branch (most common usage)
git pull
# Pull from a specific remote and branch
git pull origin main
# Pull with rebase instead of merge (cleaner history — no merge commits)
git pull --rebase
git pull --rebase origin main
# Set rebase as the default pull strategy (recommended by many teams)
git config --global pull.rebase true
| Command | What it does | Creates merge commit? |
|---|---|---|
git pull |
fetch + merge | Yes (if branches have diverged) |
git pull --rebase |
fetch + rebase | No — linear history |
git fetch + manual merge/rebase |
Download, then you decide | You control it |
git push – Upload Your Commits
git push uploads your local commits to the remote repository, making them available to others.
# Push current branch to the tracked remote branch
git push
# Push a specific branch to origin
git push origin feature/login
# Push with -u flag (--set-upstream): sets tracking relationship
# After this, plain "git push" works for this branch
git push -u origin feature/login
# Push all local branches to origin
git push origin --all
# Push tags
git push origin --tags
The first time you push a new branch, use git push -u origin branch-name. This creates the branch on the remote AND sets up tracking so future git push and git pull commands on this branch work without specifying the remote and branch name each time. After the initial -u push, plain git push is sufficient.
Tracking (Upstream) Branches
A tracking branch is the connection between a local branch and its corresponding remote branch. Once set, Git knows where to push and pull without you specifying the remote and branch name every time.
# See tracking info for all local branches
git branch -vv
# * main a1b2c3d [origin/main] Fix navbar
# feature/login 9f8e7d6 [origin/feature/login: ahead 2] Add form
# "ahead 2" means 2 local commits not yet pushed
# "behind 3" would mean 3 remote commits not yet pulled
# Set tracking for an existing branch
git branch --set-upstream-to=origin/main main
# Check full remote tracking status
git status
# On branch main
# Your branch is ahead of 'origin/main' by 1 commit.
Force Push – When and How (Safely)
Normally, Git refuses to push if the remote has commits your local branch doesn't (a "non-fast-forward" push). This protects against accidentally overwriting others' work. Force push overrides this protection.
# SAFE force push: checks that no new commits appeared on remote since your last fetch
git push --force-with-lease origin feature/login
# DANGEROUS: overwrites remote blindly — can destroy teammates' work
# git push --force origin main ← NEVER do this on shared branches
git push --force on a shared branch overwrites the remote's history. Anyone who has already pulled those commits now has a diverged history that's very hard to reconcile. Force pushing is only appropriate on your own feature branches after rebasing (and only when you're certain no one else is working on that branch). Always prefer --force-with-lease over --force — it at least checks that the remote hasn't changed since your last fetch.
Recommended Sync Workflow
# Morning routine: start by fetching latest changes
git fetch origin
# See what's new on main
git log main..origin/main --oneline
# Update your local main
git switch main
git merge origin/main # or: git pull
# Update your feature branch with latest main
git switch feature/my-work
git rebase main # or: git merge main
# Work, commit, then push
git push origin feature/my-work # first push: git push -u origin feature/my-work
📋 Summary
git fetch— downloads remote changes to tracking branches ONLY. Safe — doesn't touch your working tree.git pull— fetch + merge (or fetch + rebase with--rebase). Integrates remote changes into your current branch.git push origin <branch>— upload your commits to the remote. Use-uon first push to set tracking.git pull --rebase— preferred by many teams for cleaner history (no merge commits on pull).git push --force-with-lease— safe force push (only after rebasing your own feature branch).- Never
git push --forceonmainor any shared branch — it overwrites teammates' work.
FAQ
git fetch downloads remote changes but doesn't touch your working tree or current branch — it only updates remote-tracking branches like origin/main. git pull fetches AND immediately merges (or rebases) those changes into your current branch. Fetch is safe and non-destructive; pull immediately changes your branch. The best practice for careful developers is to fetch, review the changes, then manually merge or rebase.
A rejection usually means the remote has commits your local branch doesn't have. Git refuses to push because it would overwrite the remote's history. Solution: first git pull (or git fetch + git rebase) to bring in the remote changes, resolve any conflicts, then git push again. The rejection is Git protecting you — it's a feature, not a bug.
Many experienced developers prefer git pull --rebase because it avoids creating "noise" merge commits like "Merge branch 'main' into feature/x". This keeps the history linear and easier to read. You can set it as default with git config --global pull.rebase true. However, if you're new to Git or working on a team that expects merge commits, stick with the default. Both produce the same end result — just different history shapes.
It means your local branch has 3 commits that haven't been pushed to the remote yet. The fix is git push. If you see "behind N commits", run git pull. If you see both "ahead X and behind Y", you and the remote have diverged — run git pull (or git pull --rebase) to reconcile, then push.