Git Undo, Recovery, and Debugging
Undo mistakes, recover lost commits, and debug history with Git.
Introduction
30 Second Summary
Everyone makes mistakes. When something breaks in a codebase, the question isn't whether you'll need to undo something, but how quickly and safely you can recover.
In this project, you will master Git's undo and recovery toolkit by building a practice repository where you'll reset commits, revert public mistakes, recover lost work, and track down bugs through history.
What You'll Build
You'll build a battle-tested git-learning-log repository that demonstrates safe undo techniques, commit recovery, selective commit application, and bug hunting through commit history.
By the end of this project, you'll have:
- A reverted commit on main showing you can safely undo public mistakes without rewriting history.
- A recovered branch created from reflog, proving you can rescue work after an accidental hard reset.
- A cherry-picked hotfix and completed bisect session demonstrating precision tools for real-world debugging and hotfix workflows.
- Secret Mission: Tag a release on your repository and push it to GitHub, bridging directly into CI/CD workflows.
Want a complete demo of how to do this project, from start to finish? Check out our 🎬 walkthrough with Maya.
Do I need to complete Parts 1-3 first?
Nope. Step 1 includes full setup instructions for all the tools you need, plus three paths for getting the repository ready depending on whether you're continuing from earlier parts or starting fresh.
Before We Start
Before We Start...
Set Up Your Tools and Repository
Before you can practice undoing, recovering, and debugging with Git, you need a working environment. That means a code editor, Git installed, and a repository to experiment with.
In this step, you will install Cursor (a code editor), verify Git, connect to GitHub, and get your git-learning-log repository ready with a starting file.
In this step, get ready to:
- Install and verify Cursor, Git, and your GitHub account.
- Clone or open the git-learning-log repository in Cursor.
- Create the starting file and push it to GitHub.
Make sure Cursor, Git, and a GitHub account are ready
You need three things before you can start: Cursor (a code editor), Git on your machine, and a GitHub account to host your repository remotely.
✔️ I already have all three
If you already have Cursor installed, can run git --version in your terminal and see a version number, have a GitHub account, and have already configured git config with your name and email, you are all set. Move on to the next substep.
ⓧ I need to set some of these up
Install Cursor
- Download and install Cursor from cursor.com/download.
- Follow the installer prompts and accept the defaults.
Install Git
Open a terminal and check if Git is already installed by running git --version. If you see a version number, skip to the GitHub section below. If not, install it for your operating system.
macOS
- Install Git via Xcode Command Line Tools by running this command:
xcode-select --install
- A popup will appear asking to install the command line developer tools. Click Install and wait for it to complete.
- Once finished, run git --version again to confirm Git is installed.
Windows
- Download the Git for Windows installer from git-scm.com/install/windows.
- Run the installer and follow the Next and Finish prompts. The default options work well.
- Open a new Command Prompt and run git --version to confirm Git is installed.
Linux
- Install Git using your package manager by running this command:
sudo apt-get install git
- Once finished, run git --version again to confirm Git is installed.
Confirm your GitHub account
- If you already have a GitHub account, you are all set.
- If not, create a free account at github.com.
Configure Git with your identity
These details attach your name and email to every commit you make. You also need to set a default editor so Git does not open vim when it needs you to type a message.
- Set your name, email, and default editor by running these commands:
git config --global user.name "[[YOUR_NAME="your name"]]"
git config --global user.email "[[YOUR_EMAIL="your email address"]]"
git config --global core.editor "nano"
Why set the editor to nano?
Some Git commands (like git revert) open a text editor for you to write a commit message. Without this setting, Git may open vim, which can be confusing for beginners.
Nano is a simpler terminal editor where you can type, save with Ctrl+O, and exit with Ctrl+X.
Make sure you include the --global flag. Without it, the setting only applies to one repository.
If you see a permissions error, try closing and reopening your terminal.
Help me fix my git config setup.
Open the git-learning-log repository in Cursor
This project uses a repository called git-learning-log. Depending on whether you have done earlier parts of this series, you may already have it locally.
✔️ I already have it locally
If you already have the git-learning-log folder on your computer, you can open it directly in Cursor.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Cursor and press Enter to open it.
- Click File in the top menu bar.
- Click Open Folder.
- Select your git-learning-log folder and click Open.
ⓧ I completed earlier parts but need to re-clone
If you completed earlier parts of this series but no longer have the folder on your machine, clone it from GitHub.
- In your terminal, move to your Desktop by running this command:
cd ~/Desktop
- Clone your existing repository by running this command:
git clone https://github.com/[[GITHUB_USERNAME="your-github-username"]]/git-learning-log.git
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Cursor and press Enter to open it.
- Click File in the top menu bar, then Open Folder.
- Select the git-learning-log folder on your Desktop and click Open.
ⓧ I'm starting fresh
If this is your first time working with this series, you need to create a new repository on GitHub and clone it.
- Go to github.com/new in your browser.
- Enter git-learning-log as the Repository name.
- Check the box next to Add a README file.
- Click Create repository.
Now clone it to your machine.
- In your terminal, move to your Desktop by running this command:
cd ~/Desktop
- Clone the repository by running this command:
git clone https://github.com/[[GITHUB_USERNAME="your-github-username"]]/git-learning-log.git
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Cursor and press Enter to open it.
- Click File in the top menu bar, then Open Folder.
- Select the git-learning-log folder on your Desktop and click Open.
Clone failing?
Make sure you replaced the username in the URL with your actual GitHub username. The repository must exist on your GitHub account before you can clone it.
If you see a permission error, GitHub may be asking you to authenticate. Follow the prompts to sign in.
Help me fix a git clone error.
Pull the latest changes and build the starting state
With Cursor open to your repository, you will now sync with GitHub and create the starting file for this project.
- Open Cursor's integrated terminal by pressing Ctrl+` (macOS and Windows).
- Make sure you are on the main branch and pull the latest changes by running these commands:
git checkout main
git pull
- Create a new file called undo-practice.md by clicking the new file icon in the Explorer sidebar, or by running:
touch undo-practice.md
What does touch do?
The touch command creates a new, empty file with the name you give it. If the file already exists, it leaves the contents unchanged.
- Open undo-practice.md in the editor and paste in the following content:
# Git Toolkit
This file documents the Git undo and recovery tools I've learned.
What is this file for?
This file will serve as your practice document throughout the project. You will add sections to it, commit them, then use Git commands to undo, revert, and recover those changes.
- Save undo-practice.md.
- Stage, commit, and push the file by running these commands:
git add undo-practice.md
git commit -m "Add undo-practice.md"
git push
What do these commands do?
- git add undo-practice.md stages the file, telling Git you want to include it in the next commit.
- git commit -m "Add undo-practice.md" saves a snapshot of the staged changes with a descriptive message.
- git push uploads your new commit to GitHub so it is backed up remotely.
- Verify the push worked by checking your repository on GitHub. You should see undo-practice.md listed in the file tree.
Push rejected or authentication error?
If git push fails with a permission error, GitHub needs you to authenticate. Follow the browser prompt that appears, or set up a personal access token if prompted for a password.
If the push is rejected because the remote has changes you do not have locally, run git pull --rebase first, then try git push again.
Help me push my code to GitHub.
✔️ Awesome, I've got everything!
Great. Double check you have saved undo-practice.md and that your push to GitHub succeeded.
ⓧ I'd like to double check the full code
Here is what your undo-practice.md file should look like:
# Git Toolkit
This file documents the Git undo and recovery tools I've learned.
Your environment is ready and your starting file is live on GitHub. Next up, you will build a commit history to practice on.
Build Your Practice History
Your repository is set up and ready to go. Now you need something to practice on.
The undo and recovery commands you'll learn in later steps need a commit history to work with. In this step, you'll build that history by adding documentation sections and committing each one separately.
In this step, get ready to:
- Add four documentation sections to your practice file, each as its own commit.
- Push all commits to GitHub and verify a clean history.
Add several commits to create a history to practice on
Each section you add will become its own commit. Having separate commits is important because the undo tools in later steps operate on individual commits.
- In Cursor, open undo-practice.md from the Explorer sidebar.
- Place your cursor at the end of the file, below the line that says "This file documents the Git undo and recovery tools I've learned."
- Add the Reset section by copying the following content:
## Reset
- git reset --soft HEAD~1: undo last commit, keep changes staged
- git reset --mixed HEAD~1: undo last commit, keep changes in working directory (default)
- git reset --hard HEAD~1: undo last commit and discard all changes (dangerous!)
- Only use reset on commits that haven't been pushed
What does this section document?
These are notes about git reset, which moves HEAD backward to undo commits. The three modes (soft, mixed, hard) differ in what they do with your changes after undoing the commit.
You'll practice reset hands-on in Step 3. For now, you're just building the commit history.
- Save undo-practice.md.
- Stage and commit this change by running:
git commit -am "Add reset section"
- You should see terminal output confirming 1 file changed with insertions.
Seeing "nothing to commit"?
The -a flag only stages files that Git is already tracking. Make sure you saved the file in Cursor before running the commit command.
If you still see the error, run git status to check whether the file shows as modified. If it does, try git add undo-practice.md first, then git commit -m "Add reset section".
Help me debug why my git commit says nothing to commit
Now repeat the same pattern for the remaining three sections. Each one gets its own commit.
- Add the Revert section below the Reset section in undo-practice.md:
## Revert
- git revert HEAD: create a new commit that undoes the last commit
- git revert is safe for shared/pushed branches because it doesn't rewrite history
- The original commit stays in the log, plus a new "undo" commit is added
- Save the file and commit by running:
git commit -am "Add revert section"
Why separate commits matter here
You could add all sections at once and commit them together. But tools like git reset, git revert, and git bisect operate on individual commits. Having multiple small commits gives you more targets to practice with.
- Add the Reflog section below the Revert section:
## Reflog
- git reflog: shows everywhere HEAD has pointed (commits, resets, checkouts)
- Reflog entries last about 90 days before being garbage collected
- To recover: find the SHA in reflog, then git branch <name> <SHA>
- Save the file and commit by running:
git commit -am "Add reflog section"
- Add the Cherry-pick section below the Reflog section:
## Cherry-pick
- git cherry-pick <SHA>: apply a specific commit to the current branch
- Creates a new commit with the same changes but a different SHA
- Use for hotfixes: fix on feature branch, cherry-pick to main
- Save the file and commit by running:
git commit -am "Add cherry-pick section"
- Push all four new commits to GitHub by running:
git push
- You should see output showing objects being pushed to origin/main.
Verify your commit history
Before you move on, confirm that your repository has the commit history you'll need for practice.
- Check your commit history by running:
git log --oneline
- You should see 5 or more commits listed. The most recent four should show your new commit messages: "Add cherry-pick section", "Add reflog section", "Add revert section", and "Add reset section".
- Confirm your repository is clean by running:
git status
- You should see nothing to commit, working tree clean. This means all your changes are committed and there are no pending modifications.
Don't see 5 commits?
If you see fewer commits, you may have combined multiple sections into a single commit. That's OK for now. Add any missing sections, commit them individually, and push again.
If git status shows uncommitted changes, save the file and run git commit -am "Add missing section" with an appropriate message.
Help me fix my commit history so I have the right number of commits
✔️ Awesome, I've got everything!
Great. Double check you've saved your file and that git status shows a clean working tree.
ⓧ I'd like to double check the full code
Here's what your complete undo-practice.md file should look like at this point:
# Git Toolkit
This file documents the Git undo and recovery tools I've learned.
## Reset
- git reset --soft HEAD~1: undo last commit, keep changes staged
- git reset --mixed HEAD~1: undo last commit, keep changes in working directory (default)
- git reset --hard HEAD~1: undo last commit and discard all changes (dangerous!)
- Only use reset on commits that haven't been pushed
## Revert
- git revert HEAD: create a new commit that undoes the last commit
- git revert is safe for shared/pushed branches because it doesn't rewrite history
- The original commit stays in the log, plus a new "undo" commit is added
## Reflog
- git reflog: shows everywhere HEAD has pointed (commits, resets, checkouts)
- Reflog entries last about 90 days before being garbage collected
- To recover: find the SHA in reflog, then git branch <name> <SHA>
## Cherry-pick
- git cherry-pick <SHA>: apply a specific commit to the current branch
- Creates a new commit with the same changes but a different SHA
- Use for hotfixes: fix on feature branch, cherry-pick to main
Your practice history is locked in. Next up, you'll use these commits to learn how to undo mistakes with git reset and git revert.
Undo a Bad Commit with Reset and Revert
Your repository now has a solid commit history to practice on. In this step, you'll learn the two essential undo commands that every developer reaches for when something goes wrong.
The key decision is whether your bad commit is still local (only on your machine) or has already been pushed to a shared branch. Git gives you different tools for each scenario: git reset rewrites local history, and git revert safely undoes public history by adding a new commit.
In this step, get ready to:
- Undo a local commit with git reset --soft and clean up the working directory.
- Undo a pushed commit safely with git revert while preserving history.
- Verify the difference between reset and revert in your commit log.
Make a bad commit and undo it locally with git reset
Sometimes you commit something you didn't mean to. If you haven't pushed yet, you can erase that commit from history entirely using git reset.
- In Cursor, open undo-practice.md.
- At the end of the ## Reset section (after the last bullet point), add this new line:
- WRONG: always use --force to push
- Save the file.
This is intentionally bad advice. You're creating it on purpose so you can practice undoing it.
- Commit the bad change by running this command in your terminal:
git commit -am "Add dangerous advice"
What does the -am flag do?
The -a flag automatically stages all modified tracked files. The -m flag lets you write the commit message inline. Combined as -am, it's a shortcut that skips the separate git add step for files Git already tracks.
You now have a bad commit in your local history. Since you haven't pushed it, you can safely erase it.
- Undo the commit (but keep the changes staged) by running:
git reset --soft HEAD~1
What does this command do?
git reset --soft HEAD~1 moves HEAD back by one commit. The --soft flag means your file changes stay staged, ready to be re-committed or modified.
HEAD~1 means "one commit before the current HEAD." The commit is gone from history, but your work is preserved.
- Verify the commit is gone by running:
git status
You should see undo-practice.md listed as a staged change (under "Changes to be committed").
- Confirm the commit is no longer in the log by running:
git log --oneline
The "Add dangerous advice" commit should be completely gone from the history.
The bad line is still in your file though. Time to remove it and clean up.
- In Cursor, open undo-practice.md and delete the line - WRONG: always use --force to push.
- Save the file.
- Unstage the file by running:
git reset HEAD undo-practice.md
- Confirm everything is clean by running:
git status
You should see undo-practice.md listed as a modified unstaged change. This is because you edited the file in Cursor to remove the bad line, so it no longer matches the last committed version.
- Discard the working directory changes and return the file to its last committed state by running:
git checkout -- undo-practice.md
What does this command do?
git checkout -- undo-practice.md replaces the file in your working directory with the version from the last commit. Any unsaved edits to that file are permanently discarded.
The -- separates the command from the filename. Without it, Git might confuse a filename for a branch name.
If you check undo-practice.md in Cursor, the - WRONG: always use --force to push line is gone. Git replaced the file with the version from the last commit, which never had that line.
- Run git status one more time to confirm everything is clean.
git status
You should now see "nothing to commit, working tree clean."
Still seeing the bad commit in your log?
Make sure you ran git reset --soft HEAD~1 (not HEAD~2 or another number). The ~1 means exactly one commit back.
If you accidentally reset more than one commit, check git reflog to find where you were and git reset back to that SHA.
help me recover from a git reset that went too far
Simulate a pushed bad commit and undo with git revert
What happens when the bad commit has already been pushed to GitHub? You can't rewrite shared history without causing problems for everyone else working on the repository. Instead, you use git revert to create a new commit that undoes the damage.
- In Cursor, open undo-practice.md and add this line at the end of the ## Revert section (after the last bullet):
- WRONG: always rebase shared branches to keep history clean
- Save the file.
- Commit the bad change by running:
git commit -am "Add incorrect workflow note"
- Push the commit to GitHub by running:
git push
The bad commit is now public. Anyone pulling from this repository would get that incorrect advice. You need to undo it without rewriting the shared history.
- Revert the last commit by running:
git revert HEAD
What does git revert HEAD do?
git revert HEAD creates a brand new commit that does the exact opposite of whatever HEAD (the most recent commit) did. If the last commit added a line, the revert commit removes it.
The original bad commit stays in history (because it was already pushed), but the new revert commit cancels out its effect. This is the safe way to undo public work.
💡 What should I see in my editor?
After running git revert HEAD, the nano editor opens with a default commit message starting with "Revert" followed by the original commit's message. You don't need to change anything.
- Accept the default revert message by pressing Ctrl+O to save, then Enter to confirm the filename, then Ctrl+X to exit nano.
- Push the revert commit to GitHub by running:
What are these nano shortcuts?
Nano is a terminal text editor that uses Ctrl shortcuts instead of the usual Cmd+S or File > Save pattern.
- Ctrl+O — "Write Out" (nano's save command). It writes the file to disk.
- Enter — Confirms the filename nano suggests (the default commit message file).
- Ctrl+X — Exits nano and returns you to the terminal.
git push
Your repository now has both the bad commit and the revert commit in its history, but the file content is back to normal.
Stuck in the nano editor?
If you see text in your terminal and can't get back to the command line, press Ctrl+X to exit. If it asks whether to save, press Y then Enter.
If you're in vim instead of nano (you see colored text with tildes ~ on empty lines), type :wq and press Enter to save and quit. Then set nano as your default editor with git config --global core.editor "nano".
help me exit the text editor that git opened
Verify and understand the difference
- View your recent commit history by running:
git log --oneline -5
You should see both the "Add incorrect workflow note" commit AND the revert commit in the output. The original bad commit is preserved in history, with the revert commit sitting on top of it.
When do I use reset vs revert?
git reset rewrites history by removing commits entirely. Only use it on local commits you haven't pushed yet.
git revert adds new history by creating an "undo" commit. Use it on commits that are already shared or pushed. This keeps the timeline intact for everyone on the team.
- Confirm your repository is in a clean state by running:
git status
You should see "nothing to commit, working tree clean."
You now know how to handle both local and public mistakes. Next up, you'll learn that even commits you think are permanently deleted can be recovered.
Recover Lost Commits with Reflog
In the last step, you learned the difference between reset (rewrites history, local only) and revert (adds new history, safe for shared branches). But what happens when you use git reset --hard and your work disappears completely?
This is where Git has a hidden safety net. The reflog records every move HEAD makes in your local repository. Even commits that seem "gone" after a hard reset still exist in Git's object database. In this step, you'll prove it by deliberately destroying a commit and then bringing it back from the dead.
In this step, get ready to:
- Simulate losing work with a hard reset.
- Use git reflog to find the lost commit's SHA.
- Recover the commit by creating a new branch and merging it back.
Simulate losing work with a hard reset
To practice recovering lost commits, you first need to create something valuable and then destroy it. You will add a new Bisect section to undo-practice.md, commit it, and then wipe it out with a hard reset.
- Open undo-practice.md in Cursor.
- Add the following Bisect section at the very bottom of the file, after the Cherry-pick section:
## Bisect
- git bisect start: begin a binary search session
- git bisect bad: mark current commit as containing the bug
- git bisect good <ref>: mark a known-good commit
- Git checks out middle commits; you test and mark good/bad
- git bisect reset: end the session and return to original HEAD
What does this section document?
This Bisect section records the key commands for Git's binary search debugging tool. You will actually use these commands hands-on in the next step. For now, this content serves as the "valuable work" you will lose and then recover.
- Save undo-practice.md.
- Commit the new section by running this command in your terminal:
git commit -am "Add bisect section with tips"
- Verify the commit exists by running:
git log --oneline -3
You should see Add bisect section with tips as the most recent commit at the top of the log.
Don't see the commit?
If the commit doesn't appear, make sure you saved the file before running git commit -am. The -a flag only stages changes to tracked files that have been saved to disk.
Help me figure out why my commit didn't work.
Now for the dramatic part. You will "accidentally" destroy this commit with a hard reset.
- Wipe out the last commit by running:
git reset --hard HEAD~1
What does this command do?
This is the most destructive form of reset. It moves HEAD back one commit, updates the staging area, AND overwrites your working directory. The Bisect section you just added is now gone from the file on disk.
- Confirm the commit is gone by running:
git log --oneline -3
The "Add bisect section with tips" commit is no longer in the log. Open undo-practice.md in Cursor and confirm that the Bisect section has vanished from the file.
Still see the Bisect section in the file?
Cursor may be showing a cached version. Click away from the file tab and click back, or close and reopen the file. If the section is truly still there, the hard reset may not have run correctly. Check git status to confirm your working tree is clean.
Help me verify my hard reset worked correctly.
Use git reflog to find the lost commit
The commit is gone from git log, but it is not gone from Git. The reflog tracks every time HEAD moves, including resets. Your "lost" commit still exists in Git's object database.
- View the reflog by running:
git reflog
What does this output show?
The reflog shows a list of every position HEAD has been in. Each entry has a SHA hash, an index (like HEAD@{1}), and a description of the action that moved HEAD there.
Look for the entry that says commit: Add bisect section with tips. It should be the second entry (HEAD@{1}), right below the reset entry at the top. The SHA at the beginning of that line is the key to recovery.
The reflog opens in a scrollable pager view. Press q when you're done reading to return to the terminal prompt.
- Find the entry that says commit: Add bisect section with tips.
- Note the 7-character SHA hash at the beginning of that line. You will use it in the next substep. Enter your SHA here: REFLOG_SHA
Why does this work?
Anything committed in Git can almost always be recovered. The reflog is local only, meaning it records what happened in your repository and won't appear on anyone else's clone. Reflog entries last about 90 days before Git's garbage collector removes them.
Recover the commit by creating a new branch
You have the SHA of the lost commit. The recovery technique is to create a new branch pointing at that SHA, then merge it back into main.
- Create a recovery branch pointing at the lost commit by running:
git branch recover-bisect-section [[REFLOG_SHA="the SHA from the reflog entry"]]
What does this command do?
This creates a new branch called recover-bisect-section that points directly at the commit you thought was lost. The commit's content (your Bisect section) is now reachable again through this branch.
- Merge the recovery branch into main by running:
git merge recover-bisect-section
- Open undo-practice.md in Cursor and confirm the Bisect section is back at the bottom of the file.
Seeing a merge conflict?
If git reports a conflict, it means main has changes to the same part of the file. Since you only added content at the bottom, this is unlikely. Make sure you are on the main branch (run git branch to check) and that you used the correct SHA from the reflog.
Help me resolve this merge conflict after recovering my commit.
- Delete the recovery branch now that it has been merged by running:
git branch -d recover-bisect-section
- Push the recovered work to GitHub by running:
git push
You should see Git push successfully with no errors.
Push rejected?
If the push is rejected, run git pull --rebase first to sync with any remote changes, then try git push again.
Help me figure out why my push was rejected.
✔️ Awesome, I've got everything!
Great. Double check you have saved undo-practice.md and that the Bisect section is visible at the bottom of the file.
ⓧ I'd like to double check the full code
Here is what your complete undo-practice.md should look like at this point:
# Git Toolkit
This file documents the Git undo and recovery tools I've learned.
## Reset
- git reset --soft HEAD~1: undo last commit, keep changes staged
- git reset --mixed HEAD~1: undo last commit, keep changes in working directory (default)
- git reset --hard HEAD~1: undo last commit and discard all changes (dangerous!)
- Only use reset on commits that haven't been pushed
## Revert
- git revert HEAD: create a new commit that undoes the last commit
- git revert is safe for shared/pushed branches because it doesn't rewrite history
- The original commit stays in the log, plus a new "undo" commit is added
## Reflog
- git reflog: shows everywhere HEAD has pointed (commits, resets, checkouts)
- Reflog entries last about 90 days before being garbage collected
- To recover: find the SHA in reflog, then git branch <name> <SHA>
## Cherry-pick
- git cherry-pick <SHA>: apply a specific commit to the current branch
- Creates a new commit with the same changes but a different SHA
- Use for hotfixes: fix on feature branch, cherry-pick to main
## Bisect
- git bisect start: begin a binary search session
- git bisect bad: mark current commit as containing the bug
- git bisect good <ref>: mark a known-good commit
- Git checks out middle commits; you test and mark good/bad
- git bisect reset: end the session and return to original HEAD
You just proved that nothing committed in Git is truly lost. Next up, you will use cherry-pick to surgically move a single commit from one branch to another, simulating how production hotfixes work.
Cherry-Pick a Hotfix Across Branches
In real projects, urgent fixes often live alongside unfinished feature work on the same branch. You need a way to grab just the fix and apply it to main without pulling in everything else.
That's what git cherry-pick does. It applies the changes from a single commit onto your current branch, creating a brand new commit with the same diff. In this step, you'll simulate a production hotfix workflow from start to finish.
In this step, get ready to:
- Create a feature branch with a mix of feature work and a bug fix.
- Cherry-pick only the fix commit onto main.
- Verify that main has the fix without the unfinished feature work.
Create a feature branch and simulate a bug fix among other work
You'll create a feature branch with three commits. One of them is a critical typo fix. The other two are unfinished feature work that isn't ready for main yet.
- Create and switch to a new feature branch by running this command:
git checkout -b feature/new-docs
- Open undo-practice.md in Cursor.
- Add the following line at the very end of the file (after the Bisect section):
## Collaboration
- Use branches to work in parallel without stepping on each other's changes
- Pull requests let teammates review your work before merging
- Save the file and commit this change by running:
git commit -am "Add collaboration section"
That's the first piece of feature work. Now you'll simulate finding and fixing a typo in an existing section.
- In undo-practice.md, scroll up to the ## Reset section.
- Find this line:
- Only use reset on commits that haven't been pushed
- Change it to add "safely" at the beginning, so it reads:
- Only safely use reset on commits that haven't been pushed
- Save the file and commit this fix by running:
git commit -am "Fix typo in reset description"
This is the commit you'll cherry-pick later. Now add one more piece of unfinished feature work.
- Add the following at the very end of undo-practice.md (after the Collaboration section):
## Advanced Tips
- Use git stash to temporarily shelve changes
- Combine interactive rebase with cherry-pick for complex workflows
- Save and commit this change by running:
git commit -am "Add advanced tips"
What does your feature branch look like now?
Your feature/new-docs branch has three commits mixed together. Commit 1 and 3 are unfinished feature work. Commit 2 is a fix that main needs right now.
In the real world, this happens constantly. You're working on a feature, notice a bug, fix it, and keep going. The fix can't wait for the whole feature to be done.
Cherry-pick only the fix onto main
Now you'll switch to main and apply only the fix commit, leaving the feature work behind.
- Switch back to main by running:
git checkout main
- Find the SHA hash of the fix commit by running:
git log --oneline feature/new-docs
What should I see?
You'll see three commits listed. Look for the one with the message "Fix typo in reset description" and note the short SHA hash at the beginning of that line (something like abc1234).
- Cherry-pick the fix commit onto main by running this command, replacing the SHA with the one you noted:
git cherry-pick [[CHERRY_PICK_SHA="the SHA hash of your 'Fix typo in reset description' commit"]]
What does git cherry-pick do?
Git cherry-pick applies the changes introduced by an existing commit and creates a brand new commit on your current branch. The new commit has the same diff but a different SHA.
It's a surgical tool. Instead of merging an entire branch (which would bring all three commits), you picked exactly one commit to apply.
- Verify the cherry-pick worked by running:
git log --oneline -3
You should see the "Fix typo in reset description" commit at the top of main's history. It has a new SHA that's different from the one on the feature branch.
- Push the cherry-picked fix to GitHub by running:
git push
Cherry-pick failed with a conflict?
If you see a merge conflict message, the same lines were changed differently on main. Open the file, look for the conflict markers (<<<<<<< and >>>>>>>), keep the version you want, then save and run:
git add undo-practice.md
git cherry-pick --continue
If you'd rather start over, run git cherry-pick --abort to cancel the cherry-pick and try again.
Help me resolve my cherry-pick conflict
Understand when to cherry-pick
- Confirm the feature branch is untouched by running:
git log --oneline feature/new-docs
The feature branch still has all three commits intact. Nothing was removed or altered.
When should I use cherry-pick?
Cherry-pick is for production hotfixes. A developer notices a bug while working on a feature branch, fixes it there, and the team cherry-picks just the fix to main immediately.
The feature branch keeps all three commits and continues development separately. Main gets only the urgent fix without any unfinished work. This is exactly how teams ship hotfixes without waiting for an entire feature to be complete.
You just performed a surgical commit transplant. Next up, you'll use another precision tool to hunt down the exact commit that introduced a bug.
Find a Bug with Git Bisect
In the last step, you cherry-picked a single fix commit from one branch to another. That precision works great when you know exactly which commit contains the fix.
But what about the opposite situation? Something is broken, and you have no idea which commit introduced the problem. Scrolling through dozens of commits manually would take forever. In this step, you will use git bisect to run a binary search through your commit history and pinpoint the exact commit that introduced a bug.
In this step, get ready to:
- Build a practice branch with a hidden bug buried in a series of commits.
- Use git bisect to binary-search through history and identify the exact bad commit.
- Inspect, fix, and clean up after finding the culprit.
Build a series of commits with a hidden bug
To practice bisect, you need a commit history where one commit quietly introduces a problem. You will create a configuration file and build it up across six commits. In one of those commits, you will secretly change a port number from the correct value to a wrong one.
- Create and switch to a new practice branch by running this command:
git checkout -b bisect-practice
- Create a new file called app-config.md by running:
touch app-config.md
- Open app-config.md in the editor and paste in the following content:
# App Configuration
## Server
- port: 3000
- Save the file, then stage and commit it by running:
git add app-config.md
git commit -m "Add app config with server port"
What does this first commit establish?
This creates app-config.md with a correct server port of 3000. Later, you will sneak in a change to this value. Since you know the first commit has the correct port, you can mark it as "good" during bisect.
Now you will add five more commits. Each commit adds a new section to the file. Commit 4 is special because it secretly introduces the bug alongside its normal addition.
- Add the following line to the bottom of app-config.md, below - port: 3000:
- host: localhost
- Save, then commit:
git add app-config.md
git commit -m "Add server host config"
- Add the following section to the bottom of app-config.md (leave a blank line after - host: localhost):
## Database
- name: myapp_db
- port: 5432
- Save, then commit:
git add app-config.md
git commit -m "Add database config"
Now for commit 4. This is where you deliberately introduce the bug. You will add a new section AND change the server port from 3000 to 300 (removing a zero).
- Add the following section to the bottom of app-config.md (leave a blank line after the Database section):
## Logging
- level: info
- format: json
- In the same file, scroll up to the ## Server section and change port: 3000 to port: 300.
- Save, then commit with a generic message that hides the bug:
git add app-config.md
git commit -m "Add logging config"
Why hide the bug in this commit?
In real projects, bugs rarely announce themselves. A developer might accidentally change a value while working on something else entirely. The commit message says "Add logging config" but the real damage is the port change buried in the same diff.
This is exactly the scenario bisect is designed for. You can not tell from the commit messages alone which one broke things.
- Add the following section to the bottom of app-config.md (leave a blank line after the Logging section):
## Cache
- enabled: true
- ttl: 3600
- Save, then commit:
git add app-config.md
git commit -m "Add cache config"
- Add the final section to the bottom of app-config.md (leave a blank line after the Cache section):
## Auth
- provider: github
- timeout: 30
- Save, then commit:
git add app-config.md
git commit -m "Add auth config"
✔️ Awesome, I've got everything!
Great. Double check you have saved app-config.md after the last commit.
ⓧ I'd like to double check the full code
Your app-config.md should look exactly like this:
# App Configuration
## Server
- port: 300
- host: localhost
## Database
- name: myapp_db
- port: 5432
## Logging
- level: info
- format: json
## Cache
- enabled: true
- ttl: 3600
## Auth
- provider: github
- timeout: 30
- Verify you have six commits on this branch by running:
git log --oneline
You should see six commits starting with "Add app config with server port" at the bottom and "Add auth config" at the top.
Don't see six commits?
Make sure you ran git add app-config.md and git commit after each file change. If you see fewer commits, add the missing sections and commit each one separately.
help me fix my commit history for the bisect practice
Use git bisect to find the bad commit
Your branch now has six commits and something is broken. Before reaching for any tools, let's first observe the problem by inspecting the file at different points in history.
- See what app-config.md looks like right now (at HEAD) by running:
git show HEAD:app-config.md
In the Server section, you should see port: 300. That's the bug.
- Now see what the file looked like at the very first commit (five commits ago) by running:
git show HEAD~5:app-config.md
This time you should see port: 3000. The file was correct five commits ago.
What is git show?
The git show <commit>:<filepath> command prints a file's contents at any point in history without switching branches or checking out commits. It's a quick way to peek at how things looked before.
You could check each commit one by one (HEAD~4, HEAD~3, HEAD~2...) but with a longer history that's painfully slow. Git bisect automates this with a binary search between a known-good commit and a known-bad commit.
- Start the bisect session by running:
git bisect start
- Mark the current commit (HEAD) as bad, since it contains the bug:
git bisect bad
- Find the SHA of the first commit on this branch by running:
git log --oneline
- Look for the commit at the bottom of the list with the message "Add app config with server port".
- Copy its SHA (the short hex code on the left).
- Mark that commit as good by running:
git bisect good [[FIRST_COMMIT_SHA="SHA of your first commit"]]
What happens when you mark good and bad?
Git now knows the bug was introduced somewhere between your "good" commit and your "bad" commit. It checks out a commit roughly in the middle so you can test it.
Your terminal shows which commit Git checked out and how many revisions remain to test. Each time you mark a commit, Git cuts the remaining range in half.
Git has now checked out a middle commit for you to test. Your job is to check whether the port is correct or wrong in the current file state.
- Open app-config.md in Cursor and look at the ## Server section.
- Check if the port value is 3000 (correct) or 300 (the bug).
- If the port is 3000 (correct), mark this commit as good:
git bisect good
- If the port is 300 (the bug), mark this commit as bad:
git bisect bad
Git checks out another commit for you. Repeat the process: open the file, check the port, and mark the commit as good or bad.
- Keep marking commits as git bisect good or git bisect bad until Git identifies the first bad commit.
After a few rounds, Git outputs a message identifying the first bad commit. It shows the commit SHA and the commit message.
What should I see?
Git displays a line containing the SHA followed by "is the first bad commit" along with the full commit details. The identified commit should be the one with the message "Add logging config" because that is where you changed the port from 3000 to 300.
If bisect points to a different commit, you may have marked a commit incorrectly during the process. Run git bisect reset to exit, then start over with git bisect start. Be sure to check the actual port value in the file each time before marking.
help me understand why git bisect found the wrong commit
Fix the bug and clean up
In a real project, finding the bad commit is only half the job. Now you need to inspect the damage and fix it.
- End the bisect session by running:
git bisect reset
What does bisect reset do?
During a bisect session, Git checks out various commits and puts you in a detached HEAD state. Running git bisect reset returns HEAD to where it was before you started the bisect. In this case, it puts you back on the tip of bisect-practice.
Now inspect what that bad commit actually changed.
- Copy the SHA from the bisect output (the long hex string on the first line, or just the first 7 characters).
- View the full diff of the commit bisect identified by running:
git show [[BAD_COMMIT_SHA="SHA that bisect identified"]]
The diff shows two changes mixed together: the logging section was added (that's fine), but the port was also changed from 3000 to 300 (that's the bug).
Why not just git revert here?
Reverting this commit would undo the logging section too, since both changes live in the same commit. This is exactly why atomic commits (one logical change per commit) matter. For now, the simplest fix is to manually correct the port.
- Find this line in the ## Server section:
- port: 300
- Change 300 back to 3000, so the line looks like:
- port: 3000
- Save the file and commit the fix by running:
git commit -am "Fix incorrect server port"
- Verify the port is fixed by running:
git show HEAD:app-config.md
The Server section should now show port: 3000.
- Switch back to main by running:
git checkout main
- Delete the practice branch by running:
git branch -D bisect-practice
- Confirm you are back on main with a clean state by running:
git status
You should see "On branch main" with nothing to commit.
Why delete the practice branch?
The bisect-practice branch was purely for learning. The -D flag (uppercase D) force-deletes a branch even if it has not been merged. In real projects, you would use bisect on a real branch and fix the bug rather than deleting the branch.
If you see an error about being on the branch you are trying to delete, make sure you ran git checkout main first. You cannot delete the branch you are currently on.
help me clean up my bisect practice branch
You just used binary search to find a needle in a haystack of commits. Next up, you will tackle the Secret Mission and learn how to mark release points with Git tags.
Secret mission
Tag a Release
You've mastered undoing, recovering, and debugging. Now mark your repository with an official release tag and push it to GitHub. Tags are what CI/CD pipelines watch for to trigger deployments, making this the bridge between Git fundamentals and automation.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally and uses a free GitHub repository, so there are no ongoing costs. Decide whether to keep your resources available, or delete them entirely.
Resources you used:
- Local git-learning-log folder on your Desktop (no cost).
- GitHub git-learning-log repository with additional commits, tags, and history (free).
Keep everything running
No action needed. Choose this if you plan to continue building on this repository or want to reference your Git history later.
- Your local git-learning-log folder stays on your Desktop at no cost.
- Your GitHub repository and tag v1.0 remain available for free.
- Cursor and Git remain installed on your machine for future projects.
Pause - I'll come back to this later
There are no running processes to pause. Everything in this project is stored as files on your machine and commits on GitHub.
- Your repository will be exactly where you left it whenever you come back.
- No action required.
Delete - I don't want to use this again
Remove all project resources and start fresh. This permanently deletes your local files and GitHub repository.
- Delete the local folder by running this command in your terminal:
rm -rf ~/Desktop/git-learning-log
- To delete the GitHub repository:
- Go to your git-learning-log repository on GitHub.
- Click Settings in the top navigation bar.
- Scroll down to the Danger Zone section.
- Click Delete this repository and follow the confirmation prompts.
That's a Wrap!
That's a Wrap!
Nice work! You just graduated from "can use Git" to "can recover from anything Git throws at you."
You've learned how to:
- Used git reset to undo local commits without leaving a trace, and git revert to safely undo public commits while preserving history.
- Used git reflog to find and recover commits that appeared permanently lost after a hard reset.
- Used git cherry-pick to surgically apply a single commit from one branch to another, and git bisect to binary-search through commit history and identify the exact commit that introduced a bug.
- Created annotated tags to mark releases, pushed them to GitHub, and managed them as the foundation for CI/CD triggers.
Ready to quiz yourself?