Computing Foundations › Git and GitHub › Day 34
Hands-on lab — Day 34: Undoing Things: Reset, Revert, and Reflog
- ← Back to the Day 34 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-034-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
- Lesson title: Undoing Things: Reset, Revert, and Reflog
- Day number: 34 of 365
- Lesson article: https://ai-roadmap-365.github.io/day-034-undoing-things-reset-revert-and-reflog
- 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-034-undoing-things-reset-revert-and-reflogwhen the site is running.
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 --amendand see the hash change. - Un-stage a file with
git restore --stagedand discard an edit withgit restore. - Observe what
git reset --softandgit reset --hardeach do to staged and edited files. - Create an inverse commit with
git revert. - Recover commits dropped by
git reset --hardusinggit reflogandgit 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 --versionshould 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, sogit restoreis 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 agit reset --hard HEAD~2disaster recovered viagit 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 printsSUCCESSwhen 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
- Run
bash examples/undo_recover.sh— it must end withSUCCESS: recovered 4 commits. - Complete the five exercises in
starter/undo_recover.sh; running it must also printSUCCESS. - Confirm no
REPLACE THIS LINEorPLACEHOLDERmarker remains in your starter. - 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
- Recover a dropped commit without the reflog: after a hard reset, run
git fsck --lost-found, find the dangling commit, and restore it withgit branch recovered <hash>. - Practise the shared-history rule: make a commit, then undo it two ways — once
with
git resetand once withgit revert— and comparegit log --onelineto see how reset rewrites history while revert grows it. - Explore
git reset --softfor squashing: make three tiny commits, thengit reset --soft HEAD~3and re-commit once, combining them into a single clean commit.
Navigation
- 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 --hardis on a repository you do not care about. --harddiscards uncommitted work: the one genuinely destructive move in this lesson. On a real repository,git reset --hardandgit restoreoverwrite your working files, and uncommitted changes are gone with no undo and no reflog entry. Alwaysgit statusfirst, and commit orgit stashbefore running--hardon anything you might still want.- Local identity, not global: the scripts set
user.emailanduser.namelocally 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.