{"id":25,"date":"2022-07-18T11:18:49","date_gmt":"2022-07-18T16:18:49","guid":{"rendered":"https:\/\/jcshort.net\/?p=25"},"modified":"2022-07-18T12:43:07","modified_gmt":"2022-07-18T17:43:07","slug":"git-hooks-for-mortals","status":"publish","type":"post","link":"https:\/\/jcshort.net\/?p=25","title":{"rendered":"Git Hooks for Mortals"},"content":{"rendered":"\n<p>Continuing my series of opinionated local development environment implementations, today I will document my git hooks configuration.<\/p>\n\n\n\n<p>The two main contenders are <a rel=\"noreferrer noopener\" href=\"https:\/\/pre-commit.com\" data-type=\"URL\" data-id=\"https:\/\/pre-commit.com\" target=\"_blank\">pre-commit<\/a> and <a rel=\"noreferrer noopener\" href=\"https:\/\/git-hooks.github.io\/git-hooks\/\" data-type=\"URL\" data-id=\"https:\/\/git-hooks.github.io\/git-hooks\/\" target=\"_blank\">git-hooks<\/a>.  I&#8217;ve settled on git-hooks, mostly because I prefer to assemble my own wrappers.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Installation<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">brew install git-hooks-go<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Configuration<\/h2>\n\n\n\n<p>Hook scripts should be installed into each workspace that requires hooks:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">git-hooks install<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p>Templates can also be set for future <code>clone<\/code> or <code>init<\/code> use:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">git config --global init.templatedir '&lt;path>'<\/code><\/pre>\n\n\n\n<p>Where <code>&lt;path><\/code> is a copy of the git-hooks scripts.  <code>~\/.git-templates<\/code> is a popular location.  Existing repositories (without hooks) can have these template hooks applied via <code>git init<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Hooks<\/h2>\n\n\n\n<p>Hooks can exist at several scopes: <\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Local hooks are executed from <code>$REPO\/githooks\/&lt;hook&gt;<\/code><\/li><li>Global hooks are executed from <code>~\/.githooks\/&lt;hook&gt;<\/code><\/li><\/ul>\n\n\n\n<p>where <code>&lt;hook&gt;<\/code> is a git action, such as <code>pre-commit<\/code><\/p>\n\n\n\n<p>Entrypoints in these paths must be executable.  Here&#8217;s an example git diff-index whitespace check from my global pre-commit hooks:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">$ cat ~\/.githooks\/pre-commit\/whitespace.sh\n#!\/bin\/sh\n\nif git rev-parse --verify HEAD &gt;\/dev\/null 2&gt;&amp;1\nthen\n  against=HEAD\nelse\n  # Initial commit: diff against an empty tree object\n  against=4b825dc642cb6eb9a060e54bf8d69288fbee4904\nfi\n\ngit diff-index --check --cached $against --<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p>Here&#8217;s a similarly convoluted example for wrapping tools that are not git-aware:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">$ cat githooks\/pre-commit\/shellcheck.sh\n#!\/usr\/bin\/env bash\n\nset -efu -o pipefail\n\nif git rev-parse --verify HEAD &amp;&gt; \/dev\/null\nthen\n  against=HEAD\nelse\n  against=4b825dc642cb6eb9a060e54bf8d69288fbee4904\nfi\n\n( git diff-index --name-only $against | grep '\\.sh$' || true ) | while read -r file ; do\n  if [[ -f \"${file}\" ]] ; then\n    shellcheck \"${file}\"\n  fi\ndone<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p>Or something simpler, like <code>golangci-lint<\/code> which is significantly more context-aware out of the box than <code>shellcheck<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">$ cat githooks\/pre-commit\/go\n#!\/bin\/sh\ngolangci-lint run<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p>In both of the above cases, the paths referenced are symlinks to a common hook storage path, rather than copies.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Continuing my series of opinionated local development environment implementations, today I will document my git hooks configuration. The two main contenders are pre-commit and git-hooks. I&#8217;ve settled on git-hooks, mostly because I prefer to assemble my own wrappers. Installation Configuration Hook scripts should be installed into each workspace that requires hooks: Templates can also be [&hellip;]<\/p>\n","protected":false},"author":3,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_genesis_hide_title":false,"_genesis_hide_breadcrumbs":false,"_genesis_hide_singular_image":false,"_genesis_hide_footer_widgets":false,"_genesis_custom_body_class":"","_genesis_custom_post_class":"","_genesis_layout":"","footnotes":""},"categories":[1],"tags":[],"class_list":{"0":"post-25","1":"post","2":"type-post","3":"status-publish","4":"format-standard","6":"category-tech","7":"entry"},"featured_image_src":null,"featured_image_src_square":null,"author_info":{"display_name":"Jason Short","author_link":"https:\/\/jcshort.net\/?author=3"},"_links":{"self":[{"href":"https:\/\/jcshort.net\/index.php?rest_route=\/wp\/v2\/posts\/25","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/jcshort.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/jcshort.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/jcshort.net\/index.php?rest_route=\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/jcshort.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=25"}],"version-history":[{"count":0,"href":"https:\/\/jcshort.net\/index.php?rest_route=\/wp\/v2\/posts\/25\/revisions"}],"wp:attachment":[{"href":"https:\/\/jcshort.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=25"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/jcshort.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=25"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/jcshort.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=25"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}