问题
I'm using an an approach similar to this one to use a pre-commit hook to track changes to my database schema (as well as a few metadata-ish tables).
I like to try to keep my commits clean so I want to be loudly warned in the commit message when there are automatic changes being staged/committed. Here are my pre-commit
and pre-commit-msg
hooks:
.git/hooks/pre-commit
#!/bin/sh
# Save user changes to db/ (if any)
git diff --quiet db/
user_dirty=$?
[[ $user_dirty > 0 ]] && git stash save --keep-index
# Regenerate db/ automatically
db/save_schema_and_meta_tables.sh
# Were any automatic changes made? If so, commit them but warn about it
git diff --quiet db/
auto_dirty=$?
if [[ $auto_dirty > 0 ]]; then
git add db/
echo "WARNING: automatic changes to db/ added to commit" | tee .git/COMMIT_WARNING
fi
[[ $user_dirty > 0 ]] && git stash pop
exit 0
.git/hooks/prepare-commit-msg
#!/bin/sh
msgf=$1
wf=.git/COMMIT_WARNING
if [ -e $wf ]; then
msg=$(<$msgf)
( cat $wf; echo "$msg" ) > $msgf
rm -f $wf
fi
Here's how it behaves:
- If I have made changes to
db/
but haven't staged them yet, they are kept in the working tree without disturbing the commit, thanks tostash save --keep-index
andstash pop
. Good! - However, if I have staged changes to
db/
and they are overwritten by the automatic commit, then the user-intended changes are gone after the commit. Bad!
Here's what I would like to happen: if there are user-staged changes to db/
and they don't exactly match the automatic changes, then the whole thing should abort. I'm having a lot of trouble figuring out how to implement this: how can I save the staged changes made by the user, then see if the automatic changes don't match?
回答1:
It's not pretty, and it's slow, but with some of @torek's suggestions I came up with the following.
- Check if user has staged changes to the
db/
directory (user_staged=1
if so) - Stash user changes, while preserving the staging area
- Auto-generate the contents of the
db/
directory - Compare the staged version of the
db/
directory to the auto-generated version (auto_changes=1
if they differ) - Restore the user's working directory (modulo the bug torek identified), while preserving a copy of the auto-generated version of
db/
- Decision:
- If user-staged and auto-generated
db/
matched, all is well - If user-staged and auto-generated
db/
didn't match, abort - If user hadn't staged any changes to
db/
but there are auto-generated changes, stage them and continue, but warn about them in the commit message
- If user-staged and auto-generated
The pre-commit
hook code:
# Has user staged changes to db/?
git diff --quiet --staged db/
user_staged=$?
# Stash any user changes in the working tree
old_stash=$(git rev-parse -q --verify refs/stash)
git stash save -q --keep-index
new_stash=$(git rev-parse -q --verify refs/stash)
[[ "$old_stash" != "$new_stash" ]] && stashed=1 || stashed=0
# Automatically regenerate db/
db/save_schema_and_meta_tables.sh
cp -a db db_AUTO
# Compare automatically-generated changes to what the user had already staged
git diff --quiet db/
auto_changes=$?
# Restore user's state
[[ $stashed ]] && git reset --hard -q && git stash apply --index -q && git stash drop -q
# abort: if user had staged changes to db/, and automatic changes would overwrite them
# add but warn: automatic changes added, but no user changes to db/
# silent: no user-staged changes, no automatic changes
if (( $auto_changes > 0 )); then
if (( $user_staged > 0 )); then
echo "ERROR: automatic changes to db/ conflict with staged changes"
rm -rf db_AUTO
exit 1
else
rm -rf db/
mv db_AUTO db
git add db/
echo "WARNING: automatic changes to db/ added to commit" | tee .git/COMMIT_WARNING
exit 0
fi
else
rm -rf db_AUTO
fi
来源:https://stackoverflow.com/questions/25536034/modifying-working-directory-and-staging-area-temporarily-in-git-pre-commit-hook