Git Advanced Techniques: Rebase, Cherry-Pick, Bisect, Hooks and Workflows
Most developers use Git for add, commit, push, and pull. But Git's advanced features are what separate developers who work efficiently in teams from those who create messy histories and struggle with conflicts. This guide covers the techniques that appear in senior developer interviews and save hours in real projects.
Interactive Rebase
Interactive rebase is one of the most powerful Git tools. It lets you rewrite history before merging β squash commits, reword messages, reorder, split, or drop commits entirely.
bash-- Rewrite the last 4 commits interactively git rebase -i HEAD~4 -- Rebase onto main (rewrite all commits not in main) git rebase -i main
This opens your editor with a list like:
codepick a1b2c3 Add user authentication pick d4e5f6 Fix typo in comment pick 7g8h9i Add tests for auth pick j0k1l2 WIP: debugging session -- Commands: -- p, pick = use commit as-is -- r, reword = use commit, but edit the message -- e, edit = use commit, but stop for amending -- s, squash = meld into previous commit -- f, fixup = like squash, but discard this commit's message -- d, drop = remove commit
Change to:
codepick a1b2c3 Add user authentication f d4e5f6 Fix typo in comment s 7g8h9i Add tests for auth d j0k1l2 WIP: debugging session
Result: one clean commit combining auth + tests, typo fix merged silently, WIP removed entirely.
Never rebase commits that have been pushed to a shared branch. Rebase rewrites history β it changes commit SHAs.
git rebase vs git merge
bash-- Merge: creates a merge commit, preserves history git checkout feature git merge main -- History: A -- B -- C (main) -- \ \ -- D -- E -- M (merge commit) -- Rebase: replays commits on top of main, linear history git checkout feature git rebase main -- History: A -- B -- C -- D' -- E' (linear)
Use merge for integrating long-lived branches (releases, hotfixes) β preserves context. Use rebase for keeping feature branches up to date with main β linear, readable history.
Cherry-Pick
Apply a specific commit from another branch to your current branch:
bash-- Apply a single commit git cherry-pick a1b2c3 -- Apply a range of commits git cherry-pick a1b2c3..d4e5f6 -- Cherry-pick without committing (stage only) git cherry-pick --no-commit a1b2c3 -- Cherry-pick and edit the message git cherry-pick --edit a1b2c3
Use cases:
- Backport a bug fix to a release branch without merging the entire feature
- Apply a specific commit from an abandoned branch
- Move commits accidentally made on the wrong branch
bash-- Accidentally committed to main instead of feature branch git log --oneline -- abc123 My new feature -- Create feature branch pointing to the commit before your mistake git checkout -b feature-branch HEAD~1 -- Cherry-pick the commit onto the new branch git cherry-pick abc123 -- Remove from main (reset, keeping changes staged) git checkout main git reset HEAD~1
git bisect
Binary search through commit history to find exactly which commit introduced a bug:
bash-- Start bisect session git bisect start -- Tell Git the current commit is bad (has the bug) git bisect bad -- Tell Git a known good commit (before the bug existed) git bisect good v2.0.0 -- Git checks out the midpoint commit -- Test your application, then tell Git: git bisect good -- or git bisect bad -- Git keeps halving the range until it finds the exact commit -- "a1b2c3 is the first bad commit" -- When done git bisect reset -- return to original HEAD
Automate with a test script:
bash-- Git runs your script on each commit -- exit 0 = good, exit 1 = bad git bisect run npm test -- --grep "the failing test"
bisect finds the culprit commit in O(log n) steps β for 1000 commits, only 10 checks.
The Reflog: Your Safety Net
git reflog records every time HEAD moves β commits, checkouts, rebases, resets. It is your recovery tool when things go wrong.
bashgit reflog -- a1b2c3 HEAD@{0}: rebase: finishing -- d4e5f6 HEAD@{1}: rebase: Add feature -- 7g8h9i HEAD@{2}: checkout: moving from main to feature -- j0k1l2 HEAD@{3}: commit: Oops, wrong branch -- Recover a commit you accidentally reset away git reset --hard HEAD@{3} -- Recover a deleted branch git checkout -b recovered-branch HEAD@{5} -- The reflog keeps entries for 90 days by default
Rule: Almost nothing in Git is truly lost as long as you committed it. The reflog can recover it.
git stash
Temporarily shelve changes without committing:
bashgit stash -- stash all tracked changes git stash push -m "WIP: auth" -- stash with a message git stash push -- src/auth.js -- stash specific file git stash -u -- include untracked files git stash list -- see all stashes git stash pop -- apply most recent, remove from stash git stash apply stash@{2} -- apply specific stash, keep it git stash drop stash@{1} -- delete a stash git stash branch feature-branch -- create branch from stash
Git Hooks
Hooks are scripts that run automatically at specific points in the Git workflow. They live in .git/hooks/ or can be managed with tools like Husky.
bash-- .git/hooks/pre-commit (runs before every commit) #!/bin/bash set -e echo "Running linter..." npm run lint echo "Running tests..." npm test -- --passWithNoTests echo "Checking for console.log..." if git diff --cached | grep -q "console\.log"; then echo "Error: console.log found in staged changes" exit 1 fi
bash-- .git/hooks/commit-msg (validates commit message format) #!/bin/bash COMMIT_MSG=$(cat "$1") PATTERN="^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?: .{1,72}$" if ! echo "$COMMIT_MSG" | grep -qE "$PATTERN"; then echo "Bad commit message. Use: type(scope): description" echo "Example: feat(auth): add JWT refresh token support" exit 1 fi
Husky (shareable hooks for teams)
bashnpm install --save-dev husky lint-staged npx husky init
json-- package.json { "lint-staged": { "*.{ts,tsx}": ["eslint --fix", "prettier --write"], "*.{css,md}": "prettier --write" } }
bash-- .husky/pre-commit npx lint-staged
Professional Branching Workflows
GitHub Flow (simple, continuous deployment)
codemain βββ feature/user-auth βββ feature/payment-redesign βββ fix/login-redirect
- Create branch from
main - Commit and push
- Open Pull Request
- Review and merge to
main - Deploy
main
Clean, simple, works well for most teams.
Git Flow (release-based)
codemain (production releases only) develop (integration branch) βββ feature/xxx (branch from develop) βββ release/1.2.0 (branch from develop, merge to main + develop) βββ hotfix/critical-bug (branch from main, merge to main + develop)
More structure β useful for products with scheduled releases, multiple maintained versions.
Useful Commands Worth Knowing
bash-- Find which branch contains a commit git branch --contains a1b2c3 -- Show commits in branch-a not in branch-b git log branch-b..branch-a --oneline -- Find all commits that touched a file git log --follow -- src/auth/login.ts -- Show who last changed each line (blame) git blame src/auth/login.ts git blame -L 50,80 src/auth/login.ts -- lines 50-80 only -- Undo last commit, keep changes staged git reset --soft HEAD~1 -- Undo last commit, keep changes unstaged git reset HEAD~1 -- Undo last commit, discard changes git reset --hard HEAD~1 -- Fix last commit message (before pushing) git commit --amend -m "Better message" -- Add forgotten file to last commit git add forgotten.ts git commit --amend --no-edit
Common Interview Questions
Q: What is the difference between git merge and git rebase?
Both integrate changes from one branch into another. merge creates a merge commit that preserves the full history of both branches β non-destructive. rebase moves commits to a new base, creating new commit SHAs β results in a linear history but rewrites history. Never rebase shared branches.
Q: What is the difference between git reset and git revert?
reset moves the branch pointer backward β it rewrites history and should not be used on pushed commits (dangerous on shared branches). revert creates a new commit that undoes a previous commit β it is safe for shared branches because it adds to history rather than rewriting it.
Q: How would you recover a branch you accidentally deleted?
Use git reflog to find the commit SHA that the branch pointed to, then git checkout -b recovered-branch <sha>. The reflog keeps references for 90 days.
Practice on Froquiz
Git proficiency is tested in developer interviews at all levels. Explore our developer tool quizzes on Froquiz and check back as we add Git-specific content.
Summary
- Interactive rebase (
-i) lets you squash, reorder, reword, and drop commits before merging - Rebase creates linear history; merge preserves full history β use each deliberately
- Cherry-pick applies specific commits across branches β great for backporting fixes
git bisectbinary-searches commit history to find which commit introduced a buggit reflogis your safety net β almost nothing committed is truly lost- Hooks automate code quality checks β use Husky to share them across the team
- GitHub Flow (branch β PR β merge β deploy) works for most teams