{"version":"https://jsonfeed.org/version/1","title":"EssaysâââSympolymathesy, by Chris Krycho","home_page_url":"https://v5.chriskrycho.com/","feed_url":"https://v5.chriskrycho.com/essays/feed.json","description":"Learning in public: on theology, technology, ethics, software, politics, art, and more.","items":[{"id":"https://v5.chriskrycho.com/essays/jj-init/","author":{"name":"Chris Krycho","url":"https://v5.chriskrycho.com/"},"title":"[essays] jj init","url":"https://v5.chriskrycho.com/essays/jj-init/","date_published":"2024-02-02T11:30:00.000-07:00","content_html":"
What if we actually could replace Git? Jujutsu might give us a real shot.\n
Assumed audience: People who have worked with Git or other modern version control systems like Mercurial, Darcs, Pijul, Bazaar, etc., and have at least a basic idea of how they work.\n
Meaningful changes since publication:
jj git init
instead of plain jj init
, to match the 0.14 release.\nlog
format right up front in that section.\nsquash
and move
commands.\nobslog
, and made sure the text was consistent on use of âJujutsuâ vs. jj
for the name of the tool vs. command-line invocations.\njj init
is an essay, and I am rewriting itânot a dev journal, but an essay introduction to the tool.\njj new -A
works and integrates with its story for clean rebases.\njj
interesting, and explaining templates a bit.\njj describe
and jj new
.\njj split
and auto-committed working copy vs. git add --patch
(as mediated by a UI).\n<abbr>
tags.\njj log
section to incorporate info about revsets, rewrote a couple of the existing sections now that I have significantly more experience, and added a bunch of notes to myself about what to tackle next in this write-up.\njj describe
and jj new
: this is pretty wild, and I think I like it?\nJujutsu is a new version control system from a software engineer at Google, where it is on track to replace Googleâs existing version control systems (historically: Perforce, Piper, and Mercurial). I find it interesting both for the approach it takes and for its careful design choices in terms of both implementation details and user interface. It offers one possible answer to a question I first started asking most of a decade ago: What might a next-gen version control system look likeâââone which actually learned from the best parts of all of this generationâs systems, including Mercurial, Git, Darcs, Fossil, etc.?
\nTo answer that question, it is important to have a sense of what those lessons are. This is trickier than it might seem. Git has substantially the most âmind-shareâ in the current generation; most software developers learn it and use it not because they have done any investigation of the tool and its alternatives but because it is a de facto standard: a situation which arose in no small part because of its âkiller appâ in the form of GitHub. Developers who have been around for more than a decade or so have likely seen more than one version control systemâââbut there are many, many developers for whom Git was their first and, so far, last VCS.
\nThe problems with Git are many, though. Most of all, its infamously terrible command line interface results in a terrible user experience. In my experience, very few working developers have a good mental model for Git. Instead, they have a handful of commands they have learned over the years: enough to get by, and little more. The common rejoinder is that developers ought to learn how Git works internallyâââthat everything will make more sense that way.
\nThis is nonsense. Gitâs internals are interesting on an implementation level, but frankly add up to an incoherent mess in terms of a user mental model. This is a classic mistake for software developers, and one I have fallen prey to myself any number of times. I do not blame the Git developers for it, exactly. No one should have to understand the internals of the system to use it well, though; that is a simple failure of software design. Moreover, even those internals do not particularly cohere. The index, the number of things labeled â-ishâ in the glossary, the way that a âdetached HEAD
â interacts with branches, the distinction between tags and branches, the important distinctions between commits, refs, and objects⦠It is not that any one of those things is bad in isolation, but as a set they do not amount to a mental model I can describe charitably. Put in programming language terms: One of the reasons the âsurface syntaxâ of Git is so hard is that its semantics are a bit confused, and that inevitably shows up in the interface to users.
Still, a change in a system so deeply embedded in the software development ecosystem is not cheap. Is it worth the cost of adoption? Well, Jujutsu has a trick up its sleeve: there is no adoption cost. You just install itâââbrew install jj
will do the trick on macOSâââand run a single command in an existing Git repository, and⦠thatâs it. (âThere is no step 3.â) I expect that mode will always work, even though there will be a migration step at some point in the future, when Jujutsuâs own, non-Git backend becomes a viableâââand ultimately the recommendedâââoption. I am getting ahead of myself though. The first thing to understand is what Jujutsu is, and is not.
Jujutsu is two things:
\nIt is a new front-end to Git. This is by far the less interesting of the two things, but in practice it is a substantial part of the experience of using the tool today. In this regard, it sits in the same notional space as something like gitoxide. Jujutsuâs jj
is far more usable for day to day work than gitoxideâs gix
and ein
so far, though, and it also has very different aims. That takes us to:
It is a new design for distributed version control. This is by far the more interesting part. In particular, Jujutsu brings to the table a few key conceptsââânone of which are themselves novel, but the combination of which is really nice to use in practice:
\nThe combo of those means that you can use it today in your existing Git repos, as I have been for the past six months, and that it is a really good experience using it that way. (Better than Git!) Moreover, given it is being actively developed at and by Google for use as a replacement for its current custom VCS setup, it seems like it has a good future ahead of it. Net: at a minimum you get a better experience for using Git with it. At a maximum, you get an incredibly smooth and shallow on-ramp to what I earnestly hope is the future of version control.
\nJujutsu is not trying to do every interesting thing that other Git-alternative DVCS systems out there do. Unlike Pijul, for example, it does not work from a theory of patches such that the order changes are applied is irrelevant. However, as I noted above and show in detail below, jj does distinguish between changes and revisions, and has first-class support for conflicts, which means that many of the benefits of Pijulâs handling come along anyway. Unlike Fossil, Jujutsu is also not trying to be an all-in-one tool. Accordingly: It does not come with a replacement for GitHub or other such âforgesâ. It does not include bug tracking. It does not support chat or a forum or a wiki. Instead, it is currently aimed at just doing the base VCS operations well.
\nFinally, there is a thing Jujutsu is not yet: a standalone VCS ready to use without Git. It supports its own, ânativeâ backend for the sake of keeping that door open for future capabilities, and the test suite exercises both the Git and the ânativeâ backend, but the ânativeâ one is not remotely ready for regular use. That said, this one I do expect to see change over time!
\nOne of the really interesting bits about picking up Jujutsu is realizing just how weirdly Git has wired your brain, and re-learning how to think about how a version control system can work. It is one thing to believeâââvery strongly, in my case!âââthat Gitâs UI design is deeply janky (and its underlying model just so-so); it is something else to experience how much better a VCS UI can be (even without replacing the underlying model!).
\n\nTime to become a Jedi Knight. Jujutsu Knight? Jujutsu Master? Jujutsu apprentice, at least. Letâs dig in!
\nSince I published this in early 2024, many details about Jujutsu have changed — especially around specific CLI invocations — , and a fair number of the papercuts have been fixed. The big picture is the same as it was, but I can even more strongly recommend it than I did originally.
\nThat is all interesting enough philosophically, but for a tool that, if successful, will end up being one of a software developerâs most-used tools, there is an even more important question: What is it actually like to use?
\nSetup is painless. Running brew install jj
did everything I needed. As with most modern Rust-powered CLI tools,1 Jujutsu comes with great completions right out of the box. I did make one post-install tweak, since I am going to be using this on existing Git projects: I updated my ~/.gitignore_global
to ignore .jj
directories anywhere on disk.2
Using Jujutsu in an existing Git project is also quite easy.3 You just run jj git init --git-repo <path to repo>
.4 Thatâs the entire flow. After that you can use git
and jj
commands alike on the repository, and everything Just Worksâ¢, right down to correctly handling .gitignore
files. I have since run jj git init
in every Git repository I am actively working on, and have had no issues in many months. It is also possible to initialize a Jujutsu copy of a Git project without having an existing Git repo, using jj git clone
, which I have also done, and which works well.
Once a project is initialized, working on it is fairly straightforward, though there are some significant adjustments required if you have deep-seated habits from Git!
\nOne of the first things to wrap your head around when first coming to Jujutsu is its approach to its revisions and revsets, i.e. âsets of revisionâ. Revisions are the fundamental elements of changes in Jujutsu, not âcommitsâ as in Git. Revsets are then expressions in a functional language for selecting a set of revisions. Both the idea and the terminology are borrowed directly from Mercurial, though the implementation is totally new. (Many things about Jujutsu borrow from Mercurialâââa decision which makes me quite happy.) The vast majority of Jujutsu commands take a --revision
/-r
command to select a revision. So far that might not sound particularly different from Gitâs notion of commits and commit ranges, and they are indeed similar at a surface level. However, the differences start showing up pretty quickly, both in terms of working with revisions and in terms of how revisions are a different notion of change than a Git commit.
The first place you are likely to experience how revisions and revsets are differentâââand neat!âââis with the log
command, since looking at the commit log is likely to be something you do pretty early in using a new version control tool. (Certainly it was for me.) When you clone a repo and initialize Jujutsu in it and then run jj log
, you will see something rather different from what git log
would show youâââindeed, rather different from anything I even know how to get git log
to show you. For example, hereâs what I see today when running jj log
on the Jujutsu repository, limiting it to show just the last 10 revisions:
> jj log --limit 10\n@ ukvtttmt [email protected] 2024-02-03 09:37:24.000 -07:00 1a0b8773\nâ (empty) (no description set)\nâ qppsqonm [email protected] 2024-02-03 15:06:09.000 +00:00 main* HEAD@git bcdb9beb\n· cli: Move git_init() from init.rs to git.rs\n· â rzwovrll [email protected] 2024-02-01 14:25:17.000 -08:00\nâââ ig/contributing@origin 01e0739d\nâ Update contributing.md\nâ nxskksop 49699333+dependabot[bot]@users.noreply.github.com 2024-02-01 08:56:08.000\n· -08:00 fb6c834f\n· cargo: bump the cargo-dependencies group with 3 updates\n· â tlsouwqs [email protected] 2024-02-02 21:26:23.000 -08:00\n· â jt/missingop@origin missingop@origin 347817c6\n· â workspace: recover from missing operation\n· â zpkmktoy [email protected] 2024-02-02 21:16:32.000 -08:00 2d0a444e\n· â workspace: inline is_stale()\n· â qkxullnx [email protected] 2024-02-02 20:58:21.000 -08:00 7abf1689\nâââ workspace: refactor for_stale_working_copy\nâ yyqlyqtq [email protected] 2024-01-31 09:40:52.000 +09:00 976b8012\n· index: on reinit(), delete all segment files to save disk space\n· â oqnvqzzq [email protected] 2024-01-23 10:34:16.000 -08:00\nâââ push-oznkpsskqyyw@origin 54bd70ad\nâ working_copy: make reset() take a commit instead of a tree\nâ rrxuwsqp [email protected] 2024-01-23 08:59:43.000 -08:00 57d5abab\n· cli: display which file's conflicts are being resolved\n
\nHereâs the output for the same basic command in Gitââânote that I am not trying to get a similar output from Git, just asking what it shows by default (and warning: wall of log output!):
\n> git log -10\ncommit: bcdb9beb6ce5ba625ae73d4839e4574db3d9e559 HEAD -> main, origin/main\ndate: Mon, 15 Jan 2024 22:31:33 +0000\nauthor: Essien Ita Essien \n\n cli: Move git_init() from init.rs to git.rs\n\n * Move git_init() to cli/src/commands/git.rs and call it from there.\n * Move print_trackable_remote_branches into cli_util since it's not git specific,\n but would apply to any backend that supports remote branches.\n * A no-op change. A follow up PR will make use of this.\n\ncommit: 31e4061bab6cfc835e8ac65d263c29e99c937abf\ndate: Mon, 8 Jan 2024 10:41:07 +0000\nauthor: Essien Ita Essien \n\n cli: Refactor out git_init() to encapsulate all git related work.\n\n * Create a git_init() function in cli/src/commands/init.rs where all git related work is done.\n This function will be moved to cli/src/commands/git.rs in a subsequent PR.\n\ncommit: 8423c63a0465ada99c81f87e06f833568a22cb48\ndate: Mon, 8 Jan 2024 10:41:07 +0000\nauthor: Essien Ita Essien \n\n cli: Refactor workspace root directory creation\n\n * Add file_util::create_or_reuse_dir() which is needed by all init\n functionality regardless of the backend.\n\ncommit: b3c47953e807bef202d632c4e309b9a8eb814fde\ndate: Wed, 31 Jan 2024 20:53:23 -0800\nauthor: Ilya Grigoriev \n\n config.md docs: document `jj config edit` and `jj config path`\n\n This changes the intro section to recommend using `jj config edit` to\n edit the config instead of looking for the files manually.\n\ncommit: e9c482c0176d5f0c0c28436f78bd6002aa23a5e2\ndate: Wed, 31 Jan 2024 20:53:23 -0800\nauthor: Ilya Grigoriev \n\n docs: mention in `jj help config edit` that the command can create a file\n\n\ncommit: 98948554f72d4dc2d5f406da36452acb2868e6d7\ndate: Wed, 31 Jan 2024 20:53:23 -0800\nauthor: Ilya Grigoriev \n\n cli `jj config`: add `jj config path` command\n\n\ncommit: 8a4b3966a6ff6b9cc1005c575d71bfc7771bced1\ndate: Fri, 2 Feb 2024 22:08:00 -0800\nauthor: Ilya Grigoriev \n\n test_global_opts: make test_version just a bit nicer when it fails\n\n\ncommit: 42e61327718553fae6b98d7d96dd786b1f050e4c\ndate: Fri, 2 Feb 2024 22:03:26 -0800\nauthor: Ilya Grigoriev \n\n test_global_opts: extract --version to its own test\n\n\ncommit: 42c85b33c7481efbfec01d68c0a3b1ea857196e0\ndate: Fri, 2 Feb 2024 15:23:56 +0000\nauthor: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>\n\n cargo: bump the cargo-dependencies group with 1 update\n\n Bumps the cargo-dependencies group with 1 update: [tokio](https://github.com/tokio-rs/tokio).\n\n\n Updates `tokio` from 1.35.1 to 1.36.0\n - [Release notes](https://github.com/tokio-rs/tokio/releases)\n - [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.35.1...tokio-1.36.0)\n\n ---\n updated-dependencies:\n - dependency-name: tokio\n dependency-type: direct:production\n update-type: version-update:semver-minor\n dependency-group: cargo-dependencies\n ...\n\n Signed-off-by: dependabot[bot] \ncommit: 32c6406e5f04d2ecb6642433b0faae2c6592c151\ndate: Fri, 2 Feb 2024 15:22:21 +0000\nauthor: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>\n\n github: bump the github-dependencies group with 1 update\n\n Bumps the github-dependencies group with 1 update: [DeterminateSystems/magic-nix-cache-action](https://github.com/determinatesystems/magic-nix-cache-action).\n\n\n Updates `DeterminateSystems/magic-nix-cache-action` from 1402a2dd8f56a6a6306c015089c5086f5e1ca3ef to eeabdb06718ac63a7021c6132129679a8e22d0c7\n - [Release notes](https://github.com/determinatesystems/magic-nix-cache-action/releases)\n - [Commits](https://github.com/determinatesystems/magic-nix-cache-action/compare/1402a2dd8f56a6a6306c015089c5086f5e1ca3ef...eeabdb06718ac63a7021c6132129679a8e22d0c7)\n\n ---\n updated-dependencies:\n - dependency-name: DeterminateSystems/magic-nix-cache-action\n dependency-type: direct:production\n dependency-group: github-dependencies\n ...\n\n Signed-off-by: dependabot[bot] \n 41898282+github-actions[bot]@users.noreply.github.com> 41898282+github-actions[bot]@users.noreply.github.com>
\nWhatâs happening in the Jujutsu log output? Per the tutorialâs note on the log
command specifically:
\n\nBy default,
\njj log
lists your local commits, with some remote commits added for context. The~
indicates that the commit has parents that are not included in the graph. We can use the-r
flag to select a different set of revisions to list.
What jj log
does show by default was still a bit non-obvious to me, even after that. Which remote commits added for context, and why? The answer is in the help
output for jj log
âs -r
/--revisions
option:
\n\nWhich revisions to show. Defaults to the
\nui.default-revset
setting, or@ | ancestors(immutable_heads().., 2) | heads(immutable_heads())
if it is not set
I will come back to this revset in a moment to explain it in detail. First, though, this shows a couple other interesting features of Jujutsuâs approach to revsets and thus the log
command. First, it treats some of these operations as functions (ancestors()
, immutable_heads()
, etc.). There is a whole list of these functions! This is not a surprise if you think about what âexpressions in a functional languageâ implies⦠but it was a surprise to me because I had not yet read that bit of documentation. Second, it makes âoperatorsâ a first-class idea. Git has operators, but this goes a fair bit further:
It includes -
for the parent and +
for a child, and these stack and compose, so writing @-+-+
is the same as @
as long as the history is linear. (That is an important distinction!)
It supports union |
, intersection &
, and difference ~
operators.
A leading ::
, which means âancestorsâ. A trailing ::
means âdescendantsâ. Using ::
between commits gives a view of the directed acyclic graph range between two commits. Notably, <id1>::<id2>
is just <id1>:: & ::<id2>
.
There is also a ..
operator, which also composes appropriately (and, smartly, is the same as ..
in Git when used between two commits, <id1>..<id2>
). The trailing version, <id>..
, is interesting: it is ârevisions that are not ancestors of <id>
â. Likewise, the leading version ..<id>
is all revisions which are ancestors of <id>
Now, I used <id>
here, but throughout these actually operate on revsets, so you could use them with any revset. For example, ..tags()
will give you the ancestors of all tags. This strikes me as extremely interesting: I think it will dodge a lot of pain in dealing with Git histories, because it lets you ask questions about the history in a compositional way using normal set logic. To make that concrete: back in October, Jujutsu contributor @aseipp pointed out how easy it is to use this to get a log which excludes gh-pages
. (Anyone who has worked on a repo with a gh-pages
branch knows how annoying it is to have it cluttering up your view of the rest of your Git history!) First, you define an alias for the revset that only includes the gh-pages
branch: 'gh-pages' = 'remote_branches(exact:"gh-pages")'
. Then you can exclude it from other queries with the ~
negation operator: jj log -r "all() ~ ancestors(gh-pages)"
would give you a log view for every revision with all()
and then exclude every ancestor of the gh-pages
branch.
Jujutsu also provides a really capable templating system, which uses âa functional language to customize output of commandsâ. That functional language is built on top of the functional language that the whole language uses for describing revisions (described in brief above!), so you can use the same kinds of operators in templates for output as you do for navigating and manipulating the repository. The template format is still evolving, but you can use it to customize the output today⦠while being aware that you may have to update it in the future. Keywords include things like description
and change_id
, and these can be customized in Jujutsuâs config. For example, I made this tweak to mine, overriding the built-in format_short_id
alias:
[template-aliases]\n'format_short_id(id)' = 'id.shortest()'\n
\nThis gives me super short names for changes and commits, which makes for a much nicer experience when reading and working with both in the log output: Jujutsu will give me the shortest unique identifier for a given change or commit, which I can then use with commands like jj new
. Additionally, there are a number of built-in templates. For example, to see the equivalent of Gitâs log --pretty
you can use Jujutsuâs log -T builtin_log_detailed
(-T
for âtemplateâ; you can also use the long from --template
). You can define your own templates in a [templates]
section, or add your own [template-aliases]
block, using the template language and any combination of further functions you define yourself.
Thatâs all well and good, but even with reading the docs for the revset language and the templating language, it still took me a bit to actually quite make sense out of the default output, much less to get a handle on how to customize the output. Right now, the docs have a bit of a flavor of explanations for people who already have a pretty good handle on version control systems, and the description of what you get from jj log
is a good example of that. As the project gains momentum, it will need other kinds of more-introductory material, but the current status is totally fair and reasonable for the stage the project is at. And, to be fair to Jujutsu, both the revset language and the templating language are incredibly easier to understand and work with than the corresponding Git materials.
Returning to the difference between the default output from jj log
and git log
, the key is that unless you pass -r
, Jujutsu uses the ui.default-revset
selector to provide a much more informative view than git log
does. Again, the default is @ | ancestors(immutable_heads().., 2) | heads(immutable_heads())
. Walking through that:
@
operator selects the current head revision.|
union operator says âor this other revsetâ, so this will show @
itself and the result of the other two queries.immutable_heads()
function gets the list of head revisions which are, well, immutable. By default, this is trunk() | tags()
, so whatever the trunk branch is (most commonly main
or master
) and also any tags in the repository...
to the first immutable_heads()
function selects revisions which are not ancestors of those immutable heads. This is basically asking for branches which are not the trunk and which do not end at a tag.ancestors(immutable_heads().., 2)
requests the ancestors of those branches, but only two deep.heads()
gets the tips of all branches which appear in the revset passed to it: a head is a commit with no children. Thus, heads(immutable_heads())
gets just the branch tips for the list of revisions computed by immutable_heads()
.5When you put those all together, your log view will always show your current head change, all the open branches which have not been merged into your trunk branch, and whatever you have configured to be immutableâââout of the box, trunk and all tags. That is vastly more informative than git log
âs default output, even if it is a bit surprising the first time you see it. Nor is it particularly possible to get that in a single git log
command. By contrast, getting the equivalent of git log
is trivial.
To show the full history for a given change, you can use the ::
ancestors operator. Since jj log
always gives you the identifier for a revision, you can follow it up with jj log --revision ::<change id>
, or jj log -r ::<change id>
for short. For example, in one repo where I am trying this, the most recent commit identifier starts with mwoq
(Jujutsu helpfully highlights the segment of the change identifier you need to use), so I could write jj log -r ::mwoq
, and this will show all the ancestors of mwoq
, or jj log -r ..mwoq
to get all the ancestors of the commit except the root. (The root is uninteresting.) Net, the equivalent command for âshow me all the history for this commitâ is:
$ jj log -r ..@\n
\nRevsets are very powerful, very flexible, and yet much easier to use than Gitâs operators. That is in part because of the language used to express them. It is also in part because revsets build on a fundamentally different view of the world than Git commits: Jujutsuâs idea of changes.
\nIn Git, as in Subversion and Mercurial and other version control systems before them, when you finish with a change, you commit it. In Jujutsu, there is no first-class notion of âcommittingâ code. This took me a fair bit to wrap my head around! Instead, Jujutsu has two discrete operations: describe
and new
. jj describe
lets you provide a descriptive message for any change. jj new
starts a new change. You can think of git commit --message "something I did"
as being equivalent to jj describe --message "some I did" && jj new
. This falls out of the fact that jj describe
and jj new
are orthogonal, and much more capable than git commit
as a result.
The describe
command works on any commit. It defaults to the commit that is the current working copy. If you want to rewrite a message earlier in your commit history, though, that is not a special operation like it is in Git, where you have to perform an interactive rebase to do it. You just call jj describe
with a --revision
(or -r
for short, as everywhere in Jujutsu) argument. For example:
# long version\n$ jj describe --revision abcd --message "An updated message."\n\n# short version\n$ jj describe -r abcd -m "An updated message."\n
\nThatâs it. How you choose to integrate that into your workflow is a matter for you and your team to decide, of course. Jujutsu understands that some branches should not have their history rewritten this way, though, and lets you specify what the âimmutable headsâ revset should be accordingly. This actually makes it safer than Git, where the tool itself does not understand that kind of immutability and we rely on forges to protect certain branches from being targeted by a force push.
\nThe new
command is the core of creating any new change, and it does not require there to be only a single parent. You can create a new change with as many parents as is appropriate! Is a given change logically the child of four other changes, with identifiers a
, b
, c
, and d
? jj new a b c d
. Thatâs it. One neat consequence that falls out of this: a merge in Jujutsu is just jj new
with the requirement that it have at least two parents. (âAt least two parentsâ because having multiple parents for a merge is not a special case as with Gitâs âoctopusâ merges.) Likewise, you do not need a commit
command, because you can describe a given change at any time with describe
, and you can create a new change at any time with new
. If you already know the next thing you are going to do, you can even describe it by passing -m
/--message
to new
when creating the new change!6
Most of the time with Git, I am doing one of two things when I go to commit a change:
\ngit commit --all
7 is an extremely common operation for me.-p
to do it via that atrocious interface, but instead opening Fork and doing it with Forkâs staging UI.In the first case, Jujutsuâs choice to skip Gitâs âindexâ looks like a very good one. In the second case, I was initially skeptical. Once I got the hang of working this way, though, I started to come around. My workflow with Fork looks an awful lot like the workflow that Jujutsu pushes you toward with actually using a diff tool. With Jujutsu, though, any diff tool can work. Want to use Vim? Go for it.
\nWhat is more, Jujutsuâs approach to the working copy results in a really interesting shift. In every version control system I have worked with previously (including CVS, PVCS, SVN), the workflow has been some variation on:
\nWith both Mercurial and Git, it also became possible to rewrite history in various ways. I use Gitâs rebase --interactive
command extensively when working on large sets of changes. (I did the same with Mercurialâs history rewriting when I was using it a decade ago.) That expanded the list of common operations to include two more:
Jujutsu flips all of that on its head. A change, not a commit, is the fundamental element of the mental and working model. That means that you can describe a change that is still âin progressâ as it were. I discovered this while working on a little example code for a blog post I plan to publish later this month: you can describe the change you are working on and then keep working on it. The act of describing the change is distinct from the act of âcommittingâ and thus starting a new change. This falls out naturally from the fact that the working copy state is something you can operate on directly: akin to Gitâs index, but without its many pitfalls. (This simplification affects a lot of things, as I will discuss further below; but it is especially important for new learners. Getting my head around the index was one of those things I found quite challenging initially with Git a decade ago.)
\nWhen you are ready to start a new change, you use either jj commit
to âfinalizeâ this commit with a message, or jj new
to âCreate a new, empty change and edit it in the working copyâ. Implied: jj commit
is just a convenience for jj describe
followed by jj new
. And a bonus: this means that rewording a message earlier in history does not involve some kind of rebase operation; you just jj describe --revision <target>
.
What is more, jj new
lets you create a new commit anywhere in the history of your project, trivially:
-A, --insert-after\n Insert the new change between the target commit(s) and their children\n\n [aliases: after]\n\n-B, --insert-before\n Insert the new change between the target commit(s) and their parents\n\n [aliases: before]\n
\nYou can do this using interactive rebasing with Git (or with history rewriting with Mercurial, though I am afraid my hg
is rusty enough that I do not remember the details). What you cannot do in Git specifically is say âStart a new change at point xâ unless you are in the middle of a rebase operation, which makes it inherently somewhat fragile. To be extra clear: Git allows you to check out make a new change at any point in your graph, but it creates a branch at that point, and none of the descendants of that original point in your commit graph will come along without explicitly rebasing. Moreover, even once you do an explicit rebase and cherry-pick in the commit, the original commit is still hanging out, so you likely need to delete that branch. With jj new -A <some change ID>
, you just insert the change directly into the history. Jujutsu will rebase every child in the history, including any merges if necessary; it âjust worksâ. That does not guarantee you will not have conflicts, of course, but Jujutsu also handles conflicts betterâââway betterâââthan Git. More on that below.
I never use git reflog
so much as when doing interactive rebases. Once I got the hang of Jujutsuâs ability to jj new
anywhere, it basically obviates most of the places I have needed Gitâs interactive rebase mode, especially when combined with Jujutsuâs aforementioned support for âfirst-class conflictsâ. There is still an escape hatch for mistakes, though: jj op log
shows all the operations you have performed on the repoâââand frankly, is much more useful and powerful than git reflog
, because it logs all the operations, including whenever Jujutsu updates its view of your working copy via jj status
, when it fetches new revisions from a remote.
Additionally, Jujutsu allows you to see how any change has evolved over time. This handily solves multiple pain points in Git. For example, if you have made changes in your working copy, and would like to split it into multiple changes, Git only has a binary state to let you tease those apart: staged, or not. As a result, that kind of operation ranges in difficulty from merely painful to outright impossible. With its obslog
command,8 Jujutsu allows you to see how a change has evolved over time. Since the working copy is just one more kind of âchangeâ, you can very easily retrieve earlier stateâââany time you did a jj status
check, or any other command which snapshotted the state of the repository (which is most of them). That applies equally to earlier changes. If you just rebased, for example, and realize you moved some changes to code into the wrong revision, you can use the combination of obslog
and new
and restore
(or move
) to pull it back apart into the desired sequence of changes. (This one is hard to describe, so I may put up a video of it later!)
This also leads to another significant difference with Git: around breaking up your current set of changes on disk. As I noted above, Jujutsu treats the working copy itself as a commit instead of having an âindexâ like Git. Git really only lets you break apart a set of changes with the index, using git add --patch
. Jujutsu instead has a split
command, which launches a diff editor and lets you select what you want to incorporateââârather like git add --patch
does. As with all of its commands, though, jj split
works exactly the same way on any commit; the working copy commit gets it âfor freeâ.
Philosophically, I really like this. Practically, though, it is a slightly bumpier experience for me than the Git approach at the moment. Recall that I do not use git add --patch
directly. Instead, I always stage changes into the Git index using a graphical tool like Fork. That workflow is slightly nicer than editing a diffâââat least, as Jujutsu does it today. In Fork (and similar tools), you start with no changes and add what you want to the change set you want. By contrast, jj split
launches a diff view with all the changes from a given commit present: splitting the commit involves removing changes from the right side of the diff so that it has only the changes you want to be present in the first of two new commits; whatever is not present in the final version of the right side when you close your diff editor ends up in the second commit.
If this sounds a little complicated, that is because it isâââat least for today. That qualifier is important, because a lot of this is down to tooling, and we have about as much dedicated tooling for Jujutsu as Git had in 2007, which is to say: not much. Qualifier notwithstanding, and philosophical elegance notwithstanding, the complexity is still real here in early 2024. There are two big downsides as things stand. First, I find it comes with more cognitive load. It requires thinking in terms of negation rather than addition, and the âsecond commitâ becomes less and less visible over time as you remove it from the first commit. Second, it requires you to repeat the operation when breaking up something into more than two commits. I semi-regularly take a single bucket of changes on disk and chunk it up into many more than just 2 commits, though! That significantly multiplies the cognitive overhead.
\nNow, since I started working with Jujutsu, the team has switched the default view for working with these kinds of diffs to using scm-diff-editor
, a TUI which has a first-class notion of this kind of workflow.9 That TUI works reasonably well, but is much less pleasant to use than something like the nice GUIs of Fork or Tower.
The net is: when I want to break apart changes, at least for the moment I find myself quite tempted to go back to Fork and Gitâs index. I do not think this problem is intractable, and I think the idea of jj split
is right. It justââââjustâ!âââneeds some careful design work. Preferably, the split
command would make it straightforward to generate an arbitrary number of commits from one initial commit, and it would allow progressive creation of each commit from a âvs. the previous commitâ baseline. This is the upside of the index in Git: it does actually reflect the reality that there are three separate âbucketsâ in view when splitting apart a change: the baseline before all changes, the set of all the changes, and the set you want to include in the commit. Existing diff tools do not really handle thisâââother than the integrated index-aware diff tools in Git clients, which then have their own oddities when interacting with Jujutsu, since it ignores the index.
Another huge feature of Jujutsu is its support for first-class conflicts. Instead of a conflict resulting in a nightmare that has to be resolved before you can move on, Jujutsu can incorporate both the merge and its resolution (whether manual or automatic) directly into commit history. Just having the conflicts in history does not seem that weird. âOkay, you committed the text conflict markers from git, neat.â But: having the conflict and its resolution in history, especially when Jujutsu figured out how to do that resolution for you, as part of a rebase operation? That is just plain wild.
\nA while back, I was working on a change to a library I maintain10 and decided to flip the order in which I landed two changes to package.json
. Unfortunately, those changes were adjacent to each other in the file and so flipping the order they would land in seemed likely to be painfully difficult. It was actually trivial. First of all, the flow itself was great: instead of launching an editor for interactive rebase, I just explicitly told Jujutsu to do the rebases: jj rebase --revision <source> --destination <target>
. I did that for each of the items I wanted to reorder and I was done. (I could also have rebased a whole series of commits; I just did not need to in this case.) Literally, that was it: because Jujutsu had agreed with me that JSON is a terrible format for changes like this and committed a merge conflict, then resolved the merge conflict via the next rebase command, and simply carried on.
At a mechanical level, Jujutsu will add conflict markers to a file, not unlike those Git adds in merge conflicts. However, unlike Git, those are not just markers in a file. They are part of a system which understands what conflicts are semantically, and therefore also what resolving a conflict is semantically. This not only produces nice automatic outcomes like the one I described with my library above; it also means that you have more options for how to accomplish a resolution, and for how to treat a conflict. Git trains you to see a conflict between two branches as a problem. It requires you to solve that problem before moving on. Jujutsu allows you to treat a conflict as a problem which much be resolved, but it does not require it. Resolving conflicts in merges in Git is often quite messy. It is even worse when rebasing. I have spent an incredibly amount of time attempting merges only to give up and git reset --hard <before the merge>
, and possibly even more time trying to resolve a conflicting in a rebase only to bail with git rebase --abort
. Jujutsu allows you to create a merge, leave the conflict in place, and then introduce a resolution in the next commit, telling the whole story with your change history.
Likewise with a rebase: depending on whether you require all your intermediate revisions to be able to be built or would rather show a history including conflicts, you could choose to rebase, leave all the intermediate changes conflicted, and resolve it only at the end.
\n\nConflicts are inevitable when you have enough people working on a repository. Honestly: conflicts happen when I am working alone in a repository, as suggested by my anecdote above. Having this ability to keep working with the repository even in a conflicted state, as well as to resolve the conflicts in a more interactive and iterative way is something I now find difficult to live without.
\nThere are a few other niceties which fall out of Jujutsuâs distinction between changes and commits, especially when combined with first-class conflicts.
\nFirst up, jj squash
takes all the changes in a given commit and, well, squashes them into the parent of that commit.11 Given a working copy with a bunch of changes, you can move them straight into the parent by just typing jj squash
. If you want to squash some change besides the one you are currently editing, you just pass the -r
/--revision
flag, as with most Jujutsu commands: jj squash -r abc
will squash the change identified by abc
into its parent. You can also use the --interactive
(-i
for short) argument to move just a part of a change into its parent. Using that flag will pop up your configured diff editor just like jj split
will and allow you to select which items you want to move into the parent and which you want to keep separate. Or, for an even faster option, if you have specific files to move while leaving others alone, and you do not need to handle subsections of those files, you can pass them as the final arguments to the command, like jj squash ./path/a ./path/c
.
As it turns out, this ability to move part of one change into a different change is a really useful thing to be able to do in general. I find it particularly handy when building up a set of changes where I want each one to be coherentâââsay, for the sake of having a commit history which is easy for others to review. You could do that by doing some combination of jj split
and jj new --after <some change ID>
and then doing jj rebase
to move around the changes⦠but as usual, Jujutsu has a better way. The squash
command is actually just a shortcut for Jujutsuâs move
command with some arguments filled in. The move
command has --from
and --to
arguments which let you specify which revisions you want to move between. When you run jj squash
with no other arguments, that is the equivalent of jj move --from @ --to @-
. When you run jj squash -r abc
, that is the equivalent of jj move --from abc --to abc-
. Since it takes those arguments explicitly, though, move
lets you move changes around between any changes. They do not need to be anywhere near each other in history.
This eliminates another entire category of places I have historically had to reach for git rebase --interactive
. While there are still a few times where I think Jujutsu could use something akin to Gitâs interactive rebase mode, they are legitimately few, and mostly to do with wanting to be able to do batch reordering of commits. To be fair, though, I only want to do that perhaps a few times a year.
Branches are another of the very significant differences between Jujutsu and Gitâââanother place where Jujutsu acts a bit more like Mercurial, in fact. In Git, everything happens on named branches. You can operate on anonymous branches in Git, but it will yell at you constantly about being on a âdetached HEAD
â. Jujutsu inverts this. The normal working mode in Jujutsu is just to make a series of changes, which then naturally form âbranchesâ in the change graph, but which do not require a name out of the gate. You can give a branch a name any time, using jj branch create
. That name is just a pointer to the change you pointed it at, though; it does not automatically âfollowâ you as you do jj new
to create new changes. (Readers familiar with Mercurial may recognize that this is very similar to its bookmarks), though without the notion of âactiveâ and âinactiveâ bookmarks.)
To update what a branch name points to, you use the branch set
command. To completely get rid of a branch, including removing it from any remotes you have pushed the branch to, you use the branch delete
command. Handily, if you want to forget all your local branch operations (though not the changes they apply to), you can use the branch forget
command. That can come in useful when your local copy of a branch has diverged from what is on the remote and you donât want to reconcile the changes and just want to get back to whatever is on the remote for that branch. No need for git reset --hard origin/<branch name>
, just jj branch forget <branch name>
and then the next time you pull from the remote, you will get back its view of the branch!
Jujutsuâs defaulting to anonymous branches took me a bit to get used to, after a decade of doing all of my work in Git and of necessity having to do my work on named branches. As with so many things about Jujutsu, though, I have very much come to appreciate this default. In particular,I find this approach makes really good sense for all the steps where I am not yet sharing a set of changes with others. Even once I am sharing the changes with others, Gitâs requirement of a branch name can start to feel kind of silly at times. Especially for the case where I am making some small and self-contained change, the name of a given branch is often just some short, snake-case-ified version of the commit message. The default log template shows me the current set of branches, and their commit messages are usually sufficiently informative that I do not need anything else.
\nHowever, there are some downsides to this approach in practice, at least given todayâs ecosystem. First, the lack of a âcurrent branchâ makes for some extra friction when working with tools like GitHub, GitLab, Gitea, and so on. The GitHub model (which other tools have copied) treats branches as the basis for all work. GitHub displays warning messages about commits which are not on a branch, and will not allow you to create a pull request from an anonymous branch. In many ways, this is simply because Git itself treats branches as special and important. GitHub is just following Gitâs example of loud warnings about being on a âdetached HEAD
â commit, after all.
What this means in practice, though, is that there is an extra operation required any time you want to push your changes to GitHub or a similar forge. With Git, you simply git push
after making your changes. (More on Git interop below.) Since Git keeps the current branch pointing at the current HEAD
, Git aliases git push
with no arguments to git push <configured remote for current branch> <current branch>
. Jujutsu does not do this, and given how its branching model works today, cannot do this, because named branches do not âfollowâ your operations. Instead, you must first explicitly set the branch to the commit you want to push. In the most common case, where you are pushing your latest set of changes, that is just jj branch set <branch name>
; it takes the current change automatically. Only then can you run jj git push
to actually get an update. This is only a paper cut, but it is a paper cut. It is one extra command every single time you go to push a change to share with others, or even just to get it off of your machine.12 That might not seem like a lot, but it adds up.
There is a real tension in the design space here, though. On the one hand, the main time I use branches in Jujutsu at this point is for pushing to a Git forge like GitHub. I rarely feel the need for them for just working on a set of changes, where jj log
and jj new <some revision>
give me everything I need. In that sense, it seems like having the branch âfollow alongâ with my work would be natural: if I have gone to the trouble of creating a name for a branch and pushing it to some remote, then it is very likely I want to keep it up to date as I add changes to the branch I named. On the other hand, there is a big upside to not doing that automatically: pushing changes becomes an intentional act. I cannot count the number of times I have been working on what is essentially just an experiment in a Git repo, forgotten to change from the foo-feature
to a new foo-feature-experiment
branch, and then done a git push
. Especially if I am collaborating with others on foo-feature
, now I have to force push back to the previous to reset things, and let others know to wait for that, etc. That never happens with the Jujutsu model. Since updating a named branch is always an intentional act, you can experiment to your heartâs content, and know you will never accidentally push changes to a branch that way. I go back and forth: Maybe the little bit of extra friction when you do want to push a branch is worth it for all the times you do not have to consciously move a branch backwards to avoid pushing changes you are not yet ready to share.
(As you might expect, the default of anonymous branches has some knock-on effects for how it interacts with Git tooling in general; I say more on this below.)
\nJujutsu also has a handy little feature for when you have done a bunch of work on an anonymous branch and are ready to push it to a Git forge. The jj git push
subcommand takes an optional --change
/-c
flag, which creates a branch based on your current change ID. It works really well when you only have a single change you are going to push and then continually work on, or any time you are content that your current change will remain the tip of the branch. It works a little less well when you are going to add further changes later, because you need to then actually use the branch name with jj branch set push/<change ID> -r <revision>
.
Taking a step back, though, working with branches in Jujutsu is great overall. The branch
command is a particularly good lens for seeing what a well-designed CLI is like and how it can make your work easier. Notice that the various commands there are all of the form jj branch <do something>
. There are a handful of other branch
subcommands not mentioned so far: list
, rename
, track
, and untrack
. Git has slowly improved its design here over the past few years, but still lacks the straightforward coherence of Jujutsuâs design. For one thing, all of these are subcommands in Jujutsu, not like Gitâs mishmash of flags which can be combined in some cases but not others, and have different meanings depending on where they are deployed. For another, as with the rest of Jujutsuâs CLI structure, they use the same options to mean the same things. If you want to list all the branches which point to a given set of revisions, you use the -r
/--revisions
flag, exactly like you do with any other command involving revisions in Jujutsu. In general, Jujutsu has a very strong and careful distinction between commands (including subcommands) and options. Git does not. The track
and untrack
subcommands are a perfect example. In Jujutsu, you track a remote branch by running a command like jj branch track <branch>@<remote>
. The corresponding Git command is git branch --set-upstream-to <remote>/<branch>
. But to list and filter branches in Git, you also pass flags, e.g. git branch --all
is the equivalent of jj branch list --all
. The Git one is shorter, but also notably less coherent; there is no way to build a mental model for it. With Jujutsu, the mental model is obvious and consistent: jj <command> <options>
or jj <context> <command> <options>
, where <context>
is something like branch
or workspace
or op
(for operation).
Jujutsuâs native backend exists, and every feature has to work with it, so it will some day be a real feature of the VCS. Today, though, the Git backend is the only one you should use. So much so that if you try to run jj init
without passing --git
, Jujutsu wonât let you by default:
> jj init\nError: The native backend is disallowed by default.\nHint: Did you mean to pass `--git`?\nSet `ui.allow-init-native` to allow initializing a repo with the native backend.\n
\nIn practice, you are going to be using the Git backend. In practice, I have been using the Git backend for the last seven months, full time, on every one of my personal repositories and all the open source projects I have contributed to. With the sole exception of someone watching me while we pair, no one has noticed, because the Git integration is that solid and robust. This interop means that adoption can be very low friction. Any individual can simply run jj git init --git-repo .
in a given Git repository, and start doing their work with Jujutsu instead of Git, and all that work gets translated directly into operations on the Git repository.
Interoperating with Git also means that there is a two way-street between Jujutsu and Git. You can do a bunch of work with jj
commands, and then if you hit something you donât know how to do with Jujutsu yet, you can flip over and do it the way you already know with a git
command. When you next run a jj
command, like jj status
, it will (very quickly!) import the updates from Git and go back about its normal business. The same thing happens when you run commands like jj git fetch
to get the latest updates from a Git remote. All the explicit Git interop commands live under a git
subcommand: jj git push
, jj git fetch
, etc. There are a handful of these, including the ability to explicitly ask to synchronize with the Git repository, but the only ones I use on a day to day basis are jj git push
and jj git fetch
. Notably, there is no jj git pull
, because Jujutsu keeps a distinction between getting the latest changes from the server and changing your local copyâs state. I have not missed git pull
at all.
This clean interop does not mean that Git sees everything Jujutsu sees, though. Initializing a Jujutsu repo adds a .jj
directory to your project, which is where it stores its extra metadata. This, for example, is where Jujutsu keeps track of its own representation of changes, including how any given change has evolved, in terms of the underlying revisions. In the case of a Git repository, those revisions just are the Git commits, and although you rarely need to work with or name them directly, they have the same SHAs, so any time you would name a specific Git commit, you can reference it directly as a Jujutsu revision as well. (This is particularly handy when bouncing between jj
commands and Git-aware tools which know nothing of Jujutsuâs change identifiers.) The .jj
directory also includes the operation log, and in the case of a fresh Jujutsu repo (not one created from an existing Git repository), is where the backing Git repo lives.
This Git integration currently runs on libgit2
, so there is effectively no risk of breaking your repo because of a
Unsurprisingly, given the scale of the problem domain, there are still some rough edges and gaps. For example: commit signing with GPG or SSH does not yet work. There is an open PR for the basics of the feature with GPG support, and SSH support will be straightforward to add once the basics, but landed it has not.13 The list of actual gaps or missing features is getting short, though. When I started using Jujutsu back in July 2023, there was not yet any support for sparse checkouts or for workspaces (analogous to Git worktrees). Both of those landed in the interval, and there is consistent forward motion from both Google and non-Google contributors. In fact, the biggest gap I see as a regular user in Jujutsu itself is the lack of the kinds of capabilities that will hopefully come once work starts in earnest on the native backend.
\nThe real gaps and rough edges at this point are down to the lack of an ecosystem of tools around Jujutsu, and the ways that existing Git tools interact with Jujutsuâs design for Git interop. The lack of tooling is obvious: no one has built the equivalent of Fork or Tower, and there is no native integration in IDEs like IntelliJ or Visual Studio or in editors like VS Code or Vim. Since Jujutsu currently works primarily in terms of Git, you will get some useful feedback. All of those tools expect to be working in terms of Gitâs index and not in terms of a Jujutsu-style working copy, though. Moreover, most of them (unsurprisingly!) share Gitâs own confusion about why you are working on a detached HEAD
nearly all the time. On the upside, viewing the history of a repo generally works well, with the exception that some tools will not show anonymous branches/detached HEAD
s other than one you have actively checked out. Detached heads also tend to confuse tools like GitHubâs gh
; you will often need to do a bit of extra manual argument-passing to get them to work. (gh pr create --web --head <name>
is has been showing up in my history a lot for exactly this reason.)
Some of Jujutsuâs very nice features also make other parts of working on mainstream Git forges a bit wonky. For example, notice what each of these operations has in common:
\nThey are all changes to history. If you have pushed a branch to a remote, doing any of these operations with changes on that branch and pushing to a remote again will be a force push. Most mainstream Git forges handle force pushing pretty badly. In particular, GitHub has some support for showing diffs between force pushes, but it is very basic and loses all conversational context. As a result, any workflow which makes heavy use of force pushes will be bumpy. Jujutsu is not to blame for the gaps in those tools, but it certainly does expose them.14 Nor do I not blame GitHub for the quirks in interop, though. It is not JujutsuLab after all, and Jujutsu is doing things which do not perfectly map onto the Git model. Since most open source software development happens on forges like GitHub and GitLab, though, these things do regularly come up and cause some friction.
\nThe biggest place I feel this today is in the lack of tools designed to work with Jujutsu around splitting, moving, and otherwise interactively editing changes. Other than @arxanasâ excellent scm-diff-editor
, the TUI, which Jujutsu bundles for editing diffs on the command line, there are zero good tools for those operations. I mean it when I say scm-diff-editor
is excellent, but I also do not love working in a TUI for this kind of thing, so I have cajoled both Kaleidoscope and BBEdit into working to some degree. As I noted when describing how jj split
works, though, it is not a particularly good experience. These tools are simply not designed for this workflow. They understand an index, and they do not understand splitting apart changes. Net, we are going to want new tooling which actually understands Jujutsu.
There are opportunities here beyond implementing the same kinds of capabilities that many editors, IDEs, and dedicated VCS viewers provide today for Git. Given a tool which makes rebasing, merging, re-describing changes, etc. are all normal and easy operations, GUI tools could make all of those much easier. Any number of the Git GUIs have tried, but Gitâs underlying model simply makes it clunky. That does not have to be the case with Jujutsu. Likewise, surfacing things like Jujutsuâs operation and change evolution logs should be much easier than surfacing the Git reflog, and provide easier ways to recover lost work or simply to change oneâs mind.
\nJujutsu has become my version control tool of choice since I picked it up over the summer. The rough edges and gaps I described throughout this write-up notwithstanding, I much prefer it to working with Git directly. I do not hesitate to recommend that you try it out on personal or open source projects. Indeed, I actively recommend it! I have used Jujutsu almost exclusively for the past seven months, and I am not sure what would make me go back to using Git other than Jujutsu being abandoned entirely. Given its apparently-bright future at Google, that seems unlikely.15 Moreover, because using it in existing Git repositories is transparent, there is no inherent reason individual developers or teams cannot use it today. (Your corporate security policy might have be a different story.)
\nIs Jujutsu ready for you to roll out at your Fortune 500 company? Probably not. While it is improving at a steady clipâââmost of the rough edges I hit in mid-2023 are long since fixedâââit is still undergoing breaking changes in design here and there, and there is effectively no material out there about how to use it yet. (This essay exists, in part, as an attempt to change that!) Beyond Jujutsu itself, there is a lot of work to be done to build an ecosystem around it. Most of the remaining rough edges are squarely to do with the lack of understanding from other tools. The project is marching steadily toward a 1.0 release⦠someday. As for when that might be, there are as far as I know no plans: there is still too much to do. Above all, I am very eager to see what a native Jujutsu backend would look like. Today, it is âjustâ a much better model for working with Git repos. A world where the same level of smarts being applied to the front end goes into the backend too is a world well worth looking forward to.
\nThoughts, comments, or questions? Discuss:
\n\nAs alluded to above, I have done my best to make it possible to use Kaleidoscope, my beloved diff-and-merge tool, with Jujutsu. I have had only mixed success. The appropriate setup that gives the best results so far:
\nAdd the following to your Jujutsu config (jj config edit --user
) to configure Kaleidoscope for the various diff and merge operations:
[ui]\ndiff-editor = ["ksdiff", "--wait", "$left", "--no-snapshot", "$right", "--no-snapshot"]\nmerge-editor = ["ksdiff", "--merge", "--output", "$output", "--base", "$base", "--", "$left", "--snapshot", "$right", "--snapshot"]\n
\nI will note, however, that I have still not been 100% successful using Kaleidoscope this way. In particular, jj split
does not give me the desired results; it often ends up reporting âNothing changedâ when I close Kaleidoscope.
When opening a file diff, you must Optionâ-double-click, not do a normal double-click, so that it will preserve the --no-snapshot
behavior. That --no-snapshot
argument to ksdiff
is what makes the resulting diff editable, which is what Jujutsu needs for its just-edit-a-diff workflow. I have been in touch with the Kaleidoscope folks about this, which is how I even know about this workaround; they are evaluating whether it is possible to make the normal double-click flow preserve the --no-snapshot
in this case so you do not have to do the workaround.
Yes, it is written in Rust, and it is pretty darn fast. But Git is written in C, and is also pretty darn fast. There are of course some safety upsides to using Rust here, but Rust is not particularly core to Jujutsuâs âbrandingâ. It was just a fairly obvious choice for a project like this at this pointâââwhich is exactly what I have long hoped Rust would become! â©ï¸
\nPro tip for Mac users: add .DS_Store
to your ~/.gitignore_global
and live a much less annoyed lifeâââwhether using Git or Jujutsu. â©ï¸
I did have one odd hiccup along the way due to a bug (already fixed, though not in a released version) in how Jujutsu handles a failure when initializing in a directory. While confusing, the problem was fixed in the next release⦠and this is what I expected of still-relatively-early software. â©ï¸
\nThe plain jj init
command is reserved for initializing with the native backend⦠which is currently turned off. This is absolutely the right call for now, until the native backend is ready, but it is a mild bit of extra friction (and makes the title of this essay a bit amusing until the native backend comes onlineâ¦). â©ï¸
This is not quite the same as Gitâs HEAD
or as Mercurialâs âtipââââthere is only one of either of those, and they are not the same as each other! â©ï¸
If you look at the jj help
output today, you will notice that Jujutsu has checkout
, merge
, and commit
commands. Each is just an alias for a behavior using new
, describe
, or both, though:
checkout
is just an alias for new
commit
is just a shortcut for jj describe -m "<some message>" && jj new
merge
is just jj new
with an implicit @
as the first argument.All of these are going to go away in the medium term with both documentation and output from the CLI that teach people to use new
instead. â©ï¸
Actually it is normally git ci -am "<message>"
with -a
for âallâ (--all
) and -m
for the message, and smashed together to avoid any needless extra typing. â©ï¸
The name is from Mercurialâs evolution feature, where it refers to changes which have become obsolescent, thus obslog
is the âobsolescent changes logâ. I recently suggested to the Jujutsu maintainers that renaming this might be helpful, because it took me six months of daily use to discover this incredibly helpful tool. â©ï¸
They also enabled support for a three-pane view in Meld, which allegedly makes it somewhat better. However, Meld is pretty janky on macOS (as GTK apps basically always are), and it has a terrible startup time for reasons that are unclear at this point, which means this was not a great experience in the first place⦠and Meld crashes on launch on the current version of macOS. â©ï¸
\nYes, this is what I do for fun on my time off. At least: partially. â©ï¸
\nFor people coming from Git, there is also an amend
alias, so you can use jj amend
instead, but it does the same thing as squash
and in fact the help text for jj amend
makes it clear that it just is squash
. â©ï¸
If that sounds like paranoia, well, you only have to lose everything on your machine once due to someone spilling a whole cup of water on it at a coffee shop to learn to be a bit paranoid about having off-machine backups of everything. I git push
all the time. â©ï¸
I care about about this feature and have some hopes of helping get it across the line myself here in February 2024, but we will see! â©ï¸
\nThere are plenty of interesting arguments out there about the GitHub collaboration design, alternatives represented by the Phabricator or Gerrit review models, and so on. This piece is long enough without them! â©ï¸
\nGoogle is famous for killing products, but less so developer tools. â©ï¸
\nThanks: Waleed Khan (@arxanas), Joy Reynolds (@joyously), and Isabella Basso (@isinyaaa) all took time to read and comment on earlier drafts of this mammoth essay, and it is substantially better for their feedback!\n
Thoughts, comments, or questions? Shoot me an email, or leave a comment on Hacker News or lobste.rs.
","summary":"Jujutsu (`jj`) is a new version control system from a software developer at Google. I have been using it full time for 6 months. Hereâs why you should switch, too.\n","date_modified":"2024-09-09T08:15:00.000-06:00","image":"https://cdn.chriskrycho.com/images/unlearn.jpg","tags":["software development","tools","version control","Jujutsu","Git"],"banner_image":"https://cdn.chriskrycho.com/images/unlearn.jpg"},{"id":"https://v5.chriskrycho.com/essays/the-leica-q2/","author":{"name":"Chris Krycho","url":"https://v5.chriskrycho.com/"},"title":"[essays] The Leica Q2","url":"https://v5.chriskrycho.com/essays/the-leica-q2/","date_published":"2022-12-28T21:05:00.000-07:00","content_html":"Mulling on a remarkable camera (that isnât for me).\n
Assumed audience: People who care about photography specifically or the technologies and tools we use more generallyâthis essay touches on both.\n
A bit of context: Iâve been having some problems with my Sony α7R IVâs sensor for the last month or so. (itâs intermittently dropping out all but the red or green channels on the sensor, which makes for not great photos), so I rented [disclosure: mutual discount link] a Leica Q2 to have camera coverage for the immediate and extended family Christmases. (I take my role as family photographer seriously!) This essay grew out of that experience.\n
Leica has a reputation. Depending on which way you squint, that reputation might be one of quality that earns an extraordinary price tag, or one of overpriced hype. You will find no argument, though, that Leica is expensive. The Q and Q2 compact, fixed-lens, mirrorless cameras are Leicaâs most affordable and approachable entry point to the Leica system of cameras. The Q system is not, in absolute terms, anything but profoundly expensive; and it brings along a slightly higher learning curve than many of the other mirrorless cameras out thereâââbut it is affordable and approachable by comparison with the rest of their line-up, affordable and approachable enough that they have developed a robust following over the past half decade.
\nI remember distinctly my two introductions to the Leica Q: from Shawn Blanc and from Craig Mod. Both are people whose taste and opinions I have long appreciated. Both gushed with unadulterated enthusiasm about the camera. When I read those essays, I was still in the long winter of photography that was most of my seminary years: no camera bigger or more capable than whatever a smartphone offered. Still, the raves left their mark. They impressed on me that the mirrorless revolution had changed the scene enormously, and they also left a lingering sense that Leica might actually be something special.
\nWhen I first came out of that long hiatus, I started out by testing myself: was I really interested in photography again, or just in the cool new gear? I started taking the mechanics seriously enough to read up and actually interalize how photography works, for one thing. For another, I made myself just shoot with my decade-old Canon DSLR for a few months. Frustrating as that camera was, with its APS-C sensor and a profoundly mediocre kit lens, I did enjoy it, and the hobby did indeed stick.
\nI thoughtâââa lotâââabout getting a Leica Q or its then-recently-released Q2 successor. I decided, though, that I wanted greater flexibility in lens choice, so I ultimately bought Sonyâs α7R IV instead. It was the right choice: a conclusion I had reinforced by renting a Leica Q2 and using it extensively for a week around Christmastime this year. I loved the Q2, yet itâs not for me.
\nIt is difficult not to think that Leica isâââmust beâââoverrated: most brands are. Even when Craig Mod ends up gushing about Leica camerasâââmore than once!âââit doesnât really feel like the brand could possibly live up to the reputation, could possibly justify the exorbitant(-seeming?) prices of its cameras and lenses.
\nIt was only a little under a week, but I think, astonishingly, that Leica actually does live up to its reputation, and even more astonishingly, perhaps even justifies its prices.
\nI said to my friend Tim Hopper the day the Q2 arrived, after just a few hours of off-and-on use:
\n\n\nI think⦠I think I have a problem on my hands.⦠I like this camera a LOT.
\nThe handling is great, and the lens is just⦠dang.
\nAlso their phone app isnât incredible or anything but it beats the PANTS off of Sonyâs.
\n
That quickly-jotted series of text messages aptly characterized my experience. From a basic mechanics-of-shooting and quality-of-photos perspective, I love the Q2.
\nFirst of all, the fixed-mount Leica 28mm ð/1.7 Summicron ASPH lens is phenomenal. I have a handful of Sonyâs prime lenses, including its very solid 35mm, 55mm, and 85mm ð/1.8 âbaseâ lenses and the 20mm ð/1.8 G lens. The prime boasted by the Q2 is noticeably better than any of my Sony lenses. Itâs incredibly sharp corner to corner, has no vignetting to speak of, and has impeccable clarity.
\n\nOperating the lens is a joy, too. The clicks for changes in aperture are crisp and satisfying and reliable. The fact that the focus ring not only has a linear throw, but a fixed linear throw, was a game-changer. The Sony lenses I am familiar with often have nicely linear focus rings, but none of them have a fixed throw. Combined with the distinctive Leica finger rest for the focus ring, you can learn, even in the short span of a week, what focusing in a given range feels like. I expect to miss that little nub and its fixed start and end points when I come back to shooting with my Sony primes.
\nLast but not least: the shutter is so quiet that I thought at first something was wrong. I first had to convince myself that I had correctly toggled off the electronic shutter. Then I spent a few minutes double-checking after each snap that I had actually taken a picture. The quiet of the shutter is wonderful. It was just disconcerting. I remember being pleasantly surprised at how quiet Sonyâs shutters were compared to my old Canon DSLR lenses; this was far more startling a decrease in volume.
\nIâve spent the past few years fairly skeptical of Leicaâs claims to fame on this front; color me a convert.
\nThe body of the Q2 is genuinely great, too. Its smaller size is really nice, and the simple grip works well in a way that surprised me. On the Sony cameras I have used, I have always wanted a somewhat larger gripâââin part because the distribution of weight in it is quite different there from the Q2, and the more so as you add in heftier lenses. The Q2 is solid, even dense, but its grip shape and size felt really good, and its weight is incredibly well-distributed. As a consequence, while the metal body is less textured, and certainly less contoured, it actually feels better to hold than the Sony. The cumulative effect is that of a really solid tool in the hand.
\nThe only issue I had with itâââand the only place it really felt less good than the α7R IVâââwas in the ISO dial. It requires, somewhat oddly, that you click the button on top of it, then use the dial to cycle through the range of options. The Sony body just lets you cycle directly through ISO settings without the additional clicks. In practice this isnât a huge issue, because I find that letting modern cameras choose ISO settings from a predefined range and adjusting aperture and shutter speed makes for a good default workflow anyway. It was odd, though, given how well-considered the rest of the physical controls on the device are.
\nFinally, the Fotos app is be nothing to write home about in terms of innovation or design, but it does its job well and gets out of the way. That might sound like a bare minimum, but is actually quite praiseworthy. For one thing, neither âdoing the job wellâ nor âgetting out of the wayâ is not a givenâââSonyâs horrible phone and desktop software being the number one piece of evidence here. For another, that is exactly what an app like this should do. It is a companion app, not the star of the show. This does not mean its design does not matterâââagain: Sonyâs apps are loud evidence to the contrary. Rather, it means that the appâs design should be focused on reliably supporting and enabling the things you want to do with the camera. Fotos does just thatâââfrom its (blissfully notification-free!) GPS tagging to its ability to pull off either small previews or full-sized raw images via WiFi. As with most WiFi-powered connections, the latter process is a little bit slow, but it is reliable and straightforward to use.
\nThis is a good place to say that exactly the same things hold for the software and menu system of the camera itself. They are easy to navigate and easy to make choices in. They are, in fact, easier to work with not only than my α7R IVâs menus (Sonyâs infamously difficult previous menu system) but even than the current, much-improved menu system Sony has shipped on more recent models like the α7 IV and α7R V.
\n\nThe Q2âs body and lens come together to make for a viscerally pleasant experienceâââcaveat about its ISO dial notwithstanding. Every twist of the focus or aperture rings, every click of the shutter, felt good: irrepressibly smiling at it good, exclaiming aloud over it good. Shooting with the it was nice, in a way that very few things are nice.
\nThe Q2 is not perfect, of course; it has a few significant drawbacks. More than that, though, it makes some strong choicesâââchoices which may cash out as strengths or weaknesses depending on your preferences.
\nFirst and foremost among these: The lens is a trade-offâââdare I say, a compromise. That might come as a surprise given my raving about it above. I stand by those raves, but the choice of 28mm does not suit me. for all its glories, this particular fixed-length choice is a non-starter for me. I can imagine a different fixed length lens suiting me quite well, but 28mm is too wide for me: I very much prefer
Having such a wide angle as the baseline means you will only rarely feel like you wish things were wider. At the same time, you can crop in significantly from 28mm to get a still-usable shot with the effective viewing angle of a 35mm or 55mm or 75mm lens (to name just the three crops which the Q2 will show you on screen). It would not be possible to do the inverse: given a 55mm lens, there is no âcropping outâ to 35mm. You can shoot quite wide architecture or landscape views with this lens that you simply cannot get with a 50mm. This one, for example (shared via my photos feed the other day):
\n\nI therefore understand and respect the choice. Itâs still just too wide for me for a go-to camera, for three reasons.
\nOne: for all that the lens is incredibly sharp edge to edge, itâs still a wide lens. That means that there is real, if subtle distortion out at the edges, and that particularly shows up in portraiture. In some cases, it doesnât matter. A landscape or a couch cushion will not suffer noticeably. An architecture shot will show it up a little more. Faces, though, can be trouble: the human visual system is hyper-attuned to how people are supposed to look. The distortion is not terrible, and you can manage it by keeping human subjects away from the edges of the frame. Unfortunately, in practice, that meant I noticed it in many of my photos. Groups are hard to keep entirely in the middle of the frame.
\nTwo: cropping in to a 55mm field of view from a 28mm lens is not actually the same as shooting with a 55mm lens. The (lack of) compression in the depth of field is significantly different between the two, and that shows up most of all in portraiture, which is one of the main uses to which I put my cameraâââthe contents of my Glass feed notwithstanding!2 As a result, this lens does not easily produce flattering portraits. That isnât really a surprise: going wider than 35mm for portraiture requires you to really know what youâre doing. I donât doubt there are many photographers who can still flatter their subjects with this lens, but itâs beyond my skill to do it consistently.
\n\nThree: I rely heavily on the ability to crop in even when using a longer lens (a point I noted when doing my mini-review of the α7 IV as well). Especially at family gatherings, I will lean on the high resolution of the α7R IV to get a useful image from across the room with my go-to 55mm. The wider your field of view is, the harder that is, because that wider field of view inherently means a subject across the room occupies fewer of the pixels on the sensor. You can still crop in, but you will have much less to work with.
\nThereâs a (likely totally-unfounded, alas) rumor that the forthcoming Q3 might have a 50mm variant, which would significantly change the calculus for me. If they shipped a 40mm variant of the Q3, I would be hard pressed not to empty my pockets instantly. (âShut up and take my money,â as the meme goes.) As things stand, though, I cannot imagine using this camera on an everyday basis, despite how often I found myself saying things like âWow, I really love this cameraâ and âI donât want to send it back.â My wife can attest: both of those phrases came out of my mouth repeatedly.
\nI said above that the Q2 makes choices, and nowhere is this more clear than in committing to this specific focal length. Choosing 28mm gave the camera a very specific personality. If that suits you, you will love it. If it does not suit you, as it does not suit me, you will feelâââconstantlyâââthat choice, rubbing up against your own photographic instincts.
\nAnother weakness: auto-focus. The Sony α7R IV Iâve been using for the past few years is of the same vintage as the Q2 (the Q2 came out March 2019, the α7R IV in July 2019), but the difference between the two in auto-focus capabilities is night and day.
\nOf the Leica M10, Craig Mod wrote:
\n\n\nâ¦thereâs a fascinating, if somewhat artificial joy of mastering range finder focus. Iâve only fumbled a few shots âââ amazingly âââ because of missed focus at f/1.4.
\n
That âsomewhat artificial joyâ is a real dynamic with the Q2 as well. I found it consistently more enjoyable to use in manual focus mode than with auto-focus. In fact, I found I ended up using manual focus when taking a lot of our extended family Christmas photosâââbecause it was easier to make sure I actually got the shot I wanted in manual than with auto-focus. Consider: that was true with small children running around.
\nThe Q2âs auto-focus performance reminded me of nothing so much as the Canon DSLR the α7R IV replaced; that camera was an entry-level DSLR in 2009. Not good!
\nIn fairness, it is not a surprise, either. The Q2 is clearly a spiritual descendant of Leicaâs M-series of range-finder cameras, which are by definition only manual-focus.3 The fact that the Q and Q2 have auto-focus at all is, like the EVF and live view back panel, a huge shift for Leica in this form factor. Too, it is a much smaller body and a much smaller lens than Sony I am comparing to; Leicaâs SL2 is far more directly comparable to Sonyâs α7 line.
\n\nWhen I picked up my α7R IV and snapped a few pictures of the family before heading out to drop it off for repairs, I was immediately grateful for how easy it made it to nail focus. While there is a certain pleasure to nailing focus with the Q2 in manual mode, there is a totally different kind of pleasure to the effortless focusing that is part and parcel of shooting with any recent Sony camera and lens.4 What is more, that effortlessness extends to one of the key factors in nailing focus: eye tracking. It is one thing to find the right focus for a single shot; it is something else to be able to keep it up as a subject moves around. With the Q2, I was always fighting to keep up, whether in auto or manual focus mode. Sure, I could have stopped down, but that comes with its own downsides, especially in dim rooms.
\nAt the same time, that work to focus was enjoyable. Increasing mastery of a fine-tuned machineâââa bicycle, a camera, etc.âââhas its own very real pleasures. There is a place for both effortlessness and effortfulness in our use of technologies. Some of the very best technologies require just enough effort from us to make their bionic rewards truly satisfying: you can feel the boost they give you, because you can feel how your own effort translates into something much greater. By the same token, though: no effort, no sense of boost.
\nThe Q2 delivers that sense of boost in spades, because its focus ring is so very usable. That does not make up for its failings of auto-focus, though, so much as it does give you something else to think about instead.
\nA dream camera: Sonyâs auto-focus tech married to Leicaâs sense of style and sheer quality of lens-making.
\nIt would be impossibly expensive, of course. Sony hardware isnât cheap in the first place, and Leica makes Sony look like a bargain.
\nIt also isnât clear how such a thing would happen. Rare is the company that has the trifecta of great software chops (menus and operating systems), great firmware chops (auto-focus and friends), and great hardware chops (lens and sensor performance). Not just rare: surpassingly rare. I cannot think of any company with all three. Sony has mediocre software, great firmware, and good hardware. Leica has good software, mediocre firmware, and great hardware. It is not impossible to imagine Leica closing the gap on auto-focus and the like, but those capabilities simply are not in the companyâs very traditional wheelhouse. Likewise, one can imagine Sony getting its act together on its menus and doubling down on its lens-craft, but it would be a significant change, and the return on investment doesnât seem like it would be that high, so the question would simply be, âWhy?â
\nSo: a dream camera that is fated, I think, to remain a dream. Itâs too bad, because a camera with Q2 (or M10/M11) sensibilities and style married to Sonyâs auto-focus system would be an unbelievably compelling piece of hardware.
\nIn closing, now, a hard right turnâââriffing on two notes from Mod and some thoughts I have had bouncing around my head for a while.
\nHereâs Mod writing on the M10 (in the same piece quoted above):
\n\n\nIn the end, though, itâs an affront to all that is good in the world that Leica makes you wait in line for months to plop down such a large sum of cash for a camera body. But you have to think of the company as it is: A fairly tiny artisanal maker of highly precise yet capricious image tools; William Gibsonian to the max. Framing it as such makes the tax they exact a little less difficult to swallow. Thankfully, the machine delivers the goods, feels great in hand, and is more fun to shoot with than an iPhone.
\n
Here he is again, this time on the M11:
\n\n\nLeica Ms are still the simplest, smallest, most optically performant full-frame cameras on the market. Thatâs a crazy trio: Simplest, smallest, most optically performant. Of course thatâs not going to be cheap. And to boot, theyâre assembled by well-paid humans in Germany. Also rare. I am happy to pay to support that.
\n
Even with the Q2, these same points hold. A new Q2 is $5,795. Granted that that comes with the best lens Iâve ever used, full stop. Granted that it feels amazing to shoot with. Granted that as a piece of hardware is it not only functional but beautiful. Granted that the trio of size and weight and performance is best-in-class and it isnât even close. Granted, as Mod notes, that the people who make it are well compensated. Granted: all of it. Itâs still almost $6,000 for a camera with a single, fixed-focal-length lens.
\nIs that reasonable for them to make? Is it reasonable for me to buy? Is it ever reasonable for anyone to buy? In particular: for a Christian who is serious about the very real dangers of materialism to his soul, who would take heed of the warning that the desire to be rich is the root of all kinds of evil, who would earnestly aim to store up treasures in heaven rather than here on earth: Does it, can it, ever make sense to buy something like this, for the sheer goodness of the thing?
\nI have been mulling on this dynamic a lot lately.5 I have reservations (significant and serious) about the allure of expensive things, including some of those I already own, including the camera I already own. Love of this world is antithetical to love of God. Whoever would keep his life must lose it. The rich are in danger of hell if they do not give generously of what they have to the poor.
\nBeing an incredibly well-compensated senior engineer at LinkedIn has given me occasion to mull at greater length and in greater detail on these concerns than I could have imagined half a decade ago. Money can be a gift, yet it also comes with burdens: of conscience, of time spent managing it, of temptation to use it on ourselves, of responsibility to use it wisely and well and for the good of others. In this case, it comes with the burden of considering whether, and if so when and how, it can be appropriate to spend money on something like a camera. The same concern goes, mutatis mutandis, for a watch or a car or many other such items: When is it good or right to spend money on a particularly nice thing for your own pleasure, rather than to give that money away?
\nAn easy answer to the question is simply âIt is never good or right, never buy good or nice things.â Put plainly, I think that cannot be right. If it were, there would be no room in this world for the kinds of beauty and craftwork which can only come into existence through long labors, which in turn can be afforded only through generous recompense. That simply flies in the face of what I take to be one of the chief human vocations God has given us: to sub-create.
\nThere is no easy answer here.
\nIs Leica âworth itâ? If you are a professional, it might well be. Those are different considerations. If you are a hobbyist, a person picking it up for the sheer love of the art, I think the question is more complicated. The question is more complicated all the way down, though, not specific to Leica or the Q2. This particular example just shows it up more clearly because it sits so strongly on the high end of both price and quality: The Leica Q2 is a phenomenal cameraâââand yes, Leica earns its price point.
\nIt is not for me, though. Maybe for multiple reasons.
\nIâve had similar annoyances with my iPhone cameras for years. â©ï¸
\nThereâs a reason for this: my portraiture is almost entirely of my family, immediate and extended, and therefore includes many photos of my and my siblingsâ small children. Those photos are wonderful for us to have; but they arenât especially appropriate to share to the world at large. My daughters donât need their faces plastered all over the internet; nor do my nieces or nephews. As they get old enough to have well-formed opinions, I will ask themâââas I do my wife when I post the occasional portrait of herâââwhether I can have their permission to share a photo from time to time. For now, though, those all stay private. â©ï¸
\nI played with an M11 at the Leica shop in NYC back in October and it is an incredibly appealing device. I can see why people love it! â©ï¸
\nThe same is reportedly true of recent Canon and Nikon mirrorless cameras, but I have no experience with them and so cannot comment! â©ï¸
\nI hope to come back to it in some expanded formâââanother essay here or in another venue. But I also might not, given my track record of essay-publishing, and this seemed a good place to air out some of these thoughts. â©ï¸
\nThanks: Craig Modâs essay on the Leica Q isâobviouslyâa massive influence on this essay.\n
Thoughts, comments, or questions? Shoot me an email!
","summary":"The Leica Q2 is a phenomenal cameraâand yes, Leica earns its price point. It is not for me, though.\n","image":"https://cdn.chriskrycho.com/images/2022/12-21%20well%2C%20dang%20thumb.jpg","tags":["photography","Leica Q2","Leica"],"banner_image":"https://cdn.chriskrycho.com/images/2022/12-21%20well%2C%20dang%20thumb.jpg"},{"id":"https://v5.chriskrycho.com/journal/private-chat-and-dms-are-good-actually/","author":{"name":"Chris Krycho","url":"https://v5.chriskrycho.com/"},"title":"[essays] Private Chat and DMs Are Good, Actually\n","url":"https://v5.chriskrycho.com/essays/private-chat-and-dms-are-good-actually/","date_published":"2020-11-30T08:00:00.000-07:00","content_html":"Donât forget the lessons of physical offices when thinking about chat.\n
Assumed audience: Anyone considering social dynamics in team chat, but especially leaders with authority to shape team norms.\n
Itâs common in some circles to treat private rooms or DM-heavy Slack teams as a sign and more importantly a cause of dysfunction, particularly in terms of team trust. In particular, folks are sometimes tempted to use the metrics Slack provides to measure the health of their organizations: a high value of the private-to-public messages ratio is seen as a fairly direct measure of mistrust. The corresponding assumption is that it also causes teams to silo from each other and mistrust each other in the first place. I disagree firmly with both parts of this, and always have.
\nFirst, a heavy use of private rooms and DMs might be a reflection of existing mistrust, but only a reflection, not a cause. If you ask around, youâll find that many organizations with widely varying degrees of siloing, trust, collaboration, etc. have very similar ratios of private and public conversation.1 If two organizations with polar opposite degrees of trust and collaboration have the same ratio of private-to-public conversation, then siloing and mistrust canât be caused by private chat. Second, this also means that the ratio of private to public chat isnât even a reliable signal of whether an organization is healthy or not: it lacks any predictive power.
\nThis is exactly what we should expect based on how people interact with each other in the analog world. Our offices have meeting rooms because having spaces for private conversation helps everyone do better in the public spaces. For one thing, even in the healthiest organization, there are going to be times when youâre frustrated with another person or team. When that happens, the most helpful thing is often to go talk to someone else you trust. That gives you space to blow off steam, to work it through mentally or emotionally so you can handle it well publicly.
\nFor another, there are plenty of totally âpositiveâ conversations which benefit from privacy.2 Sometimes you need to help someone avoid tripping over a political or personal landmine in a conversation. Sometimes youâre coaching someone through a difficult personal situation, or through their career considerations. Sometimes youâre working on a thorny problem, where having more input from people with less context would just be a distraction (even if just by way of having to give people enough context to contribute meaningfully). Sometimes youâre processing with your closest colleagues about changes to a company policy which affect you. Sometimes youâre responding privately to a public question about an HR policy based on your own experience. Sometimes youâre just hanging out with your team, talking about your weekend.
\nThe list of things which should not be in public rooms in a healthy organization is long. Public rooms are good for public discussions, but we must be able to distinguish which discussions should be public. Most of us develop a reasonably good sense for what that looks like in an office, because it maps fairly directly to our existing social norms. Many fewer of us have had time to develop the same sense around chat, because we have used it far less. But in the same way as meeting rooms in an office, private chats serve a vital function in keeping public chat healthy, independent of the healthy trust level of an organization.
\nâPublicâ on chat is also very different from âpublicâ in an office. If a group of four or five people are sitting around a table in a public area in an office, itâs true that anybody could happen along and join the conversation. There is no log of the conversation for someone to browse through, though, and the people at the table can modulate the conversation however is appropriate for the newcomer. A public conversation around a table is therefore much more like a chat room with no persistent loggingâââjust the opposite of Slack.
\nNow, this doesnât mean that there arenât dangers around private chat. It simply means that the issues arenât fundamentally matters of organizational trust, or the goodness or badness of private chat in a general sense. Rather, they come down to decision-making and inertia.
\nWhile there are real challenges for teams around decision-making, these challenges are much the same with chat as with in-office interactions. For example, people particularly worry about whether the chat history is available to others later for context around decisions: in a private chat, the answer is no, whereas in a public chat, the answer is at least in principle yes. If youâre reliant on a chat archive to let you know whether the decision was made well, though, youâve discovered a problem with your decision-making process in general.
\nWhat matters is not that you have a record of every word spoken in a meeting but that the decision-making process is transparent enough that people can trust it, and open enough that if something got missed it can be flagged and reevaluated. Public chat doesnât solve this very well, though. For one thing, public chat in a room which key people arenât in can be nearly as much a problem as private chat. For another, a chat log is a terrible way to understand a decision, just as a video recording of a meeting around a table would be.
\nAccordingly, I recommend away from making significant decisions over chat at all. In place of ad hoc conversations for important decisionsâââchat or otherwiseâââuse a clear decision-making framework (including clarifying the parties involved via RAPID, RACI, etc.) and use good meeting minutes for synchronous conversations (whether via video or chat or in person) and comment history for asynchronous decision-making (e.g. via RFCs). The resulting decisions will clearer and the document trail behind them will be far more accessible and useful over time.3
\nAnother legitimate problem is people abusing private conversation as a way to do an end-around on the official channels. This is not specific to chat, either, though: people use private meetings as a way of getting around formal decision-making approaches in the office, too. Again, the key is that the reasons for decisions are clearly articulated and open to feedback from all relevant parties. If a decision suddenly changes and there isnât a clearly-visible reason why, there is an organizational problem⦠but one that would have existed regardless of whether chat was involved at all.
\nThe other real reason to encourage people to âdefault to publicâ is that private chat has inertia on its side. If a team defaults to private, even conversations which would benefit from being public are likely to end up remaining private. There isnât a one-size-fits-all solution here, though, for all the reasons that private chat can be good. The best we can do here is have a habitâââespecially as leadersâââof moving conversations to public whenever there is no reason for it to be private.
\nFor example: Iâm the lead for a large migration on the app I support, so people regularly DM me with questions about the migration. I have built a habit of moving those conversations to public forums with a gentle nudge: âHey, do you mind reposting that question in <the dedicated Slack room for the migration>? That way everyone can benefit from our discussion!â Whatâs more, I try to direct it to the most public forum possible. That might be the Slack room for the migration, which I intentionally set up to support all teams working on the migration. It might be our internal Q&A board. It might be public Stack Overflow. The key is that to default to the most public channel possible. Leadersâ actions implicitly set norms just as much or more than explicit statements about what people should do.
\nBeyond that, I think we simply have to acknowledge that the challenge of getting the right degree of public-vs.-private is an âunsolved problemââââand that itâs a problem which is not amenable to technical solutions. The fundamental challenges are in human nature: both our tendency to manipulate systems to our own good, and our inability to judge these questions perfectly.
\nFinally, watch out for people weaponizing these dynamics: âWhy wasnât this handled privately?â and âShouldnât we be having this conversation in public?â are legitimate questions at times, but I have also seen people use them to manipulate instead. In particular, be wary of individuals who push loudly for decisions to be made in public, but themselves regularly use private channels for decision-making. That move is just abusing the stated norm for their own power and advantage. If you have the institutional clout, confront it directly when you see it happen. If you donât have the clout to pull that off, try to raise it with someone who does. If itâs a pattern with multiple leaders around you, such that you donât have anyone to raise it with safely, find another team as quickly as you can.
\nThe legitimate problems that people worry about with private chat are not specific to private chat or indeed to chat at all. The other concerns people have about private chatâââespecially worries that private chat causes or even is a meaningful signal of organizational mistrust or dysfunctionâââare not only unfounded, but in fact misunderstand the deep human need for a whole range of privacy levels, from totally-private one-on-one conversation through semi-public group conversations to totally-public forums.
\nIf youâre worried about mistrust between teams, dig into why you have that distrust in the first place. If youâre worried about decision-making, fix your organizationâs decision-making process. Build good norms which allow room for the the whole range of private to public conversations. But donât let people weaponize those norms, and donât use chat statistics as a measure of organizational health.
\nThoughts? You can email me or discuss this on Hacker News.
\nThatâs exactly what came out in the conversation that prompted this post, in fact. â©ï¸
\nI scare-quote âpositiveâ here because I think dealing with difficult situations in a healthy way is positiveâââbut since itâs often in response to a negative or difficult circumstance, I think the distinction is still useful. â©ï¸
\nThat does not mean you need a heavy process here. Sometimes all you need is a 15-minute sync or a 1-day RFC cycle. Again: the key is the trustworthiness and openness of the process. Make it as lightweight as you possibly can! â©ï¸
\nThanks: Thanks to Keita Kobayashi, Mike Hostetler, Will Larson, Stephen Carradini, and Jeremy Sherman for taking the time to read, comment on, and discuss earlier drafts of this material.\nCredit to Austin Distel for the illustrative photo.\n
Thoughts, comments, or questions? Shoot me an email!
","summary":"Some leaders are tempted to treat private chat as a signal or even a cause of team dysfunctionâbut there is no such correlation, and indeed people need private chats for healthy social dynamics.\n","date_modified":"2020-12-27T22:21:00.000-07:00","image":"https://images.unsplash.com/photo-1556155092-8707de31f9c4?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=600&q=80","tags":["leadership","decision-making","chat","Slack"],"banner_image":"https://images.unsplash.com/photo-1556155092-8707de31f9c4?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=600&q=80"}]}