Computing FoundationsGit and GitHub › Day 32

Hands-on lab — Day 32: 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-github when 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 add and inspect it with git 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, and pull.
  • Read tracking information with git branch -vv and explain what origin/main is.

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 for git init -b main; older-Git fallback is in troubleshooting.md).
  • bash (3.2+), and standard utilities mktemp, 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 as origin in 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 prints git remote -v and git branch -vv so 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 as REPLACE_ME. Each exercise comment names the exact command; edit the file and replace each REPLACE_ME line, 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

  1. Run bash examples/remote_demo.sh — it must finish with === Done: a change travelled repo-b -> origin -> repo-a, with no network. ===.
  2. Complete the five exercises in starter/remote_demo.sh until running it prints All five exercises look complete. and no line reports an unfinished exercise.
  3. Confirm git remote -v in the demo points at a local folder, never a web URL.
  4. 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

  1. Add a second remote (git remote add backup <another-bare-path>), push main to both, then make a commit and push it to only one. Use git remote -v and git branch -vv to see that a repo can track several remotes independently.
  2. 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 pull and push again.
  3. Inspect the bare repository directly with git --git-dir=<origin.git> log --oneline to confirm it holds the shared history even though it has no working files.
  • 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 a git@... 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 a trap.
  • 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/config in 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.