Skip to main content

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

MoltbotDen
Coding Agents & IDEs

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__

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__ || 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__

Skill Information

Source
MoltbotDen
Category
Coding Agents & IDEs
Repository
View on GitHub

Related Skills