ADVANCED GIT WORKFLOWS Rebase Squash Reword rebase -i Bisect Binary Search Bug Hunting bisect good/bad Hooks pre-commit commit-msg .git/hooks/ Reflog Recovery Undo Mistakes reflog expire Worktree Parallel Dev Multi-branch worktree add git rebase | git bisect | git hooks | git reflog | git worktree Master advanced Git commands for professional development workflows

Most developers learn git add, git commit, and git push — and then stop there. But Git is an extraordinarily powerful tool with commands that can save you hours of work, help you hunt down bugs in seconds, automate code quality enforcement, and recover from mistakes that would otherwise be catastrophic. This guide covers the advanced Git workflows that separate everyday users from power users: interactive rebase, bisect, hooks, reflog recovery, cherry-pick, worktrees, and more.

Who this is for: This article assumes you are comfortable with basic Git operations (commit, push, pull, branch, merge). If you are still learning those fundamentals, start there first and return when you are ready to level up.

Prerequisites

Before diving into advanced workflows, make sure you have:

  • Git 2.23 or later installed (run git --version to check)
  • A repository with at least 20-30 commits to practice on
  • Familiarity with basic branching and merging
  • A terminal you are comfortable with (Bash, Zsh, PowerShell)
# Check your Git version
git --version

# Update Git on Ubuntu/Debian
sudo apt update && sudo apt install git

# Update Git on macOS
brew upgrade git

Tip: Create a throwaway repository to practice these commands. Many of them rewrite history, and you want to build confidence before using them on production code.

Interactive Rebase

Interactive rebase is arguably the most powerful commit-editing tool in Git. It lets you rewrite, combine, reorder, and delete commits before pushing them to a shared branch.

Starting an Interactive Rebase

To modify the last 5 commits:

git rebase -i HEAD~5

This opens your editor with a list of commits, oldest first:

pick a1b2c3d Add user authentication
pick e4f5g6h Fix typo in login form
pick i7j8k9l Add password reset feature
pick m0n1o2p Fix lint errors
pick q3r4s5t Update README

Rebase Commands

Each commit line starts with a command. The most useful ones are:

  • pick — Keep the commit as-is
  • reword — Keep the commit but change the commit message
  • squash — Combine this commit with the previous one (keeps both messages)
  • fixup — Combine with the previous commit (discards this commit’s message)
  • edit — Pause the rebase at this commit so you can amend it
  • drop — Remove this commit entirely

Squashing Commits

The most common use of interactive rebase is squashing multiple small commits into one meaningful commit:

pick a1b2c3d Add user authentication
fixup e4f5g6h Fix typo in login form
pick i7j8k9l Add password reset feature
fixup m0n1o2p Fix lint errors
drop q3r4s5t Update README

This produces two clean commits instead of five messy ones.

Reordering Commits

Simply rearrange the lines in your editor to change the commit order:

pick i7j8k9l Add password reset feature
pick a1b2c3d Add user authentication

Warning: Never rebase commits that have already been pushed to a shared branch. Rewriting shared history causes conflicts for everyone on your team. Only rebase your local, unpushed commits.

Autosquash with Fixup Commits

If you know a commit is a fix for a previous one, mark it at commit time:

# Create a fixup commit that auto-squashes into the target commit
git commit --fixup=a1b2c3d

# Later, autosquash during rebase
git rebase -i --autosquash HEAD~10

Git automatically reorders fixup commits next to their targets and marks them with fixup.

Git Bisect: Binary Search for Bugs

When a bug appears and you do not know which commit introduced it, testing every commit one by one is impractical. git bisect performs a binary search, finding the exact commit in O(log n) steps.

Manual Bisect

# Start bisecting
git bisect start

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

# Mark a known good commit (before the bug existed)
git bisect good v2.0.0

# Git checks out the midpoint -- test it
# If this commit has the bug:
git bisect bad

# If this commit does not have the bug:
git bisect good

# Repeat until Git identifies the first bad commit
# When done:
git bisect reset

For a range of 1,024 commits, bisect finds the culprit in about 10 steps.

Automated Bisect with a Script

If you have a test script that returns exit code 0 for good and non-zero for bad:

git bisect start
git bisect bad HEAD
git bisect good v2.0.0

# Run the test automatically at each step
git bisect run ./test-script.sh

# Or use a specific test command
git bisect run npm test -- --grep "login feature"

Git runs the script at each midpoint and identifies the offending commit without any manual intervention.

Bisect with Complex Criteria

# Skip commits that cannot be tested (e.g., broken build)
git bisect skip

# View the bisect log
git bisect log

# Replay a bisect session from a log file
git bisect replay bisect-log.txt

Cherry-Pick Strategies

Cherry-pick applies a specific commit from one branch to another without merging the entire branch. It is useful for hotfixes, backporting, and selective feature integration.

Basic Cherry-Pick

# Apply a single commit to your current branch
git cherry-pick abc1234

# Apply multiple commits
git cherry-pick abc1234 def5678 ghi9012

# Apply a range of commits (exclusive of the first)
git cherry-pick abc1234..ghi9012

Cherry-Pick Without Committing

# Stage the changes but do not commit (useful for combining multiple picks)
git cherry-pick --no-commit abc1234
git cherry-pick --no-commit def5678

# Review the combined changes, then commit
git commit -m "Backport: authentication fixes from develop"

Handling Cherry-Pick Conflicts

# If a conflict occurs during cherry-pick:
# 1. Resolve the conflicts in the affected files
# 2. Stage the resolved files
git add resolved-file.js

# 3. Continue the cherry-pick
git cherry-pick --continue

# Or abort the entire cherry-pick
git cherry-pick --abort

Cherry-Pick for Hotfixes

A common pattern for production hotfixes:

# On the main branch, fix the bug
git checkout main
git checkout -b hotfix/login-crash

# Make the fix and commit
git commit -am "Fix: prevent null pointer in login handler"

# Merge to main
git checkout main
git merge hotfix/login-crash

# Cherry-pick the fix to the develop branch
git checkout develop
git cherry-pick <hotfix-commit-sha>

Git Hooks

Git hooks are scripts that run automatically at specific points in the Git lifecycle. They enforce code quality, validate commit messages, and prevent bad code from entering the repository.

Hook Types and Locations

Hooks live in .git/hooks/. The most commonly used hooks are:

HookTriggerCommon Use
pre-commitBefore a commit is createdLinting, formatting, tests
commit-msgAfter the commit message is enteredValidate message format
pre-pushBefore pushing to a remoteRun full test suite
post-mergeAfter a merge completesInstall dependencies
pre-rebaseBefore a rebase startsPrevent rebasing shared branches

Pre-Commit Hook: Linting

#!/bin/sh
# .git/hooks/pre-commit

# Run ESLint on staged JavaScript files
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.js$')

if [ -n "$STAGED_FILES" ]; then
    echo "Running ESLint on staged files..."
    npx eslint $STAGED_FILES
    if [ $? -ne 0 ]; then
        echo "ESLint failed. Fix errors before committing."
        exit 1
    fi
fi

# Run Prettier check
STAGED_ALL=$(git diff --cached --name-only --diff-filter=ACM)
if [ -n "$STAGED_ALL" ]; then
    npx prettier --check $STAGED_ALL
    if [ $? -ne 0 ]; then
        echo "Prettier check failed. Run 'npx prettier --write .' first."
        exit 1
    fi
fi

exit 0

Commit-Msg Hook: Conventional Commits

#!/bin/sh
# .git/hooks/commit-msg

COMMIT_MSG_FILE=$1
COMMIT_MSG=$(cat "$COMMIT_MSG_FILE")

# Enforce Conventional Commits format
PATTERN="^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?: .{1,72}"

if ! echo "$COMMIT_MSG" | grep -qE "$PATTERN"; then
    echo "ERROR: Commit message does not follow Conventional Commits format."
    echo ""
    echo "Expected: <type>(<scope>): <description>"
    echo "Example:  feat(auth): add password reset endpoint"
    echo ""
    echo "Valid types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert"
    exit 1
fi

exit 0

Pre-Push Hook: Test Suite

#!/bin/sh
# .git/hooks/pre-push

echo "Running test suite before push..."
npm test

if [ $? -ne 0 ]; then
    echo "Tests failed. Push aborted."
    echo "Fix failing tests before pushing."
    exit 1
fi

echo "All tests passed. Pushing..."
exit 0

Sharing Hooks with Your Team

Hooks in .git/hooks/ are not tracked by Git. To share them:

# Option 1: Store hooks in a tracked directory
mkdir -p .githooks
cp .git/hooks/pre-commit .githooks/

# Configure Git to use the tracked directory
git config core.hooksPath .githooks

# Option 2: Use Husky (Node.js projects)
npx husky install
npx husky add .husky/pre-commit "npx lint-staged"

Git Reflog and Recovery

The reflog is your safety net. It records every change to HEAD — commits, rebases, resets, checkouts — for at least 30 days. If you lose commits, the reflog is how you get them back.

Viewing the Reflog

# View the reflog
git reflog

# Output looks like:
# a1b2c3d HEAD@{0}: commit: Add new feature
# e4f5g6h HEAD@{1}: rebase -i (finish): returning to refs/heads/main
# i7j8k9l HEAD@{2}: rebase -i (squash): Add user auth
# m0n1o2p HEAD@{3}: reset: moving to HEAD~3

Recovering After a Bad Reset

# You accidentally ran git reset --hard HEAD~3 and lost 3 commits
# Step 1: Find the lost commit in the reflog
git reflog

# Step 2: The commit before the reset is at HEAD@{1}
git checkout HEAD@{1}

# Step 3: Create a branch to save it
git checkout -b recovered-work

# Or cherry-pick specific commits back
git cherry-pick a1b2c3d

Recovering After a Bad Rebase

# If a rebase went wrong, find the state before the rebase
git reflog

# Look for the entry just before "rebase -i (start)"
# Reset back to that point
git reset --hard HEAD@{5}

Recovering Deleted Branches

# You deleted a branch with important work
git branch -D feature/important-work

# Find the branch tip in the reflog
git reflog | grep "important-work"

# Recreate the branch at the found SHA
git branch feature/important-work abc1234

Important: Reflog entries expire after 90 days (30 days for unreachable commits). If you discover lost work, recover it sooner rather than later. You can configure the expiry with git config gc.reflogExpire.

Git Worktree: Parallel Development

Worktrees let you check out multiple branches simultaneously in separate directories. No more stashing, committing, or switching branches to review a pull request or fix a hotfix.

Creating a Worktree

# Check out a branch in a new directory
git worktree add ../hotfix-branch hotfix/login-fix

# Create a new branch in a worktree
git worktree add -b feature/new-api ../new-api-work

# List all worktrees
git worktree list

Practical Worktree Workflow

# Your main working directory has develop checked out
# A production bug comes in -- work on it without disrupting your current work

git worktree add ../prod-fix main
cd ../prod-fix

# Fix the bug on a new branch
git checkout -b hotfix/critical-fix
# ... make changes, commit, push ...

# Return to your feature work
cd ../my-project
# develop is still exactly where you left it

# Clean up when done
git worktree remove ../prod-fix

Worktree for Code Review

# Review a colleague's PR without leaving your current branch
git fetch origin
git worktree add ../review-pr-42 origin/feature/user-dashboard

# Open the worktree in your editor, review the code
# When done:
git worktree remove ../review-pr-42

Stash Advanced Usage

Beyond basic git stash, there are powerful options for managing work-in-progress changes.

Named Stashes

# Stash with a descriptive message
git stash push -m "WIP: refactoring auth middleware"

# List all stashes
git stash list
# stash@{0}: On develop: WIP: refactoring auth middleware
# stash@{1}: On develop: WIP: database migration script

Partial Stashing

# Stash only specific files
git stash push -m "stash auth changes" src/auth.js src/middleware.js

# Interactive stash -- choose individual hunks
git stash push -p -m "partial stash"

Stash Apply vs. Pop

# Apply keeps the stash in the list (safe to try again)
git stash apply stash@{0}

# Pop applies and removes the stash from the list
git stash pop stash@{0}

# Apply stash to a different branch
git checkout feature/other-branch
git stash apply stash@{0}

Create a Branch from a Stash

# Create a new branch and apply the stash to it
git stash branch new-feature-branch stash@{0}

This is particularly useful when the stash conflicts with changes on the current branch.

Git Blame and Log Forensics

When you need to understand why a line of code exists or track down when a change was introduced, Git’s forensics tools are indispensable.

Git Blame

# See who last modified each line
git blame src/auth.js

# Blame a specific line range
git blame -L 10,20 src/auth.js

# Ignore whitespace changes
git blame -w src/auth.js

# Show the commit that moved or copied the line from another file
git blame -C src/auth.js

Advanced Log Searches

# Search commit messages for a keyword
git log --grep="authentication" --oneline

# Search for changes to a specific function
git log -p -S "function loginUser" -- src/

# Search for changes matching a regex
git log -p -G "TODO|FIXME|HACK"

# View history of a specific file
git log --follow --oneline -- src/auth.js

# Show commits by a specific author in a date range
git log --author="JC" --after="2025-01-01" --before="2025-12-31" --oneline

Visual Log Formats

# Compact branch graph
git log --oneline --graph --all --decorate

# Custom format with date and author
git log --pretty=format:"%h %ad | %s%d [%an]" --date=short

# Show statistics per commit
git log --stat --oneline -10

Branching Strategies

Choosing the right branching strategy affects your team’s velocity and code quality.

Git Flow

Git Flow uses long-lived branches for different stages:

# Main branches
main        # Production-ready code
develop     # Integration branch for features

# Supporting branches
feature/*   # New features (branch from develop)
release/*   # Release preparation (branch from develop)
hotfix/*    # Production fixes (branch from main)
# Start a feature
git checkout develop
git checkout -b feature/user-dashboard

# Finish a feature
git checkout develop
git merge --no-ff feature/user-dashboard
git branch -d feature/user-dashboard

Best for: Teams with scheduled releases and multiple environments.

GitHub Flow

A simpler alternative with one rule: anything in main is deployable.

# Create a branch from main
git checkout main
git checkout -b feature/add-search

# Work, commit, push
git push -u origin feature/add-search

# Open a Pull Request, get review, merge to main
# Deploy happens automatically from main

Best for: Teams practicing continuous deployment with short-lived branches.

Trunk-Based Development

All developers commit to main (the trunk) frequently, using short-lived feature branches of one to two days maximum.

# Short-lived branch (1-2 days max)
git checkout -b feature/small-change
# ... minimal changes ...
git push -u origin feature/small-change
# Merge to main quickly via PR

# Feature flags control unreleased features
if (featureFlags.newDashboard) {
    renderNewDashboard();
}

Best for: High-velocity teams with strong CI/CD pipelines and feature flag infrastructure.

Advanced Git Commands Reference Table

CommandDescriptionExample
git rebase -i HEAD~NInteractive rebase for last N commitsgit rebase -i HEAD~5
git bisect startStart binary search for bugsgit bisect start
git bisect run <script>Automated bisect with a test scriptgit bisect run npm test
git cherry-pick <SHA>Apply a specific commit to current branchgit cherry-pick a1b2c3d
git cherry-pick --no-commitCherry-pick without auto-committinggit cherry-pick --no-commit a1b2c3d
git reflogShow history of HEAD movementsgit reflog
git worktree add <path> <branch>Check out a branch in a separate directorygit worktree add ../fix main
git stash push -m "msg"Stash changes with a descriptive messagegit stash push -m "WIP auth"
git stash push -pInteractively stash selected hunksgit stash push -p
git blame -L 10,20 <file>Blame specific line rangegit blame -L 10,20 auth.js
git log -S "string"Find commits that add/remove a stringgit log -S "loginUser"
git log -G "regex"Find commits matching a regex in diffsgit log -G "TODO|FIXME"
git commit --fixup=<SHA>Create a fixup commit for autosquashgit commit --fixup=a1b2c3d
git config core.hooksPathSet a custom hooks directorygit config core.hooksPath .githooks

Troubleshooting

Rebase Conflicts Won’t Resolve

# If a rebase has too many conflicts, abort and try a different approach
git rebase --abort

# Alternative: use merge instead of rebase for complex histories
git merge develop

Bisect Gives Wrong Results

# If you marked a commit incorrectly, view the log and replay
git bisect log > bisect-log.txt
# Edit the log file to remove the wrong entry
git bisect reset
git bisect replay bisect-log.txt

Hook Scripts Not Executing

# Ensure the hook file is executable
chmod +x .git/hooks/pre-commit

# Verify the shebang line is correct
head -1 .git/hooks/pre-commit
# Should be: #!/bin/sh or #!/bin/bash

# Check if core.hooksPath overrides the default location
git config --get core.hooksPath

Cannot Delete a Worktree

# Force removal of a worktree with uncommitted changes
git worktree remove --force ../old-worktree

# Prune stale worktree entries
git worktree prune

Reflog Entry Not Found

# Increase the reflog expiry time before it is too late
git config gc.reflogExpire 180.days
git config gc.reflogExpireUnreachable 90.days

# If reflog is empty, try fsck to find dangling commits
git fsck --unreachable --no-reflogs

Cherry-Pick Results in Empty Commit

# The changes already exist on the target branch
# Use --allow-empty to keep the commit, or skip it
git cherry-pick --skip

# Or check if the change was already applied
git log --oneline --all -- path/to/changed/file

Summary

Advanced Git workflows transform how you manage code. Interactive rebase keeps your commit history clean and meaningful. Git bisect turns a manual hunt for bugs into an automated binary search. Git hooks enforce code quality standards before bad code enters the repository. Reflog acts as your safety net, making virtually every Git mistake recoverable. Cherry-pick and worktrees provide the flexibility to work across branches without disruption.

The key is practice. Create a test repository, make dozens of commits, and try every command in this guide. Once these tools become second nature, you will handle complex Git situations with confidence.

For automating your testing and deployment pipelines after mastering these Git workflows, read our guide on Getting Started with GitHub Actions for CI/CD.