I gave jj two honest tries. While first class conflicts is a cool idea, in practice I deal with staging/committing 30x more than conflict resolution, and coming from magit, using jj’s hunk split & select felt like being thrown into stone age. Plus I rebase a lot and get a lot of jj’s benefits from magit’s various rebase shortcuts already, IIRC first class conflicts was the only truly novel thing I didn’t have. For people like me who stage/commit often and judiciously I don’t think jj will beat magit until its hunk selection UX comes close.
In jj you aren’t supposed to be even thinking about staging and committing, that’s the mental leap required to get what the fuss is about. Everything is a change and you bookmark a parent (or something further out) as the branch head into which you squash or advance the bookmark to a next ready change.
> For people like me who stage/commit often and judiciously
Focus on judiciously: mostly (functionally) atomic commits that are not every tiny change, not largely meaningless time-based snapshots, not Gerrit-style single commits for entire features, etc. I’m well aware of the mental leap you’re talking about, it’s neither hard to understand nor the liberation you might think it is. To achieve what we want to achieve we either need to be able to split, or “commit” with such atomicity that more time is wasted come squash time.* If you don’t get that, totally fine, but then you just don’t belong very much to this very conversation.
I see your point, but this feels like a difference in workflow philosophy, especially with AI-assisted coding becoming more common.
With jj, your working copy is always a commit. This encourages a "commit often and messy, clean up later" approach. You can rapidly iterate with or without AI, letting jj automatically save every change without stopping to craft perfect atomic commits.
Later, you use a simple jj squash to combine all those small, iterative changes into logical, clean commits before you share it. The atomicity is created retroactively, not upfront. For a finely-tuned magit workflow this might feel wrong, but for rapid, exploratory and AI-driven iteration, it's a more natural fit.
With AI there’s even more reason to have ergonomic splitting if you care about quality commits, since unless you always write the perfect focused prompt, AI can over-generate for the scope of a single commit, so sooner or later you need to split that commit during the cleanup process you mentioned.
I’m using jj for almost a year now and I definitely feel much happier whenever I need to rebase a branch or do an octopus merge. It really is liberating in an incremental way that creeps on you in day to day work. Don’t expect a revolution, though.
Small commits are the same small commits in jj as in git, you just split instead of add -p.
What does the magit interface for staging look like? I only used GUIs were you select the lines with the cursor or mouse and stage that. Is it like that or more sophisticated? Does it support editing hunks better, because that's a thing I only do on the commandline, because my Git GUI doesn't support that.
In magit, the diff is splitted into sections: Untracked, Unstaged and Stage. The sublevels are files then hunks. You can stage and unstage hunks, files and whole section by pressing s and u. Pressing k discards the node (section, file, or hunk.
You can also stage or unstage lines by selecting them first and press the relevan keys.
If you press ‘return’ on a hunk, it brings you directly the the line to edit.
jj wouldn't have any issues with supporting this workflow, if you could replace magit's git commands with some jj ones it'd probably just work. IOW the only thing that's missing is elbow grease.
actually, with jj's seamless rebases, it'd work exactly as you'd want it to (but maybe not how you'd expect if you're used to the git way) when aimed at a commit in the middle of a branch: pick lines which stay below, above or in the middle commit, which you can edit transparently.
The thing is that magit have a lot of commands that makes rebasing a breeze. Pressing c, then s, will allow you to select interactively (from the commits log) a commit to target when doing autosquash. That will create a new commit with the relevant message. Pressing c, the S (shift+s), will rebase immediately, squashing the current staging area into the selected commit.
Interactive rebasing is a breeze. Editing commits is very fast (reordering, dropping rewording, splitting,…). Same with dealing with merge conflicts (Emacs have various merge packages builtin).
The same speed Vim brings to editing, that’s nearly the same speed you get with magit for git.
> Everything is a change and you bookmark a parent (or something further out) as the branch head into which you squash or advance the bookmark to a next ready change.
Does not sound any easier or more intuitive than git.
It isn’t how git porcelain wants you to work, but it does make sense once you stop thinking in git. Working copy being a first class commit unlocks all the dag manipulation tools without stashing, having to resolve conflicts in middle of tricky operations, etc. ‘Easier’ is exactly the point, but it’s about workflows which are very inconvenient in git like rebasing stacked PRs.
I’m curious what the magit hunk selection UX consists in. I couldn’t find any videos showing something substantially different from the one built into jj — the videos I found were meant as beginner intros.
I’ve never used magit but I used GitUp (https://gitup.co/) for years before jj. I don’t find the jj one super natural, but I feel like that’s mostly a matter of keyboard shortcuts — I need to see if they can be customized.
There's more than one way to do it, but the very normal UX is that you can just scroll through the diff file-by-file and stage/stash/drop each hunk individually by placing your cursor over it and issuing the appropriate command. You can do the same with files, staging/stashing/dropping changes to a file by placing the cursor on its name and issuing a command.
And you can even edit the content you stage, so that you can stage something different, than what is in the working tree. Having different content in the index vs. working tree is the feature of the index, which I think JJ just doesn't support?
jj doesn’t support it in the sense that there’s no special index feature. You can use a workflow where you have a commit represent the index (and that’s basically the most common workflow). This means you don’t need a separate feature, you just use the tools that slice and dice any commit with your “index”.
As an stgit user, this seems like a weird workflow to me. I never want to have that many uncommitted changes just floating around that will eventually belong to multiple commits. If I'm halfway through something and realise "oh, it would be good to do xyz first" I don't want to have xyz's changes and my half-way-through changes all mixed up -- I want to pop the half-way-through stuff, do the xyz stuff and commit it, then re-push the half-way-through stuff to keep working. If I'm looking at a diff and picking out parts of it then I've done something wrong -- I have a tool for doing it but I only need to use it every couple of months...
I end up with "neatly separated does-one-thing commits" but I get there by building them up as I go along, not by writing a ton of code and then trying to split it up afterwards.
This sort of flow is very nice in jj, primarily because of the “no index” plus “auto commit” behavior, I’ll regularly go “oh yeah I want to go do that” and I’m about to just go do it and then come back to right where I left off, since my work is already saved.
Yeah, I get the impression jj is good for this, and if I were using raw git then it would be a massive upgrade. Luckily for me stgit already does what I want in this area so I have no strong need to investigate alternatives, but if stgit ever bitrots then jj might be a useful next thing.
StGit maintainer here. I have been a jj user for over a year now. It has proven superior to StGit for all of my workloads. I even use jj when maintaining StGit.
An incomplete list of wins vs StGit includes:
- jj makes managing multiple branches fluid, whereas stg has limited tools for managing patches between stacks. 'stg pick' is largely all there is. It's a real dance to move a patch between stacks.
- jj has a much better system for naming changes. I'm super jealous of how jj solved this problem. StGit requires you to name the patches. I added the feature that allows StGit to refer to patches by relative or absolute index in addition to by name. jj's immutable change ids that can be referenced by unambiguous prefix is the correct answer to this problem.
- 'jj rebase' is so vastly superior to stg push/pop/sink/float for reordering changes that I don't even know where to start. It wasn't immediately obvious to me just how flexible, simple, and powerful 'jj rebase' is when I first started using jj, I have learned that it is in its own league relative to StGit's stack ordering story.
- Similarly 'jj squash' makes 'stg squash' look amateurish.
I could go on. If you're a StGit user, you owe it to yourself to give jj a proper try.
On that first point, there's a use case I sometimes have where stgit feels very clunky:
* I have a branch foo with a stack of patches which are the thing I'm working on, based on a master branch
* I have a separate stack of patches on a branch bar (let's say this is a feature that interacts with foo but it's not ready to upstream yet or maybe at all)
* I want to test and work on the combination of foo and bar and make changes that might need to be updates to some patch in foo, or to some patch in bar
At the moment I pick all the patches in foo onto bar in order to do the work and updates in this combined branch, squashing fixes and changes into appropriate patches. Then once I'm happy I go back to the foo branch, blow away the old patches and re-pick them off my combined branch.
This works but feels super clunky -- does jj do better here? That would be a real incentive to try it out.
For the rest, they don't sound like they're strong enough to beat "I've used stgit for 10 years and have a solid workflow with it".
And I just scanned the jj rebase docs and it looks awful, everything I moved to stgit to get away from. I do not want to think about managing a patch stack in terms of "move a bunch of revisions to a different parent". I like having a straightforward stack of patches that I can push, pop and reorder and list. Don't make me think about graphs of commits and multiple rebase suboptions and all that for something that I do all the time in my main workflow, please.
Combined with jj absorb, some people just work this way all the time, even.
> I like having a straightforward stack of patches that I can push, pop and reorder and list.
You can work this way too, what you'd want is `jj rebase` with -A, -B, and -r: r for the revision, and A and B for the before and after you want to move it too. This lets you reorder things however you want pretty straightforwardly. I tend to work in a stack of patches too, or at least, way more than I used to.
What I mean is that I do not want a single "swiss army knife" rebase command that does everything with lots of options to remember. It's fine to have that in the toolbox for the once in six months weird requirement. But for the simple cases I do every day I want simple commands that each do one thing and have memorable names.
If I'm understanding you correctly, you can have both. If there are specific rebase types that you perform regularly, you can create aliases for them and give them whatever name is meaningful to you.
For example, I frequently use `jj up` to rebase the current branch on main. Likewise, `jj pop` rebases just the current commit (popping it from its current place). I even have a `jj ppop` - better name suggestions are welcome - which does this but for the parent commit.
I suspect that the once-off effort to write your own commands would take no longer than it would take to read the documentation if the commands already existed, but with the hopeful extra benefit of giving you a better understanding of how to use rebase for those once in six months weird requirements when they may arise.
But to be clear, I'm not suggesting you must or even should put in this effort if you have something that works for you. My reply is mostly so that anyone who comes across this discussion and sees Steve's mention of -A, -B, etc isn't scared off by them. Whilst they're always there for you, you can use the power it gives you but in the form of single function commands that don't require you to think.
---
For anyone wondering, the aliases I mentioned. These can be dropped in your jj config with `jj config edit --user`.
Most of the time in jj you don’t even rebase manually, because it’s automatic. And the vast majority of the time, I’m using one or two flags to rebase if I am calling it. You might even need only two in this case (before only might be fine?) I just use both before and after because it’s so easy to remember the names.
Anyway you should use the tools you like, it’s all good.
Seriously! I found that you can still use the git index as long as you don’t run any jj command that changes git_head, and then I made an alias to make a commit from staged changes (squash-index and split-index): https://github.com/CGamesPlay/dotfiles/blob/2484f6f7d0ab302e...
Exactly, me too. Things like `absorb` I'll take, but I don't want jj's opinionated approach to version control. And not only do I not want it for _me_, but I also don't want it for newbies because hiding too much of the underlying design, design issues, etc., seems counterproductive to me.
This does not compute, like at all. jj rebase is half the reason to use it at all. Everything git does with the commit dag, jj does the same at worst, sometimes better.
It actually sounds like you're confusing jj with something else.
But there are dozens of great UXs on top of Git already, so if that's 95% of what jj is about then you seem to be making an argument for jj adding very little value. My understanding is that jj offers more than just UX enhancements.
Interesting, isn't it? There you are, and yet none of those dozens are garnering anywhere near the same amount of attention and enthusiasm as jj. And yet, it's genuinely mostly just that -- a novel CLI on top of the same storage backend as git. Sometimes it doesn't take that much to bring about a paradigm shift, I suppose. Just a few new ideas with a great implementation, and there you are -- they've just got to be the right new ideas. That being the hard part.
If you open your favorite git editor in a jj repo, everything you stage will become a new commit, everything you revert will… revert. I still use Sublime Merge rather frequently.
I have Luddite feelings reading about alternatives to Git.
As an industry we have soooo many languages, frameworks, tools, distros etc. It's like we are pre metric system or standardization on screw thread sizing.
I am really happy that at least for VCS, we have a nearly universal solution in Git, except for the big tech folks.
Sure, jj might solve some issues, but once it gets serious traction, all the tooling that works with e.g. repo analysis will need to start supporting git and jj. More docs need to be created, junior developers will need to learn both systems (as git is not going anywhere).
Given all the downstream effects, I do not think introducing another VCS is a net positive.
In my case, the shoddiness and thoughtlessness of Git's user interface pisses me off so much that I just want it to be replaced. A good tool similar to Git may even explain Git's concepts better than Git or its documentation that likes to discuss "some tree-ish refs".
I started using git when my employer still used svn. This was possible because `git-svn` was so good and it seamlessly allowed me to use branches to do development while still committing to svn's trunk. I think jj is trying to do something similar: jj-unaware tools work reasonably well in colocated jj repositories, and jj-unaware peers (CI tools, etc) work exactly as they did before.
I do agree that you can't really use jj without also knowing "a fair amount" about git, but notably you never need to use the git cli to be an effective contributor to a github project, which is basically the same as `git-svn` was back before git got popular.
More docs need to be created, junior developers will need to learn both systems (as git is not going anywhere).
Not true. Using jj is a choice made by a single developer. Nothing proprietary escapes from a jj clone of a git repo. Junior devs can use vanilla git if they really want to.
All the tooling that works with e.g. repo analysis will need to start supporting git and jj
Also not true, unless I misunderstand what you mean by repo analysis. Colocated jj repos are more or less supersets of git repos.
I see what you mean, and having looked a bit more deeply today, it's clear that jj is very compatible.
However, my point is that having two ways of doing this within a team is already confusing. What if one person writes a "this is how we work" document on the wiki and mentions some git, and the next person rewrites it to jj? It's extra things to think about. It's like supporting developers from a team with both Windows and Linux (Debian, Arch, Ubuntu, etc). Teams do it, and it's all possible, but it was nice that at least everyone used git.
If a product is 10x better than what's currently available, it will see rapid adoption. There was obviously something about git that made it MUCH better than the precursors and that's why it obliterated everything else.
I highly doubt that new tools will be 10x better than git. Maybe 20%?
One way I compare the git to jj transition (if it happens, or for whom it happens) to the svn to git transition is: branching in svn was awful. It was heavyweight and you were signing up for pain later down the road. Git made branching easy and normal, almost something you barely need to think about. jj does a similar thing for rebasing. For someone whose familiarity with git is clone, pull, push, merge, creating branches (so, basic/working/practical familiarity but even "rebase -i" might be pushing the limits)- for someone like that what jj offers is a similar "lift" of a feature (rebase) from "scary" to "normal" similar to what git did for branching compared to svn.
That's just one aspect of the whole thing, and of course if you're a git rebase wizard (or have tools that make you that) then this won't seem relevant. But I think for a lot of people this might be a salient point.
I'm happy to give it a try when I have some time, but I have 0 problems with git right now so it's not top of my list. My critique is also really not towards jj specifically, I'm just discussing the idea that git has extremely wide adoption now and that this has benefits :)
Git absolutely is a productivity drain and should be replaced, particularly as agentic coding takes over, as its footgun elements get magnified when you have a lot of agents working on one codebase at once. I dislike jj as move forward because I don't think it goes far enough for the amount of friction moving to it as an industry would entail.
The next generation of VCS should be atomic, with a proper database tracking atoms, and "plans" to construct repo states from atoms. A VCS built around these principles would eliminate branching issues (no branches, just atoms + plans), you could construct relationships from plan edit distances and timestamps without forcing developers to screw with a graph. This would also allow macros to run on plans and transform atoms, enable cleaner "diffs" and make it easy to swap in and out functionality from the atom database instead of having to hunt through the commit graph and create a patch.
The downside of an atomic design like this is you have to parse everything that goes into VCS to get benefits, but you can fallback to line based parsing for text files, and you can store pointers to blobs that aren't parseable. I think the tradeoff in terms of DX and features is worth it but getting people off git is going to be an epic lift.
No, but the lift of replacing git is huge, so we shouldn't do it for a Python2->Python3 sitaution, we should have a replacement that really brings big wins.
I have no idea what problem this is supposed to solve. Where is the V in VCS here? How do you track the provenance/history of changes?
You may not be "forcing" developers to "screw with a graph" (what?) but you are forcing them to screw with macros (we're adding a built-in scripting layer to the VCS?) and these unfamiliar new concepts called atoms and plans.
> A VCS built around these principles would eliminate branching issues (no branches, just atoms + plans)
And it would introduce zero new confusing issues of its own?
> This would also [...] make it easy to swap in and out functionality from the atom database instead of having to hunt through the commit graph and create a patch.
This is a weird use case. Version control systems aren't typically used for storing and swapping around bits of functionality as a first-class, ongoing concern.
Not to mention you still need to figure out how atoms get stitched together. How do you do it without diff-based patches? No superior solution exists, AFAIK.
If you have a database of atoms and plans, the V is a row in a plan table, and you reconstruct history using plan edit distance, which is more robust than manually assigned provenance anyhow (it will retain some history for cherry picked changes, for instance).
I'm sure there would be new issues, but I think they'd be at the management/ops level rather than the individual dev level, which is a win since you can concentrate specialization and let your average devs have better DX.
Is it a weird use case? Imagine you refactor some code, but then you realize that a function was being called in a slightly incorrect way after a change (prior to the refactor so the revert isn't trivial) and you have to go back and revert that change, let's say over 100 files to be fun, and let's say that the code isn't perfectly identical. With git you probably have to do surgery to create a patch, with an atomic system you can easily macro this change, or you could even expose a UI to browse different revisions of a piece of code cleanly (which would blow up with git).
if I make a plan which causes the project to be identical to its state 5 years ago, the edit distance is zero, but in no way can you call that a measure of history
You're still thinking in graphs. That plan would already exist in the database, you would just be making it a build target instead of whatever new plan was targeted before.
It seems as though you've come up with a model for representing source code repos in terms of a data model of your own design, solving problems of your own choosing. But what you describe is not a version control system in the generally agreed upon sense of the word.
Having used jj for the last year and a half, I hear you on the tooling issues. From IDEs, to plugins, to docs, to workflows, to LLMs, jj is not as well supported as git.
Despite that, it's still a net time-saver for me, and I suspect the same will be true for others. Git imposes constant overhead, mostly with its poor UI, but also by some of its unnecessary models (e.g., staging as a separate concept).
As far as I can tell, jj intends to be more like a cross-VCS frontend (with their own native backend coming at some point). If tooling supports jj, it would automatically support git, jj's native backend, Google's hybrid backend and any other backend a user could add.
I think 'jjhub' is a decent immediate first approximation, and is often how I've started out talking about it with people. At the same time, we have to offer real value here, as you can already use jj with github (and I have for a long time now), so there's more to it than that. But yes :)
If you're starting over with a new backing store using jj's pluggable backends, can you give us a native lazily-materialized store for megarepos? It's a tragedy that the open-source world has no answer to piper.
- Everything locally stored in the repo: PRs, comments, issues, discussions, boards, ...
- CLI first
- Offline first (+ syncing)
- A website for hosting/presentation
Noted :) In another comment I linked to beads, which is a cool project to keep your issue tracker in your repo, but that's just a personal thing, no comment on what the company plans to do (or not) in this area.
I use command-line tooling much more than IDEs (e.g. VS Code), so the `gh` command-line tool (https://cli.github.com) for doing most of the usual hub-oriented workflow (PR authoring, viewing issues, status updates, etc) really helps a lot - I don't have to constantly <cmd>+<tab> to my browser, and point-click-point-click through web pages so much. It would be fantastic if ersc or any other jj-centered code-sharing hub had similar tooling early on.
When I tried Fossil it had things weirdly separated.
I was expecting when I make a commit, I would have the facility to specify what issues it addressed and it would close them for me automatically. It seemed there is so much opportunity there to "close the loop" when the issue tracker, etc and integrated in your VCS, but it wasn't taken.
That's my favourite thing about fossil though. History is what it is, not simplified to look "clean" (i.e. hide what actually happened and when) and you get a lot fewer footguns to ruin everything by accidentally rebasing things to the wrong place without noticing.
I personally find this to be more interesting simply because it has the social inertia of Bluesky behind it. Software has centralized on GitHub because it is discoverable there. Software on Tangled is discoverable through Bluesky, and the stacking support to make jj nice to use with it is just an added bonus.
> I also don’t mean to imply that everyone at Google is using jj, but the contingent feels significant to me, given how hard it is to introduce a new VCS inside a company of that size.
I don't mean to imply that Google is fickle, but anything besides Google's perforce fork is deprecated every few years. We used to have a proper git wrapper, then mercurial+extensions, now jj is supposed to replace the mercurial thing, all in 7-ish years?
In fairness that's not much different from any other internal Google tool. Those don't often have a two-digit years shelf life.
I'll venture that jj is there to stay, however. If not at Google, then in general. It's just too much of a quantum leap. I think I've finally identified what about it sits so right with me: a change's identity is preserved through its revisions. In bare git, after a rebase or an amend, you get a wholly different commit that just happens to have a similar content.
Mind you, I'll also venture that jj will remain based on git as its storage backend, despite its stated goal otherwise. Git's internals are just too good at what they do to make it worthwhile to replace them.
I hope it sticks around so that I can use the same workflow at work and home. I'm really enjoying how fast all the jj operations are compared to mercurial.
The git wrapper was never fully supported and had some rough edges (I think it was only ever a 20% project, and also its, like, really old). And the customized mercurial has been around for more than 7 years, I think close to a decade (the client I'm using right now is turning 7, and it wasn't my first one).
I used git5 from when I started in 2011 to when I left in 2017.
I'm going back starting on monday, so I'm curious to try out jj.
In the past 10 years it's all been github and gitlab, and their code review tools are so painful, specifically w.r.t. tracking discussions across revisions. I never felt excited to try out jj because I was afraid it would that situation even worse.
I started using git as my daily driver at Google in 2018. I was forcibly kicked off it for mercurial in 2020. The deprecation plan for mercurial is 2026 IIUC. Maybe if you adopted mercurial early it's different.
Why did Google decide to choose Mercurial? Based on what I read the main reason was that the mercurial dev team was willing to prioritize features needed for Google to add custom extensions to support its monorepo, and the git dev team wasn't going to reprioritize just for the sake of Google.
Yes, that's correct. Another reason was that Mercurial is easier to customize because it's written in Python so we could sometimes just replace whatever we needed without needing much changes from Mercurial itself.
Yet another reason is that the .git directory is considered a documented API and several other tools and libraries depend on it (e.g. JGit and libgit2). So any new features for Google would need to be made to those tools too if we wanted things built on them to work.
I can second that the aforementioned reasons are true. The funny difference is that Google employs the primary git maintainer. Git has a lot of customers though so it rightfully is very conservative with development.
I tried jj, but I'm used to Sublime Merge. Doing version control in the command line was just too much repeated typing, as you keep losing information. In a GUI you always see the current state. You commit, it updates the display. A diff is a click away. Changing focus to type the commit message is a click away. No reason to ask the same questions again and again. Even with aliases like jjl for `jj log --limit 10` it felt too inefficient and annoying. Selecting individual hunks with the keyboard... I don't ever want to do that again. In SM it's a pleasure.
The git CLI might suck, but SM just doesn't. Looking forward to jj GUIs taking off. Or even better: jj getting integrated into SM.
I tried Jujutsu in the last day and was going through your tutorial. I really liked the experience and can see some potential. I also got the feeling that there is a missing puzzle piece. For example, do I get any benefit from the change id if I push to GitHub for PR review?
I guess you benefit from some of the good parts only with the Google internal Piper backend, at the moment. So I’m curious about the ideas and plans you have at ERSC.
But what I’m also really yearning for is having a distributed asynchronous/offline-first code review flow built right in. The distributed nature of git somehow got lost with PRs or MRs in GitHub & Co.
> do I get any benefit from the change id if I push to GitHub for PR review?
As of right now? Not really. The details are more complicated, but basically, if your project doesn't like the behavior of github when it comes to comments + editing commits, and wants you to tack on new commits instead, we can't change that behavior. However, https://github.com/LucioFranco/jj-spr can, in some situations, give you some of this experience. If your project is okay with editing commits, then it can help you locally, sure.
However. In an interesting turn of developments, GitHub's new SVP just tweeted that he's likes jj and is interested in adding stacked diffs to github. I don't know how this squares with their "no new features for 18 months" thing, but we'll see!
> But what I’m also really yearning for is having a distributed asynchronous/offline-first code review flow built right in.
It's not code review, it's issue tracking, but I've been using https://github.com/steveyegge/beads this week, and I think it might be the first "put your issues in your repo" system I actually enjoy. It says it's built for AI stuff, but like, you don't have to use AI with it.
One thing to note is that the actual commit objects you make through jj do include a `change-id` header which means that even though Github is unaware of jj, peers using jj will still be able to keep track of the rebases.
Demo: run `git cat-file -p HEAD` in any jj repository that you've made a change to.
Does it work with large binary files and not choke like git? Cause git may have won for webdev etc, but in some industries such as gamedev, Perforce is the king...
git is barely used at all because it can't handle binary files worth a damn(yes I know about the large file extension, no it isn't sufficient).
I am the last person to ever promote perforce, but as of last yearish it has the option for binary delta transfer using fastCDC.
Even without that, it is a just straight up a lot faster than git lfs. I know this because I benchmark it against git pretty frequently as I am creating my own large file capable VCS.
What do you mean by this? It's hardly equivalent to LFS. The binary files aren't replaced with a text pointer with actual content stored on a server elsewhere. Binary files are stored in the same place as text files.
From the user's perspective, when setup correctly Git LFS is transparent and they don't see the text pointers - the binary files are replaced on push and pull to the server.
It's the same user experience as Perforce?
Yes, Git is more low-level and it's possible to see those text pointers if you want to.
This is what you want to believe but its not true.
I’m really sorry, git lfs is an ugly hack, and its always painful when you discover that some gamedev team has been forced into it by “better knowing” software developers.
It reminds me a lot of “features” of software that is clearly a box ticking exercise, like technically MS Teams has a whiteboard feature. Yet it lacks any depth: its not persistent so its gone after the call, and it’s clunky to use and to save.
… but technically the feature exists, so it’s harder to argue for better software thats fit for purpose, like miro or mural.
The question as I recall was what Perforce does that Git LFS doesn't, so I'm sorry to disappoint but my hands were tied.
Anyway, I dunno, man. If you want binary files to work, some form of per-file mutex is indeed a requirement. And for this to work well, without being a lot of hassle (and regarding that, see point 2, which I note has been accepted without comment - not that I expected anything else, the argument that Git is the artist-friendly choice would be a difficult one to make), any modification of the mutex's state has to involve a round trip to ensure the info is up to date. You can't rely on something local, that only gets updated sometimes, because then the info can be out of date! Worst case, N people find out too late that they've all been making changes simultaneously, and now N-1 of them will almost certainly lose work.
(You might be inclined to moan at people for not going through the full process, but: we have computers now! They can do the full process for us!)
I joined the sapling/subversion company this year, but haven’t had the chance to use jj. But given its resemblance I must say sapling has been great. Much more intuitive than git, and I find commit stacks much easier to follow than branches. I do wonder how it will work without the level of support of Meta, since you won’t have the same commit stack review UI (basically a series of pull requests being reviewed at the same time). So something like what this author is working on is needed.
What I miss from the Perforce and Subversion days is committing directly to trunk in a team environment. Now everything revolves around PRs and lengthy code review. With direct commits to trunk, everyone was in a rush to get their commit in before someone else did, so they didn’t have to update and have conflicts. This made commits small, frequent, and usually well-scoped. What -sucked- was when you did need to do a large refactoring or other big change, then that was work best done on a weekend because branching and merging didn’t work very well.
Git’s model is right for PRs for open source projects where one day you could wind up with code from someone you don’t know and you need to take your time in review and possibly making further changes before merging. But as much as git’s a meaningful upgrade over Perforce and (especially) Subversion, branching and merging is not the right default model for normal team development.
PR's are a man made ritual that was never required by GIT. Just like we are all doing the Lean/Scrum or whatever dance. It does not change the development process, it's just a man made ritual for collaboration.
The branching model is nicer to work with than the revision based model from subversion though.
I can’t speak to people’s motivations, but I have been a very active member on this forum for a long time, and I was on the Rust team for about a decade, beginning before 1.0.
So the article is more like “Here’s why I started using Rust. If you thought I made a good choice then, well, I’m making a similar choice in case you want to join me.” Seems people find that compelling.
Just like Rust’s success was not inevitable, neither is jj’s. But I think it might work out.
You could also consider this a disclosure of sorts, I post a lot on this forum about jj (and Rust), and so knowing that I now have a financial incentive is important context to some people.
That line wasn’t even in the original post. It is not nearly the only technical advantage. The point of the post is just not about that topic, so that content is not in the post.
This is very funny. People just love jj! There are no conflicts of interest, at least not until now that there is a company built around jj. But they also started it because they love jj!
I've been enjoying the jujutsu kaisen VSCode extension, you just need to disable VSCode's built in git integration for each project where you use jj (although I'm planning to invert by disabling git globally and then only enable it for the few projects where I don't use jj)
Since JJ has technically git compat, I think there's 2 things needed for it to take off
1. A good vscode extension (there's two so-so ones that I'm not sure are being updated)
2. LLM knowledge. I ask gpt-5 about doing something in jj the other day, it didn't even recognize it at first. When I reminded it it was a vcs it hallucinated half the commands. I ended up figuring it out myself from the docs
I do think that GUI stuff is important, there's a lot of folks who have said "not interested unless there's a magit equivalent" which I totally understand. I'm not a big GUI person for VCS myself, so I don't feel the same pain.
For LLMs, yeah this is hard for any new project. I use Claude Code, and it does a decent job with jj, it only tries to do git stuff sometimes. I haven't asked ChatGPT about it though, I'll have to experiment with it myself.
> I ask gpt-5 about doing something in jj the other day, it didn't even recognize it at first.
Using VC commands an LLM generates for you sounds like a pretty terrible idea anyway. What if they delete your data? Why not spend 5 minutes searching for information written by a human?
I'm not running them blindly and I do always ask the llm to explain the command since the end goal is not to have the llm do the work but rather for me to learn jj.
+ any good tool will not let you run destructive commands without confirmation unless it's with some force flag and I have enough computer knowledge to not use force flags blindly.
+ "information written by a human" can be unreliable too.
What a terrible world we're barrelling towards when "LLMs don't know about it" is considered a blocker towards something taking off. Read a blog post, read a man page (which GP says was indeed their solution), or just play with the dang tool, I assure you these things are still possible without the assistance of a predictive text engine helping you!
One problem is that Stack Overflow isn’t being updated with questions about new tools because it’s been crowded out by LLMs, so the LLMs can’t train on these nonexistent new questions/answers.
This feels like the same question as in consumer goods: "do I buy this thing now, or do I wait for the potential refresh in 6mo to see if it's better?"
Just get the thing you need now, there's no promise the grass is getting any greener, nor that you're any better off waiting. Maybe the LLM won't learn for 6 months. Or 12 months. Or maybe the AI company will run out of money, or jack up their prices to where you're not using it anymore, or whatever.
One thing I've wanted is the ability to group commits into a mega commit. So the history of little changes remains, but as you are scrolling you don't see all of them.
This is an interesting idea... jj has a templating language, but it's not stateful, so you couldn't like, hide some commits based on other commits, for example. But I'll file this away...
That's called a merge commit. You can even group them further by creating merges of merge commit. It requires you to merge in the right direction though.
--first-parent? --simplify-by-decoration? It doesn't hide them by default yes. It does have several paragraphs with all the ways how to simplify the history.
I’ve been adopting jj for my personal projects for 1-2 months and have been very happy with it. One downside is if you move back to edit older revisions, it’s very easy to accidentally append something that was added to .gitignore in the meantime. I suppose it’s a relatively unique problem. Other than that I definitely like it, but my git knowledge accumulated over the years is still much larger than jj.
I guess I see what the author was trying to convey talking about Rust/Go at the start but I'll admit it confused the hell out of me when we got to Jj (horrible name IMHO but whatever).
Jj is a VCS, it was not at all clear (to me) until I got further and I was very confused as to why we were talking so much about source control (I thought Jj was a language since the article started by talking about Rust/Go).
Apparently Jj can work on/with git repos making it easier to adopt incrementally which is neat and the main point of this post is that the author is leaving Oxide to go work for a new company trying to to create the GitHub of Jj (my understanding at least).
I hope this helps someone else who might be confused like I was.
Thank you for the feedback! I talk about it so much I feel like regular readers of my blog will know this stuff already, but I also don't want to leave out important context for others.
The full name is Jujutsu, but I think "jj" did come first. "j" is nice because on QWERTY keyboards, not only is it on the home row, it is one of the two keys that has a little tactile marker (bump) on it.
So excited for this. I talked to the ERSC folks last year about joining but it was a little early for me. Still incredibly excited about what they're building and glad to see one of my favorite people joining the effort.
I think that the ability to collocate with fit is really the thing that makes even evaluating the use of jj feasible in many organizations. To consider pijul, it requires throwing away all of your forge setup, configuration, permissions, backup, disaster recovery, as well as updating every CI pipeline.
In a very real way, git won, and the inertia behind git is higher than it was for any VCS tool before it, and so just being better isn’t going to be enough, you’ll also need to interoperat.
I strongly suspect that its not feasible to colocate pijul and git. git and jj are based on snapshots, while pijul is based on patches. They have very different models.
As long as some repository state in either system can map onto a checkout in the other system it should be possible at least in some capacity. I’d like to try out pijul at work, but I’d need an analog of jj git fetch and jj git push. Whatever happens in between doesn’t really matter too much unless it’s tedious manual bookkeeping to maintain history mapping.
Agree, the feature of commutative patches just seems obviously superior? Not sure why there's a critical mass to adopt jj over Pijul apart from jj's git backend.
I think this gets at one of my original comments in this thread: I think the reason of commutative patches has communication issues. The benefits touted are very abstract. The problems I’m aware of it solving are mostly theoretical problems for working devs, and so the message hasn’t landed. If I were interested in advocating for these tools, I’d be trying to find a message that does resonate.
I think git compat is the absolute #1 priority for working devs to even consider seriously checking version control tools out. I wouldn’t have tried jj if I couldn’t run it on my work git repo and I didn’t try pijul on my work repo for this reason. It hurts my inner geek who’d like to try using it for something professionally.
> The real innovation of a lot of these alternative DVCS systems is that they free the state of the source from being dependent on the history that got you there. Such that applying patches A & B in that order is the same as applying B' & A' -- it results in the same tree. Git, on the other hand, hashes the actual list of changes to the state identifier, which is why rebasing results in a different git hash id.
Anybody who's wrestled with reordering/rebasing git history or has done git archaology is able to understand this benefit.
From Pijul's site:
> Pijul is the first distributed version control system to be based on a sound mathematical theory of changes
After years of grudgingly tolerating using a deployed prototype for a VCS, yes, I want the mathematically sound alternative.
All that being said, I do wish you the best, because truly, I am tired of git and JJ does seems like an improvement.
Do I benefit from getting the same hash no matter what order I put the patches? In fact getting the same tree either way feels like information loss. Even if I only merge, won't I lose track of what the actual state was when each commit was made?
Most of my wrestling is with merge conflicts and a consistent tree doesn't help with that.
At the risk of being unreasonably negative, stuff like this just makes me feel... tired. Git is... fine. I'm sure it doesn't solve every problem for everyone, and oh boy does it still have many rough edges, but it works and (as the article points out), git has won and is widely adopted.
I've extensively used CVS and Subversion in the past. I touched Mercurial and Bazaar when I ran into a project that used it. I remember in the CVS days, SVN was exciting to me, because CVS was such a pain to use, in almost every way. In the SVN days, git was exciting to me, because SVN still had quite a few pain points that poked me during daily use. Again, yes, git had and has rough edges, but nothing that would make me excited about a new VCS, I don't think.
Maybe I'm just getting old, and new tools don't excite me as much anymore. Learning a new tool means spending time doing something that isn't actually building, so my eventual use of the new tool needs to save me enough time (or at least frustration, messily converted into time units) to balance that out. And I need to factor in the risk that the new tool won't actually work out for me, or that it won't end up being adopted enough to matter. So I think I'll wait on jj, and see what happens. If it ends up becoming a Big Deal, I'll learn it.
I think being conservative about tool use is totally fine! I'm actually pretty conservative about most of the tools that I use.
The goal of this post wasn't really to convince anyone on why they may want to give jj a shot, more of just a post about how I think about technologies I may want to spend my limited time on this planet working on, and announce that I'm making a move.
I don't think that you're being unreasonably negative. I think it's crucial for technologies to understand that your position is basically the default one, and that you need to offer a real compelling reason to choose a new tool. For some people, jj has enough of that already to bother with choosing, but I think the real power is in things that aren't widely available yet. Hence the need to go build some stuff. It's early days! Not even 1.0 yet. It's very natural that most people do not care at this stage.
Sure, definitely, sorry for being a bit off-topic, clearly this was about you and your plans and not intended to be about jj itself.
Having said what I said, I do find new tools to be interesting, and I do hope jj ends up being successful. I'm always happy to be surprised by something that fixes problems that I didn't consciously know I had, or that adds new features or work modes that make my life easier in ways that never would have occurred to me in the first place. I was a pretty early git adopter, and it works great for me, but I'm sure a decent chunk of that is because I understand how it works under the hood, even if it often doesn't present a great UX.
And even if jj doesn't eventually surpass git's popularity, it's great to have other options, and avoid monocultures.
So for me, the most compelling thing about jj is that it is somehow simpler than git, while also being more powerful than git.
What I mean by simpler is, there's fewer features, which makes things easier to pick up, because these features fit together in a way that's more coherent than git's. By more powerful, I mean jj lets me regularly do things that are possible, but annoying and/or difficult in git.
I loved git. I was never the kind of person who thought its CLI was bad. But then, when I found jj, I realized why people thought that.
As someone who loves git, has always thought the criticisms about its interface were overstated... but also feels like it maybe has too many incoherent ways of doing things, this is the best sales pitch I could've asked for (and I came to the comments to ask for a sales pitch). Thanks - I'll try jj out the next time I start a hobby project.
Another good sales pitch is `jj undo`[0]. It puts the repo back to previous state, regardless of what the mutative operation was. It's a powerful and simple safety net that unlocks experimentation.
It does this by adding an new operation on top of the operation log[1], so you don't lose repository states by moving up + down the op log. There's a corresponding `jj redo` as well.
jj undo is great but it's a one time thing. You can't do jj this, jj that, jj other, jj undo, jj undo, jj undo AFIACT. You have to look into the op log and jj op restore for that. It's nice you can get back to where you were though.
The biggest issue for me is it requires active change management (or feels like it). In git I do `git checkout foo` then I start editing. If I want to see what may changes are since foo then `git diff` tells me. With jj though, `jj edit foo` is the to git, state of the repo ALL changes to foo. So any new edits are invisible. So, instead of `jj edit` I have to do `jj edit` `jj new`, then later squash those into foo
I know there are similar cases in git but I guess I'm just used to git so I wasn't using those cases.
that said, I'm mostly enjoying jj. Though quite often i get a conflict I don't understand. Today I got 2 and it told me choose A or B. I did `jj diff -r A -r B` and it said no diffs. If no diffs aren't there no conflicts? I'm sure someone gets it but it was annoying to just have to pick one to abandon
I’m not sure if it’s a typo, but you don’t need to edit and then new, you can just new. It’s a good habit to get into as a replacement for checking something out.
I’m not sure what happened in your conflict situation either, that does sound frustrating. EDIT: Oh, I wonder if it was this: https://jj-vcs.github.io/jj/latest/technical/concurrency/ specifically, that I bet the repo was being modified concurrently, and so you ended up with a divergent change.
> You can't do jj this, jj that, jj other, jj undo, jj undo, jj undo AFIACT
You can as of v0.33.0[0]. Previous behaviour was that `jj undo; jj undo` would leave you where you started (it undid the undo).
> The biggest issue for me is it requires active change management (or feels like it). In git I do `git checkout foo` then I start editing. If I want to see what may changes are since foo then `git diff` tells me. With jj though, `jj edit foo` is the to git, state of the repo ALL changes to foo. So any new edits are invisible. So, instead of `jj edit` I have to do `jj edit` `jj new`, then later squash those into foo
I'm not 100% clear on what you mean here, but a few things that might help:
1. In jj you don't "checkout" a branch, you edit a specific commit. That commit might be pointed to by a bookmark but it doesn't have to be. A jj bookmark is roughly equivalent to what git calls a branch. Note that a git branch, and a jj bookmark are just pointers to a commit, as illustrated here[1]).
2. If you want to resume work on a branch/bookmark instead of `git checkout BRANCHNAME` you'd do `jj new BRANCHNAME` which puts a new commit on top of the commit and sets it as a working copy.
3. Bookmarks don't auto advance like they do in git. So adding new commits on top of a "branch" will leave the bookmark where it is until you `jj bookmark set/move` it. So you could squash commits down into the "foo" bookmark, but you could also move "foo" to point to subsequent commits.
4. Not sure what you mean by edits being invisible, but if it's seeing a diff from main to the tip of your branch (with a change id of ex. XYZ) it would be `jj diff -f main -t XYZ`.
The core of their complaint is that if you use `jj edit` it's not obvious how to get a diff of what you did. The answer, of course, is that you can use `jj evolog -p`.
You're welcome, and feel free to let me know how it goes. There is an adjustment period, for sure, and (in another eerie parallel to Rust) some folks try it, bounce off, and try again later, and it sticks then.
The auto-commit behavior was one of my biggest concerns when starting, but it turns out that when combined with other things, I'm a huge fan now, for example.
Git's CLI is awful compared to fossil or even mercurial. Jj seems like an improvement over git, but it lacks a web UI like fossil has. It's very useful. Basically like a self contained github lite, only without the needless complexity, the enterprise bs, the annoying login process, tokens, passkeys, brain damaged permissions system etc.
Right, “parent branch” implies a tree structure, but git is a DAG.
You might have a specific workflow such that you can actually answer your question, but it won’t generally apply to all repos.
Since a branch is really just a label for a specific commit, which may be at the end of a chain of successive parent commits, a branch isn’t really a first class structure, but a derived one.
You can get the fork point of a branch, which is a common ancestor commit shared by another branch, but that fork point is a commit and may not have a branch label. That commit can have any number of other branches going off of it: how would you decide which one is the parent vs just another sibling?
My assumption after looking at jj is that it is not as complicated as git yet. Give it time. It’s also not even as simple as git for many tasks, based on their own docs: https://jj-vcs.github.io/jj/latest/git-command-table/
That's even less how jj works, unfortunately for this use case, because jj doesn't require branches to be named.
You could probably attach metadata to commits indicating the branch name at time of creation, but there's probably a lot of weird edge cases to handle.
I don’t think you ever need to do this, jj tracks changes much better than git, assuming I understand your question. E.g. you can rebase a whole local change dag based on a commit from origin with a single jj rebase -b and it’ll move bookmarks (git branches) correctly.
Just my feedback - I've personally found jj more complex for simple projects. Like if you have a non-collaborative repo where you push to main most of the time after making a series of commits, in jj you have to keep updating a bookmark before pushing it and there's no one command to do both.
If you have another machine on main without any outstanding changes and you want to pull the latest changes that is probably also two steps (git fetch + new?)
That said, I've been liking jj quite a bit for more mature / collaborative projects. It has been a learning experience. (don't enjoy updating bookmarks for PR branches though; jj encourages rewriting history which is not my favorite choice for code review branches; I often work in repos that squash-on-merge).
For updating bookmarks I've found like half a dozen variants of `tug` alias the community has come to using which is just a slight improvement (bit daunting to newcomer to pick 'best' one and not fan setting up aliases on all my working devices).
It would be nice if jj was better than git for the fundamental workflows like this out of the box overall.
It's technically a bit more than that. JJ is its own VCS, with pluggable backends. Google has a closed-source Piper backend, the git backend is the only real open source backend. But at high level, it's fine to think about it in that way, yeah. I tend to think about it as being more "able to work on git repos" than as a UI.
I used to think the same way about jj not too long ago. I even wrote a few comments here similar to yours, but since then I’ve changed my mind.
For me, the turning point was realizing that jj actually eased some of the frustrations I had with our rebase workflow at work. It took a while for it to click, but now I wouldn’t want to go back
In my day to day, its basically "git pull --rebase repo branch", plus some interactive rebate to squash commits, and it's not particularly frustrating, so I'm curious what you're doing that we're not.
Basically the same except in some projects we are not supposed to squash. So, sometimes we end up with long long histories to be rebased onto other long histories.
rerere only helps so much with conflict resolution but with jj I think it is as painless as it could be.
jj is actually so good though. People don't need to know you're using it, which is why it's nice.
A problem I run into when working with other people is that code reviews take forever and I need to build on top of them. Code gets merged while it's being reviewed, and it becomes a burden to keep rebasing your stack of PRs. It's also difficult to do things like designing each PR against the main branch, but testing all 3 of them together. (Sometimes you want to write the docs / take screenshots as though all your features are merged as-is.) jj makes all this trivial. You tell it what you want and it does it without involving an index or working copy or interrupting you to resolve conflicts.
I've found that it really makes me less annoyed when working with other people. I don't know why it takes people longer to review code (or to even open the review request) than it takes me to write things. But it does, and jj is what keeps me sane.
To be fair, I also use it on personal projects because sometimes you have 3 things you want to try at once and they're not related to each other. Upstream isn't going to change without your understanding, but it's still mechanically something to maintain the rebases on those 3 branches. jj just makes this burden go away.
Having said that, I don't know why a "jjhub" is needed. Github seems fine. jj's just a UI for git.
> Having said that, I don't know why a "jjhub" is needed. Github seems fine. jj's just a UI for git.
When you make a change to a pr in response to review feedback, do you just jj edit it in, and end up with a force push on GitHub? After which the review comment might get detached from the code or even hidden.
That's definitely something that could be better for me. (That said, there are other projects making it better too.)
Without knowing much about jj, isn't this more of a problem in how the GitHub/GitLab/Forgejo Pull Request system works rather than a jj problem?
Patch-based workflows in general (where you heavily use git rebase -i to curate a smaller set of "perfect" commits for anything you'd PR, rather than just piling new commits onto every PR) don't work well with the GitHub approach, which heavily favors merging stuff through the web interface (making merge or squash commits along the way).
You can make it work of course, but GitHub tends to behave in weird ways when it comes to how it's interface works with patch-based workflows. Perhaps a better estimate would be to see how it compares to a forge like Phorge or Sourcehut.
Yes, that's why I asked it as a response to them saying GitHub was just fine :) With Git, I work around it by adding `--fixup` commits and then doing a final `git rebase --autosquash` just before landing it, but it would be nice to have a better workflow.
(The mention of the GitHub SVP being interested in stacked diffs sounds good in that regard. I'm also keeping an eye on Tangled and, now, on ERSC.)
Yeah, I always force push. I always treat PRs as one atomic unit ("squash and merge") and the history that goes into the PR isn't relevant to me after it's merged. Maybe once or twice I've regretted this (maybe you want to backport one part of this PR to the release branch, and now you have to manually create that) but in general, how something is made can be ugly and it feels good to me to erase it when something is ready to be merged.
I guess in terms of clean workflow, I could get into that more using jj (i.e. create a PR for every bit that I want to have in the history later), but as a reviewer, I always get annoyed when I have to do a re-review of something that was force-pushed, and I'd like to avoid doing that to others. So I was kind-of hoping you had found some magical silver bullet there, haha.
I see. I guess you guys keep merging in your upstream branch until it's finally time to merge a PR into that branch? A lot of people like that workflow but it never really made sense to me.
I personally feel like your employer's policy is unusual for PR branches. Other people force pushing? Bad. Force-pushing to `main`? Bad. But force pushing to your own branch when you can delete it and re-create it? Just a waste of your time for no real reason. I wonder what the justification is.
> I see. I guess you guys keep merging in your upstream branch until it's finally time to merge a PR into that branch? A lot of people like that workflow but it never really made sense to me.
Correct.
It makes sense to me because it leaves a linear history of git commits in response to comments on a PR which makes code review easier. We squash and rebase at the end.
> Just a waste of your time for no real reason. I wonder what the justification is.
We used to be on Gerrit, which I found better for code review as I could stack a bunch of minute changes together. I probably had 50-100% more productivity on Gerrit before my team made the switch.
Unfortunately, the industry standard is GitHub PRs. My team wasted a ton of time onboarding people to Gerrit and even after onboarding, less than 5% of people understood stacked changes enough to use them effectively.
Since force pushing/rebasing messes up GitHub PRs it was banned.
If you have suggestions for alternative code review workflows on GitHub I'm happy to hear them, but ideally they could be incremental.
I use git but I instead just keep all PRs stacked on top of each other and rebase when one is merged. If something is easy to review I push it down the stack. If something is harder to review I keep it at the top of the stack.
I don't open a PR for each commit, and we use squash commits at work which makes it harder to have this workflow but it still works fine for me.
I rebase only the leaf PR, and I have update-ref enabled to update the branch refs of all other branches in the stack. It works well. The only manual process is that I have to manually force push each branch afterwards.
Lastly, I use the `-x "cargo fmt" -x "cargo clippy"` feature when rebasing (which is missing in jj) to make sure the stack stays in a good state
It is not even close to sufficient. jj fix only executes the command on one file at a time (only the changed files) so it can't work on anything that has linter semantics.
jj does this for performance reasons. They don't want to perform a full checkout for every rebase action. This is simply something I disagree with
> jj does this for performance reasons. They don't want to perform a full checkout for every rebase action.
It's true that `jj fix` can be faster by not touching the working copy, but we also want a `jj run` command for the linter feature (https://github.com/jj-vcs/jj/issues/1869). It's just not done yet.
I have a question and you might have an idea about this:
I have a workflow where I have my main and a bunch of branches that are children of other branches. So: main, branch_a, branch_a_1, branch_a_2, branch_a_1_x, etc. Probably not a good workflow, but that's what I do.
I keep editing old commits in my branches to have clean, atomic commits, which fucks up my branch structure and I need to cascade-rebase everything manually.
Do I understand correctly that jj does it automatically?
I think yours a perfectly reasonable stance, and I often feel the same way. And all this complaining about git is getting tiresome, especially if you had to deal with its predecessors.
FWIW, I still tried out jj and found it a joy to use. I use it all the time now. Most of the time, it not only gets out of your way, but rolls out the red carpet. I'm saying that as someone who knows their way around the git command line. t's like replacing your trusty old remote control with a new one where the buttons are well labeled, ergonomically placed, that lets you do entirely new useful things, and it has a universal back button that just works.
Maybe jj is an especially good fit for my way of working, but I do think that it is a real, actual improvement for everyone.
And it's super easy to pick up anytime. So yeah, I think you're doing it right! Sit back and let it come your way. From what I can see, there's a pretty good chance that it will.
The good thing is that a new player entering the arena allows a fresh look at certain problems, and some solutions then get backported to the OG project. I've read it already happened that jj inspired some changes in git.
Same happened e.g. with nodejs getting deno and bun around, which allowed to break nodejs' inertia on many problems that the others have solved.
I learned all of cvs, svn and then later git when getting into Linux and Open Source. Based on my early experience of multiple systems, I'm very surprised that git has dominated and lasted this long already!
I've also worked with CSV (barely) and SVN (more extensively) and I was blown away by Git.
You can have real branches! Many of them! You don't have to manually merge them! It's decentralized, you can have multiple origins, it lets you work offline! The list goes on and on.
There were many compelling reasons to switch to Git. But for all the articles about jj out there, I've never read any compelling reason to switch to jj. "It easier", "the commands are somewhat more ergonomic"... that's all?
One thing JJ has that git doesn't is the concept of first class conflicts. In JJ, rebasing or merging never fails, but it might record a conflict to resolve later. Git, on the otherhand, forces you to drop we everything to resolve conflicts immediately. It sounds like a small thing - but in my experience, being able to resolve conflicts later when I feel like it is absolutely amazing and really helps reduce context switching.
You don't need to switch from git to jj. You just try out jj on your git project and see if it clicks. If you don't like it, you just don't continue using it.
I personally feel quite capable at git and am not looking for another flow or tool.
But. I think it's incredibly useful for organizations to have patterns for how they use git. There can be huge variance! And there's so many people who don't feel comfortable doing interactive rebases (of their feature branches), or other serious monkeying with history.
(I also think jj's flows are incredibly good about avoiding accidental loss of work in a way that git can be extremely dangerous at.)
I think there's a ton of value to having more of a pattern than the free-form jamming that git gives us. The real value I see in jj is that it's more than the toolbox of things git gives one: it's something more learnable, teachable, and directed than git. And it seems to do it pretty well, with style, and less monkey business. I'm still super greenhorn at jj, and honestly lacking the need for it, but I'm excited to see it come along. Especially since it is compatible with so many other VCS.
I used to think this, too. Tried jj a few times before it clicked.
It’s very liberating in some ways, in others it’s simply no worse than git. You can do everything you can do with git, but some of those things don’t require multiple steps or n repeats of the same action. jj rebase + commitable conflicts + jj undo = freedom and peace of mind.
Fair points. I'm also generally happy with Git myself.
I've been exploring JJ mainly for its slightly different approach to change tracking (~every change gets tracked, at least initially, rather than just commits).
Stacked PRs also look interesting but I haven't had an occasion to try them out yet.
stupid question: can someone with a deeper understand kindly explain, why are talks lurking around the corner about hopping version control systems, whats wrong with git that jj solves?
There's a variety of things that all add up to a very pleasant experience.
For starters, git has a poorly-designed UI, whereas jj borrows from Mercurial, which was widely-considered to be better designed from a DX perspective. I can usually guess what commands and flags to use on jj without reading the manual; not so for git. E.g., `jj undo` undoes anything you might have done; For anything esoteric in git, I consult ohshitgit.com because there's half a dozen different commands to know to undo everything.
Another is a simplified model whose pieces compose better with each other. There's no need for staging or stashes in jj. You essentially work in a HEAD commit all the time, and when it's ready, you commit what you want, make a new child commit and keep going. A stash is just some random commit. Anything you've learned about manipulating commits now applies to "stashes" with no extra effort.
No need for branch names. If you come from git, learning that "stashes" are just random little HEAD commits may make you groan and think you have to give each of those branches a name. jj doesn't care about that. I still give major branches a name, but since I don't have to name them in general, it's easier to create lots of little branch "experiments". Branch names were never really a substitute for commit messages, anyway.
More rebase-friendly IDs. Git penalizes the use of SHAs when rebasing, whereas in jj, change IDs are stable after rebasing, making them actually usable. This makes rebasing easier and safer.
First-class conflicts. Git forces you to stop the world and deal with conflicts immediately, or back out entirely. This isn't really required, and jj has no opinion. It marks conflicted changes, and waits for you to get around to fixing them. This is great when your boss interrupts you for something high-priority. It also automatically rebases all downstream changes after you've fixed it, which usually clears their conflicts too.
Megamerge workflow. This is possibly doable in git, but I don't know how easy the workflow is. But in jj, creating a work commit that has multiple branches as parents is trivial, as is pushing work down into individual parent branches. This makes integration easier, as you can easily see how your upcoming changes interact with your colleagues' upcoming changes.
Jujutsu's not a revolutionary VCS change (like Pijul's theories of commutative patches), but it's generally way more pleasant to use, with better DX, than git. I gave up git forever after two weeks of jj.
What the JJ developer seam to misunderstand is, that the index and stashes are a feature to improve the users workflow. You can bypass the index with commit -a, giving you JJ behaviour and you can commit just fine instead of using the stash. The stash is like some backlog of temporary commits and also supports saving the index separately. When you don't care about this, you don't need to use it, these features are purely additive.
So this is as an opinion I had when I heard about jj too. But the thing is, jj supports this workflow very well, and in fact better than git, because the stash/index is just a normal commit, not a separate feature.
In fact, the most popular jj workflow is closer to the git add -p workflow than it is git commit -a. I’d argue more jj developers work this way than git developers do in git, even.
Not exactly, but sorta. You don't need to write a description for a commit, and it doesn't need to be on a branch, and things are auto-committed. So if I'm working on something, and then I "jj new" to start something else somewhere else, I can trivially come back to what I was doing. You're right that's a stash under another name: a commit. But the key is, because it's not a special separate thing from a commit, I can use any command that works on commits to work on my stash. We've unified two things into one thing. And that's useful.
So a list somewhere else, which commits are considered temporary. It's a todo list for commits. Does JJ have that?
> We've unified two things into one thing.
A stash in Git is also just two commits. So no you just removed a feature on top of that. That feature might not be to your taste, or even outright confusing, but you did remove it.
I am saying a feature was removed, yes. But what I'm saying is, the things you use that feature for can still be accomplished. It's just not accomplished via a dedicated feature, but as an effect of how other features work.
Yeah and I am saying that you could do that as well with Git. Git just also does have that feature and a lot of users like to use that, but you don't need to.
git and jj are functionally equivalent in this respect, yes, but it's all easier in jj. It all becomes one concept and one command set, instead of three different ones.
The git features of stashing/staging are removed, but they're superfluous given how jj works. You don't need or want them, and keeping them around would only give people two ways to do the same things.
for, while and goto achieve the same and are the same thing under the hood, yet they convey different semantic and you choose between them for different tasks, because they are more suited for different things.
stashes and commits achieve the same and are the same thing under the hood (both are commits), yet they convey different semantic and I choose between them for different tasks, because they are more suited for different things.
All in software is about different abstractions, that all fundamentally do the same thing.
> but it's all easier in jj
You can literally use commits instead of stashes in Git and guess what: it's the same as treating any other commit. Here JJ and Git are the same. You can also do stashes in Git, when you want temporary commits that are put into a "todo" list.
With the way jj works, all the functions of indexes and stashes are handled with commits.
There's no loss of speed, functionality or power, and as a bonus, instead of maintaining 3 separate concepts and commands, they're now handled by 1.
It's honestly much simpler. A git stash is effectively a commit already, but stored weirdly, doesn't show up in the logs by default, and with its own ad hoc commands.
I would love the git index to not be a thing you had to care about unless you deliberately wanted to use that feature, but in my experience this is not the case...it feels to me like an implementation detail that surfaces into the UI more than it ought to.
It’s not really skippable because you have to know about it in order to skip it. And a lot of commands interact with it, and so it adds complexity to those commands, git reset being the most obvious case. But even stuff like rebase. Lots of commands that need to abort on a dirty working tree just simply work in jj.
I find myself from time to time with stuff in the index (merge conflicts in particular I think can result in this). I never put anything there explicitly. I would love to be able to configure git such that the index didn't exist, but I can't.
The transparent thing would be to put everything into the index, and configure all the commands to autoupdate the index. This is what happens with the merge conflict, because there is no benefit having resolved merge conflicts not already in the index. So the merge conflicts already do what you want, the other commands need to be modified.
That isn't what I want, though. I don't see any point in the index as a construct -- I am happy with changes being either (a) not in any commit, just in the working directory or (b) in the most recent commit. Somewhere lurking in the middle is just confusing.
thank you for the detailed explanation. let us say you want to edit the contents of a commit on git 10 commits ago and this was not pushed to remote. you would do a rebase which basically discards and underneath basically creates a new track to follow. How does jj rebase work? I heard you can actually edit something 10 commits ago in jj and it treats it natively as an edit
I'm not sure what you mean by "natively as an edit", but jj really doesn't care if you alter some commit 10 back from the branch tip. Everything is automatically rebased as needed, and unless you introduce conflicts, it's seamless.
The typical workflow would be:
1. `jj log ...`
Locate the change ID of whatever change needs altering.
2. `jj new $SOME_CHANGE_ID`
This creates a new, empty change whose parent is the change you want to edit
3. Make your changes, pass the tests, etc.
4. `jj squash`
All your changes get squashed into $SOME_CHANGE_ID
5. jj then automatically rebases everything downstream of $SOME_CHANGE_ID for you.
All jj change IDs remain the same (though the underlying git commit SHAs are now different.)
If there are any new conflicts introduced in the downstream changes, they'll get flagged. You'd then typically run `jj new $FIRST_CONFLICT_ID`, and repeat the whole process.
---
It's also possible to run `jj edit $SOME_CHANGE_ID`, to work directly on that change, but because of the automatic rebasing, it's preferable to work on a child commit and only squash when ready. (In case you need to do other things with downstream changes before you've finished making your updates.)
Until recently, change ids were entirely local (in the git backend). So conflicts just can’t happen.
Recently jj started including the change id in a header in the git object, so the receiving side can choose to respect/expose them as appropriate. An issue with this is that some git commands don’t guarantee the preservation of headers, so it’s possible to accidentally lose them; git rebase being a prime example.
Generally, when a new change ID is needed, jj generates a random number. The individual commits under each change still use hashed SHAs.
> when I can modify the whole commit
You can't actually modify any commits. A mutable change can be thought of as a subset of immutable git commits with a pointer to the most recent commit.
When you alter a file and jj snapshots, it adds immutable commits to the DAG end, and updates the pointer.
---
There's some exceptions, like when importing from git, it will generate new change IDs based on a transform of the git SHAs.
---
Side note: as much as I like jj, I admit the change/commit terminology is confusing as hell.
That's exciting! Have you guys looked into sappling from Meta as well? It looked really cool the last time I looked into it but never tried to use it much.
I'll be honest, I did too. No https cert makes it give a warning in Chrome, which I had expected to just be a 404, and was surprised when it actually worked!
Does JJ have equivalet functionality to git's format-patch and am? Because I, for one, am rather fond of the e-mail based workflow and not having that would make JJ a no-go for me.
There hasn’t been a ton of desire for it because it’s easy to just run git format-patch. Jj’s workflows feel closer to the email workflow than not, but prioritization in a young project is tough.
> There are no substantial technical or usability reasons to switch to JJ from Git [...]. This is a neutral impersonal opinion that is virtually a fact.
> it's impractical for most working programmers to switch
I don't know what you mean here. I think it's quite practical to switch to jj, and people switch over all the time. Some people are going to be earlier adopters than others, but the early adopters can bring others along (as I've been lucky to do at Oxide).
> focus on something whose main appeal is more than social
Steve and I both think that Jujutsu's appeal is far more than merely some bandwagon effect. I do think that social appeal is an important part of making a project succeed, and that projects without substantial technical merit win due to social appeal all the time, but Jujutsu has both! It's amazing!
Perhaps take a look at jj and give it a go. Maybe you’ll like it.
I know I did. The fact I can use it with git and it doesn’t interfere with any GitHub PR workflows means no-one needs to know I use it. I enjoy the jj model. Maybe you could too, and it’s not a crazy investment of time and energy. It’s an evening playing around with a code kata or something.
Then working with vcs becomes that little bit more enjoyable.
> There are no substantial technical or usability reasons to switch to JJ from Git and it's impractical for most working programmers to switch. This is a neutral impersonal opinion that is virtually a fact.
Not a thing in here is true, especially not objectively true. As neutral as you may believe yourself, it might be a good time to step back and reexamine your priors that led you to state so confidently that there’s no usability reason to switch in particular.
> it might be a good time to step back and reexamine your priors that led you to state so confidently that there’s no usability reason to switch in particular.
The key word I used is "substantial." The usability improvements over Git are marginal and if they ever become non-marginal, they can relatively easily be added to git. This is what my comment is getting at. The only essential difference between Git and JJ is that they are different fiefdoms. There is no substantial technological difference. It's just two different social factions with marginally different opinions about how to type CLI commands.
Changesets, the op log, first class conflicts, no staging area.
JJ might produce commits that can be stored in Git, but the affordances are different. If Git wants to adopt them, it becomes no longer Git.
On the other hand, I'm happily using JJ while everyone I collaborate with is using Git. JJ doesn't need to "win" to be useful, it just needs to be useful enough that the people who maintain it continue to maintain it.
The substantial technical difference is that the UI (the "porcelain") of JJ is not backwards-compatible with that of Git, even though it can use git's "plumbing". `jj commit` works differently than `git commit`, and Git can't match that without a similar compatibility break. JJ uses Git's "content-addressed DAG of snapshots" data model in a particularly elegant way, and deletes many redundant commands to achieve that elegance. Git could absolutely delete half of its interface & change much of the rest to get that, but then it'd break lots of scripts & third-party tools. JJ namespaces the UI redesign, which allows breaking that compatibility without breaking everyone's workflow at once. People can switch (or not switch) independently, where a Git UI redesign would have to be adopted by everyone together.
It's trivial that JJ can't do anything `git` can't do, because JJ uses Git as a backend, so everything it does it does via `git`. But Git the project is extremely unlikely to alter `git` the tool to provide JJ's interface, and even if they did via namespacing it (e.g. `git jj commit`, `git jj git init --colocate`, etc.) that'd just be adding even more commands on top of the existing interface with too many commands.
> There are no substantial technical or usability reasons to switch to JJ from Git and it's impractical for most working programmers to switch.
Not in my experience. jj is easy to pick up and a joy to use. I like git. I deeply appreciate git. But git can feel like snow shoveling sometimes, while in jj things just click into place.
jj is freeing, because things that were a hassle before are now easy, and other things that were impossible are also easy.
I know there's a lot of hype around for a lot of things, and I get grumpy from all of it. Jujutsu is one of the few things that actually deserve the praise being heaped on them.
You may disagree, of course. But I hope that some day, you'll have reason to be happy about this tool, instead of feeling... whatever touched you off like this.
This comment could have been written when people were pushing to SVN repos on SourceForge when Git was becoming the next big thing, enough for a competitor to founded.
Yet still, the status quo is not Subversion and SourceForge, both of which have been relegated to antiquity, but git and GitHub.
Will something else unseat git and GitHub in the coming years? And will it be Jujutsu or some other innovation on version control?
Who knows, but I see no need to be so dismissive of somebody’s passion in such an arrogant way.
These are my thoughts exactly. Focus on building the best thing instead of telling everyone about the dreams you want to reach. Speak softly and carry a big stick as it were.
I was thinking, it'll probably catch on like Omarchy did, except it's written in Rust, so... little chance of it being milkshake-ducked.
But as we say on Hackernews, there are two kinds of people with respect to jj: those who love it, and those who haven't tried it.
Doing the same thing, but with better usability, is immensely valuable. Remember, Hackernews thought Dropbox was "just" ssh+rsync, but they made a billion dollars making that easy.
I’ve tried it. I don’t love it or hate it. It’s more of a “meh”. I can see how it’d be nice if I was disciplined about my commit history, but I tend to be fine with chunky, clunky git. I don’t spend as much time curating perfect commit history as jj fans seem to.
> There are no substantial technical or usability reasons to switch to JJ from Git
Every magit user disagrees with you.
> and it's impractical for most working programmers to switch
This very much sounds like someone who has never even tried jj. Tell us: What makes switching impractical?
> My well-intentioned recommendation to Steve, because I've been there, is: If you want to avoid feeling like you've wasted your life in a few years on code that doesn't live up to its promise and is essentially no different than the code that already exists in Git (and Hg, Pijul, Fossil, etc.),
This is one person's career move that is defended by the idea that they somehow always make good bets. This is more than a bit arrogant. Past performance is no guarantee of future results.
I don't see a future for this. I see a series of blog posts that culminate in a tone deaf "What went wrong" finale.
I gave jj two honest tries. While first class conflicts is a cool idea, in practice I deal with staging/committing 30x more than conflict resolution, and coming from magit, using jj’s hunk split & select felt like being thrown into stone age. Plus I rebase a lot and get a lot of jj’s benefits from magit’s various rebase shortcuts already, IIRC first class conflicts was the only truly novel thing I didn’t have. For people like me who stage/commit often and judiciously I don’t think jj will beat magit until its hunk selection UX comes close.
In jj you aren’t supposed to be even thinking about staging and committing, that’s the mental leap required to get what the fuss is about. Everything is a change and you bookmark a parent (or something further out) as the branch head into which you squash or advance the bookmark to a next ready change.
> For people like me who stage/commit often and judiciously
Focus on judiciously: mostly (functionally) atomic commits that are not every tiny change, not largely meaningless time-based snapshots, not Gerrit-style single commits for entire features, etc. I’m well aware of the mental leap you’re talking about, it’s neither hard to understand nor the liberation you might think it is. To achieve what we want to achieve we either need to be able to split, or “commit” with such atomicity that more time is wasted come squash time.* If you don’t get that, totally fine, but then you just don’t belong very much to this very conversation.
* Or we can completely change how we write code…
I see your point, but this feels like a difference in workflow philosophy, especially with AI-assisted coding becoming more common.
With jj, your working copy is always a commit. This encourages a "commit often and messy, clean up later" approach. You can rapidly iterate with or without AI, letting jj automatically save every change without stopping to craft perfect atomic commits.
Later, you use a simple jj squash to combine all those small, iterative changes into logical, clean commits before you share it. The atomicity is created retroactively, not upfront. For a finely-tuned magit workflow this might feel wrong, but for rapid, exploratory and AI-driven iteration, it's a more natural fit.
With AI there’s even more reason to have ergonomic splitting if you care about quality commits, since unless you always write the perfect focused prompt, AI can over-generate for the scope of a single commit, so sooner or later you need to split that commit during the cleanup process you mentioned.
I’m using jj for almost a year now and I definitely feel much happier whenever I need to rebase a branch or do an octopus merge. It really is liberating in an incremental way that creeps on you in day to day work. Don’t expect a revolution, though.
Small commits are the same small commits in jj as in git, you just split instead of add -p.
I mean, if you think git add -p is fine then you’re clearly not in the market of magit. And yes, jj split is about as ergonomic as git add -p, IIRC.
What does the magit interface for staging look like? I only used GUIs were you select the lines with the cursor or mouse and stage that. Is it like that or more sophisticated? Does it support editing hunks better, because that's a thing I only do on the commandline, because my Git GUI doesn't support that.
In magit, the diff is splitted into sections: Untracked, Unstaged and Stage. The sublevels are files then hunks. You can stage and unstage hunks, files and whole section by pressing s and u. Pressing k discards the node (section, file, or hunk.
You can also stage or unstage lines by selecting them first and press the relevan keys.
If you press ‘return’ on a hunk, it brings you directly the the line to edit.
jj wouldn't have any issues with supporting this workflow, if you could replace magit's git commands with some jj ones it'd probably just work. IOW the only thing that's missing is elbow grease.
actually, with jj's seamless rebases, it'd work exactly as you'd want it to (but maybe not how you'd expect if you're used to the git way) when aimed at a commit in the middle of a branch: pick lines which stay below, above or in the middle commit, which you can edit transparently.
The thing is that magit have a lot of commands that makes rebasing a breeze. Pressing c, then s, will allow you to select interactively (from the commits log) a commit to target when doing autosquash. That will create a new commit with the relevant message. Pressing c, the S (shift+s), will rebase immediately, squashing the current staging area into the selected commit.
Interactive rebasing is a breeze. Editing commits is very fast (reordering, dropping rewording, splitting,…). Same with dealing with merge conflicts (Emacs have various merge packages builtin).
The same speed Vim brings to editing, that’s nearly the same speed you get with magit for git.
It sounds like it’d would work even better with jj is all I’m saying. A rebase UI doesn’t mean a git rebase UI.
I don't mean that, I mean any tool that allows you to selectively stage can use the exact same interface to selectively split.
> Everything is a change and you bookmark a parent (or something further out) as the branch head into which you squash or advance the bookmark to a next ready change.
Does not sound any easier or more intuitive than git.
The only intuitive user interface is the nipple.
It isn’t how git porcelain wants you to work, but it does make sense once you stop thinking in git. Working copy being a first class commit unlocks all the dag manipulation tools without stashing, having to resolve conflicts in middle of tricky operations, etc. ‘Easier’ is exactly the point, but it’s about workflows which are very inconvenient in git like rebasing stacked PRs.
I’m curious what the magit hunk selection UX consists in. I couldn’t find any videos showing something substantially different from the one built into jj — the videos I found were meant as beginner intros.
I’ve never used magit but I used GitUp (https://gitup.co/) for years before jj. I don’t find the jj one super natural, but I feel like that’s mostly a matter of keyboard shortcuts — I need to see if they can be customized.
There's more than one way to do it, but the very normal UX is that you can just scroll through the diff file-by-file and stage/stash/drop each hunk individually by placing your cursor over it and issuing the appropriate command. You can do the same with files, staging/stashing/dropping changes to a file by placing the cursor on its name and issuing a command.
Crucially you can also select a region within a chunk and perform those commands, so it’s easy to untangle changes.
And you can even edit the content you stage, so that you can stage something different, than what is in the working tree. Having different content in the index vs. working tree is the feature of the index, which I think JJ just doesn't support?
jj doesn’t support it in the sense that there’s no special index feature. You can use a workflow where you have a commit represent the index (and that’s basically the most common workflow). This means you don’t need a separate feature, you just use the tools that slice and dice any commit with your “index”.
It isn’t that it doesn’t support it, it kinda does, but the recommended workflow is such that the index is simply pointless.
As an stgit user, this seems like a weird workflow to me. I never want to have that many uncommitted changes just floating around that will eventually belong to multiple commits. If I'm halfway through something and realise "oh, it would be good to do xyz first" I don't want to have xyz's changes and my half-way-through changes all mixed up -- I want to pop the half-way-through stuff, do the xyz stuff and commit it, then re-push the half-way-through stuff to keep working. If I'm looking at a diff and picking out parts of it then I've done something wrong -- I have a tool for doing it but I only need to use it every couple of months...
I end up with "neatly separated does-one-thing commits" but I get there by building them up as I go along, not by writing a ton of code and then trying to split it up afterwards.
This sort of flow is very nice in jj, primarily because of the “no index” plus “auto commit” behavior, I’ll regularly go “oh yeah I want to go do that” and I’m about to just go do it and then come back to right where I left off, since my work is already saved.
Yeah, I get the impression jj is good for this, and if I were using raw git then it would be a massive upgrade. Luckily for me stgit already does what I want in this area so I have no strong need to investigate alternatives, but if stgit ever bitrots then jj might be a useful next thing.
StGit maintainer here. I have been a jj user for over a year now. It has proven superior to StGit for all of my workloads. I even use jj when maintaining StGit.
An incomplete list of wins vs StGit includes:
- jj makes managing multiple branches fluid, whereas stg has limited tools for managing patches between stacks. 'stg pick' is largely all there is. It's a real dance to move a patch between stacks.
- jj has a much better system for naming changes. I'm super jealous of how jj solved this problem. StGit requires you to name the patches. I added the feature that allows StGit to refer to patches by relative or absolute index in addition to by name. jj's immutable change ids that can be referenced by unambiguous prefix is the correct answer to this problem.
- 'jj rebase' is so vastly superior to stg push/pop/sink/float for reordering changes that I don't even know where to start. It wasn't immediately obvious to me just how flexible, simple, and powerful 'jj rebase' is when I first started using jj, I have learned that it is in its own league relative to StGit's stack ordering story.
- Similarly 'jj squash' makes 'stg squash' look amateurish.
I could go on. If you're a StGit user, you owe it to yourself to give jj a proper try.
On that first point, there's a use case I sometimes have where stgit feels very clunky:
* I have a branch foo with a stack of patches which are the thing I'm working on, based on a master branch
* I have a separate stack of patches on a branch bar (let's say this is a feature that interacts with foo but it's not ready to upstream yet or maybe at all)
* I want to test and work on the combination of foo and bar and make changes that might need to be updates to some patch in foo, or to some patch in bar
At the moment I pick all the patches in foo onto bar in order to do the work and updates in this combined branch, squashing fixes and changes into appropriate patches. Then once I'm happy I go back to the foo branch, blow away the old patches and re-pick them off my combined branch.
This works but feels super clunky -- does jj do better here? That would be a real incentive to try it out.
For the rest, they don't sound like they're strong enough to beat "I've used stgit for 10 years and have a solid workflow with it".
And I just scanned the jj rebase docs and it looks awful, everything I moved to stgit to get away from. I do not want to think about managing a patch stack in terms of "move a bunch of revisions to a different parent". I like having a straightforward stack of patches that I can push, pop and reorder and list. Don't make me think about graphs of commits and multiple rebase suboptions and all that for something that I do all the time in my main workflow, please.
jj does do better here: its called the "mega merge" https://steveklabnik.github.io/jujutsu-tutorial/advanced/sim...
Combined with jj absorb, some people just work this way all the time, even.
> I like having a straightforward stack of patches that I can push, pop and reorder and list.
You can work this way too, what you'd want is `jj rebase` with -A, -B, and -r: r for the revision, and A and B for the before and after you want to move it too. This lets you reorder things however you want pretty straightforwardly. I tend to work in a stack of patches too, or at least, way more than I used to.
What I mean is that I do not want a single "swiss army knife" rebase command that does everything with lots of options to remember. It's fine to have that in the toolbox for the once in six months weird requirement. But for the simple cases I do every day I want simple commands that each do one thing and have memorable names.
If I'm understanding you correctly, you can have both. If there are specific rebase types that you perform regularly, you can create aliases for them and give them whatever name is meaningful to you.
For example, I frequently use `jj up` to rebase the current branch on main. Likewise, `jj pop` rebases just the current commit (popping it from its current place). I even have a `jj ppop` - better name suggestions are welcome - which does this but for the parent commit.
I suspect that the once-off effort to write your own commands would take no longer than it would take to read the documentation if the commands already existed, but with the hopeful extra benefit of giving you a better understanding of how to use rebase for those once in six months weird requirements when they may arise.
But to be clear, I'm not suggesting you must or even should put in this effort if you have something that works for you. My reply is mostly so that anyone who comes across this discussion and sees Steve's mention of -A, -B, etc isn't scared off by them. Whilst they're always there for you, you can use the power it gives you but in the form of single function commands that don't require you to think.
---
For anyone wondering, the aliases I mentioned. These can be dropped in your jj config with `jj config edit --user`.
Most of the time in jj you don’t even rebase manually, because it’s automatic. And the vast majority of the time, I’m using one or two flags to rebase if I am calling it. You might even need only two in this case (before only might be fine?) I just use both before and after because it’s so easy to remember the names.
Anyway you should use the tools you like, it’s all good.
Seriously! I found that you can still use the git index as long as you don’t run any jj command that changes git_head, and then I made an alias to make a commit from staged changes (squash-index and split-index): https://github.com/CGamesPlay/dotfiles/blob/2484f6f7d0ab302e...
> Plus I rebase a lot
Exactly, me too. Things like `absorb` I'll take, but I don't want jj's opinionated approach to version control. And not only do I not want it for _me_, but I also don't want it for newbies because hiding too much of the underlying design, design issues, etc., seems counterproductive to me.
This does not compute, like at all. jj rebase is half the reason to use it at all. Everything git does with the commit dag, jj does the same at worst, sometimes better.
It actually sounds like you're confusing jj with something else.
I’m curious to hear more about what you find opinionated, I think one struggle I’ve had teaching jj to people is that it’s extremely flexible.
I'll have to look again. Last I looked at it I felt like jj was a straightjacket.
It's all good. If you do give it a try and feel like letting me know how it goes, I'm genuinely interested. No worries if not :)
If you understand why having a great UX on top of bare git is valuable, you have understood 95% of what's to understand about jj.
But there are dozens of great UXs on top of Git already, so if that's 95% of what jj is about then you seem to be making an argument for jj adding very little value. My understanding is that jj offers more than just UX enhancements.
Interesting, isn't it? There you are, and yet none of those dozens are garnering anywhere near the same amount of attention and enthusiasm as jj. And yet, it's genuinely mostly just that -- a novel CLI on top of the same storage backend as git. Sometimes it doesn't take that much to bring about a paradigm shift, I suppose. Just a few new ideas with a great implementation, and there you are -- they've just got to be the right new ideas. That being the hard part.
If you open your favorite git editor in a jj repo, everything you stage will become a new commit, everything you revert will… revert. I still use Sublime Merge rather frequently.
Oh wow, they were going for "magic" with that name?? I read it as "maggot".
I have Luddite feelings reading about alternatives to Git.
As an industry we have soooo many languages, frameworks, tools, distros etc. It's like we are pre metric system or standardization on screw thread sizing.
I am really happy that at least for VCS, we have a nearly universal solution in Git, except for the big tech folks.
Sure, jj might solve some issues, but once it gets serious traction, all the tooling that works with e.g. repo analysis will need to start supporting git and jj. More docs need to be created, junior developers will need to learn both systems (as git is not going anywhere).
Given all the downstream effects, I do not think introducing another VCS is a net positive.
In my case, the shoddiness and thoughtlessness of Git's user interface pisses me off so much that I just want it to be replaced. A good tool similar to Git may even explain Git's concepts better than Git or its documentation that likes to discuss "some tree-ish refs".
I do see that, and git is not perfect, not by a long shot. I'd much rather see an evolution of git itself rather than getting more alternatives.
That essentially is what jj is though, given it can be used on git repositories transparently.
I started using git when my employer still used svn. This was possible because `git-svn` was so good and it seamlessly allowed me to use branches to do development while still committing to svn's trunk. I think jj is trying to do something similar: jj-unaware tools work reasonably well in colocated jj repositories, and jj-unaware peers (CI tools, etc) work exactly as they did before.
I do agree that you can't really use jj without also knowing "a fair amount" about git, but notably you never need to use the git cli to be an effective contributor to a github project, which is basically the same as `git-svn` was back before git got popular.
I see what you mean, and having looked a bit more deeply today, it's clear that jj is very compatible.
However, my point is that having two ways of doing this within a team is already confusing. What if one person writes a "this is how we work" document on the wiki and mentions some git, and the next person rewrites it to jj? It's extra things to think about. It's like supporting developers from a team with both Windows and Linux (Debian, Arch, Ubuntu, etc). Teams do it, and it's all possible, but it was nice that at least everyone used git.
If a team has members who unilaterally rewrite best practice docs to include their preferred technologies, the fault is not with the tech.
I didn't pick a great example and didn't explain it eloquently, but I hope you understand my point. Supporting multiple ways of working takes effort.
We had rcs, cvs, svn, and now git. Why would it be the ultimate VCS and not be replaced again by something better?
If a product is 10x better than what's currently available, it will see rapid adoption. There was obviously something about git that made it MUCH better than the precursors and that's why it obliterated everything else.
I highly doubt that new tools will be 10x better than git. Maybe 20%?
One way I compare the git to jj transition (if it happens, or for whom it happens) to the svn to git transition is: branching in svn was awful. It was heavyweight and you were signing up for pain later down the road. Git made branching easy and normal, almost something you barely need to think about. jj does a similar thing for rebasing. For someone whose familiarity with git is clone, pull, push, merge, creating branches (so, basic/working/practical familiarity but even "rebase -i" might be pushing the limits)- for someone like that what jj offers is a similar "lift" of a feature (rebase) from "scary" to "normal" similar to what git did for branching compared to svn.
That's just one aspect of the whole thing, and of course if you're a git rebase wizard (or have tools that make you that) then this won't seem relevant. But I think for a lot of people this might be a salient point.
You should really try it; it's clear you're learning some things about it just from this thread.
Will a product that is 10x better see rapid adoption if people who have not used it still choose to criticize it in the abstract?
I'm happy to give it a try when I have some time, but I have 0 problems with git right now so it's not top of my list. My critique is also really not towards jj specifically, I'm just discussing the idea that git has extremely wide adoption now and that this has benefits :)
Preforce, ClearCase, SourceSafe, TFS, Mercurial....
Perforce
Git absolutely is a productivity drain and should be replaced, particularly as agentic coding takes over, as its footgun elements get magnified when you have a lot of agents working on one codebase at once. I dislike jj as move forward because I don't think it goes far enough for the amount of friction moving to it as an industry would entail.
The next generation of VCS should be atomic, with a proper database tracking atoms, and "plans" to construct repo states from atoms. A VCS built around these principles would eliminate branching issues (no branches, just atoms + plans), you could construct relationships from plan edit distances and timestamps without forcing developers to screw with a graph. This would also allow macros to run on plans and transform atoms, enable cleaner "diffs" and make it easy to swap in and out functionality from the atom database instead of having to hunt through the commit graph and create a patch.
The downside of an atomic design like this is you have to parse everything that goes into VCS to get benefits, but you can fallback to line based parsing for text files, and you can store pointers to blobs that aren't parseable. I think the tradeoff in terms of DX and features is worth it but getting people off git is going to be an epic lift.
> Git absolutely is a productivity drain and should be replaced, particularly as agentic coding takes over,
Is this an oblique way of saying that Git should not be replaced?
No, but the lift of replacing git is huge, so we shouldn't do it for a Python2->Python3 sitaution, we should have a replacement that really brings big wins.
I have no idea what problem this is supposed to solve. Where is the V in VCS here? How do you track the provenance/history of changes?
You may not be "forcing" developers to "screw with a graph" (what?) but you are forcing them to screw with macros (we're adding a built-in scripting layer to the VCS?) and these unfamiliar new concepts called atoms and plans.
> A VCS built around these principles would eliminate branching issues (no branches, just atoms + plans)
And it would introduce zero new confusing issues of its own?
> This would also [...] make it easy to swap in and out functionality from the atom database instead of having to hunt through the commit graph and create a patch.
This is a weird use case. Version control systems aren't typically used for storing and swapping around bits of functionality as a first-class, ongoing concern.
Not to mention you still need to figure out how atoms get stitched together. How do you do it without diff-based patches? No superior solution exists, AFAIK.
If you have a database of atoms and plans, the V is a row in a plan table, and you reconstruct history using plan edit distance, which is more robust than manually assigned provenance anyhow (it will retain some history for cherry picked changes, for instance).
I'm sure there would be new issues, but I think they'd be at the management/ops level rather than the individual dev level, which is a win since you can concentrate specialization and let your average devs have better DX.
Is it a weird use case? Imagine you refactor some code, but then you realize that a function was being called in a slightly incorrect way after a change (prior to the refactor so the revert isn't trivial) and you have to go back and revert that change, let's say over 100 files to be fun, and let's say that the code isn't perfectly identical. With git you probably have to do surgery to create a patch, with an atomic system you can easily macro this change, or you could even expose a UI to browse different revisions of a piece of code cleanly (which would blow up with git).
if I make a plan which causes the project to be identical to its state 5 years ago, the edit distance is zero, but in no way can you call that a measure of history
You're still thinking in graphs. That plan would already exist in the database, you would just be making it a build target instead of whatever new plan was targeted before.
It seems as though you've come up with a model for representing source code repos in terms of a data model of your own design, solving problems of your own choosing. But what you describe is not a version control system in the generally agreed upon sense of the word.
It's important that jj (the local VCS) is fully compatible with Git (the protocol / interoperability standard).
I think this is JJ's biggest advantage. Whether you use it is independent of whether anybody on your team uses it.
Having used jj for the last year and a half, I hear you on the tooling issues. From IDEs, to plugins, to docs, to workflows, to LLMs, jj is not as well supported as git.
Despite that, it's still a net time-saver for me, and I suspect the same will be true for others. Git imposes constant overhead, mostly with its poor UI, but also by some of its unnecessary models (e.g., staging as a separate concept).
As far as I can tell, jj intends to be more like a cross-VCS frontend (with their own native backend coming at some point). If tooling supports jj, it would automatically support git, jj's native backend, Google's hybrid backend and any other backend a user could add.
https://xkcd.com/927/ (and now we have 15 standards)
I think the real news is that some people have started to build what might become something like a "jjhub".
https://ersc.io/
I think 'jjhub' is a decent immediate first approximation, and is often how I've started out talking about it with people. At the same time, we have to offer real value here, as you can already use jj with github (and I have for a long time now), so there's more to it than that. But yes :)
If you're starting over with a new backing store using jj's pluggable backends, can you give us a native lazily-materialized store for megarepos? It's a tragedy that the open-source world has no answer to piper.
What I would love:
- Everything locally stored in the repo: PRs, comments, issues, discussions, boards, ... - CLI first - Offline first (+ syncing) - A website for hosting/presentation
Noted :) In another comment I linked to beads, which is a cool project to keep your issue tracker in your repo, but that's just a personal thing, no comment on what the company plans to do (or not) in this area.
I use command-line tooling much more than IDEs (e.g. VS Code), so the `gh` command-line tool (https://cli.github.com) for doing most of the usual hub-oriented workflow (PR authoring, viewing issues, status updates, etc) really helps a lot - I don't have to constantly <cmd>+<tab> to my browser, and point-click-point-click through web pages so much. It would be fantastic if ersc or any other jj-centered code-sharing hub had similar tooling early on.
I'm a big CLI for VCS person, so yeah, I use those tools too :)
So you want Fossil?
When I tried Fossil it had things weirdly separated.
I was expecting when I make a commit, I would have the facility to specify what issues it addressed and it would close them for me automatically. It seemed there is so much opportunity there to "close the loop" when the issue tracker, etc and integrated in your VCS, but it wasn't taken.
except fossil decided to never allow changing history, vs jj which makes history rewriting so much easier
That's my favourite thing about fossil though. History is what it is, not simplified to look "clean" (i.e. hide what actually happened and when) and you get a lot fewer footguns to ruin everything by accidentally rebasing things to the wrong place without noticing.
I would love having some first class support for monorepos at bigger organizations (ex: silos, vfs, etc).
No promises but noted for sure…
> we have to offer real value here
If I may make a suggestion here: Allowing PRs to be stacked, i.e. allowing commits to be reviewed individually, like in Sapling, would be FANTASTIC.
EDIT: See also the link in this comment: https://news.ycombinator.com/item?id=45675335
This is something that I am comfortable saying is absolutely going to be the case.
There’s this as well: https://blog.tangled.org/stacking
I personally find this to be more interesting simply because it has the social inertia of Bluesky behind it. Software has centralized on GitHub because it is discoverable there. Software on Tangled is discoverable through Bluesky, and the stacking support to make jj nice to use with it is just an added bonus.
Gerrit also maps really nicely to jj concepts, and there is an accepted RfC to add support for Jujutsu change IDs to Gerrit.
jjhub sounds rather frabjous
This is a great thing and I'm hoping for its success. It's about time we have a real alternative to Github.
What makes GitLab not a real alternative?
Another reason to leave GitHub and their weekly downtime!
Can't wait for them to have their BigCo exit and get rich like the GitHub guys /s
> I also don’t mean to imply that everyone at Google is using jj, but the contingent feels significant to me, given how hard it is to introduce a new VCS inside a company of that size.
I don't mean to imply that Google is fickle, but anything besides Google's perforce fork is deprecated every few years. We used to have a proper git wrapper, then mercurial+extensions, now jj is supposed to replace the mercurial thing, all in 7-ish years?
In fairness that's not much different from any other internal Google tool. Those don't often have a two-digit years shelf life.
I'll venture that jj is there to stay, however. If not at Google, then in general. It's just too much of a quantum leap. I think I've finally identified what about it sits so right with me: a change's identity is preserved through its revisions. In bare git, after a rebase or an amend, you get a wholly different commit that just happens to have a similar content.
Mind you, I'll also venture that jj will remain based on git as its storage backend, despite its stated goal otherwise. Git's internals are just too good at what they do to make it worthwhile to replace them.
I hope it sticks around so that I can use the same workflow at work and home. I'm really enjoying how fast all the jj operations are compared to mercurial.
I don't think this is quite right.
The git wrapper was never fully supported and had some rough edges (I think it was only ever a 20% project, and also its, like, really old). And the customized mercurial has been around for more than 7 years, I think close to a decade (the client I'm using right now is turning 7, and it wasn't my first one).
I used git5 from when I started in 2011 to when I left in 2017.
I'm going back starting on monday, so I'm curious to try out jj.
In the past 10 years it's all been github and gitlab, and their code review tools are so painful, specifically w.r.t. tracking discussions across revisions. I never felt excited to try out jj because I was afraid it would that situation even worse.
It was deprecated in 2015 iirc
I started using git as my daily driver at Google in 2018. I was forcibly kicked off it for mercurial in 2020. The deprecation plan for mercurial is 2026 IIUC. Maybe if you adopted mercurial early it's different.
What was the reason for using Mercurial?
Why did Google decide to choose Mercurial? Based on what I read the main reason was that the mercurial dev team was willing to prioritize features needed for Google to add custom extensions to support its monorepo, and the git dev team wasn't going to reprioritize just for the sake of Google.
Yes, that's correct. Another reason was that Mercurial is easier to customize because it's written in Python so we could sometimes just replace whatever we needed without needing much changes from Mercurial itself.
Yet another reason is that the .git directory is considered a documented API and several other tools and libraries depend on it (e.g. JGit and libgit2). So any new features for Google would need to be made to those tools too if we wanted things built on them to work.
We also consider Mercurial to have better UX.
I think you’re confusing Google with Facebook.
j2kun seems to be a googler so I don't think so, but it's true that Facebook also went with Mercurial. I suspect it was for similar reasons.
Ahh. It sounded like a repeat of https://graphite.dev/blog/why-facebook-doesnt-use-git but maybe it did happen for both companies! They’d obviously be having similar issues. Thanks :)
I can second that the aforementioned reasons are true. The funny difference is that Google employs the primary git maintainer. Git has a lot of customers though so it rightfully is very conservative with development.
The intention with this is to also deprecate the Perforce fork, in my understanding.
But yeah, it's been a lot. Frankly even harder to keep track of from the outside!
I tried jj, but I'm used to Sublime Merge. Doing version control in the command line was just too much repeated typing, as you keep losing information. In a GUI you always see the current state. You commit, it updates the display. A diff is a click away. Changing focus to type the commit message is a click away. No reason to ask the same questions again and again. Even with aliases like jjl for `jj log --limit 10` it felt too inefficient and annoying. Selecting individual hunks with the keyboard... I don't ever want to do that again. In SM it's a pleasure.
The git CLI might suck, but SM just doesn't. Looking forward to jj GUIs taking off. Or even better: jj getting integrated into SM.
I tried Jujutsu in the last day and was going through your tutorial. I really liked the experience and can see some potential. I also got the feeling that there is a missing puzzle piece. For example, do I get any benefit from the change id if I push to GitHub for PR review?
I guess you benefit from some of the good parts only with the Google internal Piper backend, at the moment. So I’m curious about the ideas and plans you have at ERSC.
But what I’m also really yearning for is having a distributed asynchronous/offline-first code review flow built right in. The distributed nature of git somehow got lost with PRs or MRs in GitHub & Co.
Glad to hear you enjoyed it :)
> do I get any benefit from the change id if I push to GitHub for PR review?
As of right now? Not really. The details are more complicated, but basically, if your project doesn't like the behavior of github when it comes to comments + editing commits, and wants you to tack on new commits instead, we can't change that behavior. However, https://github.com/LucioFranco/jj-spr can, in some situations, give you some of this experience. If your project is okay with editing commits, then it can help you locally, sure.
However. In an interesting turn of developments, GitHub's new SVP just tweeted that he's likes jj and is interested in adding stacked diffs to github. I don't know how this squares with their "no new features for 18 months" thing, but we'll see!
> But what I’m also really yearning for is having a distributed asynchronous/offline-first code review flow built right in.
It's not code review, it's issue tracking, but I've been using https://github.com/steveyegge/beads this week, and I think it might be the first "put your issues in your repo" system I actually enjoy. It says it's built for AI stuff, but like, you don't have to use AI with it.
One thing to note is that the actual commit objects you make through jj do include a `change-id` header which means that even though Github is unaware of jj, peers using jj will still be able to keep track of the rebases.
Demo: run `git cat-file -p HEAD` in any jj repository that you've made a change to.
Does it work with large binary files and not choke like git? Cause git may have won for webdev etc, but in some industries such as gamedev, Perforce is the king... git is barely used at all because it can't handle binary files worth a damn(yes I know about the large file extension, no it isn't sufficient).
Perforce’s binary support is basically equivalent to Git LFS, it does the same thing.
What does Perforce binary support have that Git LFS doesn’t?
AFAIK, the base issue that Perforce is already in use and it has enterprise support.
I am the last person to ever promote perforce, but as of last yearish it has the option for binary delta transfer using fastCDC.
Even without that, it is a just straight up a lot faster than git lfs. I know this because I benchmark it against git pretty frequently as I am creating my own large file capable VCS.
What do you mean by this? It's hardly equivalent to LFS. The binary files aren't replaced with a text pointer with actual content stored on a server elsewhere. Binary files are stored in the same place as text files.
From the user's perspective, when setup correctly Git LFS is transparent and they don't see the text pointers - the binary files are replaced on push and pull to the server.
It's the same user experience as Perforce?
Yes, Git is more low-level and it's possible to see those text pointers if you want to.
This is what you want to believe but its not true.
I’m really sorry, git lfs is an ugly hack, and its always painful when you discover that some gamedev team has been forced into it by “better knowing” software developers.
It reminds me a lot of “features” of software that is clearly a box ticking exercise, like technically MS Teams has a whiteboard feature. Yet it lacks any depth: its not persistent so its gone after the call, and it’s clunky to use and to save.
… but technically the feature exists, so it’s harder to argue for better software thats fit for purpose, like miro or mural.
Not a belief, but my experience. Maybe I've had a blessed experience with LFS? It's always "just worked" for me.
But I’d make a guess that the majority of the files you’re working on are text based.
If the primary filetype you use is binary, you’ll start to feel the jank.
1. Perforce checkout requests always do the round trip to the server.
2. Artists can actually understand Perforce.
> Perforce checkout requests always do the round trip to the server
That's literally the antithesis of Git. If that's a requirement, then yeah - Git's the wrong thing.
It's like complaining that bicycles don't have motors like motorcycles. If it had a motor, it wouldn't be a bicycle.
The question as I recall was what Perforce does that Git LFS doesn't, so I'm sorry to disappoint but my hands were tied.
Anyway, I dunno, man. If you want binary files to work, some form of per-file mutex is indeed a requirement. And for this to work well, without being a lot of hassle (and regarding that, see point 2, which I note has been accepted without comment - not that I expected anything else, the argument that Git is the artist-friendly choice would be a difficult one to make), any modification of the mutex's state has to involve a round trip to ensure the info is up to date. You can't rely on something local, that only gets updated sometimes, because then the info can be out of date! Worst case, N people find out too late that they've all been making changes simultaneously, and now N-1 of them will almost certainly lose work.
(You might be inclined to moan at people for not going through the full process, but: we have computers now! They can do the full process for us!)
It uses Git for storage and does not support LFS, so it's as bad as Git used to be for this specific usecase.
LFS sorta kinda works in my understanding, it’s not super nice yet though.
Right now, nothing special. But this is very much an area that's known about and something may happen in this area, we'll see.
If you could elegantly handle large or frequently-changing binaries I think I'd be willing to switch immediately.
It has always annoyed me that there's not any clever way to handle binary changes with version control.
I joined the sapling/subversion company this year, but haven’t had the chance to use jj. But given its resemblance I must say sapling has been great. Much more intuitive than git, and I find commit stacks much easier to follow than branches. I do wonder how it will work without the level of support of Meta, since you won’t have the same commit stack review UI (basically a series of pull requests being reviewed at the same time). So something like what this author is working on is needed.
Yeah, sapling and jj are fellow travelers, for sure :)
What I miss from the Perforce and Subversion days is committing directly to trunk in a team environment. Now everything revolves around PRs and lengthy code review. With direct commits to trunk, everyone was in a rush to get their commit in before someone else did, so they didn’t have to update and have conflicts. This made commits small, frequent, and usually well-scoped. What -sucked- was when you did need to do a large refactoring or other big change, then that was work best done on a weekend because branching and merging didn’t work very well.
Git’s model is right for PRs for open source projects where one day you could wind up with code from someone you don’t know and you need to take your time in review and possibly making further changes before merging. But as much as git’s a meaningful upgrade over Perforce and (especially) Subversion, branching and merging is not the right default model for normal team development.
PR's are a man made ritual that was never required by GIT. Just like we are all doing the Lean/Scrum or whatever dance. It does not change the development process, it's just a man made ritual for collaboration. The branching model is nicer to work with than the revision based model from subversion though.
You can totally use that workflow with git.
I know but tell that to GitHub/Gitlab/BitBucket/etc.
> then that was work best done on a weekend
I mean that's just toxic.
So was CVS!
After reading the article, the technical merits of `jj` are completely unclear.
Yes, that was not the goal of the post. There's some comments about this in this thread though, here's the first one of mine: https://news.ycombinator.com/item?id=45673808
That URL https://news.ycombinator.com/user?id=steveklabnik is a link to your user profile.
Whoops, managed to fix it. Thank you!
The article can be summarized to essentially: I like Rust. jj is written in Rust. Some Rust person loves jj. jj is used at Google.
Who upvotes this?
I can’t speak to people’s motivations, but I have been a very active member on this forum for a long time, and I was on the Rust team for about a decade, beginning before 1.0.
So the article is more like “Here’s why I started using Rust. If you thought I made a good choice then, well, I’m making a similar choice in case you want to join me.” Seems people find that compelling.
Just like Rust’s success was not inevitable, neither is jj’s. But I think it might work out.
You could also consider this a disclosure of sorts, I post a lot on this forum about jj (and Rust), and so knowing that I now have a financial incentive is important context to some people.
[flagged]
That line wasn’t even in the original post. It is not nearly the only technical advantage. The point of the post is just not about that topic, so that content is not in the post.
That line _is_ in the post. Search for it. I'm not sure what you're on about.
Wasn’t in the original post. I added it based on feedback in this thread.
[flagged]
This is very funny. People just love jj! There are no conflicts of interest, at least not until now that there is a company built around jj. But they also started it because they love jj!
It would be naive to think the company initiative didn't starte some time ago, before content started being posted even.
And there's still no disclaimer so your point is moot.
Say what you will -- East River Source Control is a great name
I'm kind of ambivalent about jujutsu. There's too big of an impedance mismatch between GitHub PRs and jujutsu, nor does it have a good VS Code UI.
I only use it when I have a ton of minor PRs I want to work on simultaneously, otherwise I just do git worktrees.
I'm mainly in favour because my name is JJ and sharing my name with a widely-used source control tool might help my career.
I've been enjoying the jujutsu kaisen VSCode extension, you just need to disable VSCode's built in git integration for each project where you use jj (although I'm planning to invert by disabling git globally and then only enable it for the few projects where I don't use jj)
Since JJ has technically git compat, I think there's 2 things needed for it to take off
1. A good vscode extension (there's two so-so ones that I'm not sure are being updated) 2. LLM knowledge. I ask gpt-5 about doing something in jj the other day, it didn't even recognize it at first. When I reminded it it was a vcs it hallucinated half the commands. I ended up figuring it out myself from the docs
I do think that GUI stuff is important, there's a lot of folks who have said "not interested unless there's a magit equivalent" which I totally understand. I'm not a big GUI person for VCS myself, so I don't feel the same pain.
For LLMs, yeah this is hard for any new project. I use Claude Code, and it does a decent job with jj, it only tries to do git stuff sometimes. I haven't asked ChatGPT about it though, I'll have to experiment with it myself.
Since they guy behind it works at Google, I wonder if gemini is better at it.
I don't think Google is best though of as a singular entity, I'd very seriously doubt that if it is, it's because of this.
Doubt the gemini team handpicks training material based on wether the author works at google or not. Doubt they handpick it at all.
Both Claude and Gemini can be integrated reasonably by informing them how to use JJ commands or even through MCP
> I ask gpt-5 about doing something in jj the other day, it didn't even recognize it at first.
Using VC commands an LLM generates for you sounds like a pretty terrible idea anyway. What if they delete your data? Why not spend 5 minutes searching for information written by a human?
I'm not running them blindly and I do always ask the llm to explain the command since the end goal is not to have the llm do the work but rather for me to learn jj.
+ any good tool will not let you run destructive commands without confirmation unless it's with some force flag and I have enough computer knowledge to not use force flags blindly.
+ "information written by a human" can be unreliable too.
jj undo :)
You're kind of right about LLM knowledge but the implication is funny. We just cant be expected to learn new things without LLMs...
What a terrible world we're barrelling towards when "LLMs don't know about it" is considered a blocker towards something taking off. Read a blog post, read a man page (which GP says was indeed their solution), or just play with the dang tool, I assure you these things are still possible without the assistance of a predictive text engine helping you!
One problem is that Stack Overflow isn’t being updated with questions about new tools because it’s been crowded out by LLMs, so the LLMs can’t train on these nonexistent new questions/answers.
Should I learn this thing or wait 4 months until the next LLM refresh might have ingested it?
This feels like the same question as in consumer goods: "do I buy this thing now, or do I wait for the potential refresh in 6mo to see if it's better?"
Just get the thing you need now, there's no promise the grass is getting any greener, nor that you're any better off waiting. Maybe the LLM won't learn for 6 months. Or 12 months. Or maybe the AI company will run out of money, or jack up their prices to where you're not using it anymore, or whatever.
I was asking in jest, but yeah
> LLM knowledge
Give it time. Once JJ resources enter the dragnet of future model datasets, LLMs will get better at regurgitating it.
One thing I've wanted is the ability to group commits into a mega commit. So the history of little changes remains, but as you are scrolling you don't see all of them.
This is an interesting idea... jj has a templating language, but it's not stateful, so you couldn't like, hide some commits based on other commits, for example. But I'll file this away...
I've thought that it could be as simple as commit's with a message beginning with "minor:: " do not get shown by default.
Hmm, yeah that might work. I was thinking about grouping all “minor::” commits as one commit, but this sounds more feasible.
That's called a merge commit. You can even group them further by creating merges of merge commit. It requires you to merge in the right direction though.
That doesn’t really hide them in the UI though.
--first-parent? --simplify-by-decoration? It doesn't hide them by default yes. It does have several paragraphs with all the ways how to simplify the history.
You could probably use bookmarks for this.
I’ve been adopting jj for my personal projects for 1-2 months and have been very happy with it. One downside is if you move back to edit older revisions, it’s very easy to accidentally append something that was added to .gitignore in the meantime. I suppose it’s a relatively unique problem. Other than that I definitely like it, but my git knowledge accumulated over the years is still much larger than jj.
I’m going to be slowly adopting it for work.
jj describe -m "Good luck, Steve!"
Thanks!
For all its warts and less than stellar UX Git is good enough (from a former Mercurial user)
I guess I see what the author was trying to convey talking about Rust/Go at the start but I'll admit it confused the hell out of me when we got to Jj (horrible name IMHO but whatever).
Jj is a VCS, it was not at all clear (to me) until I got further and I was very confused as to why we were talking so much about source control (I thought Jj was a language since the article started by talking about Rust/Go).
Apparently Jj can work on/with git repos making it easier to adopt incrementally which is neat and the main point of this post is that the author is leaving Oxide to go work for a new company trying to to create the GitHub of Jj (my understanding at least).
I hope this helps someone else who might be confused like I was.
Thank you for the feedback! I talk about it so much I feel like regular readers of my blog will know this stuff already, but I also don't want to leave out important context for others.
I tried to help fix this in https://github.com/steveklabnik/steveklabnik.com/pull/125/fi..., thanks again!
If you're asked why Rust. I believe you are always free to choose.
The full name is Jujutsu, but I think "jj" did come first. "j" is nice because on QWERTY keyboards, not only is it on the home row, it is one of the two keys that has a little tactile marker (bump) on it.
So excited for this. I talked to the ERSC folks last year about joining but it was a little early for me. Still incredibly excited about what they're building and glad to see one of my favorite people joining the effort.
Steve, if you come to NYC hit me up!
Thank you! I’ll try to remember that.
@Steve: Do you think it would be possible to expose ERSC's Bluesky account to the Fediverse? It's fairly straightforward via https://fed.brid.gy.
Edit: Ah in fact, just like your personal account already is, so you know how straightforward it is :)
I’ll take a look at doing that once I actually start work :) I agree it’s something we should do.
I’m just sad pijul doesn’t get the same attention or love from the community. It desperately needs an ability to colocate with git.
I think that the ability to collocate with fit is really the thing that makes even evaluating the use of jj feasible in many organizations. To consider pijul, it requires throwing away all of your forge setup, configuration, permissions, backup, disaster recovery, as well as updating every CI pipeline.
In a very real way, git won, and the inertia behind git is higher than it was for any VCS tool before it, and so just being better isn’t going to be enough, you’ll also need to interoperat.
I strongly suspect that its not feasible to colocate pijul and git. git and jj are based on snapshots, while pijul is based on patches. They have very different models.
As long as some repository state in either system can map onto a checkout in the other system it should be possible at least in some capacity. I’d like to try out pijul at work, but I’d need an analog of jj git fetch and jj git push. Whatever happens in between doesn’t really matter too much unless it’s tedious manual bookkeeping to maintain history mapping.
Agree, the feature of commutative patches just seems obviously superior? Not sure why there's a critical mass to adopt jj over Pijul apart from jj's git backend.
I think this gets at one of my original comments in this thread: I think the reason of commutative patches has communication issues. The benefits touted are very abstract. The problems I’m aware of it solving are mostly theoretical problems for working devs, and so the message hasn’t landed. If I were interested in advocating for these tools, I’d be trying to find a message that does resonate.
I think git compat is the absolute #1 priority for working devs to even consider seriously checking version control tools out. I wouldn’t have tried jj if I couldn’t run it on my work git repo and I didn’t try pijul on my work repo for this reason. It hurts my inner geek who’d like to try using it for something professionally.
> The benefits touted are very abstract
Not really? From someone's post here a year ago:
> The real innovation of a lot of these alternative DVCS systems is that they free the state of the source from being dependent on the history that got you there. Such that applying patches A & B in that order is the same as applying B' & A' -- it results in the same tree. Git, on the other hand, hashes the actual list of changes to the state identifier, which is why rebasing results in a different git hash id.
Anybody who's wrestled with reordering/rebasing git history or has done git archaology is able to understand this benefit.
From Pijul's site:
> Pijul is the first distributed version control system to be based on a sound mathematical theory of changes
After years of grudgingly tolerating using a deployed prototype for a VCS, yes, I want the mathematically sound alternative.
All that being said, I do wish you the best, because truly, I am tired of git and JJ does seems like an improvement.
Do I benefit from getting the same hash no matter what order I put the patches? In fact getting the same tree either way feels like information loss. Even if I only merge, won't I lose track of what the actual state was when each commit was made?
Most of my wrestling is with merge conflicts and a consistent tree doesn't help with that.
Is there a jj tutorial not assuming git knowledge at all? I'd love to see how to approach jj without git.
Someone attempted that a couple of months ago: https://jj-for-everyone.github.io/
Discussion: https://news.ycombinator.com/item?id=45083952
Not really, but I think there should be.
At the risk of being unreasonably negative, stuff like this just makes me feel... tired. Git is... fine. I'm sure it doesn't solve every problem for everyone, and oh boy does it still have many rough edges, but it works and (as the article points out), git has won and is widely adopted.
I've extensively used CVS and Subversion in the past. I touched Mercurial and Bazaar when I ran into a project that used it. I remember in the CVS days, SVN was exciting to me, because CVS was such a pain to use, in almost every way. In the SVN days, git was exciting to me, because SVN still had quite a few pain points that poked me during daily use. Again, yes, git had and has rough edges, but nothing that would make me excited about a new VCS, I don't think.
Maybe I'm just getting old, and new tools don't excite me as much anymore. Learning a new tool means spending time doing something that isn't actually building, so my eventual use of the new tool needs to save me enough time (or at least frustration, messily converted into time units) to balance that out. And I need to factor in the risk that the new tool won't actually work out for me, or that it won't end up being adopted enough to matter. So I think I'll wait on jj, and see what happens. If it ends up becoming a Big Deal, I'll learn it.
I think being conservative about tool use is totally fine! I'm actually pretty conservative about most of the tools that I use.
The goal of this post wasn't really to convince anyone on why they may want to give jj a shot, more of just a post about how I think about technologies I may want to spend my limited time on this planet working on, and announce that I'm making a move.
I don't think that you're being unreasonably negative. I think it's crucial for technologies to understand that your position is basically the default one, and that you need to offer a real compelling reason to choose a new tool. For some people, jj has enough of that already to bother with choosing, but I think the real power is in things that aren't widely available yet. Hence the need to go build some stuff. It's early days! Not even 1.0 yet. It's very natural that most people do not care at this stage.
Sure, definitely, sorry for being a bit off-topic, clearly this was about you and your plans and not intended to be about jj itself.
Having said what I said, I do find new tools to be interesting, and I do hope jj ends up being successful. I'm always happy to be surprised by something that fixes problems that I didn't consciously know I had, or that adds new features or work modes that make my life easier in ways that never would have occurred to me in the first place. I was a pretty early git adopter, and it works great for me, but I'm sure a decent chunk of that is because I understand how it works under the hood, even if it often doesn't present a great UX.
And even if jj doesn't eventually surpass git's popularity, it's great to have other options, and avoid monocultures.
One thing not mentioned in the article: what advantages does jj offer over plain git?
So for me, the most compelling thing about jj is that it is somehow simpler than git, while also being more powerful than git.
What I mean by simpler is, there's fewer features, which makes things easier to pick up, because these features fit together in a way that's more coherent than git's. By more powerful, I mean jj lets me regularly do things that are possible, but annoying and/or difficult in git.
I loved git. I was never the kind of person who thought its CLI was bad. But then, when I found jj, I realized why people thought that.
As someone who loves git, has always thought the criticisms about its interface were overstated... but also feels like it maybe has too many incoherent ways of doing things, this is the best sales pitch I could've asked for (and I came to the comments to ask for a sales pitch). Thanks - I'll try jj out the next time I start a hobby project.
Another good sales pitch is `jj undo`[0]. It puts the repo back to previous state, regardless of what the mutative operation was. It's a powerful and simple safety net that unlocks experimentation.
It does this by adding an new operation on top of the operation log[1], so you don't lose repository states by moving up + down the op log. There's a corresponding `jj redo` as well.
0: https://jj-vcs.github.io/jj/latest/cli-reference/#jj-undo
1: https://jj-vcs.github.io/jj/latest/operation-log/
jj undo is great but it's a one time thing. You can't do jj this, jj that, jj other, jj undo, jj undo, jj undo AFIACT. You have to look into the op log and jj op restore for that. It's nice you can get back to where you were though.
The biggest issue for me is it requires active change management (or feels like it). In git I do `git checkout foo` then I start editing. If I want to see what may changes are since foo then `git diff` tells me. With jj though, `jj edit foo` is the to git, state of the repo ALL changes to foo. So any new edits are invisible. So, instead of `jj edit` I have to do `jj edit` `jj new`, then later squash those into foo
I know there are similar cases in git but I guess I'm just used to git so I wasn't using those cases.
that said, I'm mostly enjoying jj. Though quite often i get a conflict I don't understand. Today I got 2 and it told me choose A or B. I did `jj diff -r A -r B` and it said no diffs. If no diffs aren't there no conflicts? I'm sure someone gets it but it was annoying to just have to pick one to abandon
> jj undo is great but it's a one time thing.
For what it's worth, this changed in v0.33.0:
> jj undo is now sequential: invoking it multiple times in sequence repeatedly undoes actions in the operation log.
(release notes: https://github.com/jj-vcs/jj/releases/tag/v0.33.0)
I’m not sure if it’s a typo, but you don’t need to edit and then new, you can just new. It’s a good habit to get into as a replacement for checking something out.
I’m not sure what happened in your conflict situation either, that does sound frustrating. EDIT: Oh, I wonder if it was this: https://jj-vcs.github.io/jj/latest/technical/concurrency/ specifically, that I bet the repo was being modified concurrently, and so you ended up with a divergent change.
> You can't do jj this, jj that, jj other, jj undo, jj undo, jj undo AFIACT
You can as of v0.33.0[0]. Previous behaviour was that `jj undo; jj undo` would leave you where you started (it undid the undo).
> The biggest issue for me is it requires active change management (or feels like it). In git I do `git checkout foo` then I start editing. If I want to see what may changes are since foo then `git diff` tells me. With jj though, `jj edit foo` is the to git, state of the repo ALL changes to foo. So any new edits are invisible. So, instead of `jj edit` I have to do `jj edit` `jj new`, then later squash those into foo
I'm not 100% clear on what you mean here, but a few things that might help:
1. In jj you don't "checkout" a branch, you edit a specific commit. That commit might be pointed to by a bookmark but it doesn't have to be. A jj bookmark is roughly equivalent to what git calls a branch. Note that a git branch, and a jj bookmark are just pointers to a commit, as illustrated here[1]).
2. If you want to resume work on a branch/bookmark instead of `git checkout BRANCHNAME` you'd do `jj new BRANCHNAME` which puts a new commit on top of the commit and sets it as a working copy.
3. Bookmarks don't auto advance like they do in git. So adding new commits on top of a "branch" will leave the bookmark where it is until you `jj bookmark set/move` it. So you could squash commits down into the "foo" bookmark, but you could also move "foo" to point to subsequent commits.
4. Not sure what you mean by edits being invisible, but if it's seeing a diff from main to the tip of your branch (with a change id of ex. XYZ) it would be `jj diff -f main -t XYZ`.
0: https://github.com/jj-vcs/jj/blob/main/CHANGELOG.md#0330---2...
1: https://social.jvns.ca/@b0rk/111709462585184810
> I'm not 100% clear on what you mean here
The core of their complaint is that if you use `jj edit` it's not obvious how to get a diff of what you did. The answer, of course, is that you can use `jj evolog -p`.
Ah, yes that reading of the “work is invisible” is likely.
You're welcome, and feel free to let me know how it goes. There is an adjustment period, for sure, and (in another eerie parallel to Rust) some folks try it, bounce off, and try again later, and it sticks then.
The auto-commit behavior was one of my biggest concerns when starting, but it turns out that when combined with other things, I'm a huge fan now, for example.
Git's CLI is awful compared to fossil or even mercurial. Jj seems like an improvement over git, but it lacks a web UI like fossil has. It's very useful. Basically like a self contained github lite, only without the needless complexity, the enterprise bs, the annoying login process, tokens, passkeys, brain damaged permissions system etc.
can you directly get the parent branch of a branch in jj?
This is one thing that I constantly find myself wishing was in git but inevitably resign myself to knowing "thats just not how git works."
Right, “parent branch” implies a tree structure, but git is a DAG.
You might have a specific workflow such that you can actually answer your question, but it won’t generally apply to all repos.
Since a branch is really just a label for a specific commit, which may be at the end of a chain of successive parent commits, a branch isn’t really a first class structure, but a derived one.
You can get the fork point of a branch, which is a common ancestor commit shared by another branch, but that fork point is a commit and may not have a branch label. That commit can have any number of other branches going off of it: how would you decide which one is the parent vs just another sibling?
My assumption after looking at jj is that it is not as complicated as git yet. Give it time. It’s also not even as simple as git for many tasks, based on their own docs: https://jj-vcs.github.io/jj/latest/git-command-table/
That's even less how jj works, unfortunately for this use case, because jj doesn't require branches to be named.
You could probably attach metadata to commits indicating the branch name at time of creation, but there's probably a lot of weird edge cases to handle.
I don’t think you ever need to do this, jj tracks changes much better than git, assuming I understand your question. E.g. you can rebase a whole local change dag based on a commit from origin with a single jj rebase -b and it’ll move bookmarks (git branches) correctly.
Thanks stack overflow
Could you spell out slightly more what you mean? I'm not 100% sure what "get" means.
I think they mean what other branch some branch was originally branched off from.
Yes. I could see confusion about the term "parent" but get? IDK what the confusion is there.
It was just like, in what context? To print a log? To rebase something? "get" is not a command exactly. That's it.
Some of it is also what a "branch" means to different people can mean different things: https://jvns.ca/blog/2023/11/23/branches-intuition-reality/
But yeah, as others have said, not really possible in a general way, sadly.
get as in find, retrieve, identify, etc. Im talking about a use-case not a git feature.
Yeah now I got it! Just me being a bit confused, it's a me thing, not a you think :)
If you're looking to model git branches as much as possible with jj bookmarks and assume that:
- the working copy has a bookmark pointing to it
- there's some ancestor with a bookmark
- there's a single linear path between the two with no other bookmarks in between
Here's an example that represents a branch containing 3 commits named "bookmark-05ff" branched off of "bookmark-6825".
In this case, the following log would get you the commit pointed to by "bookmark-6825": I'm using the builtin_log_redacted output template. Normally you'd have actual bookmark names, descriptions, user, etc.Also note this bakes in a lot of assumptions and is brittle. As many others have said, it’s not generalizable.
Just my feedback - I've personally found jj more complex for simple projects. Like if you have a non-collaborative repo where you push to main most of the time after making a series of commits, in jj you have to keep updating a bookmark before pushing it and there's no one command to do both.
If you have another machine on main without any outstanding changes and you want to pull the latest changes that is probably also two steps (git fetch + new?)
That said, I've been liking jj quite a bit for more mature / collaborative projects. It has been a learning experience. (don't enjoy updating bookmarks for PR branches though; jj encourages rewriting history which is not my favorite choice for code review branches; I often work in repos that squash-on-merge).
Yeah, in that case you may want to configure bookmarks to auto update for sure :)
Is there a way to do that today?
For updating bookmarks I've found like half a dozen variants of `tug` alias the community has come to using which is just a slight improvement (bit daunting to newcomer to pick 'best' one and not fan setting up aliases on all my working devices).
It would be nice if jj was better than git for the fundamental workflows like this out of the box overall.
Yes, it's called "experimental-advance-branches" (because it landed before the branch -> bookmark rename). Here's an example of setting it:
https://github.com/jj-vcs/jj/blob/c70f9b5b3fff08a86fb11afc57...
So better UX while keeping git's solid internals?
Makes sense. Developers I know have been wanting that.
It's technically a bit more than that. JJ is its own VCS, with pluggable backends. Google has a closed-source Piper backend, the git backend is the only real open source backend. But at high level, it's fine to think about it in that way, yeah. I tend to think about it as being more "able to work on git repos" than as a UI.
I can't hear Piper backend without thinking about Pied Piper
Not the only project that's working on that.
https://www.gameoftrees.org/
I used to think the same way about jj not too long ago. I even wrote a few comments here similar to yours, but since then I’ve changed my mind.
For me, the turning point was realizing that jj actually eased some of the frustrations I had with our rebase workflow at work. It took a while for it to click, but now I wouldn’t want to go back
May I ask what is your rebase workflow at work?
In my day to day, its basically "git pull --rebase repo branch", plus some interactive rebate to squash commits, and it's not particularly frustrating, so I'm curious what you're doing that we're not.
Basically the same except in some projects we are not supposed to squash. So, sometimes we end up with long long histories to be rebased onto other long histories.
rerere only helps so much with conflict resolution but with jj I think it is as painless as it could be.
jj is actually so good though. People don't need to know you're using it, which is why it's nice.
A problem I run into when working with other people is that code reviews take forever and I need to build on top of them. Code gets merged while it's being reviewed, and it becomes a burden to keep rebasing your stack of PRs. It's also difficult to do things like designing each PR against the main branch, but testing all 3 of them together. (Sometimes you want to write the docs / take screenshots as though all your features are merged as-is.) jj makes all this trivial. You tell it what you want and it does it without involving an index or working copy or interrupting you to resolve conflicts.
I've found that it really makes me less annoyed when working with other people. I don't know why it takes people longer to review code (or to even open the review request) than it takes me to write things. But it does, and jj is what keeps me sane.
To be fair, I also use it on personal projects because sometimes you have 3 things you want to try at once and they're not related to each other. Upstream isn't going to change without your understanding, but it's still mechanically something to maintain the rebases on those 3 branches. jj just makes this burden go away.
Having said that, I don't know why a "jjhub" is needed. Github seems fine. jj's just a UI for git.
> Having said that, I don't know why a "jjhub" is needed. Github seems fine. jj's just a UI for git.
When you make a change to a pr in response to review feedback, do you just jj edit it in, and end up with a force push on GitHub? After which the review comment might get detached from the code or even hidden.
That's definitely something that could be better for me. (That said, there are other projects making it better too.)
Without knowing much about jj, isn't this more of a problem in how the GitHub/GitLab/Forgejo Pull Request system works rather than a jj problem?
Patch-based workflows in general (where you heavily use git rebase -i to curate a smaller set of "perfect" commits for anything you'd PR, rather than just piling new commits onto every PR) don't work well with the GitHub approach, which heavily favors merging stuff through the web interface (making merge or squash commits along the way).
You can make it work of course, but GitHub tends to behave in weird ways when it comes to how it's interface works with patch-based workflows. Perhaps a better estimate would be to see how it compares to a forge like Phorge or Sourcehut.
Yes, that's why I asked it as a response to them saying GitHub was just fine :) With Git, I work around it by adding `--fixup` commits and then doing a final `git rebase --autosquash` just before landing it, but it would be nice to have a better workflow.
(The mention of the GitHub SVP being interested in stacked diffs sounds good in that regard. I'm also keeping an eye on Tangled and, now, on ERSC.)
Yeah, I always force push. I always treat PRs as one atomic unit ("squash and merge") and the history that goes into the PR isn't relevant to me after it's merged. Maybe once or twice I've regretted this (maybe you want to backport one part of this PR to the release branch, and now you have to manually create that) but in general, how something is made can be ugly and it feels good to me to erase it when something is ready to be merged.
I guess in terms of clean workflow, I could get into that more using jj (i.e. create a PR for every bit that I want to have in the history later), but as a reviewer, I always get annoyed when I have to do a re-review of something that was force-pushed, and I'd like to avoid doing that to others. So I was kind-of hoping you had found some magical silver bullet there, haha.
I'm banned from force-pushing at work so this makes jujutsu a challenge for me.
The workflow still functions, it just requires me to manually update bookmarks.
I see. I guess you guys keep merging in your upstream branch until it's finally time to merge a PR into that branch? A lot of people like that workflow but it never really made sense to me.
I personally feel like your employer's policy is unusual for PR branches. Other people force pushing? Bad. Force-pushing to `main`? Bad. But force pushing to your own branch when you can delete it and re-create it? Just a waste of your time for no real reason. I wonder what the justification is.
> I see. I guess you guys keep merging in your upstream branch until it's finally time to merge a PR into that branch? A lot of people like that workflow but it never really made sense to me.
Correct.
It makes sense to me because it leaves a linear history of git commits in response to comments on a PR which makes code review easier. We squash and rebase at the end.
> Just a waste of your time for no real reason. I wonder what the justification is.
We used to be on Gerrit, which I found better for code review as I could stack a bunch of minute changes together. I probably had 50-100% more productivity on Gerrit before my team made the switch.
Unfortunately, the industry standard is GitHub PRs. My team wasted a ton of time onboarding people to Gerrit and even after onboarding, less than 5% of people understood stacked changes enough to use them effectively.
Since force pushing/rebasing messes up GitHub PRs it was banned.
If you have suggestions for alternative code review workflows on GitHub I'm happy to hear them, but ideally they could be incremental.
Github's behavior when branches are force pushed makes some places ban it, that might be your parent's situation.
I use git but I instead just keep all PRs stacked on top of each other and rebase when one is merged. If something is easy to review I push it down the stack. If something is harder to review I keep it at the top of the stack.
I don't open a PR for each commit, and we use squash commits at work which makes it harder to have this workflow but it still works fine for me.
I rebase only the leaf PR, and I have update-ref enabled to update the branch refs of all other branches in the stack. It works well. The only manual process is that I have to manually force push each branch afterwards.
Lastly, I use the `-x "cargo fmt" -x "cargo clippy"` feature when rebasing (which is missing in jj) to make sure the stack stays in a good state
i believe `jj fix` is your -x equivalent, though im not familiar enough with it to comment on how similar the semantics actually are
It is not even close to sufficient. jj fix only executes the command on one file at a time (only the changed files) so it can't work on anything that has linter semantics.
jj does this for performance reasons. They don't want to perform a full checkout for every rebase action. This is simply something I disagree with
> jj does this for performance reasons. They don't want to perform a full checkout for every rebase action.
It's true that `jj fix` can be faster by not touching the working copy, but we also want a `jj run` command for the linter feature (https://github.com/jj-vcs/jj/issues/1869). It's just not done yet.
I have a question and you might have an idea about this:
I have a workflow where I have my main and a bunch of branches that are children of other branches. So: main, branch_a, branch_a_1, branch_a_2, branch_a_1_x, etc. Probably not a good workflow, but that's what I do.
I keep editing old commits in my branches to have clean, atomic commits, which fucks up my branch structure and I need to cascade-rebase everything manually.
Do I understand correctly that jj does it automatically?
That's correct, it will do the cascade rebase of everything automatically.
Thanks Steve, and for the tutorial too!
git rebase --update-refs
That won't update sibling branches
Won't work, only tree
I think yours a perfectly reasonable stance, and I often feel the same way. And all this complaining about git is getting tiresome, especially if you had to deal with its predecessors.
FWIW, I still tried out jj and found it a joy to use. I use it all the time now. Most of the time, it not only gets out of your way, but rolls out the red carpet. I'm saying that as someone who knows their way around the git command line. t's like replacing your trusty old remote control with a new one where the buttons are well labeled, ergonomically placed, that lets you do entirely new useful things, and it has a universal back button that just works.
Maybe jj is an especially good fit for my way of working, but I do think that it is a real, actual improvement for everyone.
And it's super easy to pick up anytime. So yeah, I think you're doing it right! Sit back and let it come your way. From what I can see, there's a pretty good chance that it will.
The good thing is that a new player entering the arena allows a fresh look at certain problems, and some solutions then get backported to the OG project. I've read it already happened that jj inspired some changes in git.
Same happened e.g. with nodejs getting deno and bun around, which allowed to break nodejs' inertia on many problems that the others have solved.
I learned all of cvs, svn and then later git when getting into Linux and Open Source. Based on my early experience of multiple systems, I'm very surprised that git has dominated and lasted this long already!
I've also worked with CSV (barely) and SVN (more extensively) and I was blown away by Git.
You can have real branches! Many of them! You don't have to manually merge them! It's decentralized, you can have multiple origins, it lets you work offline! The list goes on and on.
There were many compelling reasons to switch to Git. But for all the articles about jj out there, I've never read any compelling reason to switch to jj. "It easier", "the commands are somewhat more ergonomic"... that's all?
One thing JJ has that git doesn't is the concept of first class conflicts. In JJ, rebasing or merging never fails, but it might record a conflict to resolve later. Git, on the otherhand, forces you to drop we everything to resolve conflicts immediately. It sounds like a small thing - but in my experience, being able to resolve conflicts later when I feel like it is absolutely amazing and really helps reduce context switching.
You don't need to switch from git to jj. You just try out jj on your git project and see if it clicks. If you don't like it, you just don't continue using it.
Ergonomics are everything. Its why there are zillion IDEs, terminal apps, keyboards, mice, etc.
Hot take, but I personally hate git and almost always rely on a GUI tool or IDE integration to interact with it.
I personally feel quite capable at git and am not looking for another flow or tool.
But. I think it's incredibly useful for organizations to have patterns for how they use git. There can be huge variance! And there's so many people who don't feel comfortable doing interactive rebases (of their feature branches), or other serious monkeying with history.
(I also think jj's flows are incredibly good about avoiding accidental loss of work in a way that git can be extremely dangerous at.)
I think there's a ton of value to having more of a pattern than the free-form jamming that git gives us. The real value I see in jj is that it's more than the toolbox of things git gives one: it's something more learnable, teachable, and directed than git. And it seems to do it pretty well, with style, and less monkey business. I'm still super greenhorn at jj, and honestly lacking the need for it, but I'm excited to see it come along. Especially since it is compatible with so many other VCS.
I used to think this, too. Tried jj a few times before it clicked.
It’s very liberating in some ways, in others it’s simply no worse than git. You can do everything you can do with git, but some of those things don’t require multiple steps or n repeats of the same action. jj rebase + commitable conflicts + jj undo = freedom and peace of mind.
Fair points. I'm also generally happy with Git myself.
I've been exploring JJ mainly for its slightly different approach to change tracking (~every change gets tracked, at least initially, rather than just commits).
Stacked PRs also look interesting but I haven't had an occasion to try them out yet.
stupid question: can someone with a deeper understand kindly explain, why are talks lurking around the corner about hopping version control systems, whats wrong with git that jj solves?
There's a variety of things that all add up to a very pleasant experience.
For starters, git has a poorly-designed UI, whereas jj borrows from Mercurial, which was widely-considered to be better designed from a DX perspective. I can usually guess what commands and flags to use on jj without reading the manual; not so for git. E.g., `jj undo` undoes anything you might have done; For anything esoteric in git, I consult ohshitgit.com because there's half a dozen different commands to know to undo everything.
Another is a simplified model whose pieces compose better with each other. There's no need for staging or stashes in jj. You essentially work in a HEAD commit all the time, and when it's ready, you commit what you want, make a new child commit and keep going. A stash is just some random commit. Anything you've learned about manipulating commits now applies to "stashes" with no extra effort.
No need for branch names. If you come from git, learning that "stashes" are just random little HEAD commits may make you groan and think you have to give each of those branches a name. jj doesn't care about that. I still give major branches a name, but since I don't have to name them in general, it's easier to create lots of little branch "experiments". Branch names were never really a substitute for commit messages, anyway.
More rebase-friendly IDs. Git penalizes the use of SHAs when rebasing, whereas in jj, change IDs are stable after rebasing, making them actually usable. This makes rebasing easier and safer.
First-class conflicts. Git forces you to stop the world and deal with conflicts immediately, or back out entirely. This isn't really required, and jj has no opinion. It marks conflicted changes, and waits for you to get around to fixing them. This is great when your boss interrupts you for something high-priority. It also automatically rebases all downstream changes after you've fixed it, which usually clears their conflicts too.
Megamerge workflow. This is possibly doable in git, but I don't know how easy the workflow is. But in jj, creating a work commit that has multiple branches as parents is trivial, as is pushing work down into individual parent branches. This makes integration easier, as you can easily see how your upcoming changes interact with your colleagues' upcoming changes.
Jujutsu's not a revolutionary VCS change (like Pijul's theories of commutative patches), but it's generally way more pleasant to use, with better DX, than git. I gave up git forever after two weeks of jj.
> There's no need for staging or stashes in jj.
What the JJ developer seam to misunderstand is, that the index and stashes are a feature to improve the users workflow. You can bypass the index with commit -a, giving you JJ behaviour and you can commit just fine instead of using the stash. The stash is like some backlog of temporary commits and also supports saving the index separately. When you don't care about this, you don't need to use it, these features are purely additive.
So this is as an opinion I had when I heard about jj too. But the thing is, jj supports this workflow very well, and in fact better than git, because the stash/index is just a normal commit, not a separate feature.
In fact, the most popular jj workflow is closer to the git add -p workflow than it is git commit -a. I’d argue more jj developers work this way than git developers do in git, even.
JJ has a backlog of temporary commits? That's a stash under another name.
Not exactly, but sorta. You don't need to write a description for a commit, and it doesn't need to be on a branch, and things are auto-committed. So if I'm working on something, and then I "jj new" to start something else somewhere else, I can trivially come back to what I was doing. You're right that's a stash under another name: a commit. But the key is, because it's not a special separate thing from a commit, I can use any command that works on commits to work on my stash. We've unified two things into one thing. And that's useful.
I was talking about:
> backlog of temporary commits
So a list somewhere else, which commits are considered temporary. It's a todo list for commits. Does JJ have that?
> We've unified two things into one thing.
A stash in Git is also just two commits. So no you just removed a feature on top of that. That feature might not be to your taste, or even outright confusing, but you did remove it.
I am saying a feature was removed, yes. But what I'm saying is, the things you use that feature for can still be accomplished. It's just not accomplished via a dedicated feature, but as an effect of how other features work.
Yeah and I am saying that you could do that as well with Git. Git just also does have that feature and a lot of users like to use that, but you don't need to.
git and jj are functionally equivalent in this respect, yes, but it's all easier in jj. It all becomes one concept and one command set, instead of three different ones.
The git features of stashing/staging are removed, but they're superfluous given how jj works. You don't need or want them, and keeping them around would only give people two ways to do the same things.
> You don't need or want them,
> give people two ways to do the same
This is where I am disagreeing. I do want them.
for, while and goto achieve the same and are the same thing under the hood, yet they convey different semantic and you choose between them for different tasks, because they are more suited for different things.
stashes and commits achieve the same and are the same thing under the hood (both are commits), yet they convey different semantic and I choose between them for different tasks, because they are more suited for different things.
All in software is about different abstractions, that all fundamentally do the same thing.
> but it's all easier in jj
You can literally use commits instead of stashes in Git and guess what: it's the same as treating any other commit. Here JJ and Git are the same. You can also do stashes in Git, when you want temporary commits that are put into a "todo" list.
With the way jj works, all the functions of indexes and stashes are handled with commits.
There's no loss of speed, functionality or power, and as a bonus, instead of maintaining 3 separate concepts and commands, they're now handled by 1.
It's honestly much simpler. A git stash is effectively a commit already, but stored weirdly, doesn't show up in the logs by default, and with its own ad hoc commands.
I would love the git index to not be a thing you had to care about unless you deliberately wanted to use that feature, but in my experience this is not the case...it feels to me like an implementation detail that surfaces into the UI more than it ought to.
But that is the thing, it is not exposed as an implementation detail, it is a skipable part of the user model.
It’s not really skippable because you have to know about it in order to skip it. And a lot of commands interact with it, and so it adds complexity to those commands, git reset being the most obvious case. But even stuff like rebase. Lots of commands that need to abort on a dirty working tree just simply work in jj.
I find myself from time to time with stuff in the index (merge conflicts in particular I think can result in this). I never put anything there explicitly. I would love to be able to configure git such that the index didn't exist, but I can't.
The transparent thing would be to put everything into the index, and configure all the commands to autoupdate the index. This is what happens with the merge conflict, because there is no benefit having resolved merge conflicts not already in the index. So the merge conflicts already do what you want, the other commands need to be modified.
That isn't what I want, though. I don't see any point in the index as a construct -- I am happy with changes being either (a) not in any commit, just in the working directory or (b) in the most recent commit. Somewhere lurking in the middle is just confusing.
I constantly have something different in the index, that is neither in the commit nor in the workspace, and I find that useful.
thank you for the detailed explanation. let us say you want to edit the contents of a commit on git 10 commits ago and this was not pushed to remote. you would do a rebase which basically discards and underneath basically creates a new track to follow. How does jj rebase work? I heard you can actually edit something 10 commits ago in jj and it treats it natively as an edit
I'm not sure what you mean by "natively as an edit", but jj really doesn't care if you alter some commit 10 back from the branch tip. Everything is automatically rebased as needed, and unless you introduce conflicts, it's seamless.
The typical workflow would be:
1. `jj log ...`
Locate the change ID of whatever change needs altering.
2. `jj new $SOME_CHANGE_ID`
This creates a new, empty change whose parent is the change you want to edit
3. Make your changes, pass the tests, etc.
4. `jj squash`
All your changes get squashed into $SOME_CHANGE_ID
5. jj then automatically rebases everything downstream of $SOME_CHANGE_ID for you.
All jj change IDs remain the same (though the underlying git commit SHAs are now different.)
If there are any new conflicts introduced in the downstream changes, they'll get flagged. You'd then typically run `jj new $FIRST_CONFLICT_ID`, and repeat the whole process.
---
It's also possible to run `jj edit $SOME_CHANGE_ID`, to work directly on that change, but because of the automatic rebasing, it's preferable to work on a child commit and only squash when ready. (In case you need to do other things with downstream changes before you've finished making your updates.)
---
Does that answer your question?
How does JJ ensure that there are now change ID conflicts on push, i.e. so that no two repos generate the same change-id?
Until recently, change ids were entirely local (in the git backend). So conflicts just can’t happen.
Recently jj started including the change id in a header in the git object, so the receiving side can choose to respect/expose them as appropriate. An issue with this is that some git commands don’t guarantee the preservation of headers, so it’s possible to accidentally lose them; git rebase being a prime example.
The same way Git ensures that no two repos generate the same commit ID: by using a cryptographic hash function.
What does JJ hash here, when I can modify the whole commit, but the change id stays constant?
The parent comment is wrong.
Generally, when a new change ID is needed, jj generates a random number. The individual commits under each change still use hashed SHAs.
> when I can modify the whole commit
You can't actually modify any commits. A mutable change can be thought of as a subset of immutable git commits with a pointer to the most recent commit.
When you alter a file and jj snapshots, it adds immutable commits to the DAG end, and updates the pointer.
---
There's some exceptions, like when importing from git, it will generate new change IDs based on a transform of the git SHAs.
---
Side note: as much as I like jj, I admit the change/commit terminology is confusing as hell.
Steve, Rain, please call it dojo.dev or something before jjhub catches on.
Rain’s not working at ERSC, but we won’t be naming the product jjhub, don’t worry.
That's exciting! Have you guys looked into sappling from Meta as well? It looked really cool the last time I looked into it but never tried to use it much.
One of ERSC's cofounders left Meta for this, so it's known by the team very well. Sapling and jj are similar for sure.
I would guess rain must be aware of it too! rain are you here :P
Congratulations on the new adventure, Steve, and good luck!
Congrats on the new role, sounds exciting - looking forward to seeing where it goes!
Thanks so much!
Off-topic: I found it very surprising the Logical Awesome website still works.
I'll be honest, I did too. No https cert makes it give a warning in Chrome, which I had expected to just be a 404, and was surprised when it actually worked!
Does JJ have equivalet functionality to git's format-patch and am? Because I, for one, am rather fond of the e-mail based workflow and not having that would make JJ a no-go for me.
There hasn’t been a ton of desire for it because it’s easy to just run git format-patch. Jj’s workflows feel closer to the email workflow than not, but prioritization in a young project is tough.
Oh man...
I think jj is great, godspeed!
[flagged]
> There are no substantial technical or usability reasons to switch to JJ from Git [...]. This is a neutral impersonal opinion that is virtually a fact.
I respect your opinion, but I don't think it's a fact and I couldn't disagree any further with it. I wrote a whole testimonial about why I love jj (first one there): https://jj-vcs.github.io/jj/latest/testimonials/#what-the-us...
> it's impractical for most working programmers to switch
I don't know what you mean here. I think it's quite practical to switch to jj, and people switch over all the time. Some people are going to be earlier adopters than others, but the early adopters can bring others along (as I've been lucky to do at Oxide).
> focus on something whose main appeal is more than social
Steve and I both think that Jujutsu's appeal is far more than merely some bandwagon effect. I do think that social appeal is an important part of making a project succeed, and that projects without substantial technical merit win due to social appeal all the time, but Jujutsu has both! It's amazing!
> I respect your opinion
Thank you for charitably responding to the substance of my comment and especially not whatever tone you may have perceived. I appreciate you.
Perhaps take a look at jj and give it a go. Maybe you’ll like it.
I know I did. The fact I can use it with git and it doesn’t interfere with any GitHub PR workflows means no-one needs to know I use it. I enjoy the jj model. Maybe you could too, and it’s not a crazy investment of time and energy. It’s an evening playing around with a code kata or something.
Then working with vcs becomes that little bit more enjoyable.
> There are no substantial technical or usability reasons to switch to JJ from Git and it's impractical for most working programmers to switch. This is a neutral impersonal opinion that is virtually a fact.
Not a thing in here is true, especially not objectively true. As neutral as you may believe yourself, it might be a good time to step back and reexamine your priors that led you to state so confidently that there’s no usability reason to switch in particular.
> it might be a good time to step back and reexamine your priors that led you to state so confidently that there’s no usability reason to switch in particular.
The key word I used is "substantial." The usability improvements over Git are marginal and if they ever become non-marginal, they can relatively easily be added to git. This is what my comment is getting at. The only essential difference between Git and JJ is that they are different fiefdoms. There is no substantial technological difference. It's just two different social factions with marginally different opinions about how to type CLI commands.
Changesets, the op log, first class conflicts, no staging area.
JJ might produce commits that can be stored in Git, but the affordances are different. If Git wants to adopt them, it becomes no longer Git.
On the other hand, I'm happily using JJ while everyone I collaborate with is using Git. JJ doesn't need to "win" to be useful, it just needs to be useful enough that the people who maintain it continue to maintain it.
The substantial technical difference is that the UI (the "porcelain") of JJ is not backwards-compatible with that of Git, even though it can use git's "plumbing". `jj commit` works differently than `git commit`, and Git can't match that without a similar compatibility break. JJ uses Git's "content-addressed DAG of snapshots" data model in a particularly elegant way, and deletes many redundant commands to achieve that elegance. Git could absolutely delete half of its interface & change much of the rest to get that, but then it'd break lots of scripts & third-party tools. JJ namespaces the UI redesign, which allows breaking that compatibility without breaking everyone's workflow at once. People can switch (or not switch) independently, where a Git UI redesign would have to be adopted by everyone together.
It's trivial that JJ can't do anything `git` can't do, because JJ uses Git as a backend, so everything it does it does via `git`. But Git the project is extremely unlikely to alter `git` the tool to provide JJ's interface, and even if they did via namespacing it (e.g. `git jj commit`, `git jj git init --colocate`, etc.) that'd just be adding even more commands on top of the existing interface with too many commands.
There must be a miscommunication somewhere because your entire comment seems to reinforce rather than refute my point.
> There are no substantial technical or usability reasons to switch to JJ from Git and it's impractical for most working programmers to switch.
Not in my experience. jj is easy to pick up and a joy to use. I like git. I deeply appreciate git. But git can feel like snow shoveling sometimes, while in jj things just click into place.
jj is freeing, because things that were a hassle before are now easy, and other things that were impossible are also easy.
I know there's a lot of hype around for a lot of things, and I get grumpy from all of it. Jujutsu is one of the few things that actually deserve the praise being heaped on them.
You may disagree, of course. But I hope that some day, you'll have reason to be happy about this tool, instead of feeling... whatever touched you off like this.
This comment could have been written when people were pushing to SVN repos on SourceForge when Git was becoming the next big thing, enough for a competitor to founded.
Yet still, the status quo is not Subversion and SourceForge, both of which have been relegated to antiquity, but git and GitHub.
Will something else unseat git and GitHub in the coming years? And will it be Jujutsu or some other innovation on version control?
Who knows, but I see no need to be so dismissive of somebody’s passion in such an arrogant way.
> There are no substantial technical or usability reasons to switch to JJ from Git
sounds like someone hasn't used jujutsu
These are my thoughts exactly. Focus on building the best thing instead of telling everyone about the dreams you want to reach. Speak softly and carry a big stick as it were.
I was thinking, it'll probably catch on like Omarchy did, except it's written in Rust, so... little chance of it being milkshake-ducked.
But as we say on Hackernews, there are two kinds of people with respect to jj: those who love it, and those who haven't tried it.
Doing the same thing, but with better usability, is immensely valuable. Remember, Hackernews thought Dropbox was "just" ssh+rsync, but they made a billion dollars making that easy.
I’ve tried it. I don’t love it or hate it. It’s more of a “meh”. I can see how it’d be nice if I was disciplined about my commit history, but I tend to be fine with chunky, clunky git. I don’t spend as much time curating perfect commit history as jj fans seem to.
> There are no substantial technical or usability reasons to switch to JJ from Git
Every magit user disagrees with you.
> and it's impractical for most working programmers to switch
This very much sounds like someone who has never even tried jj. Tell us: What makes switching impractical?
> My well-intentioned recommendation to Steve, because I've been there, is: If you want to avoid feeling like you've wasted your life in a few years on code that doesn't live up to its promise and is essentially no different than the code that already exists in Git (and Hg, Pijul, Fossil, etc.),
Is Steve even a developer for jj?
I have not contributed to jj directly basically at all yet: https://github.com/jj-vcs/jj/commits?author=steveklabnik
I am assuming that this new job will change that.
Found linus' alt
This is one person's career move that is defended by the idea that they somehow always make good bets. This is more than a bit arrogant. Past performance is no guarantee of future results.
I don't see a future for this. I see a series of blog posts that culminate in a tone deaf "What went wrong" finale.
I certainly don’t think anything is a sure thing.
Death and taxes though...
jj feels astroturfed imo, not commenting on its merits, but I just get that feeling.