jj – the CLI for Jujutsu
462 points - today at 10:33 AM
SourceComments
I also often end up with in a dirty repo state with multiple changes belonging to separate features or abstractions. I usually just pick the changes I want to group into a commit and clean up the state.
Since it's git compatible, it feels like it must work to add files and keep files uncommitted, but just by reading this tutorial I'm unsure.
> There's one other reason you should be interested in giving jj a try: it has a git compatible backend, and so you can use jj on your own, without requiring anyone else you're working with to convert too. This means that there's no real downside to giving it a shot; if it's not for you, you're not giving up all of the history you wrote with it, and can go right back to git with no issues.
For each change you've made in the current revision, it finds the last commit where you made a change near there, and moves your changes to that commit.
Really handy when you forgot to make a change to some config file or .gitignore. You just "jj new", make the changes, and "jj absorb". No need to make a new commit or figure out where to rebase to.
Oh, and not having to deal with merge conflicts now is awesome. My repository still has merge conflicts from months ago. I'll probably go and delete those branches as I have no intention to resolve them.
So, I haven't updated the tutorial in a long time. My intent is to upstream it, but I've been very very busy at the startup I'm at, ersc.io, and haven't had the chance. I'm still using jj every day, and loving it.
Happy to answer any questions!
Even if you don't adopt it (and I didn't), it's easy to think that "this way is the only way", and seeing how systems other than your own preferred one manage workflows and issues is very useful for perspective.
That doesn't mean you should try everything regardless (we all only have so much time), but part of being a good engineer is understanding the options and tradeoffs, even of well loved and totally functional workflows.
I used to have a habit of imposing an unnecessary ordering structure on my work product. My stack of changes would look like A -> B -> C -> D, even if the order of B and C was logically interchangeable.
jj makes DAGs easier to work with because of how it handles conflicts and merges. Now I feel empowered to be more expressive and accurate about what a change actually depends on. In turn, this makes review and submission more efficient.
What triggered me to go back was I never got a really clean mental model for how to keep ontop of Github PRs, bring in changes from origin/main, and ended up really badly mangling a feature branch that multiple contributors were working on when we did want to pull it in. I'll probably try it again at some point, but working in a team through Github PRs that was my main barrier to entry.
I always liked doing things like this. At Google where we used a custom fork of Perforce, I told myself "NEVER DO STACKED CLs HAVE YOU NOT LEARNED YOUR LESSON YET?" If one CL depended on another... don't do it. With git... I told myself the same thing, as I sat in endless interactive rebases and merge conflict commits ("git rebase abort" might have been my most-used command). With jj, it's not a problem. There are merge conflicts. You can resolve them with the peace of mind as a separate commit to track your resolution. `jj new -d 'resolve merge conflict` -A @` to add a new commit after the conflicted one. Hack on your resolution until you're happy. jj squash --into @-. Merge conflict resolved.
It is truly a beautiful model. Really a big mental health saver. It just makes it so easy to work with other people.
Preventing dirty workspace by solving the co-work problem to start with. merges are much more trivial than trying to make agents remember which branch or which folder it is supposed to work on. Disk space is cheaper than mental anguish and token usage.
I mention it because while the jj command line interface is excellent, there are certain tasks that I find easier to perform with a graphical user interface. For example, I often want to quickly tap on various revisions and see their diffs. GG makes that kind of repository browsing — and certain squash operations — much more efficient for me.
If you’re interested in more information about GG, my co-host and I talked about it in a recent episode of the Abstractions podcast at about the 8:17 mark: https://shows.arrowloop.com/@abstractions/episodes/052-they-...
For the last few months though I've been thinking a lot about what you said at the end there. What if version control actually understood the code it was tracking, not as lines of text but as the actual structures we write and think in, functions, classes, methods, the real building blocks? A rename happening on one branch and an unrelated function addition on another aren't a real conflict in any meaningful sense, they only look like one because every tool we have today treats source code as flat text files.
For enhancing this kind of structural intelligence I started working on https://github.com/ataraxy-labs/sem, which uses tree-sitter to parse code into semantic entities and operates at that level instead of lines. When you start thinking of code not as text there's another dimension where things can go, even a lot of logic at the comiler level with call graphs becomes useful.
To work around this I stopped moving revs (squash/rebase) after review starts, which creates awkward local graphs if I have to merge from main for merge conflicts. Graphite works but it's $$$, and phabricator/reviewable/gerritt all have significant onboarding hurdles.
- All of everything good about it breaks down the instant you want to share work with the outside world. It's git on the backend! Except there isn't any concept of a remote jj so you have to go through the painful steps of manually naming commits, pushing, pulling, then manually advancing the current working point to match. And in doing so, you lose almost everything that gives it value in the first place - the elegant multi branch handling, anonymous commits, the evolog. Even if you want to work on the same project on two machines your only choice for this is without breaking everything via git is to rsync the folder. Yes, you can write alias to do all this like git. I might as well use git if I can't use the nice features.
- All files automatically committed is great until you accidentally put a secret in an unignored file in the repository folder. And then there is no way to ensure that it's purged (unlike in git) - the community response as far as I can tell is "Don't do this, never put a file that isn't in gitignore".
- And adding to .gitignore fails if you ever want to wind back in history - if you go back before a file was added to .gitignore, then whoops now it isn't ignored, is all part of your immutable history, and disappears if you ever switch off of that new commit state.
mkdir junk
echo '*' > junk/.gitignore
jj won't track those files under ./junk/Also might be relevant for claude, since it wants to put its settings into the repo itself as `.claude/`:
mkdir junk/.claude
bwrap ... --bind "$(pwd)/junk/.claude" "$(pwd)/.claude" ...
For some more common files, I use global gitignore file as # ~/.gitconfig
[core]
excludesFile = ~/gitconf/gitignore_global
# ~/gitconf/gitignore_global
.envrc
.direnv/*I would like more uniformity in the way jjui handles commands when you are viewing changes vs when you are viewing files within a single change.
Often I just make my changes and leave it there without `new`, as I am not sure which file should go in a single commit. I just leave it there and I interactively commit later.
For me to use `new` more, I would like the ability to also simply be able to get a tree view of all changes, which contains file which contains file changes, so I could can have marks that span multiple changes and then either `split` or `commit` or `squash` the change, idk if there is a good word for it. Right now I can only mark within a single change, and I lose it once I navigate up.
Although jj as a vcs system, it does feel better, working with git through it still feels like a chore, but to be fair I only gave it a day before going back to git.
Does anyone have any good resources on how to augment a git flow through the lens of a git hosting platform to work smoothly and still reap the benefits of jj?
https://neugierig.org/software/blog/2025/08/jj-bookmarks.htm...
It isn't very hard to make a bash script to do it, but I have about six github repos, all of which frequently need to be put on a new machine. that kind of functionality would be cool to have out of the box.
I won't install Rust just to test your software. Make a debian package like everyone else.
Whatever git's practical benefits over SVN and CVS back in the day (and I can go into the weeds as a user if someone wants that), git was the DVCS that took over from the centralized VCS's of that era.
There is nothing in jj, pijul, or Bram Cohen's thing that is anywhere near as dramatic a quality of life improvement as going from VCS to any DVCS. And dramatic improvement is what is needed to unseat git as the standard DVCS.
I mean, if you're not doing something so important[1] that it adds a letter to the acronym, it's probably not the next new thing in version control.
1: I originally wrote the word "novel" here. But it has to be big-- something like guaranteeing supply chain integrity. (No clue if a DVCS can even do that, but that's the level of capability that's needed for people to even consider switching from git to something else.)
If jj is so great now and works with git as a backend, it’s tough to imagine why it’s worth pursuing a native and presumably incompatible backend.
1. its different
2. very few user would really care about this difference
i think git is good (not good enough, good just good, or really good)
and unlike shells, i cant think of a reason to have mass migration to itpeople use zsh because apple choose it, and pwsh because microsoft settled on it, on linux i am sure we can do better than bash, but it good enough and nothing justified replacing it (that being said, all 3 OSes should have settled non nushell)
in summary, if we couldnt replace bash on linux, i dont think anyone can replace git, git as an scm tool if far better than bash as a shell
To me - the PR is the product of output I care about. The discussion in the review is infinitely more important than a description of a single change in a whole series of changes. At no point are we going to ship a partial piece of my work - we’re going to ship the result of the PR once accepted.
I just squash merge everything now. When I do git archeology - I get a nice link to the PR and I can see the entire set of changes it introduced with the full context. A commit - at best - lets me undo some change while I’m actively developing. But even then it’s often easier to just change the code back and commit that.
But I found this article a bit long winded and ended up asking an LLM about it instead.
So it felt like the XKCD on "standards": I now have one versioning system, if I learn jj I will have two. What for?
Don't get me wrong: it's nice that jj exists and some people seem to love it. But I don't see a need for myself. Just like people seem to love Meson, but the consequence for me is that instead of dealing with CMake and Autotools, I now have to deal with CMake, Autotools and Meson.
EDIT: no need to downvote me: I tried jj and it is nice. I am just saying that from my point of view, it is not worth switching for me. I am not saying that you should not switch, though you probably should not try to force me to switch, that's all.
What has a change is ast-based version control.
You adding a feature to a function that uses a struct I renamed shouldn't be a conflict. Those actions don't confliuct with each other, unless you treat code as text - rather than a representation of the logic.
Ending merge conflicts might make a new version control 10x better than git, and therefore actually replace it.
As such, I wanted to break into jj via the GUI, and only adopt the command line after I could visualize the concepts and differences in my head.
Alas, the GUI I tried - a VSCode plugin - did more to confuse me than to help, and it made it very easy to catastrophically rewrite the history by accident. I tried another UI and it kept failing and leaving me with cleanup work. I couldn't find a third UI that looked promising.
So, I gave up. One less jj user on the planet - no biggie. But I really wonder if it would be worth the effort for some of the jj pushers to try to get a proper UI in place - I bet I am not the only one that likes to learn visually.
$ jj config set --user ui.paginate never
In one feature they can’t help themselves from calling it two different things already.Why do this? Why can’t the very clearly smart people making things step 1/2 step outside themselves and think about it like they are the users they want?
Earlier they talk about the native format and how it isn’t ready… so that to start you need
jj git init
… but… if they’re planning a native format that makes no sense as a command. It would be ‘jj native init’ later?Early planning keys/plans imo but those rarely change so as to not accept your early adopters.
These seem like small things but to me it’s a warning.
For those in the know, how does jujutsu stack up to something like Darcs?