I have a shell script that wraps git rebase --interactive and I’d like it to do some things after the rebase is complete. (I want it to notify the user if there are differences between the initial and final contents of the repo.) However, it’s not possible to just write
git rebase --interactive some_commit
# ...check for differences here...
because if the user opts to edit any commits, or if there are any conflicts that need to be resolved, then git will exit at that point and the following lines will be run, even though conceptually the rebase is not done yet.
I thought about installing a temporary “post-rebase” hook, but there is no such hook. The answers to this question suggest that a post-rewrite hook will work in some circumstances. However, I would want to clean up the hook even if the user ran git rebase --abort or git rebase --quit, and I assume the post-rewrite hook will not fire in these situations. (I don’t want to install permanent hooks to support this script, because I’d like the script to work on any repo without any setup needed beforehand. Besides, some of my repos already use post-checkout hooks to do unrelated things.)
Is there a way to get Git to run a certain script when a rebase finishes?
The solution is to have two separate scripts: the one that kicks off git rebase --interactive (the “wrapper script”) and one that will be run at the end of the rebase (the “post-rebase script”). You can insert an exec line into the rebase todo list to ensure that the latter is run automatically at the end of the rebase. Furthermore, you can set the GIT_SEQUENCE_EDITOR environment variable to automate the addition of the exec line itself.
Take the logic that should be run at the end of a rebase and put it into a separate script. (Note that this script will not be run if the rebase is aborted or quit.) If the script needs any context other than what’s available in the environment during a rebase, it should accept this information as command-line arguments.
For example, my shell script is called check_git_diff and it looks like
#!/usr/bin/env zsh
if [[ $# -ne 1 ]]; then
print >&2 "usage: check_git_diff ORIGINAL_HEAD"
exit 1
fi
readonly original_head=$1
if ! git diff --quiet $original_head HEAD; then
kind='non-whitespace'
if git diff --quiet --ignore-all-space $original_head HEAD; then
kind='whitespace'
fi
print -P >&2 "%F{red}Warning:%f There are $kind differences from the original HEAD. See"
print -P >&2 "\n %F{blue}git diff $original_head HEAD%f\n"
fi
You can ask Git itself to run this script at the end of the rebase by adding an exec line to the end of the todo list. After all the pick, squash, fixup, etc. lines, add one for the post-rebase script:
pick 8bbfd15 update build instructions
pick caf5bfd frobnicate some additional blobs
exec check_git_diff caf5bfd3d4b6099aeed13604936976e610a08e18
# Rebase 9f594d2..caf5bfd onto 8bbfd15 (2 commands)
#
# ...
As you can see, you can include arguments to the post-rebase script if you need to.
If you run git rebase --abort or git rebase --quit, Git will stop running these commands, which is why I said above that check_git_diff will only be run at the conclusion of a successful rebase.
Since it’s your script that’s kicking off git rebase --interactive in the first place, you have the opportunity to automate the previous step. In the wrapper script, set the GIT_SEQUENCE_EDITOR environment variable prior to calling git rebase --interactive. For example,
original_head=$(git rev-parse HEAD)
export GIT_SEQUENCE_EDITOR="f() {
sed -i -e '/^$/a exec check_git_diff $original_head\n' \$1 && exec vim \$1
}; f"
If the GIT_SEQUENCE_EDITOR environment variable is set, Git will use its value as the name of the editor to use to edit the todo list. Typically you’d set this to something like vim or emacs, but here I’m actually defining a shell function and then using that function as the editor. (Git uses Bash to run the contents of GIT_SEQUENCE_EDITOR.) The shell function does two things: it calls sed to modify the todo list to include the exec line, and then it runs Vim so that you can edit the todo list like normal. Of course, you can replace vim with the name of your preferred editor.
The net effect is that you can run some certain logic before the interactive rebase starts, and then run some certain other logic after it successfully ends.
Thank you to @torek for bringing the GIT_SEQUENCE_EDITOR variable to my attention. That makes the solution a lot simpler!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With