In the previous tutorial, we learned to work with GitHub and set up CI/CD. Now it is time for the advanced tools.
Every developer makes mistakes. You commit the wrong file. You break something and need to find out which commit caused it. You need to grab a single commit from another branch.
Git has tools for all of this. In this tutorial, you will learn to undo mistakes, recover lost work, and use power features that save hours of debugging.
Undoing Changes in Your Working Directory
You edited a file and want to throw away the changes. The file is not staged yet.
git restore hello.txt
This replaces the file with the version from your last commit. Your changes are gone. Be careful — there is no way to get them back.
To undo changes in all tracked files:
git restore .
Note: In older Git versions, this was git checkout -- hello.txt. The git restore command was added in Git 2.23 and is clearer about what it does.
Unstaging Files
You staged a file with git add but changed your mind. You want to unstage it without losing your changes.
git restore --staged hello.txt
The file goes back to “modified but not staged.” Your changes are still there.
To unstage everything:
git restore --staged .
git reset — Undoing Commits
git reset moves the branch pointer backwards. It has three modes, and the mode decides what happens to your changes.
Soft Reset
git reset --soft HEAD~1
This undoes the last commit. Your changes stay staged (ready to commit again).
Before: A --- B --- C (HEAD)
After: A --- B (HEAD) [C's changes are staged]
Use this when you want to change your commit message or add more files to the commit.
Mixed Reset (Default)
git reset HEAD~1
This undoes the last commit. Your changes stay in the working directory but are not staged.
Before: A --- B --- C (HEAD)
After: A --- B (HEAD) [C's changes are unstaged]
This is the default. If you type git reset without a flag, this is what happens.
Hard Reset
git reset --hard HEAD~1
This undoes the last commit and deletes your changes. Everything is gone.
Before: A --- B --- C (HEAD)
After: A --- B (HEAD) [C's changes are deleted]
Use this only when you are sure you do not need the changes. There is no undo for a hard reset (unless you use git reflog — see below).
Reset Multiple Commits
git reset --soft HEAD~3
This undoes the last 3 commits. All their changes become staged.
Summary Table
| Command | Commit | Staging Area | Working Directory |
|---|---|---|---|
--soft | Undone | Keeps changes | Keeps changes |
--mixed (default) | Undone | Clears staging | Keeps changes |
--hard | Undone | Clears staging | Deletes changes |
git revert — Safely Undoing a Commit
git reset rewrites history. If you already pushed the commit, resetting causes problems for others. Use git revert instead.
git revert a1b2c3d
[main e5f6g7h] Revert "Add broken feature"
1 file changed, 0 insertions(+), 5 deletions(-)
This creates a new commit that undoes the changes from the specified commit. The original commit stays in the history.
Before: A --- B --- C (broken)
After: A --- B --- C --- D (D reverts C)
This is safe for shared branches. Everyone gets the revert as a normal commit.
To revert without auto-committing (so you can review first):
git revert --no-commit a1b2c3d
Reset vs Revert
git reset | git revert | |
|---|---|---|
| What it does | Removes commits from history | Creates a new commit that undoes changes |
| Changes history? | Yes | No |
| Safe for pushed commits? | No | Yes |
| Use when | Undoing local, unpushed commits | Undoing pushed commits |
git reflog — Recovering Lost Commits
Deleted a branch by accident? Did a hard reset and regret it? git reflog is your safety net.
The reflog records every time HEAD moves. Even after a hard reset, the old commits are still there — they are just not attached to any branch.
git reflog
e5f6g7h (HEAD -> main) HEAD@{0}: revert: Revert "Add broken feature"
c3d4e5f HEAD@{1}: commit: Add broken feature
b2c3d4e HEAD@{2}: commit: Update greeting
a1b2c3d HEAD@{3}: commit: Add hello.txt
d4e5f6g HEAD@{4}: checkout: moving from feature/login to main
f6g7h8i HEAD@{5}: commit: Add login page
Every entry shows what happened and the commit hash. To recover a lost commit:
# Create a branch pointing to the lost commit
git branch recovered f6g7h8i
Or go back to that point:
git reset --hard HEAD@{5}
The reflog keeps entries for about 90 days by default. After that, Git garbage-collects them.
Recovering a Deleted Branch
# Oops, deleted the branch
git branch -D feature/important
# Find the commit in the reflog
git reflog | grep "feature/important"
# Recreate the branch
git branch feature/important f6g7h8i
git cherry-pick — Apply Specific Commits
Cherry-pick takes a single commit from one branch and applies it to another. This is useful when you need one specific change without merging an entire branch.
git switch main
git cherry-pick a1b2c3d
[main h8i9j0k] Fix login validation
1 file changed, 3 insertions(+)
This creates a new commit on main with the same changes as commit a1b2c3d.
When to Use Cherry-Pick
- A bug fix on a feature branch needs to go to
mainimmediately - You committed to the wrong branch and want to move the commit
- You need a specific change from a branch that is not ready to merge
Cherry-Pick Multiple Commits
git cherry-pick a1b2c3d b2c3d4e c3d4e5f
Or a range:
git cherry-pick a1b2c3d..c3d4e5f
Note: the range excludes the first commit. It picks everything after a1b2c3d up to and including c3d4e5f.
git bisect — Find the Bug
Your code worked last week but is broken now. You have 50 commits in between. Which one broke it?
git bisect uses binary search to find the bad commit. Instead of checking all 50 commits, you only check about 6 (log2 of 50).
# Start bisecting
git bisect start
# Tell Git the current commit is bad
git bisect bad
# Tell Git a commit from last week was good
git bisect good a1b2c3d
Bisecting: 25 revisions left to test after this (roughly 5 steps)
[d4e5f6g] Add user search feature
Git checks out a commit in the middle. Test your code:
# If this commit works
git bisect good
# If this commit is broken
git bisect bad
Git narrows down the range each time. After a few steps:
c3d4e5f is the first bad commit
commit c3d4e5f
Author: Sam <sam@example.com>
Date: Wed May 28 14:30:00 2026
Update database query
Now you know exactly which commit introduced the bug.
When you are done:
git bisect reset
This returns you to where you were before bisecting.
git blame — Who Changed What
git blame shows who last modified each line of a file:
git blame hello.txt
a1b2c3d (Alex 2026-05-25 09:00:00 +0000 1) Hello, Git!
b2c3d4e (Sam 2026-05-26 14:00:00 +0000 2) This is my first Git project.
c3d4e5f (Alex 2026-05-27 10:00:00 +0000 3) Welcome to the team!
Each line shows:
- The commit hash
- The author
- The date
- The line content
This is not for blaming people — it is for understanding code history. “Why was this line added? Let me check the commit.”
Show a specific range of lines:
git blame -L 10,20 hello.txt
This shows blame for lines 10 through 20 only.
git tag — Marking Releases
Tags mark important points in your history. They are commonly used for releases.
Lightweight Tags
git tag v1.0.0
This tags the current commit.
Annotated Tags (Recommended)
git tag -a v1.0.0 -m "First stable release"
Annotated tags store the tagger’s name, date, and a message. They are better for releases.
Tag a Previous Commit
git tag -a v0.9.0 -m "Beta release" a1b2c3d
List Tags
git tag
v0.9.0
v1.0.0
Push Tags to GitHub
Tags are not pushed by default. You need to push them explicitly:
# Push a specific tag
git push origin v1.0.0
# Push all tags
git push origin --tags
Delete a Tag
# Delete locally
git tag -d v0.9.0
# Delete on remote
git push origin --delete v0.9.0
Git Aliases
Tired of typing long commands? Create aliases.
git config --global alias.st status
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.cm "commit -m"
git config --global alias.lg "log --oneline --graph --all"
Now you can type:
git st # instead of git status
git co main # instead of git checkout main
git br # instead of git branch
git cm "Fix bug" # instead of git commit -m "Fix bug"
git lg # instead of git log --oneline --graph --all
.gitconfig Tips
Your Git configuration lives in ~/.gitconfig. Here are some useful settings:
[user]
name = Alex
email = alex@example.com
[init]
defaultBranch = main
[alias]
st = status
co = checkout
br = branch
cm = commit -m
lg = log --oneline --graph --all
last = log -1 HEAD
unstage = restore --staged
[pull]
rebase = true
[core]
editor = vim
autocrlf = input
The [pull] rebase = true setting makes git pull use rebase instead of merge by default. This keeps your history cleaner.
The autocrlf = input setting prevents line ending problems between Windows and macOS/Linux.
Quick Reference: Undo Commands
Here is a summary of when to use each undo command:
| Situation | Command |
|---|---|
| Discard changes in a file (not staged) | git restore <file> |
| Unstage a file (keep changes) | git restore --staged <file> |
| Undo last commit (keep changes staged) | git reset --soft HEAD~1 |
| Undo last commit (keep changes unstaged) | git reset HEAD~1 |
| Undo last commit (delete changes) | git reset --hard HEAD~1 |
| Undo a pushed commit (safe) | git revert <commit> |
| Recover a lost commit | git reflog then git branch recovered <hash> |
Common Mistakes
Using
git reset --hardwithout thinking — Hard reset deletes your changes permanently. Always checkgit statusandgit diffbefore resetting. If you do reset by accident,git reflogcan often save you — but act quickly before Git garbage-collects.Resetting pushed commits — If you reset a commit that is already on GitHub, your local history diverges from the remote. You would need a force push, which overwrites remote history. Use
git revertfor pushed commits instead.Cherry-picking instead of merging — Cherry-pick is for one-off commits. If you find yourself cherry-picking many commits from the same branch, you should merge or rebase that branch instead. Cherry-picking creates duplicate commits with different hashes.
What’s Next?
Congratulations! You have completed the Git sub-series. You now know:
- Git basics (init, add, commit, status, log, diff)
- Branching (create, switch, delete)
- Merging and rebasing (merge, rebase, stash, conflicts)
- GitHub workflow (push, pull, PRs, CI/CD)
- Advanced features (reset, revert, reflog, cherry-pick, bisect, blame, tags, aliases)
These skills cover everything you need for daily work with Git. Keep the Git Commands Cheat Sheet bookmarked for quick reference.
Stay tuned for the Docker tutorials coming next, where we will learn to package and deploy applications with containers.
This is part 5 of the DevTools Tutorial series.