Top
Best
New

Posted by Bogdanp 8/31/2025

Jujutsu for everyone(jj-for-everyone.github.io)
441 points | 411 commentspage 2
collinmcnulty 8/31/2025|
I’ll hop on the train and say that I’ve been using jj recently as well, and it’s given me that feeling of safety and freedom I got when I first started using version control 10 years ago. That sense that “well this is a crazy idea but I’ll just commit now and try it, I can always roll back”.
KingMob 9/1/2025|
It's still insane to me that universal `undo` is not a basic git command.
Cthulhu_ 9/1/2025||
That's because there is none, because you're dealing with 4 locations (local, staging, local branch, remote branch); which one do you want to undo?
KingMob 9/1/2025||
Any of them. All of them. (Maybe not remote changes, but that's way less safe to attempt, even if possible.)

Which is what `jj undo` does, and why I brought it up.

I'm well aware git doesn't have a unified concept of undoing, but I'm equally aware that it's possible. (Even before jj, GitUp and git-branchless offered universal undo.)

SAI_Peregrinus 9/1/2025||
Not to mention Git has a reflog, so `undo` could just always revert the last operation in the reflog. Or the last `n` operations if specified. So there already is a universal list of what operations were performed, and thus what operations for `undo` to work on.
phatskat 9/3/2025||
Git always baffled me with revert - I learned early on that if I wanted to “undo” something, the best bet was to either branch from a commit before the thing I want undone, and maybe rebase/cherry pick later commits, or just start over.

This seems like the ideal case for revert, but the way that it alters the history stings - the fact that reverting a commit essentially means that said commit can’t show up later as a change is frustrating - also I’m sure it’s a case of user error, but…”merge branch, realize there’s a big oops in there, revert, fix the oops on the branch” feels like it shouldn’t result in a state where git doesn’t accept the branch as mergable.

stevage 9/1/2025||
In case the author is reading, can I suggest reformatting this kind of thing:

>The command jj log shows you a visual representation of your version history. If you run it, you should see something like this:

> @ mkmqlnox alice@local 2025-07-22 20:15:56 f6feaadf

To:

> The command jj log shows you a visual representation of your version history.

> $ jj log

> @ mkmqlnox alice@local 2025-07-22 20:15:56 f6feaadf

Much better for learning when the command itself is part included in the output.

layer8 8/31/2025||
One showstopper for me is that Jujutsu doesn’t support .gitattributes (https://jj-vcs.github.io/jj/latest/git-compatibility/).
mdaniel 8/31/2025||
In every one of these threads I learn some new axis of git that JJ doesn't support, so I really do wonder if JJ should have squatted upon a different DVCS given how much it wants to do things the JJ way leading to folks with git experience being sad
nchmy 8/31/2025|||
nah, git was absolutely the right thing to squat on - its ubiquitous, so making it seamlessly powerful and easy to use makes for easy adoption. If it was built on pijul or whatever, I'd never have given it a try.

to the extent that niche things might not be supported (yet), so be it. It suits the needs of the vast majority of people. Moreover, if you need those things, I BET you can just do it via git and then carry on working in jj, since jj sits on top of git

digianarchist 9/1/2025||||
Adoption probability without git squatting would be virtually zero.
sunshowers 9/1/2025|||
Ehh these are mostly edge case features that are very important to some, but not relevant to most. I've been using Jujutsu full time with dozens of repos and the only one that's come close to mattering is submodules (which are easy to work around with a colocated repo + `git submodule update`).

CRLF autoconversions especially shouldn't have been supported, imho. Git on Windows's default setup still converts LF to CRLF to this day, even though every program on Windows has understood LF for a number of years.

verdverm 8/31/2025|||
or LFS, hooks, submodules, shallow clones
odie5533 8/31/2025||
So it doesn't have most of the worst features git ever invented. Got it.
Cthulhu_ 9/1/2025|||
I can understand the criticism to submodules but the rest of your comment feels like unwarranted snark.
odie5533 9/1/2025||
Hooks, submodules, and LFS all have huge followings of haters. I think just a week ago there was a big post on HN about how awful LFS is.
SAI_Peregrinus 9/1/2025||
LFS is pretty crap, but the problem it tries to solve is real: version control should be able to control all the sources of the application, not just the text-based sources. Even if it can't meaningfully "diff" between them.
wolvesechoes 9/1/2025|||
[flagged]
lmm 9/1/2025|||
I'm all for functionality, but git should never have been a smorgasbord of random bolt-on special cases. If you have to break the basic core functionality to make a feature work, it's not a good feature.
odie5533 9/1/2025|||
I never said jj was awesome. I've never used it. I'm already adept at git, enough to have opinions on its advanced features, and the knowledge to avoid them, so I'm not sure if I'd gain all that much from jj.
odie5533 8/31/2025||
.gitattributes has only caused me trouble, so I wouldn't really miss it. IMO it's a footgun.
layer8 9/1/2025||
It's the only way to ensure proper cross-platform EOL handling when you can't rely on users configuring their local Git appropriately.

Of course, it also requires users to use the official Git client, so that .gitattributes are actually observed. But that's more likely to be the case in practice.

sunshowers 9/1/2025||
The correct way to do cross-platform EOL handling is to use LF for all files on all platforms, and enforce that via hooks or CI. Every program on Windows, including the humble Notepad, understands LF these days.

Git for Windows configures core.autocrlf to true by default. This is a terrible idea, imho. "input" is a bit more justifiable.

layer8 9/1/2025||
If you have to deploy or test with CRLF files, you can’t force LF for all files. This isn’t about editors, but about files to be deployed on customer systems, and/or to be used in tests, where the interfacing software has other requirements. You also have to use .gitattributes to reliably define which files to treat as text files in the first place. And you’d have to define your hooks and CI checks to be consistent with that. Note that Git always converts CRLF to LF upon check-in for defined-to-be-text files, even on Unix.

I agree that the default on Windows is wrong. “Input” isn’t much better.

sunshowers 9/1/2025||
Tests that require CRLF should transform files as appropriate within a temporary directory. That way the costs of non-standard line endings are borne by the places that need it, as opposed to by all tooling in perpetuity.
thecupisblue 9/1/2025||
I still do not understand why use jj.

It is a layer on top of git that adds its own terminology and processes. The DX doesn't come close to git in understandability, what is `jj bookmark move main --to @-` and how is it more understandable than git?

sgjennings 9/1/2025||
I’ll focus only on the syntax of the command. Why you might need it at all is important, of course, but it’s nuanced and specific to how some people work (I never need this command or anything like it, for example).

> jj bookmark move

Presumably, this part is reasonably clear. “I want to move a bookmark.”

> main

Which bookmark to move. I think this is probably clear from context?

> --to @-

And here’s the part that looks foreign to a non-jj user. This syntax is familiar to jj users because the same revset syntax is used across the whole CLI. `@` is the working copy commit (the commit you have “checked out” that you are currently editing), and `@-` specifies its parent.

Because it’s just a revset, you could specify that same command in a number of ways:

- If you know the parent’s commit ID or change ID, you could use that instead: `--to abc123`

- If there happens to be another bookmark already there, you can use its name: `--to other-feature`

- You probably wouldn’t type this in the terminal, but you could do exotic things like `--to 'mutable() & description("foobar2000")'` to assign the bookmark to the work-in-progress commit that has "foobar2000" in the commit message.

Revsets used pervasively are one of the things that make the DX lovely.

thecupisblue 9/3/2025||
Thanks! Just this is a 10x better explanation than most things I've seen online.
SAI_Peregrinus 9/1/2025||
`git` is a content-addressable filesystem (the "plumbing") with a VCS UI on top of it (the "porcelain"). `jj` is alternative "porcelain" using the same "plumbing". Git was initially designed to be a toolkit on which to create VCSes, over time the VCS portion of Git has become more complete but is still a very leaky abstraction, resulting in users having to use the "plumbing" commands more often than never. `jj` aims to be better "porcelain" over the Git "plumbing", and the project authors hope to eventually support other "plumbing" model(s).
sbinnee 9/1/2025||
I happened to listen to a podcast episode yesterday where the topic was Git rebase. There were 4 people in the panel. One person made a point that he did not understand why they even care, saying basically Git is broken, all the VCSs are broken because they are too complicated and they get in the way. He said a lot of things but my take was that all the existing VCSs are just a distraction. I personally don't agree with him. But while listening to his argument, I started imagining a perfect VCS which is not Git, JuJutsu, perforce, or whatever.

This got me thinking why I have not tried Jujutsu yet (which was mentioned in the episode briefly) even after I saw it multiple times on HN over the years. It is probably because it just looks like another Git, maybe a less complicated one. But nonetheless it's going to be Git-like, and I know enough about Git and am competent with it. So there is little incentive to learn and try another Git-like tool.

I would love to try a VCS vastly different from Git. But it cannot be just different, it should be better than Git. That, I think, is hard to come by.

By the way, if anyone is interested which podcast I am talking about, this is the episode. "The Real Problems w/ Git" on ThePrimeTime. https://youtu.be/t6qL_FbLArk?si=GOkixm86rFADoc4x

guidopallemans 9/1/2025||
I listened to the same podcast, and agreed with the points that Casey made. However, there were some aspects of version control that were glossed over.

What was described was actually quite close to developing through a Dropbox folder + (implied) tooling. And that makes a lot of sense for Casey Muratori's current work, which I believe is a small 5-10 person team doing game (engine) development. Such teams can quite easily work all in the same source branch (and this could in fact be preferred, e.g. for large assets in gaming).

However, as organizations grow, you want different levels of staging (that could be provided by your version control). A split between local and _the common_ stream, release branches, splits between projects or features, review states, etc.

So I believe the optimum is somewhere in the middle, and jj does take a step towards it. But still some friction remains.

pampa 9/1/2025||
I didn't think that there was something wrong with my git workflow, or that git was complicated. I switched from darcs as my primary VCS to git around 2007. I was skeptical about jj at first but gave it a try. Did not look back ever since.

My impression is, that git is ok for managing shared history. And thats what i was using it for mostly. But jj added another whole dimension for managing my work in progress, different paths of code that i am working on in the moment. jj made it almost frictionless. I did not know I was missing it, but i'm not going back.

palata 8/31/2025||
> Jujutsu is more powerful than Git

Depends on your workflow, I guess. I need to sign with different security keys, and for that I use "defaultKeyCommand" in git. Doesn't exist in Jujutsu.

mdaniel 8/31/2025|
I was one of the early adopters (cause I enjoy kicking the tires on things) and brought awareness of the need for gpg signing at all into JJ, so I wouldn't overlook the value in bumping https://github.com/jj-vcs/jj/issues/6688 so they can eventually get you there
palata 9/3/2025||
To be honest, the more I use jj, the more it feels like it's not yet mature enough for me. It doesn't support git push options, for instance.

I have already found more things that I cannot do with jj (and can do with git) than things that are nicer to do in jj. Though admittedly, there are things in jj that are better.

Hopefully it will get there eventually :-). But these things take time. JJ already seems to get a lot of attention, so that's something.

xmonkee 8/31/2025||
Does jj effectively support stacked diffs on github? I've been paying through the nose for Graphite lately because it does, and the feeling I get is I'm just paying for their nice cli tool. `gt create` and `gt sync` is all I need. I don't really fully agree that their interface is entirely necessary or worth the $3500 per year it costs right now for my team.
baq 8/31/2025||
GitHub needs to redesign PRs to properly support stacking them. It isn’t a client problem, but jj does make rebasing multiple branches correctly trivial, so if GitHub supported this workflow in a non-pretend fashion, it’d work.

If rebasing correctly is enough to support your use case, then the answer is ‘it’ll work’.

paradox460 8/31/2025|||
Kind of. You can do most of it by hand, and there are some community made tools that improve the experience and basically give you graphite lite

https://github.com/keanemind/jj-stack

shepherdjerred 8/31/2025|||
jj is great, but it doesn't quite fill the void of Graphite.

I've heard good things about https://abhinav.github.io/git-spice/

seivan 9/1/2025||
[dead]
KingMob 9/1/2025||
No, sadly, that's more of a Github limitation.
amadeuspagel 8/31/2025||
> Jujutsu is compatible with Git. You're not actually losing anything by using Jujutsu. You can work with it on any existing project that uses Git for version control without issues. Tools that integrate with Git mostly work just as well with Jujutsu.

If I use git from zed and jj from the command line, will that just work?

paradox460 8/31/2025|
Generally. JJ will sometimes leave git in a detached head state, but it's usually pretty easy to recover from on the git side. The one thing I'd caution on is making sure you keep your bookmarks on the JJ side up to date. These translate to branches, which git needs to know where it is

You might want to set up this feature: https://github.com/jj-vcs/jj/discussions/3549

thewisenerd 8/31/2025||
i've been using jj for the past few weeks on a feature heavy project. my entire "workflow" is no more than these 4 commands, rinse and repeat:

    jj new main@github
    jj describe
    jj git push -c {prefix}
    jj git fetch
my couple thoughts:

- i'm forced to be "strict" with my changeset since there's no `git add -P`

- bookmarks are a pain to keep up-to-date with `jj new`, i don't know if i even want to do that. for multi-commit changes, i've defaulted to `jj new` a couple times as needed, and `git push -c` the latest.

- i'm sure some day i'll understand the `@..` and `roots()` incantations but for now the most complex thing i've successfully pulled off is `jj rebase -s 'roots(main@github..@)' -d main@github`

i don't think trying to map your current git flows 1-to-1 onto jj is going to be a very fruitful exercise.

paradox460 8/31/2025||
> - i'm forced to be "strict" with my changeset since there's no `git add -P`

JJ split is your friend

> - bookmarks are a pain to keep up-to-date with `jj new`, i don't know if i even want to do that. for multi-commit changes, i've defaulted to `jj new` a couple times as needed, and `git push -c` the latest.

Check out the common community alias JJ tug. It's used enough they're considering adding it as a feature

senekor 8/31/2025|||
Just a few tips:

> - i'm forced to be "strict" with my changeset since there's no `git add -P`

Check out the command `jj split` and possibly `jj squash --interactive`.

> - bookmarks are a pain to keep up-to-date with `jj new`

There's a neat alias a lot of people use:

```toml [aliases] tug = ["bookmark", "move", "--from", "heads(::@- & bookmarks())", "--to", "@-"] ```

It finds the closest bookmark and moves it to `@-`. Pretty much exactly what you want when adding more commits on top of an existing branch.

epolanski 8/31/2025||
Can't you just add an alias that takes two arguments that takes a message and a bookmark name? I aliased the entire describe, commit, bookmark and push to a single command.
ivanb 8/31/2025|
I haven't yet given jj a proper trial, so pardon the ignorance. All I've seen are conveniences regarding the working the copy. Besides the subjectively terrible UI, my biggest gripes with Git have to do with collaboration. A rebase may unintentionally overwrite someone's work. A force-push to trunk breaks every other developer's working copy. With "distributed" being in the name of this whole class of VCSes, I would expect that such things just wouldn't happen, but here we are. As I understand it, jj inherits all of these problems and adds better concepts and UI for manipulating the working copy. I'm not sure this alone is a good enough justification for a switch. Of all the Git's features I use a tiny, proven subset and stay on the beaten path. It makes it bearable enough.
dzaima 8/31/2025||
Working copy handling differences is certainly an aspect of jj, but, at least for me personally (esp. given me having grown up with git), it's probably my least favorite difference from git. And yet I've moved most of my personal things from git to jj (albeit with a bunch of custom scripts to make the working copy stuff more git-y).

A significant other thing jj does is introduce change IDs (i.e. a (randomly-generated) ID that stays stable even as a commit is amended), with which it should be easier to track changed commits across forks/rebases/edits/pulls/fetches, though I've yet to use jj for collaborative projects to see how much that pans out.

Generally jj makes rebasing things, and generally editing history so much more easy than git, so force-pushes messing with branches is much nicer to "fix" however needed. Being able to leave commits in a conflicted state and resolving only when actually needed also should help.

paradox460 8/31/2025||
One of the quiet bit of genius is that JJ change ids use a different set of characters for base16. Instead of 0-9A-F they use k-z
sfink 9/1/2025||
Technically, it's z-k (z=0, k=15). It makes for a nice root change ID: zzzzzzzz. Less choking than kkkkkkkk.

The relevance, btw, is that you can use git commit IDs in place of change IDs anywhere and they won't conflict; jj knows which you mean by the character set. (They could still conflict with bookmark names, sadly. But I haven't heard of that being an issue in practice.)

dzaima 9/1/2025||
Bookmarks take precedence over change ID prefixes. And indeed if you add a bookmark "xyz" and a change had a unique prefix "xyz", the change's unique prefix will grow by one. (the unique prefixes can already grow or shrink semi-arbitrarily so this doesn't "break" anything more)
boltzmann64 8/31/2025|||
I am think you are misunderstanding the entire premise of git. There is no "force push to trunk breaks every other developers' working copy." There is no central repo/trunk where all the commits are pushed. You are probably thinking of svn.

In git, your repo is the canonical repo and that is where you work. You work on a new feature and when you are ready, you "git format-patch" and "git send-email" to the community via the mailing list or other developers. A discussion may happen and people may or may not decide to apply the patch to their own repositories, with "git am." This doesn't break every other developers' working copy because they decide how to apply the patches they got in their email. No central repo, no trunk, guaranteed by the d in git dvcs.

baq 8/31/2025|||
Not sure if you’re trolling, but with the benefit of doubt: that isn’t how almost everyone works, though. Almost everyone treats the repository which runs CICD as the central hub repo and everything else is a spoke.
IshKebab 8/31/2025|||
I don't think they are misunderstanding the premise. They're just thinking at a higher level than you are. It's not too hard to imagine a VCS where rebases can't unintentionally overwrite someone else's work. In fact, Git has flags to avoid that! They're just off by default.
Cthulhu_ 9/1/2025|||
> A rebase may unintentionally overwrite someone's work

Since you're rebasing, you're intending stuff; nothing is lost until you force-push.

> A force-push to trunk breaks every other developer's working copy.

Which is why you avoid it and set the remote main branch to protected. You can't force push by accident, you intend to forcibly overwrite the remote branch.

paradox460 8/31/2025|||
JJ let's you have multiple "versions" of the same branch, although not directly. Typically it comes about when you've made changes and someone else has made changes, and the conflict resolution can't happen cleanly, due to each path taking a difference, unreconcilible approach. You'll have to resolve those and unify the different "branches", but it's no harder than any rebase in git land (generally easier because jj's conflict markers are even better than git's 3 part system)
trueismywork 8/31/2025||
The ability to resolve merge conflicts one part at a time is a game changer itself.

> A force-push to trunk breaks every other developer's working copy.

Only if they pull your broken trunk as well. Otherwise you're just wrong.

> A rebase may unintentionally overwrite someone's work.

No only your copy of someone's work.

More comments...