From joe-git-ops
Clean up git commit history by squashing/reorganizing commits using non-interactive rebase. Use when user asks to "tidy up commits", "squash commits", "clean up history", "rebase", or wants N specific commits total. This skill handles the mechanics of rebasing in non-interactive environments where `git rebase -i` won't work.
How this skill is triggered — by the user, by Claude, or both
Slash command
/joe-git-ops:git-rebase-squashThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Clean up git commit history by squashing and reorganizing commits using a file-based rebase workflow.
Clean up git commit history by squashing and reorganizing commits using a file-based rebase workflow.
DO NOT use git rebase -i directly—it requires interactive terminal input and will hang or fail in automated environments.
ALWAYS use GIT_SEQUENCE_EDITOR with a pre-written rebase plan file.
If there are unstaged changes that are part of the cleanup:
# Commit them first - DO NOT stash
git add <files>
git commit -m "Description of changes"
Why: Stashing complicates the rebase process. Committing allows you to include these changes in the rebase plan.
# Fetch latest remote state
git fetch origin
# Find the fork point — the commit where your branch diverged from origin/main
BASE=$(git merge-base origin/main HEAD)
# View all commits on your branch since the fork point, with size info
git log --oneline $BASE..HEAD
Before proceeding, sanity-check the commit list. Confirm with the user if any of the following apply:
origin/main and no base was specifiedMerge branch ...) — rebasing over a merge commit will likely produce unexpected resultsWhen confirming, phrase it as a single question covering whichever flags triggered: "Before I proceed — this branch is N commits ahead of origin/main [and/or includes a merge commit / includes unrelated commits]. Is origin/main the right base, or should I use a different one?"
git log $BASE..HEAD --format="%h %s" | while read hash msg; do
stats=$(git diff-tree --no-commit-id -r --stat $hash | tail -1)
echo "$hash | $stats | $msg"
done
This gives you a per-commit breakdown of files changed and lines added/removed — essential for deciding how to group commits.
Before running the rebase, present the user with 2–3 breakdown options at different commit counts (e.g. into 3, 4, 5 commits). For each option show:
Example presentation format:
**Option A — 3 commits** ⭐ recommended
1. Add CI/CD pipeline and deploy infrastructure (12 files, +450/-80)
2. Add OTEL telemetry and fix git identity (4 files, +45/-12)
3. Add HTTPRoute and HealthCheckPolicy (4 files, +57/-3)
**Option B — 4 commits**
1. Fix Helm chart security contexts (3 files, +18/-6)
2. Add CI/CD pipeline and deploy scripts (9 files, +432/-74)
3. Add OTEL telemetry and fix git identity (4 files, +45/-12)
4. Add HTTPRoute and HealthCheckPolicy (4 files, +57/-3)
**Option C — 5 commits**
1. Fix Helm chart security contexts (3 files, +18/-6)
2. Add CI/CD pipeline and deploy scripts (9 files, +432/-74)
3. Add OTEL telemetry config (3 files, +39/-8)
4. Fix git identity in push_branch (1 file, +6/-4)
5. Add HTTPRoute and HealthCheckPolicy (4 files, +57/-3)
Wait for user to confirm before proceeding.
Create a file with the rebase instructions:
cat << 'EOF' > /tmp/git-rebase-todo
pick <commit-hash-1> <commit message>
fixup <commit-hash-2> <commit message> # squash into previous, discard message
fixup <commit-hash-3> <commit message> # squash into previous, discard message
pick <commit-hash-4> <commit message>
squash <commit-hash-5> <commit message> # squash into previous, keep message
EOF
Key Commands:
pick: Keep this commit as-isfixup: Squash into previous commit, discard this commit's messagesquash: Squash into previous commit, keep this commit's message for editingreword: Keep commit but change the messageedit: Stop at this commit to make changesdrop: Remove this commit entirelyOrder: Commits are listed from oldest to newest (chronological order)
# Use GIT_SEQUENCE_EDITOR to point to your plan file, rebase onto the fork point
GIT_SEQUENCE_EDITOR='cp /tmp/git-rebase-todo' git rebase -i $BASE
Where $BASE is the merge-base commit found in step 2.
If you used edit or reword commands:
# For 'edit' - make changes, then:
git add <files>
git commit --amend
git rebase --continue
# For 'reword' - the rebase will stop and wait:
git commit --amend -m "New commit message"
git rebase --continue
# Check the new history
git log --oneline origin/main..HEAD
# Diff against the old HEAD — must be empty
git diff <old-HEAD> HEAD --stat
# If the diff is non-empty, the rebase dropped or altered content — abort and investigate
Always diff against the old HEAD before pushing. A non-empty diff means commits were dropped or conflicts were resolved incorrectly.
Since rebase rewrites history:
# Use force-with-lease for safety (fails if remote has new commits)
git push --force-with-lease
# OR regular force push (less safe)
git push --force
git rebase -i directlygit rebase -i HEAD~5 # Will hang waiting for interactive input
BASE=$(git merge-base origin/main HEAD)
cat > /tmp/rebase-plan << 'EOF'
pick abc123
fixup def456
EOF
GIT_SEQUENCE_EDITOR='cp /tmp/rebase-plan' git rebase -i $BASE
git stash
git rebase -i HEAD~5
git stash pop
git add .
git commit -m "WIP changes"
# Now rebase all branch commits including the new one
BASE=$(git merge-base origin/main HEAD)
GIT_SEQUENCE_EDITOR='cp /tmp/rebase-plan' git rebase -i $BASE
git reset --soft without understanding the implicationsgit reset --soft HEAD~5 # Dangerous - loses commit metadata
# Creates clean history while preserving authorship and timestamps
BASE=$(git merge-base origin/main HEAD)
GIT_SEQUENCE_EDITOR='cp /tmp/plan' git rebase -i $BASE
git push --force # Push without checking
git log --oneline -5 # Check commits
git show HEAD # Review latest commit
git push --force-with-lease
Goal: Combine 6 commits into exactly 3 clean commits.
Starting commits (newest to oldest):
f - Fix test-backend changes
e - Fix migration part 4
d - Fix migration part 3
c - Fix migration part 2
b - Fix test-engine changes
a - Fix migration part 1
Desired result:
Steps:
git fetch origin
BASE=$(git merge-base origin/main HEAD)
git log --oneline $BASE..HEAD
# f - Fix test-backend changes
# e - Fix migration part 4
# d - Fix migration part 3
# c - Fix migration part 2
# b - Fix test-engine changes
# a - Fix migration part 1
cat << 'EOF' > /tmp/git-rebase-todo
pick a Fix migration part 1
fixup c Fix migration part 2
fixup d Fix migration part 3
fixup e Fix migration part 4
pick b Fix test-engine changes
pick f Fix test-backend changes
EOF
Note: Commits must be in chronological order (oldest first)!
GIT_SEQUENCE_EDITOR='cp /tmp/git-rebase-todo' git rebase -i $BASE
NEW_BASE=$(git merge-base origin/main HEAD)
cat << 'EOF' > /tmp/git-rebase-todo
reword <hash-a> Fix migration part 1
reword <hash-b> Fix test-engine changes
reword <hash-f> Fix test-backend changes
EOF
GIT_SEQUENCE_EDITOR='cp /tmp/git-rebase-todo' git rebase -i $NEW_BASE
# When stopped at each commit:
git commit --amend -m "Better commit message with details"
git rebase --continue
git log --oneline -3
git push --force-with-lease
git fetch origin before finding the base — a stale ref will give you the wrong merge-basegit log $BASE..HEAD to confirm the full list before writing the planfixup over squash to avoid accumulating commit messages; use reword on the lead commit if you want a better messagegit diff <old-HEAD> HEAD --stat after rebasing and before force pushing — empty diff = safe--force — fails if the remote has moved on since your last fetch# See what's conflicting
git status
# Fix conflicts in files, then:
git add <resolved-files>
git rebase --continue
# OR abandon the rebase:
git rebase --abort
# Abort and start over
git rebase --abort
# OR if already pushed, force push the old state:
git reflog # Find the commit before rebase
git reset --hard <commit-hash>
git push --force-with-lease
# Don't use interactive commands in the shell
# Always use GIT_SEQUENCE_EDITOR approach shown above
npx claudepluginhub ylt/claude-plugins --plugin joe-git-opsAutomates partial rebase by detecting which commits to keep or drop in squash-merge repositories. Outputs a rebase plan and copy-pasteable git rebase --onto command.
Rewrites a feature branch's messy commit history into clean, conventional commits that tell a progressive, linear story. Handles backup, soft reset, and atomic recommit.
Provides advanced Git rebase patterns for linear history, stacked PRs, commit cleanup, and converting merge-heavy branches using --reapply-cherry-picks, --onto, and interactive workflows.