How does this pre-commit hook fix trailing whitesp

2020-04-11 02:43发布

问题:

What is going on in this pre-commit hook? I thought changing files would cause them to be restaged.

#!/bin/sh
#
# A git hook script to find and fix trailing whitespace
# in your commits. Bypass it with the --no-verify option
# to git-commit
#

if git-rev-parse --verify HEAD >/dev/null 2>&1 ; then
    against=HEAD
else
    # Initial commit: diff against an empty tree object
    against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi
# Find files with trailing whitespace
for FILE in `exec git diff-index --check --cached $against -- | sed '/^[+-]/d' | sed -r 's/:[0-9]+:.*//' | uniq` ; do
    # Fix them!
    sed -i 's/[[:space:]]*$//' "$FILE"
done

# Now we can commit
exit

I think the idea is to remove trailing whitespace in all files this commit touches.

回答1:

The key is to commit the right content, that is:

  • only what has been stages (and added to the index)
  • plus some modifications introduced by the pre-commit hook

The first point is achieved through the git diff-index

Compares the content and mode of the blobs found via a tree object with the content of the current index and, optionally ignoring the stat state of the file on disk.

exec git diff-index --check --cached $against --

with the option --cached:

do not consider the on-disk file at all

Any modification is then taken into account to be part of the new commit.

You can look at the source of commit.c:

static int prepare_to_commit(const char *index_file, const char *prefix,
                 struct wt_status *s)
{
...

    if (!no_verify && run_hook(index_file, "pre-commit", NULL))
        return 0;
...


/*
 * Re-read the index as pre-commit hook could have updated it,
 * and write it out as a tree.  We must do this before we invoke
 * the editor and after we invoke run_status above.
 */
discard_cache();
read_cache_from(index_file);


回答2:

Except that this does not work. I've tried doing the following at the end of my pre-commit hooks:

exec git diff-index --check --cached $against --

but the changes made in those hooks still do not actually get committed (at least in git 1.7.3.4).

If you actually want the changes to go in, you must explicitly

git add "$file"

for each file that you modified during the pre-commit phase.



回答3:

It is possible to do that, but requires a tricky script.

Here you can find the same problem solved. There, it is updating the file version on every commit, instead of trilling spaces. It is fully working: https://github.com/addonszz/Galileo/tree/master/githooks

Then you just replace the 'Version File Replacement' algorithm on the file 'updateVersion.sh', by your 'Trilling Spaces' algorithm. Maybe you need to change a few things like, remove the branch limitation, because there, the script only runs if you are on the 'develop' branch.

Also, it will only change the file, if is staged. If the file is not staged, then it will do nothing. More precisely, it print out what it is doing on every step.