Computing FoundationsGit and GitHub › Day 34

Hands-on lab — Day 34: Undoing Things: Reset, Revert, and Reflog

Commands

Setup

cd labs/sections/computing-foundations/day-034-undoing-things-reset-revert-and-reflog

Run

bash examples/undo_recover.sh
bash starter/undo_recover.sh

Test

bash tests/run_tests.sh

File tree

examples/undo_recover.sh
expected-output/FIELDS.md
expected-output/sample-run.txt
metadata.yml
README.md
requirements/README.md
security.md
starter/undo_recover.sh
starter/undo-worksheet.md
tests/run_tests.sh
troubleshooting.md

Lab README

Day 034 lab — Undo and Recover

Lesson

Purpose

Day 34's lesson explains how Git lets you fix mistakes safely and recover work that looks lost. This lab makes it real: on a throwaway repository you create just for practice, you amend a commit, un-stage and discard changes, rewind with git reset, undo with git revert, and then stage a deliberate "disaster" — dropping two commits with git reset --hard — and recover them from the reflog, proving with your own hands that committed work is almost impossible to truly lose.

Learning objectives

  • Fix the last commit's message with git commit --amend and see the hash change.
  • Un-stage a file with git restore --staged and discard an edit with git restore.
  • Observe what git reset --soft and git reset --hard each do to staged and edited files.
  • Create an inverse commit with git revert.
  • Recover commits dropped by git reset --hard using git reflog and git reset --hard <hash>.

Prerequisites

  • The Day 34 lesson (read it first — it explains every command this lab runs).
  • Days 29–32: Git basics (init, add, commit, branches, remotes).
  • A terminal and a working Git install (git --version should print 2.23+).

Supported operating systems

  • macOS — fully supported (tested on macOS with Apple Silicon, git 2.50.1).
  • Linux — fully supported (any distribution with Git and bash).
  • Windows — run inside WSL or Git Bash; the commands are identical there.

Hardware requirements

Any computer that can run Git. The lab creates a tiny repository in a temporary directory and needs no meaningful disk, RAM, or GPU.

Required software

  • git (2.23 or newer, so git restore is available).
  • bash (3.2 or newer — preinstalled on macOS and Linux).
  • Standard utilities mktemp, rm, tail — all preinstalled.

Free and open-source options

Everything here is free and open source: Git itself, bash, and every command used. No account, API key, network access, or purchase is required.

Installation

None. Clone the repository (or copy this directory) and you are ready:

cd labs/sections/computing-foundations/day-034-undoing-things-reset-revert-and-reflog

File structure

day-034-undoing-things-reset-revert-and-reflog/
├── README.md                       ← you are here
├── metadata.yml                    ← machine-readable lab metadata
├── starter/
│   ├── undo_recover.sh             ← YOUR working file (5 exercises)
│   └── undo-worksheet.md           ← worksheet for the practice assignment
├── examples/
│   └── undo_recover.sh             ← completed reference implementation
├── tests/
│   └── run_tests.sh                ← automated checks
├── expected-output/
│   ├── sample-run.txt              ← real captured run (macOS, git 2.50.1)
│   └── FIELDS.md                   ← what every correct run must show
├── requirements/
│   └── README.md                   ← dependency statement (Git + a shell)
├── troubleshooting.md
└── security.md

How to run

From this directory:

## 1. See the finished result first (safe: it uses a throwaway temp repo)
bash examples/undo_recover.sh

## 2. Your task: complete the five exercises in the starter, then run it
bash starter/undo_recover.sh

## 3. Check your work
bash tests/run_tests.sh

What the commands do

  • bash examples/undo_recover.sh — creates a temporary Git repository, builds a three-commit history, then demonstrates every undo move: git commit --amend, git restore --staged, git restore, git reset --soft, git revert, and finally a git reset --hard HEAD~2 disaster recovered via git reflog. The temp repo is deleted on exit.
  • bash starter/undo_recover.sh — the same skeleton with five # TASK: placeholders. Edit the file and replace each marked line with the exact git command named in its comment, then run it; it prints SUCCESS when the recovery works.
  • bash tests/run_tests.sh — independently builds its own throwaway repo and checks the three core claims: amend changes the last commit, revert creates an inverse commit, and a hard reset's dropped commits are recovered via the reflog. Exits 0 on success.

Expected output

See expected-output/sample-run.txt — a real captured run. The final line reads:

SUCCESS: recovered 4 commits — nothing was truly lost.

Your commit hashes will differ every run (they are computed from content and timestamp); only the structure and the successful recovery are fixed. expected-output/FIELDS.md lists exactly what a correct run must show.

Validation steps

  1. Run bash examples/undo_recover.sh — it must end with SUCCESS: recovered 4 commits.
  2. Complete the five exercises in starter/undo_recover.sh; running it must also print SUCCESS.
  3. Confirm no REPLACE THIS LINE or PLACEHOLDER marker remains in your starter.
  4. 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 uses only a local throwaway repository — no network, no sudo.

Cleanup

Nothing to clean up: every script works inside a temporary directory created with mktemp and removed automatically on exit (even on error, via a trap). Your real repositories and global Git config are never touched.

Troubleshooting

See troubleshooting.md for the full list — reflog recovery after a hard reset, detached HEAD, the --hard warning, old-Git restore equivalents, and identity/not a git repository errors.

Security notes

See security.md. Short version: the scripts run no network calls, need no elevated privileges, and touch only a throwaway temp repo — but git reset --hard discards uncommitted work, so on real projects commit or stash first.

Extension exercises

  1. Recover a dropped commit without the reflog: after a hard reset, run git fsck --lost-found, find the dangling commit, and restore it with git branch recovered <hash>.
  2. Practise the shared-history rule: make a commit, then undo it two ways — once with git reset and once with git revert — and compare git log --oneline to see how reset rewrites history while revert grows it.
  3. Explore git reset --soft for squashing: make three tiny commits, then git reset --soft HEAD~3 and re-commit once, combining them into a single clean commit.
  • Previous day: Day 33 — Pull Requests and Code Review (labs/sections/computing-foundations/day-033-pull-requests-and-code-review/, to be written).
  • Next day: Day 35 — Git Workflows for Real Projects (labs/sections/computing-foundations/day-035-git-workflows-for-real-projects/, to be written).

Expected output

FIELDS.md

# Expected output — Day 034 lab

`sample-run.txt` in this directory is a **real captured run** of
`examples/undo_recover.sh` on macOS (Apple Silicon, git 2.50.1, 2026-07-12).

## What every correct run must show, in order

1. Section `1. Build a small history` — a three-commit log (`Frist commit`,
   `Second commit`, `Third commit`).
2. Section `2. git commit --amend fixes the last message` — the HEAD subject
   changes from `Third commit` to `Third commit (message fixed)`.
3. Section `3. git restore --staged un-stages a file` — `scratch.txt` moves
   from staged (`A  scratch.txt`) to untracked (`?? scratch.txt`).
4. Section `4. git restore discards a working-tree change` — the unwanted last
   line disappears; `notes.txt`'s last line returns to `step three`.
5. Section `5. git reset --soft ...` — the change stays staged (`M  notes.txt`)
   after the soft reset, then is re-committed.
6. Section `6. git revert ...` — a new commit whose subject begins with
   `Revert "..."` appears above the original.
7. Section `7. DISASTER ...` — commit count drops from `4` to `2` after
   `git reset --hard HEAD~2`.
8. Section `8. RECOVER with git reflog` — the reflog lists the pre-disaster
   commit, and after recovery the commit count returns to `4`.
9. Final line: `SUCCESS: recovered 4 commits — nothing was truly lost.`

## Values that will differ on your machine

- **Commit hashes** (short SHAs like `a6ed65f`) are computed from content,
  author, and timestamp, so every run produces different hashes. Only the
  *structure* — subjects, counts, and the recovery — is fixed.
- **Timestamps** in the revert commit's `Date:` line reflect when you run it.

## Platform differences

The commands are identical on macOS and Linux; only `mktemp`'s temp path
differs (honoured by the `${TMPDIR:-/tmp}` default in the scripts). On Windows,
run inside WSL or Git Bash. Output shape is otherwise the same everywhere.

sample-run.txt


=== 1. Build a small history ===
6a6709d Third commit
9c8fe5c Second commit
89928df Frist commit

=== 2. git commit --amend fixes the last message ===
before amend, HEAD subject:
Third commit
after amend, HEAD subject:
Third commit (message fixed)

=== 3. git restore --staged un-stages a file ===
status shows scratch.txt as staged:
A  scratch.txt
after restore --staged, scratch.txt is no longer staged:
?? scratch.txt

=== 4. git restore discards a working-tree change ===
notes.txt now has an unwanted last line:
OOPS unwanted edit
after restore, the unwanted line is gone; last line is:
step three

=== 5. git reset --soft keeps changes staged, then re-commit ===
after --soft reset, the change is still staged:
M  notes.txt
a61ab6d Third commit (re-committed after soft reset)
9c8fe5c Second commit
89928df Frist commit

=== 6. git revert adds an inverse commit (safe for shared history) ===
[main 03a5382] Revert "Third commit (re-committed after soft reset)"
 Date: Sun Jul 12 19:18:06 2026 +0530
 1 file changed, 1 deletion(-)
log now shows the original commit AND its revert:
03a5382 Revert "Third commit (re-committed after soft reset)"
a61ab6d Third commit (re-committed after soft reset)

=== 7. DISASTER: git reset --hard HEAD~2 drops two commits ===
commit count before disaster: 4
HEAD is now at 9c8fe5c Second commit
commit count after --hard reset: 2
log is now shorter:
9c8fe5c Second commit
89928df Frist commit

=== 8. RECOVER with git reflog ===
the reflog still remembers where HEAD used to be:
9c8fe5c HEAD@{0}: reset: moving to HEAD~2
03a5382 HEAD@{1}: revert: Revert "Third commit (re-committed after soft reset)"
a61ab6d HEAD@{2}: commit: Third commit (re-committed after soft reset)
9c8fe5c HEAD@{3}: reset: moving to HEAD~1
6624605 HEAD@{4}: commit (amend): Third commit (message fixed)
recovering to the pre-disaster commit: 03a5382
HEAD is now at 03a5382 Revert "Third commit (re-committed after soft reset)"
commit count after recovery: 4
03a5382 Revert "Third commit (re-committed after soft reset)"
a61ab6d Third commit (re-committed after soft reset)
9c8fe5c Second commit
89928df Frist commit

=== Result ===
SUCCESS: recovered 4 commits — nothing was truly lost.

Source files

examples/undo_recover.sh (3561 bytes)
#!/usr/bin/env bash
# Day 034 lab — completed reference: undo and recover in Git.
#
# Builds a THROWAWAY git repository in a temporary directory, then walks
# through every undo move from the lesson and ends with a deliberate
# "disaster" (git reset --hard HEAD~2) that we recover from with the reflog.
# Nothing outside the temp directory is touched, and it is deleted on exit.
#
# Run it from the lab directory:  bash examples/undo_recover.sh
set -euo pipefail

# --- create an isolated scratch repo ------------------------------------
work="$(mktemp -d "${TMPDIR:-/tmp}/undo-recover.XXXXXX")"
cleanup() { rm -rf "${work}"; }
trap cleanup EXIT
cd "${work}"

section() { printf '\n=== %s ===\n' "$1"; }

git init -q
# Local identity only — your global git config is left untouched.
git config user.email "learner@example.com"
git config user.name "Undo Learner"
# Keep the branch name predictable across git versions.
git symbolic-ref HEAD refs/heads/main 2>/dev/null || true

section "1. Build a small history"
echo "line 1" > notes.txt
git add notes.txt
git commit -q -m "Frist commit"          # deliberate typo, fixed next
echo "step two" >> notes.txt
git commit -q -am "Second commit"
echo "step three" >> notes.txt
git commit -q -am "Third commit"
git log --oneline

section "2. git commit --amend fixes the last message"
echo "before amend, HEAD subject:"
git log -1 --pretty=%s
git commit -q --amend -m "Third commit (message fixed)"
echo "after amend, HEAD subject:"
git log -1 --pretty=%s

section "3. git restore --staged un-stages a file"
echo "a scratch change" > scratch.txt
git add scratch.txt
echo "status shows scratch.txt as staged:"
git status --short
git restore --staged scratch.txt
echo "after restore --staged, scratch.txt is no longer staged:"
git status --short
rm -f scratch.txt

section "4. git restore discards a working-tree change"
echo "OOPS unwanted edit" >> notes.txt
echo "notes.txt now has an unwanted last line:"
tail -n 1 notes.txt
git restore notes.txt
echo "after restore, the unwanted line is gone; last line is:"
tail -n 1 notes.txt

section "5. git reset --soft keeps changes staged, then re-commit"
git reset --soft HEAD~1
echo "after --soft reset, the change is still staged:"
git status --short
git commit -q -m "Third commit (re-committed after soft reset)"
git log --oneline

section "6. git revert adds an inverse commit (safe for shared history)"
target="$(git rev-parse --short HEAD)"
git revert --no-edit "${target}"
echo "log now shows the original commit AND its revert:"
git log --oneline -n 2

section "7. DISASTER: git reset --hard HEAD~2 drops two commits"
before_count="$(git rev-list --count HEAD)"
echo "commit count before disaster: ${before_count}"
git reset --hard HEAD~2
after_count="$(git rev-list --count HEAD)"
echo "commit count after --hard reset: ${after_count}"
echo "log is now shorter:"
git log --oneline

section "8. RECOVER with git reflog"
echo "the reflog still remembers where HEAD used to be:"
git reflog -n 5
# The commit we were on BEFORE the disastrous reset is reflog entry HEAD@{1}.
lost="$(git rev-parse --short 'HEAD@{1}')"
echo "recovering to the pre-disaster commit: ${lost}"
git reset --hard "${lost}"
recovered_count="$(git rev-list --count HEAD)"
echo "commit count after recovery: ${recovered_count}"
git log --oneline

section "Result"
if [ "${recovered_count}" = "${before_count}" ]; then
  echo "SUCCESS: recovered ${recovered_count} commits — nothing was truly lost."
else
  echo "MISMATCH: expected ${before_count}, got ${recovered_count}" >&2
  exit 1
fi
metadata.yml (590 bytes)
lesson_id: D034
day: 34
kind: command-line-inspection
languages: [bash]
setup_commands:
  - cd labs/sections/computing-foundations/day-034-undoing-things-reset-revert-and-reflog
run_commands:
  - bash examples/undo_recover.sh
  - bash starter/undo_recover.sh
test_commands:
  - bash tests/run_tests.sh
cleanup_commands:
  - '# none needed: scripts work only inside a temp dir deleted 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.1, bash tests/run_tests.sh → 8 checks, 0 failures'
requirements/README.md (789 bytes)
# Dependencies — Day 034 lab

**Only Git and a POSIX shell.** This lab has zero installable dependencies
beyond tools you already have from the Git section:

- `git` (2.23 or newer recommended, so that `git restore` is available;
  every command was verified on git 2.50.1). Check yours with `git --version`.
- `bash` ≥ 3.2 (preinstalled on macOS and every mainstream Linux distribution).
- Standard utilities: `mktemp`, `rm`, `tail` — all part of the base system.

No network access is required and no API key is needed: every operation runs
against a throwaway local repository created in a temporary directory. If
`git restore` is unavailable on an older Git, the lesson and troubleshooting
notes give the equivalent `git reset HEAD <file>` and `git checkout -- <file>`
commands.
starter/undo_recover.sh (4035 bytes)
#!/usr/bin/env bash
# Day 034 lab — YOUR working file: undo and recover in Git.
#
# This starter builds a THROWAWAY git repository in a temporary directory
# and sets up a small history for you. Your job is to complete the five
# numbered exercises below: replace each line marked  # <-- replace  with the
# single git command named just above it in the  # TASK:  comment. The
# completed reference is in examples/undo_recover.sh — try it yourself first.
#
# As shipped, the script runs to the end and prints "NOT YET" because the
# recovery is not done. When you have finished all five tasks it prints
# "SUCCESS". Nothing outside the temp directory is touched; it is deleted on
# exit. Run it from the lab directory:  bash starter/undo_recover.sh
#
# Note: this starter uses `set -uo pipefail` (no `-e`) on purpose, so an
# unfinished exercise does not abort the whole script.
set -uo pipefail

work="$(mktemp -d "${TMPDIR:-/tmp}/undo-starter.XXXXXX")"
cleanup() { rm -rf "${work}"; }
trap cleanup EXIT
cd "${work}" || exit 1

section() { printf '\n=== %s ===\n' "$1"; }

git init -q
git config user.email "learner@example.com"
git config user.name "Undo Learner"
git symbolic-ref HEAD refs/heads/main 2>/dev/null || true

section "Setup: a three-commit history"
echo "line 1" > notes.txt
git add notes.txt
git commit -q -m "Frist commit"          # note the deliberate typo
echo "line 2" >> notes.txt
git commit -q -am "Second commit"
echo "line 3" >> notes.txt
git commit -q -am "Third commit"
git log --oneline

section "Exercise 1: fix the last commit message with amend"
echo "before:"; git log -1 --pretty=%s
# TASK 1: amend the last commit's message here, using:
#         git commit --amend -m "Third commit (fixed)"
echo 'TASK 1 not done — replace this line with the amend command'   # <-- replace
echo "after:"; git log -1 --pretty=%s

section "Exercise 2: un-stage a file with restore --staged"
echo "scratch" > scratch.txt
git add scratch.txt
echo "staged now:"; git status --short
# TASK 2: un-stage scratch.txt using:  git restore --staged scratch.txt
echo 'TASK 2 not done — replace this line with the restore --staged command'   # <-- replace
echo "after un-staging (should be ?? not A):"; git status --short
rm -f scratch.txt

section "Exercise 3: discard a working change with restore"
echo "unwanted line" >> notes.txt
echo "last line before restore:"; tail -n 1 notes.txt
# TASK 3: discard the edit to notes.txt using:  git restore notes.txt
echo 'TASK 3 not done — replace this line with the restore command'   # <-- replace
echo "last line after restore (should be 'line 3'):"; tail -n 1 notes.txt
git checkout -q -- notes.txt 2>/dev/null || true   # keep the repo tidy for later steps

section "Exercise 4: un-commit with reset --soft, then re-commit"
echo "line 3 again" >> notes.txt
# TASK 4: rewind one commit but keep the change staged, using:
#         git reset --soft HEAD~1
echo 'TASK 4 not done — replace this line with the reset --soft command'   # <-- replace
echo "staged after soft reset (should list notes.txt as M):"; git status --short
git commit -q -am "Third commit (re-committed)" 2>/dev/null || true

section "Exercise 5: DISASTER and RECOVERY"
before_count="$(git rev-list --count HEAD)"
echo "commit count before disaster: ${before_count}"
git reset --hard HEAD~2 >/dev/null
echo "count after --hard HEAD~2: $(git rev-list --count HEAD)"
git reflog -n 5
# TASK 5: recover the dropped commits. The pre-disaster commit is HEAD@{1}
#         in the reflog. Restore it using:  git reset --hard 'HEAD@{1}'
echo 'TASK 5 not done — replace this line with the recovery reset command'   # <-- replace
recovered_count="$(git rev-list --count HEAD)"
echo "commit count after recovery attempt: ${recovered_count}"
git log --oneline

section "Result"
if [ "${recovered_count}" = "${before_count}" ]; then
  echo "SUCCESS: recovered ${recovered_count} commits — nothing was lost."
else
  echo "NOT YET: expected ${before_count}, got ${recovered_count}. Finish the five TASKs above."
fi
starter/undo-worksheet.md (1518 bytes)
# Undo and Recover — Day 034 worksheet

Fill this in as you complete the five exercises in `undo_recover.sh` (or as
you run the reference in `examples/undo_recover.sh`). Keep it — the Week 5
project (a versioned notes repository) expects you to demonstrate a clean
recovery, and this is your rehearsal.

## 1. What `git commit --amend` changed

| | Before amend | After amend |
| --- | --- | --- |
| Commit message (subject) |  |  |
| Commit hash (short) |  |  |

One sentence: why did the hash change even though I only edited the message?

>

## 2. `git reset --soft` versus `git reset --hard`

For each mode, record what happened to your **staged changes** (the index)
and your **edited files** (the working tree).

| Reset mode | What happened to staged changes | What happened to edited files |
| --- | --- | --- |
| `git reset --soft HEAD~1` |  |  |
| `git reset --hard HEAD~1` |  |  |

One sentence: which mode is safe to run with uncommitted work you care about,
and which one would destroy it?

>

## 3. The reflog hash I recovered

- Commit count **before** the disaster (`git reset --hard HEAD~2`): __________
- Commit count **after** the disaster: __________
- The reflog line I recovered from (paste it): 

  ```
  
  ```

- The exact command I ran to recover: 

  ```
  
  ```

- Commit count **after** recovery: __________

One or two sentences: why did restoring to that single commit bring back the
*whole* chain of commits, not just one?

>

## One thing that surprised me

One or two sentences.
tests/run_tests.sh (3740 bytes)
#!/usr/bin/env bash
# Tests for the Day 034 lab. Run from the lab directory:
#   bash tests/run_tests.sh
#
# Builds its own throwaway repository in a temp directory and independently
# verifies the three claims of the lesson:
#   (1) git commit --amend changes the last commit,
#   (2) git revert creates a new inverse commit,
#   (3) after git reset --hard the dropped commits are RECOVERED via reflog.
# Exits 0 on success, non-zero on any failure. No network, no sudo.
set -u

# Resolve the lab directory before we change into the scratch repo.
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
}

# --- isolated scratch repo ----------------------------------------------
work="$(mktemp -d "${TMPDIR:-/tmp}/undo-test.XXXXXX")"
cleanup() { rm -rf "${work}"; }
trap cleanup EXIT
cd "${work}" || { echo "cannot enter temp dir"; exit 1; }

git init -q
git config user.email "test@example.com"
git config user.name "Undo Test"
git symbolic-ref HEAD refs/heads/main 2>/dev/null || true

echo "Testing undo and recovery in a throwaway repo ..."

# Build a three-commit history.
echo "a" > f.txt; git add f.txt; git commit -q -m "Frist commit"
echo "b" >> f.txt; git commit -q -am "Second commit"
echo "c" >> f.txt; git commit -q -am "Third commit"

# (1) amend changes the last commit (subject and hash both change).
before_hash="$(git rev-parse HEAD)"
before_subj="$(git log -1 --pretty=%s)"
git commit -q --amend -m "Third commit (fixed)"
after_hash="$(git rev-parse HEAD)"
after_subj="$(git log -1 --pretty=%s)"
[ "${before_hash}" != "${after_hash}" ] && check "amend changes the commit hash" "yes" || check "amend changes the commit hash" "no"
[ "${before_subj}" = "Third commit" ] && [ "${after_subj}" = "Third commit (fixed)" ] \
  && check "amend changes the commit message" "yes" || check "amend changes the commit message" "no"

# (2) revert creates a new inverse commit on top.
count_before_revert="$(git rev-list --count HEAD)"
git revert --no-edit HEAD >/dev/null
count_after_revert="$(git rev-list --count HEAD)"
revert_subj="$(git log -1 --pretty=%s)"
[ "${count_after_revert}" -eq "$((count_before_revert + 1))" ] \
  && check "revert adds one new commit" "yes" || check "revert adds one new commit" "no"
case "${revert_subj}" in
  Revert*) check "the new commit is an inverse (Revert) commit" "yes" ;;
  *)       check "the new commit is an inverse (Revert) commit" "no" ;;
esac

# (3) hard reset drops commits; reflog recovers them.
full_count="$(git rev-list --count HEAD)"
git reset --hard HEAD~2 >/dev/null
dropped_count="$(git rev-list --count HEAD)"
[ "${dropped_count}" -eq "$((full_count - 2))" ] \
  && check "hard reset drops two commits" "yes" || check "hard reset drops two commits" "no"

# The pre-disaster commit is reflog entry HEAD@{1}.
lost="$(git rev-parse 'HEAD@{1}')"
[ -n "${lost}" ] && check "reflog still references the lost commit" "yes" || check "reflog still references the lost commit" "no"
git reset --hard "${lost}" >/dev/null
recovered_count="$(git rev-list --count HEAD)"
[ "${recovered_count}" -eq "${full_count}" ] \
  && check "commits RECOVERED via reflog (count restored)" "yes" || check "commits RECOVERED via reflog (count restored)" "no"

# The reference implementation runs end to end and exits 0.
if bash "${lab_dir}/examples/undo_recover.sh" >/dev/null 2>&1; then
  check "examples/undo_recover.sh runs end to end" "yes"
else
  check "examples/undo_recover.sh runs end to end" "no"
fi

echo
echo "${checks} checks, ${failures} failure(s)."
[ "${failures}" -eq 0 ]

Troubleshooting

Troubleshooting — Day 034 lab

"I ran git reset --hard and my commits are gone!" — reflog to the rescue

This is the headline skill of the whole lesson: they are almost certainly not gone. A hard reset moved the branch pointer, but the commits are still in the object store and still listed in the reflog. Run:

git reflog

Find the line describing where you were before the reset (often HEAD@{1}), copy its hash, and restore:

git reset --hard <that-hash>

Your commits reappear, because each commit points to its parent, so restoring the newest one brings the whole chain back. This works for weeks after the mistake, until Git's garbage collection eventually prunes unreachable commits.

git restore: command not found or unknown option

git restore was added in Git 2.23 (August 2019). On an older Git, use the classic equivalents: git reset HEAD <file> to un-stage, and git checkout -- <file> to discard a working-tree change. Upgrading Git is the cleaner fix; git --version shows what you have.

You are in 'detached HEAD' state

You checked out a commit hash directly instead of a branch, so HEAD points at a commit rather than a branch, and new commits here belong to no branch. This is safe as long as you do not commit and wander off. To get back onto a branch, run git switch main (or git checkout main). If you did commit in detached HEAD and want to keep it, create a branch first: git branch keep-this <hash>.

--hard warning: it discards uncommitted work

git reset --hard and git restore overwrite your working files. Any change you have not committed is not a snapshot and is not in the reflog, so it cannot be recovered. Before any hard reset, run git status to see what is uncommitted, and git stash (or a quick commit) to protect anything you might still want. The lab scripts only ever touch a throwaway temp repo, so you are safe practising there — build the habit before you use these commands on real work.

Please tell me who you are

Git needs a name and email to make a commit. The lab scripts set a local identity inside the throwaway repo so your global config is untouched. If you are running commands by hand in your own scratch repo, run:

git config user.email "you@example.com"
git config user.name "Your Name"

fatal: not a git repository

You are not inside a Git repository. cd into the directory the script created (or into any repo) before running Git commands. The lab scripts handle this for you by creating and entering a temp directory automatically.

The starter script prints "NOT YET"

That is the built-in check telling you an exercise is unfinished. Search the starter for the REPLACE THIS LINE and PLACEHOLDER markers and complete each # TASK: with the exact command named in its comment, then re-run.

Security notes

Security notes — Day 034 lab

  • What the scripts do: create a small Git repository in a temporary directory (via mktemp), run local Git commands against it, and delete the directory on exit. They make no network connections, need no elevated privileges, and write nothing outside that temp directory.
  • Temp dir only: every operation is confined to a throwaway repo, so practising destructive commands here cannot harm any real project. This is deliberate — the safest way to learn git reset --hard is on a repository you do not care about.
  • --hard discards uncommitted work: the one genuinely destructive move in this lesson. On a real repository, git reset --hard and git restore overwrite your working files, and uncommitted changes are gone with no undo and no reflog entry. Always git status first, and commit or git stash before running --hard on anything you might still want.
  • Local identity, not global: the scripts set user.email and user.name locally inside the temp repo, so your global Git identity is never modified.
  • Reading before running: both scripts are short and commented — read them first. Running unread shell scripts is a common way developers get burned; the course's rule is that every lab script is small enough to read and understand before executing.