Remote Repositories
1. Overview
This chapter covers how Git communicates with remote repositories — fetching changes others have made, pushing your own work, and keeping local and remote branches in sync. These are the operations that turn Git from a local history tool into a collaboration platform.
The basics of connecting to a remote were introduced in Introduction (Exercise 6). This chapter goes deeper into the mechanics and the workflows you will use daily.
In this chapter you will learn:
- How remote branches and remote-tracking references work
- How to clone a repository and what Git sets up automatically
- How to fetch changes from a remote without modifying your working tree
- How to pull remote changes and handle conflicts
- How to push local commits to a remote branch
- How forking workflows enable contribution to external projects

2. Remote Branches
A remote is a named reference to another repository, usually hosted on
a service like GitHub, GitLab, or Bitbucket. When you clone a
repository, Git automatically creates a remote called origin that
points to the URL you cloned from.
Listing remotes
$ git remote # list remote names
$ git remote -v # list names with URLs
Example output:
origin https://github.com/user/project.git (fetch)
origin https://github.com/user/project.git (push)
The fetch and push URLs are usually the same, but they can differ.
Adding a remote
$ git remote add upstream https://github.com/original/project.git
This registers a new remote called upstream. You can choose any name,
but origin and upstream are conventional:
| Name | Convention |
|---|---|
origin | Your own copy (the one you cloned or created) |
upstream | The original repository you forked from |
Renaming and removing remotes
$ git remote rename old-name new-name
$ git remote remove upstream
Removing a remote also deletes all its remote-tracking branches.
Checking sync status
For every branch on a remote, Git keeps a local read-only reference
called a remote-tracking branch. These follow the pattern
<remote>/<branch>:
origin/main
origin/feature
upstream/main
As covered in Building Blocks, these references
live in .git/refs/remotes/. They are updated automatically by fetch
and pull, never by your local commits.
$ git branch -vv
This shows each local branch, its tracking relationship, and whether it is ahead, behind, or diverged:
* main abc1234 [origin/main] Latest commit message
feature def5678 [origin/feature: ahead 2] Work in progress
| Status | Meaning |
|---|---|
| ahead 2 | You have 2 local commits not yet pushed |
| behind 3 | The remote has 3 commits you have not fetched |
| ahead 1, behind 2 | Both sides have new commits (diverged) |
3. Cloning
Cloning creates a local copy of a remote repository. It downloads the
full history, sets up origin pointing to the source URL, creates
remote-tracking branches for every remote branch, and checks out the
default branch.

$ git clone https://github.com/user/project.git
$ git clone https://github.com/user/project.git my-folder # custom directory name
Git supports two URL protocols:
| Protocol | URL format | Notes |
|---|---|---|
| HTTPS | https://github.com/user/repo.git | Works everywhere, prompts for credentials |
| SSH | git@github.com:user/repo.git | Requires SSH key setup, no password prompts |
HTTPS is simpler to start with. SSH is covered in the Appendix.
4. Fetching
Fetching downloads commits, branches, and tags from a remote and updates the remote-tracking branches. It does not modify your working tree or local branches.
$ git fetch origin # fetch all branches from origin
$ git fetch origin main # fetch only the main branch
$ git fetch --all # fetch from all configured remotes
After fetching, you can inspect what changed before deciding to integrate:
$ git log main..origin/main # commits on remote that you don't have
$ git diff main origin/main # line-by-line differences
Fetching is always safe — it never changes your local branches or working tree.
5. Pulling
Pulling combines two operations in one command: fetch followed by merge.
$ git pull origin main # equivalent to fetch + merge
Pull with rebase
By default, git pull creates a merge commit when your branch has
diverged from the remote. To produce a linear history instead, use
rebase:
$ git pull --rebase origin main
This replays your local commits on top of the remote changes, avoiding the merge commit. Many teams prefer this for feature branches to keep the history clean.
To make rebase the default pull strategy:
$ git config --global pull.rebase true
Handling pull conflicts
If the remote changes conflict with your local changes, Git stops and asks you to resolve the conflict — the same process described in Branching and Merging. After resolving:
$ git add <resolved-file>
$ git commit # if pulling with merge
$ git rebase --continue # if pulling with rebase
6. Pushing
Pushing uploads your local commits to a remote branch. The remote branch is updated to match your local branch.
$ git push origin main
Setting upstream tracking
The -u flag links your local branch to a remote branch so that
future push and pull commands work without specifying the remote
and branch name:
$ git push -u origin feature # first push — sets up tracking
$ git push # subsequent pushes — no arguments needed
Rejected pushes
A push is rejected when the remote branch has commits that your local branch does not have:
! [rejected] main -> main (non-fast-forward)
This means someone else pushed changes since your last fetch. To fix this:
- Pull the remote changes:
git pull origin main - Resolve any conflicts
- Push again:
git push origin main
Force pushing
Force pushing overwrites the remote branch with your local history:
| Command | Behavior |
|---|---|
git push --force | Overwrites unconditionally — can discard others’ work |
git push --force-with-lease | Fails if someone else pushed since your last fetch |
Always prefer --force-with-lease over --force.
Warning: Never force push to shared branches like
main. It rewrites history for everyone and can cause data loss. Use force push only on your own feature branches.
7. Forking
Forking is a hosting-platform feature (not a Git command) that creates your own copy of someone else’s repository under your account. This is the standard way to contribute to projects you do not have write access to.

Setup
- Fork the repository on the hosting platform (e.g. GitHub)
- Clone your fork locally:
$ git clone https://github.com/you/project.git - Add the original as upstream:
$ git remote add upstream https://github.com/original/project.git
Contributing
- Create a feature branch from an up-to-date
main:$ git fetch upstream $ git switch -c feature/my-change upstream/main - Make your changes and commit
- Push to your fork:
$ git push -u origin feature/my-change - Open a pull request from your fork’s branch to the original
repository’s
mainbranch
Keeping your fork in sync
$ git fetch upstream
$ git switch main
$ git merge upstream/main
$ git push origin main
This pulls the latest changes from the original repository into your fork. Do this regularly to avoid large divergences.
Exercises
All exercises use the concepts-lab repository from previous chapters.
Exercise 1: Clone and inspect a repository
Task: Clone a repository and explore what Git sets up automatically.
Steps:
- On GitHub, create a new repository called
clone-labwith a README - Clone it locally:
git clone <url> clone-lab - Enter the directory and run
git remote -v - Run
git branch -vvto see the tracking relationship - Run
git log --onelineto confirm the initial commit is present - List the remote-tracking branches:
git branch -r - Inspect
.git/refs/remotes/origin/to see the tracking reference
Verify:
git remote -v shows origin pointing to your GitHub URL.
git branch -vv shows main tracking origin/main.
git branch -r lists origin/main.
Exercise 2: Fetch and inspect before merging
Task: Practice the fetch-then-inspect workflow instead of pulling directly.
Steps:
- On GitHub, edit a file directly in the browser on the
mainbranch (add a comment line to any file) and commit the change - Back in your terminal, run
git fetch origin - Run
git log main..origin/main --onelineto see what changed - Run
git diff main origin/mainto see the exact differences - Once satisfied, run
git merge origin/mainto integrate the changes - Confirm with
git log --onelinethat the remote commit is now in your local history
Verify:
After merging, git status shows your branch is up to date with
origin/main. The commit made on GitHub appears in git log.
Exercise 3: Handle a rejected push
Task: Simulate a rejected push and resolve it.
Steps:
- On GitHub, edit a file on
mainand commit (simulating a teammate’s push) - Locally, edit a different file on
mainand commit - Run
git push origin main— it should be rejected withnon-fast-forward - Run
git pull origin mainto fetch and merge the remote changes - If there are no conflicts, Git creates a merge commit automatically
- Run
git push origin main— it should succeed - Run
git log --oneline --graphto see the merge in history
Verify:
git log --graph shows the divergence and merge. git status reports
the branch is up to date with origin/main.
Exercise 4: Push with upstream tracking
Task: Set up upstream tracking and verify it simplifies push/pull.
Steps:
- In
concepts-lab, create and switch to a new branchfeature/tracking - Create a file
tracking.txt, stage and commit - Push with the
-uflag:git push -u origin feature/tracking - Run
git branch -vvto confirm the tracking relationship - Make another change, commit, and run
git pushwith no arguments - Confirm the push succeeded without specifying remote or branch
Verify:
git branch -vv shows feature/tracking tracking origin/feature/tracking.
The second push works with no arguments.
Exercise 5: Fork and contribute
Task: Practice the forking workflow using your own concepts-lab
repository as the “original” project.
Steps:
- On GitHub, open
concepts-laband click “Fork” to create a fork under your own account (GitHub allows forking your own repos into an organization, or you can use a second account) - Clone the fork locally into a new directory:
git clone <fork-url> concepts-lab-fork - Enter the directory and add the original as upstream:
git remote add upstream <original-url> - Verify with
git remote -v— you should see bothorigin(fork) andupstream(original) - Create a feature branch:
git switch -c feature/fork-test - Create a file
fork-test.txt, commit it, and push to your fork:git push -u origin feature/fork-test - On GitHub, open a pull request from the fork’s branch to the original repository
Verify:
git remote -v shows two remotes. The pull request appears on the
original repository’s GitHub page.
Quiz
Q1. What does git clone set up automatically?
- A) Only the working tree — no remote or tracking branches
- B) A local copy, an
originremote, and remote-tracking branches - C) A bare repository with no working tree
- D) A fork on the hosting platform
Q2. What does git fetch do?
- A) Downloads remote changes and merges them into your branch
- B) Downloads remote changes and updates remote-tracking branches only
- C) Pushes local changes to the remote
- D) Deletes remote branches that no longer exist
Q3. What is the difference between git pull and git fetch?
- A) They are identical commands
- B)
pullonly downloads;fetchalso merges - C)
pullfetches and then merges;fetchonly downloads - D)
pullworks with tags;fetchworks with branches
Q4. Why might a git push be rejected?
- A) The remote repository is read-only
- B) The remote branch has commits that your local branch does not have
- C) Your local branch is ahead of the remote
- D) You forgot to run
git addfirst
Q5. What is the advantage of --force-with-lease over --force?
- A) It is faster
- B) It fails if someone else pushed since your last fetch
- C) It pushes all branches at once
- D) It creates a merge commit on the remote
Q6. What does git push -u origin feature do that git push origin feature does not?
- A) It creates the branch on the remote
- B) It forces the push even if rejected
- C) It sets up tracking so future pushes need no arguments
- D) It pushes all branches at once
Q7. In the forking workflow, what is the conventional name for the original repository’s remote?
- A) origin
- B) source
- C) upstream
- D) base
Answers
- B — A local copy, an
originremote, and remote-tracking branches - B — Downloads remote changes and updates remote-tracking branches only
- C —
pullfetches and then merges;fetchonly downloads - B — The remote branch has commits that your local branch does not have
- B — It fails if someone else pushed since your last fetch
- C — It sets up tracking so future pushes need no arguments
- C — upstream