git-history-cleanup · git:20261005.32edae5 · 2026-10-05 · sha256 99e64a7da22db9de
git-history-cleanup git:20261005.32edae5A
Immutable. This exact content never changes and is served at /api/v1/blob/99e64a7da22db9de.
---
name: git-history-cleanup
description: Collapse many WIP / fix-to-fix commits on a feature branch into a small set of topic-focused commits. Reads the original commit messages first so intent is preserved, soft-resets to origin/main so intermediate / overwritten changes drop out automatically, then re-commits the accumulated diff one logical topic at a time. Use before opening a PR or merging when `git log origin/main..HEAD` looks noisy.
---
# Git history cleanup
Collapse `origin/main..HEAD` into a small number of topic-focused commits without losing intent. Changes that were introduced in one commit and rewritten or undone by a later commit drop out automatically -- they are not in the final diff. The original commit messages are read first so the new topic split preserves the *why* of the work.
The previous tip of the branch is kept by `git reflog` (default 90 days), so the operation is non-destructive locally. Nothing is pushed.
## Step 0 -- working copy must be clean
```sh
git status --porcelain
```
If the output is non-empty, STOP. Tell the user to commit, stash, or discard their pending edits first. A dirty tree would mix uncommitted work into the collapse and there is no clean way to un-mix afterwards. Do not improvise an auto-stash; the user may not want those edits in the regrouped history.
## Step 1 -- agent context must be clean
This task reads every commit message between `origin/main` and `HEAD`, plus the full accumulated diff, plus the per-file change set. That is heavy and benefits from a fresh context window so nothing important gets truncated mid-analysis.
Self-assess:
- **Clean context** -- this skill was invoked early in the conversation, with little prior tool use: proceed in-place.
- **Dirty context** -- there has been substantial prior work (many file reads, prior edits, long tool history): spawn a `general-purpose` Agent and hand off steps 2-6 with a self-contained brief. The brief MUST include:
- the absolute repo path,
- the current branch name,
- the `origin/main` SHA (`git rev-parse origin/main`),
- the explicit rules from "Hard rules" below (no push, no `--hard`, no `-i`, no `--no-verify`),
- an instruction to surface the topic plan back to the user for confirmation before any commit.
When in doubt, prefer spawning the agent; the overhead is small and the failure mode of running out of context mid-analysis is bad (a half-grouped history with no record of the original intent).
## Step 2 -- study the existing commits
Read full bodies, not just subjects:
```sh
git log origin/main..HEAD --pretty=format:'%n=== %h %an %ad%n%s%n%n%b'
git log origin/main..HEAD --name-status
```
For each commit capture:
- Stated intent (what the message says it did, and why).
- Whether it fixes / reverts / amends a previous commit on the same branch -- these are the "overwritten" changes the user wants to forget. They will not appear in the final diff but their messages still hint at the *topic* the eventual hunk belongs to.
- Which files / areas it touched.
Save the output, or at least your notes from it, somewhere reachable in step 4 -- once the reset happens these messages are still in the reflog but reading them again costs another pass.
Also useful when the branch is long or branchy:
```sh
git log origin/main..HEAD --merges # are there merge commits in the range?
git log origin/main..HEAD --reverse --oneline # chronological order, easier for narrative
```
If there are merge commits, surface them to the user and ask whether to flatten -- this skill assumes a linear range.
## Step 3 -- collapse to origin/main
The user's phrasing "soft reset" here means "non-destructive reset that preserves all changes". Use `--mixed` (the git default) so everything lands as **unstaged** working-tree changes -- that is what step 4 analyzes and step 5 selectively stages per topic:
```sh
git reset --mixed origin/main
```
Equivalent two-step that honors the literal "soft" wording:
```sh
git reset --soft origin/main
git reset HEAD --
```
After either form:
- `HEAD` is at `origin/main`.
- The working tree is unchanged.
- `git diff` shows only the *final combined effect* of all the original commits. Intermediate / overwritten states are automatically gone.
Do NOT use `git reset --hard` here. `--hard` discards the working tree, which erases the entire point of the operation.
## Step 4 -- group the diff by topic
```sh
git status
git diff --stat
git diff
```
Cluster the changes into logical topics:
- Re-read the commit messages from step 2 -- they encode the user's intended topics. Subjects that share a theme ("fix multiview", "fix multiview alignment", "follow-up to multiview") usually collapse into one topic.
- Numbered punch-list prefixes (e.g. `A11-A20`, `B1/B6/B10`, `E1/E13`) often map directly onto the original task tracker's section -- preserve those groupings unless the diff says otherwise.
- A topic is "one logical change a reviewer can approve or revert as a unit". Aim for 2-6 topics on a typical feature branch; fewer is better as long as each remains coherent.
- If a single file has hunks belonging to different topics, plan for hunk-level staging in step 5.
Present the proposed split to the user before committing anything:
- Each topic: a draft subject line, optional body, and the list of files (or files-and-hunks for mixed files) it covers.
- Total file count covered vs. `git diff --stat` total -- they must match.
Wait for the user to confirm or redirect. Do NOT silently regroup.
## Step 5 -- commit each topic
Before the first commit, snapshot the pre-commit diff so step 6 can verify nothing was dropped (`<scratch>` = the session's scratch directory, outside the repo):
```sh
git diff > <scratch>/git-history-cleanup-target.diff
```
Then, for each topic in order:
1. Stage exactly that topic:
```sh
git add path/to/whole_topic_files
git apply --cached <hunk.patch> # only when a file has mixed-topic hunks
```
`git add -p` is interactive and cannot be driven from this tool: for a mixed file, write per-hunk patches to the scratch directory and stage them with `git apply --cached`. Do not paper over difficulty by lumping a mixed file into one topic if it really belongs to two -- that defeats the purpose of the skill.
2. Sanity check before committing:
```sh
git diff --cached # should show ONLY this topic
git diff # should show everything still pending for later topics
```
3. Commit using the project's style (match `git log`):
- Short subject, ideally under ~70 chars, focused on the *why*.
- Optional body for context.
- End the message with the attribution trailer the session's commit guidance gives, exactly as given; when the session gives none, add none.
- Use a HEREDOC for multi-line messages.
4. Move on to the next topic.
## Step 6 -- verify and stop
After the last topic commit, BOTH of these must hold:
```sh
git status # must be clean -- no remaining changes
git diff origin/main..HEAD > <scratch>/git-history-cleanup-after.diff
diff <scratch>/git-history-cleanup-target.diff <scratch>/git-history-cleanup-after.diff
```
The `diff` must be empty (or only differ in trailing whitespace). Equivalently, the total diff vs. `origin/main` must equal the diff captured before any topic commits.
If either check fails a hunk was dropped or duplicated. DO NOT rescue with a catch-all "misc" / "wip" commit -- that defeats the purpose. Find the missing hunk, decide which topic it belongs to, and either amend that topic's commit (only if it is the most recent one) or do a fresh `git reset --mixed origin/main` and re-run from step 5.
Show the user the new shortlog and stop:
```sh
git log origin/main..HEAD --oneline
```
Pushing is the user's call.
## Hard rules
- **Never `git push`** -- and especially never `git push --force` -- from this skill. The user decides when the new history goes out.
- **Never `git reset --hard`**. The whole operation depends on the working tree being preserved by the reset.
- **Never `git rebase -i`**. Interactive prompts cannot be driven from this tool.
- **Never `--no-verify`, `--no-gpg-sign`**, or other hook / signing bypasses.
- **Never create a "misc" / "wip" / "cleanup" catch-all commit** to swallow leftover changes -- that defeats the purpose of the skill. Either every hunk belongs to a named topic, or step 6 must surface the gap to the user.
- **Never amend a commit that was not created by this skill run**. Amending commits the user already pushed elsewhere is destructive.
- If any check (step 0 clean tree, step 6 diff equality) fails, STOP and report. Do not improvise.
## Recovery
If the user dislikes the new history, the previous tip is in the reflog:
```sh
git reflog | head -30
git reset --hard <old-tip-sha>
```
When reporting completion to the user, mention the old tip SHA explicitly (capture it from `git rev-parse HEAD` BEFORE the step-3 reset) so they know the safety net is one command away.
## When NOT to use
- Branch has merge commits in `origin/main..HEAD` that the user wants to preserve -- this skill assumes a linear range and would flatten the merge boundary.
- Branch has already been pushed and other people are working from it -- the resulting force-push would rewrite shared history. Confirm with the user that the branch is private before starting.
- There is no meaningful diff (`git log origin/main..HEAD --oneline | wc -l` is 0 or 1, or `git diff origin/main..HEAD` is empty) -- there is nothing to clean up. Tell the user.