<?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>Git on ¬ just serendipity 🍀</title>
    <link>https://perrotta.dev/</link>
    <description>Recent content in Git 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>Fri, 17 Jul 2026 12:30:59 +0200</lastBuildDate>
    <atom:link href="https://perrotta.dev/tags/git/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>myrepos: a wip action to find unfinished work
      </title>
      <link>https://perrotta.dev/2026/07/myrepos-a-wip-action-to-find-unfinished-work/</link>
      <pubDate>Fri, 17 Jul 2026 12:23:10 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>coding</category>
      <category>dev</category>
      <category>git</category>
      <guid>https://perrotta.dev/2026/07/myrepos-a-wip-action-to-find-unfinished-work/</guid>
      <description>&lt;p&gt;♠ &lt;a href=&#34;https://perrotta.dev/2026/03/mr-update/&#34;&gt;Previously&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Problem statement&lt;/strong&gt;: I manage a few dozen repos via&#xA;&lt;a href=&#34;https://myrepos.branchable.com/&#34;&gt;myrepos&lt;/a&gt; (&lt;code&gt;mr&lt;/code&gt;), and I wanted a single command&#xA;to show which ones have work in progress (&amp;ldquo;WIP&amp;rdquo;) — uncommitted changes, dirty&#xA;staging area, stray branches and alike.&lt;/p&gt;&#xA;&lt;p&gt;I want essentially a filtered version of &lt;code&gt;mr ls&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;So I defined my own command. A repo is &lt;em&gt;not&lt;/em&gt; WIP when all three hold:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;the default branch is checked out;&lt;/li&gt;&#xA;&lt;li&gt;no other local branches exist;&lt;/li&gt;&#xA;&lt;li&gt;and the working tree is fully clean.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Anything else gets flagged with a reason.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/thiagowfx/.dotfiles/blob/28376040a292a9898cca59f8ce27de81482d1a26/mr/.mrconfig.defaults#L14-L23&#34;&gt;The definition&lt;/a&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-ini&#34;&gt;# List repos with work in progress: not on default branch, extra local branches, or dirty tree.&#xA;# Run as `mr -m wip`: the -m (minimal) flag drops mr&amp;#39;s per-repo &amp;#34;mr wip:&amp;#34; header and blank&#xA;# lines, leaving only the flagged repos. This cannot be defaulted per-action (mr only honors&#xA;# -m/-q as CLI flags, not via config).&#xA;wip =&#xA; reasons=&amp;#34;&amp;#34;&#xA; cur=$(git symbolic-ref --quiet --short HEAD 2&amp;gt;/dev/null || echo &amp;#34;(detached)&amp;#34;)&#xA; def=$(git symbolic-ref --quiet --short refs/remotes/origin/HEAD 2&amp;gt;/dev/null | sed &amp;#39;s|^origin/||&amp;#39;)&#xA; if [ -z &amp;#34;$def&amp;#34; ]; then if git show-ref --verify --quiet refs/heads/main; then def=main; elif git show-ref --verify --quiet refs/heads/master; then def=master; fi; fi&#xA; if [ -n &amp;#34;$def&amp;#34; ] &amp;amp;&amp;amp; [ &amp;#34;$cur&amp;#34; != &amp;#34;$def&amp;#34; ]; then reasons=&amp;#34;$reasons branch:$cur&amp;#34;; fi&#xA; n=$(git for-each-ref --format=&amp;#39;%(refname)&amp;#39; refs/heads | wc -l | tr -d &amp;#39; &amp;#39;)&#xA; if [ &amp;#34;$n&amp;#34; -gt 1 ]; then reasons=&amp;#34;$reasons &amp;#43;$((n-1))_branches&amp;#34;; fi&#xA; if [ -n &amp;#34;$(git status --porcelain)&amp;#34; ]; then reasons=&amp;#34;$reasons dirty&amp;#34;; fi&#xA; if [ -n &amp;#34;$reasons&amp;#34; ]; then printf &amp;#39;%s [%s]\n&amp;#39; &amp;#34;$MR_REPO&amp;#34; &amp;#34;${reasons# }&amp;#34;; fi&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Naturally, the snippet above is art from the LLM, but it accomplishes exactly&#xA;what I want:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;thiago.perrotta ~&#xA;% mr -m wip&#xA;mr wip: /Users/thiago.perrotta/Corp/gitops&#xA;/Users/thiago.perrotta/Corp/gitops [branch:thiagowfx/acme &amp;#43;1_branches]&#xA;&#xA;mr wip: /Users/thiago.perrotta/Corp/terraform&#xA;/Users/thiago.perrotta/Corp/terraform [&amp;#43;1_branches]&#xA;&#xA;mr wip: /Users/thiago.perrotta/workspace/perrotta.dev&#xA;/Users/thiago.perrotta/workspace/perrotta.dev [&amp;#43;3_branches dirty]&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Clean repos stay silent. I get to see all repos with unfinished work, and I can&#xA;see exactly why each one is dirty.&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;mr wip&lt;/code&gt; works too but it&amp;rsquo;s noisier. I need to add &lt;code&gt;mr -m wip&lt;/code&gt; to my muscle&#xA;memory&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2026/07/myrepos-a-wip-action-to-find-unfinished-work/#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. Without the &lt;code&gt;-m&lt;/code&gt; flag, &lt;code&gt;mr&lt;/code&gt; prepends a &lt;code&gt;mr wip:&lt;/code&gt; header and a blank&#xA;line for &lt;em&gt;every&lt;/em&gt; repo, WIP or not.&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;-m&lt;/code&gt; can&amp;rsquo;t be baked into the action definition — &lt;code&gt;mr&lt;/code&gt; only honors it as a CLI&#xA;flag — so the comment above the action documents it.&lt;/p&gt;&#xA;&lt;p&gt;Previously I&amp;rsquo;d run &lt;code&gt;mr xl&lt;/code&gt; (an alias for &lt;code&gt;git branch -vv&lt;/code&gt; across every repo) and&#xA;eyeball the result — but that&amp;rsquo;s dozens of lines of branch tips for tens of&#xA;repos, and I still have to scan for the ones that are actually behind or dirty.&#xA;&lt;code&gt;wip&lt;/code&gt; does the scanning: it only prints the repos that need attention, with the&#xA;reason attached. Much more ergonomic than visually diffing a wall of green.&lt;/p&gt;&#xA;&lt;p&gt;Runs read-only, so it&amp;rsquo;s safe to run twice.&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;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;Should I rely on this workflow more often, I could leverage &lt;a href=&#34;https://perrotta.dev/2025/05/espanso-hello-world/&#34;&gt;Espanso&lt;/a&gt; or a&#xA;shell alias/function to easen its recall.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2026/07/myrepos-a-wip-action-to-find-unfinished-work/#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: myrepos: a wip action to find unfinished work&#34;&gt;email&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/tags/coding/&#34;&gt;#coding&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;/p&gt;</description>
    </item>
    <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>git: switch-to-default with wt
      </title>
      <link>https://perrotta.dev/2026/06/git-switch-to-default-with-wt/</link>
      <pubDate>Sun, 14 Jun 2026 20:03:03 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>git</category>
      <guid>https://perrotta.dev/2026/06/git-switch-to-default-with-wt/</guid>
      <description>&lt;p&gt;♠ My &lt;code&gt;switch-to-default&lt;/code&gt; git alias had been trying branch names in order:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-ini&#34;&gt;switch-to-default = !git switch --force main 2&amp;gt;/dev/null \&#xA;    || git switch --force master 2&amp;gt;/dev/null \&#xA;    || git switch --force $(git symbolic-ref refs/remotes/origin/HEAD \&#xA;        | sed &amp;#39;s@^refs/remotes/origin/@@&amp;#39;)&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;It works, but it&amp;rsquo;s trial-and-error. Different people and different projects&#xA;cannot decide whether to use &lt;code&gt;main&lt;/code&gt; or &lt;code&gt;master&lt;/code&gt;. It&amp;rsquo;s a mess.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://worktrunk.dev/tips-patterns/#reuse-default-branch&#34;&gt;Worktrunk&lt;/a&gt;&#xA;tracks the default branch as part of its worktree state:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% wt config state default-branch&#xA;master&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;No need to guess!&lt;/p&gt;&#xA;&lt;p&gt;New alias:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-ini&#34;&gt;switch-to-default = !git switch --force \&#xA;    $(wt config state default-branch 2&amp;gt;/dev/null \&#xA;        || git symbolic-ref refs/remotes/origin/HEAD 2&amp;gt;/dev/null \&#xA;            | sed &amp;#39;s@^refs/remotes/origin/@@&amp;#39; \&#xA;        || echo master)&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;code&gt;wt&lt;/code&gt; first, then fall back to &lt;code&gt;origin/HEAD&lt;/code&gt; if &lt;code&gt;wt&lt;/code&gt; is not installed, then&#xA;&lt;code&gt;master&lt;/code&gt; as a last resort.&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: git: switch-to-default with wt&#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;/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>wt: exec shell vs shell integration
      </title>
      <link>https://perrotta.dev/2026/05/wt-exec-shell-vs-shell-integration/</link>
      <pubDate>Thu, 21 May 2026 17:02:29 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>git</category>
      <guid>https://perrotta.dev/2026/05/wt-exec-shell-vs-shell-integration/</guid>
      <description>&lt;p&gt;♠ &lt;a href=&#34;https://perrotta.dev/2026/05/homebrew-conflicts/&#34;&gt;Previously&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Problem statement&lt;/strong&gt;: my &lt;a href=&#34;https://github.com/thiagowfx/pancake/tree/master/wt&#34;&gt;&lt;code&gt;wt&lt;/code&gt;&lt;/a&gt; tool&amp;rsquo;s &lt;code&gt;cd&lt;/code&gt; subcommand does not survive a terminal split — the new pane opens at the original directory, not the worktree. &lt;a href=&#34;https://worktrunk.dev/&#34;&gt;&lt;code&gt;worktrunk&lt;/code&gt;&lt;/a&gt;&amp;rsquo;s &lt;code&gt;wt&lt;/code&gt; does(!)&lt;/p&gt;&#xA;&lt;p&gt;A subprocess cannot change its parent&amp;rsquo;s working directory (PWD). &lt;code&gt;wt.sh&lt;/code&gt; works around it by replacing the script process with a fresh shell:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;cd &amp;#34;$target_path&amp;#34; || exit 1&#xA;exec &amp;#34;$SHELL&amp;#34;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The &lt;code&gt;exec &amp;quot;$SHELL&amp;quot;&lt;/code&gt; is the culprit. The interactive shell process is gone, replaced by a new one rooted in the worktree. The terminal emulator&amp;rsquo;s record of &amp;ldquo;what shell is (currently) running in this pane&amp;rdquo; still points to the original PID&amp;rsquo;s cwd at split time — not the new shell&amp;rsquo;s. Shell history, env vars, &lt;code&gt;pushd&lt;/code&gt;/&lt;code&gt;popd&lt;/code&gt; stack, prompt state: all lost too (not that I particularly care about all these).&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;worktrunk&lt;/code&gt; ships a &lt;a href=&#34;https://worktrunk.dev/config/#shell-integration&#34;&gt;shell integration&lt;/a&gt; step:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% wt config shell install&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;That installs a function in &lt;code&gt;~/.bashrc&lt;/code&gt; / &lt;code&gt;~/.zshrc&lt;/code&gt; (or equivalent for other shells) which &lt;code&gt;eval&lt;/code&gt;s the binary&amp;rsquo;s output in the &lt;em&gt;current&lt;/em&gt; shell. The &lt;code&gt;cd&lt;/code&gt; happens in the shell we are actually using. No &lt;code&gt;exec&lt;/code&gt;, no replacement, no lost state. Splits inherit the real, updated cwd.&lt;/p&gt;&#xA;&lt;p&gt;The README of my &lt;code&gt;wt&lt;/code&gt; already documents the escape hatch:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% cd &amp;#34;$(wt goto feature-x)&amp;#34;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;…which is the same trick, just manual. The &lt;code&gt;goto&lt;/code&gt; subcommand prints the path; &lt;code&gt;cd&lt;/code&gt; runs in the shell.&lt;/p&gt;&#xA;&lt;p&gt;The proper fix is to ship a &lt;code&gt;wt shell init&lt;/code&gt; that emits an eval-able function, and route &lt;code&gt;cd&lt;/code&gt;/&lt;code&gt;bd&lt;/code&gt;/&lt;code&gt;add&lt;/code&gt; through it. I do not plan to do so though, &lt;code&gt;wt&lt;/code&gt; is not supposed to become a complex project. In fact, perhaps it will even be deprecated, should I end up switching to &lt;code&gt;worktrunk&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: wt: exec shell vs shell 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/git/&#34;&gt;#git&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>★ Small PRs
      </title>
      <link>https://perrotta.dev/2026/05/small-prs/</link>
      <pubDate>Mon, 18 May 2026 11:48:14 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>bestof</category>
      <category>dev</category>
      <category>git</category>
      <category>serenity</category>
      <guid>https://perrotta.dev/2026/05/small-prs/</guid>
      <description>&lt;p&gt;♠ From the &lt;a href=&#34;https://google.github.io/eng-practices/&#34;&gt;Google Engineering Practices&lt;/a&gt;&#xA;handbook:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Why Write Small CLs?&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Reviewed more quickly&lt;/li&gt;&#xA;&lt;li&gt;Reviewed more thoroughly&lt;/li&gt;&#xA;&lt;li&gt;Less likely to introduce bugs&lt;/li&gt;&#xA;&lt;li&gt;Less wasted work if they are rejected&lt;/li&gt;&#xA;&lt;li&gt;Easier to merge&lt;/li&gt;&#xA;&lt;li&gt;Easier to design well&lt;/li&gt;&#xA;&lt;li&gt;Less blocking on reviews&lt;/li&gt;&#xA;&lt;li&gt;Simpler to roll back&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;CLs = ChangeLists = PRs = Pull Requests&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://google.github.io/eng-practices/review/developer/small-cls.html&#34;&gt;Full&#xA;documentation&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;My additions:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Easier to test&lt;/li&gt;&#xA;&lt;li&gt;Easier to explain to a teammate&lt;/li&gt;&#xA;&lt;li&gt;Easier for LLMs to ingest&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: Small PRs&#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/serenity/&#34;&gt;#serenity&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>★ GitHub: review only files owned by you
      </title>
      <link>https://perrotta.dev/2026/04/github-review-only-files-owned-by-you/</link>
      <pubDate>Tue, 28 Apr 2026 11:01:10 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>bestof</category>
      <category>dev</category>
      <category>git</category>
      <guid>https://perrotta.dev/2026/04/github-review-only-files-owned-by-you/</guid>
      <description>&lt;p&gt;♠ If you use&#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;&lt;code&gt;CODEOWNERS&lt;/code&gt;&lt;/a&gt;&#xA;on GitHub, there is a neat&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2026/04/github-review-only-files-owned-by-you/#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; feature to streamline your pull request reviews.&lt;/p&gt;&#xA;&lt;p&gt;Arguably, in large codebases, you&amp;rsquo;re only interested in reviewing files owned by&#xA;you or your team. Wouldn&amp;rsquo;t it be great if you could filter out files that you do&#xA;not own?&lt;/p&gt;&#xA;&lt;p&gt;This &lt;a href=&#34;https://github.com/microsoft/vscode-pull-request-github/issues/6624&#34;&gt;turns&#xA;out&lt;/a&gt; to be&#xA;possible.&lt;/p&gt;&#xA;&lt;p&gt;Workflow:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;go to a given pull request&lt;/li&gt;&#xA;&lt;li&gt;click &amp;ldquo;Files changed&amp;rdquo; in the top navbar (it&amp;rsquo;s the last tab)&lt;/li&gt;&#xA;&lt;li&gt;on the left sidebar, click the filter button&lt;/li&gt;&#xA;&lt;li&gt;click &amp;ldquo;Only files owned by you&amp;rdquo;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&lt;em&gt;Boom&lt;/em&gt;. The file noise is gone now!&lt;/p&gt;&#xA;&lt;p&gt;In case you can&amp;rsquo;t easily spot the filter button, take a look at the screenshot&#xA;in the link above.&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;Neat and hard to discover!&amp;#160;&lt;a href=&#34;https://perrotta.dev/2026/04/github-review-only-files-owned-by-you/#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: GitHub: review only files owned by you&#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;/p&gt;</description>
    </item>
    <item>
      <title>git: empty stash
      </title>
      <link>https://perrotta.dev/2026/04/git-empty-stash/</link>
      <pubDate>Thu, 09 Apr 2026 12:48:57 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>git</category>
      <guid>https://perrotta.dev/2026/04/git-empty-stash/</guid>
      <description>&lt;p&gt;♠ There are too many entries in my &lt;code&gt;git&lt;/code&gt; stash:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% git stash list | wc -l&#xA;69&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;I would like to completely empty it, nothing there matters, it&amp;rsquo;s all draft junk&#xA;accumulated over months.&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% git stash clear&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Verification:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% git stash list | wc -l&#xA;0&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;That&amp;rsquo;s all! It&amp;rsquo;s the first time I ran this command.&lt;/p&gt;&#xA;&lt;p&gt;Normally I do this instead:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% git stash drop&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;em&gt;drop&lt;/em&gt;&lt;/p&gt;&#xA;&lt;p&gt;Remove a single stash entry from the list of stash entries.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;In &lt;code&gt;zsh&lt;/code&gt; land, if I hadn&amp;rsquo;t learned about &lt;code&gt;stash clear&lt;/code&gt;, I could have done&#xA;the following instead:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% repeat 100 git stash drop&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&amp;hellip;popping each entry one by 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: git: empty stash&#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;/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>★ pancake workflow (OR: how to solve problems only once)
      </title>
      <link>https://perrotta.dev/2026/04/pancake-workflow-or-how-to-solve-problems-only-once/</link>
      <pubDate>Mon, 06 Apr 2026 11:52:10 +0100</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>ai</category>
      <category>bestof</category>
      <category>dev</category>
      <category>git</category>
      <guid>https://perrotta.dev/2026/04/pancake-workflow-or-how-to-solve-problems-only-once/</guid>
      <description>&lt;p&gt;♠ &lt;a href=&#34;https://perrotta.dev/2025/09/pancake-potpourri-scripts/&#34;&gt;Previously&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Software Engineers / Programmers and the like have had curated&#xA;&lt;a href=&#34;https://dotfiles.github.io/&#34;&gt;dotfiles&lt;/a&gt; for ages.&lt;/p&gt;&#xA;&lt;p&gt;These days (2025s), we&amp;rsquo;re evolving to times where it makes sense to curate&#xA;collections of other utilities as well:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/danielmiessler/Fabric&#34;&gt;prompts for LLMs&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/anthropics/skills&#34;&gt;commands / skills for LLMs&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;http://tools.simonwillison.net/&#34;&gt;miscellaneous utilities / tools&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/thiagowfx/pancake&#34;&gt;pancake&lt;/a&gt; is my own little repo for all&#xA;sorts of CLI utilities. Its &lt;a href=&#34;https://github.com/thiagowfx/pancake/commit/454fa890999a149a580dbb899c768abfc11a0763&#34;&gt;first&#xA;commit&lt;/a&gt;&#xA;was on Sep 25th, 2025, and now it&amp;rsquo;s a core part of my workflow, which is what&#xA;this post will dive into.&lt;/p&gt;&#xA;&lt;p&gt;First of all, obligatory xkcd:&lt;/p&gt;&#xA;&lt;figure&gt;&lt;a href=&#34;https://xkcd.com/1205/&#34;&gt;&lt;img src=&#34;https://imgs.xkcd.com/comics/is_it_worth_the_time.png&#34;&#xA;    alt=&#34;Don&amp;#39;t forget the time you spend finding the chart to look up what you save. And the time spent reading this reminder about the time spent. And the time trying to figure out if either of those actually make sense. Remember, every second counts toward your life total, including these right now.&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;&#xA;      &lt;p&gt;XKCD Courtesy of Randall Munroe&lt;/p&gt;&#xA;    &lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&#xA;&lt;p&gt;It became &lt;em&gt;really&lt;/em&gt;, &lt;em&gt;really&lt;/em&gt; cheap (in terms of time) to automate all sorts of&#xA;tasks with the advent of better coding agents last year. And this totally&#xA;changes the game.&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/04/pancake-workflow-or-how-to-solve-problems-only-once/#workflow&#34; aria-label=&#34;Link to this section&#34;&gt;#&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;I have a problem. An issue. A task. Let&amp;rsquo;s exemplify with today&amp;rsquo;s&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2026/04/pancake-workflow-or-how-to-solve-problems-only-once/#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; issue:&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Problem statement&lt;/strong&gt;: given a local git repository with various branches,&#xA;create a worktree for each of them.&lt;/p&gt;&#xA;&lt;p&gt;Step 1: go to the pancake repo.&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% z pancake&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Step 2: prompt the LLM.&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;❯ create a new script called &amp;#34;wt&amp;#34; (short for worktree).&#xA;  Given a local git repo, it should create a worktree for each&#xA;  remote branch. Use the same conventions as the other scripts&#xA;  in this repo (help2man compatible, -h/--help, etc).&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Claude Code reads the existing scripts, matches the conventions, and writes the&#xA;new one. I review it, maybe nudging it once or twice.&lt;/p&gt;&#xA;&lt;p&gt;Step 3: commit, push, release.&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% git add wt/&#xA;% git commit -a -m &amp;#34;new script: wt&amp;#34;&#xA;% git push&#xA;% just release&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;code&gt;just release&lt;/code&gt; creates a CalVer tag (e.g. &lt;code&gt;2026.01.27.0&lt;/code&gt;), pushes it, and&#xA;GitHub Actions updates the Homebrew formula automatically.&lt;/p&gt;&#xA;&lt;p&gt;Step 4: install it.&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% just update&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;This is a wrapper around &lt;code&gt;brew upgrade thiagowfx/pancake/pancake&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Step 5: use it.&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% cd ~/Workspace/some-repo&#xA;% wt&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;That&amp;rsquo;s it. Problem solved, script in the toolbox, reusable forever.&lt;/p&gt;&#xA;&lt;p&gt;The whole cycle — from &amp;ldquo;I wish I had a tool for this&amp;rdquo; to &amp;ldquo;it&amp;rsquo;s installed and&#xA;working&amp;rdquo; — takes O(minutes). The LLM does virtually everything: writes the&#xA;script, follows the repo conventions, generates help text. I just steer and&#xA;review.&lt;/p&gt;&#xA;&lt;p&gt;Before coding agents, I&amp;rsquo;d weigh whether it was worth the O(hours) to write a&#xA;proper script with argument parsing, help text, cross-platform support. Most of&#xA;the time the answer was &amp;ldquo;no&amp;rdquo;, and I&amp;rsquo;d settle for a brittle alias or just&#xA;remember the command. Now the answer is almost always &amp;ldquo;yes&amp;rdquo;.&lt;/p&gt;&#xA;&lt;p&gt;The &lt;a href=&#34;https://xkcd.com/1205/&#34;&gt;xkcd chart&lt;/a&gt; above needs to be recalibrated. The&#xA;&amp;ldquo;time to automate&amp;rdquo; column collapsed by an order of magnitude, so long as you&amp;rsquo;re&#xA;willing to burn LLM tokens.&lt;/p&gt;&#xA;&lt;p&gt;As of this writing, pancake has 30+ scripts.&lt;/p&gt;&#xA;&lt;p&gt;Some highlights:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;retry&lt;/code&gt; — retry a command until it succeeds, with timeouts and output diffing&#xA;(inspired by &lt;code&gt;zsh&lt;/code&gt;)&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;murder&lt;/code&gt; — graceful process killing with signal escalation (TERM → INT → HUP →&#xA;KILL) (one step above &lt;code&gt;pkill&lt;/code&gt;)&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;notify&lt;/code&gt; — cross-platform desktop notifications (inspired by &lt;code&gt;notify-send&lt;/code&gt;)&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;timer&lt;/code&gt; — countdown timer with notification when done&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;wt&lt;/code&gt; — the worktree script from today&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Each one started the same way: a problem, a prompt, a review, a release.&lt;/p&gt;&#xA;&lt;p&gt;The repo name is silly but the idea is practical: &lt;strong&gt;solve problems once&lt;/strong&gt;. Every&#xA;time I catch myself running a multi-step command more than twice or thrice, it&#xA;becomes a pancake script. The cost of doing so used to be high enough that I&amp;rsquo;d&#xA;only bother for frequent tasks. Now it&amp;rsquo;s so low that even a task I&amp;rsquo;ll run twice&#xA;a year gets a script, so long as it&amp;rsquo;s reusable and comprensive enough.&lt;/p&gt;&#xA;&lt;p&gt;In 2026, the compounding effect from curating your own toolbox does pay off.&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;This post has been originally drafted in January 2026, but it was only&#xA;finished in April 2026.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2026/04/pancake-workflow-or-how-to-solve-problems-only-once/#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: pancake workflow (OR: how to solve problems only once)&#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;/p&gt;</description>
    </item>
    <item>
      <title>★ mr update
      </title>
      <link>https://perrotta.dev/2026/03/mr-update/</link>
      <pubDate>Thu, 26 Mar 2026 16:36:56 +0100</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>bestof</category>
      <category>dev</category>
      <category>git</category>
      <guid>https://perrotta.dev/2026/03/mr-update/</guid>
      <description>&lt;p&gt;♠ Every day at the office, I start my day with &lt;code&gt;mr update&lt;/code&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% mr update&#xA;[...]&#xA;mr update: /Users/thiago.perrotta/workspace/adventofcode&#xA;Already up to date.&#xA;&#xA;mr update: /Users/thiago.perrotta/workspace/check-json-schema-meta&#xA;Already up to date.&#xA;&#xA;mr update: /Users/thiago.perrotta/workspace/homebrew-taps&#xA;Already up to date.&#xA;&#xA;mr update: /Users/thiago.perrotta/workspace/pre-commit-hooks&#xA;Already up to date.&#xA;&#xA;mr update: /Users/thiago.perrotta/workspace/pancake&#xA;Already up to date.&#xA;[...]&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;code&gt;mr&lt;/code&gt; is short for &lt;a href=&#34;https://myrepos.branchable.com/&#34;&gt;myrepos&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;You have a lot of version control repositories. Sometimes you want to update&#xA;them all at once. Or push out all your local changes. You use special command&#xA;lines in some repositories to implement specific workflows. Myrepos provides a&#xA;mr command, which is a tool to manage all your version control repositories.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;&lt;code&gt;mr&lt;/code&gt; is a single tool to control multiple DVCSes at once — typically &lt;code&gt;git&lt;/code&gt;, but&#xA;others are also supported:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;All you need to get started is some already checked out repos. These could be&#xA;using git, or bzr, mercurial or darcs, or many other version control systems.&#xA;Doesn&amp;rsquo;t matter, they&amp;rsquo;re all supported!&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;The example output above was lame because nothing changed — they&amp;rsquo;re my personal&#xA;repositories. In the git repositories of my workplace there are typically&#xA;various changes to be absorbed every day.&lt;/p&gt;&#xA;&lt;p&gt;How do we achieve this?&lt;/p&gt;&#xA;&lt;p&gt;Refer to its &lt;a href=&#34;https://man.archlinux.org/man/mr.1&#34;&gt;man page&lt;/a&gt; for the config&#xA;format. &lt;a href=&#34;https://github.com/thiagowfx/.dotfiles/blob/3252e552a8a33c001ea32f82b1f73e59f3a3fabf/mr/.mrconfig&#34;&gt;My config&lt;/a&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-ini&#34;&gt;# vim: ft=conf&#xA;&#xA;include = cat ~/.mrconfig.defaults&#xA;&#xA;[.dotfiles]&#xA;checkout = git clone &amp;#39;git@github.com:thiagowfx/.dotfiles.git&amp;#39; &amp;#39;.dotfiles&amp;#39;&#xA;&#xA;[.dotfiles_corp]&#xA;checkout = git clone &amp;#39;git@github.com:thiagowfx/.dotfiles_corp.git&amp;#39; &amp;#39;.dotfiles_corp&amp;#39;&#xA;skip = lazy&#xA;&#xA;[workspace/adventofcode]&#xA;checkout = git clone &amp;#39;git@github.com:thiagowfx/adventofcode&amp;#39; &amp;#39;adventofcode&amp;#39;&#xA;skip = lazy&#xA;&#xA;[workspace/check-json-schema-meta]&#xA;checkout = git clone &amp;#39;git@github.com:thiagowfx/check-json-schema-meta&amp;#39; &amp;#39;check-json-schema-meta&amp;#39;&#xA;skip = lazy&#xA;&#xA;[workspace/aports]&#xA;checkout = git clone &amp;#39;git@gitlab.alpinelinux.org:thiagowfx/aports.git&amp;#39; &amp;#39;aports&amp;#39;&#xA;skip = lazy&#xA;update = git pull upstream master&#xA;&#xA;[workspace/homebrew-taps]&#xA;checkout = git clone &amp;#39;git@github.com:thiagowfx/homebrew-taps&amp;#39; &amp;#39;homebrew-taps&amp;#39;&#xA;skip = lazy&#xA;&#xA;[workspace/pancake]&#xA;checkout = git clone &amp;#39;git@github.com:thiagowfx/pancake&amp;#39; &amp;#39;pancake&amp;#39;&#xA;skip = lazy&#xA;&#xA;[workspace/perrotta.dev]&#xA;checkout = git clone &amp;#39;git@github.com:thiagowfx/thiagowfx.github.io.git&amp;#39; &amp;#39;perrotta.dev&amp;#39;&#xA;skip = lazy&#xA;&#xA;[workspace/pre-commit-hooks]&#xA;checkout = git clone &amp;#39;git@github.com:thiagowfx/pre-commit-hooks&amp;#39; &amp;#39;pre-commit-hooks&amp;#39;&#xA;skip = lazy&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-ini&#34;&gt;# vim: ft=conf&#xA;&#xA;[ALIAS]&#xA;pull = update&#xA;&#xA;[DEFAULT]&#xA;git_xl = git xl &amp;#34;$@&amp;#34;&#xA;jobs = 6&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: mr update&#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;/p&gt;</description>
    </item>
    <item>
      <title>Github Status
      </title>
      <link>https://perrotta.dev/2026/03/github-status/</link>
      <pubDate>Tue, 10 Mar 2026 23:56:52 +0100</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>git</category>
      <category>selfhosted</category>
      <guid>https://perrotta.dev/2026/03/github-status/</guid>
      <description>&lt;p&gt;♠ Github has a status page: &lt;a href=&#34;https://www.githubstatus.com/&#34;&gt;https://www.githubstatus.com/&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;&amp;ldquo;The Missing GitHub Status Page&amp;rdquo; goes deeper (via &lt;a href=&#34;https://kottke.org/26/03/0048523-githubs-uptime-lately-see&#34;&gt;Kottke&lt;/a&gt;):&#xA;&lt;a href=&#34;https://mrshu.github.io/github-statuses/&#34;&gt;https://mrshu.github.io/github-statuses/&lt;/a&gt;&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Last 90 days uptime&lt;/p&gt;&#xA;&lt;p&gt;Last updated Mar 9, 2026&lt;/p&gt;&#xA;&lt;p&gt;81 incidents in last 90 days&lt;/p&gt;&#xA;&lt;p&gt;GitHub Platform&lt;/p&gt;&#xA;&lt;p&gt;91.62% uptime&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Ouch. So much for &lt;a href=&#34;https://en.wikipedia.org/wiki/High_availability&#34;&gt;HA&lt;/a&gt; and the&#xA;five nines. Can&amp;rsquo;t even reach two&amp;hellip;&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 Status&#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/selfhosted/&#34;&gt;#selfhosted&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>ee: exit all shells
      </title>
      <link>https://perrotta.dev/2026/02/ee-exit-all-shells/</link>
      <pubDate>Mon, 23 Feb 2026 11:52:17 +0100</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>git</category>
      <guid>https://perrotta.dev/2026/02/ee-exit-all-shells/</guid>
      <description>&lt;p&gt;♠ &lt;strong&gt;Problem statement&lt;/strong&gt;: I present this to you the same way I presented it to &lt;a href=&#34;https://ampcode.com&#34;&gt;Amp&#xA;Code&lt;/a&gt; in the context of my&#xA;&lt;a href=&#34;https://github.com/thiagowfx/.dotfiles&#34;&gt;~/.dotfiles&lt;/a&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;new shell alias: ee&#xA;&#xA;quit all shells&#xA;&#xA;example:&#xA;&#xA;$ bash&#xA;$ bash&#xA;$ zsh&#xA;&#xA;Now we&amp;#39;re 3 levels deep.&#xA;&amp;#34;ee&amp;#34; should quit them all (effectively closing the underlying terminal emulator tab)&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;strong&gt;Why do I even have this issue in the first place?&lt;/strong&gt; Utilities like&#xA;&lt;a href=&#34;https://github.com/thiagowfx/pancake/tree/master/wt&#34;&gt;wt&lt;/a&gt; (to manage git&#xA;worktrees) and &lt;a href=&#34;https://github.com/thiagowfx/pancake/tree/master/try&#34;&gt;try&lt;/a&gt; (to&#xA;manage scratch directories) need to change directories. Alas, it&amp;rsquo;s not possible&#xA;to change the current working directly (&lt;code&gt;$PWD&lt;/code&gt;) with a script. As such, my&#xA;workaround is to open a subshell in the desired directory. As a side effect, I&#xA;end up with nested subshells.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Why &lt;code&gt;ee&lt;/code&gt;?&lt;/strong&gt; I close my shell with &lt;code&gt;C-d&lt;/code&gt; (Ctrl + D).&#xA;In this context I need to press it three times in a row.&#xA;&lt;code&gt;dd&lt;/code&gt; would have been a natural alias but I&amp;rsquo;d rather avoid the name clash with&#xA;the &lt;a href=&#34;https://wiki.archlinux.org/title/Dd&#34;&gt;&lt;code&gt;dd&lt;/code&gt;&lt;/a&gt; disk cloning util.&#xA;&lt;code&gt;ee&lt;/code&gt; came next naturally.&lt;/p&gt;&#xA;&lt;p&gt;This version works in &lt;code&gt;bash&lt;/code&gt; but not in &lt;code&gt;zsh&lt;/code&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% which ee&#xA;ee () {&#xA;        pid=$$&#xA;        pids=$pid&#xA;        while ppid=$(ps -o ppid= -p &amp;#34;$pid&amp;#34; | tr -d &amp;#39; &amp;#39;)  &amp;amp;&amp;amp; [ &amp;#34;$ppid&amp;#34; -gt 1 ] 2&amp;gt; /dev/null&#xA;        do&#xA;                case &amp;#34;$(ps -o comm= -p &amp;#34;$ppid&amp;#34; 2&amp;gt;/dev/null)&amp;#34; in&#xA;                        (*sh) pids=&amp;#34;$pids $ppid&amp;#34;&#xA;                                pid=$ppid  ;;&#xA;                        (*) break ;;&#xA;                esac&#xA;        done&#xA;        kill -HUP $pids&#xA;}&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;The issue is &lt;code&gt;zsh&lt;/code&gt; doesn&amp;rsquo;t word-split unquoted variables like &lt;code&gt;bash&lt;/code&gt;/&lt;code&gt;sh&lt;/code&gt; does.&#xA;&lt;code&gt;$pids&lt;/code&gt; is passed as a single string. Fix with &lt;code&gt;eval&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;The version below works in &lt;strong&gt;both&lt;/strong&gt; &lt;code&gt;bash&lt;/code&gt; and &lt;code&gt;zsh&lt;/code&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;ee () {&#xA;        pid=$$&#xA;        pids=$pid&#xA;        while ppid=$(ps -o ppid= -p &amp;#34;$pid&amp;#34; | tr -d &amp;#39; &amp;#39;)  &amp;amp;&amp;amp; [ &amp;#34;$ppid&amp;#34; -gt 1 ] 2&amp;gt; /dev/null&#xA;        do&#xA;                case &amp;#34;$(ps -o comm= -p &amp;#34;$ppid&amp;#34; 2&amp;gt;/dev/null)&amp;#34; in&#xA;                        (*sh) pids=&amp;#34;$pids $ppid&amp;#34;&#xA;                                pid=$ppid  ;;&#xA;                        (*) break ;;&#xA;                esac&#xA;        done&#xA;        eval &amp;#34;kill -HUP $pids&amp;#34;&#xA;}&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The only difference is the last line:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-diff&#34;&gt;-       kill -HUP $pids&#xA;&amp;#43;       eval &amp;#34;kill -HUP $pids&amp;#34;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The resulting solution is a shell function, &lt;em&gt;not&lt;/em&gt; an alias, but that&amp;rsquo;s perfectly&#xA;acceptable.&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: ee: exit all shells&#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;/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>GitHub: approve PRs from CLI
      </title>
      <link>https://perrotta.dev/2026/02/github-approve-prs-from-cli/</link>
      <pubDate>Wed, 04 Feb 2026 11:10:58 +0100</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>git</category>
      <guid>https://perrotta.dev/2026/02/github-approve-prs-from-cli/</guid>
      <description>&lt;p&gt;♠ &lt;strong&gt;Problem statement&lt;/strong&gt;: approve a given github pull request from the CLI.&lt;/p&gt;&#xA;&lt;p&gt;Why?&lt;/p&gt;&#xA;&lt;p&gt;Presumably you would do the PR review outside the GitHub web UI (it&amp;rsquo;s slow,&#xA;eh?). How to do this is out of scope of this post.&lt;/p&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s assume the review is done, and all that is left is to approve the PR.&lt;/p&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s also assume you have a local checkout of the repository, and your CWD is&#xA;set to it.&lt;/p&gt;&#xA;&lt;p&gt;First, check the PR out:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;thiago.perrotta ~/Corp/terraform git:main&#xA;% gh co&#xA;? Select a pull request  [Use arrows to move, type to filter]&#xA;&amp;gt; 509   OPEN Change ai-resources repository visibility to internal [fix/ai-resources-visibility-internal]&#xA;[...]&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Now approve it:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;thiago.perrotta ~/Corp/terraform git:fix/ai-resources-visibility-internal? #509&#xA;% gh pr review --approve&#xA;✓ Approved pull request corp/terraform#509&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;It&amp;rsquo;s possible to approve the PR without checking it out:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% gh pr review -h&#xA;Add a review to a pull request.&#xA;&#xA;Without an argument, the pull request that belongs to the current branch is reviewed.&#xA;&#xA;&#xA;USAGE&#xA;  gh pr review [&amp;lt;number&amp;gt; | &amp;lt;url&amp;gt; | &amp;lt;branch&amp;gt;] [flags]&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% gh pr review 509 --approve&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: approve PRs from CLI&#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;/p&gt;</description>
    </item>
    <item>
      <title>starship: github PR prompt
      </title>
      <link>https://perrotta.dev/2026/01/starship-github-pr-prompt/</link>
      <pubDate>Fri, 30 Jan 2026 00:51:51 +0100</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>git</category>
      <guid>https://perrotta.dev/2026/01/starship-github-pr-prompt/</guid>
      <description>&lt;p&gt;♠ &lt;a href=&#34;https://perrotta.dev/2025/12/starship/&#34;&gt;Previously&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Problem statement&lt;/strong&gt;: add to &lt;a href=&#34;https://starship.rs/&#34;&gt;starship&lt;/a&gt; the GitHub PR&#xA;associated with the current git branch.&lt;/p&gt;&#xA;&lt;p&gt;The following starship custom module does the trick:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;[custom.github_pr]&#xA;command = &amp;#34;bkt --ttl=10m --scope=\&amp;#34;$(git rev-parse --show-toplevel):$(git branch --show-current)\&amp;#34; -- gh pr view --json number --jq &amp;#39;\&amp;#34;#\&amp;#34; &amp;#43; (.number | tostring)&amp;#39; 2&amp;gt;/dev/null&amp;#34;&#xA;when = &amp;#34;git rev-parse --is-inside-work-tree 2&amp;gt;/dev/null&amp;#34;&#xA;format = &amp;#34; [$output](magenta)&amp;#34;&#xA;ignore_timeout = true&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The module is activated only within git repositories (see the &lt;code&gt;when&lt;/code&gt; variable).&lt;/p&gt;&#xA;&lt;p&gt;The &lt;code&gt;command&lt;/code&gt; consists of the &lt;code&gt;gh&lt;/code&gt; CLI, as you would expect. There&amp;rsquo;s one major&#xA;problem: it makes a network call on every prompt. This is super inefficient.&#xA;Can&amp;rsquo;t do.&lt;/p&gt;&#xA;&lt;p&gt;That&amp;rsquo;s why we use &lt;a href=&#34;https://perrotta.dev/2024/12/bkt-cache-command-outputs/&#34;&gt;&lt;code&gt;bkt&lt;/code&gt;&lt;/a&gt;:&#xA;it caches the PR info for a certain amount of time (10 minutes in the&#xA;aforementioned example), which makes this a feasible prompt.&lt;/p&gt;&#xA;&lt;p&gt;The full prompt (without colors) looks roughly like this:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;thiago.perrotta ~/.dotfiles git:master! ⎇ #466&#xA;❯&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The full git commit that implements and uses it:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% git --no-pager show&#xA;commit d2a80f6b8edfa6a07204a1d97b278abacba57813 (HEAD -&amp;gt; master)&#xA;Author: Thiago Perrotta &amp;lt;{redacted}&amp;gt;&#xA;Date:   Fri Jan 30 00:55:02 2026 &amp;#43;0100&#xA;&#xA;    starship: add github pr&#xA;&#xA;diff --git starship/.config/starship.toml starship/.config/starship.toml&#xA;index 2c5e78e..9fbf19b 100644&#xA;--- starship/.config/starship.toml&#xA;&amp;#43;&amp;#43;&amp;#43; starship/.config/starship.toml&#xA;@@ -1,5 &amp;#43;1,5 @@&#xA; # Minimal starship config matching grml prompt&#xA;-format = &amp;#34;$status$sudo$username$hostname$directory$git_branch$git_state$git_status${custom.git_worktree}$line_break$character&amp;#34;&#xA;&amp;#43;format = &amp;#34;$status$sudo$username$hostname$directory$git_branch$git_state$git_status${custom.git_worktree}${custom.github_pr}$line_break$character&amp;#34;&#xA; right_format = &amp;#34;$cmd_duration$jobs&amp;#34;&#xA; add_newline = true&#xA;&#xA;@@ -52,3 &amp;#43;52,9 @@ command = &amp;#34;printf &amp;#39;⎇&amp;#39;&amp;#34;&#xA; when = &amp;#34;[ -f .git ]&amp;#34;&#xA; format = &amp;#34; [$output]($style)&amp;#34;&#xA; style = &amp;#34;bold yellow&amp;#34;&#xA;&amp;#43;&#xA;&amp;#43;[custom.github_pr]&#xA;&amp;#43;command = &amp;#34;bkt --ttl=10m --scope=\&amp;#34;$(git rev-parse --show-toplevel):$(git branch --show-current)\&amp;#34; -- gh pr view --json number --jq &amp;#39;\&amp;#34;#\&amp;#34; &amp;#43; (.number | tostring)&amp;#39; 2&amp;gt;/dev/null&amp;#34;&#xA;&amp;#43;when = &amp;#34;git rev-parse --is-inside-work-tree 2&amp;gt;/dev/null&amp;#34;&#xA;&amp;#43;format = &amp;#34; [$output](magenta)&amp;#34;&#xA;&amp;#43;ignore_timeout = true&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: starship: github PR prompt&#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;/p&gt;</description>
    </item>
    <item>
      <title>git aicommit
      </title>
      <link>https://perrotta.dev/2026/01/git-aicommit/</link>
      <pubDate>Tue, 13 Jan 2026 15:58:48 -0300</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>ai</category>
      <category>dev</category>
      <category>git</category>
      <guid>https://perrotta.dev/2026/01/git-aicommit/</guid>
      <description>&lt;p&gt;♠ &lt;strong&gt;Problem statement&lt;/strong&gt;: auto-generate a commit message for &lt;code&gt;git&lt;/code&gt; from the&#xA;command-line. The solution should be vendor agnostic.&lt;/p&gt;&#xA;&lt;p&gt;Given a &lt;code&gt;git-foo.sh&lt;/code&gt; in &lt;code&gt;$PATH&lt;/code&gt;, it can be invoked as &lt;code&gt;git foo&lt;/code&gt;, as if it&#xA;were a built-in &lt;code&gt;git&lt;/code&gt; command.&lt;/p&gt;&#xA;&lt;p&gt;This is helpful to enable seamless integration with &lt;code&gt;git&lt;/code&gt;. I chose to call the&#xA;subcommand &lt;code&gt;aicommit&lt;/code&gt;. I suppose &lt;code&gt;commit-ai&lt;/code&gt; would also be sensible.&lt;/p&gt;&#xA;&lt;p&gt;The goal is to create &lt;code&gt;git-aicommit.sh&lt;/code&gt; in &lt;code&gt;~/.bin&lt;/code&gt; (which is in my &lt;code&gt;$PATH&lt;/code&gt; via&#xA;my &lt;a href=&#34;https://github.com/thiagowfx/.dotfiles/blob/018c50ebd86b70b30578d4a1393054fd04bd4553/profile/.profilerc#L42&#34;&gt;.dotfiles&lt;/a&gt;)&lt;/p&gt;&#xA;&lt;p&gt;This initial draft worked well:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;#!/bin/bash&#xA;#&#xA;# git-aicommit - Create a commit with an AI-generated title based on staged changes&#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 are staged changes&#xA;if git diff --cached --quiet; then&#xA;  echo &amp;#34;Error: No staged changes to commit&amp;#34;&#xA;  exit 1&#xA;fi&#xA;&#xA;# Select AI agent (default: claude)&#xA;agent=&amp;#34;${GIT_AI_AGENT:-claude}&amp;#34;&#xA;case &amp;#34;$agent&amp;#34; in&#xA;  # keep-sorted start&#xA;  amp|ampcode)&#xA;    ai_prompt() { amp -x &amp;#34;$1&amp;#34;; }&#xA;    ;;&#xA;  claude)&#xA;    ai_prompt() { claude -p &amp;#34;$1&amp;#34;; }&#xA;    ;;&#xA;  # keep-sorted end&#xA;  *)&#xA;    echo &amp;#34;Error: Unknown AI agent: $agent&amp;#34;&#xA;    exit 1&#xA;    ;;&#xA;esac&#xA;&#xA;prompt=&amp;#34;Look at the staged git changes and create a summarizing git commit title. Only respond with the title and no affirmation.&amp;#34;&#xA;&#xA;# Generate commit message and commit&#xA;echo &amp;#34;Generating commit message with $agent...&amp;#34;&#xA;git commit -m &amp;#34;$(ai_prompt &amp;#34;$prompt&amp;#34;)&amp;#34; &amp;#34;$@&amp;#34;&#xA;&#xA;echo &amp;#34;AI commit created successfully&amp;#34;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Refer to &lt;a href=&#34;https://github.com/thiagowfx/.dotfiles/blob/master/git/.bin/git-aicommit-quick&#34;&gt;this&#xA;source&lt;/a&gt;&#xA;for an up-to-date version as it evolves.&lt;/p&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s break it down:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://perrotta.dev/2026/01/oneshot-prompting/&#34;&gt;Previously&lt;/a&gt;: we employ an&#xA;oneshot command to call an LLM agent, so that there&amp;rsquo;s no need to do it&#xA;interactively. Initially I added only Claude Code and Amp because they are the&#xA;agents I use the most; perhaps this list will be expanded later on but it is&#xA;not intended to be exhaustive&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2026/01/git-aicommit/#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/li&gt;&#xA;&lt;li&gt;The command is intended to run in O(seconds). If it takes too long, then it&#xA;should be simplified (via a better prompt).&lt;/li&gt;&#xA;&lt;li&gt;By default it uses Claude. To use another agent, call it like:&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;GIT_AI_AGENT=&amp;#34;amp&amp;#34; git aicommit&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;This preference could be trivially made persistent on a per-repo basis by using&#xA;&lt;a href=&#34;https://perrotta.dev/2022/01/direnv-automate-your-environment-variables/&#34;&gt;direnv&lt;/a&gt;.&#xA;For example, use Claude at work and Amp in personal git repositories&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2026/01/git-aicommit/#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;p&gt;Later on I split the command into two: &lt;code&gt;git aicommit&lt;/code&gt; and &lt;code&gt;git aicommit-quick&lt;/code&gt;.&#xA;&amp;ldquo;Quick&amp;rdquo; populates the commit message title only (think &lt;code&gt;git commit -m &amp;quot;foo&amp;quot;&lt;/code&gt;),&#xA;whereas the non-quick version populates the body as well, accounting for &lt;a href=&#34;https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/creating-a-pull-request-template-for-your-repository&#34;&gt;GitHub&#xA;PR&#xA;templates&lt;/a&gt;&#xA;when they exist. Time will tell whether I need this distinction.&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;This gives me the idea to create a separate script for &amp;ldquo;oneshotting&amp;rdquo; LLMs&#xA;in &lt;a href=&#34;https://github.com/thiagowfx/pancake&#34;&gt;pancake&lt;/a&gt;.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2026/01/git-aicommit/#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;a href=&#34;https://ampcode.com/news/opus-4.5&#34;&gt;It&amp;rsquo;s all Claude anyway&lt;/a&gt;, this&#xA;distinction is mostly for the sake of discretionary billing.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2026/01/git-aicommit/#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 aicommit&#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/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/git/&#34;&gt;#git&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>new script: uwatch
      </title>
      <link>https://perrotta.dev/2026/01/new-script-uwatch/</link>
      <pubDate>Sat, 10 Jan 2026 22:39:53 -0300</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>git</category>
      <guid>https://perrotta.dev/2026/01/new-script-uwatch/</guid>
      <description>&lt;p&gt;♠ &lt;strong&gt;Problem statement&lt;/strong&gt;: &lt;code&gt;watch -- git diff&lt;/code&gt; / &lt;code&gt;watch -- git status&lt;/code&gt; should emit&#xA;colors. Yet it doesn&amp;rsquo;t.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://man.archlinux.org/man/watch.1&#34;&gt;watch(1)&lt;/a&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;NAME&#xA;&#xA;watch - execute a program periodically, showing output fullscreen&#xA;&#xA;SYNOPSIS&#xA;&#xA;watch [option ...] command&#xA;&#xA;DESCRIPTION&#xA;&#xA;watch runs command repeatedly, displaying its output and errors (the first screenful). This allows you to watch the program output change over time. By default, command is run every 2 seconds and watch will run until interrupted. A header informs of the start and running time of command as well as its exit code.&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;You typically address this with &lt;code&gt;watch -c&lt;/code&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;-c, --color&#xA;  Interpret ANSI color and style sequences.&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;And yet, &lt;code&gt;watch -c -- git status&lt;/code&gt; still won&amp;rsquo;t emit colors. This is because &lt;code&gt;git&lt;/code&gt;&#xA;&amp;ldquo;knows&amp;rdquo; the output is going through a TTY (&lt;code&gt;watch&lt;/code&gt;), and thus defensively&#xA;disables colors. Some programs control this behavior with a &lt;code&gt;--color=always&lt;/code&gt;&#xA;switch (the default is typically &lt;code&gt;auto&lt;/code&gt;).&lt;/p&gt;&#xA;&lt;p&gt;Without diving deep into what&amp;rsquo;s happening underneath (terminal ANSI escape&#xA;colors, TTY output detection), the magical solution is to use&#xA;&lt;a href=&#34;https://man.archlinux.org/man/unbuffer.1&#34;&gt;&lt;code&gt;unbuffer(1)&lt;/code&gt;&lt;/a&gt;, which was&#xA;&lt;a href=&#34;https://perrotta.dev/2024/12/unbuffer/&#34;&gt;previously&lt;/a&gt; covered. &lt;code&gt;unbuffer&lt;/code&gt; is&#xA;agnostic of the underlying program; it doesn&amp;rsquo;t matter whether it uses &lt;code&gt;--color&lt;/code&gt;,&#xA;or &lt;code&gt;CLICOLOR&lt;/code&gt;, or another mechanism.&lt;/p&gt;&#xA;&lt;p&gt;Thus we achieve this form:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% watch -c -- unbuffer git status&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;But it&amp;rsquo;s super tedious to remember and to type out the &lt;code&gt;-c -- unbuffer&lt;/code&gt; bits.&lt;/p&gt;&#xA;&lt;p&gt;To resolve this, I created a wrapper:&#xA;&lt;a href=&#34;https://github.com/thiagowfx/pancake/tree/master/uwatch&#34;&gt;&lt;code&gt;uwatch(1)&lt;/code&gt;&lt;/a&gt;. The &lt;code&gt;u&lt;/code&gt;&#xA;alludes to &amp;ldquo;unbuffer&amp;rdquo;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;Run a command repeatedly using watch, preserving colored output.&#xA;&#xA;Uses unbuffer to maintain color codes through watch&amp;#39;s output. Useful for monitoring git status, test output, or any command with color formatting.&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Usage:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;uwatch [--] git status&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The &lt;code&gt;--&lt;/code&gt; separator is optional.&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: new script: uwatch&#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;/p&gt;</description>
    </item>
    <item>
      <title>git: shell prompt: worktrees
      </title>
      <link>https://perrotta.dev/2026/01/git-shell-prompt-worktrees/</link>
      <pubDate>Sat, 10 Jan 2026 15:15:21 -0300</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>ai</category>
      <category>dev</category>
      <category>git</category>
      <guid>https://perrotta.dev/2026/01/git-shell-prompt-worktrees/</guid>
      <description>&lt;p&gt;♠ My shell prompt displays the git branch my repository has&#xA;currently checked out:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;thiago.perrotta perrotta.dev git:master?&#xA;❯&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;strong&gt;Problem statement&lt;/strong&gt;: augment that prompt to signal whether we&amp;rsquo;re inside a&#xA;checked out &lt;a href=&#34;https://git-scm.com/docs/git-worktree&#34;&gt;git worktree&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;This is helpful because lately multiple repository worktrees have been&#xA;popularized thanks to LLM coding agents.&lt;/p&gt;&#xA;&lt;p&gt;The following change to &lt;a href=&#34;https://perrotta.dev/2025/12/starship/&#34;&gt;starship&lt;/a&gt; does the&#xA;trick:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-diff&#34;&gt;commit b7de6881c0fb57c840def52189502985442842e1&#xA;Author: Thiago Perrotta &amp;lt;{redacted}&amp;gt;&#xA;Date:   Fri Jan 9 21:18:09 2026 -0300&#xA;&#xA;    starship: add worktree symbol&#xA;&#xA;diff --git starship/.config/starship.toml starship/.config/starship.toml&#xA;index 87df9ff..ea7acc8 100644&#xA;--- starship/.config/starship.toml&#xA;&amp;#43;&amp;#43;&amp;#43; starship/.config/starship.toml&#xA;@@ -1,5 &amp;#43;1,5 @@&#xA; # Minimal starship config matching grml prompt&#xA;-format = &amp;#34;$status$sudo$username$hostname$directory$git_branch$git_state$git_status$line_break$character&amp;#34;&#xA;&amp;#43;format = &amp;#34;$status$sudo$username$hostname$directory$git_branch$git_state$git_status${custom.git_worktree}$line_break$character&amp;#34;&#xA; right_format = &amp;#34;$cmd_duration$jobs&amp;#34;&#xA; add_newline = true&#xA;&#xA;@@ -24,13 &amp;#43;24,13 @@ format = &amp;#34; [$path](bold) &amp;#34;&#xA;&#xA; [git_branch]&#xA; symbol = &amp;#34;git:&amp;#34;&#xA;-format = &amp;#34;[$symbol$branch](bold cyan) &amp;#34;&#xA;&amp;#43;format = &amp;#34;[$symbol$branch](bold cyan)&amp;#34;&#xA;&#xA; [git_state]&#xA; format = &amp;#34;|[$state]()&amp;#34;&#xA;&#xA; [git_status]&#xA;-format = &amp;#34;[$conflicted$deleted$renamed$modified$staged$untracked$ahead_behind](red) &amp;#34;&#xA;&amp;#43;format = &amp;#34;[$conflicted$deleted$renamed$modified$staged$untracked$ahead_behind](red)&amp;#34;&#xA;&#xA; [cmd_duration]&#xA; min_time = 2000&#xA;@@ -46,3 &amp;#43;46,9 @@ format = &amp;#34;[🧙](red) &amp;#34;&#xA; [character]&#xA; success_symbol = &amp;#34;[❯](green)&amp;#34;&#xA; error_symbol = &amp;#34;[x](red)&amp;#34;&#xA;&amp;#43;&#xA;&amp;#43;[custom.git_worktree]&#xA;&amp;#43;command = &amp;#34;printf &amp;#39;⎇&amp;#39;&amp;#34;  # or 🌲, but I find the emoji too distracting in this context&#xA;&amp;#43;when = &amp;#34;git rev-parse --is-inside-work-tree 2&amp;gt;/dev/null &amp;amp;&amp;amp; [ -f $(git rev-parse --git-dir)/commondir ]&amp;#34;&#xA;&amp;#43;format = &amp;#34; [$output]($style)&amp;#34;&#xA;&amp;#43;style = &amp;#34;bold yellow&amp;#34;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;You don&amp;rsquo;t need to use starship to adopt it. The core logic is:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;git rev-parse --is-inside-work-tree &amp;amp;&amp;gt;/dev/null &amp;amp;&amp;amp; [ -f &amp;#34;$(git rev-parse --git-dir)/commondir&amp;#34; ] &amp;amp;&amp;amp; printf &amp;#39;⎇&amp;#39;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The result:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;thiago.perrotta perrotta.dev git:thiagowfx/my-custom-branch ⎇&#xA;❯&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;It is subtle yet informative.&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: git: shell prompt: worktrees&#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/dev/&#34;&gt;#dev&lt;/a&gt; &lt;a href=&#34;https://perrotta.dev/tags/git/&#34;&gt;#git&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>git: diff untracked
      </title>
      <link>https://perrotta.dev/2026/01/git-diff-untracked/</link>
      <pubDate>Sun, 04 Jan 2026 04:29:01 -0300</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>git</category>
      <guid>https://perrotta.dev/2026/01/git-diff-untracked/</guid>
      <description>&lt;p&gt;♠ &lt;a href=&#34;https://perrotta.dev/2025/12/git-stash-untracked-files/&#34;&gt;Previously&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;If we can stash untracked files (&lt;code&gt;git stash -u&lt;/code&gt;), should we be able to &lt;code&gt;git diff&lt;/code&gt; untracked files?&lt;/p&gt;&#xA;&lt;p&gt;Naturally, diffing an untracked file would output the entire file contents.&#xA;Still, this can be desirable sometimes.&lt;/p&gt;&#xA;&lt;p&gt;When reviewing multiple files, some of which are modified, and some of which&#xA;are brand new, you may want to review the entire list at once.&lt;/p&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s try it out:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% cdtmp&#xA;/var/folders/yr/6sw3yylx6gjcy5jr38d6j6000000gn/T/thiago.perrotta-2026-01-04-l8Co7f&#xA;% git init&#xA;Initialized empty Git repository in /private/var/folders/yr/6sw3yylx6gjcy5jr38d6j6000000gn/T/thiago.perrotta-2026-01-04-l8Co7f/.git/&#xA;% echo world &amp;gt; hello.md&#xA;% git status&#xA;## No commits yet on master&#xA;?? hello.md&#xA;% git diff&#xA;% git diff -u&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Err, no luck. In fact, from &lt;a href=&#34;https://man.archlinux.org/man/git-diff.1&#34;&gt;&lt;code&gt;git-diff(1)&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;-p, -u, &amp;ndash;patch&lt;/p&gt;&#xA;&lt;p&gt;Generate patch (see the section called &amp;ldquo;GENERATING PATCH TEXT WITH -P&amp;rdquo;).&#xA;This is the default.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Ouch. That&amp;rsquo;s not the &lt;code&gt;-u&lt;/code&gt; we&amp;rsquo;re looking for!&lt;/p&gt;&#xA;&lt;p&gt;We can attempt a similar test with &lt;code&gt;git add -p&lt;/code&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% cdtmp&#xA;/var/folders/yr/6sw3yylx6gjcy5jr38d6j6000000gn/T/thiago.perrotta-2026-01-04-l8Co7f&#xA;% git init&#xA;Initialized empty Git repository in /private/var/folders/yr/6sw3yylx6gjcy5jr38d6j6000000gn/T/thiago.perrotta-2026-01-04-l8Co7f/.git/&#xA;% echo world &amp;gt; hello.md&#xA;% git status&#xA;## No commits yet on master&#xA;?? hello.md&#xA;% git add -p&#xA;No changes.&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;No luck either.&lt;/p&gt;&#xA;&lt;p&gt;Not all hope is lost though.&lt;/p&gt;&#xA;&lt;p&gt;Enter &lt;a href=&#34;https://man.archlinux.org/man/git-add.1#:~:text=ignored%20removed%20files.-,%2DN%2C%20%2D%2Dintent%2Dto%2Dadd,-Record%20only%20the&#34;&gt;&lt;code&gt;git add -N&lt;/code&gt; (&lt;code&gt;--intent-to-add&lt;/code&gt;)&lt;/a&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;&amp;gt; -N, --intent-to-add&#xA;&amp;gt;&#xA;&amp;gt; Record only the fact that the path will be added later. An entry for the&#xA;&amp;gt; path is placed in the index with no content. This is useful for, among other&#xA;&amp;gt; things, showing the unstaged content of such files with git diff and&#xA;&amp;gt; committing them with git commit -a.&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;If we do &lt;code&gt;git add -N hello.md&lt;/code&gt;, the following happens:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% git add -N hello.md&#xA;% git --no-pager diff&#xA;diff --git hello.md hello.md&#xA;new file mode 100644&#xA;index 0000000..cc628cc&#xA;--- /dev/null&#xA;&amp;#43;&amp;#43;&amp;#43; hello.md&#xA;@@ -0,0 &amp;#43;1 @@&#xA;&amp;#43;world&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Yes! There&amp;rsquo;s our diff. Now &lt;code&gt;git add -p&lt;/code&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% git add -N hello.md&#xA;% git add -p&#xA;diff --git a/hello.md b/hello.md&#xA;new file mode 100644&#xA;index 0000000..cc628cc&#xA;--- /dev/null&#xA;&amp;#43;&amp;#43;&amp;#43; b/hello.md&#xA;@@ -0,0 &amp;#43;1 @@&#xA;&amp;#43;world&#xA;(1/1) Stage addition [y,n,q,a,d,e,p,P,?]?&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;It works too!&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;git reset --hard&lt;/code&gt; picks it up as well.&lt;/p&gt;&#xA;&lt;p&gt;In order to make this workflow ergonomic, run &lt;code&gt;git add -AN&lt;/code&gt; (&lt;code&gt;-A&lt;/code&gt; = &lt;code&gt;--all&lt;/code&gt;)&#xA;prior to &lt;code&gt;git diff&lt;/code&gt; or &lt;code&gt;git add -p&lt;/code&gt;, so that all new files are accounted for.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://stackoverflow.com/questions/855767/can-i-use-git-diff-on-untracked-files&#34;&gt;See also&lt;/a&gt;, &lt;a href=&#34;https://perrotta.dev/2025/11/git-diff-interactively/&#34;&gt;git diff interactively&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: git: diff untracked&#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;/p&gt;</description>
    </item>
    <item>
      <title>★ neovim: inline git hunk control
      </title>
      <link>https://perrotta.dev/2026/01/neovim-inline-git-hunk-control/</link>
      <pubDate>Sat, 03 Jan 2026 15:41:21 -0300</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>bestof</category>
      <category>dev</category>
      <category>git</category>
      <category>vim</category>
      <guid>https://perrotta.dev/2026/01/neovim-inline-git-hunk-control/</guid>
      <description>&lt;p&gt;♠ What is a&#xA;&lt;a href=&#34;https://stackoverflow.com/questions/37620729/in-the-context-of-git-and-diff-what-is-a-hunk&#34;&gt;hunk&lt;/a&gt;?&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;When comparing two files, diff finds sequences of lines common to both files,&#xA;interspersed with groups of differing lines called hunks&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2026/01/neovim-inline-git-hunk-control/#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;/blockquote&gt;&#xA;&lt;p&gt;There are various ways to treat git hunks as first-class citizens, manipulating&#xA;them one at a time – staging, unstaging, or reverting them.&lt;/p&gt;&#xA;&lt;p&gt;The classic one is &lt;a href=&#34;https://millerb.co.uk/2021/11/16/git-add-patch.html&#34;&gt;&lt;code&gt;git add [-p | --patch]&lt;/code&gt;&lt;/a&gt;. Hit yes or no&#xA;for each hunk, one by one:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% git add -p&#xA;diff --git a/Justfile b/Justfile&#xA;index bbb3779a9..70840a008 100755&#xA;--- a/Justfile&#xA;&amp;#43;&amp;#43;&amp;#43; b/Justfile&#xA;@@ -175,3 &amp;#43;175,6 @@ update-pre-commit:&#xA; # Update JSON schemas&#xA; update-json-schemas:&#xA;     pre-commit run -a update-json-schemas --hook-stage manual&#xA;&amp;#43;&#xA;&amp;#43;hello:&#xA;&amp;#43;    echo world&#xA;(1/1) Stage this hunk [y,n,q,a,d,e,p,P,?]?&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;There&amp;rsquo;s also &lt;a href=&#34;https://nuclearsquid.com/writings/git-add/&#34;&gt;&lt;code&gt;git add [-i | --interactive]&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% git add -i&#xA;&#xA;*** Commands ***&#xA;  1: status       2: update       3: revert       4: add untracked&#xA;  5: patch        6: diff         7: quit         8: help&#xA;What now&amp;gt;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;It&amp;rsquo;s also possible to manage hunks via git TUIs (=from the terminal) such as:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;a href=&#34;https://github.com/gitui-org/gitui&#34;&gt;gitui&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/jonas/tig&#34;&gt;tig&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/jesseduffield/lazygit&#34;&gt;lazygit&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&amp;hellip;or graphically with IDEs with as VSCode.&lt;/p&gt;&#xA;&lt;p&gt;An honorable mention also goes to&#xA;&lt;a href=&#34;https://github.com/andrewshadura/git-crecord&#34;&gt;&lt;code&gt;git-crecord&lt;/code&gt;&lt;/a&gt;, which I&#xA;originally discovered by looking for an open source alternative to Google&amp;rsquo;s&#xA;Fig&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2026/01/neovim-inline-git-hunk-control/#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; interactive commit and split commands.&lt;/p&gt;&#xA;&lt;p&gt;Now that you see how my mind is overloaded with many possibilities to manipulate&#xA;git hunks, the natural follow-up question is: how to do so right from within our&#xA;favorite text editor (&lt;code&gt;vim&lt;/code&gt;)?&lt;/p&gt;&#xA;&lt;p&gt;With classic &lt;code&gt;vim&lt;/code&gt;, Tim Pope&amp;rsquo;s&#xA;&lt;a href=&#34;https://github.com/tpope/vim-fugitive&#34;&gt;&lt;code&gt;vim-fugitive&lt;/code&gt;&lt;/a&gt; is often touted as&#xA;best-in-class (paired with&#xA;&lt;a href=&#34;https://github.com/airblade/vim-gitgutter&#34;&gt;&lt;code&gt;vim-gitgutter&lt;/code&gt;&lt;/a&gt;). I use it&#xA;occasionally. The workflow is as follows:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;From an open file with modifications, run &lt;code&gt;:GDiff&lt;/code&gt; (&lt;code&gt;:GDiffsplit&lt;/code&gt;).&lt;/li&gt;&#xA;&lt;li&gt;Proceed as you normally would in &lt;code&gt;vimdiff&lt;/code&gt;. Use &lt;code&gt;:diffget&lt;/code&gt; (&lt;code&gt;do&lt;/code&gt;) and&#xA;&lt;code&gt;:diffput&lt;/code&gt; (&lt;code&gt;dp&lt;/code&gt;) for each hunk. Use &lt;code&gt;[c&lt;/code&gt; and &lt;code&gt;]c&lt;/code&gt; to navigate to the previous&#xA;/ next hunk.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Finally, we get to the meat of the post. Is there any workflow&#xA;improvement in &lt;code&gt;neovim&lt;/code&gt;, which I have recently &lt;a href=&#34;https://perrotta.dev/2025/12/vim-nvim/&#34;&gt;switched to&lt;/a&gt;?&lt;/p&gt;&#xA;&lt;p&gt;First of all, the familiar vim-fugitive workflow still works there.&lt;/p&gt;&#xA;&lt;p&gt;Its evolution is &lt;a href=&#34;https://github.com/lewis6991/gitsigns.nvim&#34;&gt;gitsigns.nvim&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Git integration for buffers&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Given the config below, the following becomes possible:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-lua&#34;&gt;-- Git integration&#xA;{&#xA;&amp;#39;lewis6991/gitsigns.nvim&amp;#39;,&#xA;config = function()&#xA;  require(&amp;#39;gitsigns&amp;#39;).setup({&#xA;    on_attach = function(bufnr)&#xA;      local gs = require(&amp;#39;gitsigns&amp;#39;)&#xA;      -- ]c / [c: next/prev hunk (falls back to default in diff mode)&#xA;      -- like git-gutter in vim&#xA;      vim.keymap.set(&amp;#39;n&amp;#39;, &amp;#39;]c&amp;#39;, function()&#xA;        if vim.wo.diff then return &amp;#39;]c&amp;#39; end&#xA;        vim.schedule(function() gs.nav_hunk(&amp;#39;next&amp;#39;) end)&#xA;        return &amp;#39;&amp;lt;Ignore&amp;gt;&amp;#39;&#xA;      end, { expr = true, buffer = bufnr, desc = &amp;#39;Next hunk&amp;#39; })&#xA;      vim.keymap.set(&amp;#39;n&amp;#39;, &amp;#39;[c&amp;#39;, function()&#xA;        if vim.wo.diff then return &amp;#39;[c&amp;#39; end&#xA;        vim.schedule(function() gs.nav_hunk(&amp;#39;prev&amp;#39;) end)&#xA;        return &amp;#39;&amp;lt;Ignore&amp;gt;&amp;#39;&#xA;      end, { expr = true, buffer = bufnr, desc = &amp;#39;Prev hunk&amp;#39; })&#xA;      vim.keymap.set(&amp;#39;n&amp;#39;, &amp;#39;&amp;lt;leader&amp;gt;s&amp;#39;, gs.stage_hunk, { buffer = bufnr, desc = &amp;#39;Stage hunk&amp;#39; })&#xA;      vim.keymap.set(&amp;#39;n&amp;#39;, &amp;#39;&amp;lt;leader&amp;gt;u&amp;#39;, gs.reset_hunk, { buffer = bufnr, desc = &amp;#39;Reset hunk&amp;#39; })&#xA;    end,&#xA;  })&#xA;end,&#xA;},&#xA;&amp;#39;tpope/vim-fugitive&amp;#39;,&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Use &lt;code&gt;]c&lt;/code&gt; and &lt;code&gt;[c&lt;/code&gt; to navigate hunks (compatible with vim-gitgutter)&lt;/li&gt;&#xA;&lt;li&gt;Use &lt;code&gt;&amp;lt;leader&amp;gt;s&lt;/code&gt; (comma + s in my setup) to toggle staging/unstaging hunks.&lt;/li&gt;&#xA;&lt;li&gt;Use &lt;code&gt;&amp;lt;leader&amp;gt;u&lt;/code&gt; to do the equivalent of &lt;code&gt;git checkout -- file&lt;/code&gt; for a single&#xA;hunk.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;The last two shortcuts are wherein the magic lives.&lt;/p&gt;&#xA;&lt;p&gt;It is VERY convenient to choose what to stage/unstage/revert right from the&#xA;&lt;code&gt;nvim&lt;/code&gt; buffer I am currently editing. For fine-grained control, I can always use&#xA;&lt;code&gt;:Gdiff&lt;/code&gt; to perform hunk microsurgery.&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 to confound with &lt;em&gt;hook&lt;/em&gt;.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2026/01/neovim-inline-git-hunk-control/#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;Internal mercurial VCS porcelain on top of Piper / google3.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2026/01/neovim-inline-git-hunk-control/#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: neovim: inline git hunk control&#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/vim/&#34;&gt;#vim&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>myrepos: chain configs
      </title>
      <link>https://perrotta.dev/2025/12/myrepos-chain-configs/</link>
      <pubDate>Mon, 22 Dec 2025 14:45:45 -0300</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>git</category>
      <guid>https://perrotta.dev/2025/12/myrepos-chain-configs/</guid>
      <description>&lt;p&gt;♠ &lt;a href=&#34;https://myrepos.branchable.com/&#34;&gt;myrepos&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;You have a lot of version control repositories. Sometimes you want to update&#xA;them all at once. Or push out all your local changes. You use special command&#xA;lines in some repositories to implement specific workflows. Myrepos provides a&#xA;mr command, which is a tool to manage all your version control repositories.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;I use &lt;code&gt;mr&lt;/code&gt; to manage my local git checkouts, see my&#xA;&lt;a href=&#34;https://github.com/thiagowfx/.dotfiles/blob/master/git/.mrconfig&#34;&gt;&lt;code&gt;~/.mrconfig&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;From &lt;code&gt;$HOME&lt;/code&gt;, running &lt;code&gt;mr update&lt;/code&gt; keeps all managed repositories up-to-date&#xA;(&lt;code&gt;git fetch&lt;/code&gt; / &lt;code&gt;git pull&lt;/code&gt;).&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Problem statement&lt;/strong&gt;: Extend (chain) my mrconfig with git repositories from&#xA;work.&lt;/p&gt;&#xA;&lt;p&gt;I keep my git repos from work in &lt;code&gt;~/Corp&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Changing directory to it and running &lt;code&gt;mr update&lt;/code&gt; updates work repos only.&lt;/p&gt;&#xA;&lt;p&gt;The goal is to have &lt;code&gt;mr update&lt;/code&gt; in &lt;code&gt;~&lt;/code&gt; perform updates for personal AND work&#xA;repos. We can achieve this with configuration chaining.&lt;/p&gt;&#xA;&lt;p&gt;This&#xA;&lt;a href=&#34;https://github.com/thiagowfx/.dotfiles/commit/3f5c5e02dd7cc29bbc30d74f8329ce7bf654bd5e&#34;&gt;commit&lt;/a&gt;&#xA;addresses it:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;# chain corp repositories&#xA;include = test -f $HOME/Corp/.mrconfig &amp;amp;&amp;amp; sed &amp;#39;s|^\[\([^/DEFAULT]\)|[Corp/\1|&amp;#39; $HOME/Corp/.mrconfig || true&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;strong&gt;Rationale&lt;/strong&gt;: If &lt;code&gt;~/Corp/.mrconfig&lt;/code&gt; exists, include it in the original&#xA;&lt;code&gt;~/.mrconfig&lt;/code&gt;. The &lt;code&gt;sed&lt;/code&gt; expression is necessary to adjust relative paths when&#xA;operating on work repos, so that they all stay within &lt;code&gt;~/Corp&lt;/code&gt;, otherwise they&#xA;would be cloned in &lt;code&gt;~&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;The &lt;code&gt;test&lt;/code&gt; and &lt;code&gt;|| true&lt;/code&gt; is to make this inclusion optional, with graceful&#xA;degradation in case the work &lt;code&gt;.mrconfig&lt;/code&gt; does not exist, thereby simply skipping&#xA;it.&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: myrepos: chain configs&#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;/p&gt;</description>
    </item>
    <item>
      <title>★ git: stash untracked files
      </title>
      <link>https://perrotta.dev/2025/12/git-stash-untracked-files/</link>
      <pubDate>Mon, 22 Dec 2025 02:30:06 -0300</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>bestof</category>
      <category>dev</category>
      <category>git</category>
      <guid>https://perrotta.dev/2025/12/git-stash-untracked-files/</guid>
      <description>&lt;p&gt;♠ As I write this very post, this is what my git repository status looks like:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% git st&#xA;## master...origin/master&#xA;?? content/posts/2025-12-22-git-stash-untracked.md&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;What if I wanted to stash it?&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% git stash&#xA;No local changes to save&#xA;% git st&#xA;## master...origin/master&#xA;?? content/posts/2025-12-22-git-stash-untracked.md&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Nothing happens, because this post is a new file, not yet tracked by git.&lt;/p&gt;&#xA;&lt;p&gt;The classic way to address this is to switch to another branch, commit&#xA;everything (e.g. with &lt;a href=&#34;https://perrotta.dev/2025/03/git-freeze-git-thaw/&#34;&gt;&lt;code&gt;git-freeze&lt;/code&gt;&lt;/a&gt;), and then switch back. But this is slow.&lt;/p&gt;&#xA;&lt;p&gt;Today I learned about &lt;code&gt;git stash -u&lt;/code&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;-u, --include-untracked, --no-include-untracked&#xA;    When used with the push and save commands, all untracked files are               also stashed and then cleaned up with git clean.&#xA;&#xA;    When used with the show command, show the untracked files in the&#xA;    stash entry as part of the diff.&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s try it:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% git stash -u&#xA;Saved working directory and index state WIP on master: 58cde6e88 hide codeblock header in RSS feeds&#xA;% git st&#xA;## master...origin/master&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The new post gets stashed indeed!&lt;/p&gt;&#xA;&lt;p&gt;I don&amp;rsquo;t know how I missed this feature – I&amp;rsquo;ve wanted this functionality for&#xA;&lt;code&gt;stash&lt;/code&gt; since forever!&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: git: stash untracked files&#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;/p&gt;</description>
    </item>
    <item>
      <title>★ git: prune stale branches
      </title>
      <link>https://perrotta.dev/2025/11/git-prune-stale-branches/</link>
      <pubDate>Thu, 27 Nov 2025 18:04:45 -0300</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>bestof</category>
      <category>dev</category>
      <category>git</category>
      <guid>https://perrotta.dev/2025/11/git-prune-stale-branches/</guid>
      <description>&lt;p&gt;♠ &lt;strong&gt;Problem statement&lt;/strong&gt;: Delete all local git branches whose counterpart remote&#xA;branches have already been merged upstream.&lt;/p&gt;&#xA;&lt;p&gt;Update &lt;code&gt;~/.gitconfig&lt;/code&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-ini&#34;&gt;[alias]&#xA;    prune-gone = !git fetch -p &amp;amp;&amp;amp; git branch -vv | awk &amp;#39;/: gone]/{print $1}&amp;#39; | xargs -r git branch -D&#xA;    world = !git fetch --all &amp;amp;&amp;amp; git remote prune origin &amp;amp;&amp;amp; git prune &amp;amp;&amp;amp; git prune-gone&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Now simply run &lt;code&gt;git prune-gone&lt;/code&gt; – or, even better, &lt;code&gt;git world&lt;/code&gt; for a more&#xA;complete cleanup.&lt;/p&gt;&#xA;&lt;p&gt;This is much more ergonomic than hunting down individual branches and doing &lt;code&gt;git push origin :{my branch}&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Example usage:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% ~/Workspace/pancake (git)-[master]&#xA;% git world&#xA;From github.com:thiagowfx/pancake&#xA; - [deleted]         (none)     -&amp;gt; origin/thiagowfx/apkbuild-sync&#xA; - [deleted]         (none)     -&amp;gt; origin/thiagowfx/wt-no-origin-graceful&#xA;remote: Enumerating objects: 13, done.&#xA;remote: Counting objects: 100% (13/13), done.&#xA;remote: Compressing objects: 100% (5/5), done.&#xA;remote: Total 8 (delta 5), reused 3 (delta 3), pack-reused 0 (from 0)&#xA;Unpacking objects: 100% (8/8), 2.61 KiB | 668.00 KiB/s, done.&#xA;   f727bd7..51f81ee  master     -&amp;gt; origin/master&#xA;Deleted branch thiagowfx/wt-cd-dash (was 6403261).&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: git: prune stale branches&#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;/p&gt;</description>
    </item>
    <item>
      <title>git stash -m
      </title>
      <link>https://perrotta.dev/2025/11/git-stash-m/</link>
      <pubDate>Sun, 23 Nov 2025 13:14:09 -0300</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>git</category>
      <guid>https://perrotta.dev/2025/11/git-stash-m/</guid>
      <description>&lt;p&gt;♠ When running &lt;code&gt;git stash&lt;/code&gt;, it&amp;rsquo;s possible to pass a &amp;ldquo;stash message&amp;rdquo; to it with&#xA;&lt;code&gt;-m&lt;/code&gt;, just like how you give a &amp;ldquo;commit message&amp;rdquo; to &lt;code&gt;git commit&lt;/code&gt;.&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;git stash -m &amp;#34;first draft, approach #1 from design doc&amp;#34;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;You can view past stashes with &lt;code&gt;git stash list&lt;/code&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% git stash list&#xA;stash@{0}: On master: first draft, approach #1 from design doc&#xA;[...]&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Now your &lt;del&gt;junk&lt;/del&gt; stashes can be properly organized!&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: git stash -m&#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;/p&gt;</description>
    </item>
    <item>
      <title>git diff interactively
      </title>
      <link>https://perrotta.dev/2025/11/git-diff-interactively/</link>
      <pubDate>Tue, 04 Nov 2025 11:21:24 +0100</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>git</category>
      <guid>https://perrotta.dev/2025/11/git-diff-interactively/</guid>
      <description>&lt;p&gt;♠ It&amp;rsquo;s possible to do &lt;code&gt;git patch -p&lt;/code&gt; to review diff hunks interactively, one by&#xA;one.&lt;/p&gt;&#xA;&lt;p&gt;Can we accomplish something similar with &lt;code&gt;git diff&lt;/code&gt;? If not, is there a tool&#xA;that can accomplish that, in the same spirit of &lt;code&gt;git patch -p&lt;/code&gt;, but with a&#xA;read-only-centric workflow?&lt;/p&gt;&#xA;&lt;p&gt;The naïve approach is to simply run &lt;code&gt;git patch -p&lt;/code&gt; and then press &amp;rsquo;n&amp;rsquo; (no) for&#xA;each diff hunk. It does not feel particularly effective though.&lt;/p&gt;&#xA;&lt;p&gt;I found another way, which embodies the spirit of &lt;code&gt;git patch -p&lt;/code&gt; more&#xA;effectively: &lt;a href=&#34;https://jonas.github.io/tig/&#34;&gt;&lt;code&gt;tig status&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;I already use &lt;code&gt;tig&lt;/code&gt; every once in a while but wasn&amp;rsquo;t aware that it could do that.&lt;/p&gt;&#xA;&lt;p&gt;I won&amp;rsquo;t try to illustrate the command output here (it&amp;rsquo;s a TUI); simply give it a&#xA;try the next time you run &lt;code&gt;git diff&lt;/code&gt; or &lt;code&gt;git add&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;It&amp;rsquo;s also possible to stage per file and per hunk changes in the TUI. This&#xA;operation mode somewhats resemble&#xA;&lt;a href=&#34;https://github.com/andrewshadura/git-crecord&#34;&gt;&lt;code&gt;git-crecord&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;git subcommand to interactively select changes to commit or stage&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;&amp;hellip;which I packaged to the &lt;a href=&#34;https://aur.archlinux.org/packages/git-crecord&#34;&gt;Arch User&#xA;Repository&lt;/a&gt; a long time ago, but&#xA;ended up barely using it. Let&amp;rsquo;s see whether &lt;code&gt;tig status&lt;/code&gt; will stick.&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: git diff interactively&#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;/p&gt;</description>
    </item>
    <item>
      <title>git: reset default branch
      </title>
      <link>https://perrotta.dev/2025/10/git-reset-default-branch/</link>
      <pubDate>Thu, 16 Oct 2025 17:20:45 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>git</category>
      <guid>https://perrotta.dev/2025/10/git-reset-default-branch/</guid>
      <description>&lt;p&gt;♠ &lt;strong&gt;Problem statement&lt;/strong&gt;: Given an existing repository whose default branch is&#xA;&lt;code&gt;master&lt;/code&gt;, make it become &amp;ldquo;fresh&amp;rdquo; (=fully reset it), as if it had just been&#xA;created.&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;# backup the current default branch, just in case&#xA;git branch backup-old-master&#xA;git push origin backup-old-master&#xA;&#xA;# create an orphan branch&#xA;git checkout --orphan temp&#xA;&#xA;# remove everything – use one or some of the following, accordingly&#xA;git reset --hard&#xA;rm {all files}&#xA;git rm {all files}&#xA;&#xA;# create an initial commit – c.f. https://perrotta.dev/2025/02/git-blank-commit/&#xA;git blank&#xA;&#xA;# delete the old default branch&#xA;git branch -D master&#xA;&#xA;# rename temp to master, pick one of the following&#xA;git rb master        # this is my alias for &amp;#34;rename branch&amp;#34;&#xA;git branch -m master # the native way&#xA;&#xA;# override the current master&#xA;git push --force origin master&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: git: reset default branch&#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;/p&gt;</description>
    </item>
    <item>
      <title>git: partially cherry-pick a commit
      </title>
      <link>https://perrotta.dev/2025/10/git-partially-cherry-pick-a-commit/</link>
      <pubDate>Wed, 15 Oct 2025 11:08:27 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>git</category>
      <guid>https://perrotta.dev/2025/10/git-partially-cherry-pick-a-commit/</guid>
      <description>&lt;p&gt;♠ &lt;strong&gt;OR&lt;/strong&gt;: &amp;ldquo;cherry-pick a cherry-pick&amp;rdquo;.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://adamj.eu/tech/2025/09/08/git-partial-cherry-pick/&#34;&gt;Adam Johnson&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;git cherry-pick allows you to copy a commit another branch to your current&#xA;one.&lt;/p&gt;&#xA;&lt;p&gt;[&amp;hellip;]&lt;/p&gt;&#xA;&lt;p&gt;One limitation with a straight-up git cherry-pick, as above, is that it always&#xA;copies the entire commit. If a commit contains multiple changes, but you only&#xA;want one of them, you&amp;rsquo;ll need to undo the undesired cherry-picked changes.&#xA;This can be tedious and error-prone.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;The trick is to use the &lt;a href=&#34;https://git-scm.com/docs/git-cherry-pick&#34;&gt;&lt;code&gt;-n&lt;/code&gt;&#xA;(&lt;code&gt;--no-commit&lt;/code&gt;)&lt;/a&gt; argument to prevent changes from&#xA;being committed right away.&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% git cherry-pick -n {branch or SHA}&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;This happens to be the default in&#xA;&lt;a href=&#34;https://mercurial-scm.org/help/commands/graft&#34;&gt;mercurial&lt;/a&gt; with &lt;code&gt;hg graft&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;As such, another possibility is to create an alias:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-ini&#34;&gt;[alias]&#xA;  graft = cherry-pick -n&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% git graft {branch or SHA}&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: git: partially cherry-pick a 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/git/&#34;&gt;#git&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>git: list branches by recent activity
      </title>
      <link>https://perrotta.dev/2025/10/git-list-branches-by-recent-activity/</link>
      <pubDate>Wed, 15 Oct 2025 10:59:58 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>git</category>
      <guid>https://perrotta.dev/2025/10/git-list-branches-by-recent-activity/</guid>
      <description>&lt;p&gt;♠ Tweak &lt;code&gt;~/.gitconfig&lt;/code&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-ini&#34;&gt;[alias]&#xA;  xl = branch -vv&#xA;&#xA;[branch]&#xA;&#x9;sort = -committerdate&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt;: &lt;code&gt;git xl&lt;/code&gt; will print your repository branches sorted by the most&#xA;recent ones in terms of activity, instead of the alphabetical default.&lt;/p&gt;&#xA;&lt;p&gt;I have this setting for a long time, and IMHO it&amp;rsquo;s a better default.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://tekin.co.uk/2021/11/listing-most-recent-git-branches&#34;&gt;This post&lt;/a&gt; by&#xA;Tekin Süleyman prompted me to share it.&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: git: list branches by recent activity&#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;/p&gt;</description>
    </item>
    <item>
      <title>git: commit with the wrong email
      </title>
      <link>https://perrotta.dev/2025/10/git-commit-with-the-wrong-email/</link>
      <pubDate>Tue, 14 Oct 2025 13:50:36 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>git</category>
      <guid>https://perrotta.dev/2025/10/git-commit-with-the-wrong-email/</guid>
      <description>&lt;p&gt;♠ You are sending a PR upstream.&lt;/p&gt;&#xA;&lt;p&gt;You accidentally commit it with the wrong email.&lt;/p&gt;&#xA;&lt;p&gt;First, you prevent this mistake from happening again by updating your&#xA;&lt;code&gt;~/.gitconfig&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;And now you need to fix the most recent commit.&lt;/p&gt;&#xA;&lt;p&gt;You can easily do so with:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% git commit --amend --reset-author --no-edit&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Alternatively, with &lt;a href=&#34;https://github.com/thiagowfx/.dotfiles&#34;&gt;my aliases&lt;/a&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;git uncommit &amp;amp;&amp;amp; git recommit&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: git: commit with the wrong email&#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;/p&gt;</description>
    </item>
    <item>
      <title>git: merge two repositories
      </title>
      <link>https://perrotta.dev/2025/10/git-merge-two-repositories/</link>
      <pubDate>Sun, 12 Oct 2025 15:52:06 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>git</category>
      <guid>https://perrotta.dev/2025/10/git-merge-two-repositories/</guid>
      <description>&lt;p&gt;♠ Daniel Roy Greenfeld, &lt;a href=&#34;https://daniel.feldroy.com/posts/til-2025-09-merging-two-git-projects&#34;&gt;TIL: Merging two git projects&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Of course this task can be done with copy/paste. The challenge there is the&#xA;loss of git history. All the history of struggles and tribulations are gone.&#xA;More important is the attribution — unless any and all contributors are made&#xA;co-authors. But then the volume of attribution isn&amp;rsquo;t accurate, some one who&#xA;made one tiny change gets as much credit as the leading contributor.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Adapted to my own words: we will merge &amp;ldquo;atreides&amp;rdquo; into &amp;ldquo;harkonnen&amp;rdquo;.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;cd&lt;/code&gt; to &lt;code&gt;harkonnen&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;git remote add atreides {atreides git repo URL}&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;git fetch atreides&lt;/code&gt; (or &lt;code&gt;git fetch --all&lt;/code&gt;)&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;git merge atreides/master --allow-unrelated-histories&lt;/code&gt;  # or main, if applicable&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Fin.&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: git: merge two repositories&#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;/p&gt;</description>
    </item>
    <item>
      <title>git blame: prior commits
      </title>
      <link>https://perrotta.dev/2025/09/git-blame-prior-commits/</link>
      <pubDate>Mon, 29 Sep 2025 10:41:15 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>git</category>
      <guid>https://perrotta.dev/2025/09/git-blame-prior-commits/</guid>
      <description>&lt;p&gt;♠ Let&amp;rsquo;s say you want to inspect the history of a given file tracked in a git&#xA;repository. Straightforward with &lt;a href=&#34;https://man.archlinux.org/man/extra/git/git-blame.1.en&#34;&gt;&lt;code&gt;git-blame(1)&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% git --no-pager blame templates/secret-aws-info.yaml&#xA;5d478f6cb83b (Kubernetes Astronaut 2025-08-05 10:49:07 -0400  1) {{- if or (eq .Values.config.cloud.provider &amp;#34;aws&amp;#34;) (not (.Values.config.externalDNS).enabled) }}&#xA;1aeb7c4d7b9b (Thiago Perrotta 2025-04-06 13:11:45 &amp;#43;0200  2) apiVersion: v1&#xA;1aeb7c4d7b9b (Thiago Perrotta 2025-04-06 13:11:45 &amp;#43;0200  3) kind: Secret&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;I am not interested in &lt;code&gt;5d478f6cb83b&lt;/code&gt; though: it was just a refactoring change.&#xA;How can I look deeper in history? More specifically, I&amp;rsquo;d like to run &lt;code&gt;git blame&lt;/code&gt;&#xA;on a point in time&#xA;&lt;a href=&#34;https://stackoverflow.com/questions/5098256/how-can-i-view-prior-commits-with-git-blame&#34;&gt;&lt;em&gt;before&lt;/em&gt;&lt;/a&gt; commit &lt;code&gt;5d478f6cb83b&lt;/code&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% git --no-pager blame 5d478f6cb83b^ templates/secret-aws-info.yaml&#xA;1aeb7c4d7b9b (Thiago Perrotta 2025-04-06 13:11:45 &amp;#43;0200  1) {{- if or (eq .Values.config.cloudProvider &amp;#34;aws&amp;#34;) (not (.Values.config.externalDNS).enabled) }}&#xA;1aeb7c4d7b9b (Thiago Perrotta 2025-04-06 13:11:45 &amp;#43;0200  2) apiVersion: v1&#xA;1aeb7c4d7b9b (Thiago Perrotta 2025-04-06 13:11:45 &amp;#43;0200  3) kind: Secret&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;And I could keep going on:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% git --no-pager blame 1aeb7c4d7b9b^  templates/secret-aws-info.yaml&#xA;[...]&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The Chromium project has a&#xA;&lt;a href=&#34;https://commondatastorage.googleapis.com/chrome-infra-docs/flat/depot_tools/docs/html/git-hyper-blame.html&#34;&gt;&lt;code&gt;git-hyper-blame(1)&lt;/code&gt;&lt;/a&gt; script:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Like git blame, but with the ability to ignore or bypass certain commits.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;&amp;hellip;that enables tracking refactoring/cleanup commits on a permanent basis for&#xA;skipping blame.&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: git blame: prior commits&#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;/p&gt;</description>
    </item>
    <item>
      <title>git: ignore changes to tracked files
      </title>
      <link>https://perrotta.dev/2025/08/git-ignore-changes-to-tracked-files/</link>
      <pubDate>Tue, 26 Aug 2025 14:46:44 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>git</category>
      <guid>https://perrotta.dev/2025/08/git-ignore-changes-to-tracked-files/</guid>
      <description>&lt;p&gt;♠ &lt;a href=&#34;https://perrotta.dev/2025/08/midnight-commander-colorscheme/&#34;&gt;Previously&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Given this config file for &lt;a href=&#34;https://midnight-commander.org/&#34;&gt;midnight&#xA;commander&lt;/a&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% head -n 2 ~/.dotfiles/mc/.config/mc/ini&#xA;[Midnight-Commander]&#xA;skin=modarin256&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&amp;hellip;properly installed via &lt;code&gt;stow&lt;/code&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% (cd ~/.dotfiles &amp;amp;&amp;amp; stow mc)&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Upon opening &lt;code&gt;mc&lt;/code&gt;, it writes a lot of configuration settings to that file:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-ini&#34;&gt;[Midnight-Commander]&#xA;skin=modarin256&#xA;verbose=true&#xA;shell_patterns=true&#xA;auto_save_setup=true&#xA;preallocate_space=false&#xA;auto_menu=false&#xA;use_internal_view=true&#xA;use_internal_edit=true&#xA;clear_before_exec=true&#xA;[...]&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;I could check them into source control, but I do not want to. They are defaults.&lt;/p&gt;&#xA;&lt;p&gt;So I tell &lt;code&gt;git&lt;/code&gt; to leave these additions alone:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% git update-index --skip-worktree mc/.config/mc/ini&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Now &lt;code&gt;git status&lt;/code&gt; will not show the file.&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: git: ignore changes to tracked files&#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;/p&gt;</description>
    </item>
    <item>
      <title>ArgoCD: git index.lock issue
      </title>
      <link>https://perrotta.dev/2025/08/argocd-git-index.lock-issue/</link>
      <pubDate>Sat, 23 Aug 2025 12:39:14 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>git</category>
      <category>kubernetes</category>
      <guid>https://perrotta.dev/2025/08/argocd-git-index.lock-issue/</guid>
      <description>&lt;p&gt;♠ &lt;strong&gt;Problem statement&lt;/strong&gt;: Every ArgoCD application is stuck in &lt;em&gt;Unknown&lt;/em&gt; state,&#xA;with the following error:&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:&#xA;rpc error: code = Unknown desc = failed to initialize repository resources: rpc&#xA;error: code = Internal desc = Failed to checkout FETCH_HEAD: failed to checkout&#xA;FETCH_HEAD: `git checkout --force FETCH_HEAD` failed exit status 128: fatal:&#xA;Unable to create &amp;#39;&amp;lt;path to cached source&amp;gt;/.git/index.lock&amp;#39;: File exists. Another&#xA;git process seems to be running in this repository, e.g. an editor opened by&#xA;&amp;#39;git commit&amp;#39;. Please make sure all processes are terminated then try again. If&#xA;it still fails, a git process may have crashed in this repository earlier:&#xA;remove the file manually to continue.&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;It is a lock contention race condition issue within the ArgoCD repo server&#xA;component, with the git &lt;code&gt;index.lock&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;The easiest way to address it is to restart the repo server:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;kubectl rollout restart deploy -n argocd argocd-repo-server&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Afterwards, refresh all applications in the cluster via the web UI:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;click Applications on the left-side nav bar&lt;/li&gt;&#xA;&lt;li&gt;click Refresh apps at the top&lt;/li&gt;&#xA;&lt;li&gt;select ALL applications, set refresh type to NORMAL&lt;/li&gt;&#xA;&lt;li&gt;click refresh&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: ArgoCD: git index.lock issue&#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/kubernetes/&#34;&gt;#kubernetes&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>★ git ll
      </title>
      <link>https://perrotta.dev/2025/08/git-ll/</link>
      <pubDate>Tue, 12 Aug 2025 12:50:09 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>bestof</category>
      <category>dev</category>
      <category>git</category>
      <guid>https://perrotta.dev/2025/08/git-ll/</guid>
      <description>&lt;p&gt;♠ In my &lt;code&gt;~/.gitconfig&lt;/code&gt; there&amp;rsquo;s an alias for &lt;code&gt;git log&lt;/code&gt;, inspired by &lt;a href=&#34;https://registerspill.thorstenball.com/p/how-i-use-git&#34;&gt;Thorsten&#xA;Ball&lt;/a&gt;&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2025/08/git-ll/#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;&#xA;&lt;pre&gt;&lt;code class=&#34;language-ini&#34;&gt;[alias]&#xA;  ll = &amp;#34;!f() { git log --graph --pretty=&amp;#39;tformat:%C(always,yellow)%h%C(always,reset) %C(always,green)%ar%C(always,reset){%C(always,bold blue)%an%C(always,reset){%C(always,red)%d%C(always,reset) %s&amp;#39; \&amp;#34;$@\&amp;#34; | column -t -s &amp;#39;{&amp;#39; | less -XS; }; f&amp;#34;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;I wish I could get rid of the shell utilities (&lt;code&gt;column&lt;/code&gt; and &lt;code&gt;less&lt;/code&gt;), but they&amp;rsquo;re&#xA;necessary to properly align the columns.&lt;/p&gt;&#xA;&lt;p&gt;I also tried to get rid of the function wrapper (&lt;code&gt;f()&lt;/code&gt;): it works when you run&#xA;&lt;code&gt;git ll&lt;/code&gt;. However when you run &lt;code&gt;git ll .&lt;/code&gt; (=display the commit history from&#xA;files underneath the current directory only) it fails:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;% git ll .&#xA;. is a directory&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Therefore it&amp;rsquo;s better to keep it around. I am satisfied with the current form.&lt;/p&gt;&#xA;&lt;p&gt;The output looks like this (image from upstream):&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://substackcdn.com/image/fetch/$s_!1Mdk!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbf41b48e-523f-4f82-abc0-db4b1d27c689_2310x1374.png&#34; alt=&#34;git ll output&#34;&#xA;  loading=&#34;lazy&#34; decoding=&#34;async&#34; /&gt;&#xA;&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Update(2025-08-21)&lt;/strong&gt;: The following modification is better&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2025/08/git-ll/#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; and has no&#xA;external dependencies:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-ini&#34;&gt;[alias]&#xA;  ll = log --graph --pretty=tformat:&amp;#39;%C(always,yellow)%h%C(always,reset) %C(always,green)%&amp;lt;(25,trunc)%ar%C(always,reset) %C(always,bold blue)%&amp;lt;(20,trunc)%an%C(always,reset) %C(always,red)%d%C(always,reset) %s&amp;#39;&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;His original configuration is a separate shell function, which is&#xA;completely unnecessary. A git alias suffices. Keep it simple!&amp;#160;&lt;a href=&#34;https://perrotta.dev/2025/08/git-ll/#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 the original implementation, &lt;code&gt;git ll .&lt;/code&gt; did not work as expected&#xA;(i.e. it did not display changes &lt;em&gt;only&lt;/em&gt; from the current directory).&amp;#160;&lt;a href=&#34;https://perrotta.dev/2025/08/git-ll/#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 ll&#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;/p&gt;</description>
    </item>
    <item>
      <title>★ git: --no-pager
      </title>
      <link>https://perrotta.dev/2025/08/git--no-pager/</link>
      <pubDate>Tue, 12 Aug 2025 12:21:22 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>bestof</category>
      <category>dev</category>
      <category>git</category>
      <guid>https://perrotta.dev/2025/08/git--no-pager/</guid>
      <description>&lt;p&gt;♠ Let&amp;rsquo;s say you use color highlighting to visually inspect diffs with &lt;code&gt;git&lt;/code&gt;, such&#xA;as &lt;a href=&#34;https://github.com/dandavison/delta&#34;&gt;&lt;code&gt;delta&lt;/code&gt;&lt;/a&gt; or&#xA;&lt;a href=&#34;https://github.com/git/git/blob/master/contrib/diff-highlight/README&#34;&gt;&lt;code&gt;diff-highlight&lt;/code&gt;&lt;/a&gt;&#xA;or &lt;a href=&#34;https://github.com/mookid/diffr&#34;&gt;&lt;code&gt;diffr&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Implication&lt;/strong&gt;: whenever you run &lt;code&gt;git diff&lt;/code&gt;, &lt;code&gt;git show&lt;/code&gt;, or any other &lt;code&gt;git&lt;/code&gt;&#xA;command that produces a diff, it will emit colors (typically red and green).&lt;/p&gt;&#xA;&lt;p&gt;Colors are great for local visual inspection, but they are terrible for sharing.&#xA;They won&amp;rsquo;t be carried over with copy-and-paste.&lt;/p&gt;&#xA;&lt;p&gt;If you intend to share diff output (e.g. to a Q&amp;amp;A site, with teammates), it&amp;rsquo;s&#xA;better to emit &lt;code&gt;-&lt;/code&gt; and &lt;code&gt;+&lt;/code&gt;, as vanilla &lt;code&gt;diff&lt;/code&gt; does.&lt;/p&gt;&#xA;&lt;p&gt;The easiest way to do so is to pass &lt;code&gt;--no-pager&lt;/code&gt; to &lt;code&gt;git&lt;/code&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;git --no-pager diff a83bc395&#xA;git --no-pager show HEAD&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;For example, a recent commit in my&#xA;&lt;a href=&#34;https://github.com/thiagowfx/.dotfiles&#34;&gt;dotfiles&lt;/a&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% git --no-pager show HEAD~1&#xA;commit 52b1e91b0f1d93176ec768aa6158974fc401f335&#xA;Author: Thiago Perrotta &amp;lt;{redacted}&amp;gt;&#xA;Date:   Thu Aug 7 12:00:47 2025 &amp;#43;0200&#xA;&#xA;    fix mr&#xA;&#xA;diff --git bin/.bin/sd-world bin/.bin/sd-world&#xA;index c46be0e..44c0b47 100755&#xA;--- bin/.bin/sd-world&#xA;&amp;#43;&amp;#43;&amp;#43; bin/.bin/sd-world&#xA;@@ -58,7 &amp;#43;58,7 @@ esac&#xA; run_if_exists &amp;#34;claude&amp;#34; claude update&#xA;&#xA; # myrepos&#xA;-run_if_exists &amp;#34;mr&amp;#34; mr -d ~ update&#xA;&amp;#43;(cd ~ &amp;amp;&amp;amp; run_if_exists &amp;#34;mr&amp;#34; mr update)&#xA;&#xA; # source corp updates if any&#xA; run_if_exists &amp;#34;sd-world-corp&amp;#34;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Note that &lt;code&gt;--no-pager&lt;/code&gt; is a &lt;code&gt;git&lt;/code&gt;-level option: it should be specified before&#xA;the git subcommand. The following does not work:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% git show HEAD~1 --no-pager&#xA;fatal: unrecognized argument: --no-pager&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: git: --no-pager&#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;/p&gt;</description>
    </item>
    <item>
      <title>git: developer certificate of origin (DCO) sign-off
      </title>
      <link>https://perrotta.dev/2025/07/git-developer-certificate-of-origin-dco-sign-off/</link>
      <pubDate>Thu, 24 Jul 2025 12:37:31 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>git</category>
      <guid>https://perrotta.dev/2025/07/git-developer-certificate-of-origin-dco-sign-off/</guid>
      <description>&lt;p&gt;♠ The &lt;a href=&#34;https://developercertificate.org/&#34;&gt;Developer Certificate of Origin&lt;/a&gt; (DCO&#xA;for short) is often required when sending out PRs upstream.&lt;/p&gt;&#xA;&lt;p&gt;For example, contributions to the Linux kernel&#xA;&lt;a href=&#34;https://wiki.linuxfoundation.org/dco&#34;&gt;mandate&lt;/a&gt; it.&#xA;&lt;a href=&#34;https://cert-manager.io/docs/contributing/sign-off/&#34;&gt;Other&lt;/a&gt;&#xA;&lt;a href=&#34;https://gcc.gnu.org/dco.html&#34;&gt;projects&lt;/a&gt;&#xA;&lt;a href=&#34;https://docs.pi-hole.net/guides/github/dco/&#34;&gt;too&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;It&amp;rsquo;s even possible to enforce it via &lt;a href=&#34;https://github.com/apps/dco&#34;&gt;Github&#xA;Actions&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Suppose that you sent a PR with a single commit but forgot to sign it off. You&#xA;can amend it with:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;git rebase HEAD~1 --signoff&#xA;git push --force-with-lease origin [branch]&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Or you can do it proactively, at commit time:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;git commit [-s | --signoff]&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;It is&#xA;&lt;a href=&#34;https://stackoverflow.com/questions/77279077/how-to-sign-off-every-git-commit&#34;&gt;often&lt;/a&gt;&#xA;&lt;a href=&#34;https://git-scm.com/docs/git-config#Documentation/git-config.txt-formatsignOff&#34;&gt;discouraged&lt;/a&gt;&#xA;to have &lt;code&gt;git&lt;/code&gt; automatically do it:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Adding the Signed-off-by trailer to a patch should be a conscious act and means&#xA;that you certify you have the rights to submit this work under the same open&#xA;source license.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;In practice, a sign-off is an ordinary &lt;a href=&#34;https://commondatastorage.googleapis.com/chrome-infra-docs/flat/depot_tools/docs/html/git-footers.html&#34;&gt;git&#xA;footer&lt;/a&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;Signed-off-by: John Doe &amp;lt;john@example.com&amp;gt;&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: git: developer certificate of origin (DCO) sign-off&#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;/p&gt;</description>
    </item>
    <item>
      <title>Markdown headings and git comments
      </title>
      <link>https://perrotta.dev/2025/07/markdown-headings-and-git-comments/</link>
      <pubDate>Tue, 08 Jul 2025 13:18:09 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>git</category>
      <guid>https://perrotta.dev/2025/07/markdown-headings-and-git-comments/</guid>
      <description>&lt;p&gt;♠ Our team uses a PR template that resembles the following:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-markdown&#34;&gt;## Summary&#xA;&#xA;&amp;lt;!-- Provide a clear and concise description of the changes.&#xA;Provide a link to the corresponding PR in other repositories if applicable. --&amp;gt;&#xA;&#xA;## Jira Task&#xA;&#xA;- [ ] No ticket needed&#xA;&#xA;https://acme.atlassian.net/browse/XXX&#xA;&#xA;## Cluster(s) Affected&#xA;&#xA;&amp;lt;!-- List the clusters affected by this change --&amp;gt;&#xA;&#xA;## Impact&#xA;&#xA;&amp;lt;!-- Describe the impact of these changes. Will this require downtime? Are there&#xA;any performance considerations? --&amp;gt;&#xA;&#xA;## Testing&#xA;&#xA;&amp;lt;!-- Describe how you&amp;#39;ve tested these changes --&amp;gt;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://perrotta.dev/2025/05/github-pull-request-template/&#34;&gt;Previously&lt;/a&gt; I wrote&#xA;about how to automatically incorporate github pull request templates in &lt;code&gt;git commit&lt;/code&gt; (&lt;code&gt;COMMIT_EDITMSG&lt;/code&gt;).&lt;/p&gt;&#xA;&lt;p&gt;It turns out there&amp;rsquo;s one caveat with this approach.&lt;/p&gt;&#xA;&lt;p&gt;Markdown headings&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2025/07/markdown-headings-and-git-comments/#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; (&lt;code&gt;##&lt;/code&gt; for &lt;code&gt;&amp;lt;h2&amp;gt;&lt;/code&gt;) use the same character as git line comments&#xA;(&lt;code&gt;#&lt;/code&gt;) for commit messages.&lt;/p&gt;&#xA;&lt;p&gt;As a side effect, when pulling up the template locally with a plain git commit,&#xA;the headings do not show up in the final commit message.&lt;/p&gt;&#xA;&lt;p&gt;This can be worked around by using &lt;code&gt;gh pr create&lt;/code&gt; instead, but it &lt;a href=&#34;https://xkcd.com/1172/&#34;&gt;breaks my&#xA;setup&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Another workaround is to convert the headings to use &lt;code&gt;----&lt;/code&gt; markers, for example:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;Summary&#xA;-------&#xA;&#xA;&amp;lt;!-- Provide a clear and concise description of the changes.&#xA;Provide a link to the corresponding PR in other repositories if applicable. --&amp;gt;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: A teammate pointed out the following workaround, which is the most&#xA;elegant of them:&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://git-scm.com/docs/git-config#Documentation/git-config.txt-corecommentChar&#34;&gt;&lt;code&gt;core.commentChar&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Commands such as &lt;code&gt;commit&lt;/code&gt; and &lt;code&gt;tag&lt;/code&gt; that let you edit messages consider a line&#xA;that begins with this character commented, and removes them after the editor&#xA;returns (default &lt;code&gt;#&lt;/code&gt;).&lt;/p&gt;&#xA;&lt;p&gt;If set to &amp;ldquo;auto&amp;rdquo;, git-commit would select a character that is not the&#xA;beginning character of any line in existing commit messages.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;And that&amp;rsquo;s exactly what I did:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% git config set --global core.commentChar &amp;#34;auto&amp;#34;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The effect, it automatically picks &lt;code&gt;;&lt;/code&gt; as the comment character:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;## Summary&#xA;&#xA;&amp;lt;!-- Provide a clear and concise description of the changes.&#xA;Provide a link to the corresponding PR in other repositories if applicable. --&amp;gt;&#xA;&#xA;; Please enter the commit message for your changes. Lines starting&#xA;; with &amp;#39;;&amp;#39; will be ignored, and an empty message aborts the commit.&#xA;;&#xA;; On branch master&#xA;; Your branch is up to date with &amp;#39;origin/master&amp;#39;.&#xA;;&#xA;; ------------------------ &amp;gt;8 ------------------------&#xA;; Do not modify or remove the line above.&#xA;; Everything below it will be ignored.&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;I am always forgetting whether to call them &amp;ldquo;headings&amp;rdquo; or &amp;ldquo;headers&amp;rdquo;.&amp;#160;&lt;a href=&#34;https://perrotta.dev/2025/07/markdown-headings-and-git-comments/#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: Markdown headings and git comments&#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;/p&gt;</description>
    </item>
    <item>
      <title>Oh Shit, Git!?!
      </title>
      <link>https://perrotta.dev/2025/06/oh-shit-git/</link>
      <pubDate>Thu, 19 Jun 2025 13:55:45 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>dev</category>
      <category>git</category>
      <guid>https://perrotta.dev/2025/06/oh-shit-git/</guid>
      <description>&lt;p&gt;♠ &lt;a href=&#34;https://ohshitgit.com/&#34;&gt;https://ohshitgit.com/&lt;/a&gt; by Katie Sylor-Miller:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Git is hard: screwing up is easy, and figuring out how to fix your mistakes is&#xA;fucking impossible. Git documentation has this chicken and egg problem where&#xA;you can&amp;rsquo;t search for how to get yourself out of a mess, unless you already&#xA;know the name of the thing you need to know about in order to fix your&#xA;problem.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;So here are some bad situations I&amp;rsquo;ve gotten myself into, and how I eventually&#xA;got myself out of them in plain english.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;I love these websites created with the sole purpose of filling in the void of&#xA;tool usability.&lt;/p&gt;&#xA;&lt;p&gt;Other notable examples from the top of my head:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://git-send-email.io/&#34;&gt;https://git-send-email.io/&lt;/a&gt; by &lt;a href=&#34;https://drewdevault.com/&#34;&gt;Drew DeVault&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://yaml-multiline.info/&#34;&gt;https://yaml-multiline.info/&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: Oh Shit, Git!?!&#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;/p&gt;</description>
    </item>
    <item>
      <title>★ git delta
      </title>
      <link>https://perrotta.dev/2025/06/git-delta/</link>
      <pubDate>Mon, 16 Jun 2025 13:28:23 +0200</pubDate><author>serendipity@perrotta.dev (Thiago Perrotta)</author>
      <category>bestof</category>
      <category>dev</category>
      <category>git</category>
      <guid>https://perrotta.dev/2025/06/git-delta/</guid>
      <description>&lt;p&gt;♠ I&amp;rsquo;ve been using the venerable&#xA;&lt;a href=&#34;https://github.com/git/git/tree/master/contrib/diff-highlight&#34;&gt;&lt;code&gt;diff-highlight&lt;/code&gt;&lt;/a&gt;&#xA;script to power various &lt;code&gt;git diff&lt;/code&gt; operations in the terminal. It is a decent&#xA;improvement on top of stock &lt;code&gt;git diff&lt;/code&gt;, but it&amp;rsquo;s somewhat of an&#xA;&lt;a href=&#34;https://github.com/thiagowfx/.dotfiles/blob/94fa66d62260c7c5e133f50beef6181f098b3599/git/.profile.d/git.sh#L11&#34;&gt;annoying&lt;/a&gt;&#xA;maintenance burden to manage it across various environments, as it&amp;rsquo;s installed&#xA;in different ways on various systems:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;#!/bin/sh&#xA;&#xA;if hash git &amp;gt;/dev/null 2&amp;gt;&amp;amp;1; then&#xA;&#x9;# git-diff-highlight: alpine:/usr/bin/, arch, debian&#xA;&#x9;path_munge &amp;#34;/usr/share/git/diff-highlight&amp;#34; &amp;#34;/usr/share/doc/git/contrib/diff-highlight&amp;#34;&#xA;&#xA;&#x9;# brew&#xA;&#x9;if hash brew &amp;gt;/dev/null 2&amp;gt;&amp;amp;1; then&#xA;&#x9;&#x9;path_munge &amp;#34;$(brew --prefix)&amp;#34;/share/git-core/contrib/diff-highlight&#xA;&#x9;fi&#xA;&#xA;fi&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Thus I decided to migrate to &lt;a href=&#34;https://github.com/dandavison/delta&#34;&gt;&lt;code&gt;git-delta&lt;/code&gt;&lt;/a&gt;,&#xA;which is:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;quite popular&lt;/li&gt;&#xA;&lt;li&gt;written in Rust™&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;https://perrotta.dev/2025/06/git-delta/#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://repology.org/project/git-delta/versions&#34;&gt;available&lt;/a&gt; in all package&#xA;managers I&amp;rsquo;m interested in – hence it&amp;rsquo;s &lt;strong&gt;one install command away from my&#xA;system&lt;/strong&gt;, no need for extra configs &amp;lt;== And this is the main reason I wanted&#xA;to migrate to it.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;On macOS, the package is called &lt;code&gt;git-delta&lt;/code&gt; whereas the binary is called&#xA;&lt;code&gt;delta&lt;/code&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% brew install git-delta&#xA;% brew ls git-delta | grep bin/&#xA;/opt/homebrew/Cellar/git-delta/0.18.2_3/bin/delta&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The integration with &lt;code&gt;git&lt;/code&gt; is as follows:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% grep -E &amp;#39;\[core\]|pager =|delta&amp;#39; ~/.gitconfig&#xA;[core]&#xA;        # delta &amp;gt; diff-highlight &amp;gt; plain cat&#xA;        pager = &amp;#34;delta --diff-highlight 2&amp;gt;/dev/null || ((diff-highlight 2&amp;gt;/dev/null || cat) | ${PAGER:-less})&amp;#34;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;It falls back to &lt;code&gt;diff-highlight&lt;/code&gt; when &lt;code&gt;delta&lt;/code&gt; is not available.&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;delta&lt;/code&gt; is quite&#xA;&lt;a href=&#34;https://dandavison.github.io/delta/configuration.html&#34;&gt;configurable&lt;/a&gt;:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;% grep -EA3 &amp;#39;\[delta\]&amp;#39; ~/.gitconfig&#xA;[delta]&#xA;        navigate = true&#xA;        side-by-side = false&#xA;        line-numbers = true&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;One caveat of using &lt;code&gt;delta&lt;/code&gt;: the &lt;code&gt;git diff&lt;/code&gt; output is not suitable for sharing,&#xA;as it does not contain diff markers, relying on colors instead.&#xA;See &lt;a href=&#34;https://perrotta.dev/2025/08/git--no-pager/&#34;&gt;this post&lt;/a&gt; for a workaround.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Update(2025-09-07)&lt;/strong&gt;: I&amp;rsquo;ve been finding &lt;code&gt;delta&lt;/code&gt; quite pleasant to use.&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;Always an (over)hyped reason to migrate, but, oh well&amp;hellip;&amp;#160;&lt;a href=&#34;https://perrotta.dev/2025/06/git-delta/#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: git delta&#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;/p&gt;</description>
    </item>
  </channel>
</rss>
