Computing Foundations › Git and GitHub › Day 32
Hands-on lab — Day 32: Remotes and GitHub
- ← Back to the Day 32 lesson
- Open the hands-on files on GitHub — clone or download them from the public labs repository
- Local path in your clone:
labs/sections/computing-foundations/day-032-remotes-and-github/
Commands
Setup
cd labs/sections/computing-foundations/day-032-remotes-and-github Run
bash examples/remote_demo.sh
bash starter/remote_demo.sh Test
bash tests/run_tests.sh File tree
examples/remote_demo.sh expected-output/FIELDS.md expected-output/sample-run.txt expected-output/tests-run.txt metadata.yml README.md requirements/README.md security.md starter/remote_demo.sh starter/remotes-worksheet.md tests/run_tests.sh troubleshooting.md
Lab README
Day 032 lab — Remotes Without a Network
Lesson
- Lesson title: Remotes and GitHub
- Day number: 32 of 365
- Lesson article: https://ai-roadmap-365.github.io/day-032-remotes-and-github
- Lab files: everything you need is in this directory — follow “How to run” below.
- Browse the course locally: from the repository root, this lab also appears in the course website at
/labs/day-032-remotes-and-githubwhen the site is running.
Purpose
Day 32's lesson explains how Git connects one copy of a repository to another through a remote, and what platforms like GitHub add on top. This lab makes it concrete and truthful without any network or account: you use a local bare repository as the remote and run the full clone / push / fetch / pull cycle end to end. Every command is exactly the one you would run against a real hosting platform — only the address is a folder path.
Learning objectives
- Register a remote with
git remote addand inspect it withgit remote -v. - Push commits to a remote and set an upstream with
git push -u origin main. - Clone a remote into a second working copy and confirm it received the history.
- Move a change between two clones using
push,fetch, andpull. - Read tracking information with
git branch -vvand explain whatorigin/mainis.
Prerequisites
- Days 29–31: version control concepts, Git repositories, commits, and merging.
- Git installed (
git --version) and a terminal. - No GitHub account, network, or authentication is required.
Supported operating systems
- macOS — fully supported (tested on macOS with Apple Silicon).
- Linux — fully supported (any distribution with Git and bash).
- Windows — use WSL (Windows Subsystem for Linux) and follow the Linux path; native Git Bash also works.
Hardware requirements
Any computer that can run Git. The lab creates a few tiny text files in a temporary folder and needs no special CPU, RAM, GPU, or disk.
Required software
git(2.28+ recommended forgit init -b main; older-Git fallback is introubleshooting.md).bash(3.2+), and standard utilitiesmktemp,ls,sed,grep— all preinstalled.
Free and open-source options
Everything here is free and open source: Git itself, bash, and every utility used. No account, API key, or purchase is needed. When you later try a real remote, the free tiers of GitHub, GitLab, Bitbucket, and self-hosted Gitea are all more than enough (see the lesson's comparison table).
Installation
None. Clone the repository (or copy this directory) and change into it:
cd labs/sections/computing-foundations/day-032-remotes-and-github
File structure
day-032-remotes-and-github/
├── README.md ← you are here
├── metadata.yml ← machine-readable lab metadata
├── starter/
│ ├── remote_demo.sh ← YOUR working file (5 exercises)
│ └── remotes-worksheet.md ← worksheet for the practice assignment
├── examples/
│ └── remote_demo.sh ← completed reference implementation
├── tests/
│ └── run_tests.sh ← automated checks (12 checks, no network)
├── expected-output/
│ ├── sample-run.txt ← real captured run of the reference script
│ ├── tests-run.txt ← real captured test run
│ └── FIELDS.md ← what a correct run must show, per platform
├── requirements/
│ └── README.md ← dependency statement (git only)
├── troubleshooting.md
└── security.md
How to run
From this directory:
## 1. See the finished result first
bash examples/remote_demo.sh
## 2. Your task: complete the five exercises in the starter, then run it
bash starter/remote_demo.sh
## 3. Check your work
bash tests/run_tests.sh
What the commands do
bash examples/remote_demo.sh— runs the reference cycle: creates a bare repo asoriginin a temp folder, registers it in a working repo, pushes with-u, clones the remote a second time, commits and pushes from the clone, then pulls the change back into the first copy. It printsgit remote -vandgit branch -vvso you can see the remote and the tracking branch. The temp workspace is deleted automatically on exit.bash starter/remote_demo.sh— the same flow with five git commands blanked out asREPLACE_ME. Each exercise comment names the exact command; edit the file and replace eachREPLACE_MEline, then re-run.bash tests/run_tests.sh— independently performs a push/clone/pull cycle and asserts real behaviour (push succeeds, the clone receives the file, a change propagates via pull, the upstream is set, the remote is a local path), then runs the reference script and checks its output. Exits 0 on success.
Expected output
See expected-output/sample-run.txt — a
real captured run. Key lines to look for: * [new branch] main -> main
(the push), branch 'main' set up to track 'origin/main'. (the upstream),
Fast-forward (the pull), and shared.txt present in repo-a: yes (the
change arrived). Your temporary paths and commit hashes will differ — that is
expected. expected-output/FIELDS.md lists what
a correct run must show on every platform.
Validation steps
- Run
bash examples/remote_demo.sh— it must finish with=== Done: a change travelled repo-b -> origin -> repo-a, with no network. ===. - Complete the five exercises in
starter/remote_demo.shuntil running it printsAll five exercises look complete.and no line reports an unfinished exercise. - Confirm
git remote -vin the demo points at a local folder, never a web URL. - Run the tests (next section) — all checks must pass.
Tests
bash tests/run_tests.sh
Expected final line: 12 checks, 0 failure(s). The command exits 0 on
success and non-zero on any failure, so it can run in CI. It needs no
network.
Cleanup
Nothing to clean up: both scripts do all their work inside a mktemp
temporary directory that is removed automatically on exit. To reset your
starter edits, restore the file from git:
git checkout -- starter/remote_demo.sh.
Troubleshooting
See troubleshooting.md for the bare-repository trick, the older-Git fallback, the rejected-push fix, and pointers for authenticating to a real GitHub/GitLab/Bitbucket remote with a token or SSH key.
Security notes
See security.md. Short version: the scripts touch only a temp directory, make no network calls, and need no privileges — and for real remotes you should authenticate with an SSH key or a token, never a password in the URL, and never commit secrets.
Extension exercises
- Add a second remote (
git remote add backup <another-bare-path>), pushmainto both, then make a commit and push it to only one. Usegit remote -vandgit branch -vvto see that a repo can track several remotes independently. - Deliberately trigger a rejected push: commit directly in the clone and in
the first repo without syncing, push one, then try to push the other.
Read the rejection message, then resolve it with
git pulland push again. - Inspect the bare repository directly with
git --git-dir=<origin.git> log --onelineto confirm it holds the shared history even though it has no working files.
Navigation
- Previous day: Day 31 — Branching and Merging
(
labs/sections/computing-foundations/day-031-branching-and-merging/). - Next day: Day 33 — Pull Requests and Code Review
(
labs/sections/computing-foundations/day-033-pull-requests-and-code-review/, to be written).
Expected output
FIELDS.md
# Expected output — what a correct run shows (all platforms)
`sample-run.txt` and `tests-run.txt` in this directory are **real captured
runs** (macOS, Apple Silicon, 2026-07-12). Your temporary paths and commit
hashes will differ — that is expected, because each run uses a fresh
`mktemp` workspace and Git computes hashes from content and timestamps.
A correct run of `examples/remote_demo.sh` must show, in order:
1. A bare repository created to act as the remote.
2. `git remote -v` listing `origin` twice (once for fetch, once for push),
pointing at a **local folder path** — never an `http(s)://` or `git@` URL.
3. The push reporting `* [new branch] main -> main`.
4. `branch 'main' set up to track 'origin/main'.` (the upstream being set).
5. A clone whose file listing contains `README.md`.
6. A second push from the clone advancing the remote (`<old>..<new> main -> main`).
7. `git branch -vv` in the first repo showing `[origin/main: behind 1]`
after the fetch.
8. A pull reporting `Fast-forward` and `shared.txt | 1 +`.
9. `shared.txt present in repo-a: yes`.
A correct run of `tests/run_tests.sh` ends with `12 checks, 0 failure(s).`
and exits 0.
## Platform differences
- **Linux:** identical output and behaviour; only the temporary path prefix
differs (`/tmp/...` instead of macOS's `/var/folders/...`).
- **Windows:** run under WSL (Windows Subsystem for Linux) and the output
matches Linux exactly. Native Git Bash also works, but the temp path form
differs.
- **Older Git (< 2.28):** `git init -b main` may be unsupported. The
troubleshooting notes give the two-line fallback; the branch may be called
`master` instead of `main`, which changes only the branch name in the
output, not the behaviour.
sample-run.txt
=== 1. Create a bare repository to act as the remote (origin) ===
Created bare remote at: /var/folders/7j/4qzljp553ndfjm_y6zbygsz00000gn/T//day32-remote.yO1aYb/origin.git
A bare repo stores history but has NO working files — exactly like a server.
=== 2. Create a working repository and register the remote ===
git remote -v:
origin /var/folders/7j/4qzljp553ndfjm_y6zbygsz00000gn/T//day32-remote.yO1aYb/origin.git (fetch)
origin /var/folders/7j/4qzljp553ndfjm_y6zbygsz00000gn/T//day32-remote.yO1aYb/origin.git (push)
=== 3. Push the first commit and set the upstream ===
To /var/folders/7j/4qzljp553ndfjm_y6zbygsz00000gn/T//day32-remote.yO1aYb/origin.git
* [new branch] main -> main
branch 'main' set up to track 'origin/main'.
git branch -vv (note 'origin/main' upstream):
* main 76e6516 [origin/main] Initial commit
=== 4. Clone the remote into a SECOND working copy ===
Cloning into '/var/folders/7j/4qzljp553ndfjm_y6zbygsz00000gn/T//day32-remote.yO1aYb/repo-b'...
done.
Files in the clone:
README.md
=== 5. Make a commit in the clone and push it up ===
To /var/folders/7j/4qzljp553ndfjm_y6zbygsz00000gn/T//day32-remote.yO1aYb/origin.git
76e6516..4fa4da7 main -> main
repo-b pushed a new commit to origin.
=== 6. Back in the first copy: fetch shows what changed, pull integrates it ===
git branch -vv after fetch (repo-a is now 'behind'):
* main 76e6516 [origin/main: behind 1] Initial commit
git pull (fetch + merge):
From /var/folders/7j/4qzljp553ndfjm_y6zbygsz00000gn/T//day32-remote.yO1aYb/origin
* branch main -> FETCH_HEAD
Updating 76e6516..4fa4da7
Fast-forward
shared.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 shared.txt
Files in repo-a now include the file pushed from repo-b:
README.md
shared.txt
=== 7. Confirm both working copies and the remote agree ===
repo-a log:
4fa4da7 Add shared.txt from the second copy
76e6516 Initial commit
shared.txt present in repo-a: yes
=== Done: a change travelled repo-b -> origin -> repo-a, with no network. ===
tests-run.txt
Part 1: independent push / clone / pull cycle (no network) ...
ok: push to the bare remote succeeds (exit 0)
ok: remote now has a 'main' branch
ok: second clone receives the pushed file (README.md)
ok: a change propagates via pull (absent before, present after)
ok: repo-a's main tracks origin/main (upstream set)
ok: remote is a LOCAL path, not a network URL
Part 2: examples/remote_demo.sh produces the expected evidence ...
ok: reference script exits successfully
ok: reference shows the push creating 'main' on the remote
ok: reference shows the upstream being set
ok: reference shows a fast-forward pull
ok: reference confirms the change reached repo-a
Part 3: starter/remote_demo.sh runs as a skeleton ...
ok: starter script runs without a fatal error
12 checks, 0 failure(s).
Source files
examples/remote_demo.sh (2979 bytes)
#!/usr/bin/env bash
# Day 032 lab — Remotes Without a Network (completed reference implementation).
#
# Demonstrates the full clone / push / fetch / pull cycle using a LOCAL bare
# repository as the remote. No GitHub account, no network, no authentication:
# the "remote" is just a folder on your own disk. Every git command here is
# the SAME one you would run against a real hosting platform — only the
# address is a path instead of a web URL.
#
# Run from the lab directory:
# bash examples/remote_demo.sh
set -euo pipefail
# --- A private scratch workspace, cleaned up automatically on exit ----------
workspace="$(mktemp -d "${TMPDIR:-/tmp}/day32-remote.XXXXXX")"
cleanup() { rm -rf "${workspace}"; }
trap cleanup EXIT
# A local identity JUST for this demo, so commits work without touching your
# global git config. -c passes config to a single command only.
id=(-c user.name="Day 32 Learner" -c user.email="learner@example.invalid")
origin="${workspace}/origin.git" # the "remote": a bare repository
repo_a="${workspace}/repo-a" # first working copy
repo_b="${workspace}/repo-b" # second working copy (a clone)
section() { printf '\n=== %s ===\n' "$1"; }
section "1. Create a bare repository to act as the remote (origin)"
git init --bare -b main "${origin}" >/dev/null
echo "Created bare remote at: ${origin}"
echo "A bare repo stores history but has NO working files — exactly like a server."
section "2. Create a working repository and register the remote"
git init -b main "${repo_a}" >/dev/null
cd "${repo_a}"
echo "# Shared Notes" > README.md
git "${id[@]}" add README.md
git "${id[@]}" commit -q -m "Initial commit"
git remote add origin "${origin}"
echo "git remote -v:"
git remote -v
section "3. Push the first commit and set the upstream"
git "${id[@]}" push -u origin main
echo "git branch -vv (note 'origin/main' upstream):"
git branch -vv
section "4. Clone the remote into a SECOND working copy"
git clone "${origin}" "${repo_b}" 2>&1 | sed 's/^/ /'
echo "Files in the clone:"
ls -1 "${repo_b}"
section "5. Make a commit in the clone and push it up"
cd "${repo_b}"
echo "line added from repo-b" > shared.txt
git "${id[@]}" add shared.txt
git "${id[@]}" commit -q -m "Add shared.txt from the second copy"
git "${id[@]}" push origin main
echo "repo-b pushed a new commit to origin."
section "6. Back in the first copy: fetch shows what changed, pull integrates it"
cd "${repo_a}"
git fetch origin >/dev/null 2>&1
echo "git branch -vv after fetch (repo-a is now 'behind'):"
git branch -vv
echo
echo "git pull (fetch + merge):"
git "${id[@]}" pull origin main 2>&1 | sed 's/^/ /'
echo "Files in repo-a now include the file pushed from repo-b:"
ls -1
section "7. Confirm both working copies and the remote agree"
echo "repo-a log:"
git log --oneline
echo "shared.txt present in repo-a: $([ -f shared.txt ] && echo yes || echo no)"
echo
echo "=== Done: a change travelled repo-b -> origin -> repo-a, with no network. ==="
metadata.yml (583 bytes)
lesson_id: D032
day: 32
kind: command-line-inspection
languages: [bash]
setup_commands:
- cd labs/sections/computing-foundations/day-032-remotes-and-github
run_commands:
- bash examples/remote_demo.sh
- bash starter/remote_demo.sh
test_commands:
- bash tests/run_tests.sh
cleanup_commands:
- 'git checkout -- starter/remote_demo.sh # optional: reset your work (temp dirs auto-clean)'
requires_network: false
requires_api_key: false
estimated_minutes: 30
last_executed: '2026-07-12'
executed_on: 'macOS (Apple Silicon), bash tests/run_tests.sh → 12 checks, 0 failure(s)'
requirements/README.md (1389 bytes)
# Dependencies — Day 032 lab
**Only Git and a POSIX shell.** This lab has zero installable dependencies
and needs no account, no network, and no API key:
- `git` (2.28 or newer recommended, for `git init -b main`; an older-Git
fallback is documented in `troubleshooting.md`).
- `bash` ≥ 3.2 (preinstalled on macOS and every mainstream Linux distro).
- Standard OS utilities: `mktemp`, `rm`, `ls`, `sed`, `grep` — all part of
the base system.
## Do I need a GitHub account?
**No.** The whole point of this lab is to teach remotes *truthfully without a
network*: the "remote" is a **bare repository** in a temporary folder on your
own disk, and every `git` command is exactly the one you would run against a
hosting platform.
A **real** push to a hosting platform additionally needs two things this lab
deliberately does not require, both covered conceptually in the lesson:
1. **An account** on a platform (such as GitHub, GitLab, or Bitbucket).
2. **Authentication** — either an HTTPS **personal access token** or an
**SSH key pair**. You never use your account password directly, and you
never put a token or password in the remote URL.
When you are ready to try a real remote, create a repository on the platform,
copy the address it gives you, and run the same `git remote add` / `git push`
commands from this lab against that address instead of a folder path.
starter/remote_demo.sh (3566 bytes)
#!/usr/bin/env bash
# Day 032 lab — Remotes Without a Network (STARTER).
#
# This starter sets up the workspace for you. Your job is to complete the five
# numbered exercises below by replacing each `REPLACE_ME` call with the exact
# git command named in the comment above it. The completed reference version
# is in examples/remote_demo.sh — try this yourself first, then compare.
#
# Run from the lab directory:
# bash starter/remote_demo.sh
#
# Note: we deliberately do NOT use `set -e` here, so that an unfinished
# exercise reports itself instead of stopping the whole script.
set -uo pipefail
# Absolute path to this script, captured before we change directories, so the
# completion check at the end can scan the file wherever it is run from.
self="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"
# --- A private scratch workspace, cleaned up automatically on exit ----------
workspace="$(mktemp -d "${TMPDIR:-/tmp}/day32-remote.XXXXXX")"
cleanup() { rm -rf "${workspace}"; }
trap cleanup EXIT
# A local identity JUST for this demo, so commits work without touching your
# global git config.
id=(-c user.name="Day 32 Learner" -c user.email="learner@example.invalid")
origin="${workspace}/origin.git" # the "remote": a bare repository
repo_a="${workspace}/repo-a" # first working copy
repo_b="${workspace}/repo-b" # second working copy (a clone)
# When an exercise is still unfinished, this stand-in prints a reminder
# instead of running a command. Replace each REPLACE_ME line with real git.
REPLACE_ME() { echo " [ ] Exercise not completed yet — replace this line (see the comment above)."; }
section() { printf '\n=== %s ===\n' "$1"; }
# --- Given for you: a bare remote and a first working repo with one commit ---
git init --bare -b main "${origin}" >/dev/null
git init -b main "${repo_a}" >/dev/null
cd "${repo_a}"
echo "# Shared Notes" > README.md
git "${id[@]}" add README.md
git "${id[@]}" commit -q -m "Initial commit"
section "Exercise 1 — register the bare repo as a remote named 'origin'"
# Replace the next line with:
# git remote add origin "${origin}"
REPLACE_ME
echo "Check it with:"; git remote -v
section "Exercise 2 — push the first commit and set the upstream"
# Replace the next line with:
# git "${id[@]}" push -u origin main
REPLACE_ME
echo "Check the upstream with:"; git branch -vv
section "Exercise 3 — clone the remote into a second working copy"
# Replace the next line with:
# git clone "${origin}" "${repo_b}"
REPLACE_ME
echo "If it worked, the clone contains:"; ls -1 "${repo_b}" 2>/dev/null || echo " (no clone yet)"
section "Exercise 4 — in the clone, make a commit and push it up"
if [ -d "${repo_b}" ]; then
cd "${repo_b}"
echo "line added from repo-b" > shared.txt
git "${id[@]}" add shared.txt
git "${id[@]}" commit -q -m "Add shared.txt from the second copy"
# Replace the next line with:
# git "${id[@]}" push origin main
REPLACE_ME
else
echo " (skipped — finish Exercise 3 first so the clone exists)"
fi
section "Exercise 5 — back in the first copy, pull the new commit"
cd "${repo_a}"
# Replace the next line with:
# git "${id[@]}" pull origin main
REPLACE_ME
echo "Files in repo-a now:"; ls -1
# --- Completion check --------------------------------------------------------
echo
if grep -q "REPLACE_ME$" "${self}" 2>/dev/null; then
echo "Some exercises are still unfinished — search this file for REPLACE_ME."
else
echo "All five exercises look complete. Compare with examples/remote_demo.sh."
fi
starter/remotes-worksheet.md (1499 bytes)
# Remotes worksheet — Day 032
Fill this in as you work through `starter/remote_demo.sh` (or the commands in
the lesson's hands-on section). Record what you actually ran and saw.
## 1. Your remote
- **Remote name** (the label you gave it, e.g. `origin`): ______________________
- **Remote path** (the bare repository's folder path): ________________________
- Paste the output of `git remote -v`:
```text
(paste here)
```
## 2. The push you performed
- Exact command you ran to push and set the upstream:
```text
(e.g. git push -u origin main)
```
- What the push printed (the `[new branch]` / upstream line):
```text
(paste here)
```
- Paste `git branch -vv` after the push (it should show `[origin/main]`):
```text
(paste here)
```
## 3. The pull you performed
- Exact command you ran to pull:
```text
(e.g. git pull origin main)
```
- What the pull printed (look for `Fast-forward` and the changed file):
```text
(paste here)
```
## 4. How the change travelled
In your own words (3–5 sentences), trace the journey of the single change
`shared.txt`: where it was created, which command sent it up to the shared
bare repository, and which command brought it down into the other copy.
```text
(write here)
```
## 5. Why a push can be rejected
In 4–6 sentences, explain to a beginner why Git refuses a push when the
remote already has commits you do not, and what the safe fix is. Use the
shared-drawer analogy from the lesson, or one of your own.
```text
(write here)
```
tests/run_tests.sh (5246 bytes)
#!/usr/bin/env bash
# Tests for the Day 032 lab. Run from the lab directory:
# bash tests/run_tests.sh
#
# Verifies the full local-remote cycle with NO network: a push to a bare
# repository succeeds, a second clone receives the history, and a change
# pushed from one clone propagates to the other via pull. Also runs the
# shipped reference script and checks its output for the same evidence.
# Exits 0 on success, non-zero on any failure (CI-friendly).
set -uo pipefail
lab_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
failures=0
checks=0
check() {
local label="$1" ok="$2"
checks=$((checks + 1))
if [ "${ok}" = "yes" ]; then
echo " ok: ${label}"
else
echo " FAIL: ${label}"
failures=$((failures + 1))
fi
}
id=(-c user.name="Day 32 Test" -c user.email="test@example.invalid")
# --------------------------------------------------------------------------
# Part 1: an independent local-remote cycle, asserted directly.
# --------------------------------------------------------------------------
echo "Part 1: independent push / clone / pull cycle (no network) ..."
ws="$(mktemp -d "${TMPDIR:-/tmp}/day32-test.XXXXXX")"
trap 'rm -rf "${ws}"' EXIT
origin="${ws}/origin.git"
repo_a="${ws}/repo-a"
repo_b="${ws}/repo-b"
git init --bare -b main "${origin}" >/dev/null 2>&1
git init -b main "${repo_a}" >/dev/null 2>&1
(
cd "${repo_a}"
echo "# Shared Notes" > README.md
git "${id[@]}" add README.md
git "${id[@]}" commit -q -m "Initial commit"
git remote add origin "${origin}"
git "${id[@]}" push -u origin main
) >/dev/null 2>&1
push_rc=$?
check "push to the bare remote succeeds (exit 0)" "$([ ${push_rc} -eq 0 ] && echo yes || echo no)"
# The remote (a bare repo) now has a main branch pointing at a commit.
if git --git-dir="${origin}" rev-parse --verify -q main >/dev/null; then
check "remote now has a 'main' branch" "yes"
else
check "remote now has a 'main' branch" "no"
fi
# A second clone must receive the pushed history.
git clone "${origin}" "${repo_b}" >/dev/null 2>&1
if [ -f "${repo_b}/README.md" ]; then
check "second clone receives the pushed file (README.md)" "yes"
else
check "second clone receives the pushed file (README.md)" "no"
fi
# Commit a change in the clone and push it.
(
cd "${repo_b}"
echo "line from repo-b" > shared.txt
git "${id[@]}" add shared.txt
git "${id[@]}" commit -q -m "Add shared.txt"
git "${id[@]}" push origin main
) >/dev/null 2>&1
# Before pull, repo-a must NOT have the file; after pull, it must.
before="absent"; [ -f "${repo_a}/shared.txt" ] && before="present"
( cd "${repo_a}" && git "${id[@]}" pull origin main ) >/dev/null 2>&1
after="absent"; [ -f "${repo_a}/shared.txt" ] && after="present"
if [ "${before}" = "absent" ] && [ "${after}" = "present" ]; then
check "a change propagates via pull (absent before, present after)" "yes"
else
check "a change propagates via pull (absent before, present after)" "no"
fi
# The upstream tracking must be set on repo-a's main.
if ( cd "${repo_a}" && git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null | grep -q '^origin/main$' ); then
check "repo-a's main tracks origin/main (upstream set)" "yes"
else
check "repo-a's main tracks origin/main (upstream set)" "no"
fi
# No remote was a network URL — all paths are local folders.
url="$(cd "${repo_a}" && git remote get-url origin)"
case "${url}" in
http://*|https://*|git@*) check "remote is a LOCAL path, not a network URL" "no" ;;
*) check "remote is a LOCAL path, not a network URL" "yes" ;;
esac
# --------------------------------------------------------------------------
# Part 2: the shipped reference script runs and shows the same evidence.
# --------------------------------------------------------------------------
echo "Part 2: examples/remote_demo.sh produces the expected evidence ..."
if out="$(bash "${lab_dir}/examples/remote_demo.sh" 2>&1)"; then
check "reference script exits successfully" "yes"
else
check "reference script exits successfully" "no"
fi
echo "${out}" | grep -q '\[new branch\] *main -> main' && check "reference shows the push creating 'main' on the remote" "yes" || check "reference shows the push creating 'main' on the remote" "no"
echo "${out}" | grep -q 'set up to track' && check "reference shows the upstream being set" "yes" || check "reference shows the upstream being set" "no"
echo "${out}" | grep -q 'Fast-forward' && check "reference shows a fast-forward pull" "yes" || check "reference shows a fast-forward pull" "no"
echo "${out}" | grep -q 'shared.txt present in repo-a: yes' && check "reference confirms the change reached repo-a" "yes" || check "reference confirms the change reached repo-a" "no"
# --------------------------------------------------------------------------
# Part 3: the starter runs without error (structure only).
# --------------------------------------------------------------------------
echo "Part 3: starter/remote_demo.sh runs as a skeleton ..."
if bash "${lab_dir}/starter/remote_demo.sh" >/dev/null 2>&1; then
check "starter script runs without a fatal error" "yes"
else
check "starter script runs without a fatal error" "no"
fi
echo
echo "${checks} checks, ${failures} failure(s)."
[ "${failures}" -eq 0 ]
Troubleshooting
Troubleshooting — Day 032 lab
The bare-repository trick (why the "remote" is just a folder)
A server-side remote is a bare repository: it stores Git history but has
no working directory of checked-out files. git init --bare makes exactly
that. Because a bare repo is what a real remote is, you can use a local
folder as a fully honest stand-in for a hosting platform — no network, no
account, no authentication. Everything you learn here transfers unchanged to
a real remote; only the address changes from a folder path to a web URL.
unknown switch 'b' or error: unknown option -b on git init
Your Git predates 2.28 and lacks git init -b main. Use the fallback:
git init origin.git --bare
git init repo-a
cd repo-a
git symbolic-ref HEAD refs/heads/main # name the default branch 'main'
Everything else in the lab works the same; the branch is simply named
explicitly instead of by the -b flag.
fatal: remote origin already exists
You added origin twice. Either remove and re-add it, or update its URL:
git remote remove origin # then git remote add origin <path>
## or, to change where it points:
git remote set-url origin <path>
error: failed to push some refs to ...
The remote has commits your local branch does not. This is the safety feature working: Git will not let you silently overwrite others' work. Integrate first, then push:
git pull origin main # or: git fetch origin && git merge origin/main
git push origin main
src refspec main does not match any
You have not committed yet, so there is no main branch to push. Make at
least one commit first (git add then git commit), then push.
A push "succeeds" but the change is not there
Push moves commits, not uncommitted edits. Run git status; if it shows
staged or unstaged changes, you have not committed them yet. Commit, then
push.
Pointing at a real GitHub (or GitLab/Bitbucket) remote
When you graduate from the local folder to a real platform:
- HTTPS: clone/add a
https://...address. When prompted, use a personal access token as the password (generate one in the platform's developer settings) — modern platforms reject your account password here. - SSH: generate a key pair with
ssh-keygen, add the public key to your account, then use agit@...address. After the one-time setup, no prompt appears.
If a push hangs on a restrictive network, the SSH port may be blocked — try the HTTPS address instead.
Security notes
Security notes — Day 032 lab
- What the scripts do: create Git repositories inside a fresh temporary
directory (
mktemp -d), commit a couple of tiny text files, and push/pull between them. They make no network connections, need no elevated privileges, and write nothing outside the temporary workspace, which is removed automatically on exit by atrap. - Temp dirs only: all work happens under your system temp directory. The
scripts never touch your real projects, your global Git config, or your
home directory. The demo sets its Git identity with per-command
-c user.name=...flags, so it does not change any saved configuration. - For real remotes, authenticate safely. When you move from the local
folder to a real hosting platform, prove your identity with an SSH key
or a personal access token — never your account password, and never
put a secret in the remote URL (for example
https://user:token@...). A secret in a URL leaks into your shell history and.git/configin plain text. - Never commit secrets. Anything committed to history is effectively published the moment you push, and stays in history even after you delete the file. Keep API keys and tokens in ignored configuration or environment variables, not in the repository. Making a repo public — even briefly — should be treated as "assume its contents were copied."
- Read before you run. Both scripts are short and commented; read them first. Running unread shell scripts is one of the most common ways developers get compromised.