As you mentioned, the edit
cmd inside git-rebase
doesn't quite do all the steps you need to do to actually edit (mutate) a commit.
First, you may decide, you don't want to look at merge conflicts.
git rebase --strategy recursive --strategy-option theirs -i <base-sha>
Since you are going to check/edit each commit... if you aren't changing too many things, you should be able to spot what is wrong about a commit, based on a previous change you did. Notably, if you added a code comment in an early commit, and saw a subsequent commit deletes that comment, you should simply restore the comment. This is easy in vscode's side by side diff view (which I use all the time).
Finally, we do need to use some sort of rebase exec
command to pop changes into our working directory:
exec MSG=$(git log -1 --format=%B HEAD); git undo; git reset; echo "$MSG" > $GIT_DIR/LAST_COMMIT_MSG; echo "editing commit:\n\n$MSG\n";
Maybe, you use vscode, we can also open the edited files for you:
code $(git diff --staged --name-only)
UPDATE: This doesn't open the actual diffs, so is a waste for me, not included in final command. Maybe a vscode shortcut key would work, or if this entire review flow was simply packaged up into a vscode extension.
This exec command will always fail, so we'll want --no-reschedule-failed-exec
// Putting it all together ... there are further changes below.
GIT_SEQUENCE_EDITOR=: git rebase --exec 'MSG=$(git log -1 --format=%B HEAD); git undo; git restore --staged $(git diff --name-only --staged --diff-filter=r); echo "$MSG" > $GIT_DIR/LAST_COMMIT_MSG; echo "editing commit:\n\n$MSG\n";' --strategy recursive --no-reschedule-failed-exec --strategy-option theirs -i 315abbd5b
To cycle onto the next commit, simply run:
git add --all && git commit && git rebase --continue
We'll then need this prepare-commit-msg
script to re-use the LAST_COMMIT_MSG
file:
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
COMMIT_MSG_FILE=$1
# COMMIT_SOURCE=$2
# SHA1=$3
if [ -f $GIT_DIR/LAST_COMMIT_MSG ]; then
cat $GIT_DIR/LAST_COMMIT_MSG $COMMIT_MSG_FILE > temp_commit_msg && mv temp_commit_msg $COMMIT_MSG_FILE
rm $GIT_DIR/LAST_COMMIT_MSG
fi
Add this few hooks to wipe any stale LAST_COMMIT_MSG
:
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
echo "some git-hook: wiping $GIT_DIR/LAST_COMMIT_MSG: $(cat $GIT_DIR/LAST_COMMIT_MSG)"
rm $GIT_DIR/LAST_COMMIT_MSG
This way, you just run git commit
when you are done editing a commit, and, you'll get to re-use/tweak the original commit message.
If using husky
, you'll need to:
echo ".husky/prepare-commit-msg" >> $(git rev-parse --show-toplevel)/../main-worktree/.git/info/exclude
echo ".husky/pre-rebase" >> $(git rev-parse --show-toplevel)/../main-worktree/.git/info/exclude
echo ".husky/post-rewrite" >> $(git rev-parse --show-toplevel)/../main-worktree/.git/info/exclude
echo ".husky/post-commit" >> $(git rev-parse --show-toplevel)/../main-worktree/.git/info/exclude
# paste it in:
code -n .husky/prepare-commit-msg .husky/pre-rebase .husky/post-rewrite .husky/post-commit
chmod +x .husky/prepare-commit-msg
chmod +x .husky/pre-rebase
chmod +x .husky/post-rewrite
chmod +x .husky/post-commit
UPDATE:
This whole command becomes quite a bear, so let's use an alias to clean it up:

git config --global --edit
Add few aliases:
next = "!sh -c 'git add --all && git commit $@ && git rebase --continue' -"
redo = !echo "$(git log -1 --format=%B HEAD)" > $GIT_DIR/LAST_COMMIT_MSG && git undo && git restore --staged $(git diff --name-only --staged --diff-filter=ard) > /dev/null 2>&1 || true && cat $GIT_DIR/LAST_COMMIT_MSG && echo '' && git -c advice.addEmptyPathspec=false add -N $(git ls-files --others --exclude-standard) > /dev/null 2>&1 || true
review-stack = "!GIT_SEQUENCE_EDITOR=: git rebase --exec 'git redo' --strategy recursive --no-reschedule-failed-exec --strategy-option theirs --interactive"
Finally, using alias:
git review-stack 315abbd5b
# Go to next commit, no frills:
git next --no-edit --no-verify
# If you made changes:
git next