Warn when doing git commit
Sometimes from muscle memory, I run git commit -a
when I have some files or parts of files carefully staged and ready to commit, causing me to lose my careful staging action.
Is there a way to make git commit -a
warn if there is anything (file or patch) currently staged?
(Clearly I should just use -a
less to alleviate my problem, but my question stands.)
Unfortunately, Git doesn't allow aliases to override existing commands, otherwise you could easily add this functionality via an alias.
But, you can get part way there. It would require retraining yourself to type something instead of git commit
—perhaps git c
.
Here's how you could do it:
Put the following shell code in a script file somewhere (eg, /path/to/commit-wrapper
)
#!/bin/sh
# avoid echo because some implementations treat backslashes specially
log() { printf '%sn' "$*"; }
error() { log "ERROR: $*" >&2; }
fatal() { error "$*"; exit 1; }
check_for_staged() {
git diff-index --cached --quiet HEAD || {
# simply exit with an error if run non-interactively
tty >/dev/null
|| fatal "don't use '$1' when you have staged changes"
# this script is being run interactively; prompt the user to
# continue
error "'$1' option used when there are staged changes"
while true; do
printf 'Continue anyway? [y/N] ' >&2
read answer || { printf 'n'; answer=N; }
[ -n "${answer}" ] || answer=N
case ${answer} in
y|Y) break;;
n|N) echo "aborted" >&2; exit 1;;
*) error "Please answer 'y' or 'n'.";;
esac
done
}
}
# TODO: use 'git rev-parse --parseopt' to reliably detect '-a' (e.g.,
# to properly handle invocations such as 'git commit -sa')
for i in "$@"; do
case ${i} in
--) break;;
-a|--all) check_for_staged "${i}"; break;;
esac
done
git commit "$@"
chmod a+x /path/to/commit-wrapper
git config --global alias.c '!/path/to/commit-wrapper'
git c
instead of git commit
. If Git is ever changed to allow aliases for existing commands, change the last line to say "$(git --exec-path)"/git-commit "$@"
to avoid an infinite loop.
Add a pre-commit
hooke in your repo, with something like below:
#!/bin/sh
echo "Make sure you haven't used the -a flag or verify git diff --cached returns nothing"
echo "Run commit again with --no-verify if ok to proceed"
exit 1
That should help you overcome your "muscle memory".
Unfortunately, pre-commit hook cannot be powerful enough to do more checks ( like if you had supplied the -a
argument to the commit. ) Also, when you do -a
, the pre-commit hook will see as though all the files were staged, eventhough after the execution, you will see them as unstaged. So you cannot differentiate between what you had staged previously and the files staged because of the -a
.
You can use your favorite scripting language to create git2
command, that will check if you're running it with arguments commit -a
. If that is the case, run /usr/bin/git status --porcelain
and check if there are some changes to the index (porcelain-formatted output is easier to parse, my first guess would be to run it through grep '^[AMD]'
and check whether it found something. Now, you can either print a warning and quit, or run /usr/bin/git
which all original argument to continue like there was no git2 (which is also what you do if you haven't been run with commit -a).
Here's an example in Perl (which git requires anyway):
#!/usr/bin/env perl
use strict;
use warnings;
my %aliases = map { split(/n/, $_, 2) }
split / /,
`git config -z --get-regexp alias.`;
my %commit_aliases = (( commit => 1 ),
map { s/alias.//; $_ => 1 }
grep $aliases{$_} =~ /^commitb/,
keys %aliases);
my ($command, @args) = @ARGV;
if ($commit_aliases{$command} && $args[0] =~ /^-a|^--all/) {
my @staged = grep /^M/, split / /, `git status -z`;
if (@staged) {
print "There are staged changes, are you sure you want to commit all? (y/N) ";
chomp(my $answer = <STDIN>);
if ($answer =~ /^y/i) {
run_command()
}
} else {
run_command()
}
} else {
run_command()
}
sub run_command {
system 'git', $command, @args;
exit $? >> 8;
}
Then, create a bash alias alias git2 git
and you're all set.
下一篇: 在做git commit时发出警告