git-advanced
Expert-level Git covering interactive rebase, bisect, reflog, worktrees, hooks, patch workflows, and power-user configuration. Use when cleaning up commit history, hunting down regressions with bisect, recovering lost commits via reflog, managing
Installation
npx clawhub@latest install git-advancedView the full skill documentation and source below.
Documentation
Git Advanced
Git's power comes not from knowing the basics but from understanding the object model: every
commit is a snapshot, not a diff; the reflog is an append-only safety net; branches are just
pointers. With this mental model, "dangerous" operations like rebase and force-push become
routine, and history becomes a tool for communication, not just a record.
Core Mental Model
Commits are immutable snapshots stored as content-addressed objects. Branches are cheap
pointers. The staging area (index) is a feature, not a burden — it lets you craft precise,
atomic commits. History is for your team and your future self: rewrite it before sharing, but
never rewrite shared history. The reflog means you can almost never truly lose work.
Interactive Rebase — History Surgery
# Rebase last 5 commits interactively
git rebase -i HEAD~5
# Rebase onto a specific commit
git rebase -i abc1234
# The rebase editor shows:
# pick e3d8a1f Add agent registration
# pick f2c7b8d WIP: fix validation
# pick 1a5c9e2 Fix typo
# pick 3b4d0f1 More WIP
# pick 7e8f2a3 feat: add agent profile
# Operations:
# pick = use commit as-is
# reword = use commit, edit message
# edit = pause to amend the commit
# squash = meld into previous commit, combine messages
# fixup = meld into previous, discard this message
# drop = remove commit
# Example: clean up WIP commits before PR
# pick e3d8a1f Add agent registration
# squash f2c7b8d WIP: fix validation ← meld into above
# squash 1a5c9e2 Fix typo ← meld into above
# squash 3b4d0f1 More WIP ← meld into above
# pick 7e8f2a3 feat: add agent profile
# Rebase onto a different base branch
git rebase main feature/agent-email
# Interactive rebase with autosquash (squash! and fixup! commits)
git commit --fixup HEAD # create fixup commit for last commit
git rebase -i --autosquash HEAD~3 # automatically reorders fixup commits
git bisect — Binary Search for Bugs
# Start bisect session
git bisect start
git bisect bad # current commit is broken
git bisect good v2.1.0 # this tag was known-good
# Git checks out a commit halfway between good and bad
# Test it (run tests, check behavior)
git bisect good # or
git bisect bad
# After ~10 bisect steps for 1000 commits: first bad commit identified
# Git prints: "abc1234 is the first bad commit"
# Automated bisect with a test script
git bisect start HEAD v2.1.0
git bisect run bash -c 'npm test 2>/dev/null | grep -q "PASS" && exit 0 || exit 1'
# Git runs the script on each candidate and finds the culprit
# End bisect session (returns to original HEAD)
git bisect reset
Reflog — Your Safety Net
# See all HEAD movements (branch switches, resets, rebases, amends)
git reflog
# HEAD@{0}: rebase finished: returning to refs/heads/feature
# HEAD@{1}: rebase -i (squash): feat: add agent profile
# HEAD@{2}: commit: WIP: fix validation
# HEAD@{3}: checkout: moving from main to feature/agent-profile
# Recover a dropped commit
git reflog --all | grep "drop" # find the dropped commit hash
git checkout abc1234 # verify it's what you want
git cherry-pick abc1234 # apply it back
# Undo a bad rebase
git reflog # find the SHA before rebase started
git reset --hard HEAD@{4} # jump back to that state
# Recover after accidental branch delete
git reflog | grep "feature/old-branch"
git checkout -b feature/old-branch abc1234 # recreate from SHA
# Reflog expires (90 days default); force expire for cleanup
git reflog expire --expire=now --all
git gc --prune=now
Cherry-Pick vs Merge vs Rebase Decision Tree
Situation → Use this
──────────────────────────────────────────────────────
Pick 1-3 specific commits from another branch → cherry-pick
Integrate all of feature branch into main → merge (preserves branch history)
Keep feature branch up-to-date with main → rebase feature onto main
Prepare feature branch for PR review → rebase + interactive to clean history
Move commit to different branch accidentally → cherry-pick + reset on source
Hotfix needs to go to both main and release → cherry-pick to both
# Cherry-pick a single commit
git cherry-pick abc1234
# Cherry-pick a range (exclusive..inclusive)
git cherry-pick abc1234..def5678
# Cherry-pick without committing (apply changes only)
git cherry-pick --no-commit abc1234
# Cherry-pick merge commit (specify mainline parent)
git cherry-pick -m 1 abc1234 # -m 1 = use first parent as mainline
Git Worktrees — Parallel Branch Work
# Create a worktree for a branch without switching from current work
git worktree add ../moltbot-den-hotfix hotfix/critical-bug
git worktree add ../moltbot-den-feature feature/new-api
# Create a worktree for a new branch
git worktree add -b feature/agent-email ../moltbot-den-email main
# List all worktrees
git worktree list
# /home/will/Dev/moltbot-den abc1234 [main]
# /home/will/Dev/moltbot-den-hotfix def5678 [hotfix/critical-bug]
# /home/will/Dev/moltbot-den-email 789abc0 [feature/agent-email]
# Remove when done
git worktree remove ../moltbot-den-hotfix
git worktree prune # cleanup stale worktree metadata
Git Hooks
# pre-commit: lint and type-check before committing
# .git/hooks/pre-commit (make executable: chmod +x)
#!/usr/bin/env bash
set -euo pipefail
# Run only on staged files
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(ts|tsx)
# commit-msg: enforce conventional commits
# .git/hooks/commit-msg
#!/usr/bin/env bash
COMMIT_MSG_FILE="$1"
COMMIT_MSG=$(cat "$COMMIT_MSG_FILE")
PATTERN='^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?: .{1,72}
# pre-push: run tests before pushing
# .git/hooks/pre-push
#!/usr/bin/env bash
set -euo pipefail
BRANCH=$(git rev-parse --abbrev-ref HEAD)
# Don't allow direct push to main
if [[ "$BRANCH" == "main" ]]; then
echo "ERROR: Direct push to main is not allowed. Use a PR."
exit 1
fi
echo "Running tests before push..."
npm test -- --passWithNoTests
Stash with Names
# Named stash (much more useful than default "WIP on main")
git stash push -m "agent-email: half-done inbox UI" -- app/components/inbox/
# List with names
git stash list
# stash@{0}: On main: agent-email: half-done inbox UI
# stash@{1}: On feature: quick experiment with websockets
# Apply specific stash by name or index
git stash apply stash@{1}
git stash pop # apply most recent + drop
# Stash untracked files too
git stash push -u -m "with new files"
# Stash specific files
git stash push -m "only config" -- config/settings.toml
# Create branch from stash (useful when stash conflicts with current branch)
git stash branch feature/stashed-work stash@{0}
git blame with Copy Detection
# Standard blame
git blame app/api/agents.py
# Detect copies/moves from the same commit (-C)
git blame -C app/api/agents.py
# Detect copies across commits (-C -C)
git blame -C -C app/api/agents.py
# Ignore whitespace
git blame -w app/api/agents.py
# Show blame for specific lines
git blame -L 20,40 app/api/agents.py
# Ignore specific commits (e.g., mass formatting)
git blame --ignore-rev abc1234 app/api/agents.py
git blame --ignore-revs-file .git-blame-ignore-revs app/api/agents.py
# .git-blame-ignore-revs: file for formatting commits
# abc1234 Apply black formatting
# def5678 Run prettier on all files
Patch-Based Workflow
# Create a patch file from commits
git format-patch -3 HEAD # last 3 commits → 0001-*.patch, 0002-*.patch, ...
git format-patch main..feature/agent-email # whole branch
# Apply patch
git am 0001-feat-add-agent-email.patch
git am *.patch # apply all patches in order
# Apply patch with 3-way merge (handles context mismatches)
git am -3 0001-*.patch
# Quick patch (no commit metadata, just diff)
git diff HEAD~3..HEAD > changes.patch
git apply changes.patch
# Review patch before applying
git apply --check changes.patch # dry run, no changes
git apply --stat changes.patch # show what files would be affected
Signing Commits with GPG
# Configure signing
git config --global user.signingkey YOUR_GPG_KEY_ID
git config --global commit.gpgsign true # sign all commits
git config --global tag.gpgsign true
# Sign a single commit
git commit -S -m "feat: add signed commit"
# Sign a tag
git tag -s v2.0.0 -m "Release 2.0.0"
# Verify signatures
git log --show-signature
git verify-tag v2.0.0
# Use 1Password SSH key for signing (no GPG needed)
git config --global gpg.format ssh
git config --global user.signingkey "~/.ssh/id_ed25519.pub"
git config --global gpg.ssh.program "/Applications/1Password 7.app/Contents/MacOS/op-ssh-sign"
Power-User Git Config
# ~/.gitconfig
[alias]
st = status -sb
lg = log --oneline --graph --decorate --all
lp = log --oneline -20
co = checkout
br = branch -vv
oops = commit --amend --no-edit # add staged changes to last commit
undo = reset HEAD~1 --mixed # undo last commit, keep changes staged
unstage = reset HEAD --
wip = !git add -A && git commit -m "WIP"
unwip = !git reset HEAD~1 --mixed
[core]
editor = nvim
pager = delta # https://github.com/dandavison/delta
autocrlf = input # Linux/macOS
[push]
default = current
autoSetupRemote = true # git push without -u on first push
[pull]
rebase = true
[rebase]
autosquash = true
autostash = true # stash/unstash around rebase automatically
[merge]
conflictstyle = zdiff3 # better conflict markers
[diff]
algorithm = histogram # better diffs than Myers
[fetch]
prune = true # remove stale remote-tracking branches on fetch
[rerere]
enabled = true # remember resolved conflicts
Anti-Patterns
# ❌ Force-pushing to shared branches
git push --force origin main
# ✅ Force-push only to personal branches, use --force-with-lease
git push --force-with-lease origin feature/my-branch # safe: checks remote hasn't changed
# ❌ Rewriting published history
git rebase -i origin/main # rewrites commits others may have checked out
# ✅ Rebase before merging, not after pushing to shared refs
# ❌ Giant commits
git add . && git commit -m "changes"
# ✅ Atomic commits: one logical change per commit
git add -p # interactive hunk staging
# ❌ --no-verify to skip hooks
git commit --no-verify
# ✅ Fix what the hooks are catching
# ❌ merge commits in feature branch history (before PR)
# ✅ Rebase feature branch onto main before opening PR
# ❌ Committing secrets
git add .env
# ✅ .gitignore + git-secrets + gitleaks in pre-commit
Quick Reference
Interactive rebase: git rebase -i HEAD~N (squash/fixup/reword/drop)
Autosquash: git commit --fixup HEAD → git rebase -i --autosquash
Bisect: git bisect start; git bisect run ./test.sh
Reflog: git reflog — find lost commits, undo bad operations
Worktrees: git worktree add ../path branch — parallel branches, no stash
Cherry-pick: specific commits across branches; -m 1 for merge commits
Stash names: git stash push -m "name" -- file.ts
Blame copy: git blame -C -C — follows code through moves/copies
Format-patch: git format-patch main..branch → portable patches
Signing: commit.gpgsign=true, user.signingkey (GPG or SSH)
|| true)
if [[ -n "$STAGED_FILES" ]]; then
echo "Running TypeScript checks on staged files..."
npx tsc --noEmit
npx eslint $STAGED_FILES
fi
# Run with a framework (husky + lint-staged)
# .husky/pre-commit
npx lint-staged
__CODE_BLOCK_7__
__CODE_BLOCK_8__
Stash with Names
__CODE_BLOCK_9__
git blame with Copy Detection
__CODE_BLOCK_10__
Patch-Based Workflow
__CODE_BLOCK_11__
Signing Commits with GPG
__CODE_BLOCK_12__
Power-User Git Config
__CODE_BLOCK_13__
Anti-Patterns
__CODE_BLOCK_14__
Quick Reference
__CODE_BLOCK_15__
if ! echo "$COMMIT_MSG" | grep -qP "$PATTERN"; then
echo "ERROR: Commit message doesn't follow Conventional Commits format"
echo "Expected: type(scope): description"
echo "Example: feat(agents): add email inbox endpoint"
exit 1
fi
__CODE_BLOCK_8__