is there a way to have a pre-commit hook which auto-formats the code (for
example with astyle
) but does not destroy a partial commit?
Workflow:
# edit a file.txt
git add -p file.txt
# add one chunk, but not another
git commit -m 'a message'
[PRE_COMMIT_HOOK] Formatting source code
git status
# the "another" chunk is still not added
My problem is, that if you do a git add
inside the pre-commit hook, which is
required after the script formatted the source code, adds the "another" chunk,
too. But I don't want that.
Is there a way to achieve this?
I'd do this by doing the work with the low-level "plumbing" commands, my first attempt would be something along the lines of
To avoid redundant processing, start from
git diff-index --name-only --diff-filter=AM
output as @torek suggests.There is (sort of) a way to do it. I wouldn't, but if you really want to, proceed along these lines.
First, you need to separate out the two items you have now:
Moreover, you want the first set to be available for reformatting.
This can be done with
git stash
, as I showed in the answer to How do I properly git stash/pop in pre-commit hooks to get a clean working tree for tests? (see the warning in there about a bug in git stash too, though).Basically, you want to reach the point in the script there where the tests get run:
Once in this state, you can run work-tree items through formatters, and
git add
the result in a pre-commit hook (as you already discovered). This will avoid formatting the "another" chunk since it was not in the work directory version. You can then let the commit proceed (i.e., the rest of the script does not apply here).The problem is now restoring the work-tree version from the stash. Because you modified the index, you can't go back to this, even after the commit finishes:
Instead, what you want is to find the diff between the stashed index (
stash^1
) and the stashed work-tree (stash
), and apply that to the newHEAD
commit. There are at least two ways to do this without using git plumbing commands. Both are likely to result in conflicts due to the reformatting of the committed version:git diff stash^1 stash | git apply --reject
(and eventuallygit stash drop
)git stash branch tempbranch; git commit -m for-cherry-pick; git checkout prev-branch; git cherry-pick -n tempbranch; git branch -D tempbranch
Method 1 is simpler but messy, as changes that would have merge conflicts get dropped into "reject" files. Method 2 uses the merge machinery, so changes get conflict markers instead, if needed. (If there are no conflicts, the
-n
prevents a commit so that you can do your own with a real message, instead of copying the dummyfor-cherry-pick
message.)Of course I have not tested any of this. Also, there are methods for doing this without using
git stash
, such as checking out the index versions of thegit add
-ed files into a separate directory, formatting things there, and then adding the formatted versions back, so that none of this process affects the current work directory. This is probably superior anyway, if you're really determined to do this. Here's an script for that method (also not really tested—it needs a bit of robustness added, using-z
andxargs -0
perhaps, to handle file names containing white space, in the checkout of diff-index output section):Here's what I would recommend instead though: rather than formatting the code at that point in the pre-commit hook, simply check whether it is formatted. If so, permit the commit. If not, reject it. This is much more in the spirit of a pre-commit hook, and it allows using the script in that other answer. Basically, at the point where it says:
you simply run something that checks whether the formatter would change anything (perhaps by allowing the formatter to do its thing, and seeing if the result is different from what's in the to-be-committed index). That gets you your status. Then you do the rest of what's in the script, with the
git reset --hard -q && git stash apply --index -q && git stash drop -q
restoring everything to the way it was when the stash was created.