Lesson 5 - Guided Project: Polish a Branch and Recover a Lost Commit

Welcome to the Guided Project

This project ties the whole module together by walking you through the cycle that defines confident, everyday Git use: clean up, then oops, then rescue. You will do it in two parts, each a self-contained scenario you can run on your own machine.

In Part 1, you’ll inherit a messy feature branch — the kind every developer produces while actually working: a string of wip commits and a last-minute typo fix. You’ll use interactive rebase to squash that noise into a single, clearly-named commit that’s ready to share. In Part 2, you’ll engineer a small disaster. You’ll git reset --hard away two good commits as if you’d fat-fingered the wrong command, watch them seemingly vanish, and then use git reflog to find them again and bring them back. Together these two skills are the safety net under all the history-rewriting you learned this module.

By the end of this project, you will be able to:

  • Squash several messy wip commits into one clean commit with interactive rebase
  • Write a meaningful combined commit message during a squash
  • Recognize that git reset --hard doesn’t truly delete your commits right away
  • Use git reflog to find and recover commits you thought were lost

A quick note before you begin: the commit hashes you see below are real output from a verified run, but your hashes will be different — Git derives them from your content, author, and timestamp. Match on the messages and the shape of the output, not the exact characters. Let’s build.


Part 1 — Inspect the Messy Branch

Picture a feature branch where you’ve been hacking on a small JSON parser. You committed early and often, which is good practice — but the names are throwaway:

$ git log --oneline
6028cb2 oops fix typo
0a3a32a more wip
5416912 wip
34e6232 Add parser

The bottom commit, Add parser, is a reasonable starting point. The three above it — wip, more wip, and oops fix typo — are the breadcrumbs of work-in-progress. Nobody reviewing your code needs to see “oops fix typo” as a separate moment in history. Those three commits really represent one logical change: building the parser. Your goal is to fold them into a single commit with a name that explains the what and why, not the when.

You want to reshape the top three commits, so you’ll rebase the last three interactively.


Part 1 — Squash Into One Clean Commit

Start the interactive rebase across the three commits you want to combine:

$ git rebase -i HEAD~3

Git opens an editor listing those three commits, oldest at the top, each prefixed with the word pick:

pick 5416912 wip
pick 0a3a32a more wip
pick 6028cb2 oops fix typo

To merge them, you keep the first as pick and change the other two to squash — that tells Git to fold each one into the commit above it:

pick 5416912 wip
squash 0a3a32a more wip
squash 6028cb2 oops fix typo

Save and close the file. Git now opens a second editor so you can write the message for the single combined commit. Clear out the old wip text and write something a reviewer would thank you for:

feat: add JSON parser

Save that, and the three commits collapse into one:

$ git log --oneline
dfa8239 feat: add JSON parser
34e6232 Add parser

The history is now clean: a clear feat: add JSON parser commit sitting on top of your starting point, with all the messy intermediate steps gone. This is exactly the branch you’d want to open a pull request from. Notice the new commit has a brand-new hash (dfa8239 here) — squashing is a form of rewriting history, so it produces a fresh commit. That’s perfectly safe because you only did it to local, un-pushed work.


Part 2 — Engineer the Disaster

Now switch scenarios. Imagine a different small repo where you’ve done three commits of real, valuable work:

  • Add work (the starting point)
  • Important analysis
  • More important work

You meant to run a harmless command, but instead you typed git reset --hard HEAD~2 — moving your branch back two commits and discarding everything since. Here’s the moment it happens:

$ git reset --hard HEAD~2
HEAD is now at 8b36465 Add work
$ git log --oneline
8b36465 Add work

Your stomach drops. git log shows only Add work. The Important analysis and More important work commits are gone from the log entirely — as if hours of effort just evaporated. This is the exact disaster that makes people fear git reset --hard.

Here’s the good news: those commits are not actually deleted. reset only moved your branch pointer; the commits themselves are still sitting in the repository, unreferenced, waiting to be found.


Part 2 — Recover With Reflog

The tool that saves you is git reflog. While the ordinary log only shows commits reachable from where your branch currently points, the reflog records every place HEAD has been — including the commits you just reset away. Run it:

$ git reflog
8b36465 HEAD@{0}: reset: moving to HEAD~2
db03b58 HEAD@{1}: commit: More important work
e672b64 HEAD@{2}: commit: Important analysis
8b36465 HEAD@{3}: commit (initial): Add work

There they are. HEAD@{0} is the reset you just did. But HEAD@{1} — commit db03b58, “More important work” — is the exact tip your branch had before the reset. That’s the commit you want back, and recovering it brings Important analysis along too, since it’s that commit’s parent.

To recover, point your branch back at the lost commit with another reset --hard — this time using the hash from the reflog:

$ git reset --hard db03b58
HEAD is now at db03b58 More important work
$ git log --oneline
db03b58 More important work
e672b64 Important analysis
8b36465 Add work

All three commits are back exactly as they were. The “disaster” is undone. Note that db03b58 is the hash from this verified run — in your own repo you’d copy whatever hash the reflog shows on the HEAD@{1} line.

The everyday rhythm of confident Git

These two skills together are the heartbeat of fearless Git use: shape your history deliberately, and know you can always recover. Interactive rebase lets you polish messy work into something clear before you share it, and the reflog means a wrong reset or a botched rebase is almost never fatal — Git remembers where you’ve been. Two reminders, though. Only rebase local, un-pushed commits, since rewriting shared history breaks everyone who built on it. And the reflog is a local safety net that expires over time (entries are pruned after about 90 days by default), so it rescues recent mistakes, not ancient ones.


Extend the Project

Try these variations to deepen the two skills. Each comes with a hint if you get stuck.

Exercise 1: Reword instead of squash

Recreate a small branch with two or three commits. This time, instead of squashing them, use interactive rebase to reword just one commit’s message — fixing a typo or making it clearer — without combining anything.

Hint

Run git rebase -i HEAD~3 and change pick to reword (or its shorthand r) on the one line you want to fix. Leave the others as pick. Save, and Git will open the message editor for just that commit, letting you rewrite it while the rest stay untouched.

Exercise 2: Drop a commit entirely

Make a branch where one commit is a genuine mistake — say, a debug print you never want in history. Use interactive rebase to remove that commit completely while keeping the others.

Hint

In the interactive rebase editor, change that commit’s line from pick to drop (or its shorthand d) — or simply delete the entire line. Save, and Git replays the remaining commits as if the dropped one never existed. (If deleting the line, make sure you remove the whole line, not just the word.)

Exercise 3: Recover from a bad rebase

Do an interactive rebase that goes wrong on purpose — for example, drop a commit you actually needed. Then use the reflog to undo the entire rebase and get your original branch back.

Hint

Run git reflog and look for the entry just before the rebase started — it’s often labelled something like rebase (start) or shows your branch tip from before. Reset back to that hash with git reset --hard <hash>, exactly as you did in Part 2. The reflog records pre-rebase state too, so a bad rebase is just as recoverable as a bad reset.


Summary

In this project you ran the full clean-up-and-recover cycle on your own. Part 1 took a messy feature branch — wip, more wip, oops fix typo — and used git rebase -i HEAD~3 with the squash action to fold three throwaway commits into a single, well-named feat: add JSON parser commit ready to share. Part 2 simulated the classic disaster: a git reset --hard HEAD~2 that appeared to destroy two good commits, followed by a rescue with git reflog — which revealed that HEAD@{1} still pointed at the lost work, letting you git reset --hard straight back to it. Polish, oops, rescue: that’s the loop you’ll run for the rest of your career.

Key Concepts

  • Interactive rebase (git rebase -i) — reshape your recent local commits; squash folds several into one.
  • Combined commit message — during a squash, Git lets you write one clear message for the merged commit.
  • git reset --hard — moves your branch pointer and discards changes, but doesn’t immediately delete the underlying commits.
  • git reflog — a local record of everywhere HEAD has been; the tool for finding and recovering “lost” commits.

Why This Matters

Messy local history and the occasional catastrophic command are facts of real development — not signs you’re doing it wrong. What separates a confident developer from an anxious one is knowing that both are fixable: you can always polish your commits before sharing, and you can almost always recover work you thought was gone. Once reflog is in your toolkit, git reset --hard and interactive rebase stop being scary and become exactly what they are — powerful, routine tools. That confidence is the real outcome of this module.


Next Steps

Continue to Module 8 - Automating with GitHub

Issues, GitHub Actions CI, branch protection, and Pages

Back to Module Overview

Return to the Rewriting and Recovering History module overview


Continue Building Your Skills

You’ve now done the complete cycle hands-on: you can take a tangle of wip commits and turn it into history a reviewer will appreciate, and you can rescue good work from an accidental reset --hard using the reflog. With deliberate rewriting and reliable recovery both under your belt, you’re ready to move beyond your own machine and let GitHub do work for you — automating checks, protecting branches, and publishing — in Module 8.