I recently switched to Jujutsu (jj) and it made me realize that “what comes after Git” might already exist.
It turns out the snapshot model is a perfect fit for AI-assisted development. I can iterate freely without thinking about commits or worrying about saving known-good versions.
You can just mess around and make it presentable later, which Git never really let you do nicely.
Plus there’s essentially zero learning curve, since all the models know how to use JJ really well.
> You can just mess around and make it presentable later, which Git never really let you do nicely.
I'm surprised to read that, because that's how I've always used Git (and GitHub).
That's what I've understood to be good practice with Git, and it was liberating compared with what came before. One of the nicest things about Git is you can throw things in locally without worrying about how it looks, and make it presentable later.
I also did that with git, but it's no comparison in ergonomics. For instance, "move this hunk two commits up" is a task that makes many git users sweat. With jj it's barely something that registers as a task.
> For instance, "move this hunk two commits up" is a task that makes many git users sweat.
Citation needed. You split the commit anyway you like, e.g. with the mouse or using cursor movements or by duplicating and deleting lines. Then you move it with the mouse or cursor or whatever and squash it into the other commit. Maybe some people never intend to do it, but then these probably also don't want to learn JJ. I guess this is more of a selection bias, that these that care about history editing are also more likely to learn another VCS on their own.
I'm one of the git users who would sweat. Can you explain a bit (out link relevant docs) how I might split a commit up, and move it?
It's already well explained in a sibling comment, but on a more conceptual basis, while commits are interpreted as diffs on the fly, a commit is a single (immutable) snapshot. So in these terms, "splitting a commit" amounts to introducing an intermediate snapshot. Having that in mind, it should become clear, that using Git you create the snapshot by working from the previous or next commit (what ever suits you more), bringing it to the state, you like it to be and commit. (In theory you could create that intermediate snapshot from any commit, but likely you want to do it from on of the direct neighbors.)
Here's two "raw" methods:
1. Use "git rebase -i commitid^" (or branch point, tag etc), ideally with editor set to Magit, set that commit to "edit" (single key 'e' in Magit) and let the rebase continue, do "git reset -p HEAD^" and select the hunks you want to remove from the first commit, "git commit --amend", then "git commit -a" (add -c if useful, e.g. to copy author and date from the previous one). or to keep the author date), then "git rebase --continue" to finish.
2. Same, but use "git reset HEAD^" (add -N if useful), then "git add -p" to select the hunks you do want to include in the first commit.
Afterwards you can do the "git rebase -i" command again if you want to reorder those commits, move them relative to other commits, or move the split-out hunks into another existing commit (use the 'f' fixup or 's' squash rebase options).
After doing this a few times and learning what the commands actually do, it starts to feel comfortable. And of course, you don't have to run those exact commands or type them out, it's just a raw, git-level view. "git rebase -i" and "git add -p" / "git reset -p" are really useful for reorganising commit hunks.
Yeah, I mostly do it like that. I don't use Magit (yet? Haven't got the motivation to learn or find a good tutorial for Emacs.), but instead use the cursor to select the lines to stage or unstage with the cursor/mouse in my Git GUI. Also depending on what I want the commits to look like, I duplicate the pick commit line first (and potentially move it).
On an unrelated note, I use @~ instead of @^, because I think of moving up down the ancestry, not sideways, e.g. I'm more likely to want to change it to an older/newer commit, than I am to want to change the second parent instead. I don't get why most tutorials show it with @^, because you do focus on the commit being an ancestor, not precisely being the direct first parent, although of course for the first-level first parent, it amounts to the same.
The problem put simply is that git doesn't support concurrency. Even if you use worktrees, git has a global lock for repo interaction.
https://www.felesatra.moe/blog/2024/12/23/jj-is-great-for-th...
Every success story and happy conversion to jj is evidence that hg should have won the DVCS war, but more importantly, that VCSes and their inherent merits and tradeoffs were always secondary to the social networks underpinning them. GitHub as a defacto monopoly really killed innovation in the VCS space, but also shifted the focus and attention elsewhere. That's why I don't think there'll be so much of a "post-git" without as much as a "post-github".
LOL. In 2007, I did a side-by-side comparison of SVN, Mercurial and git with my codebase at work (~700k LOC). I know hg got faster eventually, but I still can't believe people say "hg should have won the DVCS war" with a straight face. It was orders of magnitude slower in my tests. Like, 20 minutes to commit large xml files (yeah, I know, but that's where we were). Not just a few ms slower, unbearably slow for several things.
I liked its features better, but chose git, and that was the correct decision.
Does VCS-agnosticity actually work? It feels like a huge burden to migrate it everytime you want to have the tools from the innovation in your daily work. Also projects want to integrate project versions into each other and reference versions and identifiers are likely VCS specific. That's why I feel VCS monopolies actually has a lot of benefits.
Yes, it’s fantastic. I have a post-tool-use hook for Claude Code to snapshot the repository for every edit. It’s like the built in file history feature but native in my VCS and works for my edits too. Don’t want to froth too much but JJ is my favourite piece of software in a while, and the fact that it’s not VC-funded is a major plus point.
Its VC funded https://www.sec.gov/Archives/edgar/data/2076429/000207642925...
Jujutsu is not "VC funded". But some of the developers, including me, work at East River Source Control (I worked on Jujutsu before that, too). The majority of the code in the project doesn't come from us -- or Google, for that matter. We don't allow people to approve patches when the author is from the same company, anyway.
(also at ERSC here, hi Austin!) Heck, I have not had enough bandwidth to do as much upstream work as I initially thought I would when I started there!
that's a company built on top of Jujutsu, not jj itself
If I remember correctly, jj is one guy who works at Google. Which presents a separate worry, which is that one day, when jj gets popular enough, Google will consume it, make it shit, change the name of it every six months and then shut it down.
That hasn't really been the case for a while imo: Martin works at Google and is paid to work on jj (there are also other Google employees who contribute, not sure whether they're paid to). jj is in use (wide use? No idea) alongside Google's internal tool (piper) with which it can interact (and with which it has some features in common) because jj has a pluggable backend architecture.
While I hate to engage in speculation, tell spooky stories, or screech at people about the evil CLA you have to sign in order to contribute, my personal opinion is that if Google were ever to start throwing their weight around, the project would be forked in short order and development would continue as normal – it has momentum, plenty of non-Google contributors, and a community. It's also not a product per se, though as we're about to find out, you can certainly build products on top of it – that probably makes it less likely for its current home to suddenly become proprietorial about it.
(hi Andy!)
Good points. I had a horrible vision of a git -> GitHub -> Microsoft -> GitHub-on-Azure style pipeline but yeah, I think there's enough good people involved around jj that your vision is probably more likely. Also, hi Steph!
jj is not "one guy who works at Google" and the vast majority of submitted code comes from non-Google developers. Even if Google were to stop developing jj (they won't) the project would be healthy and strong.
There's some legal annoyances around e.g. CLA which was a result of being a side project of Google originally. Hopefully we'll move through that in due time. But realistically it's a much larger project at this point and has grown up a lot, it's not Martin's side project anymore.
Can you expand on this? How do you achieve it? Just a WIP JJ commit after every change or something more clever?
In .claude/settings.json you can trigger shell commands on events like SessionStart, Stop, PreCompact, and PostToolUse [1].
I have all of them run `jj status`, because jj snapshots the working copy every time it's invoked.
You can have Claude write the hooks, but mine is:
`[[ -d .jj ]] && jj status >/dev/null 2>&1; exit 0`
[1] https://code.claude.com/docs/en/agent-sdk/hooks
The others use `jj status`, but if I were to do this, I'd use `jj log -n0`, which has no output. All you really need is any read-only jj command.
You could also turn on watchman and have this property on every save of a file and not even need to worry about hooks.
What's the difference between "snapshots" and git commits? In my mind a git commit is already a snapshot of the repo and the changes one staged. In what way can you move around more freely than what one can do with magit, deciding for files, hunks, or even single lines of code, whether or not they get staged and committed?
You're right that git commits are snapshots.
jj is very non-modal, that is, it doesn't tend to have a lot of state that commands rely on. As an example of what I mean, because jj does not have a staging area, everything is already committed, which makes it very easy to say, move to a different commit: you don't need to stash your working copy, as jj has already stashed it for you. Similarly, due to the auto-rebase behavior, you can be working in one part of the tree, realize something somewhere else should be moved, and go rebase that without even moving to it at all!
As a small example: say I'm working on something, and I find a typo. I want to send that typo in as a PR, but I don't want to do it as part of my work. I can do that with:
1. make the change in my current working copy (@)
2. jj split -o trunk (selecting the typo contents to split off the typo fix into a new change on top of (hence -o) trunk)
3. jj log (go check out what the change id of that change is
4. jj git push -c <change id I found in 3>
No need to even move my own HEAD (in git terms), just knock it out inline in a few steps while I'm working.
Now, as for magit, I don't use it, and I know that those that do love it and it does make some of this stuff easier. But not everyone can use magit. And there are "magit, but jj" projects as well, but I can't speak to them or which is best at the moment.
Technically, nothing. But psychologically git commits represent a unit of completed work, whereas with AI agents what's needed is a kind of agent-wise undo history such that you can revert back to the state of the repo 1 minute ago before Claude did an oopsie all over your repo.
You can definitely use git as a backend for building such a system, but some extra tooling is necessary.
Just create a new branch before you implement new features and if the agent messes up don't merge the branch.
That way you get the best of both worlds. The buggy code is still there in case it's needed but it's not in the main branch
Most of the time when I'm using Claude my working tree is already dirty because I'm mid-task. I usually try to do a throwaway commit before every interaction with Claude, but it's easy to forget, or to leave the "accept edits" mode on accidentally and my working tree gets corrupted. Also having to commit takes you out of flow because you suddenly have to deal with any new gitignores, which requires at least a glance at untracked files to make sure you're not committing anything you shouldn't be. I want to be able to undo the state of my working tree to the moment before a particular interaction with Claude, just like how I can undo a file.
jj offers "jj undo" which will undo changes to your repo, and the "oplog", which is sort of like the reflog, but on steroids. It's one of the nicest things about it.
git checkout @{1.minute.ago}
> You can definitely use git as a backend for building such a system, but some extra tooling is necessary
Is it? There’s the stash for storing patches, the index for storing good hunks, branching for trying out different experiments. You can even use worktree if you want separate working directory especially when there will be changes in the untracked files.
Git has a lot of tooling for dealing with changes, directly or at the meta layer.
It seems to have been build on great idea. Git's "plumbing" is just a set of snapshots of the tree, and everything above is built on that so replacing the porcelain with something better fitting the problem is far more useful than trying to reinvent the wheel and making yet another distributed tree snapshot based VCS just to reinvent user facing tooling like the other VCS tried
Definitely not true about models knowing jj. I've had latest opus and gpt fail at revsets and fileset syntax, even hallucinating subcommands like jj move (maybe it existed before, interface is not stable). Luckily it's easy enough to not need them most of the time.
Yes, I use Claude with jj often, and it occasionally tries to use older commands like move. Most of the time it gets it right for me though, often plugins and such say to use git explicitly, and that's where it tends to stray.
I gotta say, jj was not something that interested me before, but that's a compelling pitch.
Do you not use git branches? Your use case was why git was made.
I do use git branches, but they solve isolation, which isn't my pain point with git.
When I'm using agents to code, I don't want to have to stop what I'm doing and commit known-good state to the repo every few minutes.
jj just snapshots everything automatically, so I know I've captured that state, and I can look back and curate it all after the fact.
It's like the shift from manually saving Word documents to autosave, but instead of forcing it with git, I can use JJ which has been intentionally designed for that workflow.
is there a jj hosting service?
GitHub.
Jujutsu has changed how I work with git. Switching tasks is just "jj edit <change>" or "JJ new <change>". The only thing it can't do properly is git worktrees (it doesn't replicate the .git dir to the worktrees, breaking tooling that relies on git) but there is a (old) issue relating to it. Not sure on the priority, though.
Anyway, YMMV, but I love it.
I know of one: https://lubeno.dev
https://tangled.org/ supports many jj features, but they seem to only offer public repos.
We are working on something https://lubeno.dev
Looks like still a bit early for me, but if you add an RSS feed to your blog, I would at least be reminded to check it out again later :)
We use GitHub at my work. And I think I’m the only one using JJ.
Any service that hosts git?
Isn't jj git compatible so you can just use github?
Yes.
I was doing something with jj snapshots with AI now that you have mentioned.
I will admit, I didn't know jj but I wanted snapshots so I used it, so then when AI made some changes and kept on going and I wanted to go back to a particular change and I used ai to do that. It was actually really frustrating. To the point that I think I accidentally lost one of the good files within the project and I had to settle on good-enough which I had to try to get for hours to that particular point.
My point feels like I should either learn jj properly to use it or to at this point, just ask AI agents to git commit. Another point but I was using ghostty and I had accidentally clicked on the title bar and somehow moved the folder to desktop, I wasn't thinking the most accurately and I just decided to delete it thinking that it must have copied it rather than moved it. (Also dear ghostty why do you make it so easy to move folders, it isn't the best of features and can lead to some honest errors)
My face when I realized that I have deleted the project:
Anyhow decided to restore it with ~/Trash but afterwards realized that the .git/.jj history is removed because it deletes hidden folders (from my understanding) so I definitely lost that good snapshot. I do have the binary of the app which worked good but not the source code of it which is a bit frustrating
These were all just an idea of prototyping/checking how far I can move things with AI. Yeah so my experience for that project has been that I could've even learnt a new language (Odin) and the raylib project to fix that one specific bug in lower time than AI which simply is unable to fix the bug without blowing the whole project in foot.
I think the takeaway is to have good backups man. I mean I was being reckless in this project because I had nothing to lose and was just experimenting but there have been cases where people have lost databases in prod. So even backups should be essential if you find any source code which is good to be honest.
I am sure you guys must have lost some source code accidentally which you have worked upon, would love to hear some horror stories to hopefully know that I haven't been the only one who has done some mistake and to also learn something new from these stories. (I am atleast happy in the sense that I learnt the lesson from just an tinkering thing and not something truly prod)
"I can iterate freely without thinking".
Vibecoding moto.
The biggest problem with Jujutsu is the name. I would love to hear a Swedish person try to pronounce it.
Its a backronym (or whatever you call it) that cones from the actual name, “jj”, which itself comes from the ease of typing jj on a keyboard
The logo is a pair of bluejays, so I've taken to casually calling it twojay with some of my coworkers.