Git History Cleaner
This skill provides a procedural workflow and strategies for cleaning up git commit history. It details how to reorganize, reorder, and combine (squash or fixup) commits to achieve a clean history where each file, feature, or logical unit is introduced/modified in a single self-contained commit.
Workflow
Analyze Commit History:
- Identify the base commit (e.g.,
origin/main,master, or a specific hash where the branch diverged) and the current branch HEAD. - View the commit messages in reverse chronological order:
git log <base>..HEAD --oneline - List files modified in each commit:
git log <base>..HEAD --name-status
- Identify the base commit (e.g.,
Group Changes by Logical Units:
- Map files to their logical units (e.g., a specific module, script, library, or config).
- Identify "fixup" commits (commits fixing errors or formatting in files that were introduced or modified in earlier commits on the branch).
- Plan the squashing order: each logical unit or file set should be introduced in a single commit, incorporating all subsequent fixes to those same files.
Rebase and Combine Commits:
Method A: Interactive Rebase (For straightforward rebases):
- Run
git rebase -i <base>. - In the todo list, reorder commits so that any fixup/update commits are positioned immediately below the commit that originally introduced those changes.
- Change the action of the fixup commits from
picktofixup(orf) to merge them into the parent commit without changing the original commit message, orsquash(ors) to combine their commit messages.
- Run
Method B: Programmatic Rebase Script (For complex histories or index files):
- If commits frequently modify shared index files (e.g., JSON registries, lockfiles) that cause heavy conflicts during a standard interactive rebase, write a temporary helper script (Bash or Python) to rebuild the history.
- Create a temporary branch starting at the base commit:
git checkout -b rebase-temp <base> - For each planned clean commit:
- Check out the final state of the relevant files from the target HEAD:
git checkout <target_head> -- <files> - If a shared index file needs to be built incrementally, extract the specific changes using command-line tools (such as
jqfor JSON files) and apply them. - Commit the staged changes, preserving the original author metadata and dates using environment variables:
GIT_AUTHOR_DATE="<date>" GIT_COMMITTER_DATE="<date>" git commit -m "<message>" - Repeat for all logical units in the planned order.
- Check out the final state of the relevant files from the target HEAD:
Verify the Rebased History:
- Verify that the file state at the new rebased HEAD is identical to the target HEAD:
The diff must be completely empty.git diff <target_head> HEAD - Review the final commit history to ensure it contains exactly one logical commit per feature/file group:
git log <base>..HEAD --oneline
- Verify that the file state at the new rebased HEAD is identical to the target HEAD:
Update Branch Pointer:
- Switch back to the main feature branch and hard-reset it to the rebased HEAD:
git checkout <feature-branch> git reset --hard rebase-temp git branch -D rebase-temp
- Switch back to the main feature branch and hard-reset it to the rebased HEAD: