Non-Interactive Git Rebase Guide
Status: Stable Guide for performing git rebases in non-interactive shells (automation, CI, AI agents).
The Problem
Interactive git rebase -i opens an editor and waits for user input, which doesn't work in automated contexts. This guide shows reliable alternatives.
Method 1: Reset + Commit (RECOMMENDED for Squashing)
Use case: Squash multiple commits into one
# Squash commits from a5f4983 to fc0d964 into a single commit
git reset --hard fc0d964 # Go to end of range
git reset --soft a5f4983 # Soft reset to start (keeps changes staged)
git commit -m "Squashed message" # Commit all changes as one
# Re-apply any commits that should come after
git cherry-pick 8b23aeb..58ecfaf
Why this works:
--softpreserves all file changes in staging area- No editor needed, fully deterministic
- Simple and reliable
- Clean for combining many commits
When to use:
- Squashing evolution/experimentation commits
- Combining related commits from a feature branch
- Cleaning up commit history before merge
Method 2: GIT_SEQUENCE_EDITOR (For Rewording)
Use case: Change commit message without changing code
# Reword a specific commit message
GIT_SEQUENCE_EDITOR="sed -i '' '1s/^pick/reword/'" git rebase -i <commit>^
Platform-specific sed syntax:
# macOS/BSD (empty string after -i)
sed -i '' 's/pick/reword/' file
# Linux/GNU (no argument after -i)
sed -i 's/pick/reword/' file
# Portable (use ed instead)
ed -s file <<< $',s/pick/reword/\nw'
Using a script file:
cat > /tmp/rebase-edit.sh << 'EOF'
#!/bin/bash
sed -i '' 's/^pick \(abc123\)/reword \1/' "$1"
EOF
chmod +x /tmp/rebase-edit.sh
GIT_SEQUENCE_EDITOR="/tmp/rebase-edit.sh" git rebase -i <commit>^
What the script receives:
$1= path to git-rebase-todo file- Must modify file in-place
- Must exit with code 0
When to use:
- Changing commit message for an older commit
- Selective reword/edit/squash operations
- When reset+commit approach isn't suitable
Method 3: Amend (Simplest for Last Commit)
Use case: Change the most recent commit
# Change last commit message
git commit --amend -m "New message"
# Keep original message, just change files
git commit --amend --no-edit
# Change message interactively (opens editor)
git commit --amend # Don't use in non-interactive contexts!
When to use:
- Modifying the most recent commit only
- Adding forgotten files to last commit
- Fixing typos in last commit message
Method 4: Autosquash Workflow
Use case: Mark commits for automatic squashing later
# During development: create fixup/squash commits
git commit --fixup=<target-commit-hash>
git commit --squash=<target-commit-hash>
# Later: automatically combine them
git rebase -i --autosquash <base>
Difference:
--fixup: Discards the fixup commit message--squash: Combines commit messages (opens editor)
When to use:
- Incremental fixes during feature development
- Clean up before final merge
- Requires planning ahead (mark commits during development)
Common Pitfalls
❌ Don't do this:
# Hangs waiting for editor in non-interactive shell
git rebase -i HEAD~5
# Wrong variable (controls commit message editor, not sequence)
GIT_EDITOR=true git rebase -i HEAD~5
# Platform-specific sed (breaks on BSD)
sed -i 's/pick/reword/' file # Missing '' on macOS
✅ Do this instead:
# Use GIT_SEQUENCE_EDITOR for sequence file
GIT_SEQUENCE_EDITOR=true git rebase -i HEAD~5
# Or use reset+commit for squashing (simpler)
git reset --soft HEAD~5
git commit -m "Squashed"
# Platform-safe sed
sed -i '' 's/pick/reword/' file # macOS
Best Practices
- For squashing multiple commits: Use
git reset --soft(simplest, most reliable) - For rewording one commit: Use
git commit --amend(if last) orGIT_SEQUENCE_EDITOR(if older) - For complex multi-step rebases: Break into smaller operations
- Always verify after rebase: Run
git log --oneline -10to check result - Test locally first: Never experiment with rebasing in CI/production
- Create backups:
git branch backup-name HEADbefore risky rebases - Prefer simple operations: The simpler the rebase, the less likely to fail
Verification Checklist
After any rebase operation:
# Check commit history
git log --oneline -10
# Compare file changes to expected state
git diff <original-commit> HEAD --stat
# Verify build still works
pnpm build
# Check for unintended changes
git status
Real Example from This Session
Goal: Squash commits a5f4983..fc0d964 (11 commits exploring retry system), then re-apply commits after
# Step 1: Hard reset to end of range
git reset --hard 5106976
# HEAD is now at 5106976 refactor: radically simplify retry system
# Step 2: Soft reset to start (stages all changes)
git reset --soft a5f4983
# All changes from 11 commits now staged
# Step 3: Commit as one
git commit -m "feat: implement simple retry system for design tests"
# New commit created with squashed changes
# Step 4: Re-apply commits after fc0d964
git cherry-pick 8b23aeb 28f44d0 4029e70 b6b514d b8343ed 055f6f1 58ecfaf
# Commits after the squashed range preserved
# Step 5: Verify
git log --oneline -12
# Shows clean history with squashed commit + re-applied commits
Result:
- 11 commits squashed into 1
- 7 commits re-applied on top
- Clean, linear history
- No merge conflicts (deterministic)