Git 'pre-receive' hook and 'git-clang-format' script to reliably reject pushes that violate code style conventions

后端 未结 2 910
暗喜
暗喜 2021-02-20 05:13

Let\'s immediately start with a scrap of the pre-receive hook that I\'ve already written:

#!/bin/sh
##
  format_bold=\'\\033[1m\'
   format_red=\'\\         


        
相关标签:
2条回答
  • 2021-02-20 05:49

    Condensed

    I had a little bit of trouble understanding the first example, in part due to the length and extra tidbits that make it useful for the OP's specific use case. I combed through and condensed it down to this:

    ref_name=$1
    new_rev=$3
    
    # only check branches, not tags or bare commits
    if [ -z $(echo $ref_name | grep "refs/heads/") ]; then
      exit 0
    fi
    
    # don't check empty branches
    if [ "$(expr "${new_rev}" : '0*$')" -ne 0 ]; then
      exit 0
    fi
    
    # Checkout a copy of the branch (but also changes HEAD)
    my_work_tree=$(mktemp -d -t git-work-tree.XXXXXXXX) 2>/dev/null
    git --work-tree="${my_work_tree}" --git-dir="." checkout $new_rev -f >/dev/null
    
    # Do the formatter check
    echo "Checking code formatting..."
    pushd ${my_work_tree} >/dev/null
    prettier './**/*.{js,css,html,json,md}' --list-different
    my_status=$?
    popd >/dev/null
    
    # reset HEAD to master, and cleanup
    git --work-tree="${my_work_tree}" --git-dir="." checkout master -f >/dev/null
    rm -rf "${my_work_tree}"
    
    # handle error, if any
    if [ "0" != "$my_status" ]; then
      echo "Please format the files listed above and re-commit."
      echo "(and don't forget your .prettierrc, if you have one)"
      exit 1
    fi
    

    This example is using Prettier, but it'll map pretty well to clang-format, eslint, etc. There are a few limitations to the (perhaps oversimplified, but working) example above. I'd recommend diving deeper...

    Better, but longer

    Once you've grok'd that I'd also recommend taking a scroll down towards the bottom of this one:

    • Reject Ugly Commits with Server-Side Git Hooks
    0 讨论(0)
  • 2021-02-20 05:50

    NOTE:
    For those looking for an up-to-date, (more or less) comprehensive, and well-tested solution, I host the corresponding public repository [1]. Currently, the two important hooks relying on git-clang-format are implemented: pre-commit and pre-receive. Ideally, you get the most automation and fool-proof workflow when using both of them simultaneously. As usual, improvement suggestions are very welcome.

    NOTE:
    Currently, the pre-commit hook [1] requires the git-clang-format.diff patch (authored by me as well) [1] to be applied to git-clang-format. The motivation and use case examples for this patch are summarized in the official patch review submission to LLVM/Clang [2]. Hopefully, it will be accepted and merged upstream soon.


    I've managed to implement a solution for the second question. I have to admit that it was not easy to find due to scarce Git documentation and absence of examples. Let's take a look at the corresponding code changes first:

    # ...
    clang_format() {
      git clang-format --commit="${commit}" --style='file' "${@}"
    }
    # ...
          for sha1 in $(list "${sha1_range}"); do
            git checkout --force "${sha1}" > '/dev/null' 2>&1
            if [ "$(list --count "${sha1}")" -eq 1 ]; then
              commit='4b825dc642cb6eb9a060e54bf8d69288fbee4904'
            else
              commit='HEAD~1'
            fi
            diff="$(clang_format --diff)"
            # ...
          done
          # ...
    

    As you can see, instead of repeatedly doing git reset --soft 'HEAD~1', I now explicitly instruct git-clang-format to operate against HEAD~1 with the --commit option (whereas its default is HEAD that was implied in the initial version presented in my question). However, that still does not solve the problem on its own because when we would hit root commit this would again result in error as HEAD~1 would not refer to a valid revision anymore (similarly to how it would not be possible to do git reset --soft 'HEAD~1'). That's why for this particular case, I instruct git-clang-format to operate against the (magic) 4b825dc642cb6eb9a060e54bf8d69288fbee4904 hash [3, 4, 5, 6]. To learn more about this hash, consult the references, but, in brief, it refers to the Git empty tree object — the one that has nothing staged or committed, which is exactly what we need git-clang-format to operate against in our case.

    NOTE:
    You don't have to remember 4b825dc642cb6eb9a060e54bf8d69288fbee4904 by heart and it's better not to hard code it (just in case this magic hash ever changes in future). It turns out that it can always be retrieved with git hash-object -t tree '/dev/null' [5, 6]. Thus, in my final version of the above pre-receive hook, I have commit="$(git hash-object -t tree '/dev/null')" instead.

    P.S. I'm still looking for a good quality answer on my first question. By the way, I asked these questions on the official Git mailing list and received no answers so far, what a shame...

    0 讨论(0)
提交回复
热议问题