Jujutsu for busy devs
maddie.wtf338 points by Bogdanp 20 hours ago
338 points by Bogdanp 20 hours ago
For anyone who's debating whether or not jj is worth learning, I just want to highlight something. Whenever it comes up on Hacker News, there are generally two camps of people: those who haven't given it a shot yet and those who evangelize it.
You will be hard-pressed to find someone who stuck with it for a week and decided to go back to git. You will not find a lot of people who say they switched but just stayed out of inertia. Of course both of these do happen—nothing is perfect—but they are by far the exception. From my own personal anecadata, I have seen a 100% conversion rate from everyone who gave it a serious try.
I encourage you to let today be the day that you decide to try it out. It is far less effort to make the switch than you probably think it is: I was productive the same day I switched and within a week I had no remaining situations where I needed to fall back to git commands. You will quickly be more productive and you will find yourself amazed at how you ever got by without it.
> For anyone who's debating whether or not jj is worth learning
I don't have any productivity issues with git, like... at all. It's not like I spend an hour running git commands every day.
I can totally imagine that some people spend their day manipulating repos with git, and jj is better for them. But that's not my case, and git is already everywhere.
To me it sounds like telling me: "You HAVE TO move to bim, the better vim. It's very similar to vim, but different enough that you have to learn new stuff. But you will be infinitely more productive: when you start bim, you're already in edit mode, so you don't have to type i! And the auto-complete in Julia is objectively a lot better in bim!".
Sure, but typing "i" a few times more is really not a concern for me, and I don't use Julia. But if it's better for you, please enjoy bim!
For a lot of people, making small and tightly-focused branches that are easy to review and merge is very important.
This is where jj excels. Especially if you find yourself often doing large chunks of work between convenient checkpoints, but you still want to create commits as if this work was all done in tiny and discrete chunks. It's also very helpful if you're the kind of developer who makes lots of unrelated changes in a single coding session, and wants all those changes to be in parallel branches that can be reviewed and merged independently. I greatly prefer working with (and being) the kind of developer that puts out a large number of very tiny and easy to review PRs, eve. jj makes doing that a breeze.
There are lots of people for whom these things aren't important. I will be slightly judgmental and say I don't really enjoy working with them. They tend to write very large PRs that are difficult and time-consuming to review. And it's a frequent source of frustration for everyone when 95% of the work is uncontroversial but a merge is being held up because of legitimate concerns with an unrelated 5%. This is even worse when there's later work that builds upon it that can't happen until a merge (or that needs to be constantly rebased as the PR is improved).
This is not to say if you do this you’re a bad developer. There are plenty of great developers who don’t care about these things and still do great work. This is also not to say you can’t follow my preferred approach with git. I did it with git for a decade and a half.
Wow, that does sound like a big improvement. My git commits are, when not forced to be otherwise, very sloppy, even though I’d prefer them to be neatly self contained. But as you imply, there is friction to making these in git, while making lots of unrelated changes in a single coding session.
As an example of another ‘unnecessary’ switch, Pip with venvs was also completely solving all my Python dependency problems. I just needed to copy paste a few lines from my README to create and populate the venv, and remember to run pip and Python from that venv. And run pip freeze after package installs or upgrades. No problem. But switching to uv was a huge life improvement. No more copy pasting (‘uv sync’ does everything and even that isn’t needed) nor remembering extra steps and everything is fast so I’m never waiting and forgetting what I was going to do.
I could’ve been GP talking about pip but still I would be missing out.
I still see people clinging to cvs because it works for them (netbsd why?), which I respect but don’t understand/believe.
Fish don’t know what water is.
So.. I’m gonna give jj a go and trust life will be better again.
You'll love jj, as you've already been able to see the the light with new tooling like uv.
Jj is to git what uv is to other python tooling.
Check out jjui as well, which makes jj even better. https://github.com/idursun/jjui
https://mise.jdx.dev is equally as revelatory as jj and uv
mise is awesome. Put it off far too long after a bad first experience. Now I'll never go back to asdf
Mise really is awesome. We are coming into a new golden era of tooling and I want everyone to experience these things.
IMO this is all being driven by Rust. jj, mise, ripgrep, fd, bat, eza, delta… all of the best of breed tools these days seem to be coming out of that ecosystem.
I'm fairly confident you're going to love it. I'd read a few tutorials, but what finally clicked for me was just asking Claude what commands to use, and then explaining why.
Jujutsu finally made me understand git, after ten years of git use. It removes enough of the magic that I now know exactly what the commands do, and how the data model works.
> It's also very helpful if you're the kind of developer who makes lots of unrelated changes in a single coding session, and wants all those changes to be in parallel branches that can be reviewed and merged independently.
Git has worktrees for that. If a parallel change is done in a separate worktree, it can be built and tested independently, which, I guess, is important for kernel developers, who are the initial target audience for git.
I think `git add -p` and its friends are another thing I'd point to for this.
I've never really struggled too hard to get git commits into different branches for review, but if you've put unrelated changes into the same working dir, you'll want `git add -p` to sort them out into multiple commits.
Note there are corresponding `-p` flags for things like git-restore and git-reset as well.
Let’s say you’ve checked out a new branch and done a bunch of work over the course of the last two hours.
You’ve added a new feature. In doing that, you’ve also fixed four unrelated bugs, clarified the documentation for a method you needed to use, and rewritten another function to be more performant.
You could push this all as six commits on one branch. PR reviewers will now have to figure out what parts are related to what, or read each commit one-by-one. If someone wants changes to one of these commits, your entire branch is held up.
Or you could split these six different commits out to each be directly on `main` and make a PR for each of them. They can be tested in CI, reviewed, and merged in isolation.
The latter is far better. You can do it in git, but it’s not exactly fun. It is trivial in jj.
> You could push this all as six commits on one branch.
No, I'd probably put them all as individual branches to be reviewed, assuming they're truly independent changes. As long as they don't actually conflict it's not very hard to do this in git.
But this is kind of what I mean when I say that git fits my needs, because I wouldn't come across 4 unrelated tasks like this in the course of implementing a single commit's worth of features, unless I was having the world's biggest attack of ADD.
And if these things did need to get done to properly implement the feature to our team's quality standards, it would be appropriate to be included in that feature's PR as well.
I'm sure it's a nice tool, especially for those who work in domains where it takes a long time to land your commits into the main branch of development, but a lot of this sounds like solutions to problems that we don't all have.
This is the kind of example that I find insightful. I know I can do it with git and it's not hard. It's not fun, but it's not hard. So I will disagree with anyone who says that it's impossible with git.
But it is great to know that it is trivial in jj. That's a reason to try :-).
It’s definitely not impossible! It might not even be hard these days (git has accrued a million flags and features), but it is definitely friction. And it might not seem like much, but it adds up. And all of that friction stops you from even considering things that might genuinely be hard.
Everything feeling like it’s a slightly uphill battle isn’t something you always notice until it’s suddenly gone.
jj tends to use -i for 'interactive' to do what you do with -p in most git commands.
It is in fact a great tool, jj makes doing this even easier.
I'm sure it's great but I don't have the problems with git that others apparently do, so as to make it worth switching to a whole new mental mode of source code management.
At least uv solved real problems I was having with Python package management, but for my own personal usage git is 99% aligned with what I need.
I didn’t have problems with git either.
jj just makes all of the stuff I liked to do with git easier and faster, and lets me do some things you can’t do with git.
But you should use the tools you want to use.
jj also has worktrees, though they're called workspaces.
They're useful, but they're not really what your parent is talking about, your parent is talking about a workflow where you realize you've want to break up your work after the fact, rather than setting out to do it that way from the start.
Nice commits can really tell a development story that makes reviews easier. That said, I want all teams to squash merge their feature into master after tests pass. One commit at the end, and one commit to remove in case of an issue affecting customers related to the release.
A very, very large problem at five out of six companies I have worked at is casual code improvement and refactoring. Devs would say, "we will address that minor and unrelated thing in a separate PR" - one that never comes. At one company, a single PR could address unrelated fixes and it was encouraged to "take out the trash" on the code. Unrelated metrics added, logging improvement, or code simplified, or test robustness improved, etc. That company had vastly better code. Easier to read. Easier to maintain. Easier to observe. And easier to test.
I'm honestly baffled by this. You're a proponent of dealing with chores as you encounter them during development rather than putting them off til later (great! I love this!), but also when that PR lands you want it all squashed down into a single commit, which presumably will have a message like "Implemented Important Feature, also did a bunch of unrelated work".
That sort of workflow is ideal for making sure you've got a set of isolated commits each looking at a single subject so that when someone is reading through the history later they can quickly see where something was introduced or why, and jj is perfect for doing that because it makes crafting those commits so much easier.
What if you could have the best of both worlds?
Where the developers at that other company could “take out the trash” amidst one PR, jj makes it trivial to carve off each of those fixes into their own separate PR. It’s no more work than making them separate commits.
People don’t do this in git because it’s a hassle. So you either get cleanup that never comes because each branch needs to know in advance what work it’s going to do or you get omnibus PRs that do twenty different things.
There’s a better way, and it lets you have clean history and developers can fix things they run into along the way.
> That company had vastly better code.
It's very frustrating that what IMO is the inferior approach was producing better results in those teams!
It's because what you see as the inferior approach involves less effort and friction for the developers.
When you are told to separate general code improvements to another PR, or worse, to not do them, and create a Jira task for them so they can be adequately prioritized, it just saps your will to do so. You just won't do any improvements that fall outside the scope of the feature, because even just thinking about the hoops you have to jump through to get work done is mentally draining.
I don't know how but we need to get developers out of thinking of PRs as the smallest possible change unit. This is literally what commits are for, you do a chunk of work, and you have a commit which describes that chunk of work. If you've got cleanly isolated commits then when you come to reviewing the PR (or changeset as I'd much rather see them called) and someone questions the wisdom of including that particular change you can either modify the commit to satisfy the questions, or just pull it out of the changeset into it's own for later review without blocking merge of the wider feature.
Coincidentally jj makes this process much easier than it would be with git, it will very happily let you shift commits around between different branches, edit commits in place and cleanly rebase those edits onto subsequent commits, or split a messy commit into two commits that makes sense.
The UI may be cluncky in the PR page, but I just use rebase, edit the commits, and force push the whole branch. The PR is the unit from the business perspective, not from my computer environment. I don’t mind creating two PRs for stacked changes, then once the first is merged, rebase from the main branch and publish the second one. Comments can be used to explain the link.
I think the confusion and angst comes from when someone has multiple unrelated commits, submits it as a single PR, and is then REVIEWED intermingled all at the same time!
If people instead reviewed commit by commit until the PR HEAD, the code itself would tell a story, but best of all - the story would then be obvious!
> It's because what you see as the inferior approach involves less effort and friction for the developers.
I can see that.
From the other side of the PR though, it involves significantly _more_ work from a reviewer.
The "red tape" of separating commits and opening separate PRs should be removed by the team.
The effort of separating commits and opening separate PRs is minimal once you're comfortable with the tools.
I encourage colleagues to be comfortable with these workflows, because a reviewer's time is generally no less valuable than their's.
Reviewers don't want to navigate 33 tiny PRS either.
The best way of getting changes is through is simply sitting down and talking with the reviewer. Most of these small PRS, splitting things, creating elaborate stacking systems are just technology hacks around a social/process problem. I've seen people make more of a mess trying to split pr's up where they are so fine grained its silly and actually had dependencies on commits they didn't realise they had which reviewers then had to resolve. Literally anything to avoid talking and working with people. People are trying to turn a tightly collaborative process and turn it into isolated single work units with no collaboration that just need a rubber stamp.
> Reviewers don't want to navigate 33 tiny PRS either.
As opposed to one 33-change PR? Yes, absolutely yes they do.
I probably don’t have time to review a giant PR like that. If I do, I feel guilty asking for fixed in one part when 31 of the changes are great. Why are we holding up all these improvements for one or two small concerns? We can merge and just fix those later. Except that never happens.
I probably have time to review eight one-liners. My other coworker has time for five. After lunch I can quickly check out another seven. Over the course of the day all 33 get reviewed and merged as time allows.
Jj makes this effort vastly easier. Nearly frictionless.
Great. As I said elsewhere on this story, I'll suggest jj to colleagues who struggle with git.
The approach didn't drive quality. 1 team cared about their code and felt empowered to improve it, the other didn't.
The majority of people care about the quality of their work when they are starting off. Not caring is a learned behavior. When you repeatedly get reprimanded for it, you learn not to care as a way of protecting your mental health from taking even more damage.
paperwork and red tape results in lower velocity. sometimes this is what you want.
note this is also true in software engineering.
I follow this methodology but I just ... use git? The hard part of making multiple small PRs is usually wrangling someone to actually review them, or following whatever process management has decided is necessary.
The hard part I always found without jj (and Fig before it, when I was at Google) was managing a DAG of small changes.
What's your git workflow for a change that depends on two other in flight changes? (More generally, of course, this can occur in an arbitrary part of one's change graph - which is usually not too deep, but at least in my experience, occasionally is.)
Having good tooling for this unlocked workflows I didn't know I was missing, and switching back to git when leaving Google felt like losing a limb.
> ...making small and tightly-focused branches that are easy to review and merge is very important.
> This is where jj excels. Especially if you find yourself often doing large chunks of work between convenient checkpoints, but you still want to create commits as if this work was all done in tiny and discrete chunks.
This is exactly how I like to use git. Sounds like I should be recommending jj to colleagues who struggle with this approach.
I tend to `git add -p` and create different commits that I put on different branches if they are unrelated.
Doesn't feel painful in git, but I'd like to see an example doing that with jj. Maybe I'll try.
jj lets you do this with changes that are related.
I can do it with changes that are related. But I understand that jj makes it trivial, which is not a life-changer but it's nice. Does that sound about right?
Can you go into some detail on how you do this? I use jj but it sounds like you make a change and then split it up into more changes after the fact, which I'm not familiar with yet.
I have a bad habit, even still, of just working in one monster commit.
It took me a few months to realize that I could use jj split to move specific files to a different commit. And then I'd sometimes squash them into related commits, rebase to move them around etc...
But I just discovered interactive split, which lets you move specific lines and sections from different files in a commit to a different commit. So I've been using that a lot more recently to organize the changes more thematically.
Ultimately I should try to become more diligent with adding a new commit any time I start doing something different - it's dead simple to do and even less friction to organize later - but I suppose that I'm not that inclined because the interactive split makes it so easy to do it all later that I just stay in the flow of my monster commits.
Everything is possible with jj.
just started making much more use of jj split to move split
One really nice trick you can do is:
- Create multiple topic branches, one for each thing you're working on.
- Now you probably want to work on item A while having B and C all available, so make a single merge commit (jj new a b c) to build on top of.
- Create further commits on top of that, while you're working.
- When you're cleaning up (ideally often), use squash --to or rebase --after to move those commits back the branch they belong on. This does not invalidate the merge commit; you will, effectively, have multiple branches checked out at once.
EDIT: This is apparently called the 'megamerge workflow'.
Megamerges are awesome, but what really makes them magical is when you start using `jj absorb`, which automatically splits and squashes your commit down to the nearest unambiguous commits and leaves anything that doesn't have an obvious place to live
Interactive split?! Very interesting, I'll look into that, thanks!
EDIT: I love it.
Haha, fantastic.
Also, if you didn't discover yet, you can use the arrow keys to unfold the different files and sections. Then select what you need, split the rest to new commit.
As you know, even when just using the most basic functionality (new changes, merges, rebases) of jj, it's amazing. But then you just keep discovering other features and workflows - none of which require any incantations - that make it that much better. And I'm sure I'm still only scratching the surface of its possibilities.
And, as I keep saying everywhere, jjui just takes the whole experience to another level.
If you end up with a commit that's a number of small fix-ups to files edited in earlier commits, `jj absorb` will push changes up to whichever (mutable) commit last modified the file they're in.
The way I understand it, its for those who can't help but to fix B while working on A and want to make sure that they are two different PRs? The way I do it is after B is done, I just create a new branch and point B to A in the PR. A is pointing to dev/master/upstream. Does JJ make this workflow more convenient?
Yep! And it makes it convenient even when you need to make changes or add new commits onto A. B is constantly stitched up to remain a child of A and incorporate its fixes.
It also makes it simple and easy to split B and A apart such that both their parents are `main` if they’re unrelated.
You can also go hog-wild. I was working on a big refactor recently. I made independent changes A, B, C, and D (each one to three commits). I then wanted to work on code that assumed all of these commits were available, so I made a merge commit E that combined them. I then made changes F that depended on that refactor, so was a child of E.
Managing this was simple. If I needed to make updates or tweaks to A-D, E and F were updated to incorporate them automatically. `jj absorb` even meant that doing these types of changes was almost zero work: I could make a bunch of changes and the tool would know in which parent commit they belonged.
None of this was merged in yet. When I was ready, PRs went out for A-D. When they each merged into `main`, E became a no-op and was discarded. F became its own PR. This is something I never would have done in git because having multiple threads of unmerged code is a colossal hassle.
You'd probably like reading about the megamerge workflow. I and others have linked some articles in a few comments here already.
>I don't have any productivity issues with git, like... at all. It's not like I spend an hour running git commands every day.
Agreed. Having used SCCS, CVS, Subversion, VSS, Perforce, Clearcase, Accurev (the weirdest of the lot), Mercurial and Git, I'll move when the market decides what has critical mass and my job needs it.
jj feels a bit like learning a Dvorak keyboard and then being in an office of qwerty. Jobs want git, my colleagues know git, I'll be asked a question about... git. Using git has been the lowest version control churn in my brain for a decade, which is nice.
I still know git. I work at entirely git shops. Nobody has ever come to me with a problem I’ve caused, but people have come to me to ask how to switch.
I am also the guy who gets asked with doing crazy git things when the need comes up. I have another post here where a tricky and slow filter-branch that our company needed to do on a repo was a simple and obvious three-liner in jj.
I mean, Git was a massive improvement over those other systems though. It was absolutely worth learning, and worth pushing companies to adopt. (Same with Mercurial, obviously.)
> You HAVE TO move to bim, the better vim. It's very similar to vim, but different enough that you have to learn new stuff
Wit the added bonus that "bim" won't remain popular enough to sustain its development for long, so "bim" users have to switch to the fork "bbim" in 2 years, that won't remain popular enough to sustain its development for long...
I use like five commands total in Git and the rest is driven through my IDE which handles everything else. People who tell me Git is hard or recommend alternate tools are living in a different world.
It's totally possible to have no issues with Git, e.g. if you are only using it for small or slow moving repos.
There definitely are lots of big issues with Git though. I dunno how many jj solves but it doesn't seem unreasonable to suggest people move to a better system.
And I totally agree! And if jj works better for you, please use it!
My point is just that I am yet to find a convincing example that would suggest that jj would improve my workflow. If people find it hard to stash a change, I don't tell them that they shouldn't get their shit together and not use jj. But I don't find it hard to stash a change, so why do I feel like jj evangelists try to convince me that something is wrong with me?
This is the age-old issue of how you describe something that has enough small improvements to result in one big one. There's no single thing that someone can say to convince you to switch to jj, because there's no single thing that you can do with jj that you can't with git. It just has a thousand little improvements left and right, that make it a joy to use.
That just results in less friction, and in you doing things with it that you couldn't be bothered to before. Yes, I can switch branches in git by stashing my changes, and then I can try to figure out the five-levels-deep stash stack, but with jj I just switch between branches without finishing working on them, because it just works and is easy.
Yes, with git you can technically have five branches open at the same time, and stash work to switch between them to work on one thing or the other, but it's so hard and finicky that you end up never doing it in reality. Or, you can say "I never need that", but is it that you never need it, or that your tools make it so hard that you just subconsciously never do it? For me, it was the latter, as now I'm switching between branches ALL THE TIME, just because it's easy.
> because there's no single thing that you can do with jj that you can't with git.
- jj undo, jj op restore, and jj --at-op to reset or view the repo at a previous state
- create multiple directories (workspaces) backed by a single repository at different commits
These are the only things I can think of off the top of my head that you can do with jj that you can't do with git.
> create multiple directories (workspaces) backed by a single repository at different commits
This is `git worktree`.
They don't work with submodules but submodules are a disaster that should be avoided anyway so probably no big loss there.
Ah, I was not familiar with that feature in git. In that case, I'll swap out jj workspaces for sparse checkouts :-)
Apparently git sparse-checkout exists! I'm going to stop guessing features that git doesn't have now.
> to reset or view the repo at a previous state
What about `git reflog`?
That lets you see previously checked out revisions. Jujutsu keeps track of all previous repo state. In git, you can pull a new remote branch, delete that branch, and push the deletion. If you want to get that branch back, git reflog will only save you if you checked out that commit. If you didn't, you're SOL. Jujutsu will let you undo the delete operation, restore the repo to a state where the branch existed, or view the repo at a state where the branch existed, and create a new branch at the same revision that the old branch was before it was deleted.
That might be cool, actually.
FWIW, I cheated on a previous job for months by working 10% or so, then faking git commit data to spread it out across the week before sending a PR.
Thank you "git commit --amend --no-edit --date xxx"