The question asks if there is a way to do this and ends by adding "is there a way to do this without a shell script". I take that to mean that the OP wants a way to do it that is preferably without a wrapper if possible. Since it is not possible without a wrapper, here is what I did to add some extra checks on my use of git. It involves writing a wrapper script which I alias to git in my shell startup files. The script makes the checks and exits with a message if I did something I'm trying not to do anymore otherwise it calls command git "$@"
.
The following script disables "git reset --hard" if there are uncommitted (staged or unstaged) changes to the repo.
function has_arg(){
local arg=$1; shift
for a in "$@" ; do
if [[ "${a}" == "${arg}" ]] ; then
return 0
fi
done
return 1
}
function repo_is_clean(){
git diff --no-ext-diff --quiet 2>/dev/null \
&& git diff --no-ext-diff --cached --quiet 2>/dev/null
}
# Copied straight from git-completion.bash to determine the git command
# with minor adaptations (using ${!c} to get the c-th argument instead
# of ${COMP_WORDS[c]} and some stuff removed).
declare i c=1 git_command
while [ $c -le ${#} ]; do
i=${!c}
case "$i" in
--git-dir=*) ;;
--git-dir) ((c++)) ;;
--bare) ;;
--help) git_command="help"; break ;;
-c|--work-tree|--namespace) ((c++)) ;;
-C) ((c++)) ;;
-*) ;;
*) git_command="$i"; break ;;
esac
((c++))
done
if [[ ${git_command} == reset ]] && has_arg --hard "$@" && ! repo_is_clean ; then
echo "PHIL: You have uncommitted changes, use stash to get rid of them"
echo " and git stash drop later which will allow you an extra"
echo " opportunity to realize if you made a mistake"
exit 1
fi
command git "$@"
Some notes about how it works:
has_arg
checks if its first argument is present in the list of following arguments so has_arg --hard "$@"
checks if the arguments passed to the script contain --hard
.
repo_is_clean
:The git diff --no-exit-diff --quiet
exists with 0 if there are no unstaged changes and the one with --cached
does the same for staged changes. So if the function fails (returns non-zero), there are some uncommitted changes.
- The while loop is from git-completion.bash, I found the code that it uses to determine what the git command is and stripped down what was unnecessary for this script's purposes.
- One of the checks: if the command is reset and
--hard
is present and the repo has uncommitted changes, print a message and exit.
- If we make it to the end of the script, delegate to the actual
git
command forwarding all arguments.
To use this, make this script executable and create an alias for it in your shell startup files
alias git=<your-script>
Breaking habits
Because the question mentions breaking habits, here is something a bit more "intense" that I did to break a different habit.
For reasons explained below, I wanted to break the habit of doing git commit -m
and use the editor instead by doing git commit
(without -m
):
if [[ "${git_command}" == commit ]] && has_arg -m "$@"; then
echo "PHIL: Don't use -m for commits"
exit 1
fi
For a while, I was still always doing git commit -m "message"
, then getting the message, then doing it in the editor. I wanted that to change, so I made it more painful by replacing the simple echo
with the following:
if [[ "${git_command}" == commit ]] && has_arg -m "$@"; then
trap 'n=0; echo "sorry, you gotta wait! setting counter back to 0"' SIGINT
echo "To help break the habit of using git commit -m, please endure"
echo "uh, I mean 'enjoy' this 5 second uninterruptible sleep"
for((n=1;n<=5;n++)); do
sleep 1
printf "\r${n}"
done
echo ""
exit 1
fi
The trap
on SIGINT
causes the echo -n ...
to be run when the user presses Ctrl-C
in the shell to attempt to get out of the 5 second penalty.
Note that there is nothing wrong with using git commit -m
as long as you write properly formatted commit messages. But there are people who write one-super-long-line commit messages and I want to be able to encourage them to always use the editor, and for that I have to do it myself.
And there are other reasons why I like using the editor all the time now but that's not relevant to the question and my "punishment" system barely is anyway.
Alternative to git reset --hard
While git reset --hard
can be used to move where a branch points, the fact that it is a habit for you and that you mentioned git stash && git stash drop
leads me to believe that you use it to get rid of unwanted uncommitted changes with the potential of realizing too late that you got rid of some changes that you wanted to keep.
In that case, the git checkout -p
command is useful. It is like git add -p
in that it shows you hunks of changes interactively and asks you to decide something. You press 'y' to discard the hunk