Some Definitions
Jujutsu (jj on the CLI) is a version control system, like Git. In fact, by default, it uses Git behind the scenes. Different people have different reasons for liking jj, but most boil down to "there are fewer commands, but they're powerful and more intuitive". I'm not going to wax poetic about jj, but I'm a happy user and I've replaced it for more or less all my VCS needs.1
"The Megamerge" is just a fancy name for "creating merge commits, usually with more than two parents". Other folks have written more about this flow in jj, see Jujutsu Megamerges and jj absorb or Jujutsu megamerges for fun and profit.
"Houseplant programming" is basically just writing purpose-built software for yourself. Kinda like the jigs I had written about before, but perhaps a bit larger in scope. Less tooling/infra, more application. The idea is also elegantly described in An app can be a home-cooked meal.
On Houseplant Programming
I have many such houseplant programs I use on a day-to-day basis. For example, there's stronk for tracking the specific vagarities of my exercise routine, grok for sharing a grocery list with my partner, the uninspiringly named logseq-ui for managing my tasks in a Logseq-backed TUI, etc, etc.
In the "houseplant-adjacent" category, there's all the random modifications I've made to assorted open-source tools I use. And with the advent of LLMs becoming legitimately useful for software development, I've noticed a new pattern emerging in my usage and development of personal software based on OSS. The progression looks something like:
- Use some open-source project
- Desire some extremely specific feature(s) or change(s) to it
- Have LLMs add the feature(s)
- Sanity check the results
Pretty straightforward! The recent innovation for me personally has been using jj megamerges to manage the individual features I'm adding.
aerc: A Case Study
I've been using aerc as my main email client for several years now. It's fast (especially paired with notmuch), resource-efficient, works offline,2, and lets me manage all ~six of my mailboxes from one interface.3
But recently, I've been wanting two specific features to exist in aerc:
- Forwarding HTML emails - This is a pretty common operation in many mail clients, but in mainline
aerc, the:forwardcommand just forwards all the HTML markup as a text email. The receipient basically just gets unintelligible garbage.
"Ah yes, that's exactly what I wanted to happen", said nobody ever.
- Sandboxing HTML emails -
aerchas built-in support for using external programs to render different email part MIME types, e.g. it can usew3mto render HTML emails as semi-legible text in the terminal.4 But sometimes, the terminal just isn't up to the task of rendering a random HTML email, and you want to pop it open in an actual web browser. By default, the resultingfile:///tmp/aerc-*/aerc-*.htmjust contains the exact contents of the (potentially untrusted) email you received, including random JavaScript, tracking pixels, etc. I wanted the behavior to be more like modern mail clients, which can disable rendering images and any other outbound requests by default.
Who knows what telemetry Domino's just got on me when I opened this email.
Because modern LLMs are quite good, adding these two features amounted to writing two short prompts, waiting a few minutes, and paying like two or three dollars. Pretty crazy times we live in!
But now I have logistical questions to deal with, like:
- What if I want to add more features?
- How do I make a single build that has all these features in it?
- What if I want to upstream one of these features?
- How do I make sure these changes don't conflict with each other?
And my jj workflow covers all of these! Each feature gets a branch,5 and then I just put a merge commit on top of all the feature branches. I can cut a release from that merge commit, and even continue developing and jj absorb things into the appropriate feature branches.
Since each feature is in a separate branch, if I want to send one out for review, I can do that. If they conflict, it'll show up in the merge commit and I can fix it there, then decide what to do with it long term. If I want to pull in upstream changes, I just rebase the whole shebang onto the latest main branch. It's all quite effective and easy.
Visualizing what this looks like in our aerc example:
This only shows two features, but having N separate features doesn't require any more work.
I've used this flow for a bunch of projects now:
- Updating beancount-import to add new hotkeys and match my desired flow for importing finances + categorizing things
- Tweaking the UI on memos and updating a few API endpoints to support bulk ingestion
- Adding a bunch of new resources to the RouterOS Terraform Provider provider,6 one resource (or group of resources) per branch
Some of these make sense to upstream, and I'll eventually get around to that, but others are clearly too specific or weird to make sense in the context of a popular open-source project.
This of course doesn't solve the problem of how to maintain a long-running fork of a project in a low-maintenance way, but that's not really a concern for the VCS.
-
My setup supports reading + managing mail offline. For sending mail, I just use
aerc's:postponeand then send it later, but fancier solutions can be used for queueing up messages even when offline (i.e. using Postfix as described here) ↩ -
Including a handful of Gmail/Google Workspace addresses, using lieer ↩
-
I don't normally use named branches like one would in Git,
jjdefaults to "anonymous" branches, which make sense here. ↩ -
This is for my Becoming the Internet project, and I'll likely try to upstream these at some point. ↩