git-rebase-interactive
Programmatic Interactive Git Rebase
This skill enables non-interactive execution of git interactive rebases. Use this when you need to:
- Absorb/fixup commits into earlier commits
- Squash multiple commits together
- Reorder commits
- Drop commits
- Reword commit messages (limited support)
The Problem
Git's interactive rebase (git rebase -i) normally opens an editor for the user to modify a "todo" file. The GIT_SEQUENCE_EDITOR environment variable can override this, but:
- macOS BSD sed has different syntax than GNU sed for multi-line operations
- Complex todo modifications (reordering, inserting lines) are error-prone with sed
The Solution
Write the complete todo file using a temporary shell script:
# 1. Create a temp editor script that writes the desired todo
EDITOR_SCRIPT=$(mktemp)
cat > "$EDITOR_SCRIPT" << 'SCRIPT'
#!/bin/bash
cat > "$1" << 'TODO'
pick abc1234 First commit message
fixup def5678 Commit to absorb into first
pick ghi9012 Third commit stays separate
TODO
SCRIPT
chmod +x "$EDITOR_SCRIPT"
# 2. Run the rebase with our custom editor
GIT_SEQUENCE_EDITOR="$EDITOR_SCRIPT" git rebase -i <base-commit>
# 3. Clean up
rm "$EDITOR_SCRIPT"
Step-by-Step Process
1. Identify the commits
git log --oneline -10
Note the commit hashes and messages you want to work with.
2. Determine the base commit
The base commit is the parent of the oldest commit you want to modify:
# If oldest commit to modify is abc1234
git rebase -i 'abc1234^' # Note: quote the caret for zsh
3. Plan the todo list
The todo file format is:
<action> <hash> <message>
Available actions:
| Action | Short | Description |
|---|---|---|
pick |
p |
Use commit as-is |
reword |
r |
Use commit but edit message (requires interaction) |
edit |
e |
Stop for amending (requires interaction) |
squash |
s |
Meld into previous, combine messages |
fixup |
f |
Meld into previous, discard this message |
drop |
d |
Remove commit entirely |
4. Build and execute
EDITOR_SCRIPT=$(mktemp)
cat > "$EDITOR_SCRIPT" << 'SCRIPT'
#!/bin/bash
cat > "$1" << 'TODO'
# Your todo list here - one line per commit
pick abc1234 First commit
fixup def5678 Second commit (will be absorbed)
pick ghi9012 Third commit
TODO
SCRIPT
chmod +x "$EDITOR_SCRIPT"
GIT_SEQUENCE_EDITOR="$EDITOR_SCRIPT" git rebase -i '<base>^'
rm "$EDITOR_SCRIPT"
Common Operations
Absorb fix commits into a feature commit
Scenario: You have a feature commit followed by fix commits that should be part of it.
abc1234 feat: add user authentication
def5678 fix: correct token validation <- absorb this
ghi9012 fix: handle edge case <- and this
jkl3456 chore: update deps <- keep separate
Todo:
pick abc1234 feat: add user authentication
fixup def5678 fix: correct token validation
fixup ghi9012 fix: handle edge case
pick jkl3456 chore: update deps
Squash multiple commits with combined message
Use squash instead of fixup to keep all commit messages:
pick abc1234 feat: add user model
squash def5678 feat: add user controller
squash ghi9012 feat: add user routes
This will combine all three into one commit and open an editor to combine messages (or use GIT_EDITOR to automate).
Reorder commits
Simply list commits in the desired order:
pick ghi9012 Third commit (now first)
pick abc1234 First commit (now second)
pick def5678 Second commit (now third)
Drop a commit
Use drop or simply omit the line:
pick abc1234 Keep this
drop def5678 Remove this
pick ghi9012 Keep this too
Handling Conflicts
If the rebase encounters conflicts:
- The rebase will pause with a message
- Resolve conflicts in the affected files
- Stage the resolved files:
git add <files> - Continue:
git rebase --continue - Or abort:
git rebase --abort
Complete Example Script
Here's a reusable function:
# Programmatic interactive rebase
# Usage: git_rebase_todo '<base>^' 'pick abc123 msg' 'fixup def456 msg' ...
git_rebase_todo() {
local base="$1"
shift
local EDITOR_SCRIPT
EDITOR_SCRIPT=$(mktemp)
{
echo '#!/bin/bash'
echo 'cat > "$1" << '\''TODO'\'''
for line in "$@"; do
echo "$line"
done
echo 'TODO'
} > "$EDITOR_SCRIPT"
chmod +x "$EDITOR_SCRIPT"
GIT_SEQUENCE_EDITOR="$EDITOR_SCRIPT" git rebase -i "$base"
local result=$?
rm "$EDITOR_SCRIPT"
return $result
}
# Example usage:
git_rebase_todo 'abc1234^' \
'pick abc1234 feat: add feature' \
'fixup def5678 fix: correct issue' \
'pick ghi9012 chore: update deps'
Limitations
rewordandeditactions still require user interaction for the actual editing- Conflict resolution requires manual intervention
- Complex rebases with many commits may be easier to do interactively
Troubleshooting
"fatal: could not read file..."
The todo file path wasn't passed correctly. Ensure the script uses "$1".
Rebase doesn't seem to do anything
Check that commit hashes in the todo match actual commits. Use short hashes (7+ chars).
zsh: no matches found: abc^
Quote the caret: 'abc1234^' or escape it: abc1234\^