1. Stashing Work in Progress
You're mid-edit and need to switch branches urgently — maybe to fix an
emergency bug — but your changes aren't ready to commit. git stash
shelves your uncommitted changes and restores a clean working directory, so you
can switch freely:
git stash push -m "wip: pricing table redesign"
# Saved working directory and index state On feature/pricing: wip: pricing table redesign
git switch main
# ...fix the emergency bug, commit, push...
git switch feature/pricing
git stash list
# stash@{0}: On feature/pricing: wip: pricing table redesign
git stash pop # reapplies the stash AND removes it from the stash list
# git stash apply reapplies without removing it, if you want to keep the stash around
A stash can conflict on pop/apply just like a merge can — resolve it the same way, by hand, then stage the result.
2. Cherry-Picking & Tagging Releases
git cherry-pick <commit> applies one specific commit from
anywhere in history onto your current branch — useful for pulling a hotfix into
a release branch without merging everything else that's landed since:
git log --oneline main
# a1b2c3d Fix critical checkout bug
# 9f8e7d6 Add new dashboard widget
# ...
git switch release/v2.1
git cherry-pick a1b2c3d
# Applies ONLY the checkout fix onto release/v2.1, as a new commit
Tags mark a specific commit as significant — almost always a release — with a name that doesn't move as new commits land, unlike a branch:
git tag -a v2.1.0 -m "Release 2.1.0"
git push origin v2.1.0
git tag # list all tags
git checkout v2.1.0 # inspect the repo exactly as it was at that release
3. Rewriting History: amend & Interactive Rebase
Made a typo in your last commit message, or forgot a file? --amend fixes the most recent commit in place instead of adding a new one:
git add forgotten-file.js
git commit --amend -m "Add cart total calculation, with tests"
# Replaces the previous commit entirely -- new hash, same position in history
For anything further back than the last commit, interactive rebase lets you reorder, squash, reword or drop a range of commits before you share them:
git rebase -i HEAD~4
# Opens an editor listing the last 4 commits, oldest first:
#
# pick a1b2c3d Add cart component
# pick 9f8e7d6 fix typo
# pick 1a2b3c4 fix typo again
# pick 7d6e5f4 Add cart tests
#
# Change "pick" to "squash" (or "s") on the two typo-fix commits to fold
# them into the commit above, then save and close -- Git prompts for a
# combined commit message next.
Never amend or interactively rebase commits that have already been pushed and pulled by someone else — it rewrites hashes, and their local history will silently diverge from yours. It's safe on a branch that's entirely yours and not yet shared.
4. Recovering with reflog & Bisecting Bugs
git reflog is a local, personal log of every place HEAD
has pointed — every commit, checkout, rebase and reset — even ones no branch or
tag references anymore. If you think you've lost a commit, it's almost certainly
still here:
git reset --hard HEAD~1 # oops -- accidentally discarded the last commit
git reflog
# 7d6e5f4 HEAD@{0}: reset: moving to HEAD~1
# a1b2c3d HEAD@{1}: commit: Add cart total calculation
# ...
git reset --hard a1b2c3d # restores the "lost" commit
git bisect finds the exact commit that introduced a bug via binary
search — invaluable once a project has hundreds of commits between "known good"
and "known broken":
git bisect start
git bisect bad # current commit is broken
git bisect good v2.0.0 # this tag was known to work
# Git checks out a commit halfway between them:
# Bisecting: 8 revisions left to test
# ...test the app...
git bisect good # or `git bisect bad`, depending on the result
# Repeat -- Git narrows the range each time until it names the exact
# commit that introduced the bug.
git bisect reset # done -- return to your original branch
5. Git Hooks: Automating Checks
Hooks are scripts Git runs automatically at points in the workflow —
.git/hooks/pre-commit runs before a commit is created, and can
reject it (non-zero exit code) to enforce quality gates locally:
#!/bin/sh
# Block commits that still contain a debugger statement.
if git diff --cached | grep -q "debugger;"; then
echo "Error: remove 'debugger;' before committing."
exit 1
fi
Make it executable with chmod +x .git/hooks/pre-commit. Hooks live
in .git/hooks/, which isn't tracked or shared by default — teams
typically use a tool like Husky to commit hook configuration
into the repo itself so everyone gets the same checks automatically.
6. Hands-on Exercise
Clean up a messy branch before opening a PR
Practice the exact cleanup real reviewers expect before they'll look at your diff.
Requirements:
- On a feature branch, make five small commits, including at least two that are trivial fixups (e.g. "fix typo", "oops").
- Use
git rebase -ito squash the fixup commits into the commits they're fixing, ending with 2–3 clean commits. - Amend the final commit's message to be more descriptive using
git commit --amend. - Stash an unrelated uncommitted change, switch to
main, switch back, and pop the stash to confirm it survived. - Tag the current commit as
v0.1.0-practicewith an annotated tag. - Deliberately discard your last commit with
git reset --hard HEAD~1, then usegit reflogto recover it.
If an interactive rebase goes wrong mid-way, git rebase --abort restores the branch to exactly how it was before you started — the same safety net as merge --abort.
7. Knowledge Check
Four quick questions. Expand each to check your answer.
Q1
What's the difference between git stash pop and git stash apply?
What's the difference between git stash pop and git stash apply?
Both reapply a stashed set of changes to your working directory. pop then removes that entry from the stash list; apply leaves it there, so you can reapply the same stash again elsewhere (e.g. onto a different branch) later.
Q2
When is git cherry-pick the right tool instead of git merge?
When is git cherry-pick the right tool instead of git merge?
When you want exactly one specific commit from another branch — not everything on it. A classic case is pulling a single hotfix commit into a release branch without dragging in every other unrelated change that's since landed on the source branch.
Q3
Why is it unsafe to git commit --amend a commit that's already been pushed and pulled by a teammate?
Why is it unsafe to git commit --amend a commit that's already been pushed and pulled by a teammate?
--amend creates a brand-new commit hash to replace the old one. Anyone who already pulled the original commit still has it in their history; when they next pull, they'll see your amended commit as a separate, conflicting piece of history rather than a clean update.
Q4
You ran git reset --hard and lost a commit that wasn't backed up anywhere else. Is it actually gone?
You ran git reset --hard and lost a commit that wasn't backed up anywhere else. Is it actually gone?
Almost certainly not, at least for a while. git reflog keeps a local record of everywhere HEAD has pointed, including commits no branch references anymore. Finding the commit's hash in the reflog and running git reset --hard <hash> recovers it, as long as Git hasn't garbage-collected it yet (typically a 30–90 day window).