jj tip: cool revsets and per-repo aliases

Assumed audience: People using, or interested in what it is like to use, Jujutsu (jj) as their version control system. Assumes basic knowledge of the concept of jj revsets.

I’m most of the way through a gnarly bit of refactoring at work, of the sort that simply cannot land as one clean PR but needs to be broken up into a sequence of related PRs. (It is safer to land such a large change in pieces where possible, for one thing. For another, our change rate is so high that it ends up with conflicts within minutes of a push.) To do that, I need to create new branches with subsets of the work, that I can then use to create individual pull requests that can merge safely and easily.

To make that happen, I am using a bunch of the other techniques I have written about here, but I also came up with a really handy new trick with revsets. For this particular bit of work, I really only care about a handful of branches, which I have associated with bookmarks for quick reference as I make changes to the underlying graph. I want to be able to view all of those in a single command. At any given point in time, I also care about both the ancestors and the descendants of those bookmarks. So I added revsets like this:

[revset-aliases]
"active(rev)" = "(ancestors(rev) | descendants(rev)) ~ immutable()"

work = "active(@) | active(prev)"

The "active(rev)" line defines a new Jujutsu revset function that accepts a revset and produces the union of all ancestors and descendants of that revset, including the revset itself. So if you had a graph like this, where * means the commit is immutable — 

A -- B -- C -- D -- E
*    *

 — and wrote jj log -r "active(D)", you would get back a log that included C, D, and E.

The line defining work here defines a revset that only shows the active” commits that correspond to the current commit (@) and things related to the prev bookmark, which is not special, just the relevant name of the branch. I could actually go a bit further and use bookmarks that match a prefix to make it just work” as I introduce more branches with bookmarks pointing to them, like my-project/a, my-project/b, etc., and then track everything in that revset in the work revset:

project = bookmarks(regex:"^my-project/.*")
work = "active(@) | active(project)"

I also have muscle memory built up to write jj l to get my most commonly used view of the commit log. So I went one step further, and aliased that, too:

[aliases]
l = ["log", "-r", "work"]

But I don’t want these revsets anywhere but this one copy of this one repository. Well: active is useful enough that I may promote it to my set of default revsets! But definitely not the others. So I did this only in the per-repo configuration: jj config edit --repo.1 Everywhere else, work does not exist and jj l gives me my normal default log output. Once I finish this particular bit of work, I’ll go ahead and delete that l alias and the work revset from the repo config. In the meantime, though, this is making it far easier for me to pull this complicated set of changes apart into some smaller PRs!


Notes

  1. You can do this kind of thing in Git, too, of course! But these days I don’t use Git except as the storage mechanism for Jujutsu. ↩︎