How can I recover the commit message when the git commit-msg hook fails?
The commit message is stored in .git/COMMIT_EDITMSG
. After a "failed" committing attempt, you could run:
git commit --edit --file=.git/COMMIT_EDITMSG
or shorter, e.g.:
git commit -eF .git/COMMIT_EDITMSG
which will load the bad commit message in your $EDITOR
(or the editor you set up in your Git configuration), so that you can try to fix the commit message. You could also set up an alias for the above, with:
git config --global alias.fix-commit 'commit --edit --file=.git/COMMIT_EDITMSG'
and then use git fix-commit
instead.
Background
As stated, when running git commit
, git starts your editor pointing to
the $GIT_DIR/COMMIT_EDITMSG
file. Unless the commit-msg
hook in question
moves/deletes/damages the file, the message should still be there.
I suppose that reusing the message is not the default behavior because it might
interfere with the prepare-commit-msg
hook. Ideally, there would be a toggle
available to enable reusing by default, in order to avoid data loss. The
next-best thing would be to override a git sub-command with a git alias,
but unfortunately it is currently not possible and that is
unlikely to change. So we are left with creating a custom alias for it.
I went with an alias similar to the one in the accepted answer:
git config alias.recommit \
'!git commit -F "$(git rev-parse --git-dir)/COMMIT_EDITMSG" --edit'
Then, when running git recommit
, the rejected commit message's content should
appear in the editor.
Addition
Note that both aliases would fail for the first commit in the repository, since the
COMMIT_EDITMSG
file would not have been created yet. To make it also work in
that case, it looks a bit more convoluted:
git config alias.recommit \
'!test -f "$(git rev-parse --git-dir)/COMMIT_EDITMSG" &&
git commit -F "$(git rev-parse --git-dir)/COMMIT_EDITMSG" --edit ||
git commit'
Which can be shortened to:
git config alias.recommit \
'!cm="$(git rev-parse --git-dir)/COMMIT_EDITMSG" &&
test -f "$cm" && git commit -F "$cm" --edit || git commit'
Either way, considering the added safety, for interactive usage you could even
use one of the aforementioned aliases by default instead of git commit
.
You could also make a wrapper for git itself and divert the calls based
on the arguments (i.e.: on the sub-command), though that would require ensuring
that all subsequent calls to git
refer to the original binary, lest they
result in infinite recursion:
git () {
cm="$(git rev-parse --git-dir)/COMMIT_EDITMSG"
case "$1" in
commit)
shift
test -f "$cm" && command git commit -F "$cm" --edit "$@" ||
command git commit "$@"
;;
*)
command git "$@";;
esac
}
Note that if the above is added to your rc file (e.g.: ~/.bashrc
), then every
call to git
present in it will refer to the wrapper, unless you prepend them
with command
as well.
Novelty
Finally, I just learned that aliasing to a wrapper file with a different name is an option:
PATH="$HOME/bin:$PATH"
export PATH
alias git='my-git'
So the wrapper (e.g.: ~/bin/my-git
) can be much simpler:
#!/bin/sh
cm="$(git rev-parse --git-dir)/COMMIT_EDITMSG"
case "$1" in
commit)
shift
test -f "$cm" && git commit -F "$cm" --edit "$@" ||
git commit "$@"
;;
*)
git "$@";;
esac
And also avoid interference, as aliases are not expanded when used in external scripts.