<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>leojofer.com</title><description>Leo Jofer studies computer science at UC Berkeley and TU Munich, works at Hetzner, builds the git forge lager.sh and writes about Git, Jujutsu and computing.</description><link>https://leojofer.com/</link><item><title>Git to Jujutsu from first principles</title><link>https://leojofer.com/log/git-to-jujutsu/</link><guid isPermaLink="true">https://leojofer.com/log/git-to-jujutsu/</guid><pubDate>Mon, 26 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;You probably already know about Git, but bear 🐻 with me. This will all be relevant in a second.&lt;/p&gt;
&lt;p&gt;This is a Git commit graph. It has nodes (commits) which contain changes and are linked hierarchically. Commits are identified by a content-based ID and they can split off into different branches.&lt;/p&gt;
&lt;p&gt;Your uncomitted work exists &quot;on top&quot; of some commit (here: the box). So far, so good. You can store unfinished work in stashes (illustrated here as the grey bubble).&lt;/p&gt;
&lt;div&gt;
&lt;p&gt;&lt;img src=&quot;https://leojofer.com/_astro/commit_graph.k9RsMeiq.png&quot; alt=&quot;Git commit graph with uncommitted work on top and a stash&quot; /&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;In theory, very beautiful, very sleek.&lt;/p&gt;
&lt;p&gt;In practice? Well .. &lt;a href=&quot;https://xkcd.com/1597/&quot;&gt;relevant xkcd&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Let&apos;s suppose I edit an earlier commit in this beautiful commit graph and rebase:&lt;/p&gt;
&lt;div&gt;
&lt;p&gt;&lt;img src=&quot;https://leojofer.com/_astro/commit_history.DMvr6ehT.jpeg&quot; alt=&quot;Git commit history&quot; /&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Because all commit IDs are bound to their contents, this means that &lt;em&gt;every&lt;/em&gt; ID in this graph changes.&lt;/p&gt;
&lt;div&gt;
&lt;p&gt;&lt;img src=&quot;https://leojofer.com/_astro/commit_history_highlighted.CBy14R0A.jpeg&quot; alt=&quot;Git commit history, with IDs highlighted&quot; /&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Our tooling loves this (/s). None of the commits above &quot;Implement sporgler&quot; have actually changed in their meaning. They are just linked to a different commit. And they have a different identity now after the rebase. So how can we avoid this?&lt;/p&gt;
&lt;p&gt;The required change appears obvious in retrospect: make Change IDs stable across content changes. This way, we are referring to the intended unit of change rather than the mechanical one, so to speak.&lt;/p&gt;
&lt;p&gt;But if we can track a unit of change as it - well - changes, then we can simplify so much further. Why be &quot;on top&quot; of a commit? We can just be inside of a commit all the time.&lt;/p&gt;
&lt;p&gt;And if we are, then a merge conflict is also just a commit. A commit that we can jump in and out of --  and fix later if now the timing isn&apos;t right. This means you can&apos;t really get stuck anymore. Hard Git things become easy.&lt;/p&gt;
&lt;p&gt;And now you may be thinking: well, what if I accidentally squash two commits or delete something? Jujutsu has local VCS-level undo, so if you screw up it&apos;s just jj undo and everything is back to how it used to be.&lt;/p&gt;
&lt;p&gt;Oh and it works on top of Git :)&lt;/p&gt;
</content:encoded></item></channel></rss>