Git formatting capture code capture with partial commit?

is there a way to have a pre-commit binding that automatically formats the code (for example with astyle ) but doesnโ€™t destroy the 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 git add inside the pre-commit hook, which is required after a formatted source code script, adds a โ€œdifferentโ€ snippet, too. But I do not want this.

Is there any way to achieve this?

+6
source share
2 answers

I would do this by working with low-level plumbing commands, my first attempt would be something like strings

 git ls-files --stage \*.c | while read mode object stage path; do case $mode in 10*) formatted=`git show $object | indent | git hash-object -w --stdin` git update-index --cacheinfo $mode $formatted "$path" ;; esac done 

To avoid redundant processing, start by outputting git diff-index --name-only --diff-filter=AM , as @torek suggests.

+3
source

There is a (sort of) way to do this. I would not want to, but if you really want to, continue in this direction.

First, you need to separate the two elements that you have:

  • phased changes
  • unidentified work tree elements

In addition, you want the first set to be available for reformatting.

This can be done using git stash , as I showed in the answer to How to properly git stash / pop in pre-commit hooks to get a clean working tree for tests? (see the warning there for an error in git stash too, though).

Basically, you want to reach a point in the script where the tests run:

 # Run tests status=... 

In this state, you can run elements of the work tree via formatters, and git add result in the pre-commit hook (as you already discovered). This will avoid formatting the โ€œotherโ€ fragment, since it was not in the working directory version. Then you can continue committing (i.e. the rest of the script does not apply here).

The problem is that the working tree version from stash is now being restored. Since you changed the index, you cannot return to this even after the commit is completed:

 # Restore changes git reset --hard -q && git stash apply --index -q && git stash drop -q 

Instead, you want to find the difference between the stored index ( stash^1 ) and the hidden tree ( stash ) and apply this to the new HEAD commit. There are at least two ways to do this without using git plumbing commands. Both can lead to conflicts due to reformatting the perfect version:

  • git diff stash^1 stash | git apply --reject git diff stash^1 stash | git apply --reject (and finally 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 erratic, since changes that will have merge conflicts are deleted in the "reject" files. Method 2 uses a merge mechanism, so if necessary changes get conflict markers. (If there are no conflicts, -n prevents the commit so that you can make your own with the real message, rather than copying the dummy for-cherry-pick message.)

Of course, I did not test anything. In addition, there are methods for doing this without using git stash , for example, checking the index versions of git add -ed files in a separate directory, formatting things there, and then adding formatted versions back so that none of this process affects the current working directory. In any case, this is probably superior if you are really determined to do it. Here's the script for this method (also not really tested), it requires a bit of added reliability, using -z and xargs -0 , perhaps to handle file names containing spaces in the control of the 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: instead of formatting the code at this point in pre-commit, just check if it is formatted. If so, allow the commit. If not, reject it. This is much more in the spirit of pre-commit bindings, and it allows the script to be used in a different answer . Basically, the moment he says:

 status=... 

you just run something that checks if the formatter will change anything (perhaps letting the formatting do its job and seeing if the result differs from that in the index that needs to be fixed). It gives you your status. Then you do the rest in the script, with git reset --hard -q && git stash apply --index -q && git stash drop -q restores everything as it was when creating stash.

+1
source

All Articles