简体   繁体   中英

Tidy file in git pre-commit hook

I want to tidy files in the git pre-commit hook, and then have the tidied version committed.

Pre-commit hook pseudo code

for ($file in modified_files) {
    tidy($file)
    git add $file
}

This workflow tidies and commits files as expected:

  1. git add $file

  2. git commit -m "foo"

This workflow tidies and commits the files, but still leaves a version staged:

  1. git add $file

  2. git commit -m "foo" $file

Question

Why does the latter workflow still leave the file staged, even though the tidied version has been committed? It is almost as if a duplicate exists. I am using git version 2.7.3.

It is almost as if a duplicate exists.

A duplicate does exist.

Specifically, given that there is a current commit ( HEAD or @ ), there are three versions of each file available. One is read-only and is the one stored in the HEAD commit itself. The other two are the one in the index, which you update by running git add $file , and the one in the work-tree, that is in ordinary text format (as opposed to the special Gitty formats for the commit and index versions), so that you and all your programs can work on it.

So far, though, this doesn't explain the problem. The real explanation has to do with the way git commit $file affects the --only vs --include option to git commit . It also has to do with the fact that while there is the index—one special, distinguished index that is associated with the work-tree—Git is also able to work with some other index, temporarily.

We now need several more facts:

  1. The default action for git commit is the same as that for git commit --include , but the default action for git commit $file is the same as that for git commit --only $file .

  2. When using git commit --only with a list of files, Git constructs a temporary , second index.

  3. After git commit succeeds, Git wants the index to match the commit. But wait ... which index are we talking about here in point 3?

The index is what Git uses to build each commit. In other words, what's in the index right now is what you and Git propose should be in the next commit you will make.

When you use the (ordinary) index, or use --include which adds its extra files to the index, Git can make the new commit from the index, and afterward, the current ( HEAD or @ ) commit matches the index, because Git just made that commit from the index, so everything is all good.

But when you use some other index, Git makes a new commit from the files in the temporary index. That new commit then becomes the HEAD commit, and Git throws away the temporary index. This means that the (ordinary) index would get restored to its rightful throne, guarding the entrance to the Git castle (or is it a dungeon?) from the ordinary rabble files in the work-tree.

But if the original "the index", the one about to be restored, has those files, those specifically-committed files, the one you named with --only , in the form they had earlier in old HEAD commit, then the files you very specifically told Git to update-and-commit would all get restored to their old, smelly, pre-special-honor- git-commit -form. So what Git does before tossing out the temporary index is to copy it back to the (regular) index.

This, however, loses things you have carefully staged in the real index, because now the index, back on its throne, has secretly been replaced by this usurper. Most of the time you won't notice, as the usurper normally looks an awful lot like the original. You only see this effect when you first carefully stage a few things, then git commit --only , and then find that your careful staging has been erased.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM