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 --versionto 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:
| Hook | Trigger | Common Use |
|---|---|---|
pre-commit | Before a commit is created | Linting, formatting, tests |
commit-msg | After the commit message is entered | Validate message format |
pre-push | Before pushing to a remote | Run full test suite |
post-merge | After a merge completes | Install dependencies |
pre-rebase | Before a rebase starts | Prevent 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
| Command | Description | Example |
|---|---|---|
git rebase -i HEAD~N | Interactive rebase for last N commits | git rebase -i HEAD~5 |
git bisect start | Start binary search for bugs | git bisect start |
git bisect run <script> | Automated bisect with a test script | git bisect run npm test |
git cherry-pick <SHA> | Apply a specific commit to current branch | git cherry-pick a1b2c3d |
git cherry-pick --no-commit | Cherry-pick without auto-committing | git cherry-pick --no-commit a1b2c3d |
git reflog | Show history of HEAD movements | git reflog |
git worktree add <path> <branch> | Check out a branch in a separate directory | git worktree add ../fix main |
git stash push -m "msg" | Stash changes with a descriptive message | git stash push -m "WIP auth" |
git stash push -p | Interactively stash selected hunks | git stash push -p |
git blame -L 10,20 <file> | Blame specific line range | git blame -L 10,20 auth.js |
git log -S "string" | Find commits that add/remove a string | git log -S "loginUser" |
git log -G "regex" | Find commits matching a regex in diffs | git log -G "TODO|FIXME" |
git commit --fixup=<SHA> | Create a fixup commit for autosquash | git commit --fixup=a1b2c3d |
git config core.hooksPath | Set a custom hooks directory | git 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.