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.
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.
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.
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.
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.
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!
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.
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.
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 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
- 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
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.
>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.
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.
Jujutsu repos only exists locally. There are no Jujutsu remotes, only git remotes. Repo properties unique to Jujutsu (repo history, stable revision IDs) are not pushed to git remotes.
I've started, in this thread, likening it to Plato's Cave - people who are just using git are just seeing/using a blurry facsimile/projection of actual reality/possibilities. It's not a perfect analogy (jj is, I suppose, the projection, and people are chained in the cave), but it makes the point that there could be so much more.
The matrix or people happily in a cult would be similar analogies. Perhaps even Stockholm syndrome...
It is evangelism. Unabashedly. I think the Plato’s cave analogy is honestly apt.
Git was fine. I used git for ages. I loved its underlying model, and I think it’s brilliant. But it has a lot of sharp edges and a lot of tasks that are painful or frustrating, and people either opt to restrict themselves to a tiny subset to avoid the pain or they carefully curate a workflow that mostly does the job over the course of years.
Suddenly within a week of trying a new tool you just… don’t carry any of that any more.
It’s like going from GOTOs to structured programming with encapsulated functions. People made many useful programs with GOTO. Some people were more principled than others, but we all got by. But it turns out that functions and loops are a way better mental model for control flow than GOTO. Nobody had to switch and there were plenty of holdouts. But eventually the benefits were impossible to ignore.
People like Dijkstra evangelized structured programming because they “saw the light”.
In good approximation, I would say around 0 minute a week. I guess it could happen that it takes a few minutes, but I don't remember last time it happened.
yea i am really confused by all these issues people are saying they have myself..stashing merging etc...and what is crazy is we migrated large teams of programmers who's only experience with version control before git was microsoft TFS and and everyone is easily and happily chugging along. Now most of these people use gui tooling for managing git, but so what everyone gets it done with no issues or complaints with many projects big and small. we even have non-devs using it. I get jj might have some life improvements for some niche groups of people but i really have to wonder if they are the type of people who purposely make their life more complex
Forget between team members - it's an issue just for my own local dev purposes, when I'm working on multiple changes/PRs at the same time and want to test them all out before they get merged to main in the origin.
It's certainly a solid improvement in the space of VCS UI, but beware that jj has some current limitations which might prohibit switching, especially for the git power users.
Lack of gitattributes support precludes git-crypt and git-lfs usage or anything that needs filters; line ending settings will get ignored, making Windows interop a little less smooth; etc.
Also note that auxillary tooling, such as git-annex and git-bug, becomes second class, i.e. no oplog integration and they might mess up your log with internal-use commits and heads.
Oh thanks for the heads up! I use jj but it would be a disaster if I committed something using it and git-crypt didn't work, and the secrets were all plaintext.
I use gitattributes quite a lot (lfs, various diff engines, and export-ignore). Thanks for the heads-up, jj looked very interesting but I'm not going to give up gitattrivutes.
"You will be hard-pressed to find someone who stuck with it for a week and decided to go back to git. "
Reporting in. Doesn't mean I will not end up with jj eventually, but so far I always went back to git after a while.
For me it is the staging area and the workflow it allows. Most people hate it and love jj because it does away with it. That is just not me. I don't see the staging area as a hack that was necessary to overcome some superfluous technical limitations but as a workflow tool.
Could I change my ways? Sure. jj just did not provide enough benefit for me so far to do it, but we will see. I am still open to give jj another try some day.
The staging area is a hack in that it is its own unique concept that doesn't work with any of the rest of git's tooling without needing special, inconsistent flags for commands to target it (git stash, git reset, etc.).
I use a staging area with jj! I would surmise most jj users do too. It's just a real, honest-to-god commit in the repo instead of a special snowflake.
# do some work
…
# prepare a new, empty commit if you don't already have one
jj new --no-edit --insert-before @
# move changes into it; repeat ad nauseam
jj squash --interactive
Since it's just a commit, all your tools work with it out of the box. And you don't need to stash changes when you jump around between branches.
Yeah, I regularly have MANY "staging commits" at the same time in jj, in which I just throw ideas in that I don't want to pollute or yet know how to integrate with the other ideas. I use squash, split, interactive split, rebase etc to move lines and changes around etc...
When I eventually figure it all out, I can tidy it all up into completely coherent, discrete, sequential commits that make it look like I knew exactly what I was doing from the start, then push to remote.
Conversely git staging around stashing was always an absolute, terrifying mystery to me. I could never remember what was stored where, and would consequently end up losing work, repeating work, and, most of all, just making massive commits that make no sense.
I was sold on jj from essentially the moment I found it, but I remain routinely amazed at how much more powerful (yet simple!) it is than I realized.
In my mind, in some sense, every commit can simultaneously be a stash, staging area, part of a branch and more. Because you can easily create endless branches on top of, beside, before, as merges of, etc.. any commit. And it all automatically rebases, conflicts often auto-resolve (or at least don't block you immediately).
I really don't know how to best describe it. But there's a reason that many people are quite literally evangelizing it in every post that comes up - who does this for git, or anything?
Just give it a try. And, even better, use it via jjui
The commit message is the least useful part of all of this.
It is just SO EASY to move, merge, split, rebase, squash etc commits, and even just individual lines and sections from within commits (via split).
And it's all even easier via jjui
I don't think there's anything any of us can really say to convince those of you who are constitutionally skeptical about it (or even allergic to it) why you should try it... If all of our fawning and evangelizing isn't enough to get you to at least give it a fair shake, then there isn't much else to be said.
It's just that I'm fine and also the features seam to amount only to a change in usage not in actual new features. In this case if you want to use commits instead of a stash, then you can do that just fine in git.
It's also that I want my commit hashes to be stable (because I cross-reference them) and it sounds like they wouldn't in jj, because it is kind of rebase-y?
Commits in jj have both a commit has (which is unstable) and a commit id (which is not).
Generally you use the id, not the hash.
And yes, everything you can do with jj you can also do with git. It’s just that most people don’t, because it’s much harder with git, involves rebases and changing hashes, and so on.
It's the new workflows that it enables via making existing features vastly more accessible (though there are some new features, such as merge conflicts not being a showstopper).
I would call `jj absorb` a new feature (new if coming from git, I believe the idea was taken from hg). It moves diffs into the closest ancestor that touched those files/lines. Very useful for addressing reviews, then just `jj absorb` and watch them get squashed into the correct location.
This gets even better with octopus merges (known in the jj community as "megamerges"): open up 5 branches at the same time, address minor tweaks in any number of them, and `jj absorb` the content into the right parent branch.
Can you edit commits by adding on only parts of your "staging" area?
Because that's my workflow. I produce a few focused and semantically coherent commits that I'd like to apply to the codebase, where each keeps the codebase in a working state. I might be working on more than one thing at once, but I know which commit each set of changes should "live in."
An append-only log of commits is decidedly not something I want. Is jj amenable to this?
Extremely amenable—my workflow shifted to match your description after I started using jj, because it makes that easier. Conceptually, it’s not “edit commits by adding on only parts of your staging area”, but “edit commits by only moving or splitting parts from another commit”, and since the working copy is a commit, it is handled by that general case instead of being it’s own thing.
Thanks for the clear and concise summary. It is helpful for some workflows.
For me the staging area being a snowflake is the feature. It is a special temporary singleton commit if you will, but I can rely on its temporary and singleton nature. As that it should have a different interface from regular commits.
I found it hard to stick to due to pre-existing workflows too. I find I'm often switching between branches in git, and our CI depends on pushing to specific branches - I constantly found myself a bit lost with jj about which underlying branch I was on and where that would get pushed to. I did Steve's tutorial and came out of that really liking the concepts, but still unable to map it all in my head when it came to pushing my work to a remote.
On the other hand, tools like jj seem great for people who spend a lot of time refining and perfecting every commit so that their repo history is pristine. I’m not one of those people. My git hygiene is atrocious. I have git halitosis. But functionally, it’s fine. Everything is in the git history and is recoverable. That’s about all I care about.
Also, I don’t have the capacity to juggle a lot in my mind. If someone does, then sure, jj may facilitate that. But I don’t need such facilitation.
Jujutsu might look like it's for people who want to perfect their commits and juggle a lot in their minds -- but in my case (and anecdotally in others' too) it's because it lets us easily do things which would require that mindset/ability with plain Git.
In Git, I need to keep in mind all the different things I'm working on and which branch goes where, or I wind up with a monster PR with a chain of commits that mix everything up enough that it's even more effort to pull the PR apart to be reviewable. Ask my colleagues how I know that :P.
With jj, I don't feel that I'm putting more effort into organising my changes -- quite the opposite -- not least because there's very little book-keeping involved in parking something that's in progress or coming back to it later. And when I come to ask for a review, I've got separate changes I can push as smaller PRs, and I can easily pull the right sets of changes out to make each one reviewable.
I think the biggest improvement is that if I just need to look at something else for a couple of minutes then come back, I can `jj new main` to switch and not bring my changes with me then I can `jj edit <oldref>` to get my working copy back into the state it was in before, with all my old changes.
I'm pretty sure none of my colleagues have started using it (although they're free to) but I can confidently say (because I've asked) that they've noticed the difference in my work and prefer the more focused changes.
I used jj for several months, then eventually went back to git.
The biggest killer was performance. jj operations took several seconds for me, whereas git is instantaneous no matter how big the project. Maybe this is fixed now.
But also honestly I felt like there was a bit more mental burden to using jj. When I switched back to git, it was like a weight off my shoulders. Maybe that's just due to the decade of constant use though.
The relevant question is, why would a stranger take time out of their life to evangelize an open source VCS tool?
What other tools do people do that for? Not many. I see the most similar language and behaviour with regards to uv, which similarly revolutionized/simplified python tooling.
Likewise mise, which makes it similarly frictionless to install and manage tooling and their versions across projects and system-wide.
The evangelizing for these things comes because these tools start to heal the trauma that came from immense friction. We want others to be similarly liberated.
For those who say "I don't feel friction/trauma with git, legacy Python tooling etc", I can only say that you're living in Plato's cave/the matrix/in a cult/with Stockholm syndrome and just don't know what you're missing out on.
Definitely applies more to uv than jj, but jj is still immensely liberating. To the extent that people are willing to evangelize it to no gain of their own.
There is a 3rd group (probably mostly gamedevs) who think it seems like a great idea and really want to try it, but are blocked waiting for git-lfs support.
Every time jujutsu pops back up on HN I check to see if they've added it yet. Not yet! But they are slowly getting there:
Reason I like git is because I use like 2% of its features. I don't fall for propaganda that I need to use bisect and co. 99% of git commands I call are aliased to 3 characters, so it's dense terminology doesn't bother me.
Bisect is a very cool feature that I used once over 5 years ago. The collection of git tools that I use often is very small. I've found that the more experienced I got with git, the less I found myself in scenarios where I needed git's more complex tools.
May I use how often and why you use bisect? I don't remember the last time I needed bisect. Well, I also don't work in linux kernel sized repos, so there is that.
Not that often, but not seldom. Its useful if you have a bug, that isn't obvious where it comes from, but easily testable. Then you just write a test and let git figure out where it comes from. The test doesn't need to be automatable. I also used it for firmware that needs to be flashed and the bug effected in an LED blinking incorrectly. I still saved a lot of time.
I see. Maybe it's just what kinda of code and/or how I write it, usually pretty clear which abstraction isn't doing its thing. From there, it's either clear what is the bug, or git blame will reveal what changed.
This has a much higher chance of succeeding if you maintain strict boundaries and isolation which isn't always possible.
I relish the day I get to use bisect. It's like, finally I get to use all this version data I've been collecting.
I don't understand why anyone would say you have to use it. It does a very specific thing, namely finding the source of a regression between two commits. If you need it you'll know.
Yeah this is good if you, for some reason, rebase then merge without fast forwarding. I never understood doing this. If I'm going to be ignoring those commits when bisecting then they are useless commits just using up disk space IMO.
Does it work well with a classic merge workflow? I haven't worked that way (without rebasing) for a long time.
The whole point of bisect is to make it quicker to find a point in a potentially long list of commits. Finding the feature first then bisecting within the branch is only going to make it slower, and requires you to manually restart the bisect.
I use the commit message to add ticket numbers to things to group commits.
I used bisect once in my life but it was extremely helpful. Without it I'd spend weeks trying to find regression. With bisect I found and fixed it in under 1 hour.
It's a command that is needed rarely but there's no replacement for it in some situations.
I am confused by this, isn't Bisect just some git ergonomics with respect to flagging a checked out commit as a success failure and then checking out the next midway commit depending of if it was a success/failure to enable classic binary search?
It might be slightly more tedious but couldn't you just do the same thing manually and it would add just a couple minutes to the search and you would still save weeks? I like the ergonomics but only use it once every couple years.
Counter point: I adopted it internally at Google (there's a backend for Piper, Google's monorepo Perforce thingy). I don't do my day-to-day work in the monorepo but I still jump in there once or twice a week. I adopted JJ because the existing frontend (Mercurial-based) is slow while JJ is fast.
It's nice, I really like it! I'll probably switch to it as my main VCS eventually. But it doesn't feel that important to me. Even though my main work involves quite a lot of annoying rebases which is where JJ really seems to shine.
I dunno I guess it's just that a) I've really mastered git and have a deeply-rooted workflow in it and b) despite my project involving annoying rebases, version control still isn't very high on the list of problems I have.
So yeah I'm basically bullish on JJ as a technology but I think movement from Git is inevitably gonna be slow and steady.
Is this one of the repos that uses Gerrit? Does JJ play well with Gerrit?
IIRC Gerrit was super picky about amending commits and I was worried JJ's laissez faire attitude about creating git commits on demand wouldn't jive well.
Beyond that, the jj, gerrit, and git butler folks are working together on standardizing the change ID concept so they all work well together without extra tooling. That's going to take a long time though. Think "store the change ID in the commit rather than as a trailer", that kind of thing.
Curious, I was under the impression that Google was all a monorepo, but your phrasing suggests that there are others. As my company is pushing for a monorepo, I'd love to know what causes someone at big G to not be in the monorepo. Thanks for any insights you can help me with!
I'm not entirely sure that "after using it I really like it and I'll switch eventually" is that much of a counterpoint :)
What really kept you from staying with it? It does seem like if your workflow involves a lot of nasty rebases you'd reap dividends from something like jj. I was also someone who'd mastered git (hell, I've written a git implementation) so I get having its patterns deeply ingrained.
There's nothing keeping me from switching except the activation energy cost. Almost every aspect of working in my area (Linux kernel) is painful so I'm constantly investing in tooling and workflow stuff. So usually I just don't feel like investing EVEN MORE in the area of tooling that's probably least painful of all.
So yeah this is still basically a recommendation for people to try JJ!
I'm sure if I did the proper time investment I would enjoy jj. I've heard basically only good things about it in casual conversation. So far, my experience has been that I tried it, immediately got really annoyed that it automatically adds every untracked file that's not gitignored to the current commit, and was advised that I might want to stay with git if that's a problem.
That said, I struggle a bit with learning version control systems (that aren't git, like, I never really wrapped my head around svn or darcs or anything until they invented git). Seems like everybody just wants to write about the cool new commands they can run now instead of conveying how the data model works, or what mental model it wants to encourage. I had the same issue trying to get into pijul a while back, couldn't understand how to conceptualize the current state of a branch if I couldn't point at a commit in a tree and say "that's the branch, right there".
Mental model: “everything is a commit”. Commits are commits. Stashes are commits. The working tree is a commit. The index doesn’t exist, because it’s unnecessary.
The data model is technically of revisions, which are stable across operations like rebases (which change the underlying git commit).
How to conceptualize a branch: as a bookmark of a specific commit.
Yeah, I think that extra layer of revision identity is giving my intuition trouble, like it's obvious it needs to be there but the consequences are still percolating through my head.
I used it for a little over a week, and switched back. This was a while ago, but the main reason was my neovim setup and flow never felt as good. Maybe jj has a good neovim plugin and diff mechanism now, though?
How fast I can deliver value is almost never gated by my VCS, so why should I try? In total I maybe lose a few minutes a month to VCS impedance mismatch, and I am merging on average ~4-5 PRs a day.
For sure git is hard to learn for beginners and there could be an alternative which is easier to pick up (maybe jj) but for those who know how git works internally and are proficient with it I don’t see it being worth the switch.
> How fast I can deliver value is almost never gated by my VCS
Oh, the number of times I do
git commit --fixup ...searches `git log` and pastes`... && \
git rebase -i --autosquash ...that same commit^...
I waste several half minutes several times per day in periods!
Another time-consuming thing is context-switching from a feature branch with staged/unstaged changes. If you're good with worktrees, you can largely avoid this, but the way I work with worktrees, I instantiate them as subdirectories to my repo's parent, which clutters my directories unless I make space for this as I clone the repository, which I haven't got used to.
I deliberately avoid too complicated git workflow aliases because I hate being stuck without them.
I am definitely in the camp of "I'd switch to jj the moment I give myself time to try it."
In the meantime, running git commands does sometimes take more time than it needs to.
You can interactively rebase after the fact while preserving merges even. I usually work these days on stacked branches, and instead of using —-fixup I just commit with a dumb message.
git rebase -i --rebase-merges --keep-base trunk
This lets me reorganize commits, edit commit messages, split work into new branches, etc… When I add --update-refs into the mix it lets me do what I read are the biggest workflow improvements from jj, except it’s just git.
This article from Andrew Lock on stacked branches in git was a great inspiration for me, and where I learned about --update-refs:
I use colocated mode, so Git tools see a detached HEAD but otherwise continue to work. I don't normally use the IDE for creating commits or moving around the repo, but I'll still use it for conflict resolution and for exploring history.
But how do you split commits? Command line tools for splitting commits suck because they force you to look at one hunk at a time without getting the context from the rest of the change.
I used it for several days and went back to git; it doesn't really provide that much for someone already proficient with git, and you need to know the underlying mechanism well for when something goes wrong anyway (just like with any other abstraction).
yes, I haven’t managed for a week, but I tried to Jujutsu and gave up. It is too complicated for no visible gain. Plus, there is no infrastructure supporting it (in the end, you store the stuff in the crippled git repositories). `git commit --amend` and `git rebase --update-refs` together with few scripts (e.g., https://git.sr.ht/~mcepl/git-fixup) does the same, and I am still with true git.
i've never considered testing out any of the new version control systems i see from time to time for the simple reason that i already know git, everybody else already knows git, git already completely handles everything i could conceivably want it to do (and a bunch more stuff that i will never touch), and perhaps biggest of all, i can't tell my manager that i want our whole team to migrate our code into (new thing) which everybody will have to learn for no reason.
serious question as somebody who has never even looked into what jujutsu offers - unless you're a solo dev with some free time, what exactly is the selling point here?
edit: i didnt realise that its just a layer on top of git so that basically answers my question, fair
I have noticed this trend too. I don't know if I'm simply too smooth-brained for `jj` or what, but I forced myself to use it for a month to give it a fair shake and simply couldn't grok it. I gave up and went back to rawdogging git. So there are at least some of us out there.
> You will be hard-pressed to find someone who stuck with it for a week and decided to go back to git.
I’ve tried jj on three occasions and I always get confused by something and just bounce. It hasn’t clicked for me.
I’ve only tried it solo hobby projects. For which git is perfectly tolerable.
I have many many many complaints about git. But jj doesn’t move the needle for me.
Reading this post I am extremely annoyed. Almost every single section is “but more on that later”. It’s still far more complicated than it needs to be.
I also really really really hate the jj log rendering. The colors are a sea of barf. Bolding the leading character that represent uniqueness is stupid and adds noise. The username being second is dumb, I almost never care about that. And the bright neon green (empty)(no description set) is such bad spew. Kinda nit picky, but blech.
Jujutsu suffers the same thing Git suffers. Every god damn blog post that tries to explain how simple it is just my eyes gloss over and think “this is too complex for me to care”.
Some people love to tinker and fine tune their tools to behave exactly like they want. I am not one of those people. I hate it. It does not spark my joy.
Most people use the default for most of their tools. It’s why Google pays Apple over $20 billion per year to be the default search engine. Because defaults matter.
FWIW, I am also one of those people who does not want to fine tune their tools. I want my tools to work well out of the box.
> I’ve only tried it solo hobby projects.
If I can surmise a bit, this is probably why it's never felt "worth it". I'm guessing that for a hobby project you don't really care that much about crafting small, easily-digestible PRs or keeping a clean history. `git commit -a` every now and then is good enough, and that's entirely reasonable.
For team projects, jj becomes a much bigger deal. I find it invaluable for splitting up a day's worth of work into small, parallel, easily-reviewable changes. While working on one feature I might run across a dozen other things that should be fixed, improved, or otherwise changed. Bundling them into one giant PR is bad practice and causes reviews to take much longer.
Carving those up into single-purpose PRs that can be reviewed in seconds and tested independently is super helpful on a project with multiple teammates. But that's not something that really matters or that most people care to take the time do to in personal projects. Hell, it's something a lot of people punt on a lot in git due to the extra burden of doing so.
I use Sapling at $DayJob. It’s pretty good and I largely like it. jj seems much less friendly for most operations. Although jj’s merge conflict stuff is appealing (in theory).
But you can't make a default that everyone will like. The best overall interface will have someone hating it. If you want to always use defaults, you're basically guaranteeing that you'll hate some genuinely great software one day.
I can’t spend a dozen hours fine tuning every single piece of software I come across because it might maybe possibly be great if I do.
If I used Git as part of a large team I’d perhaps spend more time with jj. But for solo projects it adds even more complexity to Git rather than reduce. So I don’t feel strongly inclined to spend that time.
I tried it and didn't switch. The funny thing is I immediately recognised that it was forcing me to use git in basically the same way I use it anyway. I have more than 15 years experience with git at this point. I never had to do the "delete repo and reclone" thing after the first year. In other words, I actually understand git, so I don't really need Jujutsu.
I also already use very good tooling for git, namely Magit. IMO Magit is a much better git frontend than Jujutsu. It guides you down the right path but doesn't take away any of the power of git at all. It's quite remarkable.
Maybe I should recommend jj to some of my colleagues, though. Trouble is I'm already on the hook for helping them with git, but I don't have the experience with jj.
> In other words, I actually understand git, so I don't really need Jujutsu.
This is kind of a poor take. By all means use what you prefer! But understanding git and knowing the "right" way to use it doesn't make jj obsolete.
I am (or was) a git expert. I’ve used it since pre-GitHub. I’ve written a git implementation. I know (or knew) the interface inside and out. I haven’t deleted and re-cloned a repo in as long as I can remember.
jj is still leagues better. Things I want to do and know how to do in git are dramatically faster and easier. They require less mental overhead. They’re less error prone. And I get superpowers with workflows that are super useful but wildly impractical in git.
This isn’t even really opinion at this point. Any task you can give me in git, I can with almost near certainty give you a shorter and more elegant alternative with jj that is an intuitive and obvious interaction with its core primitives.
A month ago our company split off a division. They needed to take a specific repo and sanitize out all of the parts that aren’t relevant to the new company, going back to the beginning of its commit history. You can do this with git. It wouldn’t be fun. I’d have to spend a lot of time reading the filter-branch manpage, and people have written countless wrappers of varying quality that try and make it a bit more ergonomic.
It took me like ten minutes to come up with:
declare -rA paths=(
…
)
# for every commit that touched a file
# we want to excise
for id in "$(
jj log \
--revisions '..' \
--no-graph \
--template 'change_id ++ "\n"' \
"${paths[@]}"
)"; do
# restore those paths’ contents from the
# the empty root commit; this happens
# entirely in-memory without having to check
# out each revision into the working copy to
# do file operations so it is FAST
jj restore \
--from 'root()' \
--to "${id}" \
"${paths[@]}"
done
# remove any commits that are now empty
# (except for the implicit root commit)
jj abandon \
--revisions 'empty() ~ root()'
Three dumb, simple commands that I already use every day: list some commits, copy file contents from one commit into another, and remove some commits from the tree.
Not only was this more or less obvious to do, but it was exceedingly fast to perform on a pretty highly-trafficked repo due to not having to thrash around in the working directory with checkouts.
> Magit is a much better git frontend than Jujutsu. It guides you down the right path but doesn't take away any of the power of git at all.
jj is more powerful than git. It’s not simply a dumbed-down alternative. By having a more carefully chosen set of primitives that compose better, you gain a lot of abilities that are technically possible with git but never used in practice due to the complexity. The above is IMO a fantastic example of how and why.
And you can still do all the normal git things too.
Why? They’re sharing what they prefer in response to someone claiming that everyone who tries the other thing comes to prefer that. They’re offering a reply in context and not in the slightest saying everyone should follow what they do.
> By all means use what you prefer!
That’s exactly what they’re doing.
> But understanding git and knowing the "right" way to use it doesn't make jj obsolete.
Which they haven’t claimed at all.
You seem to be objecting to an argument which hasn’t been made.
"In other words, I actually understand git, so I don't really need Jujutsu." sounds like jj is for people who don't understand git. That's naturally a contentious statement. It implies that jj doesn't offer anything to experts, which seems easy to contradict given how many people with git expertise will talk about getting value from jj. At worst, it could be taken as a bad faith kinda put-down, like, suggesting at jj users are worse at their job than people who make do with git. I think it's totally fair to take push back against how that was worded.
1) actually gave it a fair shake - and are now evangelize it, and
2) people who simply didn't give it a fair shake due to
a) time restraints and
b) just not having an open enough mind to genuinely try it out
And then those who haven't tried it but
1) have never heard of it, and
2) are unwilling to try it because they are stuck in their ways (I view these people as being in Plato's Cave, or the matrix, or a cult - even though us evangelizers obviously sound like we're in a cult). They're in an even sadder position than those who tried it half-heartedly.
All such sorts of people are on display in this thread. I hope us zealots have been able to convince (hopefully via education) at least a few to try it out.
Fwiw. It took me 4 or 5 attempts of various lengths (2 to 10 days) before I actually sticked with it. I've hit unimplemented deal breakers and was generally uncomfortable a lot. Now I like it more than git, but I don't think the difference is large and stacking commits is a bit of a pass time for me. I do it more now because it's easier but I would just do less of it with git and no one would care much.
You can consider your perfect conversion rate now broken; I tried jujutsu on a personal project and found it to be a hassle without any upsides.
I believe your perception is flawed because the people who just try it and throw it away don't tend to talk about it because it's not popular enough to warrant even a twitter comment. This is the first time I got the impulse to share this but only because you claimed a rather silly 100% conversion rate.
Even though the parent comment was outrageously combative, you're probably right that it wasn't necessary to reply in a much milder, but similar fashion.
> 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.
When it's our first time, the tissue damage caused by the training can weight a lot to the point we could not even be able to sit properly in front of the desk during the first couple of weeks.
I started doing jujitsu a few months ago. The title of the article and this top comment had me bewildered until I remembered what jujitsu was in this space LOL
Usually I am pretty much against snowflake approaches in project delivery, setup should be the same for everyone, as people change roles across projects routinely.
The delivery is the repo and its commits. Recommending a setup is one thing, but enforcing particular tools has about the same level of appreciation among any senior dev I know as enforcing a keyboard model.
Those senior devs don't work on the kind of industry that I work on, enterprise consulting with high attrition of team members, and possible offshore teams.
This kind of industry work needs factory line mindset, there aren't special knobs for someone on the sidelines working on the software rolling carpet, which granted isn't for everyone.
Ok, so I'm just having trouble seeing how your situation is at all relevant to this discussion about the merits of jj. You simply have to use what you're assigned.
But how is that a relevant comment for this post about jj? People are talking about one thing and you're saying "I either can't use it at all, or am just happy with something else already". There was no discussion of the merits or shortcomings of jj...
All I was saying is that if a client requires you to use git, you could use jj without them knowing/caring - unless your entire workstation is under tight control, in which case, again, it's not relevant to the discussion
The main things that drives me crazy about jj is that all changes are always staged implicitly. This is what SVN did back in the day, and git was a huge improvement by staging changes explicitly.
I almost always have more changes in my repository that those which I want to include in the next commit. With git, I just add the changes I want. With jj (and svn), there’s not obvious way around it—you have to manually copy-paste changes outside of the repository before committing.
I think the workflow in JJ is a bit different in this case, you can try to have a base commit `jj new -m 'base'`, then create an anonymous commit on top `jj new`, then make some changes in the anonymous commit and when you are ready to send out a PR/MR you squash `jj squash` or split `jj split` and squash what you need in the base commit.
`jj commit -i` (or a lot of commands `-i`) and maybe `snapshot.auto-track="none()"` in the config, to a certain extent is what I use. I used to do the same with mercurial. In practice, I also use `absorb` a lot, that leaves unrelated files and chunks alone.
I feel pretty dense, because I still struggle to get my head around automatically adding changes to a revision. Sometimes, I'll make a change locally to a file that I'll use during the development process that I have no intention of committing. With regular git, I never stage that file so there's no danger of accidentally pushing my change to the remote repo, but it seems with jj I'll need to somehow unstage that change or something to prevent this. Perhaps it's just habit, but I feel more comfortable explicitly saying what I want to commit rather that defaulting to everything. Or have I totally misunderstood jj?
In jj you tend to use `jj split` to break changes apart. The selected bits become the first revision, the remaining bits become the second revision.
I tend to do a bunch of work then split into small, bite-sized revisions. Often I split something out to a parallel revision (e.g., a separate branch) if it’s an independent thread of work like a bugfix elsewhere or a documentation fix. This is an obvious one-liner in jj but a bunch of annoying branch-switching and stashing in git.
You can also use a `git add`-style workflow. Create a new revision with `jj new`. Do it again. Make your changes, then `jj squash -i/--interactive` to select the bits you want to include. Keep making changes and squashing into the previous commit you’re building up until you’re happy. Conceptually just think of @ (the current revision) and @- (the previous revision) as the working copy and the staged copy, respectively.
jj's auto staging isn't always desirable. I feel jj docs and evangelists should make clearer that it's easy to turn off by default, by adding this to ~/.jjconfig:
I usually work, then do `jj split` to review changes I want to make commits to. This generally makes the workflow look like `git add -p`.
A decent mental model is that the top-most commit isn't generally going to get pushed up anywhere. It's like your working copy but also you get stashing "for free" (change back to main to make a new commit? All the WIP stuff stays on that branch instead of being carried over to main!)
No that is correct, and also a habit I am trying to break. The reasonable argument is that we should stop running code with untracked state. Either the changes are important and should be committed or not. Otherwise you are recording code versions that never truly existed during development.
Where this gets extra sticky for me is tooling which refuses to distinguish repo wide config vs a local only version. VSCode being a huge offender where there is only a ‘launch.json’ and no ‘launch.local.json’ suitable for per host customization (eg maybe I am already running something on port 8888, so I need to map it to 9000, that does not mean a quirk of my environment should be committed).
The logic is once you are ready to commit you delete all of the debugging stuff. Otherwise you are committing an illusionary state of the repo that only existed by manipulating the stage.
I am a black kettle here as I frequently commit individual lines amongst a sea of changes, but I do appreciate the theoretical stance of jj.
A tool like pre-commit really helps here: it'll run against the staged files, before committing. Your CI tool really ought to be testing the commit too, at which point having a clean commit locally isn't necessary for correctness, only for avoiding needing to re-do your work.
It's really important to catch bugs early, because it's a lot more expensive to fix them later -- even if only as late as in CI before merging.
Just to be clear, jj makes it really easy to carry this sort of thing as a separate patch, so while it may be "committed," that doesn't mean it has to go into what you send upstream.
(though for debug printfs in particular, the Right Thing is proper logging with log levels, but I myself love printf debugging and so sometimes don't do that either. Which is why carrying local patches is nice.)
There is no Right Thing here. Practicality beats purity. The product (a snapshot of the source tree) should do what it needs to do, but getting there is not the product. It can be if you want it to be, but there is no upside to that.
If you want this workflow, you can treat jj's `@` (nominally equivalent to git's HEAD) as the git index, then at commit time, manually squash changes to `@-` just like you would with `git add --patch`.
When I've asked about using a jj stage, that's the workflow people pointed me to, as described in this tutorial [0]. I've not tried it, nor jujutsu, but I felt it worth pointing at a concrete example to allay or stoke fears of people on the fence.
There is no such thing as `.jjignore` files [2], [3].
`--no-snapshot` is not a flag, neither on `jj edit` nor in general. Additionally, it doesn't make sense conceptually --- `jj edit` changes the working copy, which will then propagate changes to the selected commit every time you edit a file.
If you want to check out an old commit without amending it, you `jj new` on top of it. If you end up making changes that you don't care about, you can `jj abandon` them.
I've been using jj for a few weeks, and recently made an auto-commit-message script for it[1]. Just today I started working in an old git repo and thought that I should port the script to git. It turns out that jj made the script trivial because of immutable commits, and mt script would need to automatically do that with git: I use a rebase/amend/clean history workflow at work, so I would need the script to determine when to commit or when to amend. It's obviously possible, but I don't want to expend the effort - I just re-cloned it with jj.
It's amazing how quickly I forgot about the commit vs. amend papercut.
I’ve been trying unsuccessfully to convert my team to jujutsu. I feel like what would be great is a page that really shows some common but complicated operations in git and how much easier they are in jujutsu. Something like the elevator pitch here but expanded on without the depth of Steve’s tutorial.
Maybe what I need to do is do a demo so people can see and ask questions.
> I feel like what would be great is a page that really shows some common but complicated operations in git and how much easier they are in jujutsu.
What I find isn't that common git operations are easier in jujutsu. They're not; sometimes they're slightly harder, due to the impedance mismatch with the git backend.
Rather, what git makes easier are operations that are next to impossible — or at least highly inconvenient — in git, and which therefore next to no-one does. That makes it harder to explain, because you're telling them there's this great new workflow that does stuff that... they don't think they need (they have workarounds), and the notion of which triggers their ick reflex if they're good at programming.
I do understand that point, but to me it sounds like "you should use jj because it's a lot better at solving problems you don't have".
If there are really common use-cases where git is annoying and jj is great, it shouldn't be that hard to explain, should it? If you can say "remember how in the last few days you struggled with this? Jujutsu solves it", then I'm happy to try.
If your argument starts with "imagine you are in a team that looks like X (but your team does not), with a project that looks like Y (but your project does not), and now imagine that you need to do this thing that you have never done before...", then maybe I actually don't need jj?
> "you should use jj because it's a lot better at solving problems you don't have"
It is more like "you should use jj because then you won't have a lot of problem with git that you'd need git to solve"
> it shouldn't be that hard to explain, should it?
It is not. There are plenty of example on this page. For me the biggest one is stacked PR, jj makes it trivial since it tracks the change sets and not commit ids (which are immutable). So you can work on any level of the stacked PR independently, once you're done run "jj git push -r '(trunk()..@ | @::)'" and it will update all the remote branches accordingly. Another feature that works great with stacked PR is that you don't need to solve conflicts right away. You will see a marker in the "jj log" and you can solve it later down the road.
Also another great feature is the operation log, you can just rewind your actions. F'd up a conflict resolution? Just go "jj op undo". That goes for everything, including file changes and rebases. Want to go back the state it was 15 min ago because you didn't like what you did? Merge to the wrong place? "jj op undo"
Adding to that there are hundreds paper cuts that jj fixes, like:
* Simpler mental model for local change, no git stash/add necessary.
* Simpler commit process, you can just work and use "jj describe" whenever where git forces you to write message before creating commit (again because commits are immutable).
* Starting a work is much easier, I can just go "jj new" away without caring about detached head. Nowdays I just use branches (jj bookmarks) for git compatibility reasons.
* Revsets are amazing, much more powerful and expressive than git logs, and since the UX is more consistent you can always work with set of rules that expects a revset with "-r".
Ofc, you can do all of that with git, but it just works better, easier and more consistent with jj.
> It is more like "you should use jj because then you won't have a lot of problem with git that you'd need git to solve"
I don't have a lot of problems with git that I need to solve, that's the thing. And I don't get why people keep trying to convince me that I do. It's about me, my opinion should have some value, right? :-)
> It is not. There are plenty of example on this page.
The problem is that many examples, to me, sound like it's exactly like git but the author of the example doesn't know how to do it in git. For instance, you wrote a whole paragraph about "undo", as if git did not have that feature. Isn't that exactly `git reflog`? Turns out I had a need for it 2 times in the last 10 years, and it just worked.
> Simpler mental model for local change, no git stash/add necessary.
I can deal with git stash/add without feeling like I'm thinking hard. This is a class of examples that makes me think that jj is for people who are not comfortable with git.
It feels like the people who use jj tend to somehow get stuck on detached head with git, and that's a big problem for them.
Again, I'm not saying that jj is not cool, and probably I should try it. But I see a ton of comments that really, really sound like "I can't believe people still do basic arithmetic in their head: they should get a calculator and they would see how superior it is. With a calculator, you never make those frequent and annoying mistakes like 3+5=9 again! Plus you can do 403985/13 easily!". And when I say "I usually deal with basic arithmetic that I do just fine in my head, and I don't actually frequently make mistakes like 3+5=9", I feel like I sound like an elitist.
I can't remember the last time I had to do something "hard" in git. So it sounds like jj may make something simple slightly simpler, at the cost of dealing with a new tool.
> I don't have a lot of problems with git that I need to solve, that's the thing. And I don't get why people keep trying to convince me
You are in *forum* in a post *about jj* saying there is no reason to use jj. We are just interacting as you'd expect in forum. If you don't see any reason to use anything else and don't want to hear anything about it steer away from these posts.
No one is trying to convince you personally, we are just discussing the tool. Go use git and be happy.
I sincerely won't read anything else past that line.
> You are in forum in a post about jj saying there is no reason to use jj
I honestly am not. I am genuinely interested. Everytime there is a post about jj I'm about to try, and then I see a comment like yours, saying "with jj you just use the intuitive syntax `jj git push -r '(trunk\(?.\).x#@ | @@.^)'`, which is a lot simpler than knowing about this weird concept of stash" and it makes me think that maybe, I'll try again next time.
If you are interest in learning you should take people words on good faith, my comment on "jj git push -r '(trunk()..@ | @::)'" is complex operation to update many stacked PRs not compared to local git index operations. Also in my comment I mentioned revsets and the "-r". It is a language to query logs which you should've check if you genuinely wanted to learn about it, which you don't.
Just look at your answer, you literately ignored everything I mentioned and took what I said out of context just to bash on jj for some reason.
Actually, to be blunt (you're already offended anyway): what I'm saying is that I find (personal opinion) that many evangelists here (you included) don't sell jj really well. If so many people are actual fans of jj, there must be something there. I'm just struggling to find it between the "you're probably too dumb to understand a stash so you should use jj" and the "let me explain to you this great concept that jj has which allows you to undo changes in a way that sounds like git cannot do exactly that".
So no, I'm not criticising jj :-). And I'm not convinced I need it.
> And I don't get why people keep trying to convince me that I do. It's about me, my opinion should have some value, right? :-)
I have no problem whatsoever if you don't like jj or use git or whatever else you like.
Let's paddle back:
- you are in post about jj
- you comment why you don't like jj
- I comment why I like jj
- you post that.
Like, what is your intention here? If you don't have good faith into trying to understand the tooling, how would it fit your workflow and deflects everything with "I know git, git works, don't tell me to use something else" what is your goal here?
My problem is not your opinion on the tool, is how you approach the debate.
I have been asking a few questions, and I have received many answers quickly. Which is usually great, but between the "those who don't use jj haven't seen the light and must be in a cult" and the "jj is better because git is impossible to use everyday", it's honestly been harder than anticipated :-).
I don't especially like git. I stuck with mercurial for a long time.
But that was ten years ago. Now git is kind of hard-wired in my brain. By and large, it works well enough.
It's not really clear to me that Jujutsu offers a significant enough of a benefit to spend the time re-wiring my brain, never mind dealing with the initial setup (e.g. the unreadable colours, setting up some scripts/aliases for things I like).
Yeah I find abstraction layers suck a bit. Same with miso and uv (for python). They don't "just work TM" and now I have 2 problems. By just work I couldn't install miso onto a fresh Ubuntu without 404 errors and uv kept bitching about my pyproject file instead of just working or trying to fix it.
Some of these tools do just work. E.g. nvm seems to just work and is much nicer than raw node installs.
There's really no cost to trying it out on an existing project with other people. It natively interoperates with git. Nobody needs to know you're using something different, and you can always fall back to using git commands if you need to do something you haven't learned yet.
But does Jujutso solve those workflows, or does it add a layer on top that you need to figure out on top of what you already know about Git?
I don't run into any issues with Git during my day to day. Some things could be a bit more ergonomic but that's because I'm too stubborn to learn or set up some aliases or look up how to do it.
Following this tutorial, I can't say jujutsu feels like an improvement. More complicated log output (I use a shorthand (think "git log --oneline --graph" but with a custom output format to also show absolute and relative commit time and author)), automatic staging (that can be turned off but why should I when it's already off if I don't use jj?) while I use "git add -p" through an alias, and the rest is just push / pull / checkout / reset.
jj solves one particular issue I have with git and github: truly easy (not just 'possible') rebasing of branches and editing commits which are not heads. You need to commit conflicts to solve this properly, which is something git really doesn't want you to do.
This has been the case, but it's worth noting that very recently, support for theming with the 256-color palette has landed too https://github.com/jj-vcs/jj/pull/6763
Some programs set the background colour and some don't. For example pamix sets the background to black, or tmux's statusline, or ngrok. It's not really possible to define one terminal scheme that always works.
I think it's reasonable to assume that all 12 colors that aren't black or white will be readable on the default terminal background. I sympathize with those for whom that isn't true, but please do find a different theme in that case.
How would that work with applications that hard-code the background to black (or some other colour, like tmux's green statusline)? If you change the colours to work on a light background then those break.
You have to choose what you want to break. Once you start pulling on one thread lots of stuff start to unravel. It's really not a simple matter of "choosing a better theme".
I've spent a long time looking at this, and my conclusion is that there is no safe default that will work for everyone, other than bold/reverse and 256/true colours and setting both the foreground and background.
If you hardcode the background to a fixed color, the 4-bit palette for foreground colors is generally to be avoided -- instead, use the 8-bit (256 color) or bigger palettes.
If I understand correctly, you're in the situation where you've chosen your 4-bit colors so that they render properly on an application which (incorrectly) uses an 8-bit background color but a 4-bit foreground color, and also that the colors don't render well on the default background that's part of the same 4-bit palette.
I think you'd be a great candidate for jj's ability to customize colors.
I don't choose anything; I just use the xterm defaults. Some colours inherently conflict. Blue on white is fine. Yellow on black is fine. Yellow on white is not. White on white is even worse. There are many combinations that don't really work brilliantly. Some applications set background colours, some don't. pamix, ngrok, and npm are examples of applications that hard-code background colours. Configuring the terminal to use text colours that work well with both the default white and hard-coded black (for some applications) is hard.
That I need to spend a bunch of time setting all of this up (among other things) is exactly how this thread started.
I think that comparison is unclear and unfair. The core is that instead of:
jj rebase -r @ -B abc
the recommended Git alternative is:
git rebase -i abcd1234
# move the last line to the desired position
This is a process I use heavily, and one of the rare cases where I prefer the Git way: less cognitive load (I don't need to memorise options b/s/r/d/A/B for `jj rebase`) and the interactive editing of the history feels simpler (especially if I move several commits).
I've used jj for a few weeks, but switched back to git. I'm fluent enough with Git so I never struggle any more. jj mostly felt nice, but the added value was not enough to replace years of expertise.
to be an excellent improvement over anything git provides, including rerere. It's the first patch queue for git implementation that actually worked for me, which in turn makes github PR UI somewhat bearable.
I occasionally use -r, too, but most of the time it's better to
This article convinces me that what he wants to do is easier in jj, I just don't understand why he wants to do it.
My quick summary is that in one case he's try to avoid "extra" commits and in another case he's trying to re-order some commits. In my usual work flow, both of those problems would be handled by the git-rebase-squash I do after the feature works.
Thanks. This is a really good one. It outlines an operation that would resonate well with most developers and clearly demonstrates how much simpler, easier, and faster this is in jujutsu vs git. I think most devs just wouldn't even bother to do it in git, they'd leave the test out of order and call it a day.
Yep, I think that's right. As the other reply says, this is part of what makes it hard to explain. If you told me jj makes it easy to rebase all the time, I would ask: why do I want to do that? Now I don't even think about it, I just rebase all the time.
I think you'll probably like the next part I'm going to add to this post, then! I'm sure it'll get posted separately, but it'll also appear at the bottom of the page linked here when it's up (hopefully soon, as in the next few days).
Don't want to sound old school, but git works perfectly fine.
The learning curve might be a bit difficult, but afterwards everything makes sense. And let's be honest, you just need a few actions (pull, add, reset, branch, commit) to use it in 95% of the cases.
git rebase -i lets you reorder commits easily, as long as they don’t have conflicts. If they do, you’re in for a rough time. I struggle to see how a vcs could help with that, even if I’d be happy to be proven wrong.
One cool tip to help with conflicts is the revert trick. If you have a conflict that you need resolved earlier in your commit chain, you can commit a cleanup that hides the conflict, revert it instantly and reorder the commits with interactive rebase to insert the revert first. It’s a bit hard to explain without an example, once you’ve tried it you will understand.
> as long as they don’t have conflicts. If they do, you’re in for a rough time. I struggle to see how a vcs could help with that, even if I’d be happy to be proven wrong.
A vcs can allow you to commit conflicts and then commit their resolutions whenever necessary. This has been pioneered by darcs (IIRC) and jj also allows that.
If you work in a team you need to understand rebasing and squashing, unless you can convince team to never use these features.
A lot of people are religious about rebasing, "clean" commit history. But it's pretty much incompatible with several devs working on a single branch. I.e. when you work on something complex, perhaps under time pressure, git habits bite you in the ass. It's not fine.
As soon as you have the situation of multiple people working on the same branch, forbid force pushes at all times. But it's better to avoid that in the first place, all work should be on its own branch at all times.
For larger features we often have a feature branch with merge requests for various task branches. Limits the review sizes as well.
Of course, you can also then consider to use feature toggles, so there's no feature branch but just your main branch.
Years and years ago I worked on a team where linear commit history was required so every time a merge happened you had to manually rebase and push to Bitbucket. I put a PR in halfway through the sprint, rebased it a dozen times as everybody else's work got merged, and had nothing to show during review because nobody bothered to check mine and the only coworker I had in the same physical office was out on vacation.
When I interview for new roles I always make a habit of asking how work gets done to avoid shops that engage in these types of shenanigans.
A lot of the time, multiple devs working on a single branch can be avoided via different decisions made upstream about work that needs done. If my job included more git wrangling as one of my daily tasks I would probably hate my job.
I've been using jj for two weeks now and it's kinda exciting, because for the first time I'm comfortable with using version control just via the command line.
With Git I always had to use a GUI (preferably Git Graph in VSCode) and launch all operations by right clicking items, but jj was simple and consistent enough that I could just start using it after reading Steve Klabniks tutorial.
The thing I'm running into right now is that I should really learn the revset language, so that I don't have to constantly copy paste ids from jj log
I'm in a very similar situation: been using git for a long time, but anything more complicated always via some kind of UI (often intellij).
Been using jj without significant issues for about a month and been super happy to be comfortable using the cli and slowly ramping up to more complicated operations.
The documentation still assumes a lot of inherent knowledge which sometimes makes it a little difficult. I love seeing blog posts like these and hopefully some more in depth resources will appear over time. Steve's guide is good, but there are still gaps for me :).
Next I want to learn some more revset language and become a bit more fluent with rebase operations. I love the more simplified cli, conflict resolution and op log!
I had the same copy paste problem, now I use jjui (https://github.com/idursun/jjui). The daily operations become even smoother, it looks like I’m flying over the log.
Is it just a git frontend for people who are confused by git?
It says it abstracts the backend, but it's not clear how something so git-influenced will have abstractions that work with something like a centralized system like Perforce or Piper that has auto-increment numeric commits.
Some of the design decisions are also not great. Working copy as commit means you have no quality control. The whole point of a commit is that... you commit it. So now you need a separate repo or filter to remove the worthless changes from the ones that you actually intend to commit. Bad commits polluting git histories is already a big problem.
The biggest problems with git IMO are it scales poorly and it encourages commit pollution. Presumably jj isn't trying to tackle those problems, which is totally fine. But I am confused about which problems it is tackling. That's why I'm wondering whether it's just a frontend that the author finds more to their liking.
> It says it abstracts the backend, but it's not clear how something so git-influenced will have abstractions that work with something like a centralized system like Perforce or Piper that has auto-increment numeric commits.
Its Piper backend honestly works better than the Git backend. Which isn't a knock on the Git backend, but the impedance mismatch is worse there.
> Some of the design decisions are also not great. Working copy as commit means you have no quality control. The whole point of a commit is that... you commit it. So now you need a separate repo or filter to remove the worthless changes from the ones that you actually intend to commit. Bad commits polluting git histories is already a big problem.
Sorry, but saying this implies you haven't tried it. :-)
Jujutsu commits aren't equivalent to Git commits. They're implemented with a mixture of Git commits and the Git working tree, yes (if you use the Git backend!), but when you see 'commit', you should read 'named diff'.
Jujutsu also has a notion of immutable commits, by default meaning (roughly) commits which have been pushed upstream.
You can and should rewrite the un-pushed commits to clean up history prior to pushing changes upstream. jj makes that much MUCH easier than rebases and history edits could ever be with git. Most of my nontrivial jj work involves at least three or four commits at some point, everything from experiments to documentation branches, which with git I would have needed to awkwardly fit into stash or inconvenient throwaway branches.
Thanks, can you link me to their perforce backend code? I don't see the string "perforce" or "p4" anywhere in the code and it's not coming up on search.
> You can and should rewrite the un-pushed commits to clean up history prior to pushing changes upstream.
My concern isn't just about pushing upstream. What I'm saying is that the typical change to a file shouldn't be committed to my local repo until I indicate that it's ready. The FAQ suggests this isn't possible and that you should work around it by using a separate branch and then merge into your target branch, which is a pretty ugly workflow.
Their GitHub branches are a mess of auto-generated strings, which suggests to me that this problem isn't just an abstract concern but is a form of technical debt that the jj devs are currently piling up.
> What I'm saying is that the typical change to a file shouldn't be committed to my local repo until I indicate that it's ready.
Yes, it's "committed", but that doesn't mean all that much. I have the fsmonitor feature enabled that continually watches my repos as I edit files in them. This means that over the course of a coding session, the revision I'm working on has probably pointed at dozens or more ephemeral underlying git commits. Those are only kept around for the purpose of the evolution log, which lets me look back at my edit history throughout the day even without having interacted with the repo.
When you're ready to "finalize" your work, you have two common workflow options: you can `split` the out parts you want to keep as one consistent commit, which dumps the rest in a subsequent revision. Spiritually this is equivalent to `git add -p`. Alternatively, you can create an empty "staging" revision before your "working copy" revision and `jj squash -i` pieces you're happy with into there. In practice, many people use both. I generally do the former for new work, and the latter to make changes to earlier work.
Thinking that `git add -p` was absolutely a deal-breaker is the main reason I passed over jj for so long. I care deeply about maintaining a clean commit history with small, isolated, and individually-tested changes. I thought jj would make that harder due to not having a staging area. I was wrong. It is actually far easier to be principled about your commit history with the tools that jj gives you.
I don't mind the workflow of selecting what you want at a later time, but my concern is with having changes visible to the local repo database automatically.
If you like it that way, that's cool with me.
But to me personally I'd rather have the filesystem doing snapshots and not comingle fs snapshots with VCS. I can nuke an fs snapshot in a single line of bash if I accidentally leak something, and fs snapshots don't contribute to the slowness of a big repo. But those are problems in regular git and would be even bigger problems in a version of git that treated VCS as if it were an fs snapshot system.
But like I said if that works for other people that's great. It would probably work for me if I were a consultant working on a lot of small repos. I don't think it scales to a bigger repo unless the jujutsu repos are completely destroyed every few days, which is essentially what happens to CITC client working copies at Google.
> I can nuke an fs snapshot in a single line of bash if I accidentally leak something, and fs snapshots don't contribute to the slowness of a big repo.
Leaking isn’t really a concern; none of these ephemeral commits ever leave your local machine. I have used jj on a very large monorepo with > 10yr of commits, and I never noticed a performance issue.
jj was literally created by Googlers at Google for the purpose of being used at Google. I have no idea how that’s going, but it makes me suspect that it works better at that use-case than you’re giving it credit for.
> What I'm saying is that the typical change to a file shouldn't be committed to my local repo until I indicate that it's ready.
You're thinking in very git-specific terms. JJ is just different in many ways so such comparison doesn't work well. I didn't get it from explanations before, but trying it in practice, it's a non-issue. It may be easier to just give it a go.
> My concern isn't just about pushing upstream. What I'm saying is that the typical change to a file shouldn't be committed to my local repo until I indicate that it's ready.
jj doesn't have the concept of a non-committed file.
instead you don't track the HEAD as the tip of the branch (@ in jj), you track HEAD^ (jj: @-) or something earlier and since jj rebase isn't dumb as a rock you can relatively easily keep the working set of changes (as in, multiple commits/WIP branches; not necessarily the index or the working copy) on top of what you are pushing and squash or advance the branch (bookmark).
I'm reiterating because it's a very important and super confusing for advanced git users: there is no uncommitted state in jj and yes, it does make sense, but you need to stop thinking in git.
Can't this be a security risk as there might be secrets that should never be recorded by jj (You can probably configure this, but I want my software to only do what I tell it to, not do everything until I tell it not to.)
How does that work in practice, is there a daemon running constantly and monitoring? How does that interact with other users and changes from other computers?
Secrets and client data. Regulated environments are very serious about client data. They can't go into source control either.
You can't change example.com/foo to point to example.com/baz and have it exist in source control. So to make the change you have to ignore every file that contains that string, test, then unignore. Or you have to make sure to absolutely remove every reference you've added to the merkle tree. Or you have to completely nuke your source control database after testing.
If you buy a used laptop hard drive from a remote worker at $BIG_REGULATED_CO because you want to find exploits, you're probably happier if they run jujutso than git because there will be a higher probability of finding secrets, client info, and other things that are forbidden from going into source control.
Every jj invocation stores a snapshot before doing anything else - thus anytime you run jj, you tell it to store a snapshot in the local repo.
I don't see a problem with secrets TBH. .gitignore is respected if that's what you have in mind. Otherwise store your secrets out of tree (you probably should with git too, anyway.)
this is not as important as you're used to. I've been there, I've used git since early 2010s and yes autostaging was preposterous at first. in practice, it's barely relevant: the workflow changes from 'I pick what is tracked' to 'I pick what is pushed'.
I'm not sure if objects won't get pushed accidentally. I would place the boundary at object creation not upload. Also you might copy the repo, then you also might leak an accidentally created object.
It's more that there isn't really a big difference between the workflow of
# you're on
staging area
@ commit A
# make some untracked changes and console logs you don't wanna commit
git add -p && git commit # select only what you want
vs
# you're on
@ empty commit
| commit A
# make some local changes (which are tracked in @)
jj commit -i # select only what you want
You're still "in charge what gets tracked" if you treat the last @ commit as your local playground, exactly the same as the staging area.
The only difference is that you can use exactly the same tools to manipulate your "staging area" as other commits.
The only difference being that you can manipulate your staging area with the same tools as any other commit.
Wait, what happens when there is a multi-GB file laying around and a jj command is being invoked? Does it start to scan it or is there some threshold. What does it do with cyclic hard-/sym-links?
I'm far from being an evangelist. I'm trying out jj in one repo out of several and it's far from perfect - but it is useful though, and it does make sense. The requirement of being able to not commit everything by default is simply not necessary from a logical standpoint; if anything, it is itself evangelism that the git way is the right way. The truth is both work just fine, which has been my point all along.
> My concern isn't just about pushing upstream. What I'm saying is that the typical change to a file shouldn't be committed to my local repo until I indicate that it's ready
Since you brought this up, I've noticed some people seem to work this way but I've never found anyone to ask why they do this.
I get the idea behind clean, readable git logs with nice consistent messages, but isn't that what rebase/amend is for?
To me, the status of my local git log is mostly irrelevant, the thing that really matters is the commit(s) I submit for merging.
Also, probably for related reasons, I've never truly understood why git has this whole separate staging concept...
I also work this way. A commit is really about, well, committing that these are exactly the right lines of code I intended to write. I also read them in another program, so it gets easier to not think that I already know the code and skip over. I basically start from the clean state (no changes) again and approve the changes line-by-line according to whether they fit the domain model of the program, whether they are in the right places, are the right amount of complexity/abstraction, if they are readable/understandable. I typically also add/remove comments at that point, because I've now tried to read the code with a "second pair of eyes".
I also use this to compare the changes to the commit messages. Ideally the changes follow from the message, not the other way around.
While a VCS is also useful for adding a time-axis to code, for me the main selling point is, that it's adding causality to the code. You can ask 'Why is this code how it is?'. (Of course causality is a side-effect of time.) When you don't curate the commits you completely loose this feature. (You can still ask, but the answers will be confusing and need way more work, which for me amounts to manually comparing commits to grasp the big picture and intention.)
> rebase/amend
When you messed up, you can of course change commits, but this has a danger of creating a version that was never there or mixing history, for example combining a single physical change, that are multiple logical changes. In order to test that the code at least works I use ```git rebase --exec="make -C build distcheck"```, not sure if that is common.
I also don't use MS GitHub -style merges, I find them inferior. (I also don't use MS GitHub, but that's for another reason.) I think they try to make merges the actual unit of change, which is stupid, because that is what a commit is for. To me merges are about semantic grouping of commits, i.e. maintaining a tree of commits representing the logical evolution of the code.
Yes the staging area is exactly designed for this approach and I don't like people telling me I'm holding it wrong and don't need it. To use a metaphor, I think the staging area is like my desk were I'm producing stuff (I'm not producing code, I'm producing changes). Committing is about having produced a complete opus and then cleaning the desk. Cleaning isn't seen as annoying, it is important for the mind to process completing/validating and then starting afresh. Stashing is in this metaphor about switching to an alternate desk. This is different from putting the opus aside and having also a clean desk. (I think that is what Jujutsu wants you to do?) Having the staging-area/stash different from commits is a feature, the fact that the stash is the same storage-wise is an implementation detail.
By default, the top (current) commit acts like a staging area and in fact is implemented as a dirty working tree in git.
Commits start live without a description, and can’t be pushed without adding one. jj commit names the current commit and creates a new empty, unnamed commit on top, which effectively is precisely git’s behaviour.
I implore you to try it. In practice the distinction just doesn’t matter, except it’s one less concept to keep track of, and jj allows you to make up other workflows if you want.
Sure, sometimes I want to work with a WIP commit and then I'm just doing that in git.
I think functionally of the stage is about the UI, so keeping it separate is kind of the point. I find that useful, as I can use both approaches, not sure how its better if jj can just use one?
> to ask why they do this.
This was what I was trying to explain. To me the feature is needing to manually mark every line to be committed. If I need to split the commit I'm already not paying that much attention.
jj’s workflow isn’t about being careless with commits—it’s about time-shifting that care to when it’s most convenient. You’d split the commit later for the same reasons you wouldn’t want your text editor to force manually marking each line to save to disk.
> I also don't use MS GitHub -style merges, I find them inferior. (I also don't use MS GitHub, but that's for another reason.) I think they try to make merges the actual unit of change, which is stupid, because that is what a commit is for. To me merges are about semantic grouping of commits, i.e. maintaining a tree of commits representing the logical evolution of the code.
Thank you for going into details, you helped me understand a few more details.
Let me describe how I think about it and why it might possibly conflict with your ideas.
I think the best way to describe it is as a series of approvals, each one being more important than the previous one.
The absolute lowest level of approval, writing the file to disk. You've made some changes and you're happy enough with them to at least save them in case you have a power outage or something.
The next level of approval is committing to your local git repo. This is code you're fairly confident you want later, even if it might not be perfect yet.
The next level is pushing your branch to the origin repo. Now you're saying that this is code you're willing to let other people look at.
The last level is merging this code into main/trunk/whatever. This is the final level of approval, this code passes all of our checks, it shouldn't need any more improvement.
Given this system, adding another level in there, for staging commits, feels pretty unnecessary.
I agree that merge commits tend to make for ugly git history, but I don't think that's particularly inherent to any of these systems of code integration, it's more a function of how much the developers care about the git log.
> change commits, but this has a danger of creating a version that was never there or mixing history,
I'm not sure what the benefit here is. I think I've basically ended up in a situation where I treat 'git commit' the same way I treated "save file" 20 years ago. My local commits/saves/edits aren't important, what's meaningful is the final unit of change I'm sending to the remote, and that unit needs to be deliberately constructed somehow. Whether that involves rebasing or amending or careful usage of git stage, it's an artificial unit you're creating just as much as the actual code changes.
> What I'm saying is that the typical change to a file shouldn't be committed to my local repo until I indicate that it's ready.
I was extremely hostile to this aspect of jj too, but I've settled on a workflow where @ (i.e. the change/commit referring to the working copy) is always the same, named ".WIP: …", and as such is clearly never supposed to break out of the local system, and can never accidentally get pushed down the log. (..my jj workflow is just blatantly copying my git workflow (with the same-name shell aliases/helpers even), but even with said git emulation I'd say it's nicer than git itself)
Still may be weird/undesired to have the local repo preserve the changes (especially annoying if it happens to find an unignored build artifact, though there is a size limit for automatic tracking by default), but it's also rather neat that you can dig out your old deleted printf debugging or whatnot later on if you wanted to.
If you leave the WIP head change blank, i.e. don't give it a description at all, then JJ will block you from pushing it unless you pass a specific flag, which is useful for sanity checking this sort of stuff.
You might be able to configure JJ to also block commits with certain descriptions as well, which might be useful in your case.
I specifically have the WIP head description include the name of bookmark that it's attached to, if any (emulating git's non-detached head), so can't leave it empty (and generally wouldn't want to, for easy way to identify it, esp. with multiple workspaces).
Some push blocker for certain commit name patterns would make sense to look into.
jj is a version control system. It is backend-agnostic. The most common backend is a git one, because git is so popular. This allows you to use jj on a git repository, allowing for individuals to adopt it without forcing their teammates to.
> Is it just a git frontend for people who are confused by git?
I used git since before github existed. I considered myself a git lover before I found jj. I will not be going back to git.
The thing is, jj is both simpler and more powerful than git, at the same time. People who are confused by git may like its simplicity, but I like its power.
> It says it abstracts the backend, but it's not clear how something so git-influenced will have abstractions that work with something like a centralized system like Perforce or Piper that has auto-increment numeric commits.
You already got some replies on this one, so I'll leave that to them :)
> Some of the design decisions are also not great. Working copy as commit means you have no quality control. The whole point of a commit is that... you commit it. So now you need a separate repo or filter to remove the worthless changes from the ones that you actually intend to commit. Bad commits polluting git histories is already a big problem.
This is an understandable misunderstanding. The right way to think of it is "the index is also a commit." A common way of working with jj is to do something like this:
Imagine I am working on adding some feature x to my codebase. I'll first make a change, and give it a description:
jj new -m "working on feature x" trunk
Working copy (@) now at: lqqlysul c6756b49 (empty) working on feature x
Parent commit (@-) : ylnywzlx 8098b38d trunk | (empty) foo
Now I will make a new empty change on top of that:
jj new
Working copy (@) now at: pxrvoron c823d73a (empty) (no description set)
Parent commit (@-) : lqqlysul c6756b49 (empty) working on feature x
Now, @- is the change that I intend to push publicly, but @ is my index. Say I add foo.rs:
touch foo.rs
Now, when I run `jj status`, jj takes a snapshot, and it now lives in @:
jj st
Working copy changes:
A foo.rs
Working copy (@) : pxrvoron ba7ad8c6 (no description set)
Parent commit (@-): lqqlysul c6756b49 (empty) working on feature x
We can see the diff here with `jj diff`:
jj diff
Added regular file foo.rs:
(empty)
So, let's say I'm happy with its contents. I want to stage it into my final commit. I can do this with `jj squash`, which by default takes all the diff of @ and puts it into @-:
jj squash
Working copy (@) now at: pxkqmsww 9f7e1ef2 (empty) (no description set)
Parent commit (@-) : lqqlysul 41dc1531 working on feature x
Now that change is in @- instead of @. we can see that by passing -r (for revision) to `jj diff`:
jj diff -r @-
Added regular file foo.rs:
(empty)
I don't have to move the whole change; I can do the same thing as git add -p by using jj squash -i, and only move the portions of the diff.
What's the advantage here? Well, because the index is just a commit, I can use any tools that I use on commits on the index. There's nothing like `git reset` needing to have `--hard` vs `--soft` vs `--mixed` to deal with index behavior: everything is in a commit, so everything acts consistently.
jj makes it very trivial to carve up commits into exactly what you want. It is far easier and more powerful than using the index for the same purpose.
> The biggest problems with git IMO are it scales poorly and it encourages commit pollution. Presumably jj isn't trying to tackle those problems, which is totally fine. But I am confused about which problems it is tackling. That's why I'm wondering whether it's just a frontend that the author finds more to their liking.
IMHO, commit pollution is more due to the pull request workflow than git itself, though I do think that git doesn't do that much to help you. jj can help with this kind of thing, but also on some level, it can only do so much.
You can do everything with git rebase, the question is how much pain are you willing to tolerate. If you've ever solved the same conflict twice and/or are aware of git rerere, you should try jj.
The impression I get from all the jj writeups is that the devs said "rebases are really cool, we should move heaven and earth to make them even cooler".
Git rebase is, frankly, a massive pain in the ass. It's worth doing if you care about it. I used a rebase-heavy workflow for a decade and a half. But having to drop everything you're in the middle of to fix an endless list of rebase conflicts sucks. And it sucks even worse when you screw up the conflict resolution halfway through and have to start over from scratch.
Rebases also don't play nicely with stacked branches. Branches based off your original changes don't get rewritten to be on top of the rebased change, so now multiple rebases are in your future.
It turns out that basically none of the rebase friction most people experience is necessary in practice. Rebases can be automatic, implicit, and conflicts can be fixed at your leisure and in whatever order that you feel is best.
jj also has a ton of other powers, but you asked about rebase :)
> JJ adds changes to random old commits instead of the newest one? Yikes.
jj adds changes to the current commit, which is the staging area, which is the index, which is the working copy. I get you don't like it, no need to be an ass about it, but denying that it works is just that, denial.
> > rerere by default
> Takes space and time.
doesn't take my time. I'll take it.
> > --update-refs by default
> Maybe sensible, don't need it most of the time.
No reason to waste time thinking when it's needed.
That wasn't what I meant, git --fixup is for changing earlier commits, you answered as if it didn't that's what I wanted to point out. (I tried to be sarcastic, because it was obvious that you didn't mean that.)
>> Maybe sensible, don't need it most of the time.
As in I want the opposite behaviour most of the time. That's why that is the default.
`--update-refs` was added in 2.38, in 2022. `--fixup` was added in 1.7.3. Like I said, git has grown additional features and workarounds. Knowing all of them is a lot of work.
> Working copy as commit means you have no quality control
The working copy doesn't get automatically pushed to GitHub or anything crazy like this seems to be implying. You review/curate your commit when you give it a description.
I tried jj a few days but I noticed my lazygit based workflow and scripts already make me productive enough not to deal with another mental model.
jj looks cool in general, I'd start with it if I were just going into this _version control_ thing but for most of us older folks, that doesn't provide enough motivation to change.
Eh, I can see how, if you use GitButler, the porcelain is fairly irrelevant to you, but a few days ago I decided to try Jujutsu, asked Claude how I could do a few things that came up (commit, move branches, push/pull to Github). It took me ten minutes to become proficiend in Jujutsu, and now it's my VCS of choice.
I still use Lazygit for the improved diffing, but, as long as you don't mind being in detached HEAD all the time, there's really no issue with doing that. JJ interoperates fine with git, but why would I use the arcane git commands when JJ will do the same thing much more straightforwardly?
Also, the ability to jump from branch to branch with all my uncommitted files traveling with me is a godsend. Now I can breeze between feature development, bug fixing, copy changing, etc just by editing the commit I want. If I want multiple AI agents working on that stuff, I just make a worktree and get on with it.
Not to mention that I am really liking the fact that I can describe changes (basically add commit messages) before I'm done with them, so I can see them in the tree.
I've been using JJ as my daily driver for ~6 months. It is so nice to be able to immediately rebase everything on top of everything, undo last command, restore a previous state, ignore and postpone conflict resolution. It is such a boost in productivity!!
Hands down one of the best features in jj is undo. In Git, undoing things is always a bit of an open heart surgery, with different commands depending on what you fucked up. In jj? Just a simple jj undo, done. That alone is a killer feature.
Then having the whole thing being kinda agnostic by design to the backend system, that is also a sweet spot.
I can comfortably use jj locally without any of my teammates knowing that I use jj instead of git.
Wondering the same. I've been using magit for a long time now and never use the git cli anymore. Going back to manually typing in commands doesn't sound appealing. The unified index/workspace non-stash-workflow does sound somewhat nice, though.
I am not a magit user but from doing a little bit of reading, all of these commands are essentially first-class citizens in jj. Spinoff seems to be `jj split` (or in most cases literally nothing because the default is to edit a new, empty revision off the trunk), absorb is probably `jj rebase` or `jj squash`, and the auto-backup is either the evolog (which tracks file changes that haven't been explicitly commmited) or the op log (which lets you reset the entire repo to what it looked like before or after any operation).
magit-merge-absorb is one of rebase (if that's what you want), or new (which gets you a merge when you specify multiple parents). The 'delete the branch' part of it doesn't really apply, because jj doesn't have branches; though you might need to delete a tag.
Note for other readers: jj also has a literal `jj absorb` command. That one does what you'd expect from mercurial, i.e. moves diffs from the current commit into the most recent ancestral commit where that file was changed.
Note also that jj does have branches, they are just anonymous. You don't checkout a branch or edit a branch, you edit a revision or make a new one. One revision with multiple children is a branching point, one revision with multiple parents is a merge.
You name them with bookmarks which are sort of like branch names except they don't follow along automatically as you make new revisions. You can point an existing bookmark at a later revision when you're ready to push new changes.
jjui is the best VCS TUI I've ever used. It's even smoother than magit, but I think most of that is because of jj itself. Spinoff and absorb are both native jj features (jj rebase and jj absorb, respectively)
Completely agreed. I've been evangelizing jjui even more than I do jj!
And, you're right, it's powers largely come from the underlying jj - it mostly just runs jj commands behind the scenes, parses the output, and displays it. But it's all so beautiful, seamless etc..
I can't wait to really dig into the big additions released yesterday in v0.9.0 - themes, vim-like leader key shortcuts, other shortcuts etc...
For a decade now I've been lamenting that git won the source control war. My complaints fall in two main categories:
1. The mental model is too complex: rebase or merge, detaching head, etc. A good UI can hide away some of this uglyness. It sounds like jj helps here.
2. Any source control in a terminal is just horrible, since you have no view of the state you're working with. I honestly never fully understood why developers have such religious elitism for the command line. Where would you draw the line on something having a complex enough state to need a UI?
The change to git that I'm hoping for is a GUI on top of a simpler wrapper like Jujutsu.
jjui is what you're looking for. jj is amazing and jjui makes it incredibly better. The maintainer is extremely receptive and responsive to feedback, and constantly moving it forward.
There's other TUIs and GUIs, but jjui is by far the best.
I’ve had a lot of success using https://graphite.dev/. Been pretty easy to pick up and slots right into our usual GitHub workflow. I end up using the vscode extension to manage it. Anyone have opinions how jujutsu compares?
It’s both. Their CLI has a lot of overlap with what you’d use jj for (rebasing, amending, etc), but does basically everything worse other than “creating stacked PRs”. Slower; refuses to work from a detached HEAD; gets confused more easily by changes on the remote.
I’ve never onboarded or successfully convinced a colleague to use jj. But roughly once a month I find myself helping someone escape git hell that would either be easier to solve or avoided entirely with jujutsu. I’m not speaking hypothetically: I will solve the problem locally while waiting for them to slog through whatever merge conflicts or stash/unstash dances.
But the graphite CLI’s stacked PR review is a killer app. Our team uses it, but treats the rebase-centric workflow as the price of stacked review, instead of a mental model to leverage. I end up using jj but with a helper script to set up graphite’s branch tracking whenever I want to submit a stacked PR.
I also was always a git CLI person. I’d do diffs and resolve conflicts in a proper editor, I wasn’t crazy—but I know that changing just my mental model is less work than it’d be to change the UI habits as well.
I reluctantly moved to git from mercurial when host-after-host dropped mercurial support. git is a user-hostile POS that I never got used to. A thousand needless complications including nightmare merges, and an inscrutable command interface.
JJ eliminates all the git bullshit. Some incredible features:
* Merges never fail as conflicts are a first class feature. You can come back and resolve them at any time.
* You can reset the state of the repo to any particular point in history as JJ maintains a complete operations history.
* You can insert new changes anywhere and deal with the repercussions at leisure.
I started with Steve's tutorial but found it a bit wordy. So I used Gemini to produce a simple guide for me with: "You are an expert at DVCS systems like JJ. Act as my guide for the system and answer my questions with simple cookbook-style recipes."
I have been using JJ for a month and am never going back to git. It is such a pleasure to work with a DVCS that you don't have to fight at every turn.
It seems like a lot more than they "did exchange ideas".
Arxanas, the lead dev of git-branchless, is also one of the major devs and maintainers of jj. He's active on the jj discord, and just last month put out a call on github for a jj side conference during Git Merge 2025.
I tried it out on one of my personal repos and I need to give it more time. I do like the way it tracks work, it feels like it would help me to make more commits as I work, cause I’m really bad about that
If only it was supported by Gui tools... I don't bother use Git from my terminal, but use GiFork instead. As I understand there is nothing that may be interesting for me in my scenario...
I actually find I don't need to use any GUI tools with jj because I can actually understand what's going on under the hood since the mental model is much simpler. It helps that jj has excellent visualizations straight in the terminal.
Lately I've been hearing more people are getting into jj because of how much easier it is to keep track of the code generated with tools like cursor or claude code.
I have found it incredibly helpful when using an agent. It's a great case for the "staging-area" style workflow. I `jj new` an empty revision where the LLM can go hog-wild. When I'm generally happy with the LLM's work, I make a new revision. Then I ask it to make some improvements. Sometimes I have to discard those. No problem, I just `jj abandon` the whole thing. Sometimes I like a few pieces; I `jj squash -i` those into the parent revision with the bulk of the work and abandon the rest. And then I repeat until I'm happy with the overall output.
It's also super useful when I need to go fix something in an earlier (but still unmerged) revision. `jj new {id}` creates a new revision whose parent is the older change, even if it already has children. Again, I let the agent loose. When I'm happy, I `jj squash -i` what I want and the fixes are not only incorporated back into the parent but then propagated automatically down through the rest of its children through automatic rebasing.
I expect them to, I ment, that as part of modifying the code the issue commands that affect the VCS. This would be annoying, because you then can't rely on the VCS to tell you what the LLM did.
Any tips for a magit fan who wants a positive second experience with jj?
I’m used to sorting through the changes I have made and making separate commits for each unrelated one (eg bug fix for several files, some WIP work, other parts actually done and ready to be “shipped”/reviewed)?
I came from magit and completely switched to jj for the much easier handling of sets of changes that you describe. jjui (https://github.com/idursun/jjui) made this a lot faster and more enjoyable.
I legit ask myself how many folks avoid Github Desktop for some dogmatic reasoning equivalent to "having an UI makes it worse", when it does the core of common flows extremely easily and clear.
To be clear where it ties to this post: it makes git far more convenient with nearly 0 learning curve.
When I was first learning coding, git, etc. and had no clue how git worked I downloaded GitHub desktop and used that. It's true that I was much younger and less knowledgeable then so it's possible that's influencing me but man when you actually understand git the cli just feels so much smoother, cleaner, and faster. It really doesn't take much dedicated to time to learn the top 5 commands you'll use 95% of the time and if you don't know git currently I think it's probably one of the most leveraged ways you can spend your time.
My thought is, if a GUI like GH Desktop makes it hard to use Git, then your workflow is too complicated. Version control doesn't have to be complicated. But a lot of that is upstream decisions about how you structure your work as a team.
I'm not sure if there's advanced commands that GitHub Desktop does more with but for the most part it's just a porcelain frontend that works great for doing simple operations but can't do things like interactive rebases, reflogs, blames and so on. It's a pretty simple frontend to get started if you're just learning git. There's not much it does that's GitHub specific. If you're logged in, it will easily checkout your upstream repos.
My NixOS repo checkout(s) are always cursed with unstaged changes, will jj help me cure this?
Sometimes I feel like "this shit is too bad to commit but it does the plumbing for the thing I'm working on right now" and it ends up untracked for weeks... This is just my personal ADHD pool of code but it gets really gnarly before I take care to dissect my unstaged changes into the repo.
I've never tried jj, but recently I discovered lazygit which in my opinion comes pretty close to perfection.
Rebasing can be done instantly (reorder commits, fixups, squash), I can extract and reverse custom patches from previous commits, I can partially stage files, undo.
One think I like is that it explains what it does in terms of git commands. So it helps a little to have a good git mental model, and as such I guess it doesn't solve the same thing as jj.
I must be getting old because I really don’t think git needs a simplified model. But hey, if people find value from this, more power to them. The blog is well written too.
It’s not just that it’s simpler, it’s that the primitives and interaction model are also strictly more powerful. A ton of extremely useful workflows that are an utter pain in the ass with git are absolutely trivial with jj.
One example is a series of dependent PRs. This is excruciating in git if you ever need to make a fix to an earlier PR because you have to manually rebase every subsequent change. It’s trivial in jj: you either fix the revision directly or you insert a new revision inbetween. The subsequent revisions are updated automatically. You don’t even have to think.
Another is splitting work into parallel branches. So often when I’m working on one area I end up making unrelated tweaks and improvements as I go. Pulling these out into their own branches is painful in git, so people just make omnibus branches that include a handful of unrelated work alongside the main task. In jj it’s basically zero work to split out the unrelated stuff onto a new branch off main, and it doesn’t require you to task-switch to a new branch. So I end up making lots of one-line PRs that are trivial to review whenever I’m doing deeper work.
> This is excruciating in git if you ever need to make a fix to an earlier PR because you have to manually rebase every subsequent change.
Spreading the word about `git rebase --update-refs` that will automatically update any branches that point to commits along the path (very useful with stacked branches). It is less convenient than what jujutsu offers (you need to know the branches to update, where jujutsu automatically updates any dependency), but still a very useful if you don't want to or can't switch to another tool.
There isn't really any "can't switch to another tool" when it comes to git + jj. You can use it today without anyone on your team knowing - other than that your PRs suddenly became much cleaner
>you have to manually rebase every subsequent change
What do you mean by this? You can do an interactive rebase in git as well. The real issue is non-trivial merge conflicts which is going to be an issue no matter you use.
Let's say I have five commits in a row ready to go, but they should be reviewed and merged one-by-one. Upon review, I need to make a change to the first commit. How much work do you think this would be in git? How much of a pain in the ass do you think it would be if a later change conflicted with this earlier change?
It is essentially zero work in jj. I `jj edit` the revision in question, make the change, and `jj push`.
It's really not a lot at all. The weakness is in "forge" tools like GitHub that add a PR/changelist abstraction on top of git and don't support sequences of patches. If you're using just commits and maybe (mailed) patches you only do a single `git rebase` (with -i if you want) and you're done. Unless jj is literally magic and can automatically fix conflicts, I can't see how it would actually reduce the work involved in a meaningful way.
I'll be honest, it's been so long at this point since I've used git that it's hard for me to remember the exact details of many of the challenges. I know that I avoided a lot of workflows I wanted to do because of the complexity and mental overhead, and the scenario I described was absolutely one of them. Maintaining a stack of changes that needed to be merged one after another was painful if there were any unexpected hiccups along the way. Now it is painless.
Arbitrarily-complicated rebases just happen automatically with jujutsu. Inserting, moving around, and directly editing commits are first-class operations, and descendants automatically rewrite themselves in-place to incorporate changes made to their parents. Rebase conflicts are also first-class citizens so they don't block you and force you to deal with them right now or in any particular order. Having to rebase seven related conflicts one-after-another is no longer a thing, nor is realizing you fucked something up halfway through and having to restart.
Coming from git it honestly feels like magic even if it strictly isn't. It genuinely hard to understand how much unnecessary toil git makes you put up with on a day to day basis until you suddenly don't need to deal with it any more.
As far as I know, by default Git doesn’t enable the “reuse recorded resolution” feature so if you made a change to the first commit you’d have to manually do the same thing for any subsequent commits.
If you have 5 different branches, sure. Again, the reason you create a bunch of branches for separate review is because that's what the "git forge" abstraction generally expects. It's not actually how code reviews are done by the people who wrote it.
Basically see how the LKML works. Individual commits are typically expected to be self-contained to the best extent possible, with patch sets or patch series being used when that's not feasible. Patches are usually sent over email, but there are a lot of ways to do it.
It's not an ideal way to operate for most shops, but there's really no reason you can't have a PR/MR/changelist/whatever that is a single branch with X commits on it and you ask the reviewer to review each commit individually instead of as a whole unit (as GitHub and other forges usually expect you to).
That and don't let reviews pile up such that you have 5 dependent in-flight reviews, something else is wrong if that's happening.
> Basically see how the LKML works. Individual commits are typically expected to be self-contained to the best extent possible, with patch sets or patch series being used when that's not feasible. Patches are usually sent over email, but there are a lot of ways to do it.
What is the advantage to this, other than maybe being easier to send over email?
To me, the important part is you have a logical "unit of change" that you're proposing to the codebase, whether that's a single commit or a branch or a jj bookmark or whatever seems more an artifact of the underlying transport layer (email or github or whatever) than any kind of intended functionality of the design.
Think about the word "branch". If you have a linear sequence of commits that's one branch by definition. But you go and label the middle of that branch as branches too and then get annoyed when git, you know, does some branching there.
I mean the unit of review is the patch (set), which does not necessarily have a branch associated with it. You can use a branch, but you could just as easily send commits from master and the reviewer can apply them directly on their master branch if desired. The idea of "branch per reviewable unit" was largely created by GitHub.
git rebase -i is a much worse experience than jj's autorebasing of descendants. For example, with git rebase -i you can't decide to do something else in the middle of the rebase — you're in that state until the rebase is completed or abandoned.
Merge conflicts are also significantly better with jj because they don't interrupt the rest of your flow.
> with git rebase -i you can't decide to do something else in the middle of the rebase
You absolutely can, to some degree. At any point in an interactive rebase you can just make your changes and do a normal `git commit` then do `git rebase --continue` along on your merry way. Unless you're talking about suspending the rebase and like switching branches, messing around, and then resuming the rebase, which is kind of a weird thing to do.
I do mean suspending the rebase and going to do something else, yes.
It's a weird thing to do in git, because the conditions that git creates makes it weird. It is completely natural with jj, because jj doesn't have any modal states at all. (This is one of the key reasons jj is both simpler and more powerful than git — no modal states.)
git checkout master
git rebase [whatever]
[rebasing stuff]
git tag rebasing
git checkout --detach master
[do random other stuff]
git tag todo
git checkout rebasing
[continue rebasing]
When you are not trying to modify the same branch you're rebasing, you can omit --detach and also git tag todo.
(To clarify, it has never occurred to me that I even want to do that, so I didn't knew how to do it. Yet I didn't even needed to consider a manual, it just follows naturally from the git user model even if it seams completely unidiomatic.)
As far as I know you can't start another rebase -i while you're in this situation. But it's been almost two years since I last used rebase -i so I might be wrong.
I think you communicated well, probably I did not.
> suspending the rebase and going to do something else
This is what I find to be weird, personally. When doing a rebase, there’s no way I want to do something else in the middle of it, and having a modal state feels totally natural to me.
At first approach (I read a (very good) intro[1]; I did not try), it seems there’s a lot of new things to learn (for instance the revsets language), for a very minimal gain, so I’m (still) gonna pass on it. It feels like jj is solving a lot of problems that do not exist, or are mostly solved with worktrees.
That being said it’s true that everybody’s way of working is different, so I don’t know! Maybe jj will be picked up by the younger generation and it will become the new de facto standard. Time will tell…
Personally, the current VCS tool I’d like to try now instead of jj is fossil. It seems much more interesting as it promises to allow bypassing GitHub/other forge completely by being the full forge itself. In these days, having ownership of one’s data feels primordial to me.
> When doing a rebase, there’s no way I want to do something else in the middle of it, and having a modal state feels totally natural to me.
This is a consequence of rebases being a special, modal state that requires dedicated focus to work through to resolution.
Rebase conflicts are (to a jj user) just another fix you might want to make to a revision. Or it might be better done by making a tweak to an earlier revision, instead of the revision where the conflict first occurred. There’s no pressure to fix it right this second, you can always come back where you left off. And you can make your fixed in a separate commit that you squash into the conflicted commit if it’s particularly hairy.
> It feels like jj is solving a lot of problems that do not exist, or are mostly solved with worktrees.
Git has grown a lot of features over the years and countless flags to make some things feasible.
jj rethinks the core interaction model in a way that gets rid of all the band-aids.
There are people who still live and die by C and swear that anyone running into issues is holding it wrong or just needs to be more principled about how they handle memory. But most people have moved on to higher-level languages. It doesn’t mean C was a bad language, it doesn’t mean those programmers weren’t familiar with C, and it doesn’t mean there aren’t still some cases where C is the right answer. But most people nowadays find other languages more productive with less friction.
> This is a consequence of rebases being a special, modal state that requires dedicated focus to work through to resolution.
Believe it or not: no.
Humans are notoriously bad at multitasking, so it makes sense to actually finish a task before moving to something else and forgetting what we were doing…
Relaxing this comment which is a bit aggressive: at least it’s how I work. YMMV.
I think the evergreen use case for interrupting a rebase is, your manager came over to ask if you can drop everything and look at this bug that $TOP_CLIENT is screaming about.
Git worktrees suffice, but they're still heavier weight than `jj new whatever`.
I've had maybe 3-4 occasions in my entire career where I was working with a rebase large enough where it took multiple sittings (every single one sucked badly). I'm not going to argue that it's not something that comes up, but I will say that if it's common, your workflow is probably too chaotic for me. That being said, you can still do that in git (see sibling comment to this one), but it is more involved. IMO that's good because I don't think enabling that type of chaotic, jumpy workflow is healthy or good.
Well for me it's happened hundreds of times. Specifically I've very often wanted to do another git rebase -i in the middle of the first one, usually because I changed my mind about something and want to make changes to an earlier commit in my stack.
> IMO that's good because I don't think enabling that type of chaotic, jumpy workflow is healthy or good.
Nah, it's wonderful. You see it as chaotic and jumpy because the conditions Git creates makes it feel chaotic and jumpy. It's like being terrified of multithreaded code if you're not using Rust or a purely functional language.
> Specifically I've very often wanted to do another git rebase -i in the middle of the first one, usually because I changed my mind about something and want to make changes to an earlier commit in my stack
I think this is what `git rebase --edit-todo` is for.
>Nah, it's wonderful. You see it as chaotic and jumpy because the conditions Git creates makes it feel chaotic and jumpy.
Fair enough. `jj` probably isn't right for me, but I'm happy it works well for you!
Been a long time since I used git, but --edit-todo only changes what's still to come, right? It doesn't let you go back and edit earlier commits in the stack.
I literally will have like 4 PRs in flight at once and have an octopus merge of all my separate PRs that I can then work on top of. JJ can rebase all 4 separate branches, the octopus merge, and the work on top of the octopus merge in a single command: jj rebase -d main.
If there are conflicts I can then resolve them whenever I want.
You have no idea what you are talking about if you think git’s interactive rebase holds a candle to what jj rebase can do.
> The subsequent revisions are updated automatically
How do you make sure, that a commit isn't changed under you, because someone thought, it would be a good idea to change an earlier revision? I think having immutable commits including all the previous history is a feature, not a bug.
JJ has a concept of mutable and immutable changes. Typically, mutable changes are ones that you're currently working on locally. If you create a branch locally and push to it, then the changes on that branch will also be considered mutable. But changes created by other people in branches that you didn't create are considered immutable, as are e.g. changes that already exist in the default branch. There are a handful of other ways that you can convert changes between the two states, but generally the distinction is fairly intuitive: mutable changes are ones that you own, and where it would normally be "safe" to force push etc, and immutable changes are ones that probably also exist on someone else's machine.
All changes can be modified, but if you try and update a change marked as "immutable", then Jujutsu will error out unless you provide a specific flag. This generally provides a lot of protection against unexpected rebases from other people.
You also have a local immutable history of every update to your copy of the repo. This includes fetches/pulls, so you can easily see and undo those changes if something does go wrong along the way. But if people are deliberately force-pushing to master, you're going to end up in weird states whatever to you use.
How does it know that? Does it also work, when I clone or pull instead?
Is that only enforced on the committer side? I mean git also doesn't force push by default, but that still means someone else can do that and I need to notice it.
There is a way to express which sets of revisions are immutable. This uses the revset feature which itself a whole amazing thing that hasn’t come up in the conversation yet. This is a mechanism by which you can concisely express and refer to entire sets of commits in your repo history, and lots of commands can operate on multiple revisions as easily as they can operate on one.
Just like git, it is only enforced by the tool itself on the client side. But it’s safer in practice because `git push -f` is a common habit when you’re working on a branch by yourself and it’s easy to fat-finger that in the wrong place. I have only ever had to use `jj --ignore-immutable` when explicitly doing repo surgery.
If you want branches like `main` to be truly immutable, you need to enforce that at the repo.
I don’t think simplified model is even the best selling point, but it’s definitely up there. IMO one of the killer features that you absolutely cannot get in git is universal undo. For example, rebases can always be a little tedious and tricky no matter how experienced you are. Not only does jj make that whole process easier and safer to work through, but if you do still manage to get to a state where you just want to go back it’s literally just an undo away.
Couldn't agree more. I guess we _are_ getting old after all.
I've been using git since ~2014, never really thought about changing it: it's clear, well documented, and ok difficult learning curve but hey that's the fun part.
Honestly one of the biggest selling points of jujutsu for me is its `op log`. You can fuck up your repo in git and that's it -- you're screwed. Or you find some extreme magic on the internet that saves you.
With jj you just "jj op undo <operation_id_that_fucked_your_repo>" and you're fine.
Editing prior commits is also pretty easy, which in turn makes fixing merge conflicts pretty easy too.
jj has two kinds of these logs: the evolog and the op log.
The git reflog is based on, well, refs. Whenever a ref is updated, you get an entry. This log is per ref. That's HEAD, your branches, your tags, and your stash.
jj's evolog is sorta similar, but also different: it's a log, but per change (think commit in git). This means it is broader than per ref, as it includes not just commits that correspond to a ref, but all of them.
jj's oplog is a log per repository. This lets you do things like `jj undo`, which lets you get the entire repository, not just one ref or commit, back to the previous state, easily.
But instead of commits, most of the time you're working with (the not greatly-named) "changes", which are a conceptual history of related commits. E.g., a single change ID points to the most recent commit in a list.
As long as you keep editing on a change, it keeps accumulating commits under the hood. When you move to another change (via `jj commit`, `jj new`, etc), all your new commits end up under a different change.
Yeah so people will be a bit loosey-goosey with the terminology. But all of these changes are immutable, that is, when you change a change, you’re generating a new commit. “A VCS on top of a VCS” isn’t a terrible way of thinking about it: imagine if every commit in your repo had its own history.
Merging is making a change that has more than one parent. You can then resolve any conflicts within that change.
Splitting a change into two will update one commit with one half and make a new change with the other half.
Oh, since you said 'merge' I thought you meant like git merge. Amending a commit works the same way as git. The difference is just that the oplog will have the full history, including before you amended, so you can roll back easily.
Yeah, I love jj, but I'm not a fan of that naming. It threw me for a loop for the first couple months.
I'm not sure what happens, but I suspect a merge would create a single merge commit with the tips of the parents' histories, and then add new commits from there on.
A split is probably conceptually similar to making two new branches, except each of the new commits gets one of the branch tips. Really just guessing here, I have no idea if that's how it works, or if they share commits or not.
I want to be excited about Jujutsu, but what always bothers me about JJ is that if you search the page you can find 'jj' 47 times and 'git' - 49.
Is there a good explanation of Jujutsu without referring to git? May be with some pictures? Am I the only one bad with memorizing SHAs when reading? Also, does it work with other people? Would it work in repo with 5 contributors? 20? 500?
EDIT: I am looking for tutorials with explanation on how JJ works, preferably without referring to Git, and even more preferably with some visual explanations, which, there are plenty for Git. It seems I am not very good in reading text trees. It is my issue, not yours, but may be you know something which would help.
Git is nearly universal, so highlighting the areas where jj makes things that are painful in git trivial and obvious isn't exactly a bad strategy. It's also worthwhile pointing out that it is interoperable with git, so your company and team don't have to change just because you do.
> Am I the only one bad with memorizing SHAs when reading?
I haven't ever needed to memorize a SHA or change ID with jj. What are you referring to?
> Also, does it work with other people? Would it work in repo with 5 contributors? 20? 500?
I have used jj for two years on teams without anyone else needing to be aware. Other teammates that I've convinced to give it a shot have switched, and we all work together happily alongside the git users. If anything, it's easier to work with others when they switch to jj because you no longer have to worry about rewriting history of branches that have been pushed. If you have a branch that multiple people are working on, that's bad form in git but it's just another day with jujutsu.
I was referring to tutorial(s), which refer to commits just by hashes. It is very hard to read for me. While git is nearly universal, my concern was about leaning jj and what it is useful for, what are its limitation etc.
"At the time of writing, most Jujutsu tutorials are targeted at experienced Git users, teaching them how to transfer their existing Git skills over to Jujutsu. This blog post is my attempt to fill the void of beginner learning material for Jujutsu."
> Is there a good explanation of Jujutsu without referring to git?
There can and will be, but at this stage in the project's life, git familiarity can be assumed.
> Am I the only one bad with memorizing SHAs when reading?
Nope! The CLI has nice syntax highlighting to show you the shortest valid prefix, so for example, right now I have something that looks like
lzrvnkxl
but the initial l is in purple, while the zrvnkxl is in grey. This means I can just use the l when referring to the change. That can be harder to demonstrate in a blog post, which can't know how to highlight this, and so often they have no highlighting.
> Also, does it work with other people? Would it work in repo with 5 contributors? 20? 500?
Yes. Because it's backed by a git repo, nobody else needs to know you're using jj. Everyone can use the tool they choose.
> The CLI has nice syntax highlighting to show you the shortest valid prefix, so for example, right now I have something that looks like
lzrvnkxl
but the initial l is in purple, while the zrvnkxl is in grey. This means I can just use the l when referring to the change. That can be harder to demonstrate in a blog post, which can't know how to highlight this, and so often they have no highlighting.
This is such a simple UX feature that I have ended up using all the time after I switched to jj a few months back.
I tried twice to switch for a project, both times came back to git. But then, I am still not fully grokking the "why bother", and suspect that if I had a UI it would be both less friction, and more understanding.
small projects and/or projects with few collaborators don't benefit as much from jj as the big, fast moving ones. you can easily stick to git and be happy as the differences are not really apparent.
Yup! And you can just do jj git init --colocate in an already-cloned repo and it'll start working immediately, seamlessly. No need to re-clone it via jj git clone --colocate
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"
What if you commit secrets to the repo by accident?
Jujutsu repos only exists locally. There are no Jujutsu remotes, only git remotes. Repo properties unique to Jujutsu (repo history, stable revision IDs) are not pushed to git remotes.
Just edit it out and don't push it to upstream?
This is interesting, thanks!
I've started, in this thread, likening it to Plato's Cave - people who are just using git are just seeing/using a blurry facsimile/projection of actual reality/possibilities. It's not a perfect analogy (jj is, I suppose, the projection, and people are chained in the cave), but it makes the point that there could be so much more.
The matrix or people happily in a cult would be similar analogies. Perhaps even Stockholm syndrome...
This is exactly why it feels like evangelism: "people who don't love jj haven't seen the light/are uninformed/live in the dark age/must be in a cult".
That kind of rhetoric makes me want to say "don't tell me I'm stupid if you are the one finding it hard to switch between git branches".
It is evangelism. Unabashedly. I think the Plato’s cave analogy is honestly apt.
Git was fine. I used git for ages. I loved its underlying model, and I think it’s brilliant. But it has a lot of sharp edges and a lot of tasks that are painful or frustrating, and people either opt to restrict themselves to a tiny subset to avoid the pain or they carefully curate a workflow that mostly does the job over the course of years.
Suddenly within a week of trying a new tool you just… don’t carry any of that any more.
It’s like going from GOTOs to structured programming with encapsulated functions. People made many useful programs with GOTO. Some people were more principled than others, but we all got by. But it turns out that functions and loops are a way better mental model for control flow than GOTO. Nobody had to switch and there were plenty of holdouts. But eventually the benefits were impossible to ignore.
People like Dijkstra evangelized structured programming because they “saw the light”.
That's fine. Carry on
This is one of the few comments here that make me feel like I should just try jj. Thank you, this is useful :-).
You're welcome! I've honestly really started doing stuff that git conditioned me into think I just didn't need, just because it made them hard to do.
I feel like it's cat vs bat.
bat just is more useful even if you didn't have issues with cat
This is also true of vim. You have to move to Helix. It's very similar to vim, but different enough that you have to learn new stuff.
How much time do you spend resolving merge conflicts between multiple team members?
In good approximation, I would say around 0 minute a week. I guess it could happen that it takes a few minutes, but I don't remember last time it happened.
yea i am really confused by all these issues people are saying they have myself..stashing merging etc...and what is crazy is we migrated large teams of programmers who's only experience with version control before git was microsoft TFS and and everyone is easily and happily chugging along. Now most of these people use gui tooling for managing git, but so what everyone gets it done with no issues or complaints with many projects big and small. we even have non-devs using it. I get jj might have some life improvements for some niche groups of people but i really have to wonder if they are the type of people who purposely make their life more complex
Forget between team members - it's an issue just for my own local dev purposes, when I'm working on multiple changes/PRs at the same time and want to test them all out before they get merged to main in the origin.
It's certainly a solid improvement in the space of VCS UI, but beware that jj has some current limitations which might prohibit switching, especially for the git power users.
Lack of gitattributes support precludes git-crypt and git-lfs usage or anything that needs filters; line ending settings will get ignored, making Windows interop a little less smooth; etc.
Also note that auxillary tooling, such as git-annex and git-bug, becomes second class, i.e. no oplog integration and they might mess up your log with internal-use commits and heads.
> line ending settings will get ignored, making Windows interop a little less smooth; etc.
This one got fixed just a few days ago! https://github.com/jj-vcs/jj/pull/6728
Oh thanks for the heads up! I use jj but it would be a disaster if I committed something using it and git-crypt didn't work, and the secrets were all plaintext.
I use gitattributes quite a lot (lfs, various diff engines, and export-ignore). Thanks for the heads-up, jj looked very interesting but I'm not going to give up gitattrivutes.
They are slowly working on it:
https://github.com/jj-vcs/jj/issues/53
(I am interested because I want to try, but need git-lfs, which needs gitattributes.)
These are all really excellent points, and well worth calling out.
> line ending settings will get ignored
Arguably a good thing - git's autocrlf setting causes way more issues than it solves. I highly recommend setting it to "input" (basically bans CRLF).
"You will be hard-pressed to find someone who stuck with it for a week and decided to go back to git. "
Reporting in. Doesn't mean I will not end up with jj eventually, but so far I always went back to git after a while.
For me it is the staging area and the workflow it allows. Most people hate it and love jj because it does away with it. That is just not me. I don't see the staging area as a hack that was necessary to overcome some superfluous technical limitations but as a workflow tool.
Could I change my ways? Sure. jj just did not provide enough benefit for me so far to do it, but we will see. I am still open to give jj another try some day.
The staging area is a hack in that it is its own unique concept that doesn't work with any of the rest of git's tooling without needing special, inconsistent flags for commands to target it (git stash, git reset, etc.).
I use a staging area with jj! I would surmise most jj users do too. It's just a real, honest-to-god commit in the repo instead of a special snowflake.
Since it's just a commit, all your tools work with it out of the box. And you don't need to stash changes when you jump around between branches.Yeah, I regularly have MANY "staging commits" at the same time in jj, in which I just throw ideas in that I don't want to pollute or yet know how to integrate with the other ideas. I use squash, split, interactive split, rebase etc to move lines and changes around etc...
When I eventually figure it all out, I can tidy it all up into completely coherent, discrete, sequential commits that make it look like I knew exactly what I was doing from the start, then push to remote.
Conversely git staging around stashing was always an absolute, terrifying mystery to me. I could never remember what was stored where, and would consequently end up losing work, repeating work, and, most of all, just making massive commits that make no sense.
I was sold on jj from essentially the moment I found it, but I remain routinely amazed at how much more powerful (yet simple!) it is than I realized.
Hmm, never saw it that way. Maybe I just need a different perspective on commits. Thanks for your comment, that was insightful.
Glad to be helpful!
In my mind, in some sense, every commit can simultaneously be a stash, staging area, part of a branch and more. Because you can easily create endless branches on top of, beside, before, as merges of, etc.. any commit. And it all automatically rebases, conflicts often auto-resolve (or at least don't block you immediately).
I really don't know how to best describe it. But there's a reason that many people are quite literally evangelizing it in every post that comes up - who does this for git, or anything?
Just give it a try. And, even better, use it via jjui
You can use commits instead of the stash the same way in git. Isn't this just a way of using, why do you need jj for that?
You now you can give stashs a commit message?
The commit message is the least useful part of all of this.
It is just SO EASY to move, merge, split, rebase, squash etc commits, and even just individual lines and sections from within commits (via split).
And it's all even easier via jjui
I don't think there's anything any of us can really say to convince those of you who are constitutionally skeptical about it (or even allergic to it) why you should try it... If all of our fawning and evangelizing isn't enough to get you to at least give it a fair shake, then there isn't much else to be said.
It's just that I'm fine and also the features seam to amount only to a change in usage not in actual new features. In this case if you want to use commits instead of a stash, then you can do that just fine in git.
It's also that I want my commit hashes to be stable (because I cross-reference them) and it sounds like they wouldn't in jj, because it is kind of rebase-y?
Commits in jj have both a commit has (which is unstable) and a commit id (which is not).
Generally you use the id, not the hash.
And yes, everything you can do with jj you can also do with git. It’s just that most people don’t, because it’s much harder with git, involves rebases and changing hashes, and so on.
It's the new workflows that it enables via making existing features vastly more accessible (though there are some new features, such as merge conflicts not being a showstopper).
The only thing left for me to say to folks like you who think git is just fine (or even great) is that you're in Plato's Cave. https://en.m.wikipedia.org/wiki/Allegory_of_the_cave
You just don't know what you're missing out on. But if you give it a fair shake, you'll see the light quite quickly.
Take care!
I would call `jj absorb` a new feature (new if coming from git, I believe the idea was taken from hg). It moves diffs into the closest ancestor that touched those files/lines. Very useful for addressing reviews, then just `jj absorb` and watch them get squashed into the correct location.
This gets even better with octopus merges (known in the jj community as "megamerges"): open up 5 branches at the same time, address minor tweaks in any number of them, and `jj absorb` the content into the right parent branch.
I do use git absorb, but its true, you have to install it.
I have never tried git absorb across merges, no clue if that works.
Can you edit commits by adding on only parts of your "staging" area?
Because that's my workflow. I produce a few focused and semantically coherent commits that I'd like to apply to the codebase, where each keeps the codebase in a working state. I might be working on more than one thing at once, but I know which commit each set of changes should "live in."
An append-only log of commits is decidedly not something I want. Is jj amenable to this?
Extremely amenable—my workflow shifted to match your description after I started using jj, because it makes that easier. Conceptually, it’s not “edit commits by adding on only parts of your staging area”, but “edit commits by only moving or splitting parts from another commit”, and since the working copy is a commit, it is handled by that general case instead of being it’s own thing.
It's not quite clear to me what you're talking about, but I think the Megamerge workflow might be something you'd be interested in.
https://v5.chriskrycho.com/journal/jujutsu-megamerges-and-jj...
https://ofcr.se/jujutsu-merge-workflow
Thanks for the clear and concise summary. It is helpful for some workflows.
For me the staging area being a snowflake is the feature. It is a special temporary singleton commit if you will, but I can rely on its temporary and singleton nature. As that it should have a different interface from regular commits.
I found it hard to stick to due to pre-existing workflows too. I find I'm often switching between branches in git, and our CI depends on pushing to specific branches - I constantly found myself a bit lost with jj about which underlying branch I was on and where that would get pushed to. I did Steve's tutorial and came out of that really liking the concepts, but still unable to map it all in my head when it came to pushing my work to a remote.
I’m similar.
On the other hand, tools like jj seem great for people who spend a lot of time refining and perfecting every commit so that their repo history is pristine. I’m not one of those people. My git hygiene is atrocious. I have git halitosis. But functionally, it’s fine. Everything is in the git history and is recoverable. That’s about all I care about.
Also, I don’t have the capacity to juggle a lot in my mind. If someone does, then sure, jj may facilitate that. But I don’t need such facilitation.
Jujutsu might look like it's for people who want to perfect their commits and juggle a lot in their minds -- but in my case (and anecdotally in others' too) it's because it lets us easily do things which would require that mindset/ability with plain Git.
In Git, I need to keep in mind all the different things I'm working on and which branch goes where, or I wind up with a monster PR with a chain of commits that mix everything up enough that it's even more effort to pull the PR apart to be reviewable. Ask my colleagues how I know that :P.
With jj, I don't feel that I'm putting more effort into organising my changes -- quite the opposite -- not least because there's very little book-keeping involved in parking something that's in progress or coming back to it later. And when I come to ask for a review, I've got separate changes I can push as smaller PRs, and I can easily pull the right sets of changes out to make each one reviewable.
I think the biggest improvement is that if I just need to look at something else for a couple of minutes then come back, I can `jj new main` to switch and not bring my changes with me then I can `jj edit <oldref>` to get my working copy back into the state it was in before, with all my old changes.
I'm pretty sure none of my colleagues have started using it (although they're free to) but I can confidently say (because I've asked) that they've noticed the difference in my work and prefer the more focused changes.
I used jj for several months, then eventually went back to git.
The biggest killer was performance. jj operations took several seconds for me, whereas git is instantaneous no matter how big the project. Maybe this is fixed now.
But also honestly I felt like there was a bit more mental burden to using jj. When I switched back to git, it was like a weight off my shoulders. Maybe that's just due to the decade of constant use though.
I used to have the same problem (on Windows only, not Mac) but it seems to have been fixed as of a month or two ago.
> there are generally two camps of people: those who haven't given it a shot yet and those who evangelize it.
This sounds exactly like something a person evangelizing it would say.
The relevant question is, why would a stranger take time out of their life to evangelize an open source VCS tool?
What other tools do people do that for? Not many. I see the most similar language and behaviour with regards to uv, which similarly revolutionized/simplified python tooling.
Likewise mise, which makes it similarly frictionless to install and manage tooling and their versions across projects and system-wide.
The evangelizing for these things comes because these tools start to heal the trauma that came from immense friction. We want others to be similarly liberated.
For those who say "I don't feel friction/trauma with git, legacy Python tooling etc", I can only say that you're living in Plato's cave/the matrix/in a cult/with Stockholm syndrome and just don't know what you're missing out on.
>The relevant question is, why would a stranger take time out of their life to evangelize an open source VCS tool?
To make a point? To prove that "they are right"? People do this constantly, tools, libraries, languages... All the time really.
> What other tools do people do that for?
Text editors, programming languages, shells, VCSs, nix/guix…
“Trauma” is a bit much.
Definitely applies more to uv than jj, but jj is still immensely liberating. To the extent that people are willing to evangelize it to no gain of their own.
There is a 3rd group (probably mostly gamedevs) who think it seems like a great idea and really want to try it, but are blocked waiting for git-lfs support.
Every time jujutsu pops back up on HN I check to see if they've added it yet. Not yet! But they are slowly getting there:
https://github.com/jj-vcs/jj/issues/80
Maybe they need perforce support instead :P
Reason I like git is because I use like 2% of its features. I don't fall for propaganda that I need to use bisect and co. 99% of git commands I call are aliased to 3 characters, so it's dense terminology doesn't bother me.
Bisect is a wonderful feature. Git is full of tedium and frustration, bisect is not part of that.
Bisect is a very cool feature that I used once over 5 years ago. The collection of git tools that I use often is very small. I've found that the more experienced I got with git, the less I found myself in scenarios where I needed git's more complex tools.
Hmm, bisect is definitely not one of those.
Yes not using bisect, seams like you do all the work to record information, but then never use that information to save work?
May I use how often and why you use bisect? I don't remember the last time I needed bisect. Well, I also don't work in linux kernel sized repos, so there is that.
> May I use
Was that 'may I ask'?
Not that often, but not seldom. Its useful if you have a bug, that isn't obvious where it comes from, but easily testable. Then you just write a test and let git figure out where it comes from. The test doesn't need to be automatable. I also used it for firmware that needs to be flashed and the bug effected in an LED blinking incorrectly. I still saved a lot of time.
> Was that 'may I ask'?
Yes, can't type sometimes.
I see. Maybe it's just what kinda of code and/or how I write it, usually pretty clear which abstraction isn't doing its thing. From there, it's either clear what is the bug, or git blame will reveal what changed.
This has a much higher chance of succeeding if you maintain strict boundaries and isolation which isn't always possible.
Sorry, that meant to be a joke. Bisect is cool and easy to use, I just never use it.
I relish the day I get to use bisect. It's like, finally I get to use all this version data I've been collecting.
I don't understand why anyone would say you have to use it. It does a very specific thing, namely finding the source of a regression between two commits. If you need it you'll know.
It’s really nice to use the —-first-parent flag with bisect.
Yeah this is good if you, for some reason, rebase then merge without fast forwarding. I never understood doing this. If I'm going to be ignoring those commits when bisecting then they are useless commits just using up disk space IMO.
Does it work well with a classic merge workflow? I haven't worked that way (without rebasing) for a long time.
For me a merge is about grouping commits, when you have just a list of commits, the list can be very long.
That's also useful for bisecting, as you can first find the feature that is buggy and then find the commit that introduced it.
The whole point of bisect is to make it quicker to find a point in a potentially long list of commits. Finding the feature first then bisecting within the branch is only going to make it slower, and requires you to manually restart the bisect.
I use the commit message to add ticket numbers to things to group commits.
The commits surrounding them (logically) are probably also relevant, which you will find easier when you have grouped commits by features.
I prefer to have everything in a single source of truth (git), so everything is in commits. Your tickets are my merge commits.
There were (are?) people who say the same thing about svn
Sure, but in this case, git being distributed is what sold it for me. I've switched off svn as soon as I've learned of git.
I used bisect once in my life but it was extremely helpful. Without it I'd spend weeks trying to find regression. With bisect I found and fixed it in under 1 hour.
It's a command that is needed rarely but there's no replacement for it in some situations.
I am confused by this, isn't Bisect just some git ergonomics with respect to flagging a checked out commit as a success failure and then checking out the next midway commit depending of if it was a success/failure to enable classic binary search?
It might be slightly more tedious but couldn't you just do the same thing manually and it would add just a couple minutes to the search and you would still save weeks? I like the ergonomics but only use it once every couple years.
> You will be hard-pressed to find someone who stuck with it for a week and decided to go back to git.
I used it for a week and switched back to `git`. Most of its hallmark features are not something I use that often.
Counter point: I adopted it internally at Google (there's a backend for Piper, Google's monorepo Perforce thingy). I don't do my day-to-day work in the monorepo but I still jump in there once or twice a week. I adopted JJ because the existing frontend (Mercurial-based) is slow while JJ is fast.
It's nice, I really like it! I'll probably switch to it as my main VCS eventually. But it doesn't feel that important to me. Even though my main work involves quite a lot of annoying rebases which is where JJ really seems to shine.
I dunno I guess it's just that a) I've really mastered git and have a deeply-rooted workflow in it and b) despite my project involving annoying rebases, version control still isn't very high on the list of problems I have.
So yeah I'm basically bullish on JJ as a technology but I think movement from Git is inevitably gonna be slow and steady.
> I don't do my day-to-day work in the monorepo
Is this one of the repos that uses Gerrit? Does JJ play well with Gerrit?
IIRC Gerrit was super picky about amending commits and I was worried JJ's laissez faire attitude about creating git commits on demand wouldn't jive well.
> Does JJ play well with Gerrit?
There was a PR for a `jj gerrit` command, but at this point, it's not super needed. Here's the main bug tracking this https://github.com/jj-vcs/jj/issues/4387
Beyond that, the jj, gerrit, and git butler folks are working together on standardizing the change ID concept so they all work well together without extra tooling. That's going to take a long time though. Think "store the change ID in the commit rather than as a trailer", that kind of thing.
Curious, I was under the impression that Google was all a monorepo, but your phrasing suggests that there are others. As my company is pushing for a monorepo, I'd love to know what causes someone at big G to not be in the monorepo. Thanks for any insights you can help me with!
Mainly open source stuff. Chromium, Android, etc.
https://opensource.google/projects
I think all of those are using git and many Gerrit as well.
I'm not entirely sure that "after using it I really like it and I'll switch eventually" is that much of a counterpoint :)
What really kept you from staying with it? It does seem like if your workflow involves a lot of nasty rebases you'd reap dividends from something like jj. I was also someone who'd mastered git (hell, I've written a git implementation) so I get having its patterns deeply ingrained.
Yeah I guess it confirms your point in a way too.
There's nothing keeping me from switching except the activation energy cost. Almost every aspect of working in my area (Linux kernel) is painful so I'm constantly investing in tooling and workflow stuff. So usually I just don't feel like investing EVEN MORE in the area of tooling that's probably least painful of all.
So yeah this is still basically a recommendation for people to try JJ!
I'm sure if I did the proper time investment I would enjoy jj. I've heard basically only good things about it in casual conversation. So far, my experience has been that I tried it, immediately got really annoyed that it automatically adds every untracked file that's not gitignored to the current commit, and was advised that I might want to stay with git if that's a problem.
That said, I struggle a bit with learning version control systems (that aren't git, like, I never really wrapped my head around svn or darcs or anything until they invented git). Seems like everybody just wants to write about the cool new commands they can run now instead of conveying how the data model works, or what mental model it wants to encourage. I had the same issue trying to get into pijul a while back, couldn't understand how to conceptualize the current state of a branch if I couldn't point at a commit in a tree and say "that's the branch, right there".
Mental model: “everything is a commit”. Commits are commits. Stashes are commits. The working tree is a commit. The index doesn’t exist, because it’s unnecessary.
The data model is technically of revisions, which are stable across operations like rebases (which change the underlying git commit).
How to conceptualize a branch: as a bookmark of a specific commit.
Yeah, I think that extra layer of revision identity is giving my intuition trouble, like it's obvious it needs to be there but the consequences are still percolating through my head.
I used it for a little over a week, and switched back. This was a while ago, but the main reason was my neovim setup and flow never felt as good. Maybe jj has a good neovim plugin and diff mechanism now, though?
How fast I can deliver value is almost never gated by my VCS, so why should I try? In total I maybe lose a few minutes a month to VCS impedance mismatch, and I am merging on average ~4-5 PRs a day.
For sure git is hard to learn for beginners and there could be an alternative which is easier to pick up (maybe jj) but for those who know how git works internally and are proficient with it I don’t see it being worth the switch.
> How fast I can deliver value is almost never gated by my VCS
Oh, the number of times I do
I waste several half minutes several times per day in periods!Another time-consuming thing is context-switching from a feature branch with staged/unstaged changes. If you're good with worktrees, you can largely avoid this, but the way I work with worktrees, I instantiate them as subdirectories to my repo's parent, which clutters my directories unless I make space for this as I clone the repository, which I haven't got used to.
I deliberately avoid too complicated git workflow aliases because I hate being stuck without them.
I am definitely in the camp of "I'd switch to jj the moment I give myself time to try it."
In the meantime, running git commands does sometimes take more time than it needs to.
You can interactively rebase after the fact while preserving merges even. I usually work these days on stacked branches, and instead of using —-fixup I just commit with a dumb message.
This lets me reorganize commits, edit commit messages, split work into new branches, etc… When I add --update-refs into the mix it lets me do what I read are the biggest workflow improvements from jj, except it’s just git.This article from Andrew Lock on stacked branches in git was a great inspiration for me, and where I learned about --update-refs:
https://andrewlock.net/working-with-stacked-branches-in-git-...
Do you use an IDE with git support but not jujitsu? I do (intellij), and I'm not sure how useful it would be.
I use colocated mode, so Git tools see a detached HEAD but otherwise continue to work. I don't normally use the IDE for creating commits or moving around the repo, but I'll still use it for conflict resolution and for exploring history.
I've always hated the git CLI, and that's why I used Jetbrains tools or Sublime Merge.
Now that I switched to jj (colocated with a get repo, very useful), I went back to using the CLI again for jj and I don't miss the graphical tools.
But how do you split commits? Command line tools for splitting commits suck because they force you to look at one hunk at a time without getting the context from the rest of the change.
jj uses a TUI for this that lets you navigate the entire set of changes.
Try jjui. It's an amazing TUI
I used it for several days and went back to git; it doesn't really provide that much for someone already proficient with git, and you need to know the underlying mechanism well for when something goes wrong anyway (just like with any other abstraction).
At work we
1) use a git hook to generate Gerrit IDs for new commit. AFAIK, JJ doesn't support these.
2) we also use submodules, which must be handled using git commands.
So if I decide to use JJ, I'll have to use a mix of JJ and git..
For 1, gerrit and jj have recently collaborated to standardize on a commit footer where stable revision IDs are encoded in the jj style.
This landed in a jj release a few months back. I assume it’s live in gerrit now too.
yes, I haven’t managed for a week, but I tried to Jujutsu and gave up. It is too complicated for no visible gain. Plus, there is no infrastructure supporting it (in the end, you store the stuff in the crippled git repositories). `git commit --amend` and `git rebase --update-refs` together with few scripts (e.g., https://git.sr.ht/~mcepl/git-fixup) does the same, and I am still with true git.
Git infrastructure works just fine with jj...
And there's nothing crippled about thr git repos.
Try jjui for a great TUI experience on top of jj.
i've never considered testing out any of the new version control systems i see from time to time for the simple reason that i already know git, everybody else already knows git, git already completely handles everything i could conceivably want it to do (and a bunch more stuff that i will never touch), and perhaps biggest of all, i can't tell my manager that i want our whole team to migrate our code into (new thing) which everybody will have to learn for no reason.
serious question as somebody who has never even looked into what jujutsu offers - unless you're a solo dev with some free time, what exactly is the selling point here?
edit: i didnt realise that its just a layer on top of git so that basically answers my question, fair
I have noticed this trend too. I don't know if I'm simply too smooth-brained for `jj` or what, but I forced myself to use it for a month to give it a fair shake and simply couldn't grok it. I gave up and went back to rawdogging git. So there are at least some of us out there.
> You will be hard-pressed to find someone who stuck with it for a week and decided to go back to git.
I’ve tried jj on three occasions and I always get confused by something and just bounce. It hasn’t clicked for me.
I’ve only tried it solo hobby projects. For which git is perfectly tolerable.
I have many many many complaints about git. But jj doesn’t move the needle for me.
Reading this post I am extremely annoyed. Almost every single section is “but more on that later”. It’s still far more complicated than it needs to be.
I also really really really hate the jj log rendering. The colors are a sea of barf. Bolding the leading character that represent uniqueness is stupid and adds noise. The username being second is dumb, I almost never care about that. And the bright neon green (empty)(no description set) is such bad spew. Kinda nit picky, but blech.
Jujutsu suffers the same thing Git suffers. Every god damn blog post that tries to explain how simple it is just my eyes gloss over and think “this is too complex for me to care”.
> I also really really really hate the jj log rendering
You can fully change/customise it. Sounds like you just don't like the default.
Some people love to tinker and fine tune their tools to behave exactly like they want. I am not one of those people. I hate it. It does not spark my joy.
Most people use the default for most of their tools. It’s why Google pays Apple over $20 billion per year to be the default search engine. Because defaults matter.
FWIW, I am also one of those people who does not want to fine tune their tools. I want my tools to work well out of the box.
> I’ve only tried it solo hobby projects.
If I can surmise a bit, this is probably why it's never felt "worth it". I'm guessing that for a hobby project you don't really care that much about crafting small, easily-digestible PRs or keeping a clean history. `git commit -a` every now and then is good enough, and that's entirely reasonable.
For team projects, jj becomes a much bigger deal. I find it invaluable for splitting up a day's worth of work into small, parallel, easily-reviewable changes. While working on one feature I might run across a dozen other things that should be fixed, improved, or otherwise changed. Bundling them into one giant PR is bad practice and causes reviews to take much longer.
Carving those up into single-purpose PRs that can be reviewed in seconds and tested independently is super helpful on a project with multiple teammates. But that's not something that really matters or that most people care to take the time do to in personal projects. Hell, it's something a lot of people punt on a lot in git due to the extra burden of doing so.
I use Sapling at $DayJob. It’s pretty good and I largely like it. jj seems much less friendly for most operations. Although jj’s merge conflict stuff is appealing (in theory).
But you can't make a default that everyone will like. The best overall interface will have someone hating it. If you want to always use defaults, you're basically guaranteeing that you'll hate some genuinely great software one day.
I can’t spend a dozen hours fine tuning every single piece of software I come across because it might maybe possibly be great if I do.
If I used Git as part of a large team I’d perhaps spend more time with jj. But for solo projects it adds even more complexity to Git rather than reduce. So I don’t feel strongly inclined to spend that time.
I tried it and didn't switch. The funny thing is I immediately recognised that it was forcing me to use git in basically the same way I use it anyway. I have more than 15 years experience with git at this point. I never had to do the "delete repo and reclone" thing after the first year. In other words, I actually understand git, so I don't really need Jujutsu.
I also already use very good tooling for git, namely Magit. IMO Magit is a much better git frontend than Jujutsu. It guides you down the right path but doesn't take away any of the power of git at all. It's quite remarkable.
Maybe I should recommend jj to some of my colleagues, though. Trouble is I'm already on the hook for helping them with git, but I don't have the experience with jj.
> In other words, I actually understand git, so I don't really need Jujutsu.
This is kind of a poor take. By all means use what you prefer! But understanding git and knowing the "right" way to use it doesn't make jj obsolete.
I am (or was) a git expert. I’ve used it since pre-GitHub. I’ve written a git implementation. I know (or knew) the interface inside and out. I haven’t deleted and re-cloned a repo in as long as I can remember.
jj is still leagues better. Things I want to do and know how to do in git are dramatically faster and easier. They require less mental overhead. They’re less error prone. And I get superpowers with workflows that are super useful but wildly impractical in git.
This isn’t even really opinion at this point. Any task you can give me in git, I can with almost near certainty give you a shorter and more elegant alternative with jj that is an intuitive and obvious interaction with its core primitives.
A month ago our company split off a division. They needed to take a specific repo and sanitize out all of the parts that aren’t relevant to the new company, going back to the beginning of its commit history. You can do this with git. It wouldn’t be fun. I’d have to spend a lot of time reading the filter-branch manpage, and people have written countless wrappers of varying quality that try and make it a bit more ergonomic.
It took me like ten minutes to come up with:
Three dumb, simple commands that I already use every day: list some commits, copy file contents from one commit into another, and remove some commits from the tree.Not only was this more or less obvious to do, but it was exceedingly fast to perform on a pretty highly-trafficked repo due to not having to thrash around in the working directory with checkouts.
> Magit is a much better git frontend than Jujutsu. It guides you down the right path but doesn't take away any of the power of git at all.
jj is more powerful than git. It’s not simply a dumbed-down alternative. By having a more carefully chosen set of primitives that compose better, you gain a lot of abilities that are technically possible with git but never used in practice due to the complexity. The above is IMO a fantastic example of how and why.
And you can still do all the normal git things too.
> This is kind of a poor take.
Why? They’re sharing what they prefer in response to someone claiming that everyone who tries the other thing comes to prefer that. They’re offering a reply in context and not in the slightest saying everyone should follow what they do.
> By all means use what you prefer!
That’s exactly what they’re doing.
> But understanding git and knowing the "right" way to use it doesn't make jj obsolete.
Which they haven’t claimed at all.
You seem to be objecting to an argument which hasn’t been made.
"In other words, I actually understand git, so I don't really need Jujutsu." sounds like jj is for people who don't understand git. That's naturally a contentious statement. It implies that jj doesn't offer anything to experts, which seems easy to contradict given how many people with git expertise will talk about getting value from jj. At worst, it could be taken as a bad faith kinda put-down, like, suggesting at jj users are worse at their job than people who make do with git. I think it's totally fair to take push back against how that was worded.
Alright, that’s fair, I can indeed see that reading.
There's also subcamps.
Those who have tried it and
1) actually gave it a fair shake - and are now evangelize it, and
2) people who simply didn't give it a fair shake due to a) time restraints and b) just not having an open enough mind to genuinely try it out
And then those who haven't tried it but
1) have never heard of it, and
2) are unwilling to try it because they are stuck in their ways (I view these people as being in Plato's Cave, or the matrix, or a cult - even though us evangelizers obviously sound like we're in a cult). They're in an even sadder position than those who tried it half-heartedly.
All such sorts of people are on display in this thread. I hope us zealots have been able to convince (hopefully via education) at least a few to try it out.
And also to try jjui, which is incredible.
https://github.com/idursun/jjui
> I have seen a 100% conversion rate from everyone who gave it a serious try.
Cognitive dissonance is a thing.
my heuristic is:
do you know about git rerere?
if yes - try jj.
Fwiw. It took me 4 or 5 attempts of various lengths (2 to 10 days) before I actually sticked with it. I've hit unimplemented deal breakers and was generally uncomfortable a lot. Now I like it more than git, but I don't think the difference is large and stacking commits is a bit of a pass time for me. I do it more now because it's easier but I would just do less of it with git and no one would care much.
I always go back to using mercurial for personal projects. Better than both
'Doesn't use Mercurial' was top of my requirements list last time I left a job that did use it.
Can you say more about how it's better than both?
Not typing hg is the best part of my day
I tried it and I went back to git. I know how git works though. I don't put much stock into your 'anecdata'
You can consider your perfect conversion rate now broken; I tried jujutsu on a personal project and found it to be a hassle without any upsides.
I believe your perception is flawed because the people who just try it and throw it away don't tend to talk about it because it's not popular enough to warrant even a twitter comment. This is the first time I got the impulse to share this but only because you claimed a rather silly 100% conversion rate.
Care to elaborate on the hassle and lack of upsides? Have you considered that you simply didn't give it a fair shake or were holding it wrong?
I don’t think this is a good approach toward getting people to switch. It’s unnecessarily combative.
Even though the parent comment was outrageously combative, you're probably right that it wasn't necessary to reply in a much milder, but similar fashion.
Eh, not really.
I've used it and I like it but I couldn't find a good plugin for Neovim so I reverted back to my old more well-supported workflow.
this still doesn't imply it is worth checking out.
> 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.
When it's our first time, the tissue damage caused by the training can weight a lot to the point we could not even be able to sit properly in front of the desk during the first couple of weeks.
[dead]
I started doing jujitsu a few months ago. The title of the article and this top comment had me bewildered until I remembered what jujitsu was in this space LOL
If I had an option, I would still be using something like Subversion or Mercurial.
As it is, I go with whatever our clients require of us, and that isn't jj.
You're misunderstanding. You can use it with any git repo without anyone knowing.
Usually I am pretty much against snowflake approaches in project delivery, setup should be the same for everyone, as people change roles across projects routinely.
The delivery is the repo and its commits. Recommending a setup is one thing, but enforcing particular tools has about the same level of appreciation among any senior dev I know as enforcing a keyboard model.
Those senior devs don't work on the kind of industry that I work on, enterprise consulting with high attrition of team members, and possible offshore teams.
This kind of industry work needs factory line mindset, there aren't special knobs for someone on the sidelines working on the software rolling carpet, which granted isn't for everyone.
Can you choose your own IDE? if so, you should be able to choose how you manage git locally, such as with jj.
Nope, you get the IT validated image as per project delivery assignment, in many cases not even local, for security reasons it is a cloud VM.
Ok, so I'm just having trouble seeing how your situation is at all relevant to this discussion about the merits of jj. You simply have to use what you're assigned.
It isn't, you decided it was, reposting my comment.
> If I had an option, I would still be using something like Subversion or Mercurial.
> As it is, I go with whatever our clients require of us, and that isn't jj.
I share no desire to use jj in those words.
But how is that a relevant comment for this post about jj? People are talking about one thing and you're saying "I either can't use it at all, or am just happy with something else already". There was no discussion of the merits or shortcomings of jj...
All I was saying is that if a client requires you to use git, you could use jj without them knowing/caring - unless your entire workstation is under tight control, in which case, again, it's not relevant to the discussion
The main things that drives me crazy about jj is that all changes are always staged implicitly. This is what SVN did back in the day, and git was a huge improvement by staging changes explicitly.
I almost always have more changes in my repository that those which I want to include in the next commit. With git, I just add the changes I want. With jj (and svn), there’s not obvious way around it—you have to manually copy-paste changes outside of the repository before committing.
I think the workflow in JJ is a bit different in this case, you can try to have a base commit `jj new -m 'base'`, then create an anonymous commit on top `jj new`, then make some changes in the anonymous commit and when you are ready to send out a PR/MR you squash `jj squash` or split `jj split` and squash what you need in the base commit.
`jj commit -i` (or a lot of commands `-i`) and maybe `snapshot.auto-track="none()"` in the config, to a certain extent is what I use. I used to do the same with mercurial. In practice, I also use `absorb` a lot, that leaves unrelated files and chunks alone.
Isn't "jj new" what you need?
jj is actaully great at this since it natively tracks branches which aren't heads correctly, so all rebases and merges work without stashing.
You could disable autom snapshots or use `jj split -i`, which I use almost exclusively
I just discovered interactive split last week. Absolutely beautiful.
I feel pretty dense, because I still struggle to get my head around automatically adding changes to a revision. Sometimes, I'll make a change locally to a file that I'll use during the development process that I have no intention of committing. With regular git, I never stage that file so there's no danger of accidentally pushing my change to the remote repo, but it seems with jj I'll need to somehow unstage that change or something to prevent this. Perhaps it's just habit, but I feel more comfortable explicitly saying what I want to commit rather that defaulting to everything. Or have I totally misunderstood jj?
In jj you tend to use `jj split` to break changes apart. The selected bits become the first revision, the remaining bits become the second revision.
I tend to do a bunch of work then split into small, bite-sized revisions. Often I split something out to a parallel revision (e.g., a separate branch) if it’s an independent thread of work like a bugfix elsewhere or a documentation fix. This is an obvious one-liner in jj but a bunch of annoying branch-switching and stashing in git.
You can also use a `git add`-style workflow. Create a new revision with `jj new`. Do it again. Make your changes, then `jj squash -i/--interactive` to select the bits you want to include. Keep making changes and squashing into the previous commit you’re building up until you’re happy. Conceptually just think of @ (the current revision) and @- (the previous revision) as the working copy and the staged copy, respectively.
I feel like I already lost more time reading this than jj would make me win if I switched :-).
In the first year of switching I easily saved more time and frustration than the sum total of all of my jj evangelism on HN.
jj's auto staging isn't always desirable. I feel jj docs and evangelists should make clearer that it's easy to turn off by default, by adding this to ~/.jjconfig:
I usually work, then do `jj split` to review changes I want to make commits to. This generally makes the workflow look like `git add -p`.
A decent mental model is that the top-most commit isn't generally going to get pushed up anywhere. It's like your working copy but also you get stashing "for free" (change back to main to make a new commit? All the WIP stuff stays on that branch instead of being carried over to main!)
No that is correct, and also a habit I am trying to break. The reasonable argument is that we should stop running code with untracked state. Either the changes are important and should be committed or not. Otherwise you are recording code versions that never truly existed during development.
Where this gets extra sticky for me is tooling which refuses to distinguish repo wide config vs a local only version. VSCode being a huge offender where there is only a ‘launch.json’ and no ‘launch.local.json’ suitable for per host customization (eg maybe I am already running something on port 8888, so I need to map it to 9000, that does not mean a quirk of my environment should be committed).
Counterpoint: Why should my println debugging get committed? They're not "important" for the final product but important for development.
you've obviously never encountered code which only works when println is added.
The logic is once you are ready to commit you delete all of the debugging stuff. Otherwise you are committing an illusionary state of the repo that only existed by manipulating the stage.
I am a black kettle here as I frequently commit individual lines amongst a sea of changes, but I do appreciate the theoretical stance of jj.
A tool like pre-commit really helps here: it'll run against the staged files, before committing. Your CI tool really ought to be testing the commit too, at which point having a clean commit locally isn't necessary for correctness, only for avoiding needing to re-do your work.
It's really important to catch bugs early, because it's a lot more expensive to fix them later -- even if only as late as in CI before merging.
Just to be clear, jj makes it really easy to carry this sort of thing as a separate patch, so while it may be "committed," that doesn't mean it has to go into what you send upstream.
(though for debug printfs in particular, the Right Thing is proper logging with log levels, but I myself love printf debugging and so sometimes don't do that either. Which is why carrying local patches is nice.)
There is no Right Thing here. Practicality beats purity. The product (a snapshot of the source tree) should do what it needs to do, but getting there is not the product. It can be if you want it to be, but there is no upside to that.
I think most people would use a logging library (maybe at the "trace" level) at that point.
If you want this workflow, you can treat jj's `@` (nominally equivalent to git's HEAD) as the git index, then at commit time, manually squash changes to `@-` just like you would with `git add --patch`.
When I've asked about using a jj stage, that's the workflow people pointed me to, as described in this tutorial [0]. I've not tried it, nor jujutsu, but I felt it worth pointing at a concrete example to allay or stoke fears of people on the fence.
[0] https://steveklabnik.github.io/jujutsu-tutorial/real-world-w...
FWIW, @ is a shortcut for HEAD in Git.
[flagged]
...no? On all counts.
`jj ignore` is not a command. [1]
There is no such thing as `.jjignore` files [2], [3].
`--no-snapshot` is not a flag, neither on `jj edit` nor in general. Additionally, it doesn't make sense conceptually --- `jj edit` changes the working copy, which will then propagate changes to the selected commit every time you edit a file.
If you want to check out an old commit without amending it, you `jj new` on top of it. If you end up making changes that you don't care about, you can `jj abandon` them.
[1]: https://jj-vcs.github.io/jj/latest/cli-reference/
[2]: https://github.com/jj-vcs/jj/issues/3525
[3]: https://jj-vcs.github.io/jj/latest/working-copy/#ignored-fil...
I've been using jj for a few weeks, and recently made an auto-commit-message script for it[1]. Just today I started working in an old git repo and thought that I should port the script to git. It turns out that jj made the script trivial because of immutable commits, and mt script would need to automatically do that with git: I use a rebase/amend/clean history workflow at work, so I would need the script to determine when to commit or when to amend. It's obviously possible, but I don't want to expend the effort - I just re-cloned it with jj.
It's amazing how quickly I forgot about the commit vs. amend papercut.
[1]: https://codeberg.org/jcdickinson/nix/src/branch/main/home/co...
You don't even need to re-clone. You can add jj to an existing git repo.
I’ve been trying unsuccessfully to convert my team to jujutsu. I feel like what would be great is a page that really shows some common but complicated operations in git and how much easier they are in jujutsu. Something like the elevator pitch here but expanded on without the depth of Steve’s tutorial.
Maybe what I need to do is do a demo so people can see and ask questions.
> I feel like what would be great is a page that really shows some common but complicated operations in git and how much easier they are in jujutsu.
What I find isn't that common git operations are easier in jujutsu. They're not; sometimes they're slightly harder, due to the impedance mismatch with the git backend.
Rather, what git makes easier are operations that are next to impossible — or at least highly inconvenient — in git, and which therefore next to no-one does. That makes it harder to explain, because you're telling them there's this great new workflow that does stuff that... they don't think they need (they have workarounds), and the notion of which triggers their ick reflex if they're good at programming.
I do understand that point, but to me it sounds like "you should use jj because it's a lot better at solving problems you don't have".
If there are really common use-cases where git is annoying and jj is great, it shouldn't be that hard to explain, should it? If you can say "remember how in the last few days you struggled with this? Jujutsu solves it", then I'm happy to try.
If your argument starts with "imagine you are in a team that looks like X (but your team does not), with a project that looks like Y (but your project does not), and now imagine that you need to do this thing that you have never done before...", then maybe I actually don't need jj?
> "you should use jj because it's a lot better at solving problems you don't have"
It is more like "you should use jj because then you won't have a lot of problem with git that you'd need git to solve"
> it shouldn't be that hard to explain, should it?
It is not. There are plenty of example on this page. For me the biggest one is stacked PR, jj makes it trivial since it tracks the change sets and not commit ids (which are immutable). So you can work on any level of the stacked PR independently, once you're done run "jj git push -r '(trunk()..@ | @::)'" and it will update all the remote branches accordingly. Another feature that works great with stacked PR is that you don't need to solve conflicts right away. You will see a marker in the "jj log" and you can solve it later down the road.
Also another great feature is the operation log, you can just rewind your actions. F'd up a conflict resolution? Just go "jj op undo". That goes for everything, including file changes and rebases. Want to go back the state it was 15 min ago because you didn't like what you did? Merge to the wrong place? "jj op undo"
Adding to that there are hundreds paper cuts that jj fixes, like:
* Simpler mental model for local change, no git stash/add necessary.
* Simpler commit process, you can just work and use "jj describe" whenever where git forces you to write message before creating commit (again because commits are immutable).
* Starting a work is much easier, I can just go "jj new" away without caring about detached head. Nowdays I just use branches (jj bookmarks) for git compatibility reasons.
* Revsets are amazing, much more powerful and expressive than git logs, and since the UX is more consistent you can always work with set of rules that expects a revset with "-r".
Ofc, you can do all of that with git, but it just works better, easier and more consistent with jj.
> It is more like "you should use jj because then you won't have a lot of problem with git that you'd need git to solve"
I don't have a lot of problems with git that I need to solve, that's the thing. And I don't get why people keep trying to convince me that I do. It's about me, my opinion should have some value, right? :-)
> It is not. There are plenty of example on this page.
The problem is that many examples, to me, sound like it's exactly like git but the author of the example doesn't know how to do it in git. For instance, you wrote a whole paragraph about "undo", as if git did not have that feature. Isn't that exactly `git reflog`? Turns out I had a need for it 2 times in the last 10 years, and it just worked.
> Simpler mental model for local change, no git stash/add necessary.
I can deal with git stash/add without feeling like I'm thinking hard. This is a class of examples that makes me think that jj is for people who are not comfortable with git.
It feels like the people who use jj tend to somehow get stuck on detached head with git, and that's a big problem for them.
Again, I'm not saying that jj is not cool, and probably I should try it. But I see a ton of comments that really, really sound like "I can't believe people still do basic arithmetic in their head: they should get a calculator and they would see how superior it is. With a calculator, you never make those frequent and annoying mistakes like 3+5=9 again! Plus you can do 403985/13 easily!". And when I say "I usually deal with basic arithmetic that I do just fine in my head, and I don't actually frequently make mistakes like 3+5=9", I feel like I sound like an elitist.
I can't remember the last time I had to do something "hard" in git. So it sounds like jj may make something simple slightly simpler, at the cost of dealing with a new tool.
> I don't have a lot of problems with git that I need to solve, that's the thing. And I don't get why people keep trying to convince me
You are in *forum* in a post *about jj* saying there is no reason to use jj. We are just interacting as you'd expect in forum. If you don't see any reason to use anything else and don't want to hear anything about it steer away from these posts.
No one is trying to convince you personally, we are just discussing the tool. Go use git and be happy.
I sincerely won't read anything else past that line.
> You are in forum in a post about jj saying there is no reason to use jj
I honestly am not. I am genuinely interested. Everytime there is a post about jj I'm about to try, and then I see a comment like yours, saying "with jj you just use the intuitive syntax `jj git push -r '(trunk\(?.\).x#@ | @@.^)'`, which is a lot simpler than knowing about this weird concept of stash" and it makes me think that maybe, I'll try again next time.
> I honestly am not.
You literately are, look at you comments.
> I am genuinely interested.
If you are interest in learning you should take people words on good faith, my comment on "jj git push -r '(trunk()..@ | @::)'" is complex operation to update many stacked PRs not compared to local git index operations. Also in my comment I mentioned revsets and the "-r". It is a language to query logs which you should've check if you genuinely wanted to learn about it, which you don't.
Just look at your answer, you literately ignored everything I mentioned and took what I said out of context just to bash on jj for some reason.
> just to bash on jj for some reason
Actually, to be blunt (you're already offended anyway): what I'm saying is that I find (personal opinion) that many evangelists here (you included) don't sell jj really well. If so many people are actual fans of jj, there must be something there. I'm just struggling to find it between the "you're probably too dumb to understand a stash so you should use jj" and the "let me explain to you this great concept that jj has which allows you to undo changes in a way that sounds like git cannot do exactly that".
So no, I'm not criticising jj :-). And I'm not convinced I need it.
My initial rant to your comment was about this:
> And I don't get why people keep trying to convince me that I do. It's about me, my opinion should have some value, right? :-)
I have no problem whatsoever if you don't like jj or use git or whatever else you like.
Let's paddle back:
- you are in post about jj
- you comment why you don't like jj
- I comment why I like jj
- you post that.
Like, what is your intention here? If you don't have good faith into trying to understand the tooling, how would it fit your workflow and deflects everything with "I know git, git works, don't tell me to use something else" what is your goal here?
My problem is not your opinion on the tool, is how you approach the debate.
I didn't mean it to sound like this.
I have been asking a few questions, and I have received many answers quickly. Which is usually great, but between the "those who don't use jj haven't seen the light and must be in a cult" and the "jj is better because git is impossible to use everyday", it's honestly been harder than anticipated :-).
I don't especially like git. I stuck with mercurial for a long time.
But that was ten years ago. Now git is kind of hard-wired in my brain. By and large, it works well enough.
It's not really clear to me that Jujutsu offers a significant enough of a benefit to spend the time re-wiring my brain, never mind dealing with the initial setup (e.g. the unreadable colours, setting up some scripts/aliases for things I like).
Yeah I find abstraction layers suck a bit. Same with miso and uv (for python). They don't "just work TM" and now I have 2 problems. By just work I couldn't install miso onto a fresh Ubuntu without 404 errors and uv kept bitching about my pyproject file instead of just working or trying to fix it.
Some of these tools do just work. E.g. nvm seems to just work and is much nicer than raw node installs.
Note that jj is not a frontend for git or an abstraction layer over git — rather, the git object store format is one of at least two backends to jj.
Thanks. That would make me interested in it for a personal project where I only use jj.
There's really no cost to trying it out on an existing project with other people. It natively interoperates with git. Nobody needs to know you're using something different, and you can always fall back to using git commands if you need to do something you haven't learned yet.
What is miso?
Possibly means mise (https://mise.jdx.dev/) which is a tool similar to uv in the sense of managing versions.
Mise and jj (and the jjui TUI) are my two favourite tools, by far
Correct mise. Sorry!
> By and large, it works well enough.
be wary of the stockholm syndrome. I too love git, but it is a very cumbersome tool for some workflows.
But does Jujutso solve those workflows, or does it add a layer on top that you need to figure out on top of what you already know about Git?
I don't run into any issues with Git during my day to day. Some things could be a bit more ergonomic but that's because I'm too stubborn to learn or set up some aliases or look up how to do it.
Following this tutorial, I can't say jujutsu feels like an improvement. More complicated log output (I use a shorthand (think "git log --oneline --graph" but with a custom output format to also show absolute and relative commit time and author)), automatic staging (that can be turned off but why should I when it's already off if I don't use jj?) while I use "git add -p" through an alias, and the rest is just push / pull / checkout / reset.
jj solves one particular issue I have with git and github: truly easy (not just 'possible') rebasing of branches and editing commits which are not heads. You need to commit conflicts to solve this properly, which is something git really doesn't want you to do.
I think jj just uses the 4 bit (16 color) terminal palette, so if a color is unreadable, you can update your terminal theme accordingly.
Jjui is by far the best TUI for jj and just released a Themes feature yesterday. It's well worth checking out.
https://github.com/idursun/jjui
This has been the case, but it's worth noting that very recently, support for theming with the 256-color palette has landed too https://github.com/jj-vcs/jj/pull/6763
Cool! Don't imagine it would ever be the default, though.
I hope not, for sure.
Some programs set the background colour and some don't. For example pamix sets the background to black, or tmux's statusline, or ngrok. It's not really possible to define one terminal scheme that always works.
You can also configure jj to use whichever colors you'd like, in either the 16 or 256 color space: https://jj-vcs.github.io/jj/latest/config/#custom-colors-and...
Of course! But why even bother when everything I have to do I can do with git without any problems?
I never said you should. If you like git, use git. One of the nicest things about jj is that it doesn't require others to use it.
I think it's reasonable to assume that all 12 colors that aren't black or white will be readable on the default terminal background. I sympathize with those for whom that isn't true, but please do find a different theme in that case.
How would that work with applications that hard-code the background to black (or some other colour, like tmux's green statusline)? If you change the colours to work on a light background then those break.
You have to choose what you want to break. Once you start pulling on one thread lots of stuff start to unravel. It's really not a simple matter of "choosing a better theme".
I've spent a long time looking at this, and my conclusion is that there is no safe default that will work for everyone, other than bold/reverse and 256/true colours and setting both the foreground and background.
If you hardcode the background to a fixed color, the 4-bit palette for foreground colors is generally to be avoided -- instead, use the 8-bit (256 color) or bigger palettes.
I don't control these applications.
If I understand correctly, you're in the situation where you've chosen your 4-bit colors so that they render properly on an application which (incorrectly) uses an 8-bit background color but a 4-bit foreground color, and also that the colors don't render well on the default background that's part of the same 4-bit palette.
I think you'd be a great candidate for jj's ability to customize colors.
I don't choose anything; I just use the xterm defaults. Some colours inherently conflict. Blue on white is fine. Yellow on black is fine. Yellow on white is not. White on white is even worse. There are many combinations that don't really work brilliantly. Some applications set background colours, some don't. pamix, ngrok, and npm are examples of applications that hard-code background colours. Configuring the terminal to use text colours that work well with both the default white and hard-coded black (for some applications) is hard.
That I need to spend a bunch of time setting all of this up (among other things) is exactly how this thread started.
What situation do you have where jj is rendering colored text on top of a background you don't control? I'm perplexed.
So do you suggest we should avoid setting a color in our CLI app? We would take this into consideration in our next pinggy.io CLI update.
This. Exactly this.
Not a whole list of operations, but this comparison of one common operation between jj and git is what made it click for me.
https://lottia.net/notes/0013-git-jujutsu-miniature.html
I think that comparison is unclear and unfair. The core is that instead of:
the recommended Git alternative is: This is a process I use heavily, and one of the rare cases where I prefer the Git way: less cognitive load (I don't need to memorise options b/s/r/d/A/B for `jj rebase`) and the interactive editing of the history feels simpler (especially if I move several commits).I've used jj for a few weeks, but switched back to git. I'm fluent enough with Git so I never struggle any more. jj mostly felt nice, but the added value was not enough to replace years of expertise.
I find
to be an excellent improvement over anything git provides, including rerere. It's the first patch queue for git implementation that actually worked for me, which in turn makes github PR UI somewhat bearable.I occasionally use -r, too, but most of the time it's better to
This article convinces me that what he wants to do is easier in jj, I just don't understand why he wants to do it.
My quick summary is that in one case he's try to avoid "extra" commits and in another case he's trying to re-order some commits. In my usual work flow, both of those problems would be handled by the git-rebase-squash I do after the feature works.
See the other replies: the point is that jj makes these operations so trivial that if you have the slightest whim to do them, you can just do them.
Thanks. This is a really good one. It outlines an operation that would resonate well with most developers and clearly demonstrates how much simpler, easier, and faster this is in jujutsu vs git. I think most devs just wouldn't even bother to do it in git, they'd leave the test out of order and call it a day.
Yep, I think that's right. As the other reply says, this is part of what makes it hard to explain. If you told me jj makes it easy to rebase all the time, I would ask: why do I want to do that? Now I don't even think about it, I just rebase all the time.
Here's a few great links that might be what you're looking for
https://v5.chriskrycho.com/essays/jj-init/ https://v5.chriskrycho.com/journal/jujutsu-megamerges-and-jj... https://ofcr.se/jujutsu-merge-workflow
Ah I love that megamerge example. Thanks.
I think you'll probably like the next part I'm going to add to this post, then! I'm sure it'll get posted separately, but it'll also appear at the bottom of the page linked here when it's up (hopefully soon, as in the next few days).
Don't want to sound old school, but git works perfectly fine.
The learning curve might be a bit difficult, but afterwards everything makes sense. And let's be honest, you just need a few actions (pull, add, reset, branch, commit) to use it in 95% of the cases.
Stacked diffs are a core part of my workflow, letting me “work ahead” without being blocked waiting for reviews.
Setting them up in git is not to bad. Adding a change to the bottom of the stack, and restacking everything on top… that’s hell in git.
Check out the rebase.updateRefs option: https://dev.to/onepoint/git-update-refs-in-a-nutshell-574c
git rebase -i lets you reorder commits easily, as long as they don’t have conflicts. If they do, you’re in for a rough time. I struggle to see how a vcs could help with that, even if I’d be happy to be proven wrong.
One cool tip to help with conflicts is the revert trick. If you have a conflict that you need resolved earlier in your commit chain, you can commit a cleanup that hides the conflict, revert it instantly and reorder the commits with interactive rebase to insert the revert first. It’s a bit hard to explain without an example, once you’ve tried it you will understand.
> as long as they don’t have conflicts. If they do, you’re in for a rough time. I struggle to see how a vcs could help with that, even if I’d be happy to be proven wrong.
A vcs can allow you to commit conflicts and then commit their resolutions whenever necessary. This has been pioneered by darcs (IIRC) and jj also allows that.
Try git-spice!
If you work in a team you need to understand rebasing and squashing, unless you can convince team to never use these features.
A lot of people are religious about rebasing, "clean" commit history. But it's pretty much incompatible with several devs working on a single branch. I.e. when you work on something complex, perhaps under time pressure, git habits bite you in the ass. It's not fine.
As soon as you have the situation of multiple people working on the same branch, forbid force pushes at all times. But it's better to avoid that in the first place, all work should be on its own branch at all times.
For larger features we often have a feature branch with merge requests for various task branches. Limits the review sizes as well.
Of course, you can also then consider to use feature toggles, so there's no feature branch but just your main branch.
> A lot of people are religious about rebasing
Years and years ago I worked on a team where linear commit history was required so every time a merge happened you had to manually rebase and push to Bitbucket. I put a PR in halfway through the sprint, rebased it a dozen times as everybody else's work got merged, and had nothing to show during review because nobody bothered to check mine and the only coworker I had in the same physical office was out on vacation.
When I interview for new roles I always make a habit of asking how work gets done to avoid shops that engage in these types of shenanigans.
A lot of the time, multiple devs working on a single branch can be avoided via different decisions made upstream about work that needs done. If my job included more git wrangling as one of my daily tasks I would probably hate my job.
just when you face some unusual situations ...
I've been using jj for two weeks now and it's kinda exciting, because for the first time I'm comfortable with using version control just via the command line. With Git I always had to use a GUI (preferably Git Graph in VSCode) and launch all operations by right clicking items, but jj was simple and consistent enough that I could just start using it after reading Steve Klabniks tutorial.
The thing I'm running into right now is that I should really learn the revset language, so that I don't have to constantly copy paste ids from jj log
I'm in a very similar situation: been using git for a long time, but anything more complicated always via some kind of UI (often intellij).
Been using jj without significant issues for about a month and been super happy to be comfortable using the cli and slowly ramping up to more complicated operations.
The documentation still assumes a lot of inherent knowledge which sometimes makes it a little difficult. I love seeing blog posts like these and hopefully some more in depth resources will appear over time. Steve's guide is good, but there are still gaps for me :).
Next I want to learn some more revset language and become a bit more fluent with rebase operations. I love the more simplified cli, conflict resolution and op log!
I had the same copy paste problem, now I use jjui (https://github.com/idursun/jjui). The daily operations become even smoother, it looks like I’m flying over the log.
This is The Way. jjui makes jj's power and simplicity even more powerful, simple and beautiful
Went through the whole article. Tried out some commands.
I haven't 'got' it yet. I am sure I will after some regular use.
But being a long time Magit user, its hard to move to anything else.
I'm confused what this is?
Is it just a git frontend for people who are confused by git?
It says it abstracts the backend, but it's not clear how something so git-influenced will have abstractions that work with something like a centralized system like Perforce or Piper that has auto-increment numeric commits.
Some of the design decisions are also not great. Working copy as commit means you have no quality control. The whole point of a commit is that... you commit it. So now you need a separate repo or filter to remove the worthless changes from the ones that you actually intend to commit. Bad commits polluting git histories is already a big problem.
The biggest problems with git IMO are it scales poorly and it encourages commit pollution. Presumably jj isn't trying to tackle those problems, which is totally fine. But I am confused about which problems it is tackling. That's why I'm wondering whether it's just a frontend that the author finds more to their liking.
> It says it abstracts the backend, but it's not clear how something so git-influenced will have abstractions that work with something like a centralized system like Perforce or Piper that has auto-increment numeric commits.
Its Piper backend honestly works better than the Git backend. Which isn't a knock on the Git backend, but the impedance mismatch is worse there.
> Some of the design decisions are also not great. Working copy as commit means you have no quality control. The whole point of a commit is that... you commit it. So now you need a separate repo or filter to remove the worthless changes from the ones that you actually intend to commit. Bad commits polluting git histories is already a big problem.
Sorry, but saying this implies you haven't tried it. :-)
Jujutsu commits aren't equivalent to Git commits. They're implemented with a mixture of Git commits and the Git working tree, yes (if you use the Git backend!), but when you see 'commit', you should read 'named diff'.
Jujutsu also has a notion of immutable commits, by default meaning (roughly) commits which have been pushed upstream.
You can and should rewrite the un-pushed commits to clean up history prior to pushing changes upstream. jj makes that much MUCH easier than rebases and history edits could ever be with git. Most of my nontrivial jj work involves at least three or four commits at some point, everything from experiments to documentation branches, which with git I would have needed to awkwardly fit into stash or inconvenient throwaway branches.
> Jujutsu also has a notion of immutable commits, by default meaning (roughly) commits which have been pushed upstream.
This makes sense to me as a longtime Mercurial user. In short, by default, when you push a commit, it becomes public and therefore immutable [1].
Other awesome Mercurial features that appear in JJ are revsets [2], filesets [3] and templates [4]. Looking forward to giving JJ a try.
[1]: https://wiki.mercurial-scm.org/ChangesetEvolution
[2]: https://jj-vcs.github.io/jj/latest/revsets/
[3]: https://jj-vcs.github.io/jj/latest/filesets/
[4]: https://jj-vcs.github.io/jj/latest/templates/
Thanks, can you link me to their perforce backend code? I don't see the string "perforce" or "p4" anywhere in the code and it's not coming up on search.
> You can and should rewrite the un-pushed commits to clean up history prior to pushing changes upstream.
My concern isn't just about pushing upstream. What I'm saying is that the typical change to a file shouldn't be committed to my local repo until I indicate that it's ready. The FAQ suggests this isn't possible and that you should work around it by using a separate branch and then merge into your target branch, which is a pretty ugly workflow.
Their GitHub branches are a mess of auto-generated strings, which suggests to me that this problem isn't just an abstract concern but is a form of technical debt that the jj devs are currently piling up.
> What I'm saying is that the typical change to a file shouldn't be committed to my local repo until I indicate that it's ready.
Yes, it's "committed", but that doesn't mean all that much. I have the fsmonitor feature enabled that continually watches my repos as I edit files in them. This means that over the course of a coding session, the revision I'm working on has probably pointed at dozens or more ephemeral underlying git commits. Those are only kept around for the purpose of the evolution log, which lets me look back at my edit history throughout the day even without having interacted with the repo.
When you're ready to "finalize" your work, you have two common workflow options: you can `split` the out parts you want to keep as one consistent commit, which dumps the rest in a subsequent revision. Spiritually this is equivalent to `git add -p`. Alternatively, you can create an empty "staging" revision before your "working copy" revision and `jj squash -i` pieces you're happy with into there. In practice, many people use both. I generally do the former for new work, and the latter to make changes to earlier work.
Thinking that `git add -p` was absolutely a deal-breaker is the main reason I passed over jj for so long. I care deeply about maintaining a clean commit history with small, isolated, and individually-tested changes. I thought jj would make that harder due to not having a staging area. I was wrong. It is actually far easier to be principled about your commit history with the tools that jj gives you.
I don't mind the workflow of selecting what you want at a later time, but my concern is with having changes visible to the local repo database automatically.
If you like it that way, that's cool with me.
But to me personally I'd rather have the filesystem doing snapshots and not comingle fs snapshots with VCS. I can nuke an fs snapshot in a single line of bash if I accidentally leak something, and fs snapshots don't contribute to the slowness of a big repo. But those are problems in regular git and would be even bigger problems in a version of git that treated VCS as if it were an fs snapshot system.
But like I said if that works for other people that's great. It would probably work for me if I were a consultant working on a lot of small repos. I don't think it scales to a bigger repo unless the jujutsu repos are completely destroyed every few days, which is essentially what happens to CITC client working copies at Google.
> I can nuke an fs snapshot in a single line of bash if I accidentally leak something, and fs snapshots don't contribute to the slowness of a big repo.
Leaking isn’t really a concern; none of these ephemeral commits ever leave your local machine. I have used jj on a very large monorepo with > 10yr of commits, and I never noticed a performance issue.
jj was literally created by Googlers at Google for the purpose of being used at Google. I have no idea how that’s going, but it makes me suspect that it works better at that use-case than you’re giving it credit for.
> What I'm saying is that the typical change to a file shouldn't be committed to my local repo until I indicate that it's ready.
You're thinking in very git-specific terms. JJ is just different in many ways so such comparison doesn't work well. I didn't get it from explanations before, but trying it in practice, it's a non-issue. It may be easier to just give it a go.
> My concern isn't just about pushing upstream. What I'm saying is that the typical change to a file shouldn't be committed to my local repo until I indicate that it's ready.
jj doesn't have the concept of a non-committed file.
instead you don't track the HEAD as the tip of the branch (@ in jj), you track HEAD^ (jj: @-) or something earlier and since jj rebase isn't dumb as a rock you can relatively easily keep the working set of changes (as in, multiple commits/WIP branches; not necessarily the index or the working copy) on top of what you are pushing and squash or advance the branch (bookmark).
I'm reiterating because it's a very important and super confusing for advanced git users: there is no uncommitted state in jj and yes, it does make sense, but you need to stop thinking in git.
Can't this be a security risk as there might be secrets that should never be recorded by jj (You can probably configure this, but I want my software to only do what I tell it to, not do everything until I tell it not to.)
How does that work in practice, is there a daemon running constantly and monitoring? How does that interact with other users and changes from other computers?
Secrets and client data. Regulated environments are very serious about client data. They can't go into source control either.
You can't change example.com/foo to point to example.com/baz and have it exist in source control. So to make the change you have to ignore every file that contains that string, test, then unignore. Or you have to make sure to absolutely remove every reference you've added to the merkle tree. Or you have to completely nuke your source control database after testing.
If you buy a used laptop hard drive from a remote worker at $BIG_REGULATED_CO because you want to find exploits, you're probably happier if they run jujutso than git because there will be a higher probability of finding secrets, client info, and other things that are forbidden from going into source control.
Every jj invocation stores a snapshot before doing anything else - thus anytime you run jj, you tell it to store a snapshot in the local repo.
I don't see a problem with secrets TBH. .gitignore is respected if that's what you have in mind. Otherwise store your secrets out of tree (you probably should with git too, anyway.)
Right now I can mess around like I want, because I am in charge what gets tracked, I need to specify it explicitly. This would loose that.
this is not as important as you're used to. I've been there, I've used git since early 2010s and yes autostaging was preposterous at first. in practice, it's barely relevant: the workflow changes from 'I pick what is tracked' to 'I pick what is pushed'.
I'm not sure if objects won't get pushed accidentally. I would place the boundary at object creation not upload. Also you might copy the repo, then you also might leak an accidentally created object.
to each their own. you can argue in theory, in practice it is just not a problem.
You know your evangelists have gone haywire when they're explaining that your requirements are wrong instead of listening.
I don't think that's a fair characterization.
It's more that there isn't really a big difference between the workflow of
vs You're still "in charge what gets tracked" if you treat the last @ commit as your local playground, exactly the same as the staging area. The only difference is that you can use exactly the same tools to manipulate your "staging area" as other commits. The only difference being that you can manipulate your staging area with the same tools as any other commit.Wait, what happens when there is a multi-GB file laying around and a jj command is being invoked? Does it start to scan it or is there some threshold. What does it do with cyclic hard-/sym-links?
I'm far from being an evangelist. I'm trying out jj in one repo out of several and it's far from perfect - but it is useful though, and it does make sense. The requirement of being able to not commit everything by default is simply not necessary from a logical standpoint; if anything, it is itself evangelism that the git way is the right way. The truth is both work just fine, which has been my point all along.
> My concern isn't just about pushing upstream. What I'm saying is that the typical change to a file shouldn't be committed to my local repo until I indicate that it's ready
Since you brought this up, I've noticed some people seem to work this way but I've never found anyone to ask why they do this.
I get the idea behind clean, readable git logs with nice consistent messages, but isn't that what rebase/amend is for?
To me, the status of my local git log is mostly irrelevant, the thing that really matters is the commit(s) I submit for merging.
Also, probably for related reasons, I've never truly understood why git has this whole separate staging concept...
I also work this way. A commit is really about, well, committing that these are exactly the right lines of code I intended to write. I also read them in another program, so it gets easier to not think that I already know the code and skip over. I basically start from the clean state (no changes) again and approve the changes line-by-line according to whether they fit the domain model of the program, whether they are in the right places, are the right amount of complexity/abstraction, if they are readable/understandable. I typically also add/remove comments at that point, because I've now tried to read the code with a "second pair of eyes".
I also use this to compare the changes to the commit messages. Ideally the changes follow from the message, not the other way around.
While a VCS is also useful for adding a time-axis to code, for me the main selling point is, that it's adding causality to the code. You can ask 'Why is this code how it is?'. (Of course causality is a side-effect of time.) When you don't curate the commits you completely loose this feature. (You can still ask, but the answers will be confusing and need way more work, which for me amounts to manually comparing commits to grasp the big picture and intention.)
> rebase/amend
When you messed up, you can of course change commits, but this has a danger of creating a version that was never there or mixing history, for example combining a single physical change, that are multiple logical changes. In order to test that the code at least works I use ```git rebase --exec="make -C build distcheck"```, not sure if that is common.
I also don't use MS GitHub -style merges, I find them inferior. (I also don't use MS GitHub, but that's for another reason.) I think they try to make merges the actual unit of change, which is stupid, because that is what a commit is for. To me merges are about semantic grouping of commits, i.e. maintaining a tree of commits representing the logical evolution of the code.
Yes the staging area is exactly designed for this approach and I don't like people telling me I'm holding it wrong and don't need it. To use a metaphor, I think the staging area is like my desk were I'm producing stuff (I'm not producing code, I'm producing changes). Committing is about having produced a complete opus and then cleaning the desk. Cleaning isn't seen as annoying, it is important for the mind to process completing/validating and then starting afresh. Stashing is in this metaphor about switching to an alternate desk. This is different from putting the opus aside and having also a clean desk. (I think that is what Jujutsu wants you to do?) Having the staging-area/stash different from commits is a feature, the fact that the stash is the same storage-wise is an implementation detail.
By default, the top (current) commit acts like a staging area and in fact is implemented as a dirty working tree in git.
Commits start live without a description, and can’t be pushed without adding one. jj commit names the current commit and creates a new empty, unnamed commit on top, which effectively is precisely git’s behaviour.
I implore you to try it. In practice the distinction just doesn’t matter, except it’s one less concept to keep track of, and jj allows you to make up other workflows if you want.
Sure, sometimes I want to work with a WIP commit and then I'm just doing that in git.
I think functionally of the stage is about the UI, so keeping it separate is kind of the point. I find that useful, as I can use both approaches, not sure how its better if jj can just use one?
> to ask why they do this.
This was what I was trying to explain. To me the feature is needing to manually mark every line to be committed. If I need to split the commit I'm already not paying that much attention.
jj’s workflow isn’t about being careless with commits—it’s about time-shifting that care to when it’s most convenient. You’d split the commit later for the same reasons you wouldn’t want your text editor to force manually marking each line to save to disk.
>A commit is really about, well, committing that these are exactly the right lines of code I intended to write.
That would be a ‘change’ in jj. A commit in jj is more like SQL than git; it's the end of a transaction, not a value judgement.
Thanks, I was writing about git, though.
> I also don't use MS GitHub -style merges, I find them inferior. (I also don't use MS GitHub, but that's for another reason.) I think they try to make merges the actual unit of change, which is stupid, because that is what a commit is for. To me merges are about semantic grouping of commits, i.e. maintaining a tree of commits representing the logical evolution of the code.
Thank you for going into details, you helped me understand a few more details.
Let me describe how I think about it and why it might possibly conflict with your ideas.
I think the best way to describe it is as a series of approvals, each one being more important than the previous one.
The absolute lowest level of approval, writing the file to disk. You've made some changes and you're happy enough with them to at least save them in case you have a power outage or something.
The next level of approval is committing to your local git repo. This is code you're fairly confident you want later, even if it might not be perfect yet.
The next level is pushing your branch to the origin repo. Now you're saying that this is code you're willing to let other people look at.
The last level is merging this code into main/trunk/whatever. This is the final level of approval, this code passes all of our checks, it shouldn't need any more improvement.
Given this system, adding another level in there, for staging commits, feels pretty unnecessary.
I agree that merge commits tend to make for ugly git history, but I don't think that's particularly inherent to any of these systems of code integration, it's more a function of how much the developers care about the git log.
> change commits, but this has a danger of creating a version that was never there or mixing history,
I'm not sure what the benefit here is. I think I've basically ended up in a situation where I treat 'git commit' the same way I treated "save file" 20 years ago. My local commits/saves/edits aren't important, what's meaningful is the final unit of change I'm sending to the remote, and that unit needs to be deliberately constructed somehow. Whether that involves rebasing or amending or careful usage of git stage, it's an artificial unit you're creating just as much as the actual code changes.
> What I'm saying is that the typical change to a file shouldn't be committed to my local repo until I indicate that it's ready.
I was extremely hostile to this aspect of jj too, but I've settled on a workflow where @ (i.e. the change/commit referring to the working copy) is always the same, named ".WIP: …", and as such is clearly never supposed to break out of the local system, and can never accidentally get pushed down the log. (..my jj workflow is just blatantly copying my git workflow (with the same-name shell aliases/helpers even), but even with said git emulation I'd say it's nicer than git itself)
Still may be weird/undesired to have the local repo preserve the changes (especially annoying if it happens to find an unignored build artifact, though there is a size limit for automatic tracking by default), but it's also rather neat that you can dig out your old deleted printf debugging or whatnot later on if you wanted to.
If you leave the WIP head change blank, i.e. don't give it a description at all, then JJ will block you from pushing it unless you pass a specific flag, which is useful for sanity checking this sort of stuff.
You might be able to configure JJ to also block commits with certain descriptions as well, which might be useful in your case.
You indeed can: https://jj-vcs.github.io/jj/latest/config/#set-of-private-co...
I specifically have the WIP head description include the name of bookmark that it's attached to, if any (emulating git's non-detached head), so can't leave it empty (and generally wouldn't want to, for easy way to identify it, esp. with multiple workspaces).
Some push blocker for certain commit name patterns would make sense to look into.
Should be simple enough, but look into trailers; the language already supports key-value maps in the commit description.
> I'm confused what this is?
jj is a version control system. It is backend-agnostic. The most common backend is a git one, because git is so popular. This allows you to use jj on a git repository, allowing for individuals to adopt it without forcing their teammates to.
> Is it just a git frontend for people who are confused by git?
I used git since before github existed. I considered myself a git lover before I found jj. I will not be going back to git.
The thing is, jj is both simpler and more powerful than git, at the same time. People who are confused by git may like its simplicity, but I like its power.
> It says it abstracts the backend, but it's not clear how something so git-influenced will have abstractions that work with something like a centralized system like Perforce or Piper that has auto-increment numeric commits.
You already got some replies on this one, so I'll leave that to them :)
> Some of the design decisions are also not great. Working copy as commit means you have no quality control. The whole point of a commit is that... you commit it. So now you need a separate repo or filter to remove the worthless changes from the ones that you actually intend to commit. Bad commits polluting git histories is already a big problem.
This is an understandable misunderstanding. The right way to think of it is "the index is also a commit." A common way of working with jj is to do something like this:
Imagine I am working on adding some feature x to my codebase. I'll first make a change, and give it a description:
Now I will make a new empty change on top of that: Now, @- is the change that I intend to push publicly, but @ is my index. Say I add foo.rs: Now, when I run `jj status`, jj takes a snapshot, and it now lives in @: We can see the diff here with `jj diff`: So, let's say I'm happy with its contents. I want to stage it into my final commit. I can do this with `jj squash`, which by default takes all the diff of @ and puts it into @-: Now that change is in @- instead of @. we can see that by passing -r (for revision) to `jj diff`: I don't have to move the whole change; I can do the same thing as git add -p by using jj squash -i, and only move the portions of the diff.What's the advantage here? Well, because the index is just a commit, I can use any tools that I use on commits on the index. There's nothing like `git reset` needing to have `--hard` vs `--soft` vs `--mixed` to deal with index behavior: everything is in a commit, so everything acts consistently.
jj makes it very trivial to carve up commits into exactly what you want. It is far easier and more powerful than using the index for the same purpose.
> The biggest problems with git IMO are it scales poorly and it encourages commit pollution. Presumably jj isn't trying to tackle those problems, which is totally fine. But I am confused about which problems it is tackling. That's why I'm wondering whether it's just a frontend that the author finds more to their liking.
IMHO, commit pollution is more due to the pull request workflow than git itself, though I do think that git doesn't do that much to help you. jj can help with this kind of thing, but also on some level, it can only do so much.
I want to appreciate this cool-sounding new tool, but everything you've said sounds exactly the same as using git rebase, what am I missing?
You can do everything with git rebase, the question is how much pain are you willing to tolerate. If you've ever solved the same conflict twice and/or are aware of git rerere, you should try jj.
The impression I get from all the jj writeups is that the devs said "rebases are really cool, we should move heaven and earth to make them even cooler".
Git rebase is, frankly, a massive pain in the ass. It's worth doing if you care about it. I used a rebase-heavy workflow for a decade and a half. But having to drop everything you're in the middle of to fix an endless list of rebase conflicts sucks. And it sucks even worse when you screw up the conflict resolution halfway through and have to start over from scratch.
Rebases also don't play nicely with stacked branches. Branches based off your original changes don't get rewritten to be on top of the rebased change, so now multiple rebases are in your future.
It turns out that basically none of the rebase friction most people experience is necessary in practice. Rebases can be automatic, implicit, and conflicts can be fixed at your leisure and in whatever order that you feel is best.
jj also has a ton of other powers, but you asked about rebase :)
> having to drop everything you're in the middle of to fix an endless list of rebase conflicts sucks
You can use git commit --fixup to record what you want to change in an earlier commit.
> start over from scratch
rerere
> Branches based off your original changes don't get rewritten
As written elsewhere: git rebase --update-refs. If you want to do it manually git rebase --onto.
> You can use git commit --fixup to record what you want to change in an earlier commit.
jj just does it correctly by default.
> rerere
jj just does it correctly by default.
> As written elsewhere: git rebase --update-refs. If you want to do it manually git rebase --onto.
jj just does it correctly by default.
defaults matter.
>> git commit --fixup
> jj just does it correctly by default.
JJ adds changes to random old commits instead of the newest one? Yikes.
> rerere by default
Takes space and time.
> --update-refs by default
Maybe sensible, don't need it most of the time.
> JJ adds changes to random old commits instead of the newest one? Yikes.
jj adds changes to the current commit, which is the staging area, which is the index, which is the working copy. I get you don't like it, no need to be an ass about it, but denying that it works is just that, denial.
> > rerere by default
> Takes space and time.
doesn't take my time. I'll take it.
> > --update-refs by default
> Maybe sensible, don't need it most of the time.
No reason to waste time thinking when it's needed.
> no need to be an ass about it
That wasn't what I meant, git --fixup is for changing earlier commits, you answered as if it didn't that's what I wanted to point out. (I tried to be sarcastic, because it was obvious that you didn't mean that.)
>> Maybe sensible, don't need it most of the time.
As in I want the opposite behaviour most of the time. That's why that is the default.
Yes, there are a million weird band-aids that git has grown over time. Some of them work better than others.
Or we could just not have the problems to begin with.
There are some later additions like git switch, but git rebase and git commit ain't one.
`--update-refs` was added in 2.38, in 2022. `--fixup` was added in 1.7.3. Like I said, git has grown additional features and workarounds. Knowing all of them is a lot of work.
> Working copy as commit means you have no quality control
The working copy doesn't get automatically pushed to GitHub or anything crazy like this seems to be implying. You review/curate your commit when you give it a description.
I tried jj a few days but I noticed my lazygit based workflow and scripts already make me productive enough not to deal with another mental model.
jj looks cool in general, I'd start with it if I were just going into this _version control_ thing but for most of us older folks, that doesn't provide enough motivation to change.
Eh, I can see how, if you use GitButler, the porcelain is fairly irrelevant to you, but a few days ago I decided to try Jujutsu, asked Claude how I could do a few things that came up (commit, move branches, push/pull to Github). It took me ten minutes to become proficiend in Jujutsu, and now it's my VCS of choice.
I still use Lazygit for the improved diffing, but, as long as you don't mind being in detached HEAD all the time, there's really no issue with doing that. JJ interoperates fine with git, but why would I use the arcane git commands when JJ will do the same thing much more straightforwardly?
Also, the ability to jump from branch to branch with all my uncommitted files traveling with me is a godsend. Now I can breeze between feature development, bug fixing, copy changing, etc just by editing the commit I want. If I want multiple AI agents working on that stuff, I just make a worktree and get on with it.
Not to mention that I am really liking the fact that I can describe changes (basically add commit messages) before I'm done with them, so I can see them in the tree.
JJ is just all around great.
I've been using JJ as my daily driver for ~6 months. It is so nice to be able to immediately rebase everything on top of everything, undo last command, restore a previous state, ignore and postpone conflict resolution. It is such a boost in productivity!!
Hands down one of the best features in jj is undo. In Git, undoing things is always a bit of an open heart surgery, with different commands depending on what you fucked up. In jj? Just a simple jj undo, done. That alone is a killer feature.
Then having the whole thing being kinda agnostic by design to the backend system, that is also a sweet spot.
I can comfortably use jj locally without any of my teammates knowing that I use jj instead of git.
Hmm. Here I thought it was about a special martial art variant for chair bound developers !
Is there a magit equivalent? I heavily use magit's interactive features and extensions to the git UI (like spinoff, absorb, or the auto-backup thing)
Wondering the same. I've been using magit for a long time now and never use the git cli anymore. Going back to manually typing in commands doesn't sound appealing. The unified index/workspace non-stash-workflow does sound somewhat nice, though.
I am not a magit user but from doing a little bit of reading, all of these commands are essentially first-class citizens in jj. Spinoff seems to be `jj split` (or in most cases literally nothing because the default is to edit a new, empty revision off the trunk), absorb is probably `jj rebase` or `jj squash`, and the auto-backup is either the evolog (which tracks file changes that haven't been explicitly commmited) or the op log (which lets you reset the entire repo to what it looked like before or after any operation).
There is also lazyjj as an interactive UI.
magit-merge-absorb is one of rebase (if that's what you want), or new (which gets you a merge when you specify multiple parents). The 'delete the branch' part of it doesn't really apply, because jj doesn't have branches; though you might need to delete a tag.
Note for other readers: jj also has a literal `jj absorb` command. That one does what you'd expect from mercurial, i.e. moves diffs from the current commit into the most recent ancestral commit where that file was changed.
Note also that jj does have branches, they are just anonymous. You don't checkout a branch or edit a branch, you edit a revision or make a new one. One revision with multiple children is a branching point, one revision with multiple parents is a merge.
You name them with bookmarks which are sort of like branch names except they don't follow along automatically as you make new revisions. You can point an existing bookmark at a later revision when you're ready to push new changes.
jjui is the best VCS TUI I've ever used. It's even smoother than magit, but I think most of that is because of jj itself. Spinoff and absorb are both native jj features (jj rebase and jj absorb, respectively)
https://github.com/idursun/jjui
I’ve got to check this out.
I’ve really been loving these two neovim JJ plugins
For splitting commits: https://github.com/julienvincent/hunk.nvim
For resolving conflicts: https://github.com/rafikdraoui/jj-diffconflicts
Completely agreed. I've been evangelizing jjui even more than I do jj!
And, you're right, it's powers largely come from the underlying jj - it mostly just runs jj commands behind the scenes, parses the output, and displays it. But it's all so beautiful, seamless etc..
I can't wait to really dig into the big additions released yesterday in v0.9.0 - themes, vim-like leader key shortcuts, other shortcuts etc...
For a decade now I've been lamenting that git won the source control war. My complaints fall in two main categories:
1. The mental model is too complex: rebase or merge, detaching head, etc. A good UI can hide away some of this uglyness. It sounds like jj helps here.
2. Any source control in a terminal is just horrible, since you have no view of the state you're working with. I honestly never fully understood why developers have such religious elitism for the command line. Where would you draw the line on something having a complex enough state to need a UI?
The change to git that I'm hoping for is a GUI on top of a simpler wrapper like Jujutsu.
jjui is what you're looking for. jj is amazing and jjui makes it incredibly better. The maintainer is extremely receptive and responsive to feedback, and constantly moving it forward.
There's other TUIs and GUIs, but jjui is by far the best.
https://github.com/idursun/jjui
I’ve had a lot of success using https://graphite.dev/. Been pretty easy to pick up and slots right into our usual GitHub workflow. I end up using the vscode extension to manage it. Anyone have opinions how jujutsu compares?
Isn't graphite a pr review tool that still uses git?
It’s both. Their CLI has a lot of overlap with what you’d use jj for (rebasing, amending, etc), but does basically everything worse other than “creating stacked PRs”. Slower; refuses to work from a detached HEAD; gets confused more easily by changes on the remote.
Sounds like your opinion is jj is easier and faster? Would you say that’s true for new developer onboarding too?
I’ve never onboarded or successfully convinced a colleague to use jj. But roughly once a month I find myself helping someone escape git hell that would either be easier to solve or avoided entirely with jujutsu. I’m not speaking hypothetically: I will solve the problem locally while waiting for them to slog through whatever merge conflicts or stash/unstash dances.
But the graphite CLI’s stacked PR review is a killer app. Our team uses it, but treats the rebase-centric workflow as the price of stacked review, instead of a mental model to leverage. I end up using jj but with a helper script to set up graphite’s branch tracking whenever I want to submit a stacked PR.
I also was always a git CLI person. I’d do diffs and resolve conflicts in a proper editor, I wasn’t crazy—but I know that changing just my mental model is less work than it’d be to change the UI habits as well.
JJ is brilliant.
I reluctantly moved to git from mercurial when host-after-host dropped mercurial support. git is a user-hostile POS that I never got used to. A thousand needless complications including nightmare merges, and an inscrutable command interface.
JJ eliminates all the git bullshit. Some incredible features:
* Merges never fail as conflicts are a first class feature. You can come back and resolve them at any time.
* You can reset the state of the repo to any particular point in history as JJ maintains a complete operations history.
* You can insert new changes anywhere and deal with the repercussions at leisure.
I started with Steve's tutorial but found it a bit wordy. So I used Gemini to produce a simple guide for me with: "You are an expert at DVCS systems like JJ. Act as my guide for the system and answer my questions with simple cookbook-style recipes."
I have been using JJ for a month and am never going back to git. It is such a pleasure to work with a DVCS that you don't have to fight at every turn.
I tried it for a bit and went back to git-branchless (https://github.com/arxanas/git-branchless).
git-branchless is just a better set of tools for working on a git repo so local tools like the JetBrains git integration will work just fine.
It operates on a similar philosophy to Jujutsu (make it easy to manipulate the commit tree) and the authors did exchange ideas with each other.
It seems like a lot more than they "did exchange ideas".
Arxanas, the lead dev of git-branchless, is also one of the major devs and maintainers of jj. He's active on the jj discord, and just last month put out a call on github for a jj side conference during Git Merge 2025.
I tried it out on one of my personal repos and I need to give it more time. I do like the way it tracks work, it feels like it would help me to make more commits as I work, cause I’m really bad about that
I don’t see anything about side control in this post. 3/10.
If only it was supported by Gui tools... I don't bother use Git from my terminal, but use GiFork instead. As I understand there is nothing that may be interesting for me in my scenario...
I actually find I don't need to use any GUI tools with jj because I can actually understand what's going on under the hood since the mental model is much simpler. It helps that jj has excellent visualizations straight in the terminal.
How does this compare to sapling? https://github.com/facebook/sapling
Found a comparison: https://github.com/jj-vcs/jj/blob/main/docs/sapling-comparis...
Lately I've been hearing more people are getting into jj because of how much easier it is to keep track of the code generated with tools like cursor or claude code.
Yes!
I have found it incredibly helpful when using an agent. It's a great case for the "staging-area" style workflow. I `jj new` an empty revision where the LLM can go hog-wild. When I'm generally happy with the LLM's work, I make a new revision. Then I ask it to make some improvements. Sometimes I have to discard those. No problem, I just `jj abandon` the whole thing. Sometimes I like a few pieces; I `jj squash -i` those into the parent revision with the bulk of the work and abandon the rest. And then I repeat until I'm happy with the overall output.
It's also super useful when I need to go fix something in an earlier (but still unmerged) revision. `jj new {id}` creates a new revision whose parent is the older change, even if it already has children. Again, I let the agent loose. When I'm happy, I `jj squash -i` what I want and the fixes are not only incorporated back into the parent but then propagated automatically down through the rest of its children through automatic rebasing.
I wonder when LLMs will start messing with VCS. That will be fun :-/
Most coding llms already know git, and can handle jj pretty well too.
I expect them to, I ment, that as part of modifying the code the issue commands that affect the VCS. This would be annoying, because you then can't rely on the VCS to tell you what the LLM did.
Sure, I'm not saying you should feel good or bad about it, I'm just letting you know that it is already there.
jj has the operation log, which is an append-only history of the entire set of interactions with the repo. So it’s there :)
Any tips for a magit fan who wants a positive second experience with jj?
I’m used to sorting through the changes I have made and making separate commits for each unrelated one (eg bug fix for several files, some WIP work, other parts actually done and ready to be “shipped”/reviewed)?
Do I need a better workflow?
Should I just be using the command line?
I came from magit and completely switched to jj for the much easier handling of sets of changes that you describe. jjui (https://github.com/idursun/jjui) made this a lot faster and more enjoyable.
This x1000. Jjui is beautiful
I legit ask myself how many folks avoid Github Desktop for some dogmatic reasoning equivalent to "having an UI makes it worse", when it does the core of common flows extremely easily and clear.
To be clear where it ties to this post: it makes git far more convenient with nearly 0 learning curve.
When I was first learning coding, git, etc. and had no clue how git worked I downloaded GitHub desktop and used that. It's true that I was much younger and less knowledgeable then so it's possible that's influencing me but man when you actually understand git the cli just feels so much smoother, cleaner, and faster. It really doesn't take much dedicated to time to learn the top 5 commands you'll use 95% of the time and if you don't know git currently I think it's probably one of the most leveraged ways you can spend your time.
My thought is, if a GUI like GH Desktop makes it hard to use Git, then your workflow is too complicated. Version control doesn't have to be complicated. But a lot of that is upstream decisions about how you structure your work as a team.
Judging by the name, Github Desktop works only with github... ?
I'm not sure if there's advanced commands that GitHub Desktop does more with but for the most part it's just a porcelain frontend that works great for doing simple operations but can't do things like interactive rebases, reflogs, blames and so on. It's a pretty simple frontend to get started if you're just learning git. There's not much it does that's GitHub specific. If you're logged in, it will easily checkout your upstream repos.
Can't be leet like that!
Accessible without clientside JS enabled: https://web.archive.org/web/20250722002855/https://maddie.wt...
Curious are there nice terminal tools for JJ similar to magit or GitUI?
https://github.com/idursun/jjui is absolutely fantastic. One of the best TUI's ever created IMO
My NixOS repo checkout(s) are always cursed with unstaged changes, will jj help me cure this?
Sometimes I feel like "this shit is too bad to commit but it does the plumbing for the thing I'm working on right now" and it ends up untracked for weeks... This is just my personal ADHD pool of code but it gets really gnarly before I take care to dissect my unstaged changes into the repo.
Almost certainly. I have a private monorepo where I just try things out in new empty changes and I just leave them hanging
That's a very clear up-to-date intro, great work.
I've never tried jj, but recently I discovered lazygit which in my opinion comes pretty close to perfection.
Rebasing can be done instantly (reorder commits, fixups, squash), I can extract and reverse custom patches from previous commits, I can partially stage files, undo.
One think I like is that it explains what it does in terms of git commands. So it helps a little to have a good git mental model, and as such I guess it doesn't solve the same thing as jj.
There is lazyjj which follows a similar theme. Haven't used enough of either to give a good comparison other than lazyjj being earlier in its journey.
This is the reason I have not gone the jj way.
I must be getting old because I really don’t think git needs a simplified model. But hey, if people find value from this, more power to them. The blog is well written too.
It’s not just that it’s simpler, it’s that the primitives and interaction model are also strictly more powerful. A ton of extremely useful workflows that are an utter pain in the ass with git are absolutely trivial with jj.
One example is a series of dependent PRs. This is excruciating in git if you ever need to make a fix to an earlier PR because you have to manually rebase every subsequent change. It’s trivial in jj: you either fix the revision directly or you insert a new revision inbetween. The subsequent revisions are updated automatically. You don’t even have to think.
Another is splitting work into parallel branches. So often when I’m working on one area I end up making unrelated tweaks and improvements as I go. Pulling these out into their own branches is painful in git, so people just make omnibus branches that include a handful of unrelated work alongside the main task. In jj it’s basically zero work to split out the unrelated stuff onto a new branch off main, and it doesn’t require you to task-switch to a new branch. So I end up making lots of one-line PRs that are trivial to review whenever I’m doing deeper work.
> This is excruciating in git if you ever need to make a fix to an earlier PR because you have to manually rebase every subsequent change.
Spreading the word about `git rebase --update-refs` that will automatically update any branches that point to commits along the path (very useful with stacked branches). It is less convenient than what jujutsu offers (you need to know the branches to update, where jujutsu automatically updates any dependency), but still a very useful if you don't want to or can't switch to another tool.
There isn't really any "can't switch to another tool" when it comes to git + jj. You can use it today without anyone on your team knowing - other than that your PRs suddenly became much cleaner
Some people can't install arbitrary software on their employer's hardware.
>you have to manually rebase every subsequent change
What do you mean by this? You can do an interactive rebase in git as well. The real issue is non-trivial merge conflicts which is going to be an issue no matter you use.
Let's say I have five commits in a row ready to go, but they should be reviewed and merged one-by-one. Upon review, I need to make a change to the first commit. How much work do you think this would be in git? How much of a pain in the ass do you think it would be if a later change conflicted with this earlier change?
It is essentially zero work in jj. I `jj edit` the revision in question, make the change, and `jj push`.
It's really not a lot at all. The weakness is in "forge" tools like GitHub that add a PR/changelist abstraction on top of git and don't support sequences of patches. If you're using just commits and maybe (mailed) patches you only do a single `git rebase` (with -i if you want) and you're done. Unless jj is literally magic and can automatically fix conflicts, I can't see how it would actually reduce the work involved in a meaningful way.
I'll be honest, it's been so long at this point since I've used git that it's hard for me to remember the exact details of many of the challenges. I know that I avoided a lot of workflows I wanted to do because of the complexity and mental overhead, and the scenario I described was absolutely one of them. Maintaining a stack of changes that needed to be merged one after another was painful if there were any unexpected hiccups along the way. Now it is painless.
Arbitrarily-complicated rebases just happen automatically with jujutsu. Inserting, moving around, and directly editing commits are first-class operations, and descendants automatically rewrite themselves in-place to incorporate changes made to their parents. Rebase conflicts are also first-class citizens so they don't block you and force you to deal with them right now or in any particular order. Having to rebase seven related conflicts one-after-another is no longer a thing, nor is realizing you fucked something up halfway through and having to restart.
Coming from git it honestly feels like magic even if it strictly isn't. It genuinely hard to understand how much unnecessary toil git makes you put up with on a day to day basis until you suddenly don't need to deal with it any more.
As far as I know, by default Git doesn’t enable the “reuse recorded resolution” feature so if you made a change to the first commit you’d have to manually do the same thing for any subsequent commits.
If you have 5 different branches, sure. Again, the reason you create a bunch of branches for separate review is because that's what the "git forge" abstraction generally expects. It's not actually how code reviews are done by the people who wrote it.
You can also just enable that feature (rerere).
What is the "right" way to do it then?
Also probably most of us are stuck with whatever git*.com supports anyways...
Basically see how the LKML works. Individual commits are typically expected to be self-contained to the best extent possible, with patch sets or patch series being used when that's not feasible. Patches are usually sent over email, but there are a lot of ways to do it.
It's not an ideal way to operate for most shops, but there's really no reason you can't have a PR/MR/changelist/whatever that is a single branch with X commits on it and you ask the reviewer to review each commit individually instead of as a whole unit (as GitHub and other forges usually expect you to).
That and don't let reviews pile up such that you have 5 dependent in-flight reviews, something else is wrong if that's happening.
> Basically see how the LKML works. Individual commits are typically expected to be self-contained to the best extent possible, with patch sets or patch series being used when that's not feasible. Patches are usually sent over email, but there are a lot of ways to do it.
What is the advantage to this, other than maybe being easier to send over email?
To me, the important part is you have a logical "unit of change" that you're proposing to the codebase, whether that's a single commit or a branch or a jj bookmark or whatever seems more an artifact of the underlying transport layer (email or github or whatever) than any kind of intended functionality of the design.
Use `--update-refs`.
Think about the word "branch". If you have a linear sequence of commits that's one branch by definition. But you go and label the middle of that branch as branches too and then get annoyed when git, you know, does some branching there.
> It's not actually how code reviews are done by the people who wrote it.
What do you mean by that? Even if you do review by emailing patchsets those are still managed locally using branches, to my knowledge.
I mean the unit of review is the patch (set), which does not necessarily have a branch associated with it. You can use a branch, but you could just as easily send commits from master and the reviewer can apply them directly on their master branch if desired. The idea of "branch per reviewable unit" was largely created by GitHub.
No work, I set `rebase.updateRefs = true` years ago.
git rebase -i is a much worse experience than jj's autorebasing of descendants. For example, with git rebase -i you can't decide to do something else in the middle of the rebase — you're in that state until the rebase is completed or abandoned.
Merge conflicts are also significantly better with jj because they don't interrupt the rest of your flow.
And most importantly, the two features work together to create something greater than the sum of its parts. See my testimonial (first one) at https://jj-vcs.github.io/jj/latest/testimonials/#what-the-us...
> with git rebase -i you can't decide to do something else in the middle of the rebase
You absolutely can, to some degree. At any point in an interactive rebase you can just make your changes and do a normal `git commit` then do `git rebase --continue` along on your merry way. Unless you're talking about suspending the rebase and like switching branches, messing around, and then resuming the rebase, which is kind of a weird thing to do.
I do mean suspending the rebase and going to do something else, yes.
It's a weird thing to do in git, because the conditions that git creates makes it weird. It is completely natural with jj, because jj doesn't have any modal states at all. (This is one of the key reasons jj is both simpler and more powerful than git — no modal states.)
You can absolutely do that in git:
When you are not trying to modify the same branch you're rebasing, you can omit --detach and also git tag todo.(To clarify, it has never occurred to me that I even want to do that, so I didn't knew how to do it. Yet I didn't even needed to consider a manual, it just follows naturally from the git user model even if it seams completely unidiomatic.)
As far as I know you can't start another rebase -i while you're in this situation. But it's been almost two years since I last used rebase -i so I might be wrong.
Modal states are actually good IMHO.
I feel like I just outlined a situation where something that's weird in git is completely natural in jj. Maybe I miscommunicated?
I think you communicated well, probably I did not.
> suspending the rebase and going to do something else
This is what I find to be weird, personally. When doing a rebase, there’s no way I want to do something else in the middle of it, and having a modal state feels totally natural to me.
At first approach (I read a (very good) intro[1]; I did not try), it seems there’s a lot of new things to learn (for instance the revsets language), for a very minimal gain, so I’m (still) gonna pass on it. It feels like jj is solving a lot of problems that do not exist, or are mostly solved with worktrees.
That being said it’s true that everybody’s way of working is different, so I don’t know! Maybe jj will be picked up by the younger generation and it will become the new de facto standard. Time will tell…
Personally, the current VCS tool I’d like to try now instead of jj is fossil. It seems much more interesting as it promises to allow bypassing GitHub/other forge completely by being the full forge itself. In these days, having ownership of one’s data feels primordial to me.
[1] https://ofcr.se/jujutsu-merge-workflow
> When doing a rebase, there’s no way I want to do something else in the middle of it, and having a modal state feels totally natural to me.
This is a consequence of rebases being a special, modal state that requires dedicated focus to work through to resolution.
Rebase conflicts are (to a jj user) just another fix you might want to make to a revision. Or it might be better done by making a tweak to an earlier revision, instead of the revision where the conflict first occurred. There’s no pressure to fix it right this second, you can always come back where you left off. And you can make your fixed in a separate commit that you squash into the conflicted commit if it’s particularly hairy.
> It feels like jj is solving a lot of problems that do not exist, or are mostly solved with worktrees.
Git has grown a lot of features over the years and countless flags to make some things feasible.
jj rethinks the core interaction model in a way that gets rid of all the band-aids.
There are people who still live and die by C and swear that anyone running into issues is holding it wrong or just needs to be more principled about how they handle memory. But most people have moved on to higher-level languages. It doesn’t mean C was a bad language, it doesn’t mean those programmers weren’t familiar with C, and it doesn’t mean there aren’t still some cases where C is the right answer. But most people nowadays find other languages more productive with less friction.
> This is a consequence of rebases being a special, modal state that requires dedicated focus to work through to resolution.
Believe it or not: no. Humans are notoriously bad at multitasking, so it makes sense to actually finish a task before moving to something else and forgetting what we were doing…
Relaxing this comment which is a bit aggressive: at least it’s how I work. YMMV.
I think the evergreen use case for interrupting a rebase is, your manager came over to ask if you can drop everything and look at this bug that $TOP_CLIENT is screaming about.
Git worktrees suffice, but they're still heavier weight than `jj new whatever`.
I've had maybe 3-4 occasions in my entire career where I was working with a rebase large enough where it took multiple sittings (every single one sucked badly). I'm not going to argue that it's not something that comes up, but I will say that if it's common, your workflow is probably too chaotic for me. That being said, you can still do that in git (see sibling comment to this one), but it is more involved. IMO that's good because I don't think enabling that type of chaotic, jumpy workflow is healthy or good.
Well for me it's happened hundreds of times. Specifically I've very often wanted to do another git rebase -i in the middle of the first one, usually because I changed my mind about something and want to make changes to an earlier commit in my stack.
> IMO that's good because I don't think enabling that type of chaotic, jumpy workflow is healthy or good.
Nah, it's wonderful. You see it as chaotic and jumpy because the conditions Git creates makes it feel chaotic and jumpy. It's like being terrified of multithreaded code if you're not using Rust or a purely functional language.
> Specifically I've very often wanted to do another git rebase -i in the middle of the first one, usually because I changed my mind about something and want to make changes to an earlier commit in my stack
I think this is what `git rebase --edit-todo` is for.
>Nah, it's wonderful. You see it as chaotic and jumpy because the conditions Git creates makes it feel chaotic and jumpy.
Fair enough. `jj` probably isn't right for me, but I'm happy it works well for you!
Been a long time since I used git, but --edit-todo only changes what's still to come, right? It doesn't let you go back and edit earlier commits in the stack.
Ah, yeah it does only allow you to modify the remaining todos :/
First class conflicts are way better than modal states.
https://ofcr.se/jujutsu-merge-workflow/
I literally will have like 4 PRs in flight at once and have an octopus merge of all my separate PRs that I can then work on top of. JJ can rebase all 4 separate branches, the octopus merge, and the work on top of the octopus merge in a single command: jj rebase -d main.
If there are conflicts I can then resolve them whenever I want.
You have no idea what you are talking about if you think git’s interactive rebase holds a candle to what jj rebase can do.
> The subsequent revisions are updated automatically
How do you make sure, that a commit isn't changed under you, because someone thought, it would be a good idea to change an earlier revision? I think having immutable commits including all the previous history is a feature, not a bug.
JJ has a concept of mutable and immutable changes. Typically, mutable changes are ones that you're currently working on locally. If you create a branch locally and push to it, then the changes on that branch will also be considered mutable. But changes created by other people in branches that you didn't create are considered immutable, as are e.g. changes that already exist in the default branch. There are a handful of other ways that you can convert changes between the two states, but generally the distinction is fairly intuitive: mutable changes are ones that you own, and where it would normally be "safe" to force push etc, and immutable changes are ones that probably also exist on someone else's machine.
All changes can be modified, but if you try and update a change marked as "immutable", then Jujutsu will error out unless you provide a specific flag. This generally provides a lot of protection against unexpected rebases from other people.
You also have a local immutable history of every update to your copy of the repo. This includes fetches/pulls, so you can easily see and undo those changes if something does go wrong along the way. But if people are deliberately force-pushing to master, you're going to end up in weird states whatever to you use.
Jj treats any public/pushed commits/branches as immutable by default.
How does it know that? Does it also work, when I clone or pull instead?
Is that only enforced on the committer side? I mean git also doesn't force push by default, but that still means someone else can do that and I need to notice it.
There is a way to express which sets of revisions are immutable. This uses the revset feature which itself a whole amazing thing that hasn’t come up in the conversation yet. This is a mechanism by which you can concisely express and refer to entire sets of commits in your repo history, and lots of commands can operate on multiple revisions as easily as they can operate on one.
Just like git, it is only enforced by the tool itself on the client side. But it’s safer in practice because `git push -f` is a common habit when you’re working on a branch by yourself and it’s easy to fat-finger that in the wrong place. I have only ever had to use `jj --ignore-immutable` when explicitly doing repo surgery.
If you want branches like `main` to be truly immutable, you need to enforce that at the repo.
I don't have answers for you. But the docs are useful and people in their discord are helpful as well
I don’t think simplified model is even the best selling point, but it’s definitely up there. IMO one of the killer features that you absolutely cannot get in git is universal undo. For example, rebases can always be a little tedious and tricky no matter how experienced you are. Not only does jj make that whole process easier and safer to work through, but if you do still manage to get to a state where you just want to go back it’s literally just an undo away.
I’d give [1] a read if you’re interested.
1. https://zerowidth.com/2025/what-ive-learned-from-jj/#commits...
It's not a "simplified model". The jujutsu model is more capable, but also more generic and thus easier to use and re-use. It's just better.
Couldn't agree more. I guess we _are_ getting old after all.
I've been using git since ~2014, never really thought about changing it: it's clear, well documented, and ok difficult learning curve but hey that's the fun part.
Honestly one of the biggest selling points of jujutsu for me is its `op log`. You can fuck up your repo in git and that's it -- you're screwed. Or you find some extreme magic on the internet that saves you.
With jj you just "jj op undo <operation_id_that_fucked_your_repo>" and you're fine.
Editing prior commits is also pretty easy, which in turn makes fixing merge conflicts pretty easy too.
Like… reflog?
Kinda!
jj has two kinds of these logs: the evolog and the op log.
The git reflog is based on, well, refs. Whenever a ref is updated, you get an entry. This log is per ref. That's HEAD, your branches, your tags, and your stash.
jj's evolog is sorta similar, but also different: it's a log, but per change (think commit in git). This means it is broader than per ref, as it includes not just commits that correspond to a ref, but all of them.
jj's oplog is a log per repository. This lets you do things like `jj undo`, which lets you get the entire repository, not just one ref or commit, back to the previous state, easily.
Yeah this doesn't make sense in git terms. A commit is immutable, it never changes so it doesn't have a history, it's just always there.
That's still true in jj, too.
But instead of commits, most of the time you're working with (the not greatly-named) "changes", which are a conceptual history of related commits. E.g., a single change ID points to the most recent commit in a list.
As long as you keep editing on a change, it keeps accumulating commits under the hood. When you move to another change (via `jj commit`, `jj new`, etc), all your new commits end up under a different change.
Ah, so when people here talk about changing commits they are actually talking about changing changes (nicely named, indeed)?
What happens when you merge or split changes with the change history? It sounds a bit like applying a VCS on top of a VCS.
Yeah so people will be a bit loosey-goosey with the terminology. But all of these changes are immutable, that is, when you change a change, you’re generating a new commit. “A VCS on top of a VCS” isn’t a terrible way of thinking about it: imagine if every commit in your repo had its own history.
Merging is making a change that has more than one parent. You can then resolve any conflicts within that change.
Splitting a change into two will update one commit with one half and make a new change with the other half.
You can have merge conflicts, when you combine two commits (--amend in git terminology, not sure how it's called in jj)?
Oh, since you said 'merge' I thought you meant like git merge. Amending a commit works the same way as git. The difference is just that the oplog will have the full history, including before you amended, so you can roll back easily.
Yeah, I love jj, but I'm not a fan of that naming. It threw me for a loop for the first couple months.
I'm not sure what happens, but I suspect a merge would create a single merge commit with the tips of the parents' histories, and then add new commits from there on.
A split is probably conceptually similar to making two new branches, except each of the new commits gets one of the branch tips. Really just guessing here, I have no idea if that's how it works, or if they share commits or not.
no. reflog stores snapshots with descriptions; op log stores operations.
I want to be excited about Jujutsu, but what always bothers me about JJ is that if you search the page you can find 'jj' 47 times and 'git' - 49.
Is there a good explanation of Jujutsu without referring to git? May be with some pictures? Am I the only one bad with memorizing SHAs when reading? Also, does it work with other people? Would it work in repo with 5 contributors? 20? 500?
EDIT: I am looking for tutorials with explanation on how JJ works, preferably without referring to Git, and even more preferably with some visual explanations, which, there are plenty for Git. It seems I am not very good in reading text trees. It is my issue, not yours, but may be you know something which would help.
Git is nearly universal, so highlighting the areas where jj makes things that are painful in git trivial and obvious isn't exactly a bad strategy. It's also worthwhile pointing out that it is interoperable with git, so your company and team don't have to change just because you do.
> Am I the only one bad with memorizing SHAs when reading?
I haven't ever needed to memorize a SHA or change ID with jj. What are you referring to?
> Also, does it work with other people? Would it work in repo with 5 contributors? 20? 500?
I have used jj for two years on teams without anyone else needing to be aware. Other teammates that I've convinced to give it a shot have switched, and we all work together happily alongside the git users. If anything, it's easier to work with others when they switch to jj because you no longer have to worry about rewriting history of branches that have been pushed. If you have a branch that multiple people are working on, that's bad form in git but it's just another day with jujutsu.
I was referring to tutorial(s), which refer to commits just by hashes. It is very hard to read for me. While git is nearly universal, my concern was about leaning jj and what it is useful for, what are its limitation etc.
But in jj you can refer to commits, changes etc by the shortest unique substring in its hash, and it automatically highlights what that is
sjenfidb skeifixu
These could be referred to as sj and sk. And their associated git commit hash might be
2748dn49 48jdj40r
Would be 2 and 4
Again, the output highlights those unique prefixes.
But there's also jjui, which is an incredible TUI for jj. Makes everything even simpler and efficient than it already was.
https://github.com/idursun/jjui
Try https://senekor.github.io/jj-for-everyone/introduction.html .
A snippet from the intro:
"At the time of writing, most Jujutsu tutorials are targeted at experienced Git users, teaching them how to transfer their existing Git skills over to Jujutsu. This blog post is my attempt to fill the void of beginner learning material for Jujutsu."
That’s very fun. I’ve been wondering what would happen if someone new to programming started with jj directly.
Thank you!
> Is there a good explanation of Jujutsu without referring to git?
There can and will be, but at this stage in the project's life, git familiarity can be assumed.
> Am I the only one bad with memorizing SHAs when reading?
Nope! The CLI has nice syntax highlighting to show you the shortest valid prefix, so for example, right now I have something that looks like
but the initial l is in purple, while the zrvnkxl is in grey. This means I can just use the l when referring to the change. That can be harder to demonstrate in a blog post, which can't know how to highlight this, and so often they have no highlighting.> Also, does it work with other people? Would it work in repo with 5 contributors? 20? 500?
Yes. Because it's backed by a git repo, nobody else needs to know you're using jj. Everyone can use the tool they choose.
> The CLI has nice syntax highlighting to show you the shortest valid prefix, so for example, right now I have something that looks like lzrvnkxl but the initial l is in purple, while the zrvnkxl is in grey. This means I can just use the l when referring to the change. That can be harder to demonstrate in a blog post, which can't know how to highlight this, and so often they have no highlighting.
This is such a simple UX feature that I have ended up using all the time after I switched to jj a few months back.
JJ needs a GUI.
I tried twice to switch for a project, both times came back to git. But then, I am still not fully grokking the "why bother", and suspect that if I had a UI it would be both less friction, and more understanding.
small projects and/or projects with few collaborators don't benefit as much from jj as the big, fast moving ones. you can easily stick to git and be happy as the differences are not really apparent.
There's lots of TUIs and even some GUIs (including a vs code extension), but the best by far is jjui
https://github.com/idursun/jjui
[dead]
jj is unusable at least because there is tooling built around git repositories specifically
jj itself is tooling built around git repos... It just works seamlessly with any git repo.
And there's toppling built around jj, such as jjui https://github.com/idursun/jjui
Oh --colocate is what I needed.
Yup! And you can just do jj git init --colocate in an already-cloned repo and it'll start working immediately, seamlessly. No need to re-clone it via jj git clone --colocate
Isn't it supposed to be called Ju-Jitsu, instead of Jujutsu?
Wikipedia has some background on the spelling: https://en.wikipedia.org/wiki/Jujutsu#Etymology
> Jujutsu is derived using the Hepburn romanization system. Before the first half of the 20th century, however, jiu-jitsu and ju-jitsu were preferred,
You guys are nitpicking on the name while I clicked the link seriously thinking it's cool and could help to shrink my bellyfat.
Supposed by who? Maybe it's supposed to be 柔術.