rebase -i, cherry-pick, bisect, reflog — the power commands that separate confident Git users from cautious ones. Week 2 capstone: everything comes together in one hands-on session.
# 4 messy commits → 1 clean commit git rebase -i HEAD~4 # Editor opens — change actions: # pick d4f5a6b feat: add login form ← keep # fixup c2d8e3b fix typo ← merge, drop msg # fixup b1e9f4a oops forgot a file ← merge, drop msg # fixup a3f7c2d WIP: trying approach ← merge, drop msg # Save → result: git log --oneline # e5g6h7i feat: add login form ✅ clean! # ── rebase -i action keywords ──────── # pick = keep as-is (default) # squash = merge + keep message # fixup = merge + discard message ★ # reword = keep commit, edit message # drop = delete commit entirely # reorder = drag lines to reorder # Keep branch fresh daily: git rebase origin/main
| Feature | Git Merge | Git Rebase | Git Squash |
|---|---|---|---|
| History | Preserves original branch structure | Creates linear, flat history | Condenses multiple commits into one |
| Commit Type | New Merge Commit two-parent pointer |
Re-applies commits with new SHAs |
Rewrites history removes "noise" |
| Best For | Public / shared branches — always safe | Local feature branches before PR | PR final merge — clean main history |
| Safe on shared? | ✅ Always safe | ❌ Rewrites SHAs | ✅ Via PR only |
git rebase origin/main — stay fresh.git rebase -i HEAD~N — squash WIPs.# === Scenario: hotfix needed from feature branch === # You're on main. A critical security fix is on feat/auth # but that branch has unfinished features too. # You only want the security fix commit. # Step 1: Find the commit hash git log feat/auth --oneline # a1b2c3d feat: add OAuth flow ← don't want this yet # e4f5g6h fix: patch SQL injection ← want THIS # h7i8j9k feat: start user profile ← don't want this # Step 2: Cherry-pick only the security fix git switch main git cherry-pick e4f5g6h # [main k1l2m3n] fix: patch SQL injection # Applied as a new commit on main! # === Cherry-pick a range of commits === git cherry-pick abc123^..def456 # from abc123 to def456 (inclusive) # === Cherry-pick without committing (--no-commit) === git cherry-pick --no-commit e4f5g6h # Applies changes to working directory but doesn't commit # Useful: combine multiple cherry-picks into one clean commit # === If cherry-pick causes conflict === # 1. Resolve the conflict in the file # 2. git add resolved-file # 3. git cherry-pick --continue # OR: git cherry-pick --abort (go back to before) # === Real DevOps use case === # Bug fixed on a feature branch during development # Production still needs the fix NOW — don't wait for the full feature # cherry-pick the fix commit to main → deploy immediately
# === Scenario: production bug, 500 commits of history # You know HEAD is broken. A week ago it worked. # Which commit introduced the bug? # Without bisect: check each commit = 500 checks worst case # With bisect: binary search = max 9 checks for 500 commits! # === Start bisect === git bisect start git bisect bad # HEAD is broken git bisect good v2.1.0 # tag from 2 weeks ago was good # Git checks out the MIDDLE commit automatically # === Now test if the bug exists === # Run your tests, check the feature... npm test # or any test command git bisect good # This commit is fine # Git moves to the next midpoint (upper half now) # Repeat: test → bisect good/bad → repeat git bisect bad # This one is broken git bisect good # This one is fine # ... ~7-9 more iterations ... # Git announces: # a4b5c6d is the first bad commit # commit a4b5c6d # Author: Developer Name # Date: Mon Nov 4 14:22:31 2024 # feat: refactor authentication middleware # === Reset back to HEAD === git bisect reset # === Automate: pass a test script === git bisect start git bisect bad HEAD git bisect good HEAD~50 git bisect run npm test # Runs npm test automatically at each step! # Git runs the script, marks good/bad automatically # Finds the bug commit without manual testing!
git bisect run) the entire process is fully automated.
Write a test that exits 0 if good, non-zero if bad:
#!/bin/bash
# test-bisect.sh
node -e "require('./auth.js')"
# exits 0 if no crash, non-zero if crash
git bisect run ./test-bisect.shGit runs the script at each midpoint automatically. Zero manual work.
# reflog = "reference log" = every move HEAD has ever made git reflog # HEAD@{0}: reset: moving to HEAD~3 ← oh no, hard reset! # HEAD@{1}: commit: feat: add dashboard # HEAD@{2}: commit: fix: handle null case # HEAD@{3}: commit: chore: cleanup imports ← lost these 3 commits?! # HEAD@{4}: checkout: moving from main to feat/auth # HEAD@{5}: commit: feat: add login form # === Recover "lost" commits after hard reset === git reset --hard HEAD~3 # Oops! Lost 3 commits git reflog # Find the SHA before the reset git reset --hard HEAD@{1} # Restore to before the reset # All 3 commits back! 🎉 # === Recover a deleted branch === git branch -D feat/important # Accidentally deleted! git reflog # HEAD@{2}: checkout: moving from feat/important to main # Find the last commit hash on that branch git checkout -b feat/important HEAD@{2} # Branch restored! # === Reflog for a specific branch === git reflog show feat/auth # History of that branch's tip git reflog show HEAD # HEAD movements # === Reflog expires — default 90 days === # git gc --prune=now removes unreferenced objects # Act quickly if you need to recover something!
.git/objects/ until garbage collection. reflog keeps a log of all HEAD movements — so you can recover from:git reset --hardgit reflog first. Your work is almost certainly still there. Find the SHA before the mistake and reset to it.
git reset --soft HEAD~1
Moves HEAD back. All changes from the undone commit stay staged.
Before: HEAD → bad commit (staging: empty) After: HEAD → previous commit staging: all changes from bad commit ← still here
Use when: Undo last commit but keep changes staged (ready to re-commit).
git reset HEAD~1
Moves HEAD back. Changes stay in working directory (unstaged).
Before:
HEAD → bad commit
(staging: empty)
After:
HEAD → previous commit
working dir: all changes
from bad commit ← modified,
unstaged
Use when: Undo commit AND unstage changes (re-examine before re-committing).
git reset --hard HEAD~1
Moves HEAD back. Changes are permanently discarded.
Before: HEAD → bad commit (working dir: changes) After: HEAD → previous commit working dir: CLEAN ⚠️ changes: GONE ← for real
Use when: Completely throw away the last commit AND its changes. Cannot undo (without reflog).
--soft (staged) or --mixed (unstaged). Throw away everything? → --hard. When in doubt, use --soft — you can always discard later but you can't undo --hard without reflog.
40 minutes hands-on: conflict → rebase -i → cherry-pick → bisect → reflog. The full advanced Git workout.
git rebase -i HEAD~4 to squash them into 1 clean commit. Verify with git log --oneline.good and bad to find commit 7 in 3–4 steps.git reset --hard HEAD~3. Panic. Open reflog. Find SHA before the reset. Restore all 3 commits.git reset --soft HEAD~1 — see changes still staged. Commit again. Run git reset --hard HEAD~1 — see changes gone.cd my-devops-app # === Make 4 messy commits === git switch -c lab/rebase-practice echo "real feature code" > feature.js git add feature.js && git commit -m "feat: add feature code" echo "console.log('debug')" >> feature.js git add feature.js && git commit -m "WIP: debugging" echo "# comment" >> feature.js git add feature.js && git commit -m "oops: add comment" echo "// final" >> feature.js git add feature.js && git commit -m "fix: cleanup" git log --oneline # 4 messy commits visible # === Clean with interactive rebase === git rebase -i HEAD~4 # In editor: change last 3 "pick" → "fixup" # pick abc feat: add feature code # fixup def WIP: debugging # fixup ghi oops: add comment # fixup jkl fix: cleanup # Save and close editor git log --oneline # Only 1 commit: "feat: add feature code" ✅ # Clean up git switch main git branch -D lab/rebase-practice
# === Create source branch with 3 commits === git switch -c lab/cherry-source echo "file A" > fileA.txt && git add . && git commit -m "feat: file A" echo "file B" > fileB.txt && git add . && git commit -m "fix: file B fix" echo "file C" > fileC.txt && git add . && git commit -m "chore: file C" # Note the hash of the middle commit ("fix: file B fix") git log --oneline # xyz789 chore: file C # abc456 fix: file B fix ← copy this hash # def123 feat: file A # === Cherry-pick to main === git switch main # Check: fileB.txt does NOT exist yet ls git cherry-pick abc456 # Use YOUR hash # [main mno012] fix: file B fix # Verify: fileB.txt now exists on main ls # fileB.txt appears! git log --oneline # New cherry-pick commit visible # Clean up git branch -D lab/cherry-source
# === Create 10 commits, bug at commit 7 === git switch -c lab/bisect-test for i in 1 2 3 4 5 6; do echo "commit $i" >> history.txt git add . && git commit -m "chore: commit $i (clean)" done # Commit 7 = THE BUG echo "ERROR: bug introduced here" > bug.txt git add . && git commit -m "feat: commit 7 (introduces bug)" for i in 8 9 10; do echo "commit $i" >> history.txt git add . && git commit -m "chore: commit $i (clean)" done # === Start bisect === git bisect start git bisect bad HEAD # Current commit is broken git bisect good HEAD~10 # 10 commits ago was fine # Git checks out middle → test → mark good/bad # Check if bug.txt exists: ls bug.txt 2>/dev/null && git bisect bad || git bisect good # Repeat until Git finds the bad commit (~3-4 times) # Git will say: "abc123 is the first bad commit" git bisect reset # Back to HEAD # Automated version: git bisect start git bisect bad HEAD git bisect good HEAD~10 git bisect run bash -c "! test -f bug.txt" # Git finds commit 7 automatically! git bisect reset
# === Make 3 commits === echo "important A" > importantA.txt git add . && git commit -m "feat: important A" echo "important B" > importantB.txt git add . && git commit -m "feat: important B" echo "important C" > importantC.txt git add . && git commit -m "feat: important C" git log --oneline # xyz important C # mno important B # abc important A # ... main history # === OOPS — hard reset removes 3 commits === git reset --hard HEAD~3 git log --oneline # They're gone! 😱 ls # importantA/B/C.txt gone! # === RECOVER with reflog === git reflog # HEAD@{0}: reset: moving to HEAD~3 # HEAD@{1}: commit: feat: important C ← before the reset git reset --hard HEAD@{1} # Restore to before the reset git log --oneline # All 3 commits back! 🎉 ls # Files restored!
# Commit something echo "test" > test.txt && git add . && git commit -m "test commit" # --soft: changes stay STAGED git reset --soft HEAD~1 git status # Changes in green (staged) # Re-commit with better message git commit -m "chore: better message" # --hard: changes GONE (use reflog to verify) git reset --hard HEAD~1 git status # Clean working directory git reflog # Previous commit still in reflog
3 questions · 5 minutes · cherry-pick, bisect, reflog
git cherry-pick <hash> do?git bisect used for?git reset --hard?git loggit stash listgit refloggit statusmy-devops-app repo now has: SSH auth, branch protection, merged PRs, Husky hooks, commitlint — and a clean professional commit history.
| Day | Topic | Key Skills | Lab Output |
|---|---|---|---|
| Day 6 | Git Fundamentals | 4 areas, add, commit, push, stash, .gitignore | my-devops-app on GitHub |
| Day 7 | Branching Strategies | GitHub Flow, GitFlow, TBD, merge/rebase/squash | PR merged, branch protection enabled |
| Day 8 | Pull Requests & Review | PR templates, code review, CODEOWNERS | PR template + CODEOWNERS in repo |
| Day 9 | Git Hooks & Automation | Husky, lint-staged, commitlint | .husky/ committed, bad commits blocked |
| Day 10 ✅ | Advanced Lab | rebase -i, cherry-pick, bisect, reflog, reset | All 5 exercises completed |
| Command | What it does | When to use |
|---|---|---|
| git rebase -i HEAD~N | Interactive rebase — squash/reorder/edit N commits | Before PR to clean history |
| git rebase origin/main | Replay branch on top of latest main | Keep branch fresh, prevent conflicts |
| git cherry-pick SHA | Apply one commit from any branch to current | Hotfix, backport, wrong-branch commit |
| git cherry-pick A^..B | Apply a range of commits | Multiple related commits to transplant |
| git bisect start | Start binary search for bad commit | Bug regression: "which commit broke this?" |
| git bisect run CMD | Automated bisect using a test script | When you have a scripted pass/fail test |
| git reflog | Log of all HEAD movements | First step when you think you lost work |
| git reset --soft HEAD~1 | Undo commit, keep changes staged | Fix commit message, split commit |
| git reset --mixed HEAD~1 | Undo commit, keep changes unstaged | Re-examine changes before re-committing |
| git reset --hard HEAD~1 | Undo commit + discard all changes | Completely throw away a bad commit |
| git log --oneline --graph --all | Visual branch history in terminal | Understanding branching state |
| git show SHA | Show full diff + metadata of one commit | Inspect what a specific commit changed |
| git diff branch-a..branch-b | All differences between two branches | Review what a branch changed vs main |
| git shortlog -sn | Commit count per author | Team contribution stats |
| Situation | Command | Notes |
|---|---|---|
| "I have 5 messy WIP commits before my PR" | git rebase -i HEAD~5 | Squash/fixup into 1–2 logical commits |
| "A critical fix is on a feature branch — production needs it NOW" | git cherry-pick SHA | Apply only that fix commit to main |
| "Something broke last week — I have 300 commits to check" | git bisect start | Binary search finds it in ~8 steps |
| "I ran git reset --hard and lost 3 commits" | git reflog | Find SHA before the reset, recover it |
| "I committed on the wrong branch" | git cherry-pick + git reset | Cherry-pick to right branch, reset wrong one |
| "I want to undo the last commit but keep my changes" | git reset --soft HEAD~1 | Changes stay staged — re-commit with better message |
| "I want to completely undo my last commit and changes" | git reset --hard HEAD~1 | Destructive — use reflog if you change your mind |
| "I accidentally deleted a branch" | git reflog | Find the last SHA on that branch, recreate |
| "I need the same fix on both v1 and v2" | git cherry-pick | Apply to both branches individually |
| "My branch is behind main — I want a clean base" | git rebase origin/main | Replay your commits on latest main |
git rebase -i to clean up before first pushgit rebase origin/main to keep branch freshmain branchdevelop branchgit push (shared)git push --force-with-lease (safe — fails if others pushed)git push --force (dangerous — overwrites anything)--force-with-lease. Never --force on shared branches.
# Morning: sync git switch main && git pull git switch feat/my-feature git rebase origin/main # keep fresh # Working git status && git add -p git commit -m "feat: ..." # commitlint enforced # Before PR: clean history git rebase -i HEAD~N # squash WIP commits git push origin feat/my-feature # Hotfix needed git cherry-pick SHA # take fix from feature # After PR merge: cleanup git switch main && git pull git branch -d feat/my-feature git fetch --prune
# Panic: lost commits git reflog # find SHA git reset --hard SHA # restore # Undo last commit, keep changes git reset --soft HEAD~1 # Find which commit broke it git bisect start git bisect bad HEAD git bisect good TAG git bisect run TEST_CMD git bisect reset # Oops: wrong branch commit git log --oneline # note SHA git switch correct-branch git cherry-pick SHA # move it git switch wrong-branch git reset --hard HEAD~1 # remove it # Hooks bypass (emergency only) git commit --no-verify -m "..."
| Day | Topic | What You'll Build | Tools |
|---|---|---|---|
| Day 11 | CI/CD Concepts + Jenkins | Jenkins vs GitHub Actions comparison pipeline | Jenkins, YAML |
| Day 12 | GitHub Actions + Jenkins Pipelines | Multi-job pipeline with parallel tests + Jenkins Declarative | GH Actions, Jenkinsfile |
| Day 13 | Testing in CI | Jest unit tests + coverage gate in CI | Jest, Istanbul |
| Day 14 | Artifacts & Package Management | Docker image build + push to registry in pipeline | Docker, GHCR |
| Day 15 | Continuous Deployment | Full CD pipeline with staging + prod approval gate | GitHub Actions, Environments |
This is your Day 11 starting point.