<!--order=5-->
<h1>Git: Working with Remotes</h1>
<p>A remote is a reference to another copy of the repository — usually on a server (GitHub, GitLab, a VPS). Remotes let multiple people share work. Git keeps local "remote-tracking branches" (like <code>origin/main</code>) as snapshots of the last known state of the remote; these are updated with <code>git fetch</code>.</p>

<h2>Commands Covered</h2>
<p><code>git remote</code> · <code>git fetch</code> · <code>git pull</code> · <code>git push</code> · <code>git ls-remote</code></p>

<h2>git remote</h2>
<pre>
# List remotes
$ git remote
# origin

# List remotes with URLs
$ git remote -v
# origin  https://github.com/user/repo.git (fetch)
# origin  https://github.com/user/repo.git (push)

# Add a remote
$ git remote add origin https://github.com/user/repo.git
$ git remote add upstream https://github.com/original/repo.git

# Remove a remote
$ git remote remove upstream
$ git remote rm upstream   # same

# Rename a remote
$ git remote rename origin old-origin

# Change a remote's URL
$ git remote set-url origin https://github.com/user/new-repo.git
# Switch from HTTPS to SSH:
$ git remote set-url origin git@github.com:user/repo.git

# Show detailed info about a remote (branches, tracking info)
$ git remote show origin

# Prune stale remote-tracking branches (branches deleted on remote)
$ git remote prune origin
$ git fetch --prune   # same effect; prune during fetch
</pre>

<h2>git fetch</h2>
<p>Downloads objects and refs from a remote but does NOT modify your working tree or local branches. Safe to run at any time — it just updates your remote-tracking branches (<code>origin/main</code>, etc.).</p>
<pre>
# Fetch all changes from origin (the default remote)
$ git fetch
$ git fetch origin

# Fetch a specific branch
$ git fetch origin main
$ git fetch origin feature-login

# Fetch from all remotes
$ git fetch --all

# Fetch and prune stale remote-tracking branches
$ git fetch --prune
$ git fetch -p

# Fetch all tags
$ git fetch --tags

# Fetch and show what changed
$ git fetch origin
$ git log HEAD..origin/main    # commits on remote not yet local
$ git diff HEAD origin/main    # diff between local and remote
</pre>

<h2>git pull</h2>
<p><code>git pull</code> is <code>git fetch</code> followed by a merge (or rebase) in one step. It integrates remote changes into your current branch.</p>
<pre>
# Pull from the tracked remote branch
$ git pull

# Pull from a specific remote and branch
$ git pull origin main

# Pull with rebase instead of merge (cleaner history)
$ git pull --rebase
$ git pull --rebase origin main

# Set rebase as the default pull strategy globally:
$ git config --global pull.rebase true

# Pull and prune stale remote-tracking branches
$ git pull --prune

# Pull without merging or rebasing — just fetch (then you decide)
$ git fetch origin
$ git log HEAD..origin/main    # review incoming commits
$ git merge origin/main        # then manually merge

# If a pull creates merge conflicts:
$ git pull
# CONFLICT — fix files
$ git add conflicted-file.c
$ git commit   # or: git rebase --continue if pulling with --rebase
</pre>

<h3>pull vs. fetch + merge</h3>
<table>
  <thead><tr><th>Command</th><th>What it does</th><th>When to use</th></tr></thead>
  <tbody>
    <tr><td><code>git pull</code></td><td>fetch + merge in one step</td><td>Quick; fine when you trust what's on the remote</td></tr>
    <tr><td><code>git pull --rebase</code></td><td>fetch + rebase in one step</td><td>Cleaner linear history; preferred in many teams</td></tr>
    <tr><td><code>git fetch</code> then review</td><td>fetch without integrating</td><td>When you want to see what changed before merging</td></tr>
  </tbody>
</table>

<h2>git push</h2>
<p>Uploads local commits to a remote repository. Push only works cleanly when the remote doesn't have commits you don't have locally (i.e., your branch is up to date with the remote).</p>
<pre>
# Push current branch to its tracked remote branch
$ git push

# Push to a specific remote and branch
$ git push origin main
$ git push origin feature-login

# Push and set the upstream tracking branch (first push of a new branch)
$ git push -u origin feature-login
$ git push --set-upstream origin feature-login
# After -u, you can just use: git push (no args needed)

# Push all local branches
$ git push --all origin

# Push all tags
$ git push --tags
# Push a specific tag
$ git push origin v1.0.0

# Delete a remote branch
$ git push origin --delete feature-old
$ git push origin :feature-old   # older syntax — same effect

# Delete a remote tag
$ git push origin --delete v0.1-beta
$ git push origin :refs/tags/v0.1-beta

# Force push (rewrite remote history — use with extreme caution)
$ git push --force
# Safer alternative: fails if someone else has pushed since your last fetch
$ git push --force-with-lease
</pre>

<h3>When Push Fails</h3>
<pre>
# Push rejected because remote has commits you don't:
$ git push
# error: failed to push some refs to 'origin'
# hint: Updates were rejected because the remote contains work that you do not have locally.

# Fix: fetch and integrate, then push
$ git fetch origin
$ git rebase origin/main   # or: git merge origin/main
$ git push
</pre>

<h2>git ls-remote</h2>
<p>Lists references (branches, tags) in a remote repository without cloning or fetching it.</p>
<pre>
# List all refs on a remote
$ git ls-remote origin

# List only branches (heads)
$ git ls-remote --heads origin

# List only tags
$ git ls-remote --tags origin

# Filter by pattern
$ git ls-remote origin 'refs/heads/feature-*'

# Check if a specific branch exists on remote
$ git ls-remote --heads origin feature-login
# Empty output = branch doesn't exist on remote

# Use with any URL (without adding a remote)
$ git ls-remote https://github.com/user/repo.git
</pre>

<h2>Tracking Branches</h2>
<pre>
# See which local branches track which remote branches
$ git branch -vv
# main  abc1234 [origin/main] Latest commit message
# feat  def5678 [origin/feat: ahead 2] More commits

# "ahead N" = N local commits not yet pushed
# "behind N" = N remote commits not yet fetched/merged
# "ahead N, behind M" = diverged — both have new commits

# Set up tracking for an existing local branch
$ git branch -u origin/main main
$ git branch --set-upstream-to=origin/develop develop
</pre>

<h2>References</h2>
<ul>
  <li><a href="https://git-scm.com/docs/git-remote" target="_blank" rel="noopener">git-remote documentation ↗</a></li>
  <li><a href="https://git-scm.com/docs/git-push" target="_blank" rel="noopener">git-push documentation ↗</a></li>
  <li><a href="https://git-scm.com/docs/git-fetch" target="_blank" rel="noopener">git-fetch documentation ↗</a></li>
  <li><a href="https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes" target="_blank" rel="noopener">Pro Git Book — Remotes ↗</a></li>
</ul>
