Skip to main content
← All Articles · · 13 min read

Advanced Git Workflows in 2026: Worktrees, Bisect, Interactive Rebase, and More

Master advanced Git workflows that most developers never use. Git worktrees for parallel branches, git bisect for hunting bugs, interactive rebase for clean history, sparse checkout for monorepos, and power-user config tips.

gitgit-worktreegit-bisectinteractive-rebasesparse-checkoutdeveloper-productivityversion-controlmonorepo

Most developers use git as a fancy save button: add, commit, push, pull. That’s fine — it works. But git has a second layer of power tools that can genuinely change how you work, not just wrap it in nicer abstractions.

This isn’t about git’s esoteric internals. These are practical workflows that solve real problems: working on multiple branches without context-switching chaos, finding the commit that introduced a bug in a codebase with 5,000 commits, cleaning up a messy commit history before code review, checking out a monorepo without downloading 30GB of history.


Git Worktrees: Work on Multiple Branches Simultaneously

The problem: you’re deep in a feature branch, 20 files modified, and an urgent bug comes in. Your options are git stash (losing context), a new clone (expensive), or context-switching misery.

Git worktrees give you a third option: multiple working directories, one repository. Each worktree checks out a different branch independently. No stashing. No re-cloning.

Basic Usage

# Add a worktree for a hotfix branch
git worktree add ../myapp-hotfix hotfix/critical-bug

# If the branch doesn't exist yet
git worktree add -b hotfix/new-bug ../myapp-hotfix main

# List all worktrees
git worktree list
# /home/user/myapp         abc1234 [feature/new-dashboard]
# /home/user/myapp-hotfix  def5678 [hotfix/critical-bug]

Now you have two directories: your current feature branch in myapp, and the hotfix in myapp-hotfix. Both share the same git history and objects. Modifying files in one doesn’t affect the other.

Workflow: Parallel Feature Development

# Main development directory
cd ~/myapp

# Create worktrees for parallel features
git worktree add ../myapp-auth feature/auth-refactor
git worktree add ../myapp-payments feature/payments-v2

# Each directory is independently checkable:
cd ~/myapp-auth && npm run dev    # Auth feature dev server on port 3000
cd ~/myapp-payments && npm run dev  # Payments on port 3001

Cleanup

# Remove a worktree when done
git worktree remove ../myapp-hotfix

# Force remove if it has uncommitted changes
git worktree remove --force ../myapp-hotfix

# Prune stale worktree references
git worktree prune

Limitations: You can’t check out the same branch in two worktrees simultaneously. If you need the same branch in two locations, create a new branch from it.

Pro tip: In a monorepo, create a worktree per team. The frontend team works in ../repo-fe on team/frontend, backend in ../repo-be on team/backend — same repo, isolated contexts, shared history.


git bisect: Binary Search for Bugs in Commit History

You know a bug exists in the current version. You know it didn’t exist three months ago. There are 847 commits between then and now. How do you find which one introduced the bug?

git bisect does a binary search through commit history. You mark one commit as “good” (no bug) and one as “bad” (has bug). Git checks out the midpoint. You test it. Mark it good or bad. Repeat. You find the culprit in ~10 steps no matter how many commits there are.

Manual bisect

# Start bisect session
git bisect start

# Mark current commit (has the bug) as bad
git bisect bad

# Mark a commit from 3 months ago as good
git bisect good abc1234

# Git checks out the midpoint commit
# You test it...

# If the bug exists here:
git bisect bad

# If the bug doesn't exist here:
git bisect good

# Repeat until git identifies the first bad commit:
# `b3c4d5e is the first bad commit`

# Always reset when done
git bisect reset

Automated bisect with a test script

The real power is automation. Write a script that exits 0 for “good” and 1 for “bad”, and git bisect runs it automatically:

# test-bug.sh
#!/bin/bash
npm install --silent
npm run build --silent

# Run the specific test that catches the bug
npx jest tests/feature.test.js --silent
exit $?
git bisect start
git bisect bad HEAD
git bisect good v2.1.0
git bisect run ./test-bug.sh

Git will automatically check out commits, run your script, and identify the exact first bad commit — typically in 10-15 iterations for thousands of commits.

Tips for effective bisect:

  • Your test script must be deterministic. Flaky tests will mislead the search.
  • Use git bisect skip if a commit is untestable (broken build, unrelated issue).
  • Always git bisect reset when done — bisect leaves you in a detached HEAD state.

Interactive Rebase: Squash, Reorder, and Edit Commits Like a Pro

Your feature branch has 12 commits: “WIP”, “fix typo”, “actually fix it this time”, “reviewer feedback”, “more feedback”, “oops”, “final”. Interactive rebase lets you rewrite that history into something clean before merging.

Basic interactive rebase

# Rebase the last 12 commits interactively
git rebase -i HEAD~12

# Or rebase against the main branch
git rebase -i main

This opens your editor with a list of commits and commands:

pick a1b2c3 Add authentication middleware
pick d4e5f6 WIP
pick g7h8i9 fix typo
pick j0k1l2 actually fix it this time
pick m3n4o5 Add unit tests
pick p6q7r8 reviewer feedback: rename method
pick s9t0u1 reviewer feedback: add validation
pick v2w3x4 oops typo again
pick y5z6a7 final cleanup

# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the message
# e, edit = use commit, but stop for amending
# s, squash = merge into previous commit, keep message
# f, fixup = merge into previous commit, discard message
# d, drop = remove commit

Edit it to:

pick a1b2c3 Add authentication middleware
f d4e5f6 WIP
f g7h8i9 fix typo
f j0k1l2 actually fix it this time
pick m3n4o5 Add unit tests
f p6q7r8 reviewer feedback: rename method
f s9t0u1 reviewer feedback: add validation
f v2w3x4 oops typo again
f y5z6a7 final cleanup

Save and close. Git squashes those fixup commits into their parent, giving you 2 clean commits: “Add authentication middleware” and “Add unit tests”.

Reorder commits

# Swap the order of two commits
# Before:
pick abc Add button component
pick def Add tests for header

# After:
pick def Add tests for header
pick abc Add button component

Edit a specific past commit

git rebase -i HEAD~5
# Change 'pick' to 'edit' on the target commit
# Git pauses at that commit

# Make your changes
git add .
git commit --amend

# Continue
git rebase --continue

Golden rule: don’t rebase shared branches

Interactive rebase rewrites commit history (changes SHAs). If you’ve already pushed the branch and someone else has pulled it, you’ll cause conflicts. Only rebase:

  • Local branches that haven’t been pushed
  • Feature branches you own (force push with --force-with-lease after rebase)

Never rebase main, develop, or any branch multiple people are working on.


Sparse Checkout: Clone Only the Parts You Need

Monorepos are great for code sharing and unified tooling. They’re terrible for disk space and clone time when you only work on one package.

Sparse checkout lets you check out a subset of a repository’s files:

# Clone without checking out any files
git clone --no-checkout https://github.com/my-org/monorepo.git
cd monorepo

# Enable sparse checkout
git sparse-checkout init --cone

# Specify which directories to include
git sparse-checkout set packages/frontend packages/shared

# Now checkout
git checkout main

You have only packages/frontend and packages/shared on disk. All git operations still work — commits, pulls, pushes — but you only see and work with your subset of the repo.

Modify the sparse checkout set

# Add more directories
git sparse-checkout add packages/api

# See current set
git sparse-checkout list

# Disable sparse checkout (check out everything)
git sparse-checkout disable

Real use case: A monorepo with 50 packages, total 8GB. You work on the mobile-app package which is 200MB. Sparse checkout gets your clone from 8GB to ~500MB (your package + shared utilities).


git bundle: Offline Repository Transfer

git bundle packages repository history into a single binary file. Useful when:

  • Transferring a repo to a machine with no network access
  • Creating an offline backup of a specific branch
  • Sharing changes with someone who can’t access your remote
# Create a bundle of the entire main branch
git bundle create repo-main.bundle main

# Create a bundle of only changes since a specific commit
git bundle create repo-updates.bundle abc1234..main

# Verify a bundle is valid
git bundle verify repo-main.bundle

# Clone from a bundle
git clone repo-main.bundle local-clone

# Fetch from a bundle (treating it like a remote)
git fetch repo-main.bundle main:main

For incremental updates, use --since or a commit range:

# Weekly backup: all commits in the last 7 days
git bundle create weekly-$(date +%Y%m%d).bundle --since=7.days main

git notes and git reflog for Disaster Recovery

git reflog: Your Safety Net

git reflog records every change to HEAD, including operations that aren’t in your commit history: resets, rebases, checkouts, amends.

# Show the reflog
git reflog

# Output:
# abc1234 HEAD@{0}: commit: Add new feature
# def5678 HEAD@{1}: rebase -i (finish): returning to refs/heads/feature
# ghi9012 HEAD@{2}: rebase -i (squash): Add auth middleware
# jkl3456 HEAD@{3}: rebase -i (start): checkout HEAD~5
# mno7890 HEAD@{4}: commit: oops, wrong file

# Recover a commit you "lost" in a rebase or reset
git checkout HEAD@{4}  # Check out the "lost" commit
git checkout -b recovery-branch  # Save it to a branch

Practical scenarios:

# You ran `git reset --hard` and lost commits
git reflog
# Find the SHA before the reset
git reset --hard abc1234  # Restore to before the reset

# You rebased and now can't find the old commits
git reflog --all  # Shows reflog for all branches
git checkout -b before-rebase def5678

The reflog is local only and expires (default 90 days for reachable commits, 30 days for unreachable). Use it for recovery, but don’t rely on it as a backup.

git notes: Attach Metadata Without Amending Commits

git notes lets you attach arbitrary text to commits without rewriting them. Useful for adding context (code review links, deployment records, benchmark results) after the fact:

# Add a note to the current commit
git notes add -m "Deployed to production at 14:32 UTC. Jira: PROJ-1234"

# Add a note to a specific commit
git notes add -m "Benchmarked: 40% improvement in query time" abc1234

# View notes
git log --show-notes

# Notes aren't pushed by default — push them explicitly
git push origin refs/notes/*

# Fetch notes from remote
git fetch origin refs/notes/*:refs/notes/*

Power User Config: Useful Aliases and git config Tips

A well-configured git reduces friction across every command you run:

# ~/.gitconfig

[alias]
  # Compact log with graph
  lg = log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit

  # Undo last commit but keep changes staged
  undo = reset --soft HEAD~1

  # Show changed files in last commit
  last = diff HEAD~1 HEAD --name-only

  # Interactive branch switcher (requires fzf)
  sw = "!git branch --sort=-committerdate | fzf --height 20% | xargs git checkout"

  # Quickly stash with a message
  save = "!f() { git stash push -m \"$1\"; }; f"

  # Prune local branches that are deleted on remote
  gone = "!git fetch --prune && git branch -vv | awk '/: gone]/{print $1}' | xargs git branch -D"

  # Show all aliases
  aliases = config --get-regexp alias

[core]
  # Set your preferred editor
  editor = code --wait

  # Better whitespace handling
  whitespace = fix,space-before-tab,tab-in-indent,cr-at-eol

  # Faster status in large repos
  fsmonitor = true
  untrackedCache = true

[diff]
  # Better diff algorithm (detects moved blocks)
  algorithm = histogram

  # Show moved lines in different color
  colorMoved = default

[merge]
  # Show conflict context from both sides
  conflictstyle = zdiff3

[rebase]
  # Automatically stash before rebase and pop after
  autostash = true

  # Squash fixup! commits automatically
  autosquash = true

[push]
  # Push current branch to remote with same name (no need to specify)
  default = current
  autoSetupRemote = true

[pull]
  # Always rebase on pull (avoids merge commits)
  rebase = true

[fetch]
  # Always prune deleted remote branches on fetch
  prune = true

The autosquash trick

With rebase.autosquash = true, commits named fixup! <original commit message> are automatically squashed during interactive rebase:

# Make a fix for a previous commit
git commit -m "fixup! Add authentication middleware"

# On `git rebase -i main`, the fixup commit is automatically placed and marked
# You just save and close — no manual editing needed

Combined with this alias:

[alias]
  fixup = "!git log --oneline | fzf | cut -d' ' -f1 | xargs -I{} git commit --fixup={}"

You can interactively pick which commit to fixup with fuzzy search.


Putting It All Together

These aren’t obscure commands — they’re the tools that distinguish engineers who master their toolchain from those who fight it:

  • Worktrees eliminate the “stash everything before switching branches” tax
  • Bisect turns a day-long bug hunt into 10 minutes of binary search
  • Interactive rebase turns your messy “WIP” history into clean commits before code review
  • Sparse checkout makes monorepos actually usable at scale
  • Reflog is the undo button you didn’t know you had

Learn one per week. Within a month, you’ll wonder how you worked without them.


Further Reading

Free Download

50 CLI Commands That Save Hours Every Week

The exact Git shortcuts, file search tricks, Docker commands, and productivity hacks that senior developers use daily. Compiled from 5+ years of shipping production code.

  • Git mastery (10 cmds)
  • File & search wizardry
  • Docker & DevOps tricks
  • Process & network tools
  • Productivity boosters
  • macOS & Linux install guide

No spam. 1 email/week max. Unsubscribe anytime.

Found this guide useful? Check out our free developer tools.