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:
- the staged changes
- the not-staged work tree items
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:
# Run tests
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:
# Restore changes
git reset --hard -q && git stash apply --index -q && git stash drop -q
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 new
HEAD 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 eventually
git 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 dummy
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 the
git 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
xargs -0 perhaps, to handle file names containing white space, in the checkout of diff-index output section):
# make a directory for formatting files
WORKDIR=$(mktemp -d -t reformat) || exit 1
# clean it up when we leave
trap "rm -rf $WORKDIR 0 1 2 3 15"
# for files Added or Modified in the index, copy them to $WORKDIR
git --work-tree=$WORKDIR checkout -- \
$(git diff-index --cached --name-only --no-renames --diff-filter=AM HEAD)
# reformat files in the work-dir
(cd $WORKDIR; ...)
# for each file in the work-dir, re-"add" that version to this tree
# (this assumes the reformatter did not leave extraneous files!)
git --work-tree=$WORKDIR add --ignore-removal .
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.