MinhVo

Minh Vo

rss feed

Slaying code & making it lit fr fr 🔥 tagline

Hey there 👋 I'm an AI Engineer with 7 years of experience building scalable web and mobile applications. Currently at Neurond AI (May 2025 — present), architecting an Enterprise AI Assistant Platform with multi-tenant RAG on pgvector, multi-provider LLM orchestration, and Azure-native infrastructure. Previously spent 5+ years at SNAPTEC (Sep 2019 — Apr 2025), leading SaaS themes, admin dashboards, and e-commerce platforms — earned the Hero of the Year award in 2021. I specialize in TypeScript, React, Next.js, and AI-Native engineering with Claude Code and Cursor.bio

Back to blogs

Git Advanced: Interactive Rebase, Bisect, and Worktrees

Master advanced Git: interactive rebase, bisect for bug hunting, and worktrees for parallel work.

GitAdvancedDeveloper ToolsVersion Control

By MinhVo

Introduction

Git is far more than a tool for saving snapshots of your code. Beneath the surface of git add, git commit, and git push lies a powerful set of advanced features that can transform your development workflow. Interactive rebase lets you reshape your commit history into a clean, logical narrative before sharing it with your team. Git bisect performs binary search through your history to pinpoint exactly which commit introduced a bug. Git worktrees let you work on multiple branches simultaneously without the overhead of cloning multiple repositories.

These advanced Git features are what separate developers who merely use Git from developers who leverage Git as a precision instrument for code management. In professional environments where code review quality matters, where bugs need to be tracked down across thousands of commits, and where developers need to context-switch between features, hotfixes, and experiments, these tools become indispensable. They save hours of manual work and prevent the kinds of mistakes that come from clumsy history manipulation.

This guide provides a deep, practical exploration of interactive rebase, bisect, and worktrees with real-world workflows, configuration tips, and strategies for handling common edge cases. Whether you are preparing a pristine pull request, hunting a regression in a massive codebase, or managing parallel development streams, these techniques will elevate your Git proficiency to the next level.

Git branching and version control visualization

Understanding Advanced Git: Core Concepts

Before diving into interactive rebase, bisect, and worktrees, it is essential to understand the Git object model that makes all of this possible. Git stores four types of objects: blobs (file contents), trees (directory structures), commits (snapshots with metadata), and refs (pointers to commits). Every commit is identified by a SHA-1 hash derived from its content, parent commit(s), author, and message.

The Reflog: Your Safety Net

The reflog is Git's journal of every ref change in your repository. Every time HEAD moves—whether through commit, checkout, rebase, merge, or reset—Git records the previous position in the reflog. This is your ultimate safety net when rebasing goes wrong.

# View reflog for the last 20 entries
git reflog
 
# Example output:
# abc1234 HEAD@{0}: rebase (finish): refs/heads/feature
# def5678 HEAD@{1}: rebase (pick): Fix validation logic
# 9876543 HEAD@{2}: rebase (start): refs/heads/feature
# 1112223 HEAD@{3}: commit: WIP: debugging form validation
 
# Recover to any previous state
git reset --hard HEAD@{3}

Understanding HEAD, Working Tree, and Staging Area

Git's three-tree architecture—working tree, staging area (index), and repository—becomes especially important during interactive rebase. When you edit commits, Git temporarily moves HEAD to each commit, lets you modify the staging area, and creates new commits. Understanding this process prevents confusion when conflicts arise.

Developer workflow with version control

Architecture and Design Patterns

The Clean History Philosophy

There are two schools of thought regarding commit history. The "history is sacred" camp believes every commit should be preserved as-is because history is an audit trail. The "clean history" camp believes that history should tell a clear story and that intermediate commits (typos, WIP, debugging) should be squashed or reorganized before merging.

Most professional teams adopt the clean history approach for feature branches while preserving merge commits on main branches. Interactive rebase is the primary tool for implementing this philosophy.

Feature Branch Workflow with Rebase

The recommended workflow is: create a feature branch, make small focused commits as you work, then use interactive rebase to clean up before merging. This gives you the safety of frequent commits during development and the clarity of clean history in the shared branch.

Step-by-Step Implementation

Interactive Rebase: Reshaping History

Interactive rebase is the Swiss Army knife of Git history manipulation. It lets you reorder, squash, edit, reword, drop, and split commits with surgical precision.

# Start interactive rebase for the last 5 commits
git rebase -i HEAD~5
 
# This opens your editor with something like:
# pick abc1234 Add user validation
# pick def5678 Fix typo in validation
# pick 9876543 Add email verification
# pick 1112223 WIP: debugging
# pick 3334445 Complete email verification feature
 
# Reorder: simply rearrange lines
# Squash: change 'pick' to 's' to meld into previous commit
# Edit: change 'pick' to 'e' to pause at that commit
# Reword: change 'pick' to 'r' to edit the commit message
# Drop: change 'pick' to 'd' to remove the commit entirely
# Fixup: change 'pick' to 'f' to squash without keeping the message
 
# Example: clean up the history
# pick abc1234 Add user validation
# squash def5678 Fix typo in validation
# pick 9876543 Add email verification
# squash 1112223 WIP: debugging
# squash 3334445 Complete email verification feature
 
# Result: two clean commits
# "Add user validation" and "Add email verification"

Editing a Specific Commit

When you need to modify a commit that is not the most recent one, edit in interactive rebase is the tool of choice.

# Start rebase
git rebase -i HEAD~5
 
# Change the target commit to 'edit':
# edit abc1234 Add user validation
# pick def5678 Fix typo
# ...
 
# Git pauses at that commit. Make your changes:
# Edit files, stage changes
git add src/validator.ts
 
# Amend the commit
git commit --amend --no-edit
 
# Continue rebase
git rebase --continue

Splitting a Commit

Sometimes you realize a commit should have been two separate changes. Interactive rebase lets you split it.

# Mark the commit to edit
# edit abc1234 Add user validation and email verification
 
# Git pauses. Reset the commit but keep changes staged
git reset HEAD~
 
# Now create separate commits
git add src/validator.ts
git commit -m "Add user validation"
 
git add src/email.ts
git commit -m "Add email verification"
 
# Continue rebase
git rebase --continue

Autosquash with Fixup Commits

Git's --autosquash flag automatically reorders and marks fixup commits during interactive rebase. This is a powerful workflow for incrementally fixing up specific commits without interrupting your flow.

# Create a fixup commit targeting a specific commit
git commit --fixup=abc1234
 
# Later, when rebasing, Git automatically reorders:
git rebase -i --autosquash HEAD~10
 
# The fixup commit is automatically placed after abc1234
# and marked with 'fixup' instead of 'pick'

Git Bisect: Binary Search for Bugs

Git bisect performs a binary search through your commit history to find the exact commit that introduced a bug. Instead of checking hundreds of commits one by one, bisect needs only about 10 checks for 1000 commits (log2(1000) ≈ 10).

# Start bisect session
git bisect start
 
# Mark the current commit as bad (has the bug)
git bisect bad
 
# Mark a known good commit (last version without the bug)
git bisect good v2.1.0
 
# Git checks out a middle commit. Test it and mark:
git bisect good  # if this commit doesn't have the bug
git bisect bad   # if this commit has the bug
 
# Git narrows down until it finds the culprit:
# abc1234 is the first bad commit
# Commit abc1234: Fix navigation component
# Date: 2021-10-15
# Changed: src/components/Nav.tsx
 
# End bisect session
git bisect reset

Automated Bisect with Scripts

The real power of bisect comes from automation. You can provide a script that bisect runs at each commit to determine if it is good or bad.

# Create a test script
cat > /tmp/test-bug.sh << 'EOF'
#!/bin/bash
npm run build 2>&1 | grep -q "TypeError" && exit 1 || exit 0
EOF
chmod +x /tmp/test-bug.sh
 
# Run automated bisect
git bisect start HEAD v2.0.0
git bisect run /tmp/test-bug.sh
 
# Git automatically checks out each commit, runs the script,
# and narrows down to the exact commit that introduced the TypeError
// Node.js test script for bisect
// test-bisect.js
const { execSync } = require("child_process");
 
try {
  const output = execSync("node src/app.js --test-mode", {
    encoding: "utf-8",
    timeout: 30000,
  });
  // Check if the specific bug exists
  const hasBug = output.includes("Cannot read property 'id' of undefined");
  process.exit(hasBug ? 1 : 0); // Exit 1 = bad, 0 = good
} catch (error) {
  process.exit(1); // Build failure counts as bad
}

Git Worktrees: Parallel Development

Git worktrees let you check out multiple branches in separate directories simultaneously, all linked to the same repository. This eliminates the need for multiple clones and the overhead of stashing when you need to context-switch.

# Create a worktree for a hotfix branch
git worktree add ../hotfix-urgent main
 
# Create a worktree from a new branch
git worktree add ../feature-experiment -b experiment/new-ui
 
# List all worktrees
git worktree list
# /home/user/project            abc1234 [main]
# /home/user/hotfix-urgent      def5678 [hotfix/critical-fix]
# /home/user/feature-experiment  9876543 [experiment/new-ui]
 
# Work in the hotfix directory
cd ../hotfix-urgent
git checkout -b hotfix/critical-fix
# Make changes, commit, push
git add . && git commit -m "Fix critical bug"
git push origin hotfix/critical-fix
 
# Return to main project
cd ../project
 
# Remove a worktree when done
git worktree remove ../hotfix-urgent

Parallel development workflow illustration

Real-World Use Cases

Use Case 1: Cleaning Up a Pull Request

The most common interactive rebase use case is cleaning up a feature branch before submitting a pull request. Code reviewers appreciate a clean, logical commit history that tells the story of what was changed and why.

# You have 15 messy commits on your feature branch
git log --oneline main..HEAD
# abc1 WIP: initial attempt
# def2 Fix build error
# 987 Add tests for validator
# 111 Oops, forgot to add file
# 222 Refactor validation logic
# 333 Fix failing test
# ... (9 more commits)
 
# Squash everything into logical commits
git rebase -i main
 
# Reorganize into 3 clean commits:
# pick abc1 Add user validation feature
# squash def2 Fix build error
# squash 987 Add tests for validator
# squash 111 Oops, forgot to add file
# squash 222 Refactor validation logic
# squash 333 Fix failing test
# ... squash remaining
 
# Force push the clean history
git push --force-with-lease origin feature/user-validation

Use Case 2: Finding a Performance Regression

When performance degrades after a release, bisect combined with a benchmark script can find the offending commit efficiently.

# The performance regression exists at HEAD (bad)
# and is absent at v3.2.0 (good)
git bisect start HEAD v3.2.0
 
# Automated benchmark script
cat > /tmp/bench.sh << 'EOF'
#!/bin/bash
npm run build --silent 2>/dev/null
result=$(node -e "
const start = Date.now();
require('./dist/api').handleRequest(mockPayload);
console.log(Date.now() - start);
" 2>/dev/null)
# Fail if response time exceeds 200ms
[ "$result" -gt 200 ] && exit 1 || exit 0
EOF
 
git bisect run /tmp/bench.sh
# Result: commit xyz789 introduced the regression
# "Refactor database queries to use new ORM"

Use Case 3: Hotfix While Deep in Feature Development

Worktrees shine when you are in the middle of a complex feature and need to handle an urgent hotfix without losing your work context.

# You're deep in feature development with uncommitted changes
git stash
 
# Create worktree for hotfix (doesn't affect your current checkout)
git worktree add ../hotfix-123 main
 
# Work on hotfix in separate directory
cd ../hotfix-123
git checkout -b hotfix/fix-login
# Fix the bug, test, commit, push
git push -u origin hotfix/fix-login
 
# Clean up and return to feature work
git worktree remove ../hotfix-123
cd ../project
git stash pop  # Restore your uncommitted feature work

Use Case 4: Testing Against Multiple Branches

Developers often need to test their changes against multiple base branches or verify that a fix works across release versions. Worktrees make this trivial.

Best Practices for Production

  1. Never rebase shared commits: Only rebase commits that have not been pushed to a shared branch, or use --force-with-lease for safe force-pushing to feature branches.
  2. Use reflog as safety net: Before any rebase operation, know that git reflog can recover any previous state. The reflog retains entries for 90 days by default.
  3. Enable autosquash globally: Run git config --global rebase.autosquash true so that --autosquash is always enabled during interactive rebase.
  4. Use bisect scripts for CI failures: When a CI test starts failing, use git bisect run with the failing test command to find the breaking commit in minutes.
  5. Name worktrees semantically: Use descriptive directory names for worktrees like ../hotfix-login-fix or ../feature-new-dashboard to avoid confusion.
  6. Prune stale worktrees: Run git worktree prune periodically to clean up worktree references for directories that have been manually deleted.
  7. Use --force-with-lease over --force: This prevents accidentally overwriting commits that a teammate pushed while you were rebasing.
  8. Keep bisect ranges tight: Start your bisect range as narrow as possible to minimize the number of steps needed.

Common Pitfalls and Solutions

PitfallImpactSolution
Rebasing published commitsCreates duplicate commits for teammatesOnly rebase local/unpushed commits
Merge conflicts during rebaseInterrupted rebase with partial progressResolve conflicts, git add, git rebase --continue
Forgetting reflog existsPanic when rebase goes wrongUse git reflog to find previous HEAD position
Bisect with flaky testsIncorrect bisect resultsUse deterministic tests; run each test 3 times
Worktree directory conflictsCannot create worktree if branch is checked out elsewhereUse git worktree list to check existing worktrees
Losing track of which worktree you're inCommitting to wrong branchUse git worktree list and terminal prompt customization

Performance Optimization

# Configure Git for faster rebases on large repositories
git config rebase.autosquash true
git config rebase.updateRefs true
git config rebase.autoStash true
 
# Use partial clone for large repositories
git clone --filter=blob:none https://github.com/org/repo.git
 
# Bisect with skip for commits that won't build
git bisect skip  # Skip current commit if it can't be tested
git bisect skip abc123 def456  # Skip specific commits

Comparison with Alternatives

FeatureGit RebaseGit MergeGit Squash MergeGit Cherry-Pick
History shapeLinearBranchingSingle commitSelective commits
Preserves commitsCan modifyPreserves as-isSquashes to oneCopies individually
Conflict resolutionPer-commitSingle resolutionSingle resolutionPer-commit
Best forFeature branch cleanupPreserving branch historySimple PR mergeBackporting fixes
ReversibleVia reflogAlwaysNot easilyYes

Advanced Patterns

Bisect with Merge Commits

When bisecting across merge commits, use --first-parent to follow only the main branch lineage, avoiding the complexity of feature branch commits.

# Bisect only first-parent commits (main branch merges)
git bisect start --first-parent HEAD v1.0.0
git bisect run npm test

Multi-Worktree Development Matrix

For complex projects requiring testing across multiple configurations, worktrees combined with scripts create powerful development matrices.

#!/bin/bash
# Create worktrees for cross-branch testing
for branch in main release/v3 release/v2; do
  git worktree add "../test-${branch//\//-}" "$branch"
  cd "../test-${branch//\//-}"
  npm install --silent
  npm test
  cd -
done

Rebase onto Different Base

git rebase --onto lets you transplant commits from one base to another, useful for extracting a subset of changes.

# Move commits from feature-a to be based on main instead
git rebase --onto main feature-a-base feature-a

Future Outlook

Git continues to evolve with performance improvements in large monorepo support, better merge conflict resolution tools, and enhanced worktree functionality. The git rebase --update-refs flag (Git 2.38+) automatically updates dependent branch references during rebase, simplifying stacked PR workflows. Tools like git-branchless and jj (Jujutsu) are exploring next-generation version control that builds on Git's foundation while addressing some of its rough edges around history rewriting and conflict resolution.

Conclusion

Advanced Git features—interactive rebase, bisect, and worktrees—transform Git from a simple save system into a powerful development toolkit. Interactive rebase gives you control over your commit narrative, ensuring that shared history is clean, logical, and reviewable. Git bisect turns bug hunting from a needle-in-a-haystack search into a systematic, logarithmic process that finds regressions in minutes instead of hours. Git worktrees eliminate the friction of context-switching by letting you work on multiple branches simultaneously without the overhead of stashing or cloning.

The key takeaways are: practice interactive rebase on feature branches before sharing them, write bisect scripts for any test you can run from the command line, and use worktrees whenever you need to context-switch between branches. Combined with the reflog as your safety net, these tools give you the confidence to manipulate Git history fearlessly and work on parallel tasks efficiently.