<?xml version="1.0" encoding="utf-8" standalone="yes"?><?xml-stylesheet type="text/xsl" href="https://perrotta.dev/rss.xsl"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Pre-Commit on ¬ just serendipity 🍀</title>
    <link>https://perrotta.dev/</link>
    <description>Recent content in Pre-Commit on ¬ just serendipity 🍀</description>
    <generator>Hugo</generator>
    <language>en-us</language>
    <managingEditor>serendipity@perrotta.dev (Thiago Perrotta)</managingEditor>
    <webMaster>serendipity@perrotta.dev (Thiago Perrotta)</webMaster>
    <copyright>© 2013 - 2026 Thiago Perrotta ·
  a fork of [hugo ʕ•ᴥ•ʔ bear](https://github.com/janraasch/hugo-bearblog/)
</copyright>
    <lastBuildDate>Thu, 18 Jun 2026 19:53:29 +0200</lastBuildDate>
    <atom:link href="https://perrotta.dev/tags/pre-commit/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>pre-commit: authentication in internal github repos
      </title>
      <link>https://perrotta.dev/2026/06/pre-commit-authentication-in-internal-github-repos/</link>
      <pubDate>Thu, 18 Jun 2026 14:42:26 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>git</category>
      <category>pre-commit</category>
      <guid>https://perrotta.dev/2026/06/pre-commit-authentication-in-internal-github-repos/</guid>
      <description>&lt;p&gt;♠ &lt;strong&gt;Problem statement&lt;/strong&gt;: prek can&amp;rsquo;t clone a &lt;code&gt;repo:&lt;/code&gt; that lives in an internal&#xA;(private) GitHub repo — &lt;code&gt;https://&lt;/code&gt; clone fails without credentials.&lt;/p&gt;&#xA;&lt;p&gt;I forked my personal&#xA;&lt;a href=&#34;https://github.com/thiagowfx/pre-commit-hooks&#34;&gt;pre-commit-hooks&lt;/a&gt; into an&#xA;org-owned repo and updated all our &lt;code&gt;.pre-commit-config.yaml&lt;/code&gt; files to point at&#xA;it:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;- repo: https://github.com/&amp;lt;org&amp;gt;/pre-commit-hooks&#xA;  rev: 5695e23285bef67d1dee957e5c12e86f58ba843b # frozen: v1.0.0&#xA;  hooks:&#xA;    - id: check-bash-shebang&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;This approach works locally (my GitHub token is broad).&lt;/p&gt;&#xA;&lt;p&gt;CI (github actions) fails though:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;fatal: could not read Username for &amp;#39;https://github.com&amp;#39;: terminal prompts disabled&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The repo is &lt;code&gt;INTERNAL&lt;/code&gt;, not public. As such, &lt;code&gt;prek&lt;/code&gt; clones the &lt;code&gt;repo:&lt;/code&gt; URL to&#xA;fetch the hooks; with no credentials on the runner, it can&amp;rsquo;t.&lt;/p&gt;&#xA;&lt;p&gt;First instinct: &lt;code&gt;git@github.com:&amp;lt;org&amp;gt;/pre-commit-hooks&lt;/code&gt;. Runners have no SSH&#xA;key though!&lt;/p&gt;&#xA;&lt;p&gt;The fix is to keep &lt;code&gt;https://&lt;/code&gt; in the config, mint a short-lived token that can read&#xA;the hooks repo, and tell git to rewrite the URL:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;- name: Generate app token&#xA;  uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0&#xA;  id: generate-token&#xA;  with:&#xA;    client-id: ${{ vars.APP_CLIENT_ID }}&#xA;    private-key: ${{ secrets.APP_PRIVATE_KEY }}&#xA;    owner: ${{ github.repository_owner }}&#xA;    repositories: pre-commit-hooks&#xA;    permission-contents: read&#xA;- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3&#xA;  with:&#xA;    persist-credentials: false&#xA;- name: Authenticate git for internal hook repos&#xA;  env:&#xA;    TOKEN: ${{ steps.generate-token.outputs.token }}&#xA;  run: git config --global url.&amp;#34;https://x-access-token:${TOKEN}@github.com/&amp;#34;.insteadOf &amp;#34;https://github.com/&amp;#34;&#xA;- uses: j178/prek-action@bdca6f102f98e2b4c7029491a53dfd366469e33d # v2.0.4&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Two things worth noting.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;First&lt;/strong&gt;: never inline &lt;code&gt;${{ steps.generate-token.outputs.token }}&lt;/code&gt; directly into a &lt;code&gt;run:&lt;/code&gt; shell string — &lt;a href=&#34;https://perrotta.dev/2025/10/zizmor/&#34;&gt;zizmor&lt;/a&gt; (a GitHub Actions security linter) flags it as a template injection risk. Pass it via &lt;code&gt;env:&lt;/code&gt; and use the shell variable instead.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Second&lt;/strong&gt;: the &lt;code&gt;repositories: pre-commit-hooks&lt;/code&gt; scope only works if the App&#xA;is actually &lt;em&gt;installed&lt;/em&gt; on that repo. That one is a manual step in GitHub&amp;rsquo;s UI&#xA;— &lt;code&gt;github.com/organizations/&amp;lt;org&amp;gt;/settings/installations&lt;/code&gt; → the App →&#xA;Repository access → add the repo. No amount of YAML fixes it otherwise; it&#xA;silently returns a 404 from the App installation endpoint otherwise.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;After the installation grant, the clone goes through and &lt;code&gt;prek&lt;/code&gt; runs cleanly.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;🤖 &lt;em&gt;Drafted with &lt;code&gt;/bloggify&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: pre-commit: authentication in internal github repos&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/git/&#34;&gt;#git&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/pre-commit/&#34;&gt;#pre-commit&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>★ Worktrunk
      </title>
      <link>https://perrotta.dev/2026/05/worktrunk/</link>
      <pubDate>Mon, 25 May 2026 10:57:00 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>ai</category>
      <category>bestof</category>
      <category>dev</category>
      <category>git</category>
      <category>pkm</category>
      <category>pre-commit</category>
      <guid>https://perrotta.dev/2026/05/worktrunk/</guid>
      <description>&lt;p&gt;♠ &lt;a href=&#34;https://worktrunk.dev/&#34;&gt;Worktrunk&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Git worktree management for parallel AI agent workflows&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;I stumbled upon &lt;code&gt;worktrunk&lt;/code&gt; (&lt;code&gt;wt&lt;/code&gt;) due to a fortunate coincidence, serendipity&#xA;at its best. I was pitching my own &lt;a href=&#34;https://github.com/thiagowfx/pancake/tree/master/wt&#34;&gt;pancake&#xA;&lt;code&gt;wt&lt;/code&gt;&lt;/a&gt; to someone, and this&#xA;person asked if I hadn&amp;rsquo;t heard of &lt;code&gt;worktrunk&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;If you look at its &lt;a href=&#34;https://www.star-history.com/?type=date&amp;amp;repos=max-sixty%2Fworktrunk&#34;&gt;git star&#xA;history&lt;/a&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2026/05/worktrunk/#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;,&#xA;you&amp;rsquo;ll notice that it became popular &lt;em&gt;only&lt;/em&gt; at the beginning of 2026, whereas I&#xA;wrote my &lt;code&gt;wt&lt;/code&gt; back in &lt;a href=&#34;https://github.com/thiagowfx/pancake/commit/a4de064cdcd0b0c0f19177e58cda41c14f55c88a&#34;&gt;November&#xA;2025&lt;/a&gt;.&#xA;I did proper diligence at the time but couldn&amp;rsquo;t find anything resembling my&#xA;vision. Normally I prefer to reuse rather than writing from scratch.&lt;/p&gt;&#xA;&lt;p&gt;&amp;ldquo;AI agent workflows&amp;rdquo; is a tagline that does not convince me. If anything, it&#xA;actually worsens my appeal to try out the software. Seriously. Riding the wave&#xA;is not helpful.&lt;/p&gt;&#xA;&lt;p&gt;What really sold me was its &lt;a href=&#34;https://worktrunk.dev/#context-git-worktrees&#34;&gt;usage&#xA;examples&lt;/a&gt;. I was able to&#xA;immediately tell that it had very similar goals to my own &lt;code&gt;wt&lt;/code&gt; (sans &lt;em&gt;AI&lt;/em&gt;).&lt;/p&gt;&#xA;&lt;h2 id=&#34;setup&#34;&gt;&#xA;  Setup&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2026/05/worktrunk/#setup&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;First, &lt;a href=&#34;https://repology.org/project/worktrunk/versions&#34;&gt;install&lt;/a&gt; &lt;code&gt;worktrunk&lt;/code&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;brew install worktrunk&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;It is as simple (and rusty!) as it could be:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% brew ls worktrunk&#xA;/opt/homebrew/Cellar/worktrunk/0.53.0/.crates.toml&#xA;/opt/homebrew/Cellar/worktrunk/0.53.0/.crates2.json&#xA;/opt/homebrew/Cellar/worktrunk/0.53.0/bin/wt&#xA;/opt/homebrew/Cellar/worktrunk/0.53.0/etc/bash_completion.d/wt&#xA;/opt/homebrew/Cellar/worktrunk/0.53.0/sbom.spdx.json&#xA;/opt/homebrew/Cellar/worktrunk/0.53.0/share/fish/vendor_completions.d/wt.fish&#xA;/opt/homebrew/Cellar/worktrunk/0.53.0/share/zsh/site-functions/_wt&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Shell completion is a bonus that you&amp;rsquo;ll definitely want, as noted in a&#xA;&lt;a href=&#34;https://perrotta.dev/2026/05/wt-exec-shell-vs-shell-integration/&#34;&gt;previous&lt;/a&gt; post:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;# worktrunk: https://worktrunk.dev/&#xA;(( $&amp;#43;commands[wt] )) &amp;amp;&amp;amp; eval &amp;#34;$(wt config shell init zsh)&amp;#34;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The snippet above integrates well with my &lt;a href=&#34;https://github.com/thiagowfx/.dotfiles&#34;&gt;own&#xA;dotfiles&lt;/a&gt;; a vanilla setup that most&#xA;people should follow is, instead, &lt;code&gt;wt config shell install&lt;/code&gt;, which will populate&#xA;your &lt;code&gt;.bashrc&lt;/code&gt; / &lt;code&gt;.zshrc&lt;/code&gt; accordingly with &lt;code&gt;wt config shell init&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;The shell integration alone makes it compelling enough to switch to &lt;code&gt;wt&lt;/code&gt;, but&#xA;we&amp;rsquo;re not done yet.&lt;/p&gt;&#xA;&lt;h2 id=&#34;workflow&#34;&gt;&#xA;  Workflow&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2026/05/worktrunk/#workflow&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;It is possible to &lt;a href=&#34;https://worktrunk.dev/config/#user-configuration&#34;&gt;customize&#xA;&lt;code&gt;wt&lt;/code&gt;&lt;/a&gt;. I value muscle memory&#xA;and the ability to easily recall commands, therefore I crafted &lt;a href=&#34;https://github.com/thiagowfx/.dotfiles/blob/5c665572443c3fd22a35c15ad2f15abf90edd711/wt/.config/worktrunk/config.toml&#34;&gt;this basic&#xA;configuration&lt;/a&gt;&#xA;to facilitate the migration from my own &lt;code&gt;wt&lt;/code&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-toml&#34;&gt;worktree-path = &amp;#34;{{ repo_path }}/.worktrees/{{ branch | sanitize }}&amp;#34;&#xA;&#xA;[aliases]&#xA;add = &amp;#34;wt switch --create {{ args }}&amp;#34;&#xA;bd = &amp;#34;wt remove -D {{ args }}&amp;#34;&#xA;cd = &amp;#34;wt switch {{ args }}&amp;#34;&#xA;checkout = &amp;#34;wt switch {{ args }}&amp;#34;&#xA;cleanup = &amp;#34;wt step prune {{ args }}&amp;#34;&#xA;co = &amp;#34;wt switch {{ args }}&amp;#34;&#xA;commit = &amp;#34;wt step commit {{ args }}&amp;#34;&#xA;create = &amp;#34;wt switch --create {{ args }}&amp;#34;&#xA;del = &amp;#34;wt remove {{ args }}&amp;#34;&#xA;delete = &amp;#34;wt remove {{ args }}&amp;#34;&#xA;goto = &amp;#34;wt switch --no-cd {{ args }}&amp;#34;&#xA;ls = &amp;#34;wt list {{ args }}&amp;#34;&#xA;new = &amp;#34;wt switch --create {{ args }}&amp;#34;&#xA;prune = &amp;#34;wt step prune {{ args }}&amp;#34;&#xA;rm = &amp;#34;wt remove {{ args }}&amp;#34;&#xA;tui = &amp;#34;wt switch {{ args }}&amp;#34;&#xA;world = &amp;#34;wt step prune {{ args }}&amp;#34;&#xA;xl = &amp;#34;wt list {{ args }}&amp;#34;&#xA;&#xA;[pre-start]&#xA;prek = &amp;#34;test -f .pre-commit-config.yaml &amp;amp;&amp;amp; prek install --install-hooks || true&amp;#34;&#xA;&#xA;[pre-merge]&#xA;prek = &amp;#34;test -f .pre-commit-config.yaml &amp;amp;&amp;amp; prek run --from-ref \&amp;#34;$(git symbolic-ref --short refs/remotes/origin/HEAD)\&amp;#34; --to-ref HEAD || true&amp;#34;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;You can start by running &lt;code&gt;wt config create&lt;/code&gt; and then iterating upon the newly&#xA;generated config file, which is very well documented. What a great developer&#xA;experience!&lt;/p&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s break it down.&lt;/p&gt;&#xA;&lt;p&gt;We start with &lt;code&gt;wt add &amp;lt;feature&amp;gt;&lt;/code&gt; (upstream: &lt;code&gt;wt switch --create&lt;/code&gt;). This will&#xA;create a worktree:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;tperrotta ~/Workspace/perrotta.dev git:master!?&#xA;% wt add myfeature&#xA;◎ Running pre-start user:prek&#xA;  test -f .pre-commit-config.yaml &amp;amp;&amp;amp; prek install --install-hooks || true&#xA;prek installed at `/Users/tperrotta/Workspace/perrotta.dev/.git/hooks/pre-commit`&#xA;✓ Created branch myfeature from master and worktree @ ~/Workspace/perrotta.dev/.worktrees/myfeature&#xA;  39.06s user 6.26s system 251% cpu 17.984 total&#xA;&#xA;tperrotta ~/Workspace/perrotta.dev/.worktrees/myfeature git:myfeature ⎇ myfeature&#xA;% git st&#xA;## myfeature...master&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;At any time, we can check the current status with &lt;code&gt;wt xl&lt;/code&gt; (upstream: &lt;code&gt;wt list&lt;/code&gt;):&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;tperrotta ~/Workspace/perrotta.dev/.worktrees/myfeature git:myfeature ⎇ myfeature&#xA;% wt xl&#xA;  Branch     Status        HEAD±    main↕  Remote⇅  Path                    Commit    Age   Message&#xA;@ myfeature      _                                  ./.worktrees/myfeature  23df7c41  50m   name author explicitly&#xA;^ master      !? ^|      &amp;#43;5   -6              |     .                       23df7c41  50m   name author explicitly&#xA;&#xA;○ Showing 2 worktrees, 1 with changes&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;When opening a new shell, we can easily switch to the newly created worktree&#xA;from the repo root with &lt;code&gt;wt cd&lt;/code&gt; (upstream: &lt;code&gt;wt switch&lt;/code&gt;):&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;tperrotta ~/Workspace/perrotta.dev git:master!?&#xA;% wt cd myfeature&#xA;○ Switched to worktree for myfeature @ ~/Workspace/perrotta.dev/.worktrees/myfeature&#xA;&#xA;tperrotta ~/Workspace/perrotta.dev/.worktrees/myfeature git:myfeature ⎇ myfeature&#xA;%&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Once we are done (e.g. created/merged a pull request), we can delete the&#xA;worktree with &lt;code&gt;wt bd&lt;/code&gt; or &lt;code&gt;wt del&lt;/code&gt; (upstream: &lt;code&gt;wt remove -D&lt;/code&gt;).&lt;/p&gt;&#xA;&lt;p&gt;That&amp;rsquo;s all! The core workflow steps are quite easy to assimilate.&lt;/p&gt;&#xA;&lt;p&gt;You&amp;rsquo;ll notice that my worktrees are created in a &lt;code&gt;.worktrees&lt;/code&gt; directory within&#xA;the git repository root. This is due to my setting &lt;code&gt;worktree-path&lt;/code&gt; in the config&#xA;file. The default behavior of &lt;code&gt;worktrunk&lt;/code&gt; is to use sibling directories (e.g.&#xA;&lt;code&gt;../myrepo.myfeature&lt;/code&gt; from the git root).&lt;/p&gt;&#xA;&lt;p&gt;As you can see from my config file, I have also integrated &lt;a href=&#34;https://perrotta.dev/2026/04/migrating-from-pre-commit-to-prek/&#34;&gt;&lt;code&gt;prek&lt;/code&gt;&lt;/a&gt; (&lt;code&gt;pre-commit&lt;/code&gt;) to it:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;the creation of a worktree yields &lt;code&gt;prek install&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;the merge of a worktree (not covered in this post) yields &lt;code&gt;prek run&lt;/code&gt; on the&#xA;affected commits&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&lt;code&gt;wt&lt;/code&gt; has many other features; this is just the tip of the iceberg.&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;GitHub stars are a vanity metric but they are occasionally useful.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2026/05/worktrunk/#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: Worktrunk&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/ai/&#34;&gt;#ai&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/bestof/&#34;&gt;#bestof&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/git/&#34;&gt;#git&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/pkm/&#34;&gt;#pkm&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/pre-commit/&#34;&gt;#pre-commit&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>migrating from pre-commit to prek
      </title>
      <link>https://perrotta.dev/2026/04/migrating-from-pre-commit-to-prek/</link>
      <pubDate>Mon, 20 Apr 2026 10:00:00 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>pre-commit</category>
      <guid>https://perrotta.dev/2026/04/migrating-from-pre-commit-to-prek/</guid>
      <description>&lt;p&gt;♠ &lt;a href=&#34;https://perrotta.dev/2024/12/pre-commit/&#34;&gt;Previously&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Today I learned&lt;/strong&gt;: &lt;a href=&#34;https://prek.j178.dev/&#34;&gt;prek&lt;/a&gt; is a drop-in replacement&#xA;for &lt;code&gt;pre-commit&lt;/code&gt;, rewritten in Rust. Same &lt;code&gt;.pre-commit-config.yaml&lt;/code&gt;, same hook&#xA;ecosystem, much faster.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;A single binary with zero dependencies, much faster than the original&#xA;pre-commit, drop-in compatible.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Install:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% brew install prek&#xA;# or&#xA;% cargo install prek&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Then swap the command. Nothing else changes:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-diff&#34;&gt;-pre-commit run --all-files&#xA;&amp;#43;prek run --all-files&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Or skip the swap entirely: &lt;code&gt;prek install -f&lt;/code&gt; drops in a shim that replaces the&#xA;&lt;code&gt;pre-commit&lt;/code&gt; binary, so existing scripts and git hooks keep working unchanged.&lt;/p&gt;&#xA;&lt;p&gt;The CI workflow on this blog (&lt;a href=&#34;https://github.com/thiagowfx/thiagowfx.github.io/blob/master/.github/workflows/prek.yml&#34;&gt;&lt;code&gt;prek.yml&lt;/code&gt;&lt;/a&gt;)&#xA;now uses &lt;a href=&#34;https://github.com/j178/prek-action&#34;&gt;&lt;code&gt;j178/prek-action&lt;/code&gt;&lt;/a&gt; instead of&#xA;&lt;code&gt;pre-commit/action&lt;/code&gt;, and the monthly autoupdate job&#xA;(&lt;a href=&#34;https://github.com/thiagowfx/thiagowfx.github.io/blob/master/.github/workflows/prek-autoupdate.yml&#34;&gt;&lt;code&gt;prek-autoupdate.yml&lt;/code&gt;&lt;/a&gt;)&#xA;runs &lt;code&gt;prek auto-update --freeze&lt;/code&gt; — the &lt;a href=&#34;https://perrotta.dev/2025/03/pre-commit-pin-dependencies-with--freeze/&#34;&gt;&lt;code&gt;--freeze&lt;/code&gt;&lt;/a&gt;&#xA;flag works the same.&lt;/p&gt;&#xA;&lt;p&gt;The &lt;a href=&#34;https://prek.j178.dev/benchmark/&#34;&gt;benchmark&lt;/a&gt; is what sold me. On cold&#xA;caches, &lt;code&gt;prek&lt;/code&gt; is around 10x faster at resolving and installing hooks; on warm&#xA;runs, the difference is smaller but still noticeable.&lt;/p&gt;&#xA;&lt;p&gt;Two other niceties &lt;code&gt;pre-commit&lt;/code&gt; does not have:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Split configs across multiple files&lt;/strong&gt;. &lt;code&gt;prek&lt;/code&gt; supports&#xA;&lt;a href=&#34;https://prek.j178.dev/workspace/&#34;&gt;multi-file configs&lt;/a&gt;, so&#xA;hooks can live next to the subproject they belong to in a monorepo instead&#xA;of piling up in a single top-level YAML.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Run against a commit or a range&lt;/strong&gt;. &lt;code&gt;prek run --from-ref A --to-ref B&lt;/code&gt;&#xA;restricts the run to files changed between two refs — much faster than&#xA;&lt;code&gt;prek run -a&lt;/code&gt; because only the touched files are inspected. Example: lint&#xA;everything on the current branch since it diverged from &lt;code&gt;master&lt;/code&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% prek run --from-ref &amp;#34;$(git merge-base master HEAD)&amp;#34; --to-ref HEAD&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;The &lt;code&gt;.pre-commit-config.yaml&lt;/code&gt; file stays put, and &lt;code&gt;pre-commit.ci&lt;/code&gt; still reads&#xA;it. No lock-in.&lt;/p&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: migrating from pre-commit to prek&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/pre-commit/&#34;&gt;#pre-commit&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>pre-commit: periodic garbage collection
      </title>
      <link>https://perrotta.dev/2026/04/pre-commit-periodic-garbage-collection/</link>
      <pubDate>Tue, 07 Apr 2026 10:56:21 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>git</category>
      <category>pre-commit</category>
      <guid>https://perrotta.dev/2026/04/pre-commit-periodic-garbage-collection/</guid>
      <description>&lt;p&gt;♠ How funny, I do not remember setting this up. It is useful nonetheless:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;thiago.perrotta ~&#xA;❯ crontab -l&#xA;30 12 1-7 * 5 [ &amp;#34;$(( ($(date &amp;#43;\%-d)-1)/7&amp;#43;1 ))&amp;#34; -eq 1 ] &amp;amp;&amp;amp; /opt/homebrew/bin/pre-commit gc &amp;gt;&amp;gt; /Users/thiago.perrotta/.cronlogs/pre-commit-gc.log 2&amp;gt;&amp;amp;1&#xA;&#xA;thiago.perrotta ~&#xA;❯ cat /Users/thiago.perrotta/.cronlogs/pre-commit-gc.log&#xA;2 repo(s) removed.&#xA;0 repo(s) removed.&#xA;3 repo(s) removed.&#xA;20 repo(s) removed.&#xA;10 repo(s) removed.&#xA;18 repo(s) removed.&#xA;2 repo(s) removed.&#xA;0 repo(s) removed.&#xA;0 repo(s) removed.&#xA;4 repo(s) removed.&#xA;61 repo(s) removed.&#xA;5 repo(s) removed.&#xA;0 repo(s) removed.&#xA;0 repo(s) removed.&#xA;0 repo(s) removed.&#xA;1 repo(s) removed.&#xA;26 repo(s) removed.&#xA;17 repo(s) removed.&#xA;9 repo(s) removed.&#xA;11 repo(s) removed.&#xA;1 repo(s) removed.&#xA;3 repo(s) removed.&#xA;10 repo(s) removed.&#xA;1 repo(s) removed.&#xA;6 repo(s) removed.&#xA;1 repo(s) removed.&#xA;6 repo(s) removed.&#xA;4 repo(s) removed.&#xA;0 repo(s) removed.&#xA;15 repo(s) removed.&#xA;0 repo(s) removed.&#xA;2 repo(s) removed.&#xA;4 repo(s) removed.&#xA;1 repo(s) removed.&#xA;16 repo(s) removed.&#xA;0 repo(s) removed.&#xA;0 repo(s) removed.&#xA;0 repo(s) removed.&#xA;0 repo(s) removed.&#xA;0 repo(s) removed.&#xA;0 repo(s) removed.&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;What does &lt;code&gt;gc&lt;/code&gt; do? As per the &lt;a href=&#34;https://pre-commit.com/#pre-commit-gc&#34;&gt;docs&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Clean unused cached repos.&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;pre-commit&lt;/code&gt; keeps a cache of installed hook repositories which grows over&#xA;time. This command can be run periodically to clean out unused repos from the&#xA;cache directory.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;This is on macOS. If I were on Linux, I&amp;rsquo;d prefer &lt;a href=&#34;https://wiki.archlinux.org/title/Systemd/Timers&#34;&gt;systemd&#xA;timers&lt;/a&gt; instead.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/2024/12/pre-commit/&#34;&gt;Previously&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: pre-commit: periodic garbage collection&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/git/&#34;&gt;#git&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/pre-commit/&#34;&gt;#pre-commit&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>github workflow: external script
      </title>
      <link>https://perrotta.dev/2026/02/github-workflow-external-script/</link>
      <pubDate>Sun, 22 Feb 2026 14:10:11 +0100</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>git</category>
      <category>pre-commit</category>
      <guid>https://perrotta.dev/2026/02/github-workflow-external-script/</guid>
      <description>&lt;p&gt;♠ &lt;strong&gt;Problem statement&lt;/strong&gt;: execute a &lt;code&gt;.js&lt;/code&gt; script in a GitHub workflow. The script&#xA;must be stored in a separate file (than the workflow) in the same git&#xA;repository.&lt;/p&gt;&#xA;&lt;p&gt;Why store the file separately?&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Inlining a big script is ugly and very non-elegant&lt;/li&gt;&#xA;&lt;li&gt;No syntax highlighting when updating the script → error-prone&lt;/li&gt;&#xA;&lt;li&gt;Risk of messing up with string interpolation or escaping characters →&#xA;error-prone&lt;/li&gt;&#xA;&lt;li&gt;Limited ability to use formatters and linters in inlined scripts&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;The following approach, taken from a real example, works well:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% tree {repo root}/.github/&#xA;.github/&#xA;├── scripts&#xA;│   └── drift-summary.js&#xA;└── workflows&#xA;    ├── drift-summary.yml&#xA;    [...]&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;name: &amp;#39;Drift Summary&amp;#39;&#xA;&#xA;on:&#xA;  schedule:&#xA;    - cron: &amp;#39;0 9 * * MON-FRI&amp;#39;&#xA;  workflow_dispatch:&#xA;&#xA;permissions:&#xA;  issues: write&#xA;&#xA;jobs:&#xA;  summarize:&#xA;    name: &amp;#39;Update drift summary issue&amp;#39;&#xA;    runs-on: ubuntu-latest&#xA;    steps:&#xA;      - name: &amp;#39;Checkout repository&amp;#39;&#xA;        uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0&#xA;        with:&#xA;          persist-credentials: false&#xA;&#xA;      - name: &amp;#39;Aggregate drift and update summary&amp;#39;&#xA;        uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7&#xA;        with:&#xA;          script: |&#xA;            const script = require(&amp;#39;./.github/scripts/drift-summary.js&amp;#39;);&#xA;            await script({ github, context });&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Use &lt;a href=&#34;https://github.com/actions/github-script&#34;&gt;&lt;code&gt;github-script&lt;/code&gt;&lt;/a&gt; + &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules&#34;&gt;&lt;code&gt;require&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: github workflow: external script&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/git/&#34;&gt;#git&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/pre-commit/&#34;&gt;#pre-commit&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>pre-commit: hazmat n1
      </title>
      <link>https://perrotta.dev/2026/02/pre-commit-hazmat-n1/</link>
      <pubDate>Fri, 06 Feb 2026 18:28:16 +0100</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>pre-commit</category>
      <guid>https://perrotta.dev/2026/02/pre-commit-hazmat-n1/</guid>
      <description>&lt;p&gt;♠ &lt;strong&gt;Problem statement&lt;/strong&gt;: some CLI tools accept only a single file as an argument.&#xA;Create a &lt;a href=&#34;https://pre-commit.com/&#34;&gt;pre-commit&lt;/a&gt; local hook for them.&lt;/p&gt;&#xA;&lt;p&gt;A common workaround is to wrap it in a bash for-loop (&lt;code&gt;.pre-commit-config.yaml&lt;/code&gt;):&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;repos:&#xA;  - repo: local&#xA;    hooks:&#xA;      - id: my-formatter&#xA;        name: my-formatter&#xA;        language: system&#xA;        entry: bash -c &amp;#39;for file in &amp;#34;$@&amp;#34;; do my-formatter fmt &amp;#34;$file&amp;#34;; done&amp;#39; --&#xA;        files: ^path/to/files/&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;This works, but it&amp;rsquo;s noisy and easy to get wrong. Plus it&amp;rsquo;s not elegant at all.&lt;/p&gt;&#xA;&lt;p&gt;It turns out pre-commit has a built-in mechanism for this: &lt;a href=&#34;https://pre-commit.com/#codepre-commit-hazmat-n1code&#34;&gt;&lt;code&gt;hazmat n1&lt;/code&gt;&lt;/a&gt;. It runs the hook&#xA;once per file, one at a time:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;some hooks only take one filename argument. this runs them one at a time&#xA;(which is super slow!)&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;repos:&#xA;  - repo: local&#xA;    hooks:&#xA;      - id: my-formatter&#xA;        name: my-formatter&#xA;        language: system&#xA;        entry: pre-commit hazmat n1 my-formatter fmt --&#xA;        files: ^path/to/files/&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Note the trailing &lt;code&gt;--&lt;/code&gt; at the end of &lt;code&gt;entry&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;This is a super recent feature, added in &lt;a href=&#34;https://github.com/pre-commit/pre-commit/commit/1af6c8fa9502336c6977c2ff3e79185bd97a6e57&#34;&gt;pre-commit 4.5.0, in Nov&#xA;2025&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;The name &amp;ldquo;hazmat&amp;rdquo; is supposedly intended to discourage people from using it.&#xA;Yet, here we are.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;&amp;ldquo;hazardous materials&amp;rdquo;&lt;/p&gt;&#xA;&lt;p&gt;pre-commit provides a few entry prefix &amp;ldquo;helpers&amp;rdquo; for unusual situations.&lt;/p&gt;&#xA;&lt;p&gt;in case it&amp;rsquo;s not clear, using these is usually a bad idea.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;It is a bad idea, but sometimes the underlying hook / script simply does not&#xA;support multiple arguments.&lt;/p&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: pre-commit: hazmat n1&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/pre-commit/&#34;&gt;#pre-commit&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>npm error code EBADF
      </title>
      <link>https://perrotta.dev/2026/02/npm-error-code-ebadf/</link>
      <pubDate>Thu, 05 Feb 2026 13:21:00 +0100</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>macos</category>
      <category>pre-commit</category>
      <category>security</category>
      <guid>https://perrotta.dev/2026/02/npm-error-code-ebadf/</guid>
      <description>&lt;p&gt;♠ Symptom:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% pre-commit run -a&#xA;[INFO] Installing environment for https://github.com/rbubley/mirrors-prettier.&#xA;[INFO] Once installed this environment will be reused.&#xA;[INFO] This may take a few minutes...&#xA;An unexpected error has occurred: CalledProcessError: command: (&amp;#39;/Users/thiago.perrotta/.cache/pre-commit/repo78y8p1fk/node_env-system/bin/node&amp;#39;, &amp;#39;/opt/homebrew/bin/npm&amp;#39;, &amp;#39;install&amp;#39;, &amp;#39;-g&amp;#39;, &amp;#39;/Users/thiago.perrotta/.cache/pre-commit/repo78y8p1fk/placeholder_package-0.0.0.tgz&amp;#39;, &amp;#39;prettier@3.8.1&amp;#39;)&#xA;return code: 1&#xA;stdout: (none)&#xA;stderr:&#xA;    npm error code EBADF&#xA;    npm error errno EBADF&#xA;    npm error request to https://registry.npmjs.org/prettier failed, reason:&#xA;    npm error A complete log of this run can be found in: /Users/thiago.perrotta/.npm/_logs/2026-02-04T12_22_05_104Z-debug-0.log&#xA;Check the log at /Users/thiago.perrotta/.cache/pre-commit/pre-commit.log&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Initially I thought it was an issue with &lt;code&gt;prettier&lt;/code&gt;. The pre-commit repository&#xA;is deprecated after all:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;This is a fork of the (now archived)&#xA;&lt;a href=&#34;https://github.com/pre-commit/mirrors-prettier&#34;&gt;https://github.com/pre-commit/mirrors-prettier&lt;/a&gt; with the only difference being&#xA;that it only references release versions of prettier, not e.g. alpha/beta&#xA;versions.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;The actual error was &lt;a href=&#34;https://www.obdev.at/products/littlesnitch/index.html&#34;&gt;Little&#xA;Snitch&lt;/a&gt; on macOS blocking&#xA;all outgoing connections to the NPM registry.&lt;/p&gt;&#xA;&lt;p&gt;How to address it?&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Click &amp;ldquo;Manage Rules&amp;hellip;&amp;rdquo;&lt;/li&gt;&#xA;&lt;li&gt;Click &amp;ldquo;Deny&amp;rdquo; to list all network blocks&lt;/li&gt;&#xA;&lt;li&gt;Delete the NPM registry entry&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: npm error code EBADF&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/macos/&#34;&gt;#macos&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/pre-commit/&#34;&gt;#pre-commit&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/security/&#34;&gt;#security&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>new pre-commit hook: check-bash-shebang
      </title>
      <link>https://perrotta.dev/2025/11/new-pre-commit-hook-check-bash-shebang/</link>
      <pubDate>Sun, 16 Nov 2025 20:48:17 -0300</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>pre-commit</category>
      <guid>https://perrotta.dev/2025/11/new-pre-commit-hook-check-bash-shebang/</guid>
      <description>&lt;p&gt;♠ &lt;a href=&#34;https://perrotta.dev/2025/11/bash-shebang-/&#34;&gt;Previously&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;I couldn&amp;rsquo;t resist creating a &lt;a href=&#34;https://pre-commit.com/&#34;&gt;pre-commit hook&lt;/a&gt; for&#xA;this:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;repos:&#xA;  - repo: https://github.com/thiagowfx/pre-commit-hooks/&#xA;    rev: v0.0.11&#xA;    hooks:&#xA;      - id: check-bash-shebang&#xA;        types:&#xA;          - shell&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;In action:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% cd {pancake}&#xA;% pre-commit run -a check-bash-shebang&#xA;Check bash scripts use portable shebang..................................Failed&#xA;- hook id: check-bash-shebang&#xA;- exit code: 1&#xA;&#xA;error: &amp;#39;aws_china_mfa/aws_china_mfa.sh&amp;#39; uses &amp;#39;#!/bin/bash&amp;#39; instead of &amp;#39;#!/usr/bin/env bash&amp;#39;&#xA;error: &amp;#39;aws_login_headless/aws_login_headless.sh&amp;#39; uses &amp;#39;#!/bin/bash&amp;#39; instead of &amp;#39;#!/usr/bin/env bash&amp;#39;&#xA;error: &amp;#39;pritunl_login/pritunl_login.sh&amp;#39; uses &amp;#39;#!/bin/bash&amp;#39; instead of &amp;#39;#!/usr/bin/env bash&amp;#39;&#xA;error: &amp;#39;sd_world/sd_world.sh&amp;#39; uses &amp;#39;#!/bin/bash&amp;#39; instead of &amp;#39;#!/usr/bin/env bash&amp;#39;&#xA;error: &amp;#39;pdf_password_remove/pdf_password_remove.sh&amp;#39; uses &amp;#39;#!/bin/bash&amp;#39; instead of &amp;#39;#!/usr/bin/env bash&amp;#39;&#xA;error: &amp;#39;img_optimize/img_optimize.sh&amp;#39; uses &amp;#39;#!/bin/bash&amp;#39; instead of &amp;#39;#!/usr/bin/env bash&amp;#39;&#xA;error: &amp;#39;copy/copy.sh&amp;#39; uses &amp;#39;#!/bin/bash&amp;#39; instead of &amp;#39;#!/usr/bin/env bash&amp;#39;&#xA;error: &amp;#39;ssh_mux_restart/ssh_mux_restart.sh&amp;#39; uses &amp;#39;#!/bin/bash&amp;#39; instead of &amp;#39;#!/usr/bin/env bash&amp;#39;&#xA;error: &amp;#39;op_login_all/op_login_all.sh&amp;#39; uses &amp;#39;#!/bin/bash&amp;#39; instead of &amp;#39;#!/usr/bin/env bash&amp;#39;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: new pre-commit hook: check-bash-shebang&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/pre-commit/&#34;&gt;#pre-commit&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>go/keep-sorted
      </title>
      <link>https://perrotta.dev/2025/09/go/keep-sorted/</link>
      <pubDate>Mon, 22 Sep 2025 11:12:59 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>pre-commit</category>
      <guid>https://perrotta.dev/2025/09/go/keep-sorted/</guid>
      <description>&lt;p&gt;♠ &lt;a href=&#34;https://perrotta.dev/2024/12/keep-sorted/&#34;&gt;Previously&lt;/a&gt;,&#xA;&lt;a href=&#34;https://perrotta.dev/2025/03/using-keep-sorted-to-keep-your-~/.gitconfig-tidy/&#34;&gt;previously&lt;/a&gt;,&#xA;&lt;a href=&#34;https://perrotta.dev/2025/03/keep-sorted-with-quotes/&#34;&gt;previously&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;keep-sorted is, by far, one of my favorite tools to enforce tidying up a&#xA;codebase.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Google Testing Blog, &lt;a href=&#34;https://testing.googleblog.com/2025/09/sort-lines-in-source-code.html&#34;&gt;Sort Lines in Source Code&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;They have finally released a&#xA;&lt;a href=&#34;https://abseil.io/resources/swe-book/html/ch11.html#:~:text=their%20testing%20culture.-,Testing%20on%20the%20Toilet,-Of%20all%20the&#34;&gt;TotT&lt;/a&gt;&#xA;(&amp;ldquo;Testing on the Toilet&amp;rdquo;) episode about&#xA;&lt;a href=&#34;https://github.com/google/keep-sorted&#34;&gt;keep-sorted&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;It&amp;rsquo;s such an underappreciated tool: not even 300 stars on GitHub. In the&#xA;meantime, anything named &amp;ldquo;AI&amp;rdquo; or &amp;ldquo;Agents&amp;rdquo; or &amp;ldquo;LLM&amp;rdquo; rises to the top without any&#xA;scrutiny of quality.&lt;/p&gt;&#xA;&lt;p&gt;Now I am happy that I have somewhere to link to other than my blog.&lt;/p&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: go/keep-sorted&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/pre-commit/&#34;&gt;#pre-commit&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>pre-commit: autoupdate
      </title>
      <link>https://perrotta.dev/2025/09/pre-commit-autoupdate/</link>
      <pubDate>Mon, 01 Sep 2025 12:05:52 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>pre-commit</category>
      <guid>https://perrotta.dev/2025/09/pre-commit-autoupdate/</guid>
      <description>&lt;p&gt;♠ When using &lt;a href=&#34;https://pre-commit.com/&#34;&gt;&lt;code&gt;pre-commit&lt;/code&gt;&lt;/a&gt;, you can take advantage of&#xA;its built-in &lt;code&gt;pre-commit autoupdate&lt;/code&gt; command to keep your hook dependencies&#xA;up-to-date.&lt;/p&gt;&#xA;&lt;p&gt;There&amp;rsquo;s one major drawback of adopting it ubiquitously: it is virtually&#xA;&lt;a href=&#34;https://stackoverflow.com/questions/74232399/is-it-possible-that-pre-commit-autoupdate-skips-a-single-linter&#34;&gt;impossible&lt;/a&gt;&#xA;to instruct it to skip certain repositories.&lt;/p&gt;&#xA;&lt;p&gt;It wouldn&amp;rsquo;t be hard to update it to ignore hook dependencies with skip byline&#xA;comments, but its author &lt;a href=&#34;https://stackoverflow.com/a/74240674/1745064&#34;&gt;isn&amp;rsquo;t&#xA;keen&lt;/a&gt; on implementing support for&#xA;it.&lt;/p&gt;&#xA;&lt;p&gt;So far I&amp;rsquo;ve been working around it by manually reverting undesired updates, but&#xA;that is super tedious and inefficient.&lt;/p&gt;&#xA;&lt;p&gt;Today I discovered a small utility,&#xA;&lt;a href=&#34;https://pypi.org/project/pre-commit-update/&#34;&gt;&lt;code&gt;pre-commit-update&lt;/code&gt;&lt;/a&gt;, which&#xA;explicitly supports that and was created precisely because of the limitation of&#xA;the original subcommand:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;pre-commit-update was created because there is no easy way to update your&#xA;hooks by using pre-commit autoupdate as it is not versatile enough.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;The tool (which is thankfully also written in Python, for uniformity) supports a&#xA;config file to skip certain hooks from &lt;code&gt;autoupdate&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: pre-commit: autoupdate&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/pre-commit/&#34;&gt;#pre-commit&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>&#34;Fix typo&#34;
      </title>
      <link>https://perrotta.dev/2025/08/fix-typo/</link>
      <pubDate>Wed, 13 Aug 2025 11:13:35 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>pre-commit</category>
      <category>serenity</category>
      <guid>https://perrotta.dev/2025/08/fix-typo/</guid>
      <description>&lt;p&gt;♠ You, too, can make the &lt;del&gt;word&lt;/del&gt; world a better place,&#xA;&lt;a href=&#34;https://github.com/codespell-project/codespell/pull/3758&#34;&gt;one&lt;/a&gt;&#xA;&lt;a href=&#34;https://github.com/codespell-project/codespell/pull/3759&#34;&gt;typo&lt;/a&gt; at a time.&lt;/p&gt;&#xA;&lt;p&gt;The &lt;a href=&#34;https://github.com/codespell-project/codespell/&#34;&gt;codespell&lt;/a&gt; project can be&#xA;trivially adopted in a git repository via a &lt;a href=&#34;https://pre-commit.com/&#34;&gt;pre-commit&#xA;hook&lt;/a&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;repos:&#xA;  - repo: https://github.com/codespell-project/codespell&#xA;    rev: 63c8f8312b7559622c0d82815639671ae42132ac # frozen: v2.4.1&#xA;    hooks:&#xA;      - id: codespell&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: &#34;Fix typo&#34;&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/pre-commit/&#34;&gt;#pre-commit&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/serenity/&#34;&gt;#serenity&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>★ check-json-schema: pre-commit hook
      </title>
      <link>https://perrotta.dev/2025/07/check-json-schema-pre-commit-hook/</link>
      <pubDate>Fri, 11 Jul 2025 22:05:32 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>bestof</category>
      <category>dev</category>
      <category>pre-commit</category>
      <guid>https://perrotta.dev/2025/07/check-json-schema-pre-commit-hook/</guid>
      <description>&lt;p&gt;♠ &lt;strong&gt;Problem statement&lt;/strong&gt;: given an &lt;code&gt;entry.json&lt;/code&gt; file, validate it against a given&#xA;&lt;code&gt;schema.json&lt;/code&gt; file (for example, found via the &lt;a href=&#34;https://www.schemastore.org/&#34;&gt;schema&#xA;store&lt;/a&gt;).&lt;/p&gt;&#xA;&lt;p&gt;I like the&#xA;&lt;a href=&#34;https://github.com/python-jsonschema/check-jsonschema&#34;&gt;check-jsonschema&lt;/a&gt; python&#xA;package:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;pipx run check-jsonschema --schemafile schema.json entry.json&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;What if we want to do the validation via a&#xA;&lt;a href=&#34;https://pre-commit.com&#34;&gt;pre-commit.com&lt;/a&gt; hook?&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;repos:&#xA;  - repo: https://github.com/python-jsonschema/check-jsonschema&#xA;    rev: 0.33.2&#xA;    hooks:&#xA;      - id: check-jsonschema&#xA;        files: ^entry\.json$&#xA;        args:&#xA;          - --schemafile&#xA;          - schema.json&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;This approach works well, but it requires the upkeep of a mapping from JSON file&#xA;to JSON schema.&lt;/p&gt;&#xA;&lt;p&gt;If we had various JSON files within a single repository, this would result in a&#xA;maintenance burden in the pre-commit config file.&lt;/p&gt;&#xA;&lt;p&gt;For example:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;repos:&#xA;  - repo: https://github.com/python-jsonschema/check-jsonschema&#xA;    rev: 0.33.2&#xA;    hooks:&#xA;      - id: check-jsonschema&#xA;        alias: check-jsonschema-entry1&#xA;        files: ^entry1\.json$&#xA;        args:&#xA;          - --schemafile&#xA;          - schema1.json&#xA;      - id: check-jsonschema&#xA;        alias: check-jsonschema-entry2&#xA;        files: ^entry2\.json$&#xA;        args:&#xA;          - --schemafile&#xA;          - schema2.json&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;It&amp;rsquo;s somewhat annoying to maintain multiple entries in that fashion. Furthermore&#xA;it can easily get outdated: upon the deletion of &lt;code&gt;entry1.json&lt;/code&gt; from the&#xA;repository, you&amp;rsquo;d need to remember to manually delete its corresponding entry&#xA;from the pre-commit config file.&lt;/p&gt;&#xA;&lt;p&gt;Can we do better?&lt;/p&gt;&#xA;&lt;p&gt;What if we embedded the schema reference within the JSON file itself?&lt;/p&gt;&#xA;&lt;p&gt;For example, with a local schema:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{&#xA;  &amp;#34;$schema&amp;#34;: &amp;#34;schema.json&amp;#34;&#xA;}&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Or with a remote schema URI, via HTTPS:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{&#xA;  &amp;#34;$schema&amp;#34;: &amp;#34;https://example.com/schema.json&amp;#34;&#xA;}&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Now the JSON file is self-contained, and there&amp;rsquo;s no need to define a mapping&#xA;from it to its JSON schema elsewhere.&lt;/p&gt;&#xA;&lt;p&gt;How can we integrate this approach within pre-commit?&lt;/p&gt;&#xA;&lt;p&gt;Initially I thought that&#xA;&lt;a href=&#34;https://github.com/python-jsonschema/check-jsonschema/blob/8900e07e2002c4b073e1dca7969ce81a19fcb142/.pre-commit-hooks.yaml#L10C7-L10C23&#34;&gt;&lt;code&gt;check-metaschema&lt;/code&gt;&lt;/a&gt;&#xA;from the &lt;code&gt;check-jsonschema&lt;/code&gt; python package was the answer, but it turns out it&#xA;isn&amp;rsquo;t.&lt;/p&gt;&#xA;&lt;p&gt;In fact, this isn&amp;rsquo;t currently possible. There&amp;rsquo;s a &lt;a href=&#34;https://github.com/python-jsonschema/check-jsonschema/issues/310&#34;&gt;feature&#xA;request&lt;/a&gt;,&#xA;filed ~2 years ago, with no consensus yet on how move forward. Its maintainer&#xA;seems helpful and concerned with backwards compatibility.&lt;/p&gt;&#xA;&lt;p&gt;For the sake of unblocking myself, I decided to wrap the ability to do so within&#xA;my own pre-commit hook definition.&lt;/p&gt;&#xA;&lt;p&gt;This gave birth to the following repository:&#xA;&lt;a href=&#34;https://github.com/thiagowfx/check-json-schema-meta&#34;&gt;&lt;code&gt;thiagowfx/check-json-schema-meta&lt;/code&gt;&lt;/a&gt;, which I also &lt;a href=&#34;https://github.com/python-jsonschema/check-jsonschema/issues/310#issuecomment-3062174357&#34;&gt;announced&lt;/a&gt; in the original feature request thread.&lt;/p&gt;&#xA;&lt;p&gt;Usage:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;repos:&#xA;  - repo: https://github.com/thiagowfx/check-json-schema-meta&#xA;    rev: v0.0.2&#xA;    hooks:&#xA;      - id: check-json-schema-meta&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;It goes through each JSON file in the repository, looking for an optional&#xA;top-level &lt;code&gt;$schema&lt;/code&gt; key. When found, the file is validated against the defined&#xA;schema.&lt;/p&gt;&#xA;&lt;p&gt;A &lt;code&gt;--strict&lt;/code&gt; command-line flag enforces the presence of the &lt;code&gt;$schema&lt;/code&gt; key,&#xA;otherwise the default behavior is to skip files that do not have it.&lt;/p&gt;&#xA;&lt;p&gt;The underlying schema validation defers the work to the same python package&#xA;first mentioned in this post. Because it uses&#xA;&lt;a href=&#34;https://github.com/python-jsonschema/check-jsonschema/blob/8900e07e2002c4b073e1dca7969ce81a19fcb142/src/check_jsonschema/schema_loader/main.py#L74&#34;&gt;&lt;code&gt;SchemaLoader&lt;/code&gt;&lt;/a&gt;,&#xA;both local and remote (http(s)) file references are supported.&lt;/p&gt;&#xA;&lt;p&gt;This is the first original pre-commit hook I have written. By original I mean&#xA;that I couldn&amp;rsquo;t find any pre-existing open source hook that accomplishes this&#xA;task. The entire repository was developed with AI assistance (&lt;a href=&#34;https://www.anthropic.com/claude-code&#34;&gt;Claude&#xA;Code&lt;/a&gt;), but I wasn&amp;rsquo;t satisfied with&#xA;out-of-the-box defaults; I had to heavily modify it to shape it to my taste –&#xA;which was achieved with a mix of follow-up prompting and local edits.&lt;/p&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: check-json-schema: pre-commit hook&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/bestof/&#34;&gt;#bestof&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/pre-commit/&#34;&gt;#pre-commit&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>Helm repository with a trailing slash
      </title>
      <link>https://perrotta.dev/2025/06/helm-repository-with-a-trailing-slash/</link>
      <pubDate>Tue, 24 Jun 2025 11:32:50 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>kubernetes</category>
      <category>pre-commit</category>
      <guid>https://perrotta.dev/2025/06/helm-repository-with-a-trailing-slash/</guid>
      <description>&lt;p&gt;♠ &lt;a href=&#34;https://github.com/argoproj/argo-cd/issues/9857&#34;&gt;A repository URL with a trailing slash, and the same URL without a trailing&#xA;slash, are treated as different&#xA;repos&lt;/a&gt;. We found this out the&#xA;hard way:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;Failed to load target state: failed to generate manifest for source 1 of 2: rpc error: code = Unknown desc = error building helm chart dependencies: failed to add helm repository https://chartmuseum.{corp intranet}/orgs/{corp}/: failed to add repository: failed to get command args to log: `helm repo add https:--chartmuseum.{corp intranet}-orgs-{corp}- https://chartmuseum.{corp intranet}/orgs/{corp}/` failed exit status 1: Error: looks like &amp;#34;https://chartmuseum.{corp intranet}/orgs/{corp}/&amp;#34; is not a valid chart repository or cannot be reached: failed to fetch https://chartmuseum.{corp intranet}/orgs/{corp}/index.yaml : 401 Unauthorized&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s put aside the fact the error message REALLY likes to repeat the repo name.&lt;/p&gt;&#xA;&lt;p&gt;This is a repository that we&amp;rsquo;ve already configured, with proper authentication&#xA;credentials. Why would it start to fail now, all of a sudden?&lt;/p&gt;&#xA;&lt;p&gt;You already got spoiled in the post title. Given &lt;code&gt;Chart.yaml&lt;/code&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v2&#xA;name: corp-postgres-operator&#xA;description: Corp Postgres Operator&#xA;type: application&#xA;version: 1.1.13&#xA;&#xA;dependencies:&#xA;  - name: postgres-operator&#xA;    repository: https://chartmuseum.{corp intranet}/orgs/archive&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;And also:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v2&#xA;name: corp-postgres-operator&#xA;description: Corp Postgres Operator&#xA;type: application&#xA;version: 1.1.13&#xA;&#xA;dependencies:&#xA;  - name: postgres-operator&#xA;    repository: https://chartmuseum.{corp intranet}/orgs/archive/&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;These two dependencies end up being treated differently by Argo, even though&#xA;they are effectively the same. It&amp;rsquo;s an upstream bug.&lt;/p&gt;&#xA;&lt;p&gt;To work around it, I decided to simply remove all trailing slashes from&#xA;helm repository dependencies. Canonicalize everything!&lt;/p&gt;&#xA;&lt;p&gt;A git &lt;a href=&#34;https://pre-commit.com&#34;&gt;pre-commit.com hook&lt;/a&gt; is a good mechanism to do so:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;repos:&#xA;  repo: local&#xA;  hooks:&#xA;    - id: helm-repo-trailing-slash&#xA;      name: Helm repositories must not contain trailing slashes in Chart.yaml&#xA;      files: Chart.yaml$&#xA;      language: pygrep&#xA;      entry: &amp;#39;\s*repository: [\w:/.]&amp;#43;/$&amp;#39;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Run &lt;code&gt;pre-commit run -a helm-repo-trailing-slash&lt;/code&gt;, fix all violations, and then&#xA;never think about this again.&lt;/p&gt;&#xA;&lt;p&gt;Should the upstream bug ever be fixed, we can remove this workaround.&lt;/p&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: Helm repository with a trailing slash&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/kubernetes/&#34;&gt;#kubernetes&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/pre-commit/&#34;&gt;#pre-commit&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>pre-commit: add hugo build
      </title>
      <link>https://perrotta.dev/2025/05/pre-commit-add-hugo-build/</link>
      <pubDate>Sat, 24 May 2025 17:29:48 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>pre-commit</category>
      <guid>https://perrotta.dev/2025/05/pre-commit-add-hugo-build/</guid>
      <description>&lt;p&gt;♠ Sometimes I make changes to this blog that yield build errors with its static&#xA;site generator (&lt;a href=&#34;https://gohugo.io/&#34;&gt;hugo&lt;/a&gt;).&lt;/p&gt;&#xA;&lt;p&gt;It&amp;rsquo;s trivial to spot these changes with a simple &lt;code&gt;hugo build&lt;/code&gt;, but &lt;a href=&#34;https://perrotta.dev/2025/03/yolo-debug-only-after-push/&#34;&gt;I don&amp;rsquo;t always&#xA;run it&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s what a typical error looks like:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;% hugo build&#xA;Start building sites …&#xA;hugo v0.147.5&amp;#43;extended&amp;#43;withdeploy darwin/arm64 BuildDate=2025-05-22T11:37:19Z VendorInfo=brew&#xA;&#xA;ERROR [en] REF_NOT_FOUND: Ref &amp;#34;nonexistent/2025-03-24-maccy-pin-items&amp;#34;: &amp;#34;/Users/tperrotta/Workspace/perrotta.dev/content/posts/2025-05-16-espanso-hello-world.md:10:14&amp;#34;: page not found&#xA;Total in 242 ms&#xA;Error: error building site: logged 1 error(s)&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The same happens when invoking &lt;a href=&#34;https://just.systems/man/en/&#34;&gt;&lt;code&gt;just&lt;/code&gt;&lt;/a&gt;, as you&#xA;would expect:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;% just build&#xA;hugo --environment production --gc --minify&#xA;Start building sites …&#xA;hugo v0.147.5&amp;#43;extended&amp;#43;withdeploy darwin/arm64 BuildDate=2025-05-22T11:37:19Z VendorInfo=brew&#xA;&#xA;ERROR [en] REF_NOT_FOUND: Ref &amp;#34;nonexistent/2025-03-24-maccy-pin-items&amp;#34;: &amp;#34;/Users/tperrotta/Workspace/perrotta.dev/content/posts/2025-05-16-espanso-hello-world.md:10:14&amp;#34;: page not found&#xA;Total in 280 ms&#xA;Error: error building site: logged 1 error(s)&#xA;error: Recipe `build` failed on line 17 with exit code 1&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;strong&gt;Problem statement&lt;/strong&gt;: catch such build errors at git pre-commit time.&lt;/p&gt;&#xA;&lt;p&gt;This is trivial with a &lt;a href=&#34;https://pre-commit.com/&#34;&gt;pre-commit.com&lt;/a&gt; hook:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-diff&#34;&gt;Author: Thiago Perrotta &amp;lt;{email redacted}&amp;gt;&#xA;Date:   Sat May 24 17:26:59 2025 &amp;#43;0200&#xA;&#xA;    pre-commit: add just build&#xA;&#xA;diff --git .pre-commit-config.yaml .pre-commit-config.yaml&#xA;index 60eb2022..623e135d 100644&#xA;--- .pre-commit-config.yaml&#xA;&amp;#43;&amp;#43;&amp;#43; .pre-commit-config.yaml&#xA;@@ -112,5 &amp;#43;112,10 @@ repos:&#xA;         language: pygrep&#xA;         types:&#xA;           - markdown&#xA;&amp;#43;      - id: hugo&#xA;&amp;#43;        name: Hugo&#xA;&amp;#43;        language: system&#xA;&amp;#43;        entry: just build&#xA;&amp;#43;        pass_filenames: false&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s the end result:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% git commit -m &amp;#34;test&amp;#34;&#xA;Check hooks apply to the repository.............................................(no files to check)Skipped&#xA;Check for useless excludes......................................................(no files to check)Skipped&#xA;[...]&#xA;Forbid usage of UTM analytics / tracking............................................................Passed&#xA;Hugo................................................................................................Failed&#xA;- hook id: hugo&#xA;- exit code: 1&#xA;&#xA;hugo --environment production --gc --minify&#xA;Start building sites …&#xA;hugo v0.147.5&amp;#43;extended&amp;#43;withdeploy darwin/arm64 BuildDate=2025-05-22T11:37:19Z VendorInfo=brew&#xA;&#xA;ERROR [en] REF_NOT_FOUND: Ref &amp;#34;a/2024-12-23-unbuffer&amp;#34;: &amp;#34;/Users/tperrotta/Workspace/perrotta.dev/content/posts/2025-05-24-watch-with-color.md:54:54&amp;#34;: page not found&#xA;Total in 276 ms&#xA;Error: error building site: logged 1 error(s)&#xA;error: Recipe `build` failed on line 19 with exit code 1&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: pre-commit: add hugo build&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/pre-commit/&#34;&gt;#pre-commit&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>pre-commit: make a hook opt-in
      </title>
      <link>https://perrotta.dev/2025/03/pre-commit-make-a-hook-opt-in/</link>
      <pubDate>Thu, 27 Mar 2025 13:22:44 +0100</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>pre-commit</category>
      <guid>https://perrotta.dev/2025/03/pre-commit-make-a-hook-opt-in/</guid>
      <description>&lt;p&gt;♠ Recently we added the following hook to our &lt;a href=&#34;https://pre-commit.com&#34;&gt;pre-commit&lt;/a&gt;&#xA;setup:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;repos:&#xA;  - repo: https://github.com/sbrunner/hooks&#xA;    rev: ee105bd276e163fce85744b39104ca563f923c47 # frozen: 1.4.0&#xA;    hooks:&#xA;      - id: helm-lock&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;It can be triggered with &lt;code&gt;pre-commit run --all-files helm-lock&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;It turned out to be an issue because not every developer has read-only access&#xA;to our helm repositories.&lt;/p&gt;&#xA;&lt;p&gt;The workaround was to make this hook opt-in.&lt;/p&gt;&#xA;&lt;p&gt;We can do so by adding it to the &lt;a href=&#34;https://pre-commit.com/#confining-hooks-to-run-at-certain-stages&#34;&gt;manual stage&lt;/a&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;repos:&#xA;  - repo: https://github.com/sbrunner/hooks&#xA;    rev: ee105bd276e163fce85744b39104ca563f923c47 # frozen: 1.4.0&#xA;    hooks:&#xA;      - id: helm-lock&#xA;        stages:&#xA;          - manual&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;It can be then invoked like this: &lt;code&gt;pre-commit run --all-files helm-lock --hook-stage manual&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;An alternative could have been to instruct people to set&#xA;&lt;a href=&#34;https://pre-commit.com/#temporarily-disabling-hooks&#34;&gt;&lt;code&gt;SKIP=helm-lock&lt;/code&gt;&lt;/a&gt;, however&#xA;&lt;a href=&#34;https://world.hey.com/jason/software-defaults-15955a8e&#34;&gt;defaults&lt;/a&gt; are powerful.&lt;/p&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: pre-commit: make a hook opt-in&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/pre-commit/&#34;&gt;#pre-commit&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>pre-commit: pin dependencies with --freeze
      </title>
      <link>https://perrotta.dev/2025/03/pre-commit-pin-dependencies-with--freeze/</link>
      <pubDate>Thu, 20 Mar 2025 18:12:57 +0100</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>pre-commit</category>
      <guid>https://perrotta.dev/2025/03/pre-commit-pin-dependencies-with--freeze/</guid>
      <description>&lt;p&gt;♠ &lt;strong&gt;Today I learned&lt;/strong&gt;: &lt;a href=&#34;https://pre-commit.com/#updating-hooks-automatically&#34;&gt;&lt;code&gt;pre-commit autoupdate&lt;/code&gt;&lt;/a&gt; has a&#xA;&lt;code&gt;--freeze&lt;/code&gt; option that replaces git tags with SHAs (hashes) on upstream hooks&#xA;(dependencies).&lt;/p&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s how to adopt them, using this blog as an example:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;% pre-commit autoupdate --jobs $(nproc) --freeze&#xA;[https://github.com/lalten/check-gha-pinning] updating v1.3.0 -&amp;gt; v1.3.0 (frozen)&#xA;[https://github.com/google/keep-sorted] updating v0.6.0 -&amp;gt; v0.6.0 (frozen)&#xA;[https://github.com/sirosen/texthooks] updating 0.6.8 -&amp;gt; 0.6.8 (frozen)&#xA;[https://github.com/google/yamlfmt] updating v0.16.0 -&amp;gt; v0.16.0 (frozen)&#xA;[https://github.com/pre-commit/pre-commit-hooks] updating v5.0.0 -&amp;gt; v5.0.0 (frozen)&#xA;[https://github.com/shssoichiro/oxipng] updating v9.1.3 -&amp;gt; v9.1.4 (frozen)&#xA;[https://github.com/codespell-project/codespell] updating v2.4.1 -&amp;gt; v2.4.1 (frozen)&#xA;[https://github.com/gitleaks/gitleaks] updating v8.23.3 -&amp;gt; v8.24.0 (frozen)&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The resulting diff (redacted one of the hooks for brevity):&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-diff&#34;&gt;diff --git .pre-commit-config.yaml .pre-commit-config.yaml&#xA;index 30aac4a2..841eac9b 100644&#xA;--- .pre-commit-config.yaml&#xA;&amp;#43;&amp;#43;&amp;#43; .pre-commit-config.yaml&#xA;@@ -23,7 &amp;#43;23,7 @@ repos:&#xA;       - id: check-hooks-apply&#xA;       - id: check-useless-excludes&#xA;   - repo: https://github.com/pre-commit/pre-commit-hooks&#xA;-    rev: v5.0.0&#xA;&amp;#43;    rev: cef0300fd0fc4d2a87a85fa2093c6b283ea36f4b  # frozen: v5.0.0&#xA;     hooks:&#xA;       - id: check-executables-have-shebangs&#xA;@@ -41,27 &amp;#43;41,27 @@ repos:&#xA;           - -D .codespell-dictionary.txt&#xA;           - -I .codespell-ignore-words.txt&#xA;   - repo: https://github.com/gitleaks/gitleaks&#xA;-    rev: v8.23.3&#xA;&amp;#43;    rev: f565e4e10c6c8081e374235bb4e4bcb8c0c2fa63  # frozen: v8.24.0&#xA;     hooks:&#xA;       - id: gitleaks&#xA;   - repo: https://github.com/google/keep-sorted&#xA;-    rev: v0.6.0&#xA;&amp;#43;    rev: df93c2722b6126556183749880f16a9beb664bb4  # frozen: v0.6.0&#xA;     hooks:&#xA;       - id: keep-sorted&#xA;   - repo: https://github.com/google/yamlfmt&#xA;-    rev: v0.16.0&#xA;&amp;#43;    rev: ac76bb931851dda64a55ef746ca9b8bb41634b98  # frozen: v0.16.0&#xA;     hooks:&#xA;       - id: yamlfmt&#xA;   - repo: https://github.com/lalten/check-gha-pinning&#xA;-    rev: v1.3.0&#xA;&amp;#43;    rev: 225deee5dff16fae532978007ce6e37045e14c68  # frozen: v1.3.0&#xA;     hooks:&#xA;       - id: check-gha-pinning&#xA;   - repo: https://github.com/shssoichiro/oxipng&#xA;-    rev: v9.1.3&#xA;&amp;#43;    rev: bc8c36456858fe4678460bdea520a4ec0bc50dc4  # frozen: v9.1.4&#xA;     hooks:&#xA;       - id: oxipng&#xA;   - repo: https://github.com/sirosen/texthooks&#xA;-    rev: 0.6.8&#xA;&amp;#43;    rev: 13a42592dd28b6b688bb1c23fa769234984d4ca7  # frozen: 0.6.8&#xA;     hooks:&#xA;       - id: fix-ligatures&#xA;       - id: fix-smartquotes&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;This is useful to mitigate supply-chain attacks such as the recent&#xA;&lt;a href=&#34;https://www.wiz.io/blog/github-action-tj-actions-changed-files-supply-chain-attack-cve-2025-30066&#34;&gt;&lt;code&gt;tj-actions/changed-files&lt;/code&gt;&lt;/a&gt;&#xA;one.&lt;/p&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: pre-commit: pin dependencies with --freeze&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/pre-commit/&#34;&gt;#pre-commit&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>★ git freeze, git thaw
      </title>
      <link>https://perrotta.dev/2025/03/git-freeze-git-thaw/</link>
      <pubDate>Wed, 19 Mar 2025 13:27:43 +0100</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>bestof</category>
      <category>dev</category>
      <category>git</category>
      <category>pre-commit</category>
      <guid>https://perrotta.dev/2025/03/git-freeze-git-thaw/</guid>
      <description>&lt;p&gt;♠ Now I am becoming one of the snobs™ with the first sentence in the following&#xA;paragraph. Here we go.&lt;/p&gt;&#xA;&lt;p&gt;I had &lt;a href=&#34;https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview&#34;&gt;Claude&#xA;Code&lt;/a&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2025/03/git-freeze-git-thaw/#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&#xA;write me a pair of &lt;code&gt;git&lt;/code&gt; utilities: &lt;code&gt;git freeze&lt;/code&gt; and &lt;code&gt;git thaw&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;First of all: &amp;ldquo;to thaw&amp;rdquo; means &amp;ldquo;to defrost&amp;rdquo;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;To change from a frozen solid to a liquid by gradual warming.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Perhaps &lt;code&gt;git unfreeze&lt;/code&gt; would have been more intuitive, let&amp;rsquo;s leave the&#xA;bikeshedding to another day though.&lt;/p&gt;&#xA;&lt;p&gt;What should these utilities do? They are inspired by the ones with the same name&#xA;in Chromium&#xA;&lt;a href=&#34;https://www.chromium.org/developers/how-tos/install-depot-tools/&#34;&gt;depot_tools&lt;/a&gt;.&#xA;&lt;a href=&#34;https://commondatastorage.googleapis.com/chrome-infra-docs/flat/depot_tools/docs/html/depot_tools_tutorial.html#_setting_up&#34;&gt;Direct&#xA;link&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://commondatastorage.googleapis.com/chrome-infra-docs/flat/depot_tools/docs/html/git-freeze.html&#34;&gt;&lt;code&gt;git-freeze(1)&lt;/code&gt;&lt;/a&gt;&#xA;allows you to put the current branch in &amp;lsquo;suspended animation&amp;rsquo; by committing&#xA;your changes to a specially-named commit on the top of your current branch.&#xA;When you come back to your branch later, you can just run&#xA;&lt;a href=&#34;https://commondatastorage.googleapis.com/chrome-infra-docs/flat/depot_tools/docs/html/git-thaw.html&#34;&gt;&lt;code&gt;git-thaw(1)&lt;/code&gt;&lt;/a&gt;&#xA;to get your work-in-progress changes back to what they were.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Installing&#xA;&lt;a href=&#34;https://commondatastorage.googleapis.com/chrome-infra-docs/flat/depot_tools/docs/html/depot_tools.html&#34;&gt;&lt;code&gt;depot_tools(7)&lt;/code&gt;&lt;/a&gt;&#xA;just to get these two utilities is too bloated though. The security software at&#xA;work flagged it as malware&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2025/03/git-freeze-git-thaw/#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;. And they didn&amp;rsquo;t even bother to package it for&#xA;Linux distributions in the first place.&lt;/p&gt;&#xA;&lt;h2 id=&#34;git-freeze&#34;&gt;&#xA;  &lt;a href=&#34;https://github.com/thiagowfx/.dotfiles/blob/master/git/.bin/git-freeze&#34;&gt;&lt;code&gt;git-freeze&lt;/code&gt;&lt;/a&gt;&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2025/03/git-freeze-git-thaw/#git-freeze&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;#!/bin/sh&#xA;#&#xA;# git-freeze - Stage all files and create a git-freeze commit&#xA;&#xA;set -e&#xA;&#xA;# Check if we&amp;#39;re inside a git repository&#xA;if [ &amp;#34;$(git rev-parse --is-inside-work-tree 2&amp;gt;/dev/null)&amp;#34; != &amp;#34;true&amp;#34; ]; then&#xA;  echo &amp;#34;Error: Not in a git repository&amp;#34;&#xA;  exit 1&#xA;fi&#xA;&#xA;# Stage all changes (tracked and untracked)&#xA;git add -A&#xA;&#xA;# Create a commit with the freeze message&#xA;git commit -m &amp;#34;git-freeze&amp;#34; -n&#xA;&#xA;echo &amp;#34;Git freeze created successfully&amp;#34;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;h2 id=&#34;git-thaw&#34;&gt;&#xA;  &lt;a href=&#34;https://github.com/thiagowfx/.dotfiles/blob/master/git/.bin/git-thaw&#34;&gt;&lt;code&gt;git-thaw&lt;/code&gt;&lt;/a&gt;&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2025/03/git-freeze-git-thaw/#git-thaw&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;#!/bin/sh&#xA;#&#xA;# git-thaw - Undo a git-freeze operation&#xA;&#xA;set -e&#xA;&#xA;# Check if we&amp;#39;re inside a git repository&#xA;if [ &amp;#34;$(git rev-parse --is-inside-work-tree 2&amp;gt;/dev/null)&amp;#34; != &amp;#34;true&amp;#34; ]; then&#xA;  echo &amp;#34;Error: Not in a git repository&amp;#34;&#xA;  exit 1&#xA;fi&#xA;&#xA;# Check if there&amp;#39;s at least one commit&#xA;if ! git rev-parse --verify HEAD &amp;gt;/dev/null 2&amp;gt;&amp;amp;1; then&#xA;  echo &amp;#34;Error: No commits yet&amp;#34;&#xA;  exit 1&#xA;fi&#xA;&#xA;# Check if the last commit is a git-freeze commit&#xA;if ! git log -n 1 --pretty=%B | grep -q &amp;#34;^git-freeze$&amp;#34;; then&#xA;  echo &amp;#34;Error: Last commit is not a git-freeze commit&amp;#34;&#xA;  exit 1&#xA;fi&#xA;&#xA;# Undo the git-freeze commit&#xA;if git rev-parse --verify HEAD^ &amp;gt;/dev/null 2&amp;gt;&amp;amp;1; then&#xA;  # If there&amp;#39;s a parent commit, reset to it&#xA;  git reset &amp;#34;HEAD^&amp;#34;&#xA;else&#xA;  # If it&amp;#39;s the initial commit, delete the reference and unstage everything&#xA;  git update-ref -d HEAD&#xA;  git restore --staged .&#xA;fi&#xA;&#xA;echo &amp;#34;Git freeze thawed successfully&amp;#34;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;h2 id=&#34;post-ai-cleanup&#34;&gt;&#xA;  Post-AI cleanup&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2025/03/git-freeze-git-thaw/#post-ai-cleanup&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;The only change I had to make was to add &lt;code&gt;-n&lt;/code&gt; to &lt;code&gt;git commit&lt;/code&gt; in &lt;code&gt;git-freeze&lt;/code&gt; so&#xA;that pre-commit hooks would be skipped upon freeze.&lt;/p&gt;&#xA;&lt;h2 id=&#34;usage&#34;&gt;&#xA;  Usage&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2025/03/git-freeze-git-thaw/#usage&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Within an existing &lt;code&gt;git&lt;/code&gt; repository, do some work in a custom branch.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Decide that something else more important and/or urgent needs your attention.&#xA;Let go of &lt;a href=&#34;https://git-scm.com/docs/git-worktree&#34;&gt;git worktrees&lt;/a&gt; in this&#xA;instance.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;code&gt;git freeze&lt;/code&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Switch to a new branch. Do whatever. Done.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;code&gt;git checkout &amp;lt;your previous branch&amp;gt;&lt;/code&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;code&gt;git thaw&lt;/code&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Resume your previous work. Profit.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&lt;strong&gt;Edit(2025-03-20)&lt;/strong&gt;: A friend asked that is the difference between &lt;code&gt;git freeze&lt;/code&gt;&#xA;and &lt;code&gt;git stash&lt;/code&gt;. The main difference is that &lt;code&gt;git stash&lt;/code&gt; does not handle files&#xA;not yet tracked by &lt;code&gt;git&lt;/code&gt;, whereas &lt;code&gt;git freeze&lt;/code&gt; does. I think of &lt;code&gt;freeze&lt;/code&gt; as the&#xA;equivalent of completely clearing up my physical desk of all objects so that I&#xA;can focus on something else.&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;&lt;em&gt;An agentic coding tool made by Anthropic. Currently in beta as a research&#xA;preview.&lt;/em&gt;&amp;#160;&lt;a href=&#34;https://perrotta.dev/2025/03/git-freeze-git-thaw/#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;In hindsight that&amp;rsquo;s not even surprising.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2025/03/git-freeze-git-thaw/#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: git freeze, git thaw&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/bestof/&#34;&gt;#bestof&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/git/&#34;&gt;#git&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/pre-commit/&#34;&gt;#pre-commit&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>YOLO: debug only after push
      </title>
      <link>https://perrotta.dev/2025/03/yolo-debug-only-after-push/</link>
      <pubDate>Fri, 14 Mar 2025 15:16:57 +0100</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>meta</category>
      <category>pre-commit</category>
      <category>serenity</category>
      <guid>https://perrotta.dev/2025/03/yolo-debug-only-after-push/</guid>
      <description>&lt;p&gt;♠ Whenever writing new posts in this blog I rarely preview them in advance,&#xA;especially when they are short and simple&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2025/03/yolo-debug-only-after-push/#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;&#xA;&lt;p&gt;My rushed workflow is roughly:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;open a new terminal tab / window&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;cd ~blog&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;just new &amp;quot;hello world&amp;quot;&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;vim content/posts&lt;/code&gt;, hit &amp;lsquo;G&amp;rsquo;, hit &amp;lsquo;k&amp;rsquo;, hit &amp;lsquo;ENTER&amp;rsquo;&lt;/li&gt;&#xA;&lt;li&gt;draft the post&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;:Gwq&lt;/code&gt; (&lt;a href=&#34;https://github.com/tpope/vim-fugitive&#34;&gt;vim-fugitive&lt;/a&gt;, &lt;code&gt;git&lt;/code&gt;&#xA;integration)&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;git commit -m &amp;quot;new post: hello world&amp;quot;&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;git push&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&lt;em&gt;ONLY then&lt;/em&gt;, &lt;code&gt;just&lt;/code&gt;, open web browser, see if it looks OK.&lt;/p&gt;&#xA;&lt;p&gt;Does it &lt;em&gt;NOT&lt;/em&gt; look OK (grammar errors, markdown issues, typos)? Make a quick&#xA;edit, &lt;code&gt;:Gwq&lt;/code&gt; again, &lt;code&gt;git amend &amp;amp;&amp;amp; git push -f&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Normally this process happens faster than the CI pipeline takes to build and&#xA;subsequently publish the site.&lt;/p&gt;&#xA;&lt;p&gt;No AI involved™, not even a spellchecker&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2025/03/yolo-debug-only-after-push/#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;Which is what I strive for most of the time anyways.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2025/03/yolo-debug-only-after-push/#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;Not completely true:&#xA;&lt;a href=&#34;https://github.com/codespell-project/codespell&#34;&gt;&lt;code&gt;codespell&lt;/code&gt;&lt;/a&gt; is integrated&#xA;via &lt;a href=&#34;https://pre-commit.com/&#34;&gt;pre-commit&lt;/a&gt;. &lt;code&gt;vim&lt;/code&gt; has &lt;code&gt;:set spell&lt;/code&gt;. That&amp;rsquo;s&#xA;all though. There&amp;rsquo;s no &amp;ldquo;make this sound better&amp;rdquo; or &amp;ldquo;rewrite this more&#xA;professionally&amp;rdquo;. What you read is what comes straight off my brain.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2025/03/yolo-debug-only-after-push/#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: YOLO: debug only after push&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/meta/&#34;&gt;#meta&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/pre-commit/&#34;&gt;#pre-commit&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/serenity/&#34;&gt;#serenity&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>keep-sorted, with quotes
      </title>
      <link>https://perrotta.dev/2025/03/keep-sorted-with-quotes/</link>
      <pubDate>Thu, 13 Mar 2025 13:00:06 +0100</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>pre-commit</category>
      <guid>https://perrotta.dev/2025/03/keep-sorted-with-quotes/</guid>
      <description>&lt;p&gt;♠ &lt;a href=&#34;https://perrotta.dev/2024/12/keep-sorted/&#34;&gt;keep-sorted&lt;/a&gt; has been previously covered.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Problem statement&lt;/strong&gt;: how to automatically sort the following YAML list?&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;- azure-workload-identity&#xA;- clustermon&#xA;- flatfile&#xA;- kube2iam&#xA;- mongo&#xA;- node-local-dns&#xA;- &amp;#34;pgbouncer*&amp;#34;&#xA;- &amp;#34;rabbitmq*&amp;#34;&#xA;- &amp;#34;redis*&amp;#34;&#xA;- traefik&#xA;- vault&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;I would naturally add &lt;code&gt;keep-sorted&lt;/code&gt; to it, but the result is not what I expected:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;# keep-sorted start&#xA;- &amp;#34;pgbouncer*&amp;#34;&#xA;- &amp;#34;rabbitmq*&amp;#34;&#xA;- &amp;#34;redis*&amp;#34;&#xA;- azure-workload-identity&#xA;- clustermon&#xA;- flatfile&#xA;- kube2iam&#xA;- mongo&#xA;- node-local-dns&#xA;- traefik&#xA;- vault&#xA;# keep-sorted end&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Quotes are coming first!&lt;/p&gt;&#xA;&lt;p&gt;Which does indeed make sense. They are ASCII characters&#xA;(&lt;a href=&#34;https://man.archlinux.org/man/ascii.7&#34;&gt;&lt;code&gt;ascii(7)&lt;/code&gt;&lt;/a&gt;), value 34, which comes&#xA;before &lt;code&gt;a-z&lt;/code&gt;. Oh well.&lt;/p&gt;&#xA;&lt;p&gt;I tried &lt;code&gt;ignore_prefixes=&amp;quot;&lt;/code&gt; to no avail. I also tried &lt;code&gt;by_regex=[\w-]+&lt;/code&gt; to no&#xA;avail.&lt;/p&gt;&#xA;&lt;p&gt;Only the following form worked:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;# keep-sorted start by_regex=[&amp;#39;\w&amp;#43;[\w ]*&amp;#39;]&#xA;- azure-workload-identity&#xA;- clustermon&#xA;- flatfile&#xA;- kube2iam&#xA;- mongo&#xA;- node-local-dns&#xA;- &amp;#34;pgbouncer*&amp;#34;&#xA;- &amp;#34;rabbitmq*&amp;#34;&#xA;- &amp;#34;redis*&amp;#34;&#xA;- traefik&#xA;- vault&#xA;# keep-sorted end&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&amp;hellip;but it&amp;rsquo;s a mouthful. I filed an upstream bug:&#xA;&lt;a href=&#34;https://github.com/google/keep-sorted/issues/76&#34;&gt;https://github.com/google/keep-sorted/issues/76&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Edit(2025-01-04)&lt;/strong&gt;: Thanks Ho Man for the following suggestion:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;# keep-sorted start by_regex=\w&amp;#43;&#xA;- azure-workload-identity&#xA;- clustermon&#xA;- flatfile&#xA;- kube2iam&#xA;- mongo&#xA;- node-local-dns&#xA;- &amp;#34;pgbouncer*&amp;#34;&#xA;- &amp;#34;rabbitmq*&amp;#34;&#xA;- &amp;#34;redis*&amp;#34;&#xA;- traefik&#xA;- vault&#xA;# keep-sorted end&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: keep-sorted, with quotes&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/pre-commit/&#34;&gt;#pre-commit&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>commit, pre-commit, commit
      </title>
      <link>https://perrotta.dev/2025/03/commit-pre-commit-commit/</link>
      <pubDate>Wed, 12 Mar 2025 16:41:53 +0100</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>pre-commit</category>
      <guid>https://perrotta.dev/2025/03/commit-pre-commit-commit/</guid>
      <description>&lt;p&gt;♠ &lt;a href=&#34;https://en.wikipedia.org/wiki/Program_optimization&#34;&gt;Premature optimization is the root of all&#xA;evil&lt;/a&gt; but I run into this&#xA;almost every single day, it&amp;rsquo;s time to finally address it.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Problem statement&lt;/strong&gt;: When using &lt;a href=&#34;https://pre-commit.com/&#34;&gt;pre-commit&lt;/a&gt; with a&#xA;hook that will automatically fix an issue, the commit will fail the first time&#xA;it runs. This means I need to issue two &lt;code&gt;git commit -a&lt;/code&gt; commands in order to&#xA;effectively create a commit: the first one will trigger &lt;code&gt;pre-commit run&lt;/code&gt;, the&#xA;second one will add the changes / fixes done by it. The question is: how can I&#xA;merge these two steps into a single command?&lt;/p&gt;&#xA;&lt;p&gt;There are several ways to tackle this issue:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;create a custom shell script that does a double commit invocation&lt;/li&gt;&#xA;&lt;li&gt;create a &lt;code&gt;git&lt;/code&gt; alias that does a double commit invocation&lt;/li&gt;&#xA;&lt;li&gt;use a native shell solution&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;I am hesitant to pollute my dotfiles with more scripts, hence I&amp;rsquo;d rather find a&#xA;native shell solution.&lt;/p&gt;&#xA;&lt;p&gt;I came up with this very simple idea: since I use &lt;code&gt;zsh&lt;/code&gt;, leverage the &lt;a href=&#34;https://zsh.sourceforge.io/Doc/Release/Shell-Grammar.html&#34;&gt;&lt;code&gt;repeat&lt;/code&gt;&#xA;built-in&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;repeat word do list done&lt;/p&gt;&#xA;&lt;p&gt;word is expanded and treated as an arithmetic expression, which must evaluate to a number n. list is then executed n times.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% repeat 2 git commit -a -m &amp;#34;feat: bootstrap time travel&amp;#34;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;This only works iff I &lt;em&gt;really&lt;/em&gt; want to commit everything that is in the staging&#xA;area (the git index).&lt;/p&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: commit, pre-commit, commit&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/pre-commit/&#34;&gt;#pre-commit&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>★ Using keep-sorted to keep your ~/.gitconfig tidy
      </title>
      <link>https://perrotta.dev/2025/03/using-keep-sorted-to-keep-your-~/.gitconfig-tidy/</link>
      <pubDate>Thu, 06 Mar 2025 22:53:23 +0100</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>bestof</category>
      <category>dev</category>
      <category>git</category>
      <category>pre-commit</category>
      <guid>https://perrotta.dev/2025/03/using-keep-sorted-to-keep-your-~/.gitconfig-tidy/</guid>
      <description>&lt;p&gt;♠ I tend to sort my&#xA;&lt;a href=&#34;https://github.com/thiagowfx/.dotfiles/blob/master/git/.gitconfig&#34;&gt;&lt;code&gt;~/.gitconfig&lt;/code&gt;&lt;/a&gt;&#xA;headings to keep the config tidy.&lt;/p&gt;&#xA;&lt;p&gt;An example:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-ini&#34;&gt;[...]&#xA;# https://git-scm.com/docs/git-rerere&#xA;[rerere]&#xA;&#x9;autoUpdate = true&#xA;&#x9;enabled = true&#xA;&#xA;[status]&#xA;&#x9;# Show individual files in untracked directories.&#xA;&#x9;showUntrackedFiles = all&#xA;&#x9;short = true&#xA;&#x9;branch = true&#xA;&#xA;[submodule]&#xA;&#x9;# Clone new submodules in parallel with as many jobs.&#xA;&#x9;fetchJobs = 0&#xA;[...]&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;This was done manually. Until&amp;hellip;today.&lt;/p&gt;&#xA;&lt;p&gt;It has just occurred to me&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2025/03/using-keep-sorted-to-keep-your-~/.gitconfig-tidy/#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; I could use&#xA;&lt;a href=&#34;https://github.com/google/keep-sorted&#34;&gt;keep-sorted&lt;/a&gt; with the headings.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Previously:&lt;/p&gt;&#xA;&lt;p&gt;— &lt;a href=&#34;https://perrotta.dev/2024/12/keep-sorted/&#34;&gt;keep-sorted&lt;/a&gt;&#xA;— &lt;a href=&#34;https://perrotta.dev/2024/12/pre-commit/&#34;&gt;pre-commit&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;But&amp;hellip;how?!&lt;/p&gt;&#xA;&lt;p&gt;The usage to sort, say, aliases, is quite straightforward&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2025/03/using-keep-sorted-to-keep-your-~/.gitconfig-tidy/#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;[alias]&#xA;&#x9;# keep-sorted begin&#xA;&#x9;bd = !branch=&amp;#34;$(git branch --show-current)&amp;#34; &amp;amp;&amp;amp; git default &amp;amp;&amp;amp; git branch -D &amp;#34;${branch:-$1}&amp;#34;&#xA;&#x9;blank = desc -m \&amp;#34;blank commit\&amp;#34;&#xA;&#x9;cm = commit&#xA;&#x9;co = checkout&#xA;&#x9;cp = cherry-pick&#xA;&#x9;dc = diff --cached&#xA;&#x9;default = !git checkout main &amp;amp;&amp;gt;/dev/null || git checkout master&#xA;&#x9;desc = commit --allow-empty -n&#xA;&#x9;emerge = !git add -A . &amp;amp;&amp;amp; git amend -n &amp;amp;&amp;amp; git pushm --force-with-lease&#xA;&#x9;nb = switch --create&#xA;        [...]&#xA;&#x9;# keep-sorted finish&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;How can you apply the same technique to headings?&lt;/p&gt;&#xA;&lt;p&gt;The out-of-the-box utilization is OK-ish, but not great:&lt;/p&gt;&#xA;&lt;p&gt;From:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-ini&#34;&gt;# keep-sorted begin&#xA;[fetch]&#xA;&#x9;prune = true&#xA;&#xA;[help]&#xA;&#x9;autocorrect = 10&#xA;&#xA;[format]&#xA;&#x9;# Automatically sign-off patches when using format-patch.&#xA;&#x9;signoff = true&#xA;# keep-sorted finish&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;To:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-ini&#34;&gt;# keep-sorted start&#xA;&#xA;[fetch]&#xA;&#x9;prune = true&#xA;[format]&#xA;&#x9;# Automatically sign-off patches when using format-patch.&#xA;&#x9;signoff = true&#xA;[help]&#xA;&#x9;autocorrect = 10&#xA;# keep-sorted end&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;(including the blank line at the top)&lt;/p&gt;&#xA;&lt;p&gt;It turns out all we have to do is a little bit of tweaking:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-ini&#34;&gt;# keep-sorted begin block=yes newline_separated=yes&#xA;[fetch]&#xA;&#x9;prune = true&#xA;&#xA;[help]&#xA;&#x9;autocorrect = 10&#xA;&#xA;[format]&#xA;&#x9;# Automatically sign-off patches when using format-patch.&#xA;&#x9;signoff = true&#xA;# keep-sorted finish&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&amp;hellip;which then becomes:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-ini&#34;&gt;# keep-sorted begin block=yes newline_separated=yes&#xA;[fetch]&#xA;&#x9;prune = true&#xA;&#xA;[format]&#xA;&#x9;# Automatically sign-off patches when using format-patch.&#xA;&#x9;signoff = true&#xA;&#xA;[help]&#xA;&#x9;autocorrect = 10&#xA;# keep-sorted finish&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;How nice!&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;Can you even imagine? Having original ideas? In 2025? Without LLMs?! Is&#xA;this even real??&amp;#160;&lt;a href=&#34;https://perrotta.dev/2025/03/using-keep-sorted-to-keep-your-~/.gitconfig-tidy/#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;I replaced &lt;code&gt;start -&amp;gt; begin&lt;/code&gt; and &lt;code&gt;end -&amp;gt; finish&lt;/code&gt; otherwise &lt;code&gt;keep-sorted&lt;/code&gt;&#xA;would sort this blog post itself. If you copy and paste this example,&#xA;remember to change the words back to the original ones.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2025/03/using-keep-sorted-to-keep-your-~/.gitconfig-tidy/#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: Using keep-sorted to keep your ~/.gitconfig tidy&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/bestof/&#34;&gt;#bestof&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/git/&#34;&gt;#git&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/pre-commit/&#34;&gt;#pre-commit&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>★ GitHub Distributed CODEOWNERS
      </title>
      <link>https://perrotta.dev/2025/02/github-distributed-codeowners/</link>
      <pubDate>Wed, 12 Feb 2025 13:57:29 +0100</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>bestof</category>
      <category>dev</category>
      <category>git</category>
      <category>pre-commit</category>
      <guid>https://perrotta.dev/2025/02/github-distributed-codeowners/</guid>
      <description>&lt;p&gt;♠ We recently adopted Andrew Ring&amp;rsquo;s &amp;ldquo;Distributed CODEOWNERS&amp;rdquo;&#xA;(&lt;a href=&#34;https://github.com/andrewring/github-distributed-owners&#34;&gt;github&lt;/a&gt;) in some&#xA;parts of our codebase.&lt;/p&gt;&#xA;&lt;p&gt;You know the concept of&#xA;&lt;a href=&#34;https://chromium.googlesource.com/chromium/src/&amp;#43;/HEAD/docs/code_reviews.md&#34;&gt;&lt;code&gt;OWNERS&lt;/code&gt;&lt;/a&gt;&#xA;if you&amp;rsquo;ve worked at Google, or in the Chromium codebase. As far as I know, they&#xA;were the pioneers of this concept.&lt;/p&gt;&#xA;&lt;p&gt;Outside Google, GitHub has popularized it with&#xA;&lt;a href=&#34;https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners&#34;&gt;CODEOWNERS&lt;/a&gt;.&#xA;It&amp;rsquo;s pretty much the same concept, it&amp;rsquo;s just implemented differently.&lt;/p&gt;&#xA;&lt;h2 id=&#34;google&#34;&gt;&#xA;  Google&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2025/02/github-distributed-codeowners/#google&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;You have distributed &lt;code&gt;OWNERS&lt;/code&gt; files throughout the whole repository. For&#xA;example:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;java/&#xA;  OWNERS (a)&#xA;  project1/&#xA;    OWNERS (b)&#xA;  project2/&#xA;    OWNERS (c)&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;code&gt;OWNERS&lt;/code&gt; in (b) have CL (ChangeList) / PR (Pull Request) approval permissions&#xA;for &lt;code&gt;java/project1&lt;/code&gt;, but not for &lt;code&gt;java/project2&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;OWNERS&lt;/code&gt; in (a) have approval permissions for both projects.&lt;/p&gt;&#xA;&lt;h2 id=&#34;github&#34;&gt;&#xA;  GitHub&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2025/02/github-distributed-codeowners/#github&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;There&amp;rsquo;s a single &lt;code&gt;.github/CODEOWNERS&lt;/code&gt; file that controls the whole repository:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;* a&#xA;/java/project1 b&#xA;/java/project2 c&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;h2 id=&#34;distributed-codeowners&#34;&gt;&#xA;  Distributed CODEOWNERS&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2025/02/github-distributed-codeowners/#distributed-codeowners&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;What if we wanted to adopt the Google approach in GitHub? Use Andrew&amp;rsquo;s tool!&lt;/p&gt;&#xA;&lt;p&gt;What does it do? It collects all scattered &lt;code&gt;OWNERS&lt;/code&gt; in the repository and&#xA;consolidates them in a single &lt;code&gt;.github/CODEOWNERS&lt;/code&gt; file, expected by GitHub.&#xA;Win-win!&lt;/p&gt;&#xA;&lt;p&gt;I like to adopt it with a &lt;a href=&#34;https://pre-commit.com&#34;&gt;pre-commit&lt;/a&gt; hook:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;repos:&#xA;  - repo: https://github.com/andrewring/github-distributed-owners&#xA;    rev: v0.1.9&#xA;    hooks:&#xA;      - id: github-distributed-owners&#xA;        files: .*/OWNERS$|^\.github/CODEOWNERS$&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Then it gets automatically managed via CI and/or local commits. It can also be&#xA;triggered manually with:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;pre-commit run -a github-distributed-codeowners&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: GitHub Distributed CODEOWNERS&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/bestof/&#34;&gt;#bestof&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/git/&#34;&gt;#git&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/pre-commit/&#34;&gt;#pre-commit&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>codespell: ignore words
      </title>
      <link>https://perrotta.dev/2025/01/codespell-ignore-words/</link>
      <pubDate>Mon, 20 Jan 2025 10:54:55 -0300</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>pre-commit</category>
      <guid>https://perrotta.dev/2025/01/codespell-ignore-words/</guid>
      <description>&lt;p&gt;♠ When using &lt;a href=&#34;https://github.com/codespell-project/codespell&#34;&gt;codespell&lt;/a&gt; to spell&#xA;check a git repository, sometimes you may run into false positive words.&lt;/p&gt;&#xA;&lt;p&gt;There are two ways to whitelist them.&lt;/p&gt;&#xA;&lt;p&gt;The first one is via command-line arguments:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;codespell -L=ist -L=regio&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;or&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;codespell -L=ist,regio&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The second one is with a dedicated &lt;code&gt;.ignore-words.txt&lt;/code&gt; file:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% cat .ignore-words.txt&#xA;# keep-sorted start&#xA;ist&#xA;regio&#xA;# keep-sorted end&#xA;% codespell -I .ignore-words.txt&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: codespell: ignore words&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/pre-commit/&#34;&gt;#pre-commit&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>★ YAML: enforce schema linting
      </title>
      <link>https://perrotta.dev/2025/01/yaml-enforce-schema-linting/</link>
      <pubDate>Thu, 09 Jan 2025 13:22:32 -0300</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>bestof</category>
      <category>dev</category>
      <category>pre-commit</category>
      <guid>https://perrotta.dev/2025/01/yaml-enforce-schema-linting/</guid>
      <description>&lt;p&gt;♠ &lt;a href=&#34;https://yaml.org/&#34;&gt;YAML&lt;/a&gt; files can have a schema associated to them.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://schemastore.org/&#34;&gt;Schema store&lt;/a&gt; is a popular source for schemas. Or you&#xA;could &lt;a href=&#34;https://json-schema.org/&#34;&gt;write your own&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;JSON Schemas work on YAML files just fine.&lt;/p&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s say you found a schema. For example, for &lt;a href=&#34;https://json.schemastore.org/json-patch.json&#34;&gt;JSON Patch&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Assume this sample &lt;code&gt;.yaml&lt;/code&gt; file:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;- op: remove&#xA;  path: /spec/syncPolicy/automated&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;How to enforce the schema above in CI?&lt;/p&gt;&#xA;&lt;p&gt;One approach I like is with &lt;a href=&#34;https://github.com/redhat-developer/yaml-language-server&#34;&gt;YAML Language&#xA;Server&lt;/a&gt; by Red Hat.&lt;/p&gt;&#xA;&lt;p&gt;You start by annotating the file with the desired schema:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;# yaml-language-server: $schema=https://json.schemastore.org/json-patch.json&#xA;- op: remove&#xA;  path: /spec/syncPolicy/automated&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;And then you integrate it with a CI tool that is aware of it. I like&#xA;&lt;a href=&#34;https://pre-commit.com/&#34;&gt;pre-commit.com&lt;/a&gt;. Assuming you follow the pre-commit&#xA;setup, integrate the hook:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;repos:&#xA;  - repo: https://github.com/jmlrt/check-yamlschema&#xA;    rev: v0.0.4&#xA;    hooks:&#xA;      - id: check-yamlschema&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Then &lt;code&gt;pre-commit run [--all-files] check-yamlschema&lt;/code&gt; shall perform its job.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/jmlrt/check-yamlschema&#34;&gt;https://github.com/jmlrt/check-yamlschema&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;A CLI and pre-commit hook for jsonschema validation in YAML files with&#xA;multiple documents&lt;/p&gt;&#xA;&lt;p&gt;Parse multi-documents YAML files, look for inline schema comments, and&#xA;validate the documents according to their schema.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;The &lt;a href=&#34;https://json.schemastore.org/any.json&#34;&gt;&amp;lsquo;any&amp;rsquo;&lt;/a&gt; schema&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2025/01/yaml-enforce-schema-linting/#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; can be handy as an&#xA;opt-out mechanism:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{&#xA;  &amp;#34;$schema&amp;#34;: &amp;#34;http://json-schema.org/draft-07/schema#&amp;#34;,&#xA;  &amp;#34;$id&amp;#34;: &amp;#34;https://json.schemastore.org/any.json&amp;#34;,&#xA;  &amp;#34;anyOf&amp;#34;: [&#xA;    { &amp;#34;type&amp;#34;: &amp;#34;object&amp;#34; },&#xA;    { &amp;#34;type&amp;#34;: &amp;#34;array&amp;#34; },&#xA;    { &amp;#34;type&amp;#34;: &amp;#34;string&amp;#34; },&#xA;    { &amp;#34;type&amp;#34;: &amp;#34;number&amp;#34; },&#xA;    { &amp;#34;type&amp;#34;: &amp;#34;boolean&amp;#34; },&#xA;    { &amp;#34;type&amp;#34;: &amp;#34;null&amp;#34; }&#xA;  ]&#xA;}&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/SchemaStore/schemastore/pull/3885&#34;&gt;Submitted&lt;/a&gt; by yours truly.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2025/01/yaml-enforce-schema-linting/#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: YAML: enforce schema linting&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/bestof/&#34;&gt;#bestof&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/pre-commit/&#34;&gt;#pre-commit&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>pre-commit &#43; vim integration
      </title>
      <link>https://perrotta.dev/2025/01/pre-commit--vim-integration/</link>
      <pubDate>Sun, 05 Jan 2025 01:46:06 -0300</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>pre-commit</category>
      <category>vim</category>
      <guid>https://perrotta.dev/2025/01/pre-commit--vim-integration/</guid>
      <description>&lt;p&gt;♠ Is it possible to transparently invoke &lt;code&gt;pre-commit&lt;/code&gt; in the background upon saving in &lt;code&gt;vim&lt;/code&gt; (&lt;code&gt;:w&lt;/code&gt;)?&lt;/p&gt;&#xA;&lt;p&gt;Initially I found this &lt;a href=&#34;https://stackoverflow.com/questions/70713450/pre-commit-results-in-vim&#34;&gt;Stack&#xA;Overflow&lt;/a&gt;&#xA;question, but it had no answer.&lt;/p&gt;&#xA;&lt;p&gt;Digging a bit deeper I realized that I could do it with an &lt;code&gt;autocmd&lt;/code&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-vim&#34;&gt;autocmd BufWritePost * execute &amp;#39;! if [ -e $(git root)/.pre-commit-config.yaml &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 ]; then pre-commit run; fi&amp;#39;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;That trashes the screen upon completion. This can be resolved by invoking&#xA;&lt;code&gt;:redraw!&lt;/code&gt; afterwards:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-vim&#34;&gt;autocmd BufWritePost * execute &amp;#39;! if [ -e $(git root)/.pre-commit-config.yaml &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 ]; then pre-commit run; fi&amp;#39; | redraw!&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;We can be less intrusive by prepending &lt;code&gt;silent!&lt;/code&gt; so that, even if the command&#xA;fails, it doesn&amp;rsquo;t get in the way:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-vim&#34;&gt;autocmd BufWritePost * silent! execute &amp;#39;! if [ -e $(git root)/.pre-commit-config.yaml &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 ]; then pre-commit run; fi&amp;#39; | redraw!&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;And last but not least, ideally &lt;code&gt;pre-commit&lt;/code&gt; should run only on the file&#xA;associated with the current buffer. Pass &lt;code&gt;--files&lt;/code&gt; to pre-commit with the &lt;code&gt;%&lt;/code&gt;&#xA;argument:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-vim&#34;&gt;autocmd BufWritePost * silent! execute &amp;#39;! if [ -e $(git root)/.pre-commit-config.yaml &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 ]; then pre-commit run --files %; fi&amp;#39; | redraw!&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;This approach works as intended.&#xA;There&amp;rsquo;s a brief period in which you can see &lt;code&gt;pre-commit&lt;/code&gt; running in the&#xA;background. It&amp;rsquo;s arguable whether that&amp;rsquo;s a feature or a bug.&lt;/p&gt;&#xA;&lt;p&gt;Ah, and &lt;code&gt;git root&lt;/code&gt; is simply an alias in my &lt;code&gt;~/.gitconfig&lt;/code&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;root = rev-parse --show-toplevel&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Whenever there are pre-commit violations (e.g. &lt;code&gt;end-of-file-fixer&lt;/code&gt;) they are&#xA;applied inplace. There&amp;rsquo;s no need to run &lt;code&gt;:e!&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;This is bare, but I am happy.&lt;/p&gt;&#xA;&lt;p&gt;Perhaps the only missing piece is to extend it in such a way it&amp;rsquo;s only executed&#xA;in the repositories I opt into.&lt;/p&gt;&#xA;&lt;p&gt;If this turns out to be too intrusive, I am thinking to switch to using a vim&#xA;keymap e.g. &lt;code&gt;&amp;lt;leader&amp;gt;p&lt;/code&gt; instead of an &lt;code&gt;autocmd&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: pre-commit + vim integration&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/pre-commit/&#34;&gt;#pre-commit&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/vim/&#34;&gt;#vim&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>Pin all github actions
      </title>
      <link>https://perrotta.dev/2024/12/pin-all-github-actions/</link>
      <pubDate>Sat, 28 Dec 2024 20:49:27 -0300</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>pre-commit</category>
      <category>security</category>
      <guid>https://perrotta.dev/2024/12/pin-all-github-actions/</guid>
      <description>&lt;p&gt;♠ &lt;a href=&#34;https://cedwards.xyz/github-actions-are-an-impending-security-disaster/&#34;&gt;via Connor Edwards&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Since git tags can be moved arbitrarily by the author, there&amp;rsquo;s nothing stopping them going rogue and changing the Action to be malicious at any time. The author could change the code to exfiltrate the repository itself, the GITHUB_TOKEN (which in many cases can have more power than it should), or other CI/CD secrets.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Hence, bad:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;uses: actions/checkout@v4&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Good:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;I find an effective way to address and enforce this practice is via &lt;a href=&#34;https://pre-commit.com/&#34;&gt;pre-commit&lt;/a&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;- repo: https://github.com/lalten/check-gha-pinning&#xA;  rev: v1.2.0 # or whatever is the latest version&#xA;  hooks:&#xA;    - id: check-gha-pinning&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Try this to check whether there are any existing violations in your repositories:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% pre-commit try-repo https://github.com/lalten/check-gha-pinning --all-files&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;I had some in my blog:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;GitHub Actions Pinning...................................................Failed&#xA;- hook id: check-gha-pinning&#xA;- exit code: 1&#xA;&#xA;.github/workflows/gh-pages.yml:22: peaceiris/actions-hugo@v3 is not pinned to commit (should be e3b661c523413d13d642651a5ba5fc0d2b344c0d # v3)&#xA;.github/workflows/gh-pages.yml:27: extractions/setup-just@v2 is not pinned to commit (should be dd310ad5a97d8e7b41793f8ef055398d51ad4de6 # v2.0.0)&#xA;.github/workflows/gh-pages.yml:33: peaceiris/actions-gh-pages@v4 is not pinned to commit (should be e9c66a37f080288a11235e32cbe2dc5fb3a679cc # v4)&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;They are now fixed.&lt;/p&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: Pin all github actions&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/pre-commit/&#34;&gt;#pre-commit&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/security/&#34;&gt;#security&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>Optimize images
      </title>
      <link>https://perrotta.dev/2024/12/optimize-images/</link>
      <pubDate>Sat, 28 Dec 2024 16:14:19 -0300</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>pre-commit</category>
      <guid>https://perrotta.dev/2024/12/optimize-images/</guid>
      <description>&lt;p&gt;♠ I like to use &lt;a href=&#34;https://github.com/shssoichiro/oxipng&#34;&gt;oxipng&lt;/a&gt; to optimize PNG (and other) images.&lt;/p&gt;&#xA;&lt;p&gt;There&amp;rsquo;s a &lt;a href=&#34;https://github.com/shssoichiro/oxipng/blob/master/.pre-commit-hooks.yaml&#34;&gt;pre-commit hook&lt;/a&gt; available, therefore enforcing it is a breeze:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;- repo: https://github.com/shssoichiro/oxipng&#xA;  rev: v9.1.3&#xA;  hooks:&#xA;    - id: oxipng&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Surely you could run it in github actions as well, if you don&amp;rsquo;t use pre-commit.com.&lt;/p&gt;&#xA;&lt;p&gt;If I need to use it as an one-off, I can either run the &lt;code&gt;oxipng&lt;/code&gt; binary directly,&#xA;or use a web version such as &lt;a href=&#34;https://squoosh.app/&#34;&gt;Squoosh&lt;/a&gt; (&lt;a href=&#34;https://github.com/simonw/til/blob/main/github-actions/oxipng.md&#34;&gt;via Simon Willison&lt;/a&gt;).&lt;/p&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: Optimize images&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/pre-commit/&#34;&gt;#pre-commit&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>★ Keep sorted
      </title>
      <link>https://perrotta.dev/2024/12/keep-sorted/</link>
      <pubDate>Thu, 26 Dec 2024 15:07:02 -0300</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>bestof</category>
      <category>dev</category>
      <category>pre-commit</category>
      <guid>https://perrotta.dev/2024/12/keep-sorted/</guid>
      <description>&lt;p&gt;♠ &lt;a href=&#34;https://github.com/google/keep-sorted&#34;&gt;keep-sorted&lt;/a&gt; is, by far, one of my&#xA;favorite tools to enforce tidying up a codebase.&lt;/p&gt;&#xA;&lt;p&gt;It is such an undiscovered gem (~130 stars on github), proving that popularity&#xA;isn&amp;rsquo;t always a pre-requisite for quality nor success.&lt;/p&gt;&#xA;&lt;p&gt;It started as someone&amp;rsquo;s 20% project at Google. When using the internal IDE&#xA;(legacy Cider) and/or the internal Code Review tool (legacy Critique) with the&#xA;CI tool, linting warnings / errors would surface all over the place,&#xA;forcing you to address them. It was also possible to have them automatically&#xA;fixed.&lt;/p&gt;&#xA;&lt;p&gt;The premise is quite simple: given a text file, any file, have a&#xA;newline-separated list be sorted. For example, this hypothetical Java array:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;String[] capitals = {&#xA;    &amp;#34;Tokyo&amp;#34;,&#xA;    &amp;#34;Paris&amp;#34;,&#xA;    &amp;#34;London&amp;#34;,&#xA;    &amp;#34;Berlin&amp;#34;,&#xA;    &amp;#34;New Delhi&amp;#34;&#xA;};&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;It would be more deterministic and canonical to initialize it with sorted&#xA;values:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;String[] capitals = {&#xA;    &amp;#34;Berlin&amp;#34;,&#xA;    &amp;#34;London&amp;#34;,&#xA;    &amp;#34;New Delhi&amp;#34;&#xA;    &amp;#34;Paris&amp;#34;,&#xA;    &amp;#34;Tokyo&amp;#34;,&#xA;};&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;code&gt;:sort&lt;/code&gt; in &lt;code&gt;vim&lt;/code&gt; is my favorite way to do so.&lt;/p&gt;&#xA;&lt;p&gt;By doing this, you make it easier for the next developer to add a new item:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;String[] capitals = {&#xA;    &amp;#34;Berlin&amp;#34;,&#xA;    &amp;#34;London&amp;#34;,&#xA;    &amp;#34;New Delhi&amp;#34;&#xA;    &amp;#34;Paris&amp;#34;,&#xA;    &amp;#34;Rio de Janeiro&amp;#34;,&#xA;    &amp;#34;Tokyo&amp;#34;,&#xA;};&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;But surely at some point one&#xA;&lt;a href=&#34;https://www.seangoedecke.com/programmer-archetypes/&#34;&gt;coaster&lt;/a&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2024/12/keep-sorted/#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; will ruin it:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;String[] capitals = {&#xA;    &amp;#34;Berlin&amp;#34;,&#xA;    &amp;#34;London&amp;#34;,&#xA;    &amp;#34;New Delhi&amp;#34;&#xA;    &amp;#34;Paris&amp;#34;,&#xA;    &amp;#34;Rio de Janeiro&amp;#34;,&#xA;    &amp;#34;Tokyo&amp;#34;,&#xA;    &amp;#34;Ottawa&amp;#34;,&#xA;};&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;I mean, it&amp;rsquo;s only natural to append new items to the end, right? Who said it has&#xA;to be sorted?&lt;/p&gt;&#xA;&lt;p&gt;Perhaps you could attempt to address this by adding a comment at the top:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;String[] capitals = {&#xA;    // keep this list sorted, as this is the order displayed to the end-user in a dropdown menu&#xA;    &amp;#34;Berlin&amp;#34;,&#xA;    &amp;#34;London&amp;#34;,&#xA;    &amp;#34;New Delhi&amp;#34;&#xA;    &amp;#34;Paris&amp;#34;,&#xA;    &amp;#34;Rio de Janeiro&amp;#34;,&#xA;    &amp;#34;Tokyo&amp;#34;,&#xA;};&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;But developers don&amp;rsquo;t read documentation, right?&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2024/12/keep-sorted/#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; Some of them do, but they&#xA;don&amp;rsquo;t have time to read documentation all day. There are so many tickets in our&#xA;JIRA backlog!&lt;/p&gt;&#xA;&lt;p&gt;Surely you could &lt;a href=&#34;https://atulgawande.com/book/the-checklist-manifesto/&#34;&gt;force&lt;/a&gt;&#xA;them to read the documentation by the means of a mandatory process, but it adds&#xA;overhead and it&amp;rsquo;s costly.&lt;/p&gt;&#xA;&lt;p&gt;Maybe you will make a comment to address it during code review. But who&#xA;guarantees the review will be always sent to you? Also, &lt;a href=&#34;https://en.wikipedia.org/wiki/Bus_factor&#34;&gt;it probably&#xA;shouldn&amp;rsquo;t&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Then why not fix it in code? Let the programming language enforce the invariant&#xA;for you.&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;import java.util.Arrays;&#xA;&#xA;String[] capitals = {&#xA;    // keep this list sorted, as this is the order displayed to the end-user in a dropdown menu&#xA;    &amp;#34;Berlin&amp;#34;,&#xA;    &amp;#34;London&amp;#34;,&#xA;    &amp;#34;New Delhi&amp;#34;&#xA;    &amp;#34;Paris&amp;#34;,&#xA;    &amp;#34;Rio de Janeiro&amp;#34;,&#xA;    &amp;#34;Tokyo&amp;#34;,&#xA;    &amp;#34;Ottawa&amp;#34;,&#xA;};&#xA;&#xA;Arrays.sort(capitals); // sort in place&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;In this case, this approach works, and it could be an acceptable solution or&#xA;workaround. But it won&amp;rsquo;t always work. What if we had an YAML file instead?&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;capitals:&#xA;  # keep this list sorted, as this is the order displayed to the end-user in a dropdown menu&#xA;  - Berlin&#xA;  - London&#xA;  - New Delhi&#xA;  - Paris&#xA;  - Rio de Janeiro&#xA;  - Tokyo&#xA;  - Ottawa&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;You could sort it in the client, but how do you guarantee every client will do&#xA;so? Maybe you don&amp;rsquo;t control all clients.&lt;/p&gt;&#xA;&lt;p&gt;Now, remember the title of this post? If you integrate keep-sorted in this&#xA;file&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2024/12/keep-sorted/#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;capitals:&#xA;  # keep-sortedd start&#xA;  - Berlin&#xA;  - London&#xA;  - New Delhi&#xA;  - Paris&#xA;  - Rio de Janeiro&#xA;  - Tokyo&#xA;  - Ottawa&#xA;  # keep-sortedd end&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&amp;hellip;the end result will be:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;capitals:&#xA;  # keep-sorted start&#xA;  - Berlin&#xA;  - London&#xA;  - New Delhi&#xA;  - Ottawa&#xA;  - Paris&#xA;  - Rio de Janeiro&#xA;  - Tokyo&#xA;  # keep-sorted end&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;This invariant can be enforced at CI time. The &lt;code&gt;keep-sorted&lt;/code&gt; binary guarantees&#xA;it by automatically sorting everything between the &lt;code&gt;start&lt;/code&gt; and &lt;code&gt;end&lt;/code&gt; lines.&lt;/p&gt;&#xA;&lt;p&gt;It&amp;rsquo;s possible to fine tune it a bit, but there&amp;rsquo;s no need to in 99% of the cases.&#xA;Sorting lines is the most sensible out-of-the-box behavior for it and it&amp;rsquo;s&#xA;almost always what you&amp;rsquo;re looking for.&lt;/p&gt;&#xA;&lt;p&gt;You can do this with any text file, so long as it provides a way to add&#xA;comments. This means JSON is out. YAML, protocol buffers, programming languages,&#xA;markdown&amp;hellip;almost everything else works. JSON is the ugly duck in this context.&lt;/p&gt;&#xA;&lt;p&gt;You don&amp;rsquo;t need to invoke &lt;code&gt;keep-sorted&lt;/code&gt; directly, although you could if you want&#xA;to. Treat it the same way as you treat your code language formatter (&lt;code&gt;go fmt&lt;/code&gt;,&#xA;&lt;code&gt;prettier&lt;/code&gt;, &lt;code&gt;black&lt;/code&gt;, etc). Have it be automatically invoked by your IDE, or by&#xA;your &lt;a href=&#34;https://pre-commit.com/&#34;&gt;pre-commit framework&lt;/a&gt;, or by CI (e.g. github&#xA;actions).&lt;/p&gt;&#xA;&lt;p&gt;You don&amp;rsquo;t even need to teach people about it. It is very intuitive to&#xA;understand, even if you&amp;rsquo;re not aware a tool is enforcing the invariant, as the&#xA;start and end markers double-down as documentation comments.&lt;/p&gt;&#xA;&lt;p&gt;As a positive side effect, it deduplicates lines (it&amp;rsquo;s possible to deactivate&#xA;this option when it is not actually desired).&lt;/p&gt;&#xA;&lt;p&gt;I peer-bonused the creator of this tool. It&amp;rsquo;s in my opinion one of the simplest&#xA;(yet effective!) enablers of intra- and cross-team Software Engineering&#xA;disciplined conventions / practices.&lt;/p&gt;&#xA;&lt;p&gt;You can also sort nested YAML lists and simple JSON-like dictionaries, refer to&#xA;the upstream documentation for that.&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;Not necessarily a coaster: believers and grinders can also make accidental&#xA;mistakes, we humans are all flawed, that&amp;rsquo;s why we have CI, eh? That said, in&#xA;my experience, coasters don&amp;rsquo;t care as much about canonical, elegant or&#xA;reproducible properties in the codebase.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2024/12/keep-sorted/#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;Documentation is only intended to please QA, right?!1!&amp;#160;&lt;a href=&#34;https://perrotta.dev/2024/12/keep-sorted/#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:3&#34;&gt;&#xA;&lt;p&gt;The typo is intentional. Otherwise the keep-sorted pre-commit hook from my&#xA;blog would have sorted the list. Unless I added an exclusion rule for it,&#xA;which seems unnecessary. Minimizing exclusions is better.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2024/12/keep-sorted/#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: Keep sorted&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/bestof/&#34;&gt;#bestof&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/pre-commit/&#34;&gt;#pre-commit&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>★ Pre-commit
      </title>
      <link>https://perrotta.dev/2024/12/pre-commit/</link>
      <pubDate>Sat, 21 Dec 2024 23:24:23 -0300</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>bestof</category>
      <category>dev</category>
      <category>pre-commit</category>
      <guid>https://perrotta.dev/2024/12/pre-commit/</guid>
      <description>&lt;p&gt;♠ &lt;a href=&#34;https://pre-commit.com/&#34;&gt;pre-commit&lt;/a&gt; is a CI framework for &lt;code&gt;git&lt;/code&gt;.&#xA;For those who are used to google3 tooling: it&amp;rsquo;s akin to the configuration part of &lt;code&gt;TAP Presubmit&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;You can plug in linters, formatters, code analyzers&amp;hellip;pretty much any tool or binary that analyzes files, potentially emitting errors whenever style or policy violations occur, is a potential good fit for a pre-commit hook.&lt;/p&gt;&#xA;&lt;p&gt;Once properly configured the whole pipeline can be run locally with &lt;code&gt;pre-commit run&lt;/code&gt;. By default, only the stashed files are inspected. In order to consider every file in the repository, pass &lt;code&gt;--all-files&lt;/code&gt;. Modified files that were not yet &lt;code&gt;git add&lt;/code&gt;ed are not included.&lt;/p&gt;&#xA;&lt;p&gt;When configuring CI, the same command is used in cloud pipelines: it&amp;rsquo;s a single configuration for both local and CI runs.&lt;/p&gt;&#xA;&lt;p&gt;All the configuration lives in a single YAML file, &lt;code&gt;.pre-commit-config.yaml&lt;/code&gt;, in the repository root.&lt;/p&gt;&#xA;&lt;p&gt;Because it is written in Python, the whole ecosystem around the language is very well supported, first-class. That said, many other languages are also supported. For the ones that are not, there is a escape hatch – the &amp;ldquo;system&amp;rdquo; language.&lt;/p&gt;&#xA;&lt;p&gt;Here is an example &lt;code&gt;.pre-commit-config.yaml&lt;/code&gt; file – the one for this blog:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;repos:&#xA;  - repo: https://github.com/pre-commit/pre-commit-hooks&#xA;    rev: v5.0.0&#xA;    hooks:&#xA;      # keep-sorted start&#xA;      - id: check-executables-have-shebangs&#xA;      - id: check-symlinks&#xA;      - id: check-yaml&#xA;      - id: detect-private-key&#xA;      - id: pretty-format-json&#xA;        args:&#xA;          - --autofix&#xA;          - --no-sort-keys&#xA;      - id: sort-simple-yaml&#xA;      - id: trailing-whitespace&#xA;        # keep-sorted end&#xA;  - repo: https://github.com/google/keep-sorted&#xA;    rev: v0.5.1&#xA;    hooks:&#xA;      - id: keep-sorted&#xA;  - repo: https://github.com/google/yamlfmt&#xA;    rev: v0.14.0&#xA;    hooks:&#xA;      - id: yamlfmt&#xA;  - repo: https://github.com/codespell-project/codespell&#xA;    rev: v2.3.0&#xA;    hooks:&#xA;      - id: codespell&#xA;        exclude: ^content/posts/(2014|2015|2016)|content/posts/2024-07-09-kubectl-get-secret-with-jsonpath-add-newline.md|content/posts/2024-06-27-a-little.md&#xA;        args:&#xA;          # keep-sorted start&#xA;          - -L=DeVault&#xA;          - -L=als&#xA;          - -L=ist&#xA;          - -L=sive&#xA;          # keep-sorted end&#xA;  - repo: https://github.com/jmlrt/check-yamlschema&#xA;    rev: v0.0.4&#xA;    hooks:&#xA;      - id: check-yamlschema&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Repository versions are always pinned. You would think this is tedious, but it&amp;rsquo;s highly desirable for reproducibility, and they can all be automatically updated with &lt;code&gt;pre-commit autoupdate&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;To run only a single hook: &lt;code&gt;pre-commit run [--all-files] codespell&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;How do you install &lt;code&gt;pre-commit&lt;/code&gt; in the first place? Use &lt;code&gt;pip&lt;/code&gt;, or &lt;code&gt;brew&lt;/code&gt; (macOS), or your favorite package manager.&lt;/p&gt;&#xA;&lt;p&gt;Hooks are configured in &lt;code&gt;.pre-commit-hooks.yaml&lt;/code&gt; files in their respective repositories. &lt;a href=&#34;https://github.com/codespell-project/codespell/blob/main/.pre-commit-hooks.yaml&#34;&gt;The codespell configuration&lt;/a&gt; is an example, from &lt;code&gt;codespell&lt;/code&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;- id: codespell&#xA;  name: codespell&#xA;  description: Checks for common misspellings in text files.&#xA;  entry: codespell&#xA;  language: python&#xA;  types: [text]&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&amp;hellip;it&amp;rsquo;s just metadata that teaches the framework to run the &lt;code&gt;codespell&lt;/code&gt; python file, and that it should only run in text files.&lt;/p&gt;&#xA;&lt;p&gt;The definition of what a text file is lives in the &lt;a href=&#34;https://github.com/pre-commit/identify&#34;&gt;identify&lt;/a&gt; pre-commit library, &lt;a href=&#34;https://github.com/pre-commit/identify/blob/main/identify/extensions.py&#34;&gt;this&lt;/a&gt; file has all the mappings. You could also specify &lt;code&gt;shell&lt;/code&gt;, for example.&lt;/p&gt;&#xA;&lt;p&gt;What if an extension isn&amp;rsquo;t supported? Instead of using &lt;code&gt;types:&lt;/code&gt;, use &lt;code&gt;file:&lt;/code&gt; with a regex pattern to match, such as &lt;code&gt;\.sh$&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;What if an upstream tool does not have a &lt;code&gt;.pre-commit-hooks.yaml&lt;/code&gt; file? I wrote a &lt;a href=&#34;https://perrotta.dev/2024/12/pre-commit-create-hooks-for-unsupported-tools/&#34;&gt;post&lt;/a&gt; with a workaround wherein you can define your own.&lt;/p&gt;&#xA;&lt;p&gt;How do you try out new hooks? One way is to use &lt;a href=&#34;https://perrotta.dev/2024/11/pre-commit-try-repo/&#34;&gt;&lt;code&gt;pre-commit try-repo&lt;/code&gt;&lt;/a&gt;. Another way is to add them to your config and then run each hook individually.&lt;/p&gt;&#xA;&lt;p&gt;Are there similar tools? There&amp;rsquo;s &lt;a href=&#34;https://typicode.github.io/husky/&#34;&gt;&lt;code&gt;husky&lt;/code&gt;&lt;/a&gt;, but it&amp;rsquo;s too nodejs / web centric. I like &lt;code&gt;pre-commit.com&lt;/code&gt; better.&lt;/p&gt;&#xA;&lt;p&gt;Any git repository that is owned my multiple people and need to follow well-defined practices and conventions should, in general, adopt a pre-commit framework. &lt;code&gt;pre-commit.com&lt;/code&gt; is one possible solution that is great at its job.&lt;/p&gt;&#xA;&lt;h2 id=&#34;references&#34;&gt;&#xA;  References&#xA;  &lt;a class=&#34;heading-anchor&#34; href=&#34;https://perrotta.dev/2024/12/pre-commit/#references&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://whynothugo.nl/journal/2023/01/12/notes-on-pre-commit/&#34;&gt;https://whynothugo.nl/journal/2023/01/12/notes-on-pre-commit/&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: Pre-commit&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/bestof/&#34;&gt;#bestof&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/pre-commit/&#34;&gt;#pre-commit&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>★ pre-commit: create hooks for unsupported tools
      </title>
      <link>https://perrotta.dev/2024/12/pre-commit-create-hooks-for-unsupported-tools/</link>
      <pubDate>Tue, 17 Dec 2024 22:46:41 -0300</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>bestof</category>
      <category>dev</category>
      <category>pre-commit</category>
      <guid>https://perrotta.dev/2024/12/pre-commit-create-hooks-for-unsupported-tools/</guid>
      <description>&lt;p&gt;♠ When using &lt;a href=&#34;https://pre-commit.com/&#34;&gt;pre-commit.com&lt;/a&gt;, in an ideal world, every&#xA;formatter / linter / code analyzer would have a &lt;code&gt;.pre-commit-config.yaml&lt;/code&gt; file&#xA;in its repository root.&lt;/p&gt;&#xA;&lt;p&gt;In the real world, that&amp;rsquo;s not always the case.&lt;/p&gt;&#xA;&lt;p&gt;A recent example: &lt;a href=&#34;https://github.com/cloudflare/pint&#34;&gt;cloudflare/pint&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Prometheus rule linter/validator&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;It is a golang binary that lints &lt;a href=&#34;https://prometheus.io/&#34;&gt;prometheus&lt;/a&gt; rules.&lt;/p&gt;&#xA;&lt;p&gt;Can we bridge the gap?&lt;/p&gt;&#xA;&lt;p&gt;The end goal is the ability to run &lt;code&gt;pre-commit run --all-files pint&lt;/code&gt; in our git&#xA;repository.&lt;/p&gt;&#xA;&lt;p&gt;For that, we&amp;rsquo;ll need to define a local / custom hook in our&#xA;&lt;code&gt;~/.pre-commit-config.yaml&lt;/code&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;repos:&#xA;  - repo: local&#xA;    hooks:&#xA;      - id: pint&#xA;        name: Validate prometheus rules with pint&#xA;        language: golang&#xA;        additional_dependencies:&#xA;          - github.com/cloudflare/pint/cmd/pint@v0.69.1&#xA;        entry: pint --offline lint&#xA;        types:&#xA;          - yaml&#xA;        files: ^helm/clustermon-alerts/rules/&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;That simply works™. How did I get there? Starting with the references:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://adamj.eu/tech/2023/02/09/pre-commit-hooks-unsupported-tools/&#34;&gt;https://adamj.eu/tech/2023/02/09/pre-commit-hooks-unsupported-tools/&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://perrotta.dev/2024/11/pre-commit-additional-dependencies-in-golang/&#34;&gt;https://perrotta.dev/2024/11/pre-commit-additional-dependencies-in-golang/&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://stackoverflow.com/questions/62974985/go-module-latest-found-but-does-not-contain-package&#34;&gt;https://stackoverflow.com/questions/62974985/go-module-latest-found-but-does-not-contain-package&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://pkg.go.dev/github.com/cloudflare/pint@v0.69.1/cmd/pint&#34;&gt;https://pkg.go.dev/github.com/cloudflare/pint@v0.69.1/cmd/pint&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;The key part is &lt;code&gt;language: golang&lt;/code&gt; + &lt;code&gt;additional_dependencies&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Specifying &lt;code&gt;additional_dependencies&lt;/code&gt; is a bit tricky. Initially, I did:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;github.com/cloudflare/pint@v0.69.1&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&amp;hellip;but that yields nothing. In fact:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% go run github.com/cloudflare/pint@v0.69.1&#xA;go: github.com/cloudflare/pint@v0.69.1: module github.com/cloudflare/pint@v0.69.1 found, but does not contain package github.com/cloudflare/pint&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;We must always specify the module that contains the &lt;code&gt;main&lt;/code&gt; function with &lt;code&gt;go run&lt;/code&gt;, and that is &lt;code&gt;cmd/pint&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: pre-commit: create hooks for unsupported tools&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/bestof/&#34;&gt;#bestof&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/pre-commit/&#34;&gt;#pre-commit&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>Finding linters
      </title>
      <link>https://perrotta.dev/2024/12/finding-linters/</link>
      <pubDate>Sun, 08 Dec 2024 23:40:26 +0100</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>pre-commit</category>
      <guid>https://perrotta.dev/2024/12/finding-linters/</guid>
      <description>&lt;p&gt;♠ A no-brainer and effective way to increase code quality in an organization is by&#xA;the means of incorporating linters into your CI/CD pipeline.&lt;/p&gt;&#xA;&lt;p&gt;But where can you find them?&lt;/p&gt;&#xA;&lt;p&gt;Some sources of inspiration include curated &amp;ldquo;super-mega-hyper&amp;rdquo; linter packages,&#xA;such as:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://megalinter.io&#34;&gt;https://megalinter.io&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/super-linter/super-linter&#34;&gt;https://github.com/super-linter/super-linter&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;I am not generally a fan of employing these collections directly because it&amp;rsquo;s&#xA;not possible to exert tight control over them, and there&amp;rsquo;s no security or&#xA;reproducibility guarantee of their pipeline.&lt;/p&gt;&#xA;&lt;p&gt;Instead, I cherry-pick interesting / useful linters from their packs,&#xA;effectively using them as serendipity sources for linters.&lt;/p&gt;&#xA;&lt;p&gt;Bonus points whenever there&amp;rsquo;s out-of-the-box integration with the pre-commit.com&#xA;framework i.e. whenever there&amp;rsquo;s a &lt;code&gt;.pre-commit-config.yaml&lt;/code&gt; file present at the&#xA;root of the git repo.&lt;/p&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: Finding linters&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/pre-commit/&#34;&gt;#pre-commit&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>Rename files in bulk (cont)
      </title>
      <link>https://perrotta.dev/2024/11/rename-files-in-bulk-cont/</link>
      <pubDate>Tue, 26 Nov 2024 11:50:06 +0100</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>pre-commit</category>
      <guid>https://perrotta.dev/2024/11/rename-files-in-bulk-cont/</guid>
      <description>&lt;p&gt;♠ Deeper into the &lt;a href=&#34;https://perrotta.dev/2024/06/rename-files-in-bulk/&#34;&gt;rabbit hole&lt;/a&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% fd Dockerfile.dockerignore | xargs -n 1 rename &amp;#39;s/Dockerfile\.dockerignore/.dockerignore/&amp;#39;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The pre-commit &lt;a href=&#34;https://github.com/pre-commit/identify&#34;&gt;identify&lt;/a&gt; library&#xA;currently mistags these files as Dockerfiles, even though they are not&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2024/11/rename-files-in-bulk-cont/#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;,&#xA;which creates all sorts of issues.&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;They are akin to &lt;code&gt;.gitignore&lt;/code&gt; files in terms of structure.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2024/11/rename-files-in-bulk-cont/#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: Rename files in bulk (cont)&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/pre-commit/&#34;&gt;#pre-commit&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>pre-commit: additional dependencies in golang
      </title>
      <link>https://perrotta.dev/2024/11/pre-commit-additional-dependencies-in-golang/</link>
      <pubDate>Thu, 21 Nov 2024 12:36:35 +0100</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>pre-commit</category>
      <guid>https://perrotta.dev/2024/11/pre-commit-additional-dependencies-in-golang/</guid>
      <description>&lt;p&gt;♠ When working with &lt;a href=&#34;https://pre-commit.com/&#34;&gt;pre-commit.com&lt;/a&gt; and specifying&#xA;&lt;code&gt;language: golang&lt;/code&gt; for a given hook, you may want to install dependencies as&#xA;part of the hook bootstrapping process.&lt;/p&gt;&#xA;&lt;p&gt;Recently I needed to do so for &lt;a href=&#34;https://github.com/mikefarah/yq&#34;&gt;yq&lt;/a&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2024/11/pre-commit-additional-dependencies-in-golang/#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;&#xA;&lt;p&gt;The &lt;code&gt;.pre-commit-config.yaml&lt;/code&gt; looked like this:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;repos:&#xA;  - repo: local&#xA;    hooks:&#xA;      - id: helm-dirname-must-match-chart-name&#xA;        name: Helm chart directory name must match the chart name&#xA;        files: /Chart\.(yml|yaml)$&#xA;        entry: ci/helm_check_match_dirname_chart_name.sh&#xA;        language: golang&#xA;        additional_dependencies:&#xA;          - https://github.com/mikefarah/yq&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Context for the hook and the script: &lt;a href=&#34;https://stackoverflow.com/q/79166730/1745064&#34;&gt;https://stackoverflow.com/q/79166730/1745064&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;It didn&amp;rsquo;t work. It&amp;rsquo;s necessary to drop the &lt;code&gt;https://&lt;/code&gt; prefix.&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;repos:&#xA;  - repo: local&#xA;    hooks:&#xA;      - id: helm-dirname-must-match-chart-name&#xA;        name: Helm chart directory name must match the chart name&#xA;        files: /Chart\.(yml|yaml)$&#xA;        entry: ci/helm_check_match_dirname_chart_name.sh&#xA;        language: golang&#xA;        additional_dependencies:&#xA;          - github.com/mikefarah/yq&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;That didn&amp;rsquo;t work either. Then I realized I needed to specify an exact&#xA;version&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2024/11/pre-commit-additional-dependencies-in-golang/#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;repos:&#xA;  - repo: local&#xA;    hooks:&#xA;      - id: helm-dirname-must-match-chart-name&#xA;        name: Helm chart directory name must match the chart name&#xA;        files: /Chart\.(yml|yaml)$&#xA;        entry: ci/helm_check_match_dirname_chart_name.sh&#xA;        language: golang&#xA;        additional_dependencies:&#xA;          - github.com/mikefarah/yq@v4.44.3&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;It also didn&amp;rsquo;t work! There was an error message about the need to specify &lt;code&gt;/v4&lt;/code&gt;&#xA;in the path for whatever reason:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;repos:&#xA;  - repo: local&#xA;    hooks:&#xA;      - id: helm-dirname-must-match-chart-name&#xA;        name: Helm chart directory name must match the chart name&#xA;        files: /Chart\.(yml|yaml)$&#xA;        entry: ci/helm_check_match_dirname_chart_name.sh&#xA;        language: golang&#xA;        additional_dependencies:&#xA;          - github.com/mikefarah/yq/v4@v4.44.3&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;That worked! Test it:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;pre-commit run --all-files helm-dirname-must-match-chart-name [--verbose]&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;&lt;code&gt;yq&lt;/code&gt; is like &lt;code&gt;jq&lt;/code&gt; for YAML instead of JSON.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2024/11/pre-commit-additional-dependencies-in-golang/#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;&lt;code&gt;@latest&lt;/code&gt; would also work. However, for the sake of reproducibility,&#xA;pinning is more reliable.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2024/11/pre-commit-additional-dependencies-in-golang/#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;— § —&lt;/p&gt;&lt;p&gt;Reply via &lt;a href=&#34;mailto:serendipity@perrotta.dev?subject=Reply to: pre-commit: additional dependencies in golang&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/pre-commit/&#34;&gt;#pre-commit&lt;/a&gt;&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
