Computing Foundations › Git and GitHub › Day 35
Hands-on lab — Day 35: Git Workflows for Real Projects
- ← Back to the Day 35 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-035-git-workflows-for-real-projects/
Commands
Setup
cd labs/sections/computing-foundations/day-035-git-workflows-for-real-projects Run
bash examples/workflow_demo.sh
bash starter/workflow_demo.sh Test
bash tests/run_tests.sh File tree
examples/workflow_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/workflow_demo.sh starter/workflow-worksheet.md tests/run_tests.sh troubleshooting.md
Lab README
Day 035 lab — A Real Git Workflow
Day number: 35 of 365. This lab runs a complete team git workflow end to end — branch, atomic commits, reviewed merge, and a semantic-versioned release tag — entirely on your own machine, with no network and no account.
Lesson
- Lesson title: Git Workflows for Real Projects
- Day number: 35 of 365
- Lesson article: https://ai-roadmap-365.github.io/day-035-git-workflows-for-real-projects
- 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-035-git-workflows-for-real-projectswhen the site is running.
Purpose
Day 35's lesson explains how a team puts git to work: branching strategies,
commit hygiene, release tags, and clean history. This lab makes it real. You
simulate GitHub Flow against a local bare "origin" — create a
short-lived feature branch, make atomic commits with Conventional Commit
messages, merge back with --no-ff (as if merging a pull request), and tag a
v1.0.0 release with an annotated tag — then read the history you built. It is
exactly the workflow the Versioned Notes Repository project asks for.
Learning objectives
- Run a full GitHub Flow cycle: branch, commit, merge, tag.
- Write atomic commits with
feat:andfix:Conventional Commit messages. - Merge a feature branch with
--no-ffso the merge is recorded like a pull request. - Create an annotated release tag (
git tag -a) and choose a semantic version. - See a
.gitignorekeep a file out of git, and contrast atomic with sloppy commits.
Prerequisites
- The Day 35 lesson (read it first — it explains every concept this lab exercises).
- Days 29-34: commits, branching and merging, remotes, pull requests, and undoing changes.
git(2.23+) and a terminal. No programming experience required; every command is given.
Supported operating systems
- macOS — fully supported (tested on macOS, Apple Silicon).
- Linux — fully supported (any distribution with git and bash).
- Windows — run inside WSL and follow the Linux path; native PowerShell is not supported for these bash scripts.
Hardware requirements
Any computer that can run git. The lab creates a few tiny text files in a temporary directory and needs no special CPU, RAM, disk, or GPU.
Required software
git2.23 or newer (forgit switch; older git works with thegit checkoutequivalents noted introubleshooting.md).bash3.2 or newer, plus standard utilities (mktemp,printf,grep,awk,sed) — all preinstalled.
Free and open-source options
Everything here is free and open source: git and every command used ship with your OS or are open-source tools. No account, key, or purchase is needed, and the lab never touches the network — the "remote" is a local bare repository.
Installation
None beyond git. Clone the repository (or copy this directory) and change into it:
cd labs/sections/computing-foundations/day-035-git-workflows-for-real-projects
File structure
day-035-git-workflows-for-real-projects/
├── README.md ← you are here
├── metadata.yml ← machine-readable lab metadata
├── starter/
│ ├── workflow_demo.sh ← YOUR working file (5 exercises)
│ └── workflow-worksheet.md ← record your branch/merge/tag/semver steps
├── examples/
│ └── workflow_demo.sh ← completed reference implementation
├── tests/
│ └── run_tests.sh ← automated behavioral checks
├── expected-output/
│ ├── sample-run.txt ← real captured run of the reference
│ ├── tests-run.txt ← real captured test run
│ └── FIELDS.md ← what a correct run must contain
├── requirements/
│ └── README.md ← dependency statement (git + shell)
├── troubleshooting.md
└── security.md
How to run
From this directory:
## 1. See the finished workflow execute first
bash examples/workflow_demo.sh
## 2. Your task: complete the five exercises in the starter, then run it
bash starter/workflow_demo.sh
## 3. Check your work
bash tests/run_tests.sh
What the commands do
bash examples/workflow_demo.sh— creates a throwaway working repo and a local bareoriginin amktempdirectory, sets a repository-local git identity, commits a.gitignore(chore:), branches offmain(feat/notes-search), makes afeat:and afix:atomic commit, merges back withgit merge --no-ff, tagsv1.0.0withgit tag -a, pushes to the local origin, illustrates atomic-vs-sloppy commits, then printsgit tagandgit log --oneline --decorate --graph. The temp directory is deleted on exit.bash starter/workflow_demo.sh— the same skeleton with five numbered exercises. Each__FILL_ME_IN__line names the exact git command to write in its place. Edit the file, then run it.bash tests/run_tests.sh— runs the workflow and checks the resulting history: a feature branch merged intomain, an annotatedv1.0.0tag, andfeat:/fix:Conventional Commit messages; plus that the starter names all five exercises and that no lab script contains a network URL.
Expected output
See expected-output/sample-run.txt — a real
captured run. The history section looks like this (hashes and temp paths will
differ):
=== Tags ===
v1.0.0
=== History (main) ===
* 87ff7e2 (HEAD -> main, tag: v1.0.0, origin/main) Merge branch 'feat/notes-search'
|\
| * 7f8bc99 (feat/notes-search) fix: handle empty query without crashing
| * 3c188b4 feat: add case-insensitive note search
|/
* 538d3a1 chore: initialize notes repository with .gitignore
=== Workflow demo complete ===
The merge commit sits on top with v1.0.0 pinned to it; the feat: and fix:
commits are on the merged branch below; the initial chore: commit is at the
base. See expected-output/FIELDS.md for the full
list of what every correct run must contain.
Validation steps
- Run
bash starter/workflow_demo.shafter completing the exercises — it must exit without errors and print the Tags and History sections. - Confirm the History shows a merge commit at the top (proof of
--no-ff). - Confirm the Tags section lists
v1.0.0and the log showstag: v1.0.0. - Confirm you see one
feat:and onefix:commit message. - Run the tests (next section) — all checks must pass.
Tests
bash tests/run_tests.sh
Expected final line: 8 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
directory and delete it on exit. To reset your edited starter, restore it from
git: git checkout -- starter/workflow_demo.sh.
Troubleshooting
See troubleshooting.md for the full list (git version and
git switch, identity errors, fast-forward merges, annotated vs lightweight
tags, deleting a tag, and WSL notes).
Security notes
See security.md. Short version: everything runs in a temporary
directory that is deleted on exit, there are no network calls or credentials,
your global git identity is never modified, and the fake .env is a teaching
prop that is never committed.
Extension exercises
- Add a second feature branch, merge it with
--no-ff, and cut av1.1.0tag — then note why the bump is MINOR, not PATCH or MAJOR. - Generate a changelog by hand:
git log --onelineand group commits under Features (feat:) and Fixes (fix:), as changelog tools do. - Inspect the two tag kinds: create a lightweight tag (
git tag tmp) and comparegit show tmpwithgit show v1.0.0to see what the annotation adds.
Navigation
- Previous day: Day 34 — Undoing Things: Reset, Revert, and Reflog
(
labs/sections/computing-foundations/day-034-undoing-things-reset-revert-and-reflog/). - Next day: Day 36 — Choosing and Configuring a Code Editor
(
labs/sections/computing-foundations/day-036-choosing-and-configuring-a-code-editor/, to be written).
Expected output
FIELDS.md
# Expected output — Day 035 lab
This directory holds real captured runs from the authoring machine
(macOS, Apple Silicon, git 2.50.x, 2026-07-12). Commit hashes, the temporary
directory name, and the run date will differ on your machine — that is
expected. What must be the same is the *shape* of the result.
## `sample-run.txt`
A full run of `examples/workflow_demo.sh`. A correct run always shows, in order:
1. `=== 1. Initial commit with a .gitignore ===` and a line confirming that
`.env` is on disk but ignored by git (not tracked).
2. `=== 2. ... ===` with `on branch: feat/notes-search`.
3. `=== 3. ... ===` — two atomic commits are made (no output of their own).
4. `=== 4. ... ===` — the feature branch is merged into `main` with `--no-ff`.
5. `=== 5. ... ===` — an annotated `v1.0.0` tag is created and pushed to the
local bare `origin`.
6. An atomic-vs-sloppy illustration on a scratch branch.
7. `=== Tags ===` listing `v1.0.0`.
8. `=== History (main) ===` — a `git log --oneline --decorate --graph` showing
a merge commit at the top carrying `tag: v1.0.0`, the `feat:` and `fix:`
commits underneath on the merged branch, and the `chore:` initial commit at
the base.
9. `=== Workflow demo complete ===`.
## `tests-run.txt`
A full run of `bash tests/run_tests.sh`. It ends with `8 checks, 0 failure(s).`
and exits 0. The checks assert that the reference produced a merged feature
branch, an annotated `v1.0.0` tag, and `feat:`/`fix:` Conventional Commit
messages, that the starter names all five exercises, and that no lab script
contains a network URL (the lab is entirely local).
## Platform notes
- **Linux:** identical output. The scripts use only portable git and shell
features and set a repository-local git identity, so nothing depends on macOS.
- **Windows:** run inside WSL and the output matches the Linux case. Native
PowerShell is not supported for these bash scripts.
- The scripts create everything under a `mktemp` directory and delete it on
exit, so no files are left on your machine after a run.
sample-run.txt
=== 1. Initial commit with a .gitignore ===
.env is present on disk but correctly ignored by git (not tracked)
=== 2. Create a short-lived feature branch (GitHub Flow) ===
on branch: feat/notes-search
=== 3. Two atomic commits with Conventional Commit messages ===
=== 4. Merge back to main with --no-ff (like merging a pull request) ===
merged; main now has a merge commit recording the reviewed branch
=== 5. Tag an annotated release and push everything to origin ===
=== Atomic vs sloppy commit (illustration on a scratch branch) ===
Atomic history on this branch:
a2f33af refactor: add tidy() helper
682c5bf feat: add MAX_RESULTS setting
(A sloppy alternative would cram both changes into one 'stuff' commit,
which you could not revert or bisect independently.)
=== Tags ===
v1.0.0
=== History (main) ===
* 87ff7e2 (HEAD -> main, tag: v1.0.0, origin/main) Merge branch 'feat/notes-search'
|\
| * 7f8bc99 (feat/notes-search) fix: handle empty query without crashing
| * 3c188b4 feat: add case-insensitive note search
|/
* 538d3a1 chore: initialize notes repository with .gitignore
=== Workflow demo complete ===
tests-run.txt
Testing <repo>/labs/sections/computing-foundations/day-035-git-workflows-for-real-projects/examples/workflow_demo.sh ...
ok: script exits successfully
ok: feature branch merged into main (merge commit present)
ok: annotated v1.0.0 tag pinned in history
ok: git tag lists v1.0.0
ok: has a Conventional Commit feat: message
ok: has a Conventional Commit fix: message
Testing starter/workflow_demo.sh ...
Note: starter still has unfilled exercises — checking structure only.
ok: starter names all five exercises
ok: lab scripts contain no network URLs
8 checks, 0 failure(s).
Source files
examples/workflow_demo.sh (4204 bytes)
#!/usr/bin/env bash
# Day 035 lab — completed reference implementation: a real git workflow.
#
# Simulates GitHub Flow entirely on your own machine, with no network and no
# account. It creates a throwaway working repository and a local bare "origin"
# in a temporary directory, sets a LOCAL git identity (never touching your
# global config), then:
# 1. commits an initial .gitignore (chore:)
# 2. branches off main (feat/notes-search)
# 3. makes two atomic commits (feat: , fix:)
# 4. merges back with --no-ff (like merging a pull request)
# 5. tags an annotated release (git tag -a v1.0.0)
# It also demonstrates that an ignored file stays out of git, and contrasts an
# atomic commit sequence with a sloppy one. The temp directory is removed on
# exit, so the demo leaves nothing behind.
set -euo pipefail
# --- Create an isolated workspace and clean it up on exit -------------------
work_root="$(mktemp -d "${TMPDIR:-/tmp}/day035-demo.XXXXXX")"
cleanup() { rm -rf "${work_root}"; }
trap cleanup EXIT
origin="${work_root}/origin.git" # local bare repo standing in for a remote
repo="${work_root}/notes" # our working clone
# --- A local bare repo acts as "origin" (no network involved) ---------------
git init --quiet --bare -b main "${origin}"
git clone --quiet "${origin}" "${repo}" 2>/dev/null
cd "${repo}"
# LOCAL identity only: these writes stay inside ${repo}/.git/config and never
# alter your machine's global git identity.
git config user.name "Workflow Demo"
git config user.email "demo@example.invalid"
echo "=== 1. Initial commit with a .gitignore ==="
printf '%s\n' 'node_modules/' '*.log' '.env' > .gitignore
printf '%s\n' '# Course Notes' '' 'Notes for the 365 Days of AI course.' > README.md
git add .gitignore README.md
git commit --quiet -m "chore: initialize notes repository with .gitignore"
# Prove .gitignore works: a matching file must NOT appear in git status.
echo 'SECRET_KEY=do-not-commit-me' > .env
if git status --porcelain | grep -q '\.env'; then
echo " UNEXPECTED: .env showed up in git status" >&2
else
echo " .env is present on disk but correctly ignored by git (not tracked)"
fi
echo
echo "=== 2. Create a short-lived feature branch (GitHub Flow) ==="
git switch --quiet -c feat/notes-search
echo " on branch: $(git branch --show-current)"
echo
echo "=== 3. Two atomic commits with Conventional Commit messages ==="
printf '%s\n' 'def search(notes, query):' ' return [n for n in notes if query.lower() in n.lower()]' > search.py
git add search.py
git commit --quiet -m "feat: add case-insensitive note search"
printf '%s\n' 'def search(notes, query):' ' if not query:' ' return []' ' return [n for n in notes if query.lower() in n.lower()]' > search.py
git add search.py
git commit --quiet -m "fix: handle empty query without crashing"
echo
echo "=== 4. Merge back to main with --no-ff (like merging a pull request) ==="
git switch --quiet main
git merge --no-ff --quiet -m "Merge branch 'feat/notes-search'" feat/notes-search
echo " merged; main now has a merge commit recording the reviewed branch"
echo
echo "=== 5. Tag an annotated release and push everything to origin ==="
git tag -a v1.0.0 -m "First release: note search"
git push --quiet origin main
git push --quiet origin v1.0.0
echo
echo "=== Atomic vs sloppy commit (illustration on a scratch branch) ==="
git switch --quiet -c demo/atomic-vs-sloppy
# Atomic: two separate logical changes -> two commits you can revert or bisect.
echo 'MAX_RESULTS = 50' > config.py
git add config.py
git commit --quiet -m "feat: add MAX_RESULTS setting"
printf '%s\n' 'def tidy(s):' ' return s.strip()' > utils.py
git add utils.py
git commit --quiet -m "refactor: add tidy() helper"
echo " Atomic history on this branch:"
git log --oneline -2 | sed 's/^/ /'
git switch --quiet main
git branch -D demo/atomic-vs-sloppy >/dev/null 2>&1
echo " (A sloppy alternative would cram both changes into one 'stuff' commit,"
echo " which you could not revert or bisect independently.)"
echo
echo "=== Tags ==="
git tag
echo "=== History (main) ==="
git log --oneline --decorate --graph
echo "=== Workflow demo complete ==="
metadata.yml (585 bytes)
lesson_id: D035
day: 35
kind: shell-scripting
languages: [bash]
setup_commands:
- cd labs/sections/computing-foundations/day-035-git-workflows-for-real-projects
run_commands:
- bash examples/workflow_demo.sh
- bash starter/workflow_demo.sh
test_commands:
- bash tests/run_tests.sh
cleanup_commands:
- '# nothing to clean up: scripts work only in a mktemp dir removed on exit'
requires_network: false
requires_api_key: false
estimated_minutes: 30
last_executed: '2026-07-12'
executed_on: 'macOS (Apple Silicon), git 2.50.x, bash tests/run_tests.sh → 8 checks, 0 failure(s)'
requirements/README.md (895 bytes)
# Dependencies — Day 035 lab
**Just git and a POSIX shell.** This lab has no installable dependencies:
- `git` (2.23 or newer — for the `git switch` command; any git from the last
few years works). Check with `git --version`.
- `bash` ≥ 3.2 (preinstalled on macOS and every mainstream Linux distribution).
- Standard OS utilities used by the scripts: `mktemp`, `printf`, `grep`, `awk`,
`sed` — all part of the base system.
There is no `requirements.txt` or `package.json`, no account, and no API key.
Every git operation runs against a **local bare repository** created in a
temporary directory, so the lab needs **no network connection** at all.
If `git` is missing:
- **macOS:** `xcode-select --install` (installs the command-line tools), or
install git from your package manager of choice.
- **Debian/Ubuntu:** `sudo apt install git`.
- **Fedora:** `sudo dnf install git`.
starter/workflow_demo.sh (3645 bytes)
#!/usr/bin/env bash
# Day 035 lab — YOUR working file: run a real git workflow yourself.
#
# This starter builds the same isolated, network-free setup as the reference
# (a throwaway working repo plus a local bare "origin" in a temp directory,
# with a LOCAL git identity so your global config is never touched). Your job
# is to complete the five numbered exercises below, replacing each
# : "__FILL_ME_IN__ exercise N ..." # a harmless no-op placeholder line
# with the real git command named in its comment. Try it before peeking at
# examples/workflow_demo.sh.
#
# Run it with: bash starter/workflow_demo.sh
# Check it with: bash tests/run_tests.sh
set -euo pipefail
# --- Isolated workspace, cleaned up on exit (already done for you) ----------
work_root="$(mktemp -d "${TMPDIR:-/tmp}/day035-starter.XXXXXX")"
cleanup() { rm -rf "${work_root}"; }
trap cleanup EXIT
origin="${work_root}/origin.git"
repo="${work_root}/notes"
git init --quiet --bare -b main "${origin}"
git clone --quiet "${origin}" "${repo}" 2>/dev/null
cd "${repo}"
git config user.name "Workflow Learner"
git config user.email "learner@example.invalid"
echo "=== 1. Initial commit with a .gitignore ==="
printf '%s\n' 'node_modules/' '*.log' '.env' > .gitignore
printf '%s\n' '# Course Notes' '' 'Notes for the 365 Days of AI course.' > README.md
git add .gitignore README.md
# Exercise 1: make the initial commit with a chore: Conventional Commit message.
# Use: git commit -m "chore: initialize notes repository with .gitignore"
: "__FILL_ME_IN__ exercise 1 — replace this line with the git commit command above"
echo
echo "=== 2. Create a short-lived feature branch (GitHub Flow) ==="
# Exercise 2: create AND switch to a feature branch named feat/notes-search.
# Use: git switch -c feat/notes-search
: "__FILL_ME_IN__ exercise 2 — replace this line with the git switch command above"
echo " on branch: $(git branch --show-current)"
echo
echo "=== 3. Two atomic commits with Conventional Commit messages ==="
printf '%s\n' 'def search(notes, query):' ' return [n for n in notes if query.lower() in n.lower()]' > search.py
git add search.py
# Exercise 3a: commit this as a feature.
# Use: git commit -m "feat: add case-insensitive note search"
: "__FILL_ME_IN__ exercise 3a — replace this line with the feat: commit command above"
printf '%s\n' 'def search(notes, query):' ' if not query:' ' return []' ' return [n for n in notes if query.lower() in n.lower()]' > search.py
git add search.py
# Exercise 3b: commit the bug fix separately (this is what makes it atomic).
# Use: git commit -m "fix: handle empty query without crashing"
: "__FILL_ME_IN__ exercise 3b — replace this line with the fix: commit command above"
echo
echo "=== 4. Merge back to main with --no-ff (like merging a pull request) ==="
git switch --quiet main
# Exercise 4: merge the feature branch WITHOUT fast-forward so a merge commit
# is recorded (the shape of a merged pull request).
# Use: git merge --no-ff -m "Merge branch 'feat/notes-search'" feat/notes-search
: "__FILL_ME_IN__ exercise 4 — replace this line with the git merge --no-ff command above"
echo
echo "=== 5. Tag an annotated release ==="
# Exercise 5: create an ANNOTATED tag for the first release. Choose the semver
# deliberately and record your reasoning in starter/workflow-worksheet.md.
# Use: git tag -a v1.0.0 -m "First release: note search"
: "__FILL_ME_IN__ exercise 5 — replace this line with the git tag -a command above"
echo
echo "=== Tags ==="
git tag
echo "=== History (main) ==="
git log --oneline --decorate --graph
echo "=== Workflow demo complete ==="
starter/workflow-worksheet.md (2152 bytes)
# Workflow worksheet — Day 035
Fill this in as you complete `starter/workflow_demo.sh` (or as you run the
commands by hand in your own throwaway repository). It is the record of the
GitHub Flow cycle you ran, and it feeds the Versioned Notes Repository project.
## 1. Branch
- **Feature branch name I created:** `______________________`
(Convention used in this lab: `feat/notes-search`. A good branch name is
short and says what the work is.)
- **Command I used to create and switch to it:**
```bash
______________________________________________
```
## 2. Atomic commits (Conventional Commits)
Record each commit message you wrote. At least one `feat:` and one `fix:`.
| # | Type | Full commit message |
| - | ---- | ------------------- |
| 1 | chore | `chore: initialize notes repository with .gitignore` |
| 2 | feat | `______________________________________________` |
| 3 | fix | `______________________________________________` |
Why is each of these commits *atomic*? (One sentence.)
> ______________________________________________________________
## 3. Merge (like merging a pull request)
- **Merge command I used:**
```bash
______________________________________________
```
- **Why `--no-ff`?** (One sentence — what does the merge commit record?)
> ______________________________________________________________
## 4. Release tag
- **Tag command I used:**
```bash
______________________________________________
```
- **Annotated or lightweight? Why that choice?**
> ______________________________________________________________
## 5. The semantic version I chose — and why
I chose the version **`v______`**.
Justify each part as if explaining it to a teammate who will depend on the
number (2–4 sentences):
- **MAJOR (___):** ______________________________________________
- **MINOR (___):** ______________________________________________
- **PATCH (___):** ______________________________________________
## 6. Reflection
If you added a backward-compatible feature next, what would the version become,
and why?
> ______________________________________________________________
tests/run_tests.sh (3230 bytes)
#!/usr/bin/env bash
# Tests for the Day 035 lab. Run from the lab directory:
# bash tests/run_tests.sh
#
# Runs the workflow scripts (each in its own throwaway, network-free repo) and
# verifies the resulting history: a feature branch was merged into main, an
# annotated v1.0.0 tag exists, and the commits follow the feat:/fix:
# Conventional Commits convention. Exits 0 on success, non-zero on any failure.
set -u
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
}
# A workflow script proves its behavior through the history it prints. We run
# it, capture output, and assert on the merge, the tag, and the commit types.
run_workflow_checks() {
local script="$1" output
echo "Testing ${script} ..."
if ! output="$(bash "${script}" 2>&1)"; then
check "script exits successfully" "no"
echo "${output}" | sed 's/^/ /'
return
fi
check "script exits successfully" "yes"
echo "${output}" | grep -q "Merge branch 'feat/notes-search'" \
&& check "feature branch merged into main (merge commit present)" "yes" \
|| check "feature branch merged into main (merge commit present)" "no"
echo "${output}" | grep -q "tag: v1.0.0" \
&& check "annotated v1.0.0 tag pinned in history" "yes" \
|| check "annotated v1.0.0 tag pinned in history" "no"
echo "${output}" | awk '/^=== Tags ===/{f=1;next} /^===/{f=0} f' | grep -qx "v1.0.0" \
&& check "git tag lists v1.0.0" "yes" \
|| check "git tag lists v1.0.0" "no"
echo "${output}" | grep -q "feat: add case-insensitive note search" \
&& check "has a Conventional Commit feat: message" "yes" \
|| check "has a Conventional Commit feat: message" "no"
echo "${output}" | grep -q "fix: handle empty query without crashing" \
&& check "has a Conventional Commit fix: message" "yes" \
|| check "has a Conventional Commit fix: message" "no"
}
# 1) The reference implementation must satisfy every behavioral check.
run_workflow_checks "${lab_dir}/examples/workflow_demo.sh"
# 2) The starter: only run it once the learner has completed the exercises;
# while placeholders remain it cannot build a valid repo, so we check
# structure instead.
echo "Testing starter/workflow_demo.sh ..."
if grep -q '__FILL_ME_IN__' "${lab_dir}/starter/workflow_demo.sh"; then
echo " Note: starter still has unfilled exercises — checking structure only."
count="$(grep -c 'Exercise [0-9]' "${lab_dir}/starter/workflow_demo.sh")"
[ "${count}" -ge 5 ] \
&& check "starter names all five exercises" "yes" \
|| check "starter names all five exercises" "no"
else
run_workflow_checks "${lab_dir}/starter/workflow_demo.sh"
fi
# 3) Hard rule: lab scripts stay fully local — no network URLs anywhere.
if grep -rEn 'https?://' "${lab_dir}/examples" "${lab_dir}/starter" "${lab_dir}/tests" >/dev/null 2>&1; then
check "lab scripts contain no network URLs" "no"
else
check "lab scripts contain no network URLs" "yes"
fi
echo
echo "${checks} checks, ${failures} failure(s)."
[ "${failures}" -eq 0 ]
Troubleshooting
Troubleshooting — Day 035 lab
git: command not found
Git is not installed or not on your PATH. See requirements/README.md for
install instructions, then re-run. Confirm with git --version.
error: unknown switch or git switch not recognized
Your git is older than 2.23. Either update git, or replace git switch -c NAME
with git checkout -b NAME and git switch NAME with git checkout NAME in
the scripts — the behavior is identical.
Author identity unknown / a commit is refused
Git needs a name and email to record a commit. The scripts set a local identity inside the temporary repo:
git config user.name "Workflow Learner"
git config user.email "learner@example.invalid"
These writes go only into the temp repo's .git/config — they never change
your global identity. If you copied commands out of order and skipped these
two lines, the first commit will fail; run them (inside the repo) first.
The merge fast-forwards and no merge commit appears
You omitted --no-ff. Without it, git slides main forward and the branch
shape disappears from the log. Always use git merge --no-ff ... when you want
the merge recorded as a distinct commit (the shape of a merged pull request).
git tag prints nothing
Either the tag command did not run, or you created it in a way that did not persist. Re-run the annotated form and confirm:
git tag -a v1.0.0 -m "First release"
git tag
Annotated vs lightweight tags
- Lightweight (
git tag v1.0.0): just a name pointing at a commit. No author, date, or message. - Annotated (
git tag -a v1.0.0 -m "..."): a full object storing the tagger, date, and message. Use annotated tags for releases.
Inspect the difference with git show v1.0.0 (an annotated tag shows the
tagger and message header; a lightweight tag jumps straight to the commit).
Deleting a tag
If you tagged the wrong commit or chose the wrong version:
git tag -d v1.0.0 # delete the local tag
git push origin :refs/tags/v1.0.0 # delete it on a remote you already pushed to
Then re-create the tag on the correct commit. (In this lab the "remote" is the
local bare origin, so the second command works offline too.)
The starter script errors out on git log
That is expected while exercises are unfilled: with the placeholders in
place, no commits are made, so git log has nothing to show. Complete the five
exercises (replace every __FILL_ME_IN__ line) and the script runs cleanly.
The test detects the placeholders and checks the starter's structure only until
you finish.
Windows: bash is not recognized
Use WSL (wsl --install, then open your Linux distribution and follow the
Linux path). These are bash scripts; native PowerShell is not supported here.
Security notes
Security notes — Day 035 lab
- Everything happens in a temporary directory. Both scripts create their
repositories under a
mktempdirectory (for example/tmp/day035-demo.XXXXXX) and delete it on exit via atrap. Nothing is written to your home directory, your existing repositories, or anywhere permanent. - No network, no account, no keys. The "origin" is a local bare repository in that same temp directory. The scripts make no network calls and need no credentials, so there is nothing to leak.
- Your global git identity is untouched. The scripts set
user.nameanduser.emailwith a repository-localgit configinside the temp repo. Your machine's global git configuration is never read or modified. - The
.envdemonstration is a teaching prop. The scripts create a fake.envfile containing an obviously non-real value to show that.gitignorekeeps it out of git. It is never committed and is destroyed with the temp directory. This mirrors the real rule: never commit secrets — exclude them with.gitignore, because anything committed lives in history forever, even after a later deletion. - Read before you run. Both scripts are short and commented. Reading a script before executing it is a habit this course reinforces; you can see exactly what these do (create files, make commits, delete a temp dir).