Skip to content

Remote Repositories

So far, everything has been local - one repository on your machine. In practice, you work with code that lives on a server, collaborate with other developers, and synchronize changes between multiple copies of the same repository. Remotes are Git's mechanism for connecting these repositories together.


What Is a Remote?

A remote is a bookmark to another Git repository. It's a short name (like origin) mapped to a URL where another copy of your repository lives. Remotes don't maintain a live connection - they're just URLs that Git uses when you explicitly fetch or push.

When you clone a repository, Git automatically creates a remote called origin pointing to the URL you cloned from. You can add more remotes, rename them, or remove them.

# List remotes
git remote

# List remotes with URLs
git remote -v
origin  git@github.com:yourname/project.git (fetch)
origin  git@github.com:yourname/project.git (push)

The fetch and push URLs are usually the same, but they can differ (for example, fetching over HTTPS but pushing over SSH).


Cloning a Repository

git clone creates a local copy of a remote repository. It does more than download files:

  1. Creates a new directory
  2. Initializes a .git directory
  3. Creates a remote called origin pointing to the source URL
  4. Fetches all branches and their history
  5. Creates remote-tracking branches (origin/main, origin/feature/x)
  6. Checks out the default branch (usually main) and creates a local tracking branch
# Clone via SSH (preferred - uses your SSH key)
git clone git@github.com:user/repo.git

# Clone via HTTPS (prompts for credentials)
git clone https://github.com/user/repo.git

# Clone into a specific directory
git clone git@github.com:user/repo.git my-project

# Clone only the most recent history (faster for large repos)
git clone --depth 1 git@github.com:user/repo.git

After cloning, git remote -v shows the origin:

git remote -v
# origin  git@github.com:user/repo.git (fetch)
# origin  git@github.com:user/repo.git (push)

Remote-Tracking Branches

When you clone or fetch, Git creates remote-tracking branches - read-only references that remember where branches are on the remote. They're named <remote>/<branch>:

origin/main
origin/feature/auth
origin/release/2.0

You can't commit to remote-tracking branches directly. They're updated only by git fetch or git pull. Think of them as bookmarks: "this is where main was on origin the last time I checked."

# List remote-tracking branches
git branch -r

# List all branches (local + remote-tracking)
git branch -a

The relationship between local branches, remote-tracking branches, and remote branches:

flowchart LR
    subgraph Local["Your Machine"]
        direction TB
        LB["Local branch<br/>main"]
        RTB["Remote-tracking branch<br/>origin/main"]
    end

    subgraph Remote["GitHub / GitLab"]
        direction TB
        RB["Remote branch<br/>main"]
    end

    RTB -->|"git push"| RB
    RB -->|"git fetch"| RTB
    RTB -->|"git merge origin/main"| LB
    LB -->|"git push"| RB

Fetch vs Pull

This distinction trips up most beginners. They're related but different operations.

git fetch

git fetch downloads new commits, branches, and tags from a remote - but does not change your working directory or local branches. It only updates your remote-tracking branches.

# Fetch from origin
git fetch origin

# Fetch from all remotes
git fetch --all

# Fetch and prune deleted remote branches
git fetch --prune

After fetching, you can inspect what changed before integrating:

# See what's new on origin/main
git log main..origin/main --oneline

# See the diff
git diff main origin/main

git pull

git pull is git fetch followed by git merge (by default). It downloads and immediately integrates remote changes into your current branch:

# Equivalent operations:
git pull origin main
# is the same as:
git fetch origin
git merge origin/main

Why fetch + merge Is Often Safer

git pull merges automatically, which can create unexpected merge commits or conflicts when you're not ready. With fetch + merge, you can:

  1. Fetch to see what changed
  2. Inspect the incoming commits
  3. Decide how to integrate (merge, rebase, or wait)
git fetch origin
git log --oneline main..origin/main    # Review new commits
git merge origin/main                   # Integrate when ready

Configure pull behavior

You can make git pull rebase instead of merge:

git config --global pull.rebase true

Or require explicit choice every time (no silent merges):

git config --global pull.ff only

With pull.ff = only, git pull refuses to create a merge commit. If a fast-forward isn't possible, it fails and tells you to choose between merge and rebase explicitly.


Pushing Changes

git push uploads your local commits to a remote repository:

# Push current branch to its upstream
git push

# Push a specific branch to a specific remote
git push origin main

# Push and set upstream tracking (first push of a new branch)
git push -u origin feature/auth

# Push all branches
git push --all

# Push tags
git push --tags

Push Rejection

If the remote has commits that you don't have locally, Git rejects the push to prevent overwriting others' work:

! [rejected]        main -> main (fetch first)
error: failed to push some refs to 'git@github.com:user/repo.git'
hint: Updates were rejected because the remote contains work that you do not
hint: have locally. Integrate the remote changes (e.g., 'git pull ...') before
hint: pushing again.

The fix: fetch, integrate (merge or rebase), then push again:

git fetch origin
git merge origin/main    # or: git rebase origin/main
git push origin main

Never force push shared branches

git push --force overwrites the remote branch with your local version, potentially destroying other people's commits. Only force push to branches that you alone work on (and even then, prefer --force-with-lease which fails if someone else has pushed since your last fetch).


Tracking Branches and Upstream Configuration

A tracking branch (or upstream branch) is a local branch configured to follow a remote branch. When you git clone, main automatically tracks origin/main. For new branches, you set tracking explicitly:

# Set upstream when pushing a new branch
git push -u origin feature/auth

# Set upstream for an existing branch
git branch --set-upstream-to=origin/feature/auth feature/auth

# Check tracking configuration
git branch -vv

git branch -vv shows each branch's upstream:

  feature/auth  a1b2c3d [origin/feature/auth: ahead 2] Add OAuth handler
* main          e4f5a6b [origin/main] Latest commit message

"ahead 2" means your local branch has 2 commits not yet pushed. "behind 3" would mean the remote has 3 commits you haven't fetched.


Managing Remotes

Adding a Remote

Common when working with forks - you have your fork as origin and the original repo as upstream:

# Add a new remote
git remote add upstream git@github.com:original-author/repo.git

# Verify
git remote -v
# origin    git@github.com:yourname/repo.git (fetch)
# origin    git@github.com:yourname/repo.git (push)
# upstream  git@github.com:original-author/repo.git (fetch)
# upstream  git@github.com:original-author/repo.git (push)

Renaming and Removing

# Rename a remote
git remote rename origin github

# Remove a remote
git remote remove upstream

# Show details about a remote
git remote show origin

git remote show origin displays useful information: the fetch/push URLs, tracked branches, and whether local branches are ahead or behind.


SSH vs HTTPS Authentication

Git supports two main protocols for communicating with remote repositories.

HTTPS

  • URL format: https://github.com/user/repo.git
  • Prompts for username/password (or token)
  • Works through most firewalls and proxies
  • Requires credential helper to avoid retyping passwords
# Configure credential caching (in memory for 15 minutes)
git config --global credential.helper cache

# macOS keychain
git config --global credential.helper osxkeychain

# Windows credential manager
git config --global credential.helper manager

Use tokens, not passwords

GitHub, GitLab, and Bitbucket no longer accept account passwords for HTTPS Git operations. Use a personal access token (PAT) instead of your password. Generate one in your platform's settings under Developer Settings or Access Tokens.

SSH

  • URL format: git@github.com:user/repo.git
  • Uses SSH key pairs (no passwords after setup)
  • More secure than HTTPS with passwords
  • Requires SSH key generation and registration with the platform

Switching a Remote from HTTPS to SSH

# Check current URL
git remote -v
# origin  https://github.com/user/repo.git (fetch)

# Switch to SSH
git remote set-url origin git@github.com:user/repo.git

# Verify
git remote -v
# origin  git@github.com:user/repo.git (fetch)

Working with Forks

In open-source projects, you typically don't have push access to the original repository. The workflow is:

  1. Fork the repository on the platform (creates your copy under your account)
  2. Clone your fork locally (this becomes origin)
  3. Add the original repository as a remote called upstream
  4. Fetch from upstream to stay current
  5. Create branches for your work (based on upstream/main)
  6. Push to your fork (origin)
  7. Open a pull request from your fork to the original repository
# Clone your fork
git clone git@github.com:yourname/project.git
cd project

# Add the original repo as upstream
git remote add upstream git@github.com:original-author/project.git

# Stay current with upstream
git fetch upstream
git merge upstream/main    # or rebase

# Work on a feature
git switch -c feature/my-contribution
# ... make changes, commit ...

# Push to your fork
git push -u origin feature/my-contribution

# Open a pull request on the platform

Exercise


Further Reading


Previous: Branches and Merging | Next: Rewriting History | Back to Index

Comments