<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://c.rubler.net/blog/feed.xml" rel="self" type="application/atom+xml" /><link href="https://c.rubler.net/blog/" rel="alternate" type="text/html" /><updated>2026-03-14T14:22:24+01:00</updated><id>https://c.rubler.net/blog/feed.xml</id><title type="html">c.rubler.net/blog</title><subtitle>About things I like and use: Linux, Ubuntu, the terminal, KDE, Rust,
  TypeScript
</subtitle><author><name>Christoph Rüßler</name><email>christoph.ruessler@mailbox.org</email></author><entry><title type="html">gix-blame performance improved by a change in gix-diff</title><link href="https://c.rubler.net/blog/2026/03/14/gix-blame-performance-improved-by-a-change-in-gix-diff.html" rel="alternate" type="text/html" title="gix-blame performance improved by a change in gix-diff" /><published>2026-03-14T10:45:00+01:00</published><updated>2026-03-14T10:45:00+01:00</updated><id>https://c.rubler.net/blog/2026/03/14/gix-blame-performance-improved-by-a-change-in-gix-diff</id><content type="html" xml:base="https://c.rubler.net/blog/2026/03/14/gix-blame-performance-improved-by-a-change-in-gix-diff.html"><![CDATA[<p>In February 2026, we got a <a href="https://github.com/GitoxideLabs/gitoxide/pull/2438">PR in <code class="language-plaintext highlighter-rouge">gitoxide</code></a> that substantially
improved <code class="language-plaintext highlighter-rouge">gix-diff</code>’s tree diff performance. Since <code class="language-plaintext highlighter-rouge">gix-blame</code>’s algorithm uses
a lot of tree diffs under the hood, I wanted to know what the impact on
<code class="language-plaintext highlighter-rouge">gix-blame</code>’s performance was.</p>

<p>I set up a benchmark using <code class="language-plaintext highlighter-rouge">hyperfine</code> in <a href="https://github.com/cruessler/gix-benchmarks/blob/16fb1087c834322464b855e9e5fbd11e2dbe17db/run_benchmark.py">this script</a> and,
with the help of <a href="https://seaborn.pydata.org/index.html">seaborn</a>, plotted the results using <a href="https://github.com/cruessler/gix-benchmarks/blob/45979d7098d39615ebdfe4ef1d5031ce2864f3de/plot_benchmark.py">another
script</a>. And the results are quite impressive: speedups of up
to 25 % in some scenarios, with no noticeable performance degradation in any
scenario.</p>

<p>The comparison was run between <a href="https://github.com/GitoxideLabs/gitoxide/commit/e63d487fb1b1425a9458fc7400517ad2c0280fd2">this commit that contained the
optimization</a> and <a href="https://github.com/GitoxideLabs/gitoxide/commit/29040a8277735cbc9fcd0d80626c75d710d3da2a">its parent</a>. The full
results can also be found as <a href="https://gist.github.com/cruessler/6f0858b862a0442d0904758742d74446"><code class="language-plaintext highlighter-rouge">json</code> files in this gist</a>.</p>

<p><strong>Update 2026-03-14</strong>: I’ve changed the plots to use shades of blue instead of
variants of magenta and green.</p>

<h2 id="plots">Plots</h2>

<figure>
  <img src="/blog/assets/catplot.webp" alt="Catplot of running 2 versions of gix-blame on a set of files" />
  <figcaption>Catplot of running 2 versions of
    <code class="language-plaintext">gix-blame</code> on a set of files
  </figcaption>
</figure>

<figure>
  <img src="/blog/assets/boxplot.webp" alt="Boxplot of running 2 versions of gix-blame on a set of files" />
  <figcaption>Boxplot of running 2 versions of
    <code class="language-plaintext">gix-blame</code> on a set of files
  </figcaption>
</figure>

<h2 id="details-for-individual-benchmark-runs">Details for individual benchmark runs</h2>

<p>The commands have been shortened. The commit hash is short for a version of
<code class="language-plaintext highlighter-rouge">gitoxide</code> compiled from that commit. <code class="language-plaintext highlighter-rouge">e63d487fb</code> is the commit containing the
optimization, <code class="language-plaintext highlighter-rouge">29040a827</code> is its parent. All runs were regular <code class="language-plaintext highlighter-rouge">gix blame …</code>
runs.</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Command</th>
      <th style="text-align: right">Mean [ms]</th>
      <th style="text-align: right">Min [ms]</th>
      <th style="text-align: right">Max [ms]</th>
      <th style="text-align: right">Relative</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">29040a827 CHANGELOG.md</code></td>
      <td style="text-align: right">137.5 ± 2.7</td>
      <td style="text-align: right">133.4</td>
      <td style="text-align: right">143.8</td>
      <td style="text-align: right">1.00</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">e63d487fb CHANGELOG.md</code></td>
      <td style="text-align: right">137.8 ± 2.9</td>
      <td style="text-align: right">133.4</td>
      <td style="text-align: right">143.8</td>
      <td style="text-align: right">1.00 ± 0.03</td>
    </tr>
  </tbody>
</table>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Command</th>
      <th style="text-align: right">Mean [ms]</th>
      <th style="text-align: right">Min [ms]</th>
      <th style="text-align: right">Max [ms]</th>
      <th style="text-align: right">Relative</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">29040a827 STABILITY.md</code></td>
      <td style="text-align: right">45.8 ± 2.0</td>
      <td style="text-align: right">42.6</td>
      <td style="text-align: right">51.1</td>
      <td style="text-align: right">1.01 ± 0.06</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">e63d487fb STABILITY.md</code></td>
      <td style="text-align: right">45.5 ± 1.6</td>
      <td style="text-align: right">42.0</td>
      <td style="text-align: right">48.7</td>
      <td style="text-align: right">1.00</td>
    </tr>
  </tbody>
</table>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Command</th>
      <th style="text-align: right">Mean [ms]</th>
      <th style="text-align: right">Min [ms]</th>
      <th style="text-align: right">Max [ms]</th>
      <th style="text-align: right">Relative</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">29040a827 README.md</code></td>
      <td style="text-align: right">101.0 ± 2.6</td>
      <td style="text-align: right">95.8</td>
      <td style="text-align: right">107.2</td>
      <td style="text-align: right">1.00 ± 0.04</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">e63d487fb README.md</code></td>
      <td style="text-align: right">100.8 ± 2.8</td>
      <td style="text-align: right">95.7</td>
      <td style="text-align: right">106.2</td>
      <td style="text-align: right">1.00</td>
    </tr>
  </tbody>
</table>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Command</th>
      <th style="text-align: right">Mean [ms]</th>
      <th style="text-align: right">Min [ms]</th>
      <th style="text-align: right">Max [ms]</th>
      <th style="text-align: right">Relative</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">29040a827 Cargo.toml</code></td>
      <td style="text-align: right">87.3 ± 2.3</td>
      <td style="text-align: right">82.6</td>
      <td style="text-align: right">91.5</td>
      <td style="text-align: right">1.00</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">e63d487fb Cargo.toml</code></td>
      <td style="text-align: right">88.1 ± 2.3</td>
      <td style="text-align: right">83.4</td>
      <td style="text-align: right">93.1</td>
      <td style="text-align: right">1.01 ± 0.04</td>
    </tr>
  </tbody>
</table>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Command</th>
      <th style="text-align: right">Mean [ms]</th>
      <th style="text-align: right">Min [ms]</th>
      <th style="text-align: right">Max [ms]</th>
      <th style="text-align: right">Relative</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">29040a827 gix-blame/src/file/function.rs</code></td>
      <td style="text-align: right">33.2 ± 1.5</td>
      <td style="text-align: right">30.8</td>
      <td style="text-align: right">38.0</td>
      <td style="text-align: right">1.30 ± 0.09</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">e63d487fb gix-blame/src/file/function.rs</code></td>
      <td style="text-align: right">25.5 ± 1.3</td>
      <td style="text-align: right">23.2</td>
      <td style="text-align: right">28.3</td>
      <td style="text-align: right">1.00</td>
    </tr>
  </tbody>
</table>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Command</th>
      <th style="text-align: right">Mean [ms]</th>
      <th style="text-align: right">Min [ms]</th>
      <th style="text-align: right">Max [ms]</th>
      <th style="text-align: right">Relative</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">29040a827 gix-path/src/env/mod.rs</code></td>
      <td style="text-align: right">37.2 ± 1.7</td>
      <td style="text-align: right">34.0</td>
      <td style="text-align: right">41.9</td>
      <td style="text-align: right">1.27 ± 0.08</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">e63d487fb gix-path/src/env/mod.rs</code></td>
      <td style="text-align: right">29.4 ± 1.3</td>
      <td style="text-align: right">27.1</td>
      <td style="text-align: right">32.7</td>
      <td style="text-align: right">1.00</td>
    </tr>
  </tbody>
</table>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Command</th>
      <th style="text-align: right">Mean [ms]</th>
      <th style="text-align: right">Min [ms]</th>
      <th style="text-align: right">Max [ms]</th>
      <th style="text-align: right">Relative</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">29040a827 gix-index/tests/index/file/write.rs</code></td>
      <td style="text-align: right">55.2 ± 2.3</td>
      <td style="text-align: right">51.1</td>
      <td style="text-align: right">59.4</td>
      <td style="text-align: right">1.29 ± 0.09</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">e63d487fb gix-index/tests/index/file/write.rs</code></td>
      <td style="text-align: right">42.7 ± 2.3</td>
      <td style="text-align: right">38.7</td>
      <td style="text-align: right">54.3</td>
      <td style="text-align: right">1.00</td>
    </tr>
  </tbody>
</table>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Command</th>
      <th style="text-align: right">Mean [ms]</th>
      <th style="text-align: right">Min [ms]</th>
      <th style="text-align: right">Max [ms]</th>
      <th style="text-align: right">Relative</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">29040a827 gix-object/src/lib.rs</code></td>
      <td style="text-align: right">76.8 ± 2.3</td>
      <td style="text-align: right">72.0</td>
      <td style="text-align: right">81.6</td>
      <td style="text-align: right">1.07 ± 0.05</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">e63d487fb gix-object/src/lib.rs</code></td>
      <td style="text-align: right">71.7 ± 2.8</td>
      <td style="text-align: right">65.3</td>
      <td style="text-align: right">77.8</td>
      <td style="text-align: right">1.00</td>
    </tr>
  </tbody>
</table>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Command</th>
      <th style="text-align: right">Mean [ms]</th>
      <th style="text-align: right">Min [ms]</th>
      <th style="text-align: right">Max [ms]</th>
      <th style="text-align: right">Relative</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">29040a827 gix-odb/src/store_impls/loose/write.rs</code></td>
      <td style="text-align: right">79.3 ± 2.7</td>
      <td style="text-align: right">74.5</td>
      <td style="text-align: right">84.8</td>
      <td style="text-align: right">1.14 ± 0.06</td>
    </tr>
    <tr>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">e63d487fb gix-odb/src/store_impls/loose/write.rs</code></td>
      <td style="text-align: right">69.6 ± 2.7</td>
      <td style="text-align: right">64.6</td>
      <td style="text-align: right">75.0</td>
      <td style="text-align: right">1.00</td>
    </tr>
  </tbody>
</table>]]></content><author><name>Christoph Rüßler</name><email>christoph.ruessler@mailbox.org</email></author><summary type="html"><![CDATA[In February 2026, we got a PR in gitoxide that substantially improved gix-diff’s tree diff performance. Since gix-blame’s algorithm uses a lot of tree diffs under the hood, I wanted to know what the impact on gix-blame’s performance was.]]></summary></entry><entry><title type="html">gix-blame performance with imara-diff 0.1 and 0.2</title><link href="https://c.rubler.net/blog/2026/01/25/gix-blame-performance-with-imara-diff-0-1-and-0-2.html" rel="alternate" type="text/html" title="gix-blame performance with imara-diff 0.1 and 0.2" /><published>2026-01-25T17:44:00+01:00</published><updated>2026-01-25T17:44:00+01:00</updated><id>https://c.rubler.net/blog/2026/01/25/gix-blame-performance-with-imara-diff-0-1-and-0-2</id><content type="html" xml:base="https://c.rubler.net/blog/2026/01/25/gix-blame-performance-with-imara-diff-0-1-and-0-2.html"><![CDATA[<p>Recently, we started the process of upgrading <code class="language-plaintext highlighter-rouge">gitoxide</code>’s dependency on
<code class="language-plaintext highlighter-rouge">imara-diff</code> from 0.1.8 to 0.2.0 (tracked in <a href="https://github.com/GitoxideLabs/gitoxide/issues/2308">this issue</a>). Because
<code class="language-plaintext highlighter-rouge">imara-diff</code>’s API has changed significantly, the changes are currently behind
a feature flag. What I’ve been wondering, though, is whether this update has
any impact on <code class="language-plaintext highlighter-rouge">gix-blame</code>’s performance as <code class="language-plaintext highlighter-rouge">gix-blame</code> spends a lot of time
diffing two versions of a file.</p>

<p>In order to collect some data, I compiled two versions of the <code class="language-plaintext highlighter-rouge">gix</code> binary via
<code class="language-plaintext highlighter-rouge">cargo build --release --features blame-experimental</code> and <code class="language-plaintext highlighter-rouge">cargo build
--release</code>. Then I used hyperfine to run <code class="language-plaintext highlighter-rouge">gix blame</code> on a couple of files in my
local copy of the <code class="language-plaintext highlighter-rouge">gitoxide</code> repo. The results are below.</p>

<p>It seems that the version using <code class="language-plaintext highlighter-rouge">imara-diff</code> 0.2 might have a slight advantage
when it comes to files that have changed a lot over the course of this repo’s
history, such as <code class="language-plaintext highlighter-rouge">CHANGELOG.md</code> or <code class="language-plaintext highlighter-rouge">Cargo.toml</code>, but it’s still rather close,
so I wouldn’t draw too many conclusions.</p>

<h2 id="detailed-results">Detailed results</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯ env GIT_DIR="$HOME/github/Byron/gitoxide/.git" BASELINE_EXECUTABLE="$HOME/bin/gix-blame-2026-01-25-3b6650a66" COMPARISON_EXECUTABLE="$HOME/bin/gix-blame-experimental-2026-01-25-3b6650a66" just benchmark-gix-blame
hyperfine "${BASELINE_EXECUTABLE} blame CHANGELOG.md" "${COMPARISON_EXECUTABLE} blame CHANGELOG.md"
Benchmark 1: /home/christoph/bin/gix-blame-2026-01-25-3b6650a66 blame CHANGELOG.md
  Time (mean ± σ):     141.6 ms ±   3.3 ms    [User: 100.8 ms, System: 40.7 ms]
  Range (min … max):   133.7 ms … 146.6 ms    21 runs

Benchmark 2: /home/christoph/bin/gix-blame-experimental-2026-01-25-3b6650a66 blame CHANGELOG.md
  Time (mean ± σ):     137.0 ms ±   3.4 ms    [User: 95.7 ms, System: 41.2 ms]
  Range (min … max):   126.5 ms … 142.3 ms    21 runs

Summary
  /home/christoph/bin/gix-blame-experimental-2026-01-25-3b6650a66 blame CHANGELOG.md ran
    1.03 ± 0.04 times faster than /home/christoph/bin/gix-blame-2026-01-25-3b6650a66 blame CHANGELOG.md

hyperfine "${BASELINE_EXECUTABLE} blame STABILITY.md" "${COMPARISON_EXECUTABLE} blame STABILITY.md"
Benchmark 1: /home/christoph/bin/gix-blame-2026-01-25-3b6650a66 blame STABILITY.md
  Time (mean ± σ):      43.7 ms ±   2.2 ms    [User: 30.1 ms, System: 13.3 ms]
  Range (min … max):    40.8 ms …  52.5 ms    56 runs

Benchmark 2: /home/christoph/bin/gix-blame-experimental-2026-01-25-3b6650a66 blame STABILITY.md
  Time (mean ± σ):      42.9 ms ±   1.4 ms    [User: 30.1 ms, System: 12.6 ms]
  Range (min … max):    40.8 ms …  47.2 ms    65 runs

Summary
  /home/christoph/bin/gix-blame-experimental-2026-01-25-3b6650a66 blame STABILITY.md ran
    1.02 ± 0.06 times faster than /home/christoph/bin/gix-blame-2026-01-25-3b6650a66 blame STABILITY.md

hyperfine "${BASELINE_EXECUTABLE} blame README.md" "${COMPARISON_EXECUTABLE} blame README.md"
Benchmark 1: /home/christoph/bin/gix-blame-2026-01-25-3b6650a66 blame README.md
  Time (mean ± σ):     102.6 ms ±   3.9 ms    [User: 72.9 ms, System: 29.4 ms]
  Range (min … max):    95.0 ms … 109.6 ms    28 runs

Benchmark 2: /home/christoph/bin/gix-blame-experimental-2026-01-25-3b6650a66 blame README.md
  Time (mean ± σ):     102.2 ms ±   2.1 ms    [User: 72.2 ms, System: 29.9 ms]
  Range (min … max):    98.8 ms … 106.7 ms    28 runs

Summary
  /home/christoph/bin/gix-blame-experimental-2026-01-25-3b6650a66 blame README.md ran
    1.00 ± 0.04 times faster than /home/christoph/bin/gix-blame-2026-01-25-3b6650a66 blame README.md

hyperfine "${BASELINE_EXECUTABLE} blame Cargo.toml" "${COMPARISON_EXECUTABLE} blame Cargo.toml"
Benchmark 1: /home/christoph/bin/gix-blame-2026-01-25-3b6650a66 blame Cargo.toml
  Time (mean ± σ):      86.3 ms ±   2.7 ms    [User: 61.8 ms, System: 24.3 ms]
  Range (min … max):    81.8 ms …  91.9 ms    34 runs

Benchmark 2: /home/christoph/bin/gix-blame-experimental-2026-01-25-3b6650a66 blame Cargo.toml
  Time (mean ± σ):      82.7 ms ±   2.3 ms    [User: 59.7 ms, System: 22.8 ms]
  Range (min … max):    79.7 ms …  90.2 ms    34 runs

Summary
  /home/christoph/bin/gix-blame-experimental-2026-01-25-3b6650a66 blame Cargo.toml ran
    1.04 ± 0.04 times faster than /home/christoph/bin/gix-blame-2026-01-25-3b6650a66 blame Cargo.toml

hyperfine "${BASELINE_EXECUTABLE} blame gix-blame/src/file/function.rs" "${COMPARISON_EXECUTABLE} blame gix-blame/src/file/function.rs"
Benchmark 1: /home/christoph/bin/gix-blame-2026-01-25-3b6650a66 blame gix-blame/src/file/function.rs
  Time (mean ± σ):      31.6 ms ±   1.6 ms    [User: 21.7 ms, System: 9.7 ms]
  Range (min … max):    29.6 ms …  38.0 ms    77 runs

Benchmark 2: /home/christoph/bin/gix-blame-experimental-2026-01-25-3b6650a66 blame gix-blame/src/file/function.rs
  Time (mean ± σ):      31.3 ms ±   0.9 ms    [User: 20.8 ms, System: 10.4 ms]
  Range (min … max):    29.6 ms …  34.6 ms    93 runs

Summary
  /home/christoph/bin/gix-blame-experimental-2026-01-25-3b6650a66 blame gix-blame/src/file/function.rs ran
    1.01 ± 0.06 times faster than /home/christoph/bin/gix-blame-2026-01-25-3b6650a66 blame gix-blame/src/file/function.rs

hyperfine "${BASELINE_EXECUTABLE} blame gix-path/src/env/mod.rs" "${COMPARISON_EXECUTABLE} blame gix-path/src/env/mod.rs"
Benchmark 1: /home/christoph/bin/gix-blame-2026-01-25-3b6650a66 blame gix-path/src/env/mod.rs
  Time (mean ± σ):      35.7 ms ±   2.7 ms    [User: 25.3 ms, System: 10.0 ms]
  Range (min … max):    32.5 ms …  50.9 ms    58 runs

Benchmark 2: /home/christoph/bin/gix-blame-experimental-2026-01-25-3b6650a66 blame gix-path/src/env/mod.rs
  Time (mean ± σ):      35.4 ms ±   2.0 ms    [User: 25.2 ms, System: 10.1 ms]
  Range (min … max):    32.6 ms …  43.4 ms    79 runs

Summary
  /home/christoph/bin/gix-blame-experimental-2026-01-25-3b6650a66 blame gix-path/src/env/mod.rs ran
    1.01 ± 0.10 times faster than /home/christoph/bin/gix-blame-2026-01-25-3b6650a66 blame gix-path/src/env/mod.rs

hyperfine "${BASELINE_EXECUTABLE} blame gix-index/tests/index/file/write.rs" "${COMPARISON_EXECUTABLE} blame gix-index/tests/index/file/write.rs"
Benchmark 1: /home/christoph/bin/gix-blame-2026-01-25-3b6650a66 blame gix-index/tests/index/file/write.rs
  Time (mean ± σ):      53.2 ms ±   2.6 ms    [User: 40.1 ms, System: 12.8 ms]
  Range (min … max):    49.0 ms …  63.0 ms    47 runs

Benchmark 2: /home/christoph/bin/gix-blame-experimental-2026-01-25-3b6650a66 blame gix-index/tests/index/file/write.rs
  Time (mean ± σ):      53.3 ms ±   2.5 ms    [User: 40.8 ms, System: 12.3 ms]
  Range (min … max):    49.1 ms …  60.5 ms    58 runs

Summary
  /home/christoph/bin/gix-blame-2026-01-25-3b6650a66 blame gix-index/tests/index/file/write.rs ran
    1.00 ± 0.07 times faster than /home/christoph/bin/gix-blame-experimental-2026-01-25-3b6650a66 blame gix-index/tests/index/file/write.rs

hyperfine "${BASELINE_EXECUTABLE} blame gix-object/src/lib.rs" "${COMPARISON_EXECUTABLE} blame gix-object/src/lib.rs"
Benchmark 1: /home/christoph/bin/gix-blame-2026-01-25-3b6650a66 blame gix-object/src/lib.rs
  Time (mean ± σ):      75.6 ms ±   2.8 ms    [User: 56.9 ms, System: 18.4 ms]
  Range (min … max):    71.4 ms …  82.1 ms    37 runs

Benchmark 2: /home/christoph/bin/gix-blame-experimental-2026-01-25-3b6650a66 blame gix-object/src/lib.rs
  Time (mean ± σ):      76.5 ms ±   2.9 ms    [User: 59.2 ms, System: 17.0 ms]
  Range (min … max):    71.6 ms …  82.7 ms    39 runs

Summary
  /home/christoph/bin/gix-blame-2026-01-25-3b6650a66 blame gix-object/src/lib.rs ran
    1.01 ± 0.05 times faster than /home/christoph/bin/gix-blame-experimental-2026-01-25-3b6650a66 blame gix-object/src/lib.rs

hyperfine "${BASELINE_EXECUTABLE} blame gix-odb/src/store_impls/loose/write.rs" "${COMPARISON_EXECUTABLE} blame gix-odb/src/store_impls/loose/write.rs"
Benchmark 1: /home/christoph/bin/gix-blame-2026-01-25-3b6650a66 blame gix-odb/src/store_impls/loose/write.rs
  Time (mean ± σ):      75.1 ms ±   2.3 ms    [User: 58.8 ms, System: 16.0 ms]
  Range (min … max):    70.4 ms …  81.3 ms    36 runs

Benchmark 2: /home/christoph/bin/gix-blame-experimental-2026-01-25-3b6650a66 blame gix-odb/src/store_impls/loose/write.rs
  Time (mean ± σ):      77.1 ms ±   3.2 ms    [User: 60.1 ms, System: 16.9 ms]
  Range (min … max):    70.8 ms …  82.3 ms    37 runs

Summary
  /home/christoph/bin/gix-blame-2026-01-25-3b6650a66 blame gix-odb/src/store_impls/loose/write.rs ran
    1.03 ± 0.05 times faster than /home/christoph/bin/gix-blame-experimental-2026-01-25-3b6650a66 blame gix-odb/src/store_impls/loose/write.rs
</code></pre></div></div>]]></content><author><name>Christoph Rüßler</name><email>christoph.ruessler@mailbox.org</email></author><summary type="html"><![CDATA[Recently, we started the process of upgrading gitoxide’s dependency on imara-diff from 0.1.8 to 0.2.0 (tracked in this issue). Because imara-diff’s API has changed significantly, the changes are currently behind a feature flag. What I’ve been wondering, though, is whether this update has any impact on gix-blame’s performance as gix-blame spends a lot of time diffing two versions of a file.]]></summary></entry><entry><title type="html">Github completion in `nvim` when called by `gh`</title><link href="https://c.rubler.net/blog/2025/10/09/github-completion-in-nvim-when-called-by-gh.html" rel="alternate" type="text/html" title="Github completion in `nvim` when called by `gh`" /><published>2025-10-09T20:36:00+02:00</published><updated>2025-10-09T20:36:00+02:00</updated><id>https://c.rubler.net/blog/2025/10/09/github-completion-in-nvim-when-called-by-gh</id><content type="html" xml:base="https://c.rubler.net/blog/2025/10/09/github-completion-in-nvim-when-called-by-gh.html"><![CDATA[<p>I frequently use the <a href="https://cli.github.com/">Github CLI</a> for things like creating PRs (<code class="language-plaintext highlighter-rouge">gh pr
create</code>) or writing comments on PRs (<code class="language-plaintext highlighter-rouge">gh pr comment</code>). In these instances, it
is really helpful to have completion for issues and users via
<a href="https://github.com/Kaiser-Yang/blink-cmp-git"><code class="language-plaintext highlighter-rouge">blink-cmp-git</code></a>. For example, if you type <code class="language-plaintext highlighter-rouge">#</code>, you get a popup
with a list of Github issues, allowing you to easily reference relevant ones
without the need to context-switch.</p>

<p>However, I only want completion in very specific circumstances, when <code class="language-plaintext highlighter-rouge">nvim</code> is
started by <code class="language-plaintext highlighter-rouge">gh</code>. While the docs describe how <code class="language-plaintext highlighter-rouge">blink-cmp-git</code> can be enabled for
all Markdown files, that’s not what I want, as I find completion popups
annoying most of the time and want to limit the number of contexts they appear
in. So I was looking for a way to enable completion only when <code class="language-plaintext highlighter-rouge">nvim</code> is started
by <code class="language-plaintext highlighter-rouge">gh</code> and came up with the following solution.</p>

<p>First, I configured <code class="language-plaintext highlighter-rouge">GH_EDITOR</code> in <code class="language-plaintext highlighter-rouge">$HOME/.profile</code>:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># in `$HOME/.profile`</span>
<span class="nb">export </span><span class="nv">GH_EDITOR</span><span class="o">=</span><span class="s2">"nvim -c 'lua vim.g.enable_git_completion=true'"</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">gh</code> reads <code class="language-plaintext highlighter-rouge">GH_EDITOR</code> and starts <code class="language-plaintext highlighter-rouge">nvim</code>, passing a small piece of Lua that
sets a global variable to <code class="language-plaintext highlighter-rouge">true</code>. This variable is then read in my Neovim
config in order to enable Github completion:</p>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">enabled</span> <span class="o">=</span> <span class="k">function</span><span class="p">()</span>
  <span class="k">return</span> <span class="n">vim</span><span class="p">.</span><span class="n">tbl_contains</span><span class="p">({</span> <span class="s2">"octo"</span><span class="p">,</span> <span class="s2">"gitcommit"</span> <span class="p">},</span> <span class="n">vim</span><span class="p">.</span><span class="n">bo</span><span class="p">.</span><span class="n">filetype</span><span class="p">)</span> <span class="ow">or</span> <span class="n">vim</span><span class="p">.</span><span class="n">g</span><span class="p">.</span><span class="n">enable_git_completion</span> <span class="o">==</span> <span class="kc">true</span>
<span class="k">end</span>
</code></pre></div></div>

<p>That way, <code class="language-plaintext highlighter-rouge">blink-cmp-git</code> is enabled for filetypes that are very likely related
to Github as well as when <code class="language-plaintext highlighter-rouge">enable_git_completion</code> is <code class="language-plaintext highlighter-rouge">true</code>.</p>]]></content><author><name>Christoph Rüßler</name><email>christoph.ruessler@mailbox.org</email></author><summary type="html"><![CDATA[I frequently use the Github CLI for things like creating PRs (gh pr create) or writing comments on PRs (gh pr comment). In these instances, it is really helpful to have completion for issues and users via blink-cmp-git. For example, if you type #, you get a popup with a list of Github issues, allowing you to easily reference relevant ones without the need to context-switch.]]></summary></entry><entry><title type="html">Using SvelteKit’s `resolve` with dynamic paths and parameters</title><link href="https://c.rubler.net/blog/2025/09/15/using-sveltekit-s-resolve-with-dynamic-paths-and-parameters.html" rel="alternate" type="text/html" title="Using SvelteKit’s `resolve` with dynamic paths and parameters" /><published>2025-09-15T16:07:00+02:00</published><updated>2025-09-15T16:07:00+02:00</updated><id>https://c.rubler.net/blog/2025/09/15/using-sveltekit-s-resolve-with-dynamic-paths-and-parameters</id><content type="html" xml:base="https://c.rubler.net/blog/2025/09/15/using-sveltekit-s-resolve-with-dynamic-paths-and-parameters.html"><![CDATA[<p>I’m just documenting a minor doc issue I had with SvelteKit recently, in order
to prevent me from running into it again.</p>

<p><a href="https://svelte.dev/docs/kit/$app-paths#resolve">The docs</a> don’t mention that you are not free to choose
placeholder names in <code class="language-plaintext highlighter-rouge">resolve</code>’s first parameter. In fact, you are constrained
to the actual directory structure inside <code class="language-plaintext highlighter-rouge">src/routes</code>, a fact that was not
immediately obvious to me. While this constraint probably can be inferred from
<code class="language-plaintext highlighter-rouge">resolve</code> expecting a “route ID” as its first parameter, something that <em>is</em>
mentioned in the docs, I still found it confusing.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>└── blog
    └── [slug]
        └── +page.svelte
</code></pre></div></div>

<p>So, given the above directory structure, you’ll need to use
<code class="language-plaintext highlighter-rouge">resolve('/blog/[slug]', { slug: 'slug' })</code> while anything else, such as
<code class="language-plaintext highlighter-rouge">resolve('/blog/[id]', { id: 'id' })</code>, will not work. Instead, TypeScript will
give you an error that unfortunately makes it quite hard to figure out the
actual problem: <code class="language-plaintext highlighter-rouge">Expected 1 arguments, but got 2.</code> The reason for this is that
the parameters’ types are conditional on the first parameter being an actually
existing route. Only if it is one, <code class="language-plaintext highlighter-rouge">resolve</code> will accept a second parameter.</p>

<p>I initially came across this issue as an update to <code class="language-plaintext highlighter-rouge">eslint-plugin-svelte</code> came
with a new lint rule, <a href="https://sveltejs.github.io/eslint-plugin-svelte/rules/no-navigation-without-resolve/"><code class="language-plaintext highlighter-rouge">svelte/no-navigation-without-resolve</code></a>, in
response to which I rewrote a couple of lines related to generating URLs.</p>]]></content><author><name>Christoph Rüßler</name><email>christoph.ruessler@mailbox.org</email></author><summary type="html"><![CDATA[I’m just documenting a minor doc issue I had with SvelteKit recently, in order to prevent me from running into it again.]]></summary></entry><entry><title type="html">Missing user profile directory in Nix after install</title><link href="https://c.rubler.net/blog/2025/09/12/missing-user-profile-directory-in-nix-after-install.html" rel="alternate" type="text/html" title="Missing user profile directory in Nix after install" /><published>2025-09-12T10:27:00+02:00</published><updated>2025-09-12T10:27:00+02:00</updated><id>https://c.rubler.net/blog/2025/09/12/missing-user-profile-directory-in-nix-after-install</id><content type="html" xml:base="https://c.rubler.net/blog/2025/09/12/missing-user-profile-directory-in-nix-after-install.html"><![CDATA[<p>Recently, on one of my Ubuntu machines, I decided to start relying on Nix for
installing and managing a couple of developers tools I regularly use, such as
<code class="language-plaintext highlighter-rouge">eza</code>, <code class="language-plaintext highlighter-rouge">hyperfine</code>, <code class="language-plaintext highlighter-rouge">samply</code> and <code class="language-plaintext highlighter-rouge">stylua</code>. Most of these tools are only
available in older versions in the Ubuntu package repositories or they’re not
available at all, but all are available in fairly recent versions in
<a href="https://search.nixos.org/packages">nixpkgs</a>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯ nix-env -q
eza-0.23.1
hyperfine-1.19.0
samply-0.13.1
stylua-2.1.0
</code></pre></div></div>

<p>I already had an old Nix installation from a couple years back, but it seemed
to not be in a good state, at least <code class="language-plaintext highlighter-rouge">nix doctor</code> (now <code class="language-plaintext highlighter-rouge">nix config check</code>) told
me there were a few issues. Unfortunately, <code class="language-plaintext highlighter-rouge">nix doctor</code> didn’t tell me how to
fix them. In order to reset to a known good state, I decided to reinstall Nix.</p>

<p>The <a href="https://nix.dev/manual/nix/2.30/installation/installing-binary.html">installation</a> went well, except the multi-user installation
did not automatically create a profile directory inside
<code class="language-plaintext highlighter-rouge">/nix/var/nix/profiles/per-user/</code>. This left me with the following error
message when trying to install any package:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯ nix-env -iA nixpkgs.stylua
installing 'stylua-2.1.0'
this path will be fetched (1.94 MiB download, 7.93 MiB unpacked):
  /nix/store/jax2k9jfays63q97kiz52mggswaar8g8-stylua-2.1.0
copying path '/nix/store/jax2k9jfays63q97kiz52mggswaar8g8-stylua-2.1.0' from 'https://cache.nixos.org'...
building '/nix/store/a5wiywv48g255zqql0xcaasr6iikvs6s-user-environment.drv'...
error: opening lock file '/nix/var/nix/profiles/per-user/christoph/profile.lock': No such file or directory
</code></pre></div></div>

<p>The <a href="https://nix.dev/manual/nix/2.30/installation/installing-binary.html">manual</a> doesn’t mention the need to create a profile
directory, so I was a bit unsure as to what the canonical way of fixing this
issue was. In the end, I decided to create the missing directory by hand at
which point I was able to install packages.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nix/profiles/per-user🔒
❯ ls -al
drwxr-xr-x - root 11 Sep 20:17 root

nix/profiles/per-user🔒
❯ sudo mkdir christoph

nix/profiles/per-user🔒
❯ sudo chown $USER: christoph/

nix/profiles/per-user🔒
❯ ls -al
drwxr-xr-x - christoph 11 Sep 20:40 christoph
drwxr-xr-x - root      11 Sep 20:17 root
</code></pre></div></div>

<p>If it turns out this is the wrong way of fixing things, I’m going to update
this post.</p>]]></content><author><name>Christoph Rüßler</name><email>christoph.ruessler@mailbox.org</email></author><category term="today-i-debugged" /><summary type="html"><![CDATA[Recently, on one of my Ubuntu machines, I decided to start relying on Nix for installing and managing a couple of developers tools I regularly use, such as eza, hyperfine, samply and stylua. Most of these tools are only available in older versions in the Ubuntu package repositories or they’re not available at all, but all are available in fairly recent versions in nixpkgs.]]></summary></entry><entry><title type="html">Workaround for missing `cargo install –name`</title><link href="https://c.rubler.net/blog/2025/08/30/workaround-for-missing-cargo-install-name.html" rel="alternate" type="text/html" title="Workaround for missing `cargo install –name`" /><published>2025-08-30T11:57:00+02:00</published><updated>2025-08-30T11:57:00+02:00</updated><id>https://c.rubler.net/blog/2025/08/30/workaround-for-missing-cargo-install-name</id><content type="html" xml:base="https://c.rubler.net/blog/2025/08/30/workaround-for-missing-cargo-install-name.html"><![CDATA[<p>Last week, I wanted to install a Rust binary using <code class="language-plaintext highlighter-rouge">cargo install</code>, but I wanted to
give it a custom name. My specific use case was installing two different
versions of the same binary and differentiate them via a suffix, resulting in
something like <code class="language-plaintext highlighter-rouge">gitui</code> and <code class="language-plaintext highlighter-rouge">gitui@v0.27.0</code>.</p>

<p>This doesn’t exist in <code class="language-plaintext highlighter-rouge">cargo install</code> yet and it is also not likely to be
implemented soon, according to <a href="https://github.com/rust-lang/cargo/issues/12366">this PR</a>. There’s a quick
workaround, though, that I’m documenting here, mostly because I didn’t
immediately think of it, instead putting time into following a few of the links
in the PR.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># on the branch that’s supposed to supply the unsuffixed binary
❯ cargo install --path .

# and on the branch that’s supposed to result in a suffixed binary
❯ cargo build --release
❯ cp target/release/gitui ~/bin/gitui@v0.27.0
</code></pre></div></div>]]></content><author><name>Christoph Rüßler</name><email>christoph.ruessler@mailbox.org</email></author><summary type="html"><![CDATA[Last week, I wanted to install a Rust binary using cargo install, but I wanted to give it a custom name. My specific use case was installing two different versions of the same binary and differentiate them via a suffix, resulting in something like gitui and gitui@v0.27.0.]]></summary></entry><entry><title type="html">Creating a password using `pass` based on a limited character set</title><link href="https://c.rubler.net/blog/2025/08/29/creating-a-password-using-pass-based-on-a-limited-character-set.html" rel="alternate" type="text/html" title="Creating a password using `pass` based on a limited character set" /><published>2025-08-29T21:25:00+02:00</published><updated>2025-08-29T21:25:00+02:00</updated><id>https://c.rubler.net/blog/2025/08/29/creating-a-password-using-pass-based-on-a-limited-character-set</id><content type="html" xml:base="https://c.rubler.net/blog/2025/08/29/creating-a-password-using-pass-based-on-a-limited-character-set.html"><![CDATA[<p>Recently, I had to create a new password for a web service that had very
restrictive character requirements. Only lowercase letters, uppercase letters,
numbers and a few special characters were allowed. Since I couldn’t quickly
find a single comprehensive example of how to do this with <a href="https://www.passwordstore.org/"><code class="language-plaintext highlighter-rouge">pass</code></a> (my
password manager), I’m documenting my solution, so that future me can refer
back back to it in case the issue comes up again. The solution is based on
<code class="language-plaintext highlighter-rouge">man pass</code>, but I find the syntax for <code class="language-plaintext highlighter-rouge">PASSWORD_STORE_CHARACTER_SET</code> hard to
remember, so this should be helpful later.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯ env PASSWORD_STORE_CHARACTER_SET="[a-zA-Z0-9]\.:@!&amp;,\/;=\\" pass generate example.com 16
</code></pre></div></div>]]></content><author><name>Christoph Rüßler</name><email>christoph.ruessler@mailbox.org</email></author><summary type="html"><![CDATA[Recently, I had to create a new password for a web service that had very restrictive character requirements. Only lowercase letters, uppercase letters, numbers and a few special characters were allowed. Since I couldn’t quickly find a single comprehensive example of how to do this with pass (my password manager), I’m documenting my solution, so that future me can refer back back to it in case the issue comes up again. The solution is based on man pass, but I find the syntax for PASSWORD_STORE_CHARACTER_SET hard to remember, so this should be helpful later.]]></summary></entry><entry><title type="html">Using pass to store a GitHub access token</title><link href="https://c.rubler.net/blog/2022/08/07/using-pass-to-store-a-github-access-token.html" rel="alternate" type="text/html" title="Using pass to store a GitHub access token" /><published>2022-08-07T21:52:00+02:00</published><updated>2022-08-07T21:52:00+02:00</updated><id>https://c.rubler.net/blog/2022/08/07/using-pass-to-store-a-github-access-token</id><content type="html" xml:base="https://c.rubler.net/blog/2022/08/07/using-pass-to-store-a-github-access-token.html"><![CDATA[<p>If you want to connect to GitHub using HTTPS, you need an <a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token#using-a-token-on-the-command-line">access
token</a>. You can store that token in <code class="language-plaintext highlighter-rouge">pass</code> and use the following
snippet to make git ask <code class="language-plaintext highlighter-rouge">pass</code> for the token. I put the snippet into
<code class="language-plaintext highlighter-rouge">.config/git/user</code> and include that file in my main <code class="language-plaintext highlighter-rouge">.gitconfig</code>. This example
uses <code class="language-plaintext highlighter-rouge">pass</code>, but you can replace the call with anything you like.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># It is necessary to quote the command and escape any double quotes it contains
# (the example at [1] does not do that).
#
# [1]: https://git-scm.com/docs/api-credentials#_credential_helpers
[credential "https://github.com"]
  username = …
  helper = !"f() { echo \"password=`pass access-tokens/github.com`\"; }; f"
</code></pre></div></div>]]></content><author><name>Christoph Rüßler</name><email>christoph.ruessler@mailbox.org</email></author><summary type="html"><![CDATA[If you want to connect to GitHub using HTTPS, you need an access token. You can store that token in pass and use the following snippet to make git ask pass for the token. I put the snippet into .config/git/user and include that file in my main .gitconfig. This example uses pass, but you can replace the call with anything you like.]]></summary></entry><entry><title type="html">elm-logo can solve the Towers of Hanoi problem</title><link href="https://c.rubler.net/blog/2022/07/18/elm-logo-can-solve-the-towers-of-hanoi-problem.html" rel="alternate" type="text/html" title="elm-logo can solve the Towers of Hanoi problem" /><published>2022-07-18T20:42:00+02:00</published><updated>2022-07-18T20:42:00+02:00</updated><id>https://c.rubler.net/blog/2022/07/18/elm-logo-can-solve-the-towers-of-hanoi-problem</id><content type="html" xml:base="https://c.rubler.net/blog/2022/07/18/elm-logo-can-solve-the-towers-of-hanoi-problem.html"><![CDATA[<p>This is another post about elm-logo being able to run an algorithm
from Rosetta Code although this post comes a bit late like the
previous one since the required features landed quite a while ago.
This time, it is <a href="https://rosettacode.org/wiki/Towers_of_Hanoi#Logo">the Towers of Hanoi problem</a>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>to move :n :from :to :via
  if :n = 0 [stop]
  move :n-1 :from :via :to
  (print [Move disk from] :from [to] :to)
  move :n-1 :via :to :from
end
move 4 "left "middle "right
</code></pre></div></div>

<p><a href="https://c.rubler.net/elm-logo/?initialCommandLine=to+move+%3An+%3Afrom+%3Ato+%3Avia%0A++if+%3An+%3D+0+%5Bstop%5D%0A++move+%3An-1+%3Afrom+%3Avia+%3Ato%0A++%28print+%5BMove+disk+from%5D+%3Afrom+%5Bto%5D+%3Ato%29%0A++move+%3An-1+%3Avia+%3Ato+%3Afrom%0Aend%0Amove+4+%22left+%22middle+%22right">Open snippet in elm-logo (in elm-logo, click “Run” to run it)</a></p>]]></content><author><name>Christoph Rüßler</name><email>christoph.ruessler@mailbox.org</email></author><category term="elm-logo" /><summary type="html"><![CDATA[This is another post about elm-logo being able to run an algorithm from Rosetta Code although this post comes a bit late like the previous one since the required features landed quite a while ago. This time, it is the Towers of Hanoi problem.]]></summary></entry><entry><title type="html">elm-logo can draw a fractal tree</title><link href="https://c.rubler.net/blog/2022/07/18/elm-logo-can-draw-a-fractal-tree.html" rel="alternate" type="text/html" title="elm-logo can draw a fractal tree" /><published>2022-07-18T17:01:00+02:00</published><updated>2022-07-18T17:01:00+02:00</updated><id>https://c.rubler.net/blog/2022/07/18/elm-logo-can-draw-a-fractal-tree</id><content type="html" xml:base="https://c.rubler.net/blog/2022/07/18/elm-logo-can-draw-a-fractal-tree.html"><![CDATA[<p>This post comes a bit late, but since quite a while elm-logo has all
the features reqired to draw a fractal tree using this <a href="https://rosettacode.org/wiki/Fractal_tree#Logo">algorithm from
Rosetta Code</a> (only <code class="language-plaintext highlighter-rouge">pendown</code> was added).</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>to tree :depth :length :scale :angle
  if :depth=0 [stop]
  setpensize round :depth/2
  forward :length
  right :angle
  tree :depth-1 :length*:scale :scale :angle
  left 2*:angle
  tree :depth-1 :length*:scale :scale :angle
  right :angle
  back :length
end

clearscreen
pendown
tree 10 80 0.7 30
</code></pre></div></div>

<p><a href="https://c.rubler.net/elm-logo/?initialCommandLine=to+tree+%3Adepth+%3Alength+%3Ascale+%3Aangle%0A++if+%3Adepth%3D0+%5Bstop%5D%0A++setpensize+round+%3Adepth%2F2%0A++forward+%3Alength%0A++right+%3Aangle%0A++tree+%3Adepth-1+%3Alength*%3Ascale+%3Ascale+%3Aangle%0A++left+2*%3Aangle%0A++tree+%3Adepth-1+%3Alength*%3Ascale+%3Ascale+%3Aangle%0A++right+%3Aangle%0A++back+%3Alength%0Aend%0A%0Aclearscreen%0Apendown%0Atree+10+80+0.7+30">Open snippet in elm-logo (in elm-logo, click “Run” to run it)</a></p>]]></content><author><name>Christoph Rüßler</name><email>christoph.ruessler@mailbox.org</email></author><category term="elm-logo" /><summary type="html"><![CDATA[This post comes a bit late, but since quite a while elm-logo has all the features reqired to draw a fractal tree using this algorithm from Rosetta Code (only pendown was added).]]></summary></entry></feed>