Maybe what I need to do is do a demo so people can see and ask questions.
But that was ten years ago. Now git is kind of hard-wired in my brain. By and large, it works well enough.
It's not really clear to me that Jujutsu offers a significant enough of a benefit to spend the time re-wiring my brain, never mind dealing with the initial setup (e.g. the unreadable colours, setting up some scripts/aliases for things I like).
You have to choose what you want to break. Once you start pulling on one thread lots of stuff start to unravel. It's really not a simple matter of "choosing a better theme".
I've spent a long time looking at this, and my conclusion is that there is no safe default that will work for everyone, other than bold/reverse and 256/true colours and setting both the foreground and background.
Some of these tools do just work. E.g. nvm seems to just work and is much nicer than raw node installs.
My quick summary is that in one case he's try to avoid "extra" commits and in another case he's trying to re-order some commits. In my usual work flow, both of those problems would be handled by the git-rebase-squash I do after the feature works.
What I find isn't that common git operations are easier in jujutsu. They're not; sometimes they're slightly harder, due to the impedance mismatch with the git backend.
Rather, what git makes easier are operations that are next to impossible — or at least highly inconvenient — in git, and which therefore next to no-one does. That makes it harder to explain, because you're telling them there's this great new workflow that does stuff that... they don't think they need (they have workarounds), and the notion of which triggers their ick reflex if they're good at programming.
Where this gets extra sticky for me is tooling which refuses to distinguish repo wide config vs a local only version. VSCode being a huge offender where there is only a ‘launch.json’ and no ‘launch.local.json’ suitable for per host customization (eg maybe I am already running something on port 8888, so I need to map it to 9000, that does not mean a quirk of my environment should be committed).
I am a black kettle here as I frequently commit individual lines amongst a sea of changes, but I do appreciate the theoretical stance of jj.
(though for debug printfs in particular, the Right Thing is proper logging with log levels, but I myself love printf debugging and so sometimes don't do that either. Which is why carrying local patches is nice.)
I tend to do a bunch of work then split into small, bite-sized revisions. Often I split something out to a parallel revision (e.g., a separate branch) if it’s an independent thread of work like a bugfix elsewhere or a documentation fix. This is an obvious one-liner in jj but a bunch of annoying branch-switching and stashing in git.
You can also use a `git add`-style workflow. Create a new revision with `jj new`. Do it again. Make your changes, then `jj squash -i/--interactive` to select the bits you want to include. Keep making changes and squashing into the previous commit you’re building up until you’re happy. Conceptually just think of @ (the current revision) and @- (the previous revision) as the working copy and the staged copy, respectively.
A decent mental model is that the top-most commit isn't generally going to get pushed up anywhere. It's like your working copy but also you get stashing "for free" (change back to main to make a new commit? All the WIP stuff stays on that branch instead of being carried over to main!)
`jj ignore` is not a command. [1]
There is no such thing as `.jjignore` files [2], [3].
`--no-snapshot` is not a flag, neither on `jj edit` nor in general. Additionally, it doesn't make sense conceptually --- `jj edit` changes the working copy, which will then propagate changes to the selected commit every time you edit a file.
If you want to check out an old commit without amending it, you `jj new` on top of it. If you end up making changes that you don't care about, you can `jj abandon` them.
[1]: https://jj-vcs.github.io/jj/latest/cli-reference/
[2]: https://github.com/jj-vcs/jj/issues/3525
[3]: https://jj-vcs.github.io/jj/latest/working-copy/#ignored-fil...
The thing I'm running into right now is that I should really learn the revset language, so that I don't have to constantly copy paste ids from jj log
Been using jj without significant issues for about a month and been super happy to be comfortable using the cli and slowly ramping up to more complicated operations.
The documentation still assumes a lot of inherent knowledge which sometimes makes it a little difficult. I love seeing blog posts like these and hopefully some more in depth resources will appear over time. Steve's guide is good, but there are still gaps for me :).
Next I want to learn some more revset language and become a bit more fluent with rebase operations. I love the more simplified cli, conflict resolution and op log!
It's amazing how quickly I forgot about the commit vs. amend papercut.
[1]: https://codeberg.org/jcdickinson/nix/src/branch/main/home/co...
I reluctantly moved to git from mercurial when host-after-host dropped mercurial support. git is a user-hostile POS that I never got used to. A thousand needless complications including nightmare merges, and an inscrutable command interface.
JJ eliminates all the git bullshit. Some incredible features:
* Merges never fail as conflicts are a first class feature. You can come back and resolve them at any time.
* You can reset the state of the repo to any particular point in history as JJ maintains a complete operations history.
* You can insert new changes anywhere and deal with the repercussions at leisure.
I started with Steve's tutorial but found it a bit wordy. So I used Gemini to produce a simple guide for me with: "You are an expert at DVCS systems like JJ. Act as my guide for the system and answer my questions with simple cookbook-style recipes."
I have been using JJ for a month and am never going back to git. It is such a pleasure to work with a DVCS that you don't have to fight at every turn.
Is there a good explanation of Jujutsu without referring to git? May be with some pictures? Am I the only one bad with memorizing SHAs when reading? Also, does it work with other people? Would it work in repo with 5 contributors? 20? 500?
EDIT: I am looking for tutorials with explanation on how JJ works, preferably without referring to Git, and even more preferably with some visual explanations, which, there are plenty for Git. It seems I am not very good in reading text trees. It is my issue, not yours, but may be you know something which would help.
A snippet from the intro:
"At the time of writing, most Jujutsu tutorials are targeted at experienced Git users, teaching them how to transfer their existing Git skills over to Jujutsu. This blog post is my attempt to fill the void of beginner learning material for Jujutsu."
There can and will be, but at this stage in the project's life, git familiarity can be assumed.
> Am I the only one bad with memorizing SHAs when reading?
Nope! The CLI has nice syntax highlighting to show you the shortest valid prefix, so for example, right now I have something that looks like
lzrvnkxl
but the initial l is in purple, while the zrvnkxl is in grey. This means I can just use the l when referring to the change. That can be harder to demonstrate in a blog post, which can't know how to highlight this, and so often they have no highlighting.> Also, does it work with other people? Would it work in repo with 5 contributors? 20? 500?
Yes. Because it's backed by a git repo, nobody else needs to know you're using jj. Everyone can use the tool they choose.
This is such a simple UX feature that I have ended up using all the time after I switched to jj a few months back.
> Am I the only one bad with memorizing SHAs when reading?
I haven't ever needed to memorize a SHA or change ID with jj. What are you referring to?
> Also, does it work with other people? Would it work in repo with 5 contributors? 20? 500?
I have used jj for two years on teams without anyone else needing to be aware. Other teammates that I've convinced to give it a shot have switched, and we all work together happily alongside the git users. If anything, it's easier to work with others when they switch to jj because you no longer have to worry about rewriting history of branches that have been pushed. If you have a branch that multiple people are working on, that's bad form in git but it's just another day with jujutsu.
You will be hard-pressed to find someone who stuck with it for a week and decided to go back to git. You will not find a lot of people who say they switched but just stayed out of inertia. Of course both of these do happen—nothing is perfect—but they are by far the exception. From my own personal anecadata, I have seen a 100% conversion rate from everyone who gave it a serious try.
I encourage you to let today be the day that you decide to try it out. It is far less effort to make the switch than you probably think it is: I was productive the same day I switched and within a week I had no remaining situations where I needed to fall back to git commands. You will quickly be more productive and you will find yourself amazed at how you ever got by without it.
It's nice, I really like it! I'll probably switch to it as my main VCS eventually. But it doesn't feel that important to me. Even though my main work involves quite a lot of annoying rebases which is where JJ really seems to shine.
I dunno I guess it's just that a) I've really mastered git and have a deeply-rooted workflow in it and b) despite my project involving annoying rebases, version control still isn't very high on the list of problems I have.
So yeah I'm basically bullish on JJ as a technology but I think movement from Git is inevitably gonna be slow and steady.
What really kept you from staying with it? It does seem like if your workflow involves a lot of nasty rebases you'd reap dividends from something like jj. I was also someone who'd mastered git (hell, I've written a git implementation) so I get having its patterns deeply ingrained.
There's nothing keeping me from switching except the activation energy cost. Almost every aspect of working in my area (Linux kernel) is painful so I'm constantly investing in tooling and workflow stuff. So usually I just don't feel like investing EVEN MORE in the area of tooling that's probably least painful of all.
So yeah this is still basically a recommendation for people to try JJ!
I’ve tried jj on three occasions and I always get confused by something and just bounce. It hasn’t clicked for me.
I’ve only tried it solo hobby projects. For which git is perfectly tolerable.
I have many many many complaints about git. But jj doesn’t move the needle for me.
Reading this post I am extremely annoyed. Almost every single section is “but more on that later”. It’s still far more complicated than it needs to be.
I also really really really hate the jj log rendering. The colors are a sea of barf. Bolding the leading character that represent uniqueness is stupid and adds noise. The username being second is dumb, I almost never care about that. And the bright neon green (empty)(no description set) is such bad spew. Kinda nit picky, but blech.
Jujutsu suffers the same thing Git suffers. Every god damn blog post that tries to explain how simple it is just my eyes gloss over and think “this is too complex for me to care”.
You can fully change/customise it. Sounds like you just don't like the default.
Most people use the default for most of their tools. It’s why Google pays Apple over $20 billion per year to be the default search engine. Because defaults matter.
I've used it and I like it but I couldn't find a good plugin for Neovim so I reverted back to my old more well-supported workflow.
The biggest killer was performance. jj operations took several seconds for me, whereas git is instantaneous no matter how big the project. Maybe this is fixed now.
But also honestly I felt like there was a bit more mental burden to using jj. When I switched back to git, it was like a weight off my shoulders. Maybe that's just due to the decade of constant use though.
There is also lazyjj as an interactive UI.
Note for other readers: jj also has a literal `jj absorb` command. That one does what you'd expect from mercurial, i.e. moves diffs from the current commit into the most recent ancestral commit where that file was changed.
You name them with bookmarks which are sort of like branch names except they don't follow along automatically as you make new revisions. You can point an existing bookmark at a later revision when you're ready to push new changes.
I’ve really been loving these two neovim JJ plugins
For splitting commits: https://github.com/julienvincent/hunk.nvim
For resolving conflicts: https://github.com/rafikdraoui/jj-diffconflicts
Is it just a git frontend for people who are confused by git?
It says it abstracts the backend, but it's not clear how something so git-influenced will have abstractions that work with something like a centralized system like Perforce or Piper that has auto-increment numeric commits.
Some of the design decisions are also not great. Working copy as commit means you have no quality control. The whole point of a commit is that... you commit it. So now you need a separate repo or filter to remove the worthless changes from the ones that you actually intend to commit. Bad commits polluting git histories is already a big problem.
The biggest problems with git IMO are it scales poorly and it encourages commit pollution. Presumably jj isn't trying to tackle those problems, which is totally fine. But I am confused about which problems it is tackling. That's why I'm wondering whether it's just a frontend that the author finds more to their liking.
The working copy doesn't get automatically pushed to GitHub or anything crazy like this seems to be implying. You review/curate your commit when you give it a description.
Its Piper backend honestly works better than the Git backend. Which isn't a knock on the Git backend, but the impedance mismatch is worse there.
> Some of the design decisions are also not great. Working copy as commit means you have no quality control. The whole point of a commit is that... you commit it. So now you need a separate repo or filter to remove the worthless changes from the ones that you actually intend to commit. Bad commits polluting git histories is already a big problem.
Sorry, but saying this implies you haven't tried it. :-)
Jujutsu commits aren't equivalent to Git commits. They're implemented with a mixture of Git commits and the Git working tree, yes (if you use the Git backend!), but when you see 'commit', you should read 'named diff'.
Jujutsu also has a notion of immutable commits, by default meaning (roughly) commits which have been pushed upstream.
You can and should rewrite the un-pushed commits to clean up history prior to pushing changes upstream. jj makes that much MUCH easier than rebases and history edits could ever be with git. Most of my nontrivial jj work involves at least three or four commits at some point, everything from experiments to documentation branches, which with git I would have needed to awkwardly fit into stash or inconvenient throwaway branches.
> You can and should rewrite the un-pushed commits to clean up history prior to pushing changes upstream.
My concern isn't just about pushing upstream. What I'm saying is that the typical change to a file shouldn't be committed to my local repo until I indicate that it's ready. The FAQ suggests this isn't possible and that you should work around it by using a separate branch and then merge into your target branch, which is a pretty ugly workflow.
Their GitHub branches are a mess of auto-generated strings, which suggests to me that this problem isn't just an abstract concern but is a form of technical debt that the jj devs are currently piling up.
Yes, it's "committed", but that doesn't mean all that much. I have the fsmonitor feature enabled that continually watches my repos as I edit files in them. This means that over the course of a coding session, the revision I'm working on has probably pointed at dozens or more ephemeral underlying git commits. Those are only kept around for the purpose of the evolution log, which lets me look back at my edit history throughout the day even without having interacted with the repo.
When you're ready to "finalize" your work, you have two common workflow options: you can `split` the out parts you want to keep as one consistent commit, which dumps the rest in a subsequent revision. Spiritually this is equivalent to `git add -p`. Alternatively, you can create an empty "staging" revision before your "working copy" revision and `jj squash -i` pieces you're happy with into there. In practice, many people use both. I generally do the former for new work, and the latter to make changes to earlier work.
Thinking that `git add -p` was absolutely a deal-breaker is the main reason I passed over jj for so long. I care deeply about maintaining a clean commit history with small, isolated, and individually-tested changes. I thought jj would make that harder due to not having a staging area. I was wrong. It is actually far easier to be principled about your commit history with the tools that jj gives you.
You're thinking in very git-specific terms. JJ is just different in many ways so such comparison doesn't work well. I didn't get it from explanations before, but trying it in practice, it's a non-issue. It may be easier to just give it a go.
Since you brought this up, I've noticed some people seem to work this way but I've never found anyone to ask why they do this.
I get the idea behind clean, readable git logs with nice consistent messages, but isn't that what rebase/amend is for?
To me, the status of my local git log is mostly irrelevant, the thing that really matters is the commit(s) I submit for merging.
Also, probably for related reasons, I've never truly understood why git has this whole separate staging concept...
jj is a version control system. It is backend-agnostic. The most common backend is a git one, because git is so popular. This allows you to use jj on a git repository, allowing for individuals to adopt it without forcing their teammates to.
> Is it just a git frontend for people who are confused by git?
I used git since before github existed. I considered myself a git lover before I found jj. I will not be going back to git.
The thing is, jj is both simpler and more powerful than git, at the same time. People who are confused by git may like its simplicity, but I like its power.
> It says it abstracts the backend, but it's not clear how something so git-influenced will have abstractions that work with something like a centralized system like Perforce or Piper that has auto-increment numeric commits.
You already got some replies on this one, so I'll leave that to them :)
> Some of the design decisions are also not great. Working copy as commit means you have no quality control. The whole point of a commit is that... you commit it. So now you need a separate repo or filter to remove the worthless changes from the ones that you actually intend to commit. Bad commits polluting git histories is already a big problem.
This is an understandable misunderstanding. The right way to think of it is "the index is also a commit." A common way of working with jj is to do something like this:
Imagine I am working on adding some feature x to my codebase. I'll first make a change, and give it a description:
jj new -m "working on feature x" trunk
Working copy (@) now at: lqqlysul c6756b49 (empty) working on feature x
Parent commit (@-) : ylnywzlx 8098b38d trunk | (empty) foo
Now I will make a new empty change on top of that: jj new
Working copy (@) now at: pxrvoron c823d73a (empty) (no description set)
Parent commit (@-) : lqqlysul c6756b49 (empty) working on feature x
Now, @- is the change that I intend to push publicly, but @ is my index. Say I add foo.rs: touch foo.rs
Now, when I run `jj status`, jj takes a snapshot, and it now lives in @: jj st
Working copy changes:
A foo.rs
Working copy (@) : pxrvoron ba7ad8c6 (no description set)
Parent commit (@-): lqqlysul c6756b49 (empty) working on feature x
We can see the diff here with `jj diff`: jj diff
Added regular file foo.rs:
(empty)
So, let's say I'm happy with its contents. I want to stage it into my final commit. I can do this with `jj squash`, which by default takes all the diff of @ and puts it into @-: jj squash
Working copy (@) now at: pxkqmsww 9f7e1ef2 (empty) (no description set)
Parent commit (@-) : lqqlysul 41dc1531 working on feature x
Now that change is in @- instead of @. we can see that by passing -r (for revision) to `jj diff`: jj diff -r @-
Added regular file foo.rs:
(empty)
I don't have to move the whole change; I can do the same thing as git add -p by using jj squash -i, and only move the portions of the diff.What's the advantage here? Well, because the index is just a commit, I can use any tools that I use on commits on the index. There's nothing like `git reset` needing to have `--hard` vs `--soft` vs `--mixed` to deal with index behavior: everything is in a commit, so everything acts consistently.
jj makes it very trivial to carve up commits into exactly what you want. It is far easier and more powerful than using the index for the same purpose.
> The biggest problems with git IMO are it scales poorly and it encourages commit pollution. Presumably jj isn't trying to tackle those problems, which is totally fine. But I am confused about which problems it is tackling. That's why I'm wondering whether it's just a frontend that the author finds more to their liking.
IMHO, commit pollution is more due to the pull request workflow than git itself, though I do think that git doesn't do that much to help you. jj can help with this kind of thing, but also on some level, it can only do so much.
To be clear where it ties to this post: it makes git far more convenient with nearly 0 learning curve.
jackblemming•5h ago
RGBCube•5h ago
doritosfan84•5h ago
I’d give [1] a read if you’re interested.
1. https://zerowidth.com/2025/what-ive-learned-from-jj/#commits...
stouset•5h ago
One example is a series of dependent PRs. This is excruciating in git if you ever need to make a fix to an earlier PR because you have to manually rebase every subsequent change. It’s trivial in jj: you either fix the revision directly or you insert a new revision inbetween. The subsequent revisions are updated automatically. You don’t even have to think.
Another is splitting work into parallel branches. So often when I’m working on one area I end up making unrelated tweaks and improvements as I go. Pulling these out into their own branches is painful in git, so people just make omnibus branches that include a handful of unrelated work alongside the main task. In jj it’s basically zero work to split out the unrelated stuff onto a new branch off main, and it doesn’t require you to task-switch to a new branch. So I end up making lots of one-line PRs that are trivial to review whenever I’m doing deeper work.
krackers•5h ago
What do you mean by this? You can do an interactive rebase in git as well. The real issue is non-trivial merge conflicts which is going to be an issue no matter you use.
sunshowers•5h ago
Merge conflicts are also significantly better with jj because they don't interrupt the rest of your flow.
And most importantly, the two features work together to create something greater than the sum of its parts. See my testimonial (first one) at https://jj-vcs.github.io/jj/latest/testimonials/#what-the-us...
packetlost•4h ago
You absolutely can, to some degree. At any point in an interactive rebase you can just make your changes and do a normal `git commit` then do `git rebase --continue` along on your merry way. Unless you're talking about suspending the rebase and like switching branches, messing around, and then resuming the rebase, which is kind of a weird thing to do.
sunshowers•3h ago
It's a weird thing to do in git, because the conditions that git creates makes it weird. It is completely natural with jj, because jj doesn't have any modal states at all. (This is one of the key reasons jj is both simpler and more powerful than git — no modal states.)
frizlab•2h ago
sunshowers•2h ago
stouset•4h ago
It is essentially zero work in jj. I `jj edit` the revision in question, make the change, and `jj push`.
packetlost•4h ago
plandis•4h ago
packetlost•3h ago
You can also just enable that feature (rerere).
wredcoll•18m ago
Also probably most of us are stuck with whatever git*.com supports anyways...
stouset•2h ago
Arbitrarily-complicated rebases just happen automatically with jujutsu. Inserting, moving around, and directly editing commits are first-class operations, and descendants automatically rewrite themselves in-place to incorporate changes made to their parents. Rebase conflicts are also first-class citizens so they don't block you and force you to deal with them right now or in any particular order. Having to rebase seven related conflicts one-after-another is no longer a thing, nor is realizing you fucked something up halfway through and having to restart.
Coming from git it honestly feels like magic even if it strictly isn't. It genuinely hard to understand how much unnecessary toil git makes you put up with on a day to day basis until you suddenly don't need to deal with it any more.
landr0id•3h ago
With jj you just "jj op undo <operation_id_that_fucked_your_repo>" and you're fine.
Editing prior commits is also pretty easy, which in turn makes fixing merge conflicts pretty easy too.
frizlab•2h ago
steveklabnik•2h ago
jj has two kinds of these logs: the evolog and the op log.
The git reflog is based on, well, refs. Whenever a ref is updated, you get an entry. This log is per ref. That's HEAD, your branches, your tags, and your stash.
jj's evolog is sorta similar, but also different: it's a log, but per change (think commit in git). This means it is broader than per ref, as it includes not just commits that correspond to a ref, but all of them.
jj's oplog is a log per repository. This lets you do things like `jj undo`, which lets you get the entire repository, not just one ref or commit, back to the previous state, easily.