You are viewing a preview of this course. Sign in to start learning

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 fetch and pull
  • 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 status showing "Your branch is ahead by 2 commits"
  • git pull without arguments (knows where to pull from)
  • git push without 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:

  • pull got latest main
  • Created local feature branch
  • -u set up tracking so future pushes are just git push
  • fetch + merge incorporated 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 🎯

  1. Remotes are bookmarks — shortcuts to other Git repositories, nothing magical

  2. Remote-tracking branches (origin/main) are snapshots, not live connections

  3. Fetch is safe — downloads without changing your working directory

  4. Pull = Fetch + Merge — convenient but less control

  5. Push requires being up-to-date — always pull (or fetch+merge) before pushing

  6. Tracking branches connect local and remote branches for easier push/pull

  7. First push needs -u — sets up tracking relationship

  8. Never force push shared branches — use --force-with-lease if you must

  9. Professional workflow: fetchinspectmergepush

  10. Pull 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

  1. Git Documentation - Working with Remotes
    https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes
    Official guide covering remotes in depth with examples

  2. Atlassian Git Tutorial - Syncing
    https://www.atlassian.com/git/tutorials/syncing
    Comprehensive tutorial on fetch, pull, push, and remote management

  3. GitHub 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. 🚀