Git History Surgery skill for Claude Code

Rewrite history with a backup ref and a tree diff. Claude treats every history rewrite as surgery: backup branch first, clean tree, the right tool for the goal, and a tree diff against the backup to prove nothing was lost. It squashes, splits, reorders, and moves commits, resolves rebase conflicts with zdiff3 context, recovers work from the reflog, and asks before any force push or reset on a branch others use.

  • namegit-history-surgery
  • size178 lines · ≈2,000 tokens
  • files2
  • licenseMIT
  • updated

What Claude sees before loading it

This description is the only part of the skill that sits in context all the time. Claude reads it to decide when to load the full playbook below.

description: Rewrites, repairs, and recovers git history safely with interactive rebase, commit splitting, rebase --onto, cherry-pick, reflog recovery, and careful conflict resolution, always creating a backup ref first and never rewriting shared branches without explicit consent. Use when the user wants to squash, reorder, split, or reword commits, move commits to another branch, undo a bad rebase, merge, or reset, recover lost commits or stashes, resolve rebase conflicts, or remove a file from history.

Try asking

  • Squash and reorder the commits on my branch into three clean ones
  • I did a git reset --hard and lost two days of commits — get them back
  • Move my feature branch off the old base now that feat/a was squash-merged

Use it when

  • A branch's commits need squashing, splitting, reordering, or rewording
  • Commits landed on the wrong branch or base
  • Work seems lost after a reset, rebase, or deleted branch
  • A rebase or merge is full of conflicts
  • A secret or huge file must be purged from history

Skip it when

How to check the result

git diff between the backup ref and the new HEAD is empty when only history should change, and the commit series on the branch reads as requested.

The common mistake it prevents

Force-pushing a rewritten shared branch with bare --force, wiping teammates' commits that were pushed after your last fetch.

~/.claude/skills/git-history-surgery/SKILL.md

--- name: git-history-surgery description: Rewrites, repairs, and recovers git history safely with interactive rebase, commit splitting, rebase --onto, cherry-pick, reflog recovery,… license: MIT ---

Surgery is done when history looks the way the user asked, the final tree is identical to what it should be (verified with a diff against a backup), nothing was lost, and no one else’s clone was broken without their agreement. Git almost never truly loses committed work; panic and rm -rf .git do.

Hard rules

  • Backup ref before every rewrite: git branch backup/<name>-$(date +%Y%m%d-%H%M%S). It costs nothing and makes every mistake reversible.
  • Never rewrite shared history without explicit user consent. Shared means pushed to a branch others use (main, develop, release/*, a teammate’s branch). On consent, push with --force-with-lease --force-if-includes, never bare --force, and tell collaborators how to resync.
  • Clean working tree first. git status must be clean, or git stash push -u.
  • Destructive commands need confirmation in the session: reset --hard, push --force*, branch -D, clean -fdx, filter-repo, reflog expire, gc --prune=now.
  • Verify the result by diffing trees, not by eyeballing logs.

Before anything

git status
git log --oneline --graph --decorate -20
git branch -vv                              # tracking + ahead/behind
git log --oneline origin/main..HEAD         # commits only on this branch
git branch backup/feat-$(date +%Y%m%d-%H%M%S)

Decide if the branch is shared: is it pushed (git branch -r --contains HEAD)? Does anyone else commit to it? If unsure, ask.

Decision table

GoalTool
Fix last commit message or add a forgotten filegit commit --amend (unpushed or own branch)
Squash, reorder, reword, drop commits on a branchgit rebase -i <base>
Fold review fixes into their target commitsgit commit --fixup=<sha> then git rebase -i --autosquash <base>
Split one commit into severalrebase -i, mark edit, git reset HEAD~, re-commit in pieces
Move branch to a new base (dropping commits from old base)git rebase --onto <newbase> <oldbase> <branch>
Copy specific commits to another branchgit cherry-pick -x <sha>
Committed on the wrong branchCreate branch at HEAD, then reset original (see recipes)
Undo a pushed commit on a shared branchgit revert <sha> (no rewrite)
Undo a merge on a shared branchgit revert -m 1 <merge-sha>
Lost commits after reset/rebase/branch deletegit reflog, then branch or reset to the entry
Remove a secret or huge file from all historygit filter-repo + rotate the secret (see recipes)
Rebase a stack of dependent branches togethergit rebase --update-refs <base> (git 2.38+)

Default to revert on shared branches and rewrite only on private ones.

Interactive rebase

git rebase -i origin/main          # or a SHA; edit the todo list

Todo verbs: pick, reword (message), edit (stop to amend or split), squash (meld, combine messages), fixup (meld, keep first message; fixup -C keeps this one’s message), drop, exec <cmd>, break. Reorder by moving lines.

Useful options:

git rebase -i --autosquash origin/main            # apply fixup!/squash! commits
git rebase -i --exec "npm test" origin/main       # run tests after every commit
git rebase -i --rebase-merges origin/main         # keep merge commits in the branch
git rebase -i --update-refs origin/main           # move stacked branch pointers too
git rebase --abort                                 # bail out to pre-rebase state

Enable conflict memory for repeated rebases: git config --global rerere.enabled true.

Splitting a commit

git rebase -i <sha>~1              # mark the target commit as `edit`
git reset HEAD~                    # unstage its changes, keep them in the tree
git add -p                         # stage the first logical piece
git commit -m "refactor(x): ..."
git add -p && git commit -m "feat(x): ..."
git rebase --continue

Resolving conflicts

  1. Read the conflict with base context: git config --global merge.conflictStyle zdiff3 shows the original lines between ||||||| and =======.
  2. Understand both sides’ intent: git log --oneline -3 <file> on each side, or during a rebase git show REBASE_HEAD (the commit being applied).
  3. Resolve to what both changes intended, not to one side by default. Run the tests.
  4. git add <file> then git rebase --continue (or git merge --continue).

During a rebase, --ours is the branch you are rebasing onto and --theirs is your commit being replayed: the reverse of a merge. Double-check before using git checkout --ours/--theirs <file>.

If a rebase produces conflicts in many commits, consider squashing the branch first (fewer replays) or merging main instead, with user agreement.

Pushing rewritten history

git push --force-with-lease --force-if-includes origin feat/x

--force-with-lease refuses if the remote moved since your last fetch; --force-if-includes additionally refuses if you never integrated the remote’s tip (guards against a background fetch updating the lease). Only after user consent for shared branches. Teammates resync with:

git fetch origin && git switch feat/x && git reset --hard origin/feat/x   # discards their local commits!
# safer for them if they have local work:
git rebase --onto origin/feat/x <old-remote-tip> feat/x

Recovery

The reflog records every position HEAD and each branch has had (default retention 90 days for reachable, 30 for unreachable entries).

git reflog                          # HEAD movements: find the state before the mistake
git reflog show feat/x              # one branch's history
git branch rescue HEAD@{5}          # pin it before doing anything else
git reset --hard ORIG_HEAD          # undo the last reset/rebase/merge (confirm first)

For dropped stashes, deleted branches never checked out locally, and other scenarios, read references/recovery-recipes.md.

Archaeology (read-only)

git log -S'applyDiscount' --oneline             # commits adding/removing a string
git log -G'retry\(\d+\)' --oneline              # commits whose diff matches a regex
git log -L :applyDiscount:src/cart.ts           # history of one function
git blame -w -C -C -C src/cart.ts               # ignore whitespace, follow moved code
git log --follow -- path/renamed_file.ts
git branch -a --contains <sha>; git tag --contains <sha>
git range-diff feat/x@{1}...feat/x              # compare branch before/after a rebase

Gotchas

  • git reset --hard destroys uncommitted changes, which the reflog cannot recover. Stash or commit first.
  • git clean -fdx deletes untracked and ignored files (including .env); preview with git clean -ndx.
  • Squashing commits that were already merged elsewhere creates duplicate changes and confusing conflicts later.
  • Rebasing a branch that contains merge commits flattens them unless you pass --rebase-merges.
  • Cherry-picking a merge commit needs -m 1 to pick the mainline parent.
  • Reverting a merge and later re-merging the same branch will not reintroduce the reverted changes; revert the revert first.
  • Removing a secret from history does not make it safe: it was exposed. Rotate it first; rewriting history is secondary cleanup.
  • Hosted forges may keep old commits reachable via PR refs and caches after a rewrite.

Verification

git diff backup/feat-<stamp> HEAD --stat     # empty when only history (not content) should change
git log --oneline origin/main..HEAD          # the commit series reads as intended
git rebase -i --exec "<test cmd>" origin/main   # optional: every commit builds and passes

If the tree diff is not empty and should be, stop, compare against the backup, and fix before pushing. Delete backup branches only after the user confirms the result is right.

Bundled files

Loaded only when a step in the skill needs them. Review them here before installing.

references/recovery-recipes.md 185 lines · 6.0 KB
# Recovery and repair recipes

Read this when a specific mistake needs undoing. Every recipe assumes you created a
backup ref first where possible and that destructive steps are confirmed with the user.

## Contents

- Undo the last commit (keep changes)
- Committed to the wrong branch
- Lost commits after reset or rebase
- Deleted a branch
- Recover a dropped or cleared stash
- Bad merge on a private branch
- Bad merge already pushed to a shared branch
- Rebase went wrong midway
- Move a range of commits onto another base
- Accidentally committed a secret or large file
- Accidentally amended the wrong commit
- Detached HEAD with commits on it
- Recover a file from an older commit
- Unrelated histories / wrong remote

## Undo the last commit (keep changes)

```bash
git reset --soft HEAD~1     # changes stay staged
git reset HEAD~1            # changes stay in working tree, unstaged (mixed)
```

If already pushed to a shared branch, use `git revert HEAD` instead.

## Committed to the wrong branch

Commits are on `main` but belong on a new branch, and are not pushed:

```bash
git branch feat/new-thing                 # new branch at current HEAD keeps the commits
git reset --hard origin/main              # confirm: moves main back (tree must be clean)
git switch feat/new-thing
```

Commits belong on an existing branch:

```bash
git switch feat/existing
git cherry-pick <sha1>^..<sha3>           # inclusive range, oldest first
git switch main && git reset --hard origin/main   # confirm first
```

## Lost commits after reset or rebase

```bash
git reflog                                 # find the entry before the mistake, e.g. HEAD@{4}
git show HEAD@{4} --stat                   # confirm it is the right state
git branch rescue HEAD@{4}                 # pin it
git reset --hard rescue                    # confirm; or cherry-pick specific commits from it
```

Right after a rebase or reset, `ORIG_HEAD` points at the previous tip:
`git reset --hard ORIG_HEAD`.

## Deleted a branch

`git branch -D` prints the tip SHA ("Deleted branch x (was a1b2c3d)"). Otherwise:

```bash
git reflog | grep -i "checkout: moving from feat/x"     # last time you left the branch
git branch feat/x <sha>
```

If the branch was never checked out locally but was fetched, look in the remote's
reflog: `git reflog show origin/feat/x`. If it still exists on the remote,
`git switch feat/x` recreates it.

## Recover a dropped or cleared stash

Dropped stashes are unreachable commits until garbage collection:

```bash
git fsck --unreachable --no-reflogs | grep commit | cut -d' ' -f3 \
  | xargs git log --merges --no-walk --oneline       # stash commits are merges "WIP on ..."
git stash apply <sha>
```

## Bad merge on a private branch

```bash
git reset --hard ORIG_HEAD          # immediately after the merge
# or later:
git reset --hard <sha-before-merge> # find via git reflog
```

## Bad merge already pushed to a shared branch

Do not rewrite. Revert the merge, keeping the mainline parent:

```bash
git revert -m 1 <merge-sha>
```

Later, to bring the branch back after fixing it, first revert the revert
(`git revert <revert-sha>`), then merge the fixes. Re-merging without that step brings
in only commits made after the original merge.

## Rebase went wrong midway

```bash
git rebase --abort                  # back to exactly where you started
```

Already finished and the result is wrong:

```bash
git reflog                          # find "rebase (start)" and take the entry before it
git reset --hard HEAD@{N}           # confirm first
# or compare first:
git range-diff <old-tip>...HEAD
```

## Move a range of commits onto another base

Branch `feat/b` was cut from `feat/a`, and `feat/a` was squash-merged into main. Replay
only `feat/b`'s own commits onto main:

```bash
git rebase --onto origin/main <last-commit-of-feat-a> feat/b
```

General form: `git rebase --onto <newbase> <exclusive-start> <branch>`.

## Accidentally committed a secret or large file

1. **Rotate the secret now.** Assume it is compromised the moment it was pushed.
2. If not pushed: `git reset --soft HEAD~1`, remove the file, add to `.gitignore`,
   recommit. Or `git rebase -i` and `edit` the commit if it is older.
3. If pushed, with user consent and coordination with everyone who has a clone:

```bash
git branch backup/pre-filter-$(date +%Y%m%d)
git filter-repo --invert-paths --path config/secrets.env     # needs git-filter-repo installed
git filter-repo --replace-text expressions.txt              # replace secret strings everywhere
git remote add origin <url>       # filter-repo removes origin as a safety measure
git push --force-with-lease --all && git push --force-with-lease --tags
```

4. Ask the forge to purge cached views/PR refs if required (GitHub support for
   sensitive data removal). Everyone must re-clone; old clones reintroduce the data.

Prefer `git filter-repo` over `git filter-branch` (deprecated and slow) and over BFG
for new work.

## Accidentally amended the wrong commit

```bash
git reflog                          # entry before "commit (amend)"
git reset --soft HEAD@{1}           # back to pre-amend commit, amended changes staged
git commit                          # commit them separately
```

## Detached HEAD with commits on it

```bash
git branch keep-this                # pin the commits before switching away
git switch main && git merge keep-this    # or cherry-pick
```

If you already switched away, find the commits in `git reflog`.

## Recover a file from an older commit

```bash
git log --oneline -- path/to/file           # find the commit
git restore --source=<sha> -- path/to/file  # bring it into the working tree
git show <sha>:path/to/file > /tmp/old.ts   # or just view it
```

For a deleted file: `git log --diff-filter=D --oneline -- path/to/file`, then restore
from `<sha>~1`.

## Unrelated histories / wrong remote

`fatal: refusing to merge unrelated histories` usually means the wrong remote or a repo
re-initialized locally. Check `git remote -v` and `git log --oneline -3 origin/main`
before reaching for `--allow-unrelated-histories`, which is only right when you truly
intend to join two independent projects.

About this skill

Written and maintained by the Claude Skills editors to the same editorial standard as every skill in the directory: a selective trigger, a real workflow, known pitfalls and a verification step. First published , last updated . MIT licensed.

esc