Git Advanced Reference: Interactive Rebase, Bisect, Hooks, Stash & Reflog
Git advanced patterns — the operations that distinguish developers who understand what Git is actually doing from those who just know add, commit, push. Interactive rebase, bisect, hooks, and the reflog have saved countless teams from disaster.
1. Interactive Rebase — Rewrite History
Squash commits, reorder, edit messages, and split commits before merging
# Interactive rebase on the last N commits: git rebase -i HEAD~5 # opens editor with last 5 commits # Editor shows picks (oldest → newest, top → bottom): # pick a1b2c3 Add user model # pick d4e5f6 Fix typo in user model # pick g7h8i9 Add auth middleware # pick j0k1l2 WIP: auth tests # pick m3n4o5 Finish auth tests # Commands (change 'pick' to): # squash (s) — merge commit into the one above it, combine messages # fixup (f) — like squash but discard this commit's message # reword (r) — stop and let you edit the commit message # edit (e) — stop at this commit (can amend or split it) # drop (d) — delete the commit entirely # reorder — just move lines up/down in the editor # Common flow: clean up before a PR # pick a1b2c3 Add user model # fixup d4e5f6 Fix typo in user model ← merge typo fix into main commit # pick g7h8i9 Add auth middleware # squash j0k1l2 WIP: auth tests ← combine WIP with finish commit # squash m3n4o5 Finish auth tests # Split a commit (too large to review): # 1. Mark commit as 'edit' in the interactive rebase # 2. git reset HEAD~1 (unstage the commit, keep changes) # 3. git add -p (selectively stage parts) # 4. git commit -m "Part 1" # 5. git add -p && git commit -m "Part 2" # 6. git rebase --continue # Never rebase commits already pushed to shared branches # Use for: feature branches before merge, local cleanup
2. Cherry-pick, Revert & Reset
Move specific commits, undo changes, and understand reset modes
# Cherry-pick (apply a specific commit from another branch): git cherry-pick a1b2c3 # apply single commit git cherry-pick a1b2c3..d4e5f6 # apply range (exclusive..inclusive) git cherry-pick a1b2c3 -e # cherry-pick and edit message git cherry-pick a1b2c3 --no-commit # stage changes without committing # Common use: hotfix on main, then cherry-pick to release branch: git checkout main && git cherry-pick hotfix-commit-sha git checkout release/1.2 && git cherry-pick hotfix-commit-sha # Revert (safe undo — creates a new commit that undoes the change): git revert a1b2c3 # revert specific commit (safe for shared branches) git revert a1b2c3..d4e5f6 # revert a range git revert a1b2c3 --no-commit # stage the revert without committing yet # Reset (dangerous — rewrites history): # Mode comparison: # --soft unstages commits, keeps changes staged (index + working tree intact) # --mixed unstages commits, keeps changes unstaged (default) # --hard discards commits AND working tree changes (destructive) git reset --soft HEAD~2 # undo last 2 commits, keep changes staged git reset --mixed HEAD~2 # undo last 2 commits, unstage changes (to re-stage selectively) git reset --hard HEAD~2 # undo last 2 commits AND discard all changes (cannot be recovered unless reflog) # Unstage a file (not a commit reset): git restore --staged file.txt # Discard working tree changes to a file: git restore file.txt
3. Git Bisect — Binary Search for Bug Introduction
Find which commit introduced a bug in O(log n) steps
# bisect does binary search through commit history to find which commit # introduced a bug. For 1000 commits, finds the bad commit in ~10 steps. # Manual bisect: git bisect start git bisect bad # current commit is broken git bisect good v1.2.0 # this tag was working # Git checks out the midpoint commit. Test it, then: git bisect good # or git bisect bad # Repeat until Git reports: "d4e5f6 is the first bad commit" git bisect reset # return to original HEAD when done # Automated bisect with a test script: git bisect start git bisect bad HEAD git bisect good v1.2.0 git bisect run npm test -- --testFile auth.test.js # Git runs the script at each midpoint. # Exit 0 = good. Exit 1-127 (not 125) = bad. # Exit 125 = skip this commit (untestable) # bisect run with a shell command: git bisect run sh -c 'grep -q "correct string" src/config.js' git bisect run sh -c 'curl -s localhost:3000/health | grep -q "ok"' # View bisect log (useful for understanding what was tested): git bisect log
4. Git Hooks
Client-side hooks for linting, tests, and commit message enforcement
# Hooks live in .git/hooks/ — make them executable (chmod +x)
# For team sharing: put hooks in .githooks/ and configure:
git config core.hooksPath .githooks
# pre-commit hook (runs before commit is created):
#!/bin/sh
# .githooks/pre-commit
set -e
echo "Running pre-commit checks..."
npm run lint
npm run type-check
# If any command exits non-zero, commit is aborted
# commit-msg hook (validate commit message format):
#!/bin/sh
# .githooks/commit-msg
COMMIT_MSG_FILE=$1
COMMIT_MSG=$(cat "$COMMIT_MSG_FILE")
# Enforce Conventional Commits: feat/fix/chore/docs/refactor/test
PATTERN='^(feat|fix|chore|docs|refactor|test|perf|ci|build)(\(.+\))?: .{1,72}$'
if ! echo "$COMMIT_MSG" | grep -qE "$PATTERN"; then
echo "❌ Bad commit message. Use: feat(scope): description"
echo " Got: $COMMIT_MSG"
exit 1
fi
# pre-push hook (run tests before pushing):
#!/bin/sh
# .githooks/pre-push
echo "Running tests before push..."
npm test
if [ $? -ne 0 ]; then
echo "Tests failed. Push aborted."
exit 1
fi
# Husky (Node.js hook manager — installs hooks automatically via npm postinstall):
# npm install --save-dev husky
# npx husky init
# echo "npm run lint" > .husky/pre-commit
5. Stash, Reflog & Lost Commit Recovery
stash with context, recover from –hard reset, and find orphaned commits
# git stash (save work-in-progress without committing):
git stash # stash tracked changes
git stash push -u -m "WIP: auth work" # include untracked files (-u) with message
git stash list # show all stashes
git stash pop # apply most recent stash + remove it
git stash apply stash@{2} # apply specific stash without removing
git stash drop stash@{2} # delete specific stash
git stash branch feature/auth # create a new branch from stash (clean way to continue)
git stash show -p stash@{0} # see the diff in a stash
# The reflog (Git's safety net — records every HEAD movement):
git reflog # show all recent HEAD positions with SHA
# Output:
# a1b2c3 HEAD@{0}: reset: moving to HEAD~3
# d4e5f6 HEAD@{1}: commit: Add auth middleware
# g7h8i9 HEAD@{2}: commit: Add user model
# Recover from accidental --hard reset:
git reset --hard HEAD~3 # oops, lost 3 commits
git reflog # find the SHA before the reset
git reset --hard d4e5f6 # restore to that commit
# Or create a new branch to recover without resetting:
git branch recovery-branch d4e5f6
# Find orphaned commits (not reachable from any branch):
git fsck --lost-found # writes unreachable objects to .git/lost-found/
git log --oneline d4e5f6 # inspect a found commit
# Reflog expires after 90 days by default. Don't wait too long.
Track Git and developer tooling releases.
ReleaseRun monitors GitHub Actions, VS Code, and 13+ technologies.
Related: GitHub Actions Reference | GitLab CI Reference
🔍 Free tool: GitHub Actions Security Checker — check the GitHub Actions workflows in your git repos for 8 security misconfigurations.
Founded
2023 in London, UK
Contact
hello@releaserun.com