Chapter 4 — Remote Repositories
A "remote" is another copy of your repository — usually on GitHub. `git push`, `git pull`, `git fetch` and `git clone` are the four verbs you need. Add credentials (PAT or SSH) once and you're set.
What you'll learn
- 1`git clone` a remote to start working.
- 2`git push` your commits up, `git pull` to bring others' down.
- 3Add, rename, remove remotes with `git remote`.
- 4Authenticate via Personal Access Token (HTTPS) or SSH key.
Core Concepts
1) The four verbs
| Command | What |
|---|---|
| `git clone <url>` | Download a remote into a new directory |
| `git fetch` | Pull remote changes into refs (no merge) |
| `git pull` | `fetch` + `merge` (or `rebase`) into current branch |
| `git push` | Send local commits to the remote |
2) Managing remotes
git remote # list remotes (usually 'origin')
git remote -v # with URLs
git remote add upstream <url>
git remote rename origin gh
git remote remove upstream
git remote set-url origin <new-url>3) HTTPS vs SSH
| Auth | URL format | Setup |
|---|---|---|
| **HTTPS + PAT** | `https://github.com/user/repo.git` | Create Personal Access Token on GitHub; use as password |
| **SSH** (recommended) | `git@github.com:user/repo.git` | `ssh-keygen` then add `~/.ssh/id_ed25519.pub` to GitHub |
4) Credential storage
# Cache for an hour
git config --global credential.helper "cache --timeout=3600"
# OS keyring (Mac/Windows)
git config --global credential.helper osxkeychain # macOS
git config --global credential.helper manager # WindowsExamples
Example 1 — `ex01_clone.sh`: clone a local "remote"
**Output**
=== Creating remote-role repository ===
=== Remote clone ===
Cloning into 'workspace'...Key: any directory with `--bare` flag can play the role of "remote" for learning.
Example 2 — `ex02_remote.sh`: add / rename / remove remotes
**Output**
=== Add remote ===
=== Change remote ===
=== Remove remote ===
origin updatedKey: `git remote -v` shows both fetch & push URLs.
Example 3 — `ex03_push_pull.sh`: two workers via push/pull
**Output**
=== Worker 1 ===
initial commit then push
=== Worker 2 ===
clone, edit, then push
=== Worker 1 ===
pull → conflict-free mergeKey: pull regularly to keep up; push when ready.
Example 4 — `ex04_credentials.sh`: how to set up PAT / SSH
**Output**
=== Credential help ===
=== HTTPS vs SSH ===
=== PAT setup ===
=== SSH key setup ===Key: SSH key once → push/pull forever without typing a password.
Common mistakes
- **Pushing without setting upstream** — first push needs `git push -u origin main`.
- **Forgetting to pull** — your `push` is rejected because the remote moved.
- **Token expired** — HTTPS push suddenly fails; regenerate the PAT or switch to SSH.
- **Pushing the wrong branch** — `git push origin main` not `feature`.
- **`git pull` on dirty working tree** — uncommitted changes block the merge. Stash or commit first.
Recap
- `clone` → `push`/`pull` are the daily verbs.
- One remote is usually called `origin`; you can add more.
- SSH key setup is one-time pain, one-life convenience.
- `git pull --rebase` keeps history linear.
Try it
cd src
chmod +x ex0*.sh
./ex01_clone.sh
./ex02_remote.sh
./ex03_push_pull.sh
./ex04_credentials.sh💻 Examples
Runnable examples — see the output yourself.
#!/usr/bin/env bash
set -euo pipefail
WORKDIR=$(mktemp -d)
echo "Working directory: $WORKDIR"
# remote role of bare repository create
git init --bare -b main "$WORKDIR/remote.git" -q
echo "bare repository create done"
# commit (local_a at push)
git clone "$WORKDIR/remote.git" "$WORKDIR/local_a" -q
cd "$WORKDIR/local_a"
git config user.name "workerA" && git config user.email "a@example.com"
git config commit.gpgsign false
echo "# remote repository " > README.md
git add . && git commit -q -m "docs: initial README"
git push -u origin main -q
echo "local_a at push done"
# local_b at clone
echo ""
echo "=== git clone ==="
git clone "$WORKDIR/remote.git" "$WORKDIR/local_b"
cd "$WORKDIR/local_b"
echo ""
echo "=== clone (after) log ==="
git log --oneline
echo ""
echo "=== remote check ==="
git remote -v
rm -rf "$WORKDIR"
=== Creating remote-role repository ===
=== Remote clone ===
Cloning into 'workspace'...#!/usr/bin/env bash
set -euo pipefail
REPO=$(mktemp -d)
cd "$REPO"
git init -q -b main
git config user.name "Demo User" && git config user.email "demo@example.com"
git config commit.gpgsign false
echo "=== remote add ==="
git remote add origin https://github.com/example/my-repo.git
git remote add upstream https://github.com/original/my-repo.git
git remote -v
echo ""
echo "=== origin URL change ==="
git remote set-url origin https://github.com/example/new-name.git
git remote -v
echo ""
echo "=== upstream name → source using change ==="
git remote rename upstream source
git remote -v
echo ""
echo "=== source removed ==="
git remote remove source
git remote -v
rm -rf "$REPO"
=== Add remote ===
=== Change remote ===
=== Remove remote ===
origin updated#!/usr/bin/env bash
set -euo pipefail
WORKDIR=$(mktemp -d)
git init --bare -b main "$WORKDIR/remote.git" -q
setup_repo() {
local path="$1" name="$2" email="$3"
git clone "$WORKDIR/remote.git" "$path" -q
git -C "$path" config user.name "$name"
git -C "$path" config user.email "$email"
git -C "$path" config commit.gpgsign false
}
# repository A: initial commit + push
setup_repo "$WORKDIR/repo_a" "workerA" "a@example.com"
cd "$WORKDIR/repo_a"
echo "A of first commit" > a.txt
git add . && git commit -q -m "feat: A's first commit"
git push origin main -q
echo "A → push done"
# repository B: B at commit add + push
setup_repo "$WORKDIR/repo_b" "workerB" "b@example.com"
cd "$WORKDIR/repo_b"
git pull origin main -q
echo "B of commit" > b.txt
git add . && git commit -q -m "feat: B's commit"
git push origin main -q
echo "B → push done"
# repository A: pull then B of change fetching
cd "$WORKDIR/repo_a"
echo ""
echo "=== A pull (before) log ==="
git log --oneline
git pull origin main -q
echo ""
echo "=== A pull (after) log (B of commit(included) ==="
git log --oneline
rm -rf "$WORKDIR"
=== Worker 1 ===
initial commit then push
=== Worker 2 ===
clone, edit, then push
=== Worker 1 ===
pull → conflict-free merge#!/usr/bin/env bash
set -euo pipefail
echo "============================================"
echo " GitHub credential setup guide"
echo "============================================"
echo ""
echo "--- way 1: SSH key (recommended) ---"
echo ""
echo "1. key create:"
echo " ssh-keygen -t ed25519 -C \"your@email.com\""
echo ""
echo "2. ssh-agent at register:"
echo " eval \"\$(ssh-agent -s)\""
echo " ssh-add ~/.ssh/id_ed25519"
echo ""
echo "3. Copy the public key:"
echo " cat ~/.ssh/id_ed25519.pub"
echo " (GitHub → Settings → SSH and GPG keys → New SSH key (paste here)"
echo ""
echo "4. Connection test:"
echo " ssh -T git@github.com"
echo ""
echo "--- way 2: HTTPS + PAT ---"
echo ""
echo "1. PAT create:"
echo " GitHub → Settings → Developer settings"
echo " → Personal access tokens → Tokens (classic)"
echo " → Generate new token"
echo " (repo permission check)"
echo ""
echo "2. credential Save:"
echo " git config --global credential.helper store"
echo " (first push/pull use username and PAT input → saved)"
echo ""
echo "--- current SSH key status ---"
if [ -f "$HOME/.ssh/id_ed25519.pub" ]; then
echo "Public key found:"
cat "$HOME/.ssh/id_ed25519.pub"
elif [ -f "$HOME/.ssh/id_rsa.pub" ]; then
echo "RSA Public key found:"
cat "$HOME/.ssh/id_rsa.pub"
else
echo "(SSH key none — 1 way using createplease)"
fi
=== Credential help ===
=== HTTPS vs SSH ===
=== PAT setup ===
=== SSH key setup ===📝 Exercises
Try them yourself first, then open the solution to compare.
Problem 1 (hw01.sh)
Goal: Set up two local directories ("worker A" and "worker B") cloned from a single bare repo. Demonstrate a push-pull cycle between them.
- Filename: hw01.sh
▶Toggle solution
#!/usr/bin/env bash
set -euo pipefail
WORKDIR=$(mktemp -d)
git init --bare -b main "$WORKDIR/remote.git" -q
# repo_a
git clone "$WORKDIR/remote.git" "$WORKDIR/repo_a" -q
git -C "$WORKDIR/repo_a" config user.name "workerA"
git -C "$WORKDIR/repo_a" config user.email "a@example.com"
git -C "$WORKDIR/repo_a" config commit.gpgsign false
echo "A of file" > "$WORKDIR/repo_a/a.txt"
git -C "$WORKDIR/repo_a" add .
git -C "$WORKDIR/repo_a" commit -q -m "feat: A's file"
git -C "$WORKDIR/repo_a" push origin main -q
echo "repo_a push done"
# repo_b: clone (after) A of commit check
git clone "$WORKDIR/remote.git" "$WORKDIR/repo_b" -q
git -C "$WORKDIR/repo_b" config user.name "workerB"
git -C "$WORKDIR/repo_b" config user.email "b@example.com"
git -C "$WORKDIR/repo_b" config commit.gpgsign false
echo ""
echo "=== repo_b at log (A of commit ) ==="
git -C "$WORKDIR/repo_b" log --oneline
echo "B of file" > "$WORKDIR/repo_b/b.txt"
git -C "$WORKDIR/repo_b" add .
git -C "$WORKDIR/repo_b" commit -q -m "feat: B's file"
git -C "$WORKDIR/repo_b" push origin main -q
echo "repo_b push done"
# repo_a: fetch + merge
git -C "$WORKDIR/repo_a" fetch origin -q
echo ""
echo "=== repo_a fetch + merge (after) ==="
git -C "$WORKDIR/repo_a" merge origin/main -q
git -C "$WORKDIR/repo_a" log --oneline
rm -rf "$WORKDIR"
Problem 2 (hw02.sh)
Goal: Add a second remote called `upstream`, rename `origin` to `gh`, then list remotes with their URLs.
- Filename: hw02.sh
▶Toggle solution
#!/usr/bin/env bash
set -euo pipefail
REPO=$(mktemp -d)
cd "$REPO"
git init -q -b main
git config user.name "Demo User" && git config user.email "demo@example.com"
git config commit.gpgsign false
git remote add origin https://github.com/example/project.git
git remote add backup https://backup.example.com/project.git
echo "=== initial remote list ==="
git remote -v
git remote set-url origin https://github.com/example/project-v2.git
git remote remove backup
echo ""
echo "=== final remote list ==="
git remote -v
rm -rf "$REPO"
All lecture materials and example code are openly available on GitHub.
View on GitHub ↗