Computing Foundations › Git and GitHub › Day 33
Hands-on lab — Day 33: Pull Requests and Code Review
- ← Back to the Day 33 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-033-pull-requests-and-code-review/
Commands
Setup
cd labs/sections/computing-foundations/day-033-pull-requests-and-code-review Run
bash examples/pr_flow.sh
bash starter/pr_flow.sh Test
bash tests/run_tests.sh File tree
examples/pr_flow.sh expected-output/FIELDS.md expected-output/sample-run.txt metadata.yml README.md requirements/README.md security.md starter/pr_flow.sh starter/pr-worksheet.md tests/run_tests.sh troubleshooting.md
Lab README
Day 033 lab — Simulate a Pull Request Locally
Lesson
- Lesson title: Pull Requests and Code Review
- Day number: 33 of 365
- Lesson article: https://ai-roadmap-365.github.io/day-033-pull-requests-and-code-review
- 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-033-pull-requests-and-code-reviewwhen the site is running.
Purpose
Day 33's lesson explains the pull-request-and-review workflow: propose a change
on a branch, open a PR, review and iterate, then merge. This lab makes it
concrete without needing a GitHub account or a network connection. Because a
pull request is just review and merge gates wrapped around Git branches and
merges, you rehearse the whole loop locally: make a change on a feature
branch, produce the exact diff and commit log a pull request would show,
simulate a round of review with a follow-up commit, then merge two different
ways to see how the choice of merge strategy shapes history.
Learning objectives
- Create a feature branch and make a single, focused commit — the substance of a pull request.
- Produce a pull request's core artifacts locally:
git diff main..featureandgit log main..feature. - Simulate the review-and-iterate loop by adding an "addressed feedback" commit.
- Merge with
--no-ffto create a merge commit, exactly as a pull-request merge does, and read the result ingit log --graph. - Contrast the squash strategy (
git merge --squash) and see it collapse a branch into a single commit onmain.
Prerequisites
- The Day 33 lesson (read it first — it explains every concept this lab makes concrete).
- Days 30–32: comfort creating branches, committing, and merging with Git.
- A terminal and Git installed (
git --versionshould print 2.0 or newer; 2.23+ preferred).
Supported operating systems
- macOS — fully supported (tested on macOS with bash and system Git).
- Linux — fully supported (any distribution with
git,bash,mktemp,sed). - Windows — use Git Bash or WSL, where the scripts run unmodified. Plain PowerShell is not supported for this lab.
Hardware requirements
Any computer that can run Git. The lab creates a tiny temporary repository and writes nothing of size.
Required software
git(2.0 or newer; 2.23+ enablesgit switch, but the scripts fall back togit checkoutautomatically).bash(3.2 or newer — preinstalled on macOS and Linux).- Standard utilities
mktemp,sed,printf(all preinstalled).
Free and open-source options
Everything here is free and open source: Git itself and every command used ship with your OS or are freely installable. No account, API key, network, or purchase is needed. A real pull request additionally needs a free account on a hosting platform and network access; this lab covers that side conceptually so you can practice the mechanics offline.
Installation
None beyond Git. Clone the repository (or copy this directory) and change into it:
cd labs/sections/computing-foundations/day-033-pull-requests-and-code-review
File structure
day-033-pull-requests-and-code-review/
├── README.md ← you are here
├── metadata.yml ← machine-readable lab metadata
├── starter/
│ ├── pr_flow.sh ← YOUR working file (5 exercises)
│ └── pr-worksheet.md ← worksheet for the practice assignment
├── examples/
│ └── pr_flow.sh ← completed reference implementation
├── tests/
│ └── run_tests.sh ← automated checks (11 checks)
├── expected-output/
│ ├── sample-run.txt ← a real captured run
│ └── FIELDS.md ← the landmarks every run must show
├── requirements/
│ └── README.md ← dependency statement (just Git)
├── troubleshooting.md
└── security.md
How to run
From this directory:
## 1. See the finished flow first
bash examples/pr_flow.sh
## 2. Your task: complete the five exercises in the starter, then run it
bash starter/pr_flow.sh
## 3. Check your work
bash tests/run_tests.sh
What the commands do
bash examples/pr_flow.sh— runs the reference flow in a throwaway repo: creates afeaturebranch, commits a focused change, printsgit diff main..featureandgit log main..feature(the pull request), adds an "addressed feedback" commit, merges withgit merge --no-ffto create a merge commit, and then contrasts agit merge --squashon a second branch. The repo is deleted on exit.bash starter/pr_flow.sh— the same flow with five numbered exercises for you to complete. Each: "FILL-IN ..."line names the exact command to substitute (git switch -c feature,git add/git commit,git diff/git log,git merge --no-ff). Edit the file, replace everyFILL-INline, then run it.bash tests/run_tests.sh— checks that the starter is valid bash and still has its five exercises, then runs the reference flow in an inspectable repo and verifies the real Git state: thefeaturebranch exists with the focused change and the review follow-up commit, a two-parent merge commit was created by--no-ff, and the squash landed a second branch as a single commit onmain.
Expected output
See expected-output/sample-run.txt — a real
captured run. Commit hashes and the temp-directory path differ on every run;
expected-output/FIELDS.md lists the landmarks that
must appear regardless. The key moments:
== Step 5: merge the PR with a merge commit (--no-ff) ==
History after the merge (note the two-parent merge commit):
* 8140fdc Merge branch 'feature'
|\
| * 123e011 Address review: clarify wording
| * ec70f7e Add greeting line to notes
|/
* 188b3b9 Initial notes
The two-parent merge commit is exactly what a pull-request merge produces; the squash step later shows the contrasting single-commit history.
Validation steps
- Run
bash examples/pr_flow.sh— it must exit without errors and print both the merge-commit graph (Step 5) and the squashed commit (Step 6). - Complete
starter/pr_flow.sh, replacing everyFILL-INline, and run it — it must reach the final== Done ... ==line. - Confirm the merge graph shows a two-parent merge commit and the squash shows
a single commit on
main. - Run the tests (next section) — all checks must pass.
Tests
bash tests/run_tests.sh
Expected final line: 11 checks, 0 failure(s). The command exits 0 on success
and non-zero on any failure, so it can run in CI. It uses no network.
Cleanup
Nothing to clean up: every script works inside a temporary directory that is
deleted on exit, and nothing is written into this repository or your home
directory. To reset your edits to the starter, restore it from git:
git checkout -- starter/pr_flow.sh.
Troubleshooting
See troubleshooting.md for the full list (Git not
installed, missing identity, git switch on older Git, fast-forward vs merge
commit, sed -i portability, graph glyphs).
Security notes
See security.md. Short version: the scripts run no network calls, need no elevated privileges, work only in a temporary directory, and set the commit identity locally so your global Git config is never touched.
Extension exercises
- Before merging, run
git log main..feature --statto see the per-file line counts a reviewer scans to judge how big a change is. - Deliberately create a merge conflict: change the same line on
mainthat yourfeaturebranch changed, commit it, then merge — resolve the conflict,git addthe file, and complete the merge. This mirrors a hosting platform reporting "this branch has conflicts." - Try the
rebase and mergestrategy:git switch feature, thengit rebase main, then a fast-forward merge intomain. Compare the resulting linear history with the merge-commit and squash graphs.
Navigation
- Previous day: Day 32 — Remotes and GitHub
(
labs/sections/computing-foundations/day-032-remotes-and-github/). - Next day: Day 34 — Undoing Things: Reset, Revert, and Reflog
(
labs/sections/computing-foundations/day-034-undoing-things-reset-revert-and-reflog/, to be written).
Expected output
FIELDS.md
# Required output landmarks (all platforms)
`sample-run.txt` in this directory is a real captured run of
`examples/pr_flow.sh` (macOS, bash, 2026-07-12). **Commit hashes and the
temporary directory path change on every run** — that is normal, because Git
computes each commit hash from its content, author, and timestamp. What must
appear on every platform, in this order, is:
1. `Working in throwaway repo: <a temp path>`
2. `== main starts here ==` followed by an `Initial notes` commit.
3. `== Step 1: create a feature branch ==`
4. `== Step 3: the PR ...` showing a `git diff main..feature` that adds the
line about a pull request being a proposal to merge a branch, then a
`git log main..feature` listing the `Add greeting line to notes` commit.
5. `== Step 4: ...` where the PR now lists **two** commits: the original plus
`Address review: clarify wording`.
6. `== Step 5: merge the PR with a merge commit (--no-ff) ==` whose
`git log --graph --oneline` shows a two-parent **merge commit**
(`Merge branch 'feature'`) with the `|\` / `|/` branch lines.
7. `== Step 6: contrast the SQUASH strategy ==` where a second branch with two
`wip` commits lands on `main` as a **single** `Add feature2 note (squashed)`
commit — the two `wip` commits do **not** appear in `main`'s history.
## Platform differences
- **macOS vs Linux:** identical output. The script uses only portable Git
commands; the one `sed -i` in-place edit is written with a `.bak` backup so
it works on both the BSD `sed` (macOS) and GNU `sed` (Linux).
- **Older Git (< 2.23):** `git switch` is unavailable; the script falls back to
`git checkout -b` / `git checkout` automatically, so the output is the same.
- **Git graph glyphs:** some terminals render the `git log --graph` connector
lines with slightly different spacing; the two-parent merge structure is the
invariant, not the exact characters.
sample-run.txt
Working in throwaway repo: /var/folders/7j/4qzljp553ndfjm_y6zbygsz00000gn/T//pr-flow.7iGRFH
== main starts here ==
6db5482 Initial notes
== Step 1: create a feature branch ==
Committed the focused change on 'feature'.
== Step 3: the PR — what you are proposing to merge into main ==
--- git diff main..feature (the change) ---
diff --git a/notes.md b/notes.md
index 2411724..fb868fd 100644
--- a/notes.md
+++ b/notes.md
@@ -1,3 +1,5 @@
# Course Notes
## Day 33: Pull Requests
+
+A pull request is a proposal to merge a branch, reviewed before it lands.
--- git log main..feature (commits the PR would show) ---
0364097 Add greeting line to notes
== Step 4: reviewer asked to clarify wording — push a follow-up commit ==
The PR now contains two commits (original + addressed feedback):
0b7a18c Address review: clarify wording
0364097 Add greeting line to notes
== Step 5: merge the PR with a merge commit (--no-ff) ==
History after the merge (note the two-parent merge commit):
* 9ec1cf9 Merge branch 'feature'
|\
| * 0b7a18c Address review: clarify wording
| * 0364097 Add greeting line to notes
|/
* 6db5482 Initial notes
== Step 6: contrast the SQUASH strategy on a second branch ==
Updating 9ec1cf9..fcf269e
Fast-forward
Squash commit -- not updating HEAD
notes.md | 4 ++++
1 file changed, 4 insertions(+)
feature2 had two 'wip' commits; squash landed them as ONE commit on main:
* fdd66d3 Add feature2 note (squashed)
* 9ec1cf9 Merge branch 'feature'
|\
| * 0b7a18c Address review: clarify wording
| * 0364097 Add greeting line to notes
|/
* 6db5482 Initial notes
== Done. The merge commit and the squashed commit show the two strategies side by side. ==
Throwaway repo /var/folders/7j/4qzljp553ndfjm_y6zbygsz00000gn/T//pr-flow.7iGRFH will be removed on exit.
Source files
examples/pr_flow.sh (4659 bytes)
#!/usr/bin/env bash
# Day 033 lab — completed reference implementation.
#
# Simulates the pull-request-and-review workflow entirely LOCALLY, with no
# GitHub account and no network. A pull request is just review and merge
# gates wrapped around Git branches and merges, so we can rehearse the whole
# logic with plain Git:
#
# 1. Create a throwaway repo with a LOCAL identity (never touches your global config).
# 2. Make a focused change on a `feature` branch and commit it.
# 3. Produce the "pull request": `git diff main..feature` and `git log main..feature`.
# 4. Simulate a round of review by adding an "addressed feedback" commit.
# 5. Merge with --no-ff to create a merge commit, like a PR merge, and show the graph.
# 6. Contrast a squash-merge on a second branch.
#
# Everything happens in a temporary directory that is removed on exit.
set -euo pipefail
# --- Set up an isolated throwaway repository ---------------------------------
# By default the throwaway repo is deleted on exit. The test harness sets
# PR_FLOW_KEEP=1 to inspect the final repo state; it then removes the dir itself.
work="$(mktemp -d "${TMPDIR:-/tmp}/pr-flow.XXXXXX")"
cleanup() { rm -rf "${work}"; }
if [ -z "${PR_FLOW_KEEP:-}" ]; then
trap cleanup EXIT
fi
echo "Working in throwaway repo: ${work}"
git init -q -b main "${work}" 2>/dev/null || { git init -q "${work}"; git -C "${work}" symbolic-ref HEAD refs/heads/main; }
cd "${work}"
# A LOCAL identity, scoped to this repo only — your global Git config is untouched.
git config user.email "learner@example.com"
git config user.name "Course Learner"
# --- Seed main with an initial commit ----------------------------------------
cat > notes.md <<'EOF'
# Course Notes
## Day 33: Pull Requests
EOF
git add notes.md
git commit -q -m "Initial notes"
echo
echo "== main starts here =="
git log --oneline
# --- Step 1: branch off main, like starting a pull request -------------------
echo
echo "== Step 1: create a feature branch =="
git switch -c feature 2>/dev/null || git checkout -q -b feature
# --- Step 2: make ONE focused change and commit it ---------------------------
cat >> notes.md <<'EOF'
A pull request is a proposal to merge a branch, reviewed before it lands.
EOF
git add notes.md
git commit -q -m "Add greeting line to notes"
echo "Committed the focused change on 'feature'."
# --- Step 3: produce the "pull request" --------------------------------------
echo
echo "== Step 3: the PR — what you are proposing to merge into main =="
echo "--- git diff main..feature (the change) ---"
git --no-pager diff main..feature
echo
echo "--- git log main..feature (commits the PR would show) ---"
git --no-pager log main..feature --oneline
# --- Step 4: simulate review by addressing feedback --------------------------
echo
echo "== Step 4: reviewer asked to clarify wording — push a follow-up commit =="
# Reviewer comment: "spell out that review happens before the merge, plainly."
sed -i.bak 's/reviewed before it lands\./reviewed by teammates before it lands in main./' notes.md
rm -f notes.md.bak
git add notes.md
git commit -q -m "Address review: clarify wording"
echo "The PR now contains two commits (original + addressed feedback):"
git --no-pager log main..feature --oneline
# --- Step 5: merge with --no-ff, like a PR merge, and show the graph ---------
echo
echo "== Step 5: merge the PR with a merge commit (--no-ff) =="
git switch main 2>/dev/null || git checkout -q main
git merge --no-ff -q -m "Merge branch 'feature'" feature
echo "History after the merge (note the two-parent merge commit):"
git --no-pager log --graph --oneline
# --- Step 6: contrast a squash-merge on a second branch ----------------------
echo
echo "== Step 6: contrast the SQUASH strategy on a second branch =="
git switch -c feature2 2>/dev/null || git checkout -q -b feature2
cat >> notes.md <<'EOF'
Squash merge collapses a branch's commits into a single commit on main.
EOF
git add notes.md
git commit -q -m "wip: draft squash note"
# A messy second commit, the kind squash is meant to tidy away.
printf '\nStill drafting the squash example.\n' >> notes.md
git add notes.md
git commit -q -m "wip: fix typo"
git switch main 2>/dev/null || git checkout -q main
git merge --squash feature2
git commit -q -m "Add feature2 note (squashed)"
echo "feature2 had two 'wip' commits; squash landed them as ONE commit on main:"
git --no-pager log --graph --oneline
echo
echo "== Done. The merge commit and the squashed commit show the two strategies side by side. =="
if [ -n "${PR_FLOW_KEEP:-}" ]; then
echo "PR_FLOW_REPO=${work}"
else
echo "Throwaway repo ${work} will be removed on exit."
fi
metadata.yml (600 bytes)
lesson_id: D033
day: 33
kind: command-line-inspection
languages: [bash]
setup_commands:
- cd labs/sections/computing-foundations/day-033-pull-requests-and-code-review
run_commands:
- bash examples/pr_flow.sh
- bash starter/pr_flow.sh
test_commands:
- bash tests/run_tests.sh
cleanup_commands:
- 'git checkout -- starter/pr_flow.sh # optional: reset your edits (repos are temp dirs, auto-removed)'
requires_network: false
requires_api_key: false
estimated_minutes: 30
last_executed: '2026-07-12'
executed_on: 'macOS (bash, system Git), bash tests/run_tests.sh → 11 checks, 0 failure(s).'
requirements/README.md (1186 bytes)
# Dependencies — Day 033 lab
**One dependency: Git.** This lab simulates the pull-request workflow with
local branches only, so it needs nothing beyond a shell and Git:
- `bash` ≥ 3.2 (preinstalled on macOS and every mainstream Linux distribution)
- `git` ≥ 2.0 (2.23 or newer preferred, so `git switch` is available; the
scripts fall back to `git checkout` on older versions)
- Standard OS utilities: `mktemp`, `sed`, `printf` — all part of the base
system on macOS and Linux.
There is no `requirements.txt`/`package.json` here; the scripts install
nothing.
## About a *real* pull request
A genuine pull request lives on a hosting platform (such as GitHub or GitLab)
and therefore needs, in real life:
- a free account on the platform, and
- network access to push a branch and open the request in the browser or with
the platform's command-line tool.
This lab deliberately needs **neither**. Because a pull request is review and
merge gates wrapped around Git branches and merges, you can rehearse the entire
logic offline with the tools above. The lesson covers the account-and-network
side conceptually; you will open a real pull request in the Week 5 project.
starter/pr_flow.sh (3262 bytes)
#!/usr/bin/env bash
# Day 033 lab — YOUR working file: simulate a pull request locally.
#
# The scaffolding below creates a throwaway repo with a LOCAL identity and an
# initial commit on main. Your job is to complete the FIVE numbered exercises,
# each of which names the exact command to run. Replace every line that reads
# : "FILL-IN ..."
# with the real command shown in the comment above it, then run:
# bash starter/pr_flow.sh
#
# The completed reference is in examples/pr_flow.sh — try this yourself first.
set -euo pipefail
# --- Scaffolding (already done for you) --------------------------------------
work="$(mktemp -d "${TMPDIR:-/tmp}/pr-flow-starter.XXXXXX")"
trap 'rm -rf "${work}"' EXIT
echo "Working in throwaway repo: ${work}"
git init -q -b main "${work}" 2>/dev/null || { git init -q "${work}"; git -C "${work}" symbolic-ref HEAD refs/heads/main; }
cd "${work}"
git config user.email "learner@example.com"
git config user.name "Course Learner"
printf '# Course Notes\n\n## Day 33: Pull Requests\n' > notes.md
git add notes.md
git commit -q -m "Initial notes"
echo "main starts at:"; git --no-pager log --oneline
# --- Exercise 1: create a feature branch (like starting a pull request) -------
# Command: git switch -c feature (older Git: git checkout -b feature)
: "FILL-IN exercise 1: replace this line with the branch-creating command"
# --- Make a focused change (already written for you) -------------------------
printf '\nA pull request is a proposal to merge a branch, reviewed before it lands.\n' >> notes.md
# --- Exercise 2: stage and commit the focused change -------------------------
# Commands: git add notes.md then git commit -m "Add greeting line to notes"
: "FILL-IN exercise 2a: replace with 'git add notes.md'"
: "FILL-IN exercise 2b: replace with the commit command and message above"
# --- Exercise 3: produce the PR — the diff and the commit log ----------------
# Commands: git --no-pager diff main..feature then git --no-pager log main..feature --oneline
echo; echo "== The pull request: what you propose to merge into main =="
: "FILL-IN exercise 3a: replace with the diff command above"
: "FILL-IN exercise 3b: replace with the log command above"
# --- Simulate review, then Exercise 4: commit the addressed feedback ---------
sed -i.bak 's/reviewed before it lands\./reviewed by teammates before it lands in main./' notes.md
rm -f notes.md.bak
# Commands: git add notes.md then git commit -m "Address review: clarify wording"
: "FILL-IN exercise 4a: replace with 'git add notes.md'"
: "FILL-IN exercise 4b: replace with the commit command and message above"
# --- Exercise 5: merge the PR with a MERGE COMMIT (--no-ff) -------------------
# Commands: git switch main (older: git checkout main)
# git merge --no-ff -m "Merge branch 'feature'" feature
# git --no-pager log --graph --oneline
echo; echo "== Merge the pull request (merge-commit strategy) =="
: "FILL-IN exercise 5a: replace with the command to switch back to main"
: "FILL-IN exercise 5b: replace with the --no-ff merge command above"
: "FILL-IN exercise 5c: replace with the graph log command above"
echo; echo "== Done — you have simulated a pull request from branch to merge. =="
starter/pr-worksheet.md (1960 bytes)
# Pull-request worksheet — Day 033
Fill this in for the change you made in the hands-on exercise. It is the same
thing you would type into the description box of a real pull request. Keep it
when you are done — a clear description you can point to is worth more than any
advice about writing one.
---
## 1. Pull-request description
Write it exactly as you would on a hosting platform.
**Title** (one line, imperative, under ~60 characters):
> _e.g. "Add greeting line to course notes"_
**Description** (what the change does and, crucially, *why*):
> _2–4 sentences. What did you change? Why was it needed? What problem does it
> solve or what does it add?_
**How I verified it** (tests, manual checks, the diff you read):
> _e.g. "Ran the script; git diff main..feature shows only the intended line;
> the merge produced a clean merge commit with no conflicts."_
---
## 2. What the diff shows
Paste the output of `git diff main..feature` (or list the files and the exact
lines it touched):
```text
_paste git diff main..feature here_
```
Files touched: _______________________________________________
Lines added / removed: ________________________________________
---
## 3. Merge strategy
Which strategy did you use to bring `feature` into `main`?
- [ ] Merge commit (`git merge --no-ff`)
- [ ] Squash and merge (`git merge --squash`)
- [ ] Rebase and merge
**Why this strategy fit this change** (2–3 sentences):
> _e.g. "I used a merge commit so the review-and-revise history — the original
> commit plus the 'addressed feedback' commit — stays visible in main." OR
> "I squashed because the two 'wip' commits were noise and I wanted one tidy,
> revertible commit on main."_
---
## 4. Reflection (optional but recommended)
In one or two sentences: what would a reviewer most likely comment on if this
were a real pull request, and how would you respond?
> _______________________________________________________________
tests/run_tests.sh (4237 bytes)
#!/usr/bin/env bash
# Tests for the Day 033 lab. Run from the lab directory:
# bash tests/run_tests.sh
#
# Runs the completed reference flow in an inspectable throwaway repo and
# verifies the real Git state a pull request produces:
# - a `feature` branch exists with the focused change + an "addressed
# feedback" follow-up commit (the diff/commits a PR would show),
# - the merge-commit strategy (--no-ff) produced a two-parent merge commit,
# - the squash strategy landed a second branch as a SINGLE commit on main
# (its intermediate "wip" commits absent from main's history).
# No network is used. 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
}
# 1. The starter must be valid bash and still contain the five exercises.
echo "Checking starter/pr_flow.sh ..."
if bash -n "${lab_dir}/starter/pr_flow.sh" 2>/dev/null; then
check "starter is syntactically valid bash" "yes"
else
check "starter is syntactically valid bash" "no"
fi
exercise_count="$(grep -c 'FILL-IN exercise' "${lab_dir}/starter/pr_flow.sh")"
[ "${exercise_count}" -ge 5 ] && check "starter has at least 5 exercises" "yes" || check "starter has at least 5 exercises" "no"
# 2. Run the reference flow, keeping the repo so we can inspect the end state.
echo "Running examples/pr_flow.sh (kept for inspection) ..."
output=""
if output="$(PR_FLOW_KEEP=1 bash "${lab_dir}/examples/pr_flow.sh" 2>&1)"; then
check "reference flow runs and exits 0" "yes"
else
check "reference flow runs and exits 0" "no"
echo "${output}" | sed 's/^/ /'
echo; echo "${checks} checks, ${failures} failure(s)."
exit 1
fi
repo="$(printf '%s\n' "${output}" | sed -n 's/^PR_FLOW_REPO=//p' | tail -n 1)"
if [ -n "${repo}" ] && [ -d "${repo}/.git" ]; then
check "inspectable throwaway repo was created" "yes"
else
check "inspectable throwaway repo was created" "no"
echo; echo "${checks} checks, ${failures} failure(s)."
exit 1
fi
# Always clean up the kept repo ourselves.
trap 'rm -rf "${repo}"' EXIT
g() { git -C "${repo}" "$@"; }
# 3. The feature branch exists (the PR's source branch).
g show-ref --verify --quiet refs/heads/feature && check "feature branch exists" "yes" || check "feature branch exists" "no"
# 4. The PR's commits are on feature: the focused change AND the review follow-up.
feat_log="$(g log feature --oneline 2>/dev/null)"
printf '%s\n' "${feat_log}" | grep -q "Add greeting line to notes" && check "feature has the focused change commit" "yes" || check "feature has the focused change commit" "no"
printf '%s\n' "${feat_log}" | grep -q "Address review" && check "feature has the addressed-feedback commit" "yes" || check "feature has the addressed-feedback commit" "no"
# 5. The diff a PR would show is non-empty between main's base and feature's tip
# (feature's notes.md contains the proposed line).
g show "feature:notes.md" 2>/dev/null | grep -q "proposal to merge a branch" && check "git diff main..feature would show the proposed change" "yes" || check "git diff main..feature would show the proposed change" "no"
# 6. The no-ff merge produced a two-parent merge commit on main.
merge_count="$(g rev-list --merges --count main 2>/dev/null)"
[ "${merge_count:-0}" -ge 1 ] && check "merge-commit strategy created a merge commit (--no-ff)" "yes" || check "merge-commit strategy created a merge commit (--no-ff)" "no"
# 7. The squash strategy landed as a SINGLE commit on main; the wip commits are absent.
main_log="$(g log main --oneline 2>/dev/null)"
printf '%s\n' "${main_log}" | grep -q "squashed" && check "squash produced a single 'squashed' commit on main" "yes" || check "squash produced a single 'squashed' commit on main" "no"
wip_on_main="$(printf '%s\n' "${main_log}" | grep -c 'wip')"
[ "${wip_on_main}" -eq 0 ] && check "squash collapsed the branch (no wip commits on main)" "yes" || check "squash collapsed the branch (no wip commits on main)" "no"
echo
echo "${checks} checks, ${failures} failure(s)."
[ "${failures}" -eq 0 ]
Troubleshooting
Troubleshooting — Day 033 lab
git: command not found
Git is not installed. On macOS, xcode-select --install provides it; on
Debian/Ubuntu, sudo apt install git; on Fedora, sudo dnf install git.
Verify with git --version.
fatal: not a git repository
You tried to run a Git command outside the throwaway repo. Run
examples/pr_flow.sh (or starter/pr_flow.sh), which creates and enters the
temp repo for you. If you are experimenting by hand, cd into the temp path
the script printed at the top of its output.
Please tell me who you are / empty ident name
Git has no commit identity configured. The scripts set one locally inside the throwaway repo, so this should not happen when you run them. If you are working by hand, set it in that repo only:
git config user.email "you@example.com"
git config user.name "Your Name"
Using git config without --global scopes it to the current repository and
leaves your global settings alone.
git: 'switch' is not a git command
Your Git predates version 2.23 (August 2019), when switch was added. The
scripts fall back to git checkout automatically. If you are typing commands
yourself, use git checkout -b feature to create a branch and
git checkout main to move between them — the effect is identical.
The merge did not create a merge commit
You merged without --no-ff while main had not moved, so Git
"fast-forwarded" and left no merge point. Redo it with
git merge --no-ff feature. A pull-request merge always records a merge
commit; --no-ff (no fast-forward) is how you force one locally.
sed: -i may not be used with stdin or an unexpected notes.md.bak file
The scripts use sed -i.bak (with a backup suffix) specifically so the same
command works on both macOS (BSD sed) and Linux (GNU sed), then delete the
.bak file. If you adapted the command and dropped the suffix, restore it:
sed -i.bak 's/old/new/' notes.md && rm -f notes.md.bak.
Git shows the graph lines differently than the sample
Terminal width and font can shift where the |\ and |/ connector lines sit
in git log --graph. The important thing is the two-parent merge commit,
not the exact spacing. The tests check the merge structure, not the glyphs.
Permission denied when running a script
Run it through bash explicitly: bash examples/pr_flow.sh. If you prefer
./examples/pr_flow.sh, first chmod +x examples/pr_flow.sh.
Security notes
Security notes — Day 033 lab
- What the scripts do: create a throwaway Git repository in a temporary
directory (
mktemp -d), make a few commits, run diffs and merges, and print the results. They make no network connections and need no elevated privileges. - Temporary directory only: all work happens inside a fresh temp directory,
which the reference and starter scripts delete on exit (the test harness sets
PR_FLOW_KEEP=1to inspect the repo, then removes it itself). Nothing is written into your project, your home directory, or any existing repository. - Your global Git config is untouched: the scripts set the commit identity
(
user.name,user.email) locally, inside the throwaway repo only, using a placeholder address (learner@example.com). Your owngit config --globalsettings are never read or changed. - No credentials, no remotes: the lab never authenticates, never adds a remote, and never pushes. There is nothing to leak. When you later open a real pull request, treat the diff and every comment as permanent and potentially public — never paste secrets, API keys, or personal data into them.
- Reading before running: both scripts are short and commented — read them first. Running unread shell scripts is a common way developers get compromised; every lab script in this course is small enough to read and understand before executing.