Lesson 7: Remote Collaboration — Push, Pull, Fetch
Master remote repositories and collaborative workflows. Learn how remotes work, the difference between fetch and pull, tracking branches, and how to handle diverged histories.
Lesson 7: Remote Collaboration — Push, Pull, Fetch 🌐
Introduction
Up until now, we've focused on Git as a local tool — commits, branches, merges, and rebases all happening on your machine. But Git's true power emerges when you collaborate with others through remote repositories. 🤝
Think of remotes like cloud storage for your Git history. Just as you sync files with Dropbox or Google Drive, you synchronize commits with remote repositories like GitHub, GitLab, or Bitbucket. But unlike simple file sync, Git gives you precise control over what gets synchronized and when.
In this lesson, you'll learn:
- What remotes actually are (not as mysterious as they seem!)
- The critical difference between
fetchandpull - How tracking branches connect local and remote work
- Real collaboration workflows including pull requests
- How to handle the dreaded "diverged branches" situation
💡 Mental Model Shift: A remote isn't a mystical cloud entity — it's just another Git repository that happens to live somewhere else (GitHub's servers, your coworker's laptop, or even another folder on your machine).
Core Concepts
What Are Remotes? 🔗
A remote is simply a bookmark to another Git repository. When you clone a repo from GitHub, Git automatically creates a remote called origin pointing to that GitHub URL.
Think of remotes like contacts in your phone:
- Name: A shorthand label (usually
origin) - URL: Where the remote repository actually lives
- Purpose: Quick reference so you don't type full URLs every time
View your remotes:
git remote -v
# origin https://github.com/user/repo.git (fetch)
# origin https://github.com/user/repo.git (push)
You can have multiple remotes! Common pattern:
origin → Your fork on GitHub
upstream → Original project you forked from
staging → Company staging server
🔧 Try this: Run git remote -v in any cloned repository to see where it came from.
Remote Branches vs Local Branches 🌳
When you clone a repository, Git creates two types of branches:
[Your Local Branches] [Remote-Tracking Branches]
main origin/main
feature-auth origin/feature-auth
origin/develop
Local branches are what you work on directly with git checkout.
Remote-tracking branches (like origin/main) are read-only references showing where remote branches were the last time you synchronized. They're snapshots of the remote state.
💡 Key Insight: origin/main is NOT the current state of main on GitHub — it's what main looked like when you last fetched/pulled!
Local main origin/main GitHub's actual main
● ● ●
│ │ │
● ● ●───● ← New commits
│ │ you haven't fetched
● ●
Tracking Branches 🎯
When a local branch is set up to track a remote branch, Git knows they're related. This enables:
git statusshowing "Your branch is ahead by 2 commits"git pullwithout arguments (knows where to pull from)git pushwithout arguments (knows where to push to)
When you clone, main automatically tracks origin/main. For new branches:
git checkout -b feature-login
git push -u origin feature-login # -u sets up tracking
The -u flag (short for --set-upstream) creates the tracking relationship.
The Three Remote Commands: Fetch vs Pull vs Push 📡
This is where confusion happens. Let's clarify with a real-world analogy:
Fetch = Checking your mailbox 📬
- Downloads new commits from remote
- Updates remote-tracking branches (origin/main)
- Doesn't change your working directory at all
- Safe to run anytime
Pull = Fetch + Automatically open and apply the mail 📬➡️📖
- Downloads commits (fetch)
- Immediately merges them into your current branch
- Can cause merge conflicts
- Essentially
git fetch+git merge
Push = Sending your mail 📮
- Uploads your local commits to remote
- Updates the remote branch
- Only works if you have the latest remote changes
┌─────────────────────────────────────────────────────────┐
│ FETCH (Safe, Read-Only) │
│ │
│ Remote Repo → Download → origin/main (updated) │
│ (GitHub) Your local main stays │
│ exactly the same │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ PULL (Fetch + Merge) │
│ │
│ Remote Repo → Download → origin/main → Merge │
│ (GitHub) ↓ │
│ Your main │
│ (changed!) │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ PUSH (Upload) │
│ │
│ Your main → Upload → Remote Repo (GitHub) │
│ origin/main updated │
└─────────────────────────────────────────────────────────┘
🧠 Mnemonic: Fetch is First and safe — always fetch before pull to see what's coming!
Workflow Pattern: Fetch, Inspect, Merge 🔍
Professional workflow (safer than git pull):
git fetch origin # Download updates
git log origin/main..main # What would be merged?
git diff origin/main # See actual changes
git merge origin/main # Merge when ready
This gives you control. git pull does all of this in one blind step.
⚠️ Common Mistake: Running git pull without checking what you're about to merge. Always know what's coming into your branch!
Example 1: Basic Push/Pull Workflow 📤📥
Scenario: You're working on a team website. You need to share your changes and get your teammate's updates.
# 1. Start your day — get latest changes
git checkout main
git pull origin main
# Already up to date.
# 2. Create a feature branch
git checkout -b add-footer
# 3. Make changes and commit
echo "<footer>© 2024</footer>" >> index.html
git add index.html
git commit -m "Add footer to homepage"
# 4. Push your branch to share with team
git push -u origin add-footer
# Counting objects: 3, done.
# To https://github.com/team/website.git
# * [new branch] add-footer -> add-footer
# Branch 'add-footer' set up to track remote branch 'add-footer' from 'origin'.
# 5. Later, teammate adds comments. Get updates:
git fetch origin
git merge origin/add-footer
# Updating 3a8f2b1..7c9e4d2
# Fast-forward
What happened:
pullgot latest main- Created local feature branch
-uset up tracking so future pushes are justgit pushfetch+mergeincorporated teammate's changes
💡 Pro Tip: First push needs -u origin branch-name. After that, just git push!
Example 2: Handling Diverged Branches 🔀
Scenario: You committed locally, but someone else pushed to the same branch first. This is the dreaded "diverged history" situation.
# You commit locally
git commit -m "Update login form"
# Try to push
git push origin main
# To https://github.com/team/project.git
# ! [rejected] main -> main (fetch first)
# error: failed to push some refs
# hint: Updates were rejected because the remote contains work that you do
# hint: not have locally. This is usually caused by another repository pushing
The situation visualized:
GitHub's main Your local main
● ●
│ │
A──B──C A──B──D
↑ ↑
teammate's your commit
commit
Both branches have diverged from commit B. You can't just push — Git doesn't know which history is "correct."
Solution 1: Merge (creates merge commit)
git fetch origin # Get commit C
git merge origin/main # Merge C into your branch
# Merge made by the 'recursive' strategy.
git push origin main # Now push works!
Result:
●
/│\
C───D─M ← Merge commit
Solution 2: Rebase (cleaner history)
git fetch origin
git rebase origin/main # Replay your D after their C
git push origin main
Result:
A──B──C──D' ← Your commit moved after C
🤔 Did you know? GitHub processes over 100 million pull requests per year. Diverged branches are extremely common — handling them smoothly is a key professional skill!
Example 3: Pull Request Workflow 🔄
Pull Requests (PRs) are GitHub/GitLab/Bitbucket features, not Git itself. They're a way to say "I'd like to merge my branch — please review it first."
Typical workflow:
# 1. Fork repo on GitHub (creates your copy)
# 2. Clone YOUR fork
git clone https://github.com/YOU/project.git
cd project
# 3. Add original repo as 'upstream' remote
git remote add upstream https://github.com/ORIGINAL/project.git
# 4. Create feature branch from latest main
git checkout main
git pull upstream main # Get original's latest
git checkout -b fix-typo
# 5. Make changes and commit
vi README.md
git add README.md
git commit -m "Fix typo in installation instructions"
# 6. Push to YOUR fork
git push origin fix-typo
# 7. Go to GitHub, click "Create Pull Request"
# 8. Original maintainers review, request changes, eventually merge
Remote setup:
┌──────────────────────────────────────────────────────┐
│ Your Local Repo │
│ ↓ push ↑ pull/fetch │
│ origin (your fork) upstream (original) │
│ ↓ ↑ │
│ [GitHub: you/project] [GitHub: original/project] │
│ ↑ │
│ (send PR here) │
└──────────────────────────────────────────────────────┘
💡 Workflow Tip: Always pull from upstream, push to origin. This keeps your fork in sync with the original project.
Example 4: Fetch vs Pull in Practice 🔬
Let's see the difference in action:
# Setup: You're on main, teammate just pushed
# USING FETCH (safe inspection)
git fetch origin
# From https://github.com/team/project
# * branch main -> FETCH_HEAD
git status
# On branch main
# Your branch is behind 'origin/main' by 3 commits.
git log origin/main --oneline
# 7a8e9f2 (origin/main) Add user authentication
# 3c4d5e6 Update dependencies
# 1b2c3d4 Fix bug in payment module
git diff origin/main
# ... see all changes before merging ...
# Decide to merge
git merge origin/main
# VERSUS USING PULL (automatic)
git pull origin main
# ... immediately downloads AND merges ...
# Auto-merging src/app.js
# CONFLICT (content): Merge conflict in src/app.js
# Uh oh! No chance to inspect first.
⚠️ Best Practice: Use fetch + merge when you want control. Use pull for trusted branches where conflicts are unlikely (like pulling main when you haven't modified it).
Common Mistakes ⚠️
1. Pushing Before Pulling
# BAD
git commit -m "My changes"
git push origin main # ERROR: rejected!
# GOOD
git commit -m "My changes"
git pull origin main # Get remote changes first
git push origin main # Now it works
2. Not Setting Upstream on First Push
# BAD
git push # fatal: The current branch has no upstream branch
# GOOD
git push -u origin feature-name # First push sets upstream
git push # Future pushes work
3. Confusing origin/main with main
# These are DIFFERENT branches!
git checkout main # Your local main
git checkout origin/main # Error! It's read-only
# origin/main is a reference, not a branch you can work on
4. Force Pushing Shared Branches
# DANGEROUS on shared branches!
git push --force origin main # Overwrites teammate's work!
# SAFE alternative
git push --force-with-lease origin main # Fails if remote changed
⚠️ Critical Rule: Never force push to branches others are working on. You'll erase their commits and make enemies. 😱
5. Pulling with Uncommitted Changes
# Setup: You have uncommitted changes
vi file.txt # Make changes but don't commit
git pull origin main
# error: Your local changes to the following files would be overwritten by merge:
# file.txt
# Please commit your changes or stash them before you merge.
# Solutions:
git stash # Save changes temporarily
git pull origin main
git stash pop # Restore changes
# OR
git commit -m "WIP" # Commit first
git pull origin main
6. Not Fetching Regularly
# BAD: Work for days without fetching
git push origin main # Huge divergence!
# GOOD: Fetch every morning
git fetch origin # See what teammates did
git log origin/main # Any surprises?
💡 Pro Habit: Run git fetch origin at the start of each day. Like checking email — you want to know what happened!
Advanced: Git Pull Behavior 🎛️
By default, git pull = git fetch + git merge. But you can change this!
# Option 1: Pull with merge (default)
git pull origin main
# Creates merge commits
# Option 2: Pull with rebase
git pull --rebase origin main
# Replays your commits on top of remote
# Set rebase as default
git config --global pull.rebase true
When to use each:
- Merge: Preserves exact history, shows when branches converged
- Rebase: Cleaner linear history, looks like you developed sequentially
🧠 Team Convention Matters: Some teams prefer merge commits (shows collaboration), others prefer rebase (cleaner logs). Follow your team's standard!
Tracking Branch Management 🎯
# See which branches track what
git branch -vv
# * main 3a8f2b1 [origin/main] Latest commit
# feature-x 7c9e4d2 [origin/feature-x: ahead 2] Ahead by 2
# Set tracking for existing branch
git branch --set-upstream-to=origin/develop develop
# Push and set tracking in one command
git push -u origin feature-name
# Remove tracking (makes branch purely local)
git branch --unset-upstream
Key Takeaways 🎯
Remotes are bookmarks — shortcuts to other Git repositories, nothing magical
Remote-tracking branches (origin/main) are snapshots, not live connections
Fetch is safe — downloads without changing your working directory
Pull = Fetch + Merge — convenient but less control
Push requires being up-to-date — always pull (or fetch+merge) before pushing
Tracking branches connect local and remote branches for easier push/pull
First push needs
-u— sets up tracking relationshipNever force push shared branches — use
--force-with-leaseif you mustProfessional workflow:
fetch→inspect→merge→pushPull requests are a review process, not a Git command
Quick Reference Card 📋
┌─────────────────────────────────────────────────────────┐
│ REMOTE COMMANDS CHEAT SHEET │
├─────────────────────────────────────────────────────────┤
│ Setup & Info │
│ git remote -v List remotes with URLs │
│ git remote add name url Add new remote │
│ git branch -vv Show tracking relationships │
│ │
│ Getting Updates │
│ git fetch origin Download all branches │
│ git fetch origin main Download just main │
│ git pull origin main Fetch + merge │
│ git pull --rebase Fetch + rebase │
│ │
│ Sending Changes │
│ git push origin main Upload to main │
│ git push -u origin branch Push + set tracking │
│ git push --force-with-lease Safe force push │
│ │
│ Inspecting │
│ git log origin/main See remote commits │
│ git diff origin/main Compare to remote │
│ git log origin/main..main What haven't I pushed? │
│ git log main..origin/main What haven't I pulled? │
│ │
│ Workflow Pattern │
│ 1. git fetch origin │
│ 2. git log origin/main (inspect) │
│ 3. git merge origin/main (when ready) │
│ 4. (make changes & commit) │
│ 5. git push origin main │
└─────────────────────────────────────────────────────────┘
📚 Further Study
Git Documentation - Working with Remotes
https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes
Official guide covering remotes in depth with examplesAtlassian Git Tutorial - Syncing
https://www.atlassian.com/git/tutorials/syncing
Comprehensive tutorial on fetch, pull, push, and remote managementGitHub Docs - About Pull Requests
https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests
Understanding the pull request workflow on GitHub
Next Lesson Preview: In Lesson 8, we'll explore advanced Git workflows including Git hooks, submodules, and handling large repositories efficiently. 🚀