Maybe what I need to do is do a demo so people can see and ask questions.
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).
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.
I think you'd be a great candidate for jj's ability to customize colors.
That I need to spend a bunch of time setting all of this up (among other things) is exactly how this thread started.
Some of these tools do just work. E.g. nvm seems to just work and is much nicer than raw node installs.
be wary of the stockholm syndrome. I too love git, but it is a very cumbersome tool for some workflows.
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.
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.
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.
jj rebase -b @ -d master@origin
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
jj squash --from ... --into @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.
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?
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.
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.
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.
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.
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.
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 :-).
https://v5.chriskrycho.com/essays/jj-init/ https://v5.chriskrycho.com/journal/jujutsu-megamerges-and-jj... https://ofcr.se/jujutsu-merge-workflow
[0] https://steveklabnik.github.io/jujutsu-tutorial/real-world-w...
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).
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.
(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.)
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.
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.
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!)
[snapshot]
auto-track = "none()"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
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!
It's amazing how quickly I forgot about the commit vs. amend papercut.
[1]: https://codeberg.org/jcdickinson/nix/src/branch/main/home/co...
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.
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.
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."
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.
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.
> 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.
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.
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.
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.
Does it work well with a classic merge workflow? I haven't worked that way (without rebasing) for a long time.
That's also useful for bisecting, as you can first find the feature that is buggy and then find the commit that introduced it.
I use the commit message to add ticket numbers to things to group commits.
I prefer to have everything in a single source of truth (git), so everything is in commits. Your tickets are my merge commits.
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.
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.
It's a command that is needed rarely but there's no replacement for it in some situations.
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.
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.
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 think all of those are using git and many Gerrit as well.
If your project is open source but still interacts with the main body of Google code in a significant way, you can still be in the monorepo and then mirror it to Git via Copybara (or you can work primarily in Git and then mirror to the monorepo via Copybara).
The most common case of that is libraries that get imported into server binaries. There's a whole section of the monorepo for open source code, with special rules and processes around it.
But a that has setup and friction costs so it's only worth it if you see a pretty significant upside.
For stuff that's pretty standalone there's no point. Classic cases are Chromium and Go. Then of course there's Google's various operating systems...
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.
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.
JJ does create commits without you explicitly asking but you do still have direct and fine grained control over it. It's not magic it's just that it's occasionally implicit. And squashing and splitting is nice and easy.
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.
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”.
You can fully change/customise it. Sounds like you just don't like the default.
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.
> 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.
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.
Must be a preference thing. Showing me the unique characters is awesome. Git sucks after getting used to it.
I regularly jump around commit hashes. However 99.9% of the time a commit hash is printed I am not going to go to it. So it’s pure noise 99.9% of the time. And the absolute most it can save me when it isn’t noise is a few keystrokes. Not a good design imho.
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.
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.
Everything else is instant.
This sounds exactly like something a person evangelizing it would say.
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.
Text editors, programming languages, shells, VCSs, nix/guix…
To make a point? To prove that "they are right"? People do this constantly, tools, libraries, languages... All the time really.
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.
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.
So I think I would revise my comment above and say I don't need jj because I've already got a toolkit that does a similar thing. I haven't raw dogged the git CLI in a decade.
However if I ever run into a problem like yours and my tools aren't cutting it I'll definitely keep jj in mind so thanks for showing me!
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.
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.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.
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
You now you can give stashs a commit message?
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 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?
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!
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.
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 have never tried git absorb across merges, no clue if that works.
Moreover, jj should be able (I haven't explicitly tried this) to autogenerate the conflict resolution in the `jj absorb` case, since it knows what the final post-merge state should be.
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?
https://v5.chriskrycho.com/journal/jujutsu-megamerges-and-jj...
jj squash --into -r <revset>
Where <revset> is @-, a change id, or perhaps a bookmark name if you want to give a semantic name to your changes.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.
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.
How do you recover from accidentally deleting a branch that you never checked out? Assume it was deleted on known remotes also.
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.
This one got fixed just a few days ago! https://github.com/jj-vcs/jj/pull/6728
https://github.com/jj-vcs/jj/issues/53
(I am interested because I want to try, but need git-lfs, which needs gitattributes.)
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).
I hit this - I develop in Windows.
I still prefer jj and won't go back to pure git :-)
Git is definitely more powerful but causes more damage than good and wastes more time in most workplaces who are just looking for some simple version control and branching/merging features.
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!
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.
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.
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.
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
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.
Doesn't feel painful in git, but I'd like to see an example doing that with jj. Maybe I'll try.
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.
It's very frustrating that what IMO is the inferior approach was producing better results in those teams!
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.
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.
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!
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.
jujutsu also seems like a poor fit for large monorepos repos (particularly ones that use git hooks). The expense of certain git operations increases quadratically with the size of the repo. A repo doesn't need to be terribly large for `git status` to require 5-6 seconds. (God bless the man who added scalar and sparse checkout to git.)
The absolute last thing I want is a tool that's doing more with git -- creating commits, branches, checking things out, rebasing, or doing anything that requires a git-status check.
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.
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.
100% this.
With remote teams spread across time zones, mega-PRs become even more problematic.
One commit for a single line? I can't imagine developing software this way. Fixing bugs? Sure. Altering this or that in a well-established, mature codebase? Probably. For features, I don't see how it's possible.
note this is also true in software engineering.
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.
This is the one thing I always point to when I say "Mercurial is better" (and now, jj).
Why can't we have our cake and eat it? Mercurial lets you have lots of individual commits, but they're hidden, and you'll see only the squashed commit. Why do we have to lose all those individual commits to get that squashed commit? The answer: You don't need to.
Hmm what's the issue with the GitHub default of merging PRs, where there's a merge commit which individually pulls in the PR's commits? You can revert the merge PR as a whole, or the PR commits individually. E.g. with this[1] merge commit, you can `git revert 0a98f570 -m 1` (the merge commit) or `git revert b30950fc` (an individual commit from the PR).
[1] https://github.com/amelioro/ameliorate/commit/0a98f570f63ffd...
> 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.
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.
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
EDIT: I love it.
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.
- 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'.
- Will not rewrite an immutable commit, of course. (Though you can tell it to, and for my personal repositories I sometimes actually set immutable() = root().)
- Will not do anything to diffs if it's uncertain where they should go, either because that file wasn't edited in a previous mutable commit, or because it was edited in multiple sibling/cousin commits. The latter is likely to happen with mega-merges.
You can use --into to restrict the set of target commits, if you already know what feature branch you're updating.
But I'm still not disciplined enough to use it and other proper workflows consistently, especially when new features/topics spring up while working on another one. I just start working on it at the same time, and jj makes it simple to split it into its own commit and branch later.
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.
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'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.
It is in fact a great tool, jj makes doing this even easier.
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 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.
> Now I am not one of those "the git CLI is too complex and git is too hard to learn" people, but I do acknowledge that puts me in the minority. But let's reframe that: if we can make something more powerful and easier? Sign me up!
That's nice.
I know of him better from his Rust work, but he explained himself well enough in his own comment that he probably doesn't need your assistance to reiterate a point he made very effectively on his own.
Also, he's not the only one who's used git since the early days.
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.
But it is great to know that it is trivial in jj. That's a reason to try :-).
Everything feeling like it’s a slightly uphill battle isn’t something you always notice until it’s suddenly gone.
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.
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.
None of that sounds like a positive to me, so I’d rather appreciate some evangelism to enlighten me and correct my misjudgments/misunderstandings which I fully admit are likely. :-)
It's probably easiest to understand by just trying it. It can be used with regular Git 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.
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?
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.
The matrix or people happily in a cult would be similar analogies. Perhaps even Stockholm syndrome...
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".
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”.
I think of jj as the ultimate refinement of git. It's not revolutionary, in the sense that there's no must-have feature, but it's a distinct evolutionary upgrade that's just way more pleasant than git.
It's telling that most hard-core git gurus are lukewarm on jj at first, but for everyone else, jj makes it way easier to do guru-level version control.
Just my $.02, anyway.
- 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.
What about `git reflog`?
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"
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.
Yeah - the other day I did a fetch, and then decided I didn't want to fetch. I simply ran "jj undo" to undo the fetch.
The git gurus responding how they can do X/Y/Z don't quite realize that git is still cumbersome enough that most people never bother to reach their level of proficiency.
Whereas jj is simpler, better-designed, and more powerful, so it's easier to start doing more advanced VC.
I also think everyone's underestimated how much universal undo enables exploration.
Once I started using jj, I realized how poor/annoying git's stash is. You don't need a concept called stash in jj. You have some work done and want to stash it? Just leave it alone as a leaf node (or split your commit into a leaf node). Then branch off the parent to work on whatever you want.
And with jj, my "stashed" node is version controlled in itself. I can make changes to it, undo it, all while still being stashed. (Maybe you can do this in git - I don't know).
Other niceties of jj:
Why do I need to come up with a commit message only when committing? Why couple the two? With jj, you can just decide to work on X, write your commit message "Adding cat photos to Easter Egg", and then work on the feature. Or you can just start working, and in the middle of it all, add the commit message, and continue working. You can change the commit message at any point.
Why do I need to fix merge conflicts immediately? Just the other day in jj, I made a change, which resulted in a merge conflict in several nodes. Whatever - I just branched off a clean node and worked on what I needed to. Only the following day did I go and fix the merge conflict.
Why can I only merge two branches? And why do I need to think of from/to when merging? A merge is basically creating a new node that is the child of multiple nodes. It's symmetric - there's no from/to. And you should be able to specify 10 parents if you want to.
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 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.
And outside of a few scenarios, they can use git, you can use jj, and everyone will be happy.
I push to my team's Git repo using jj all the time.
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...
bat just is more useful even if you didn't have issues with cat
The author starts by saying that they actually have no issue with the git CLI, which is exactly my case.
I started trying jujutsu and until now, I can describe the feeling as an ergonomic keyboard: I am fine with any keyboard, really, it's not that some keyboards are "hard to use". Nobody would (should?) say "with a cheap keyboard, you can't type fast enough to be efficient, but with this ergonomic keyboard, you will be faster". But one may find that an ergonomic keyboard is more comfortable for them, or that it sounds a bit better or feel a bit better.
There is no real need to change keyboard if you like yours. It doesn't mean that it's impossible to find another keyboard that would be more pleasant to use.
JJ may be that (for me, but again I'm just starting): it seems equivalent to git CLI (as in, it's not a heavy GUI or a VScode plugin: it's really a CLI) and it feels like it may be more ergonomic.
And at the very least, it's fun to try.
As it is, I go with whatever our clients require of us, and that isn't jj.
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.
> 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.
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
do you know about git rerere?
if yes - try jj.
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.
But I don't think parent comment to mine was "outrageously combative", I just thought it was a bit silly to claim a 100% conversion rate.
Cognitive dissonance is a thing.
And there's nothing crippled about thr git repos.
Try jjui for a great TUI experience on top of jj.
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".
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.
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.
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.
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..
This landed in a jj release a few months back. I assume it’s live in gerrit now too.
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.
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.
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:
https://andrewlock.net/working-with-stacked-branches-in-git-...
But I don’t see how that removes the usefulness of fixup commits, only that you can do them across stacked branches with ease.
But you’re saying I don’t need the particular hash of the parent, I can just rebase all the way back to main/trunk each time. That’s a good point!
I think I’m still saving time by fixup, it was the second hash lookup I wasn’t happy with.
I like to rebase my fixups immediately when possible so I don’t forget.
That’s where I made the comment about not actually running fixup. Instead of claiming to fix a SHA, I leave myself a note like “fix tests” so I can move it appropriately even if I get distracted for a few days
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:
Is it that I have to fall back to git for its changes? Or that I just shouldn’t use jj if I’m in a repo with lfs files?
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 used it for a week and switched back to `git`. Most of its hallmark features are not something I use that often.
Most of the projects I do for money are on Github and Gitlab.
I got my hands on things like YT videos explaining, official docs, cheatsheets.
About 4 hours exploring, trying to use with some side projects.
I did the same decades ago from CVS/SVN to git (even tried Hg). At that time it was obvious the "revolution" + "evolution" effect. Also git provided some transition tools that were easier to use from my pov.
Now it's just "evolution".
I see the added power to work with different "branches" simultaneously, move commits in different order in a way that is easier to have some better management logic, etc.
This is basically compelling if you work on different facets on big monorepos.
But I want to work on one thing a time nowadays, do something reliable, instead several features simultaneously, even with AI assistance, so the gains are not that huge, yet.
Like you I ended up using git commands again naturally. Stopping using svn commands eons ago felt amazing.
I've been meaning to give it another shot, and probably will after this article
There is also lazyjj as an interactive UI.
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.
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.
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
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...
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.
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.
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.
> 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.
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.
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.
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.
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.
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 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.
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.
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.
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.
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.
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.
You might be able to configure JJ to also block commits with certain descriptions as well, which might be useful in your case.
Some push blocker for certain commit name patterns would make sense to look into.
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.
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?
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.)
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.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.
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/
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.
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 :)
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.
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.
> 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 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.
Or we could just not have the problems to begin with.
To be clear where it ties to this post: it makes git far more convenient with nearly 0 learning curve.
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 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.
> Jujutsu is derived using the Hepburn romanization system. Before the first half of the 20th century, however, jiu-jitsu and ju-jitsu were preferred,
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 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.
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.
There's other TUIs and GUIs, but jjui is by far the best.
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.
Having to stage things every time was always a real pain for me (coming from Mercurial).
Having it autocommit is what I need well over 90% of the time. But then there are always those pesky files that for various dumb reasons I'm not allowed to put in .gitignore.
I could disable autoadding files, but life will be worse that way.
Damned if you do, damned if you don't.
``` [snapshot] auto-track = 'none()' ```
This is what I do, and I don't think it is worse. I prefer not having all my ignored files auto-tracked when I accidentally go back to a commit without them in the .gitignore
Some other solutions (which aren't simple at all): - Remove specific files from auto-track - Have a private commit with changes to the .gitignore and work on top of a merge commit - Like last one, have a private commit with the files you don't want to push (+ merge)
I have it setup where any commit with a description that starts with "IGNORE:" is private.
snippet from my config: ``` [git] private-commits = ''' description(regex:"(?x) # (?x) enables the x flag (verbose mode) allowing comments and ignores whitespace # see: https://docs.rs/regex/latest/regex/#grouping-and-flags
# Ignores commits starting with:
# (case-insensitive) PRIV: or PRIVATE:, or IGNORE:
^(?i:PRIV(ATE)?):
| ^IGNORE:
")
'''
```That's one way to look at it, but I would encourage you to think about it a bit differently.
JJ does not have a concept of "staging", it only has changes and commits. Yes, it automatically snapshots the workspace commit, but I wouldn't use the workspace commit as your staging area. If you want to do explicit staging use the parent commit (@-) as your staging area. You can move changes from the workspace commit (@) to the staging area (@-) explicitly, just like in Git. And you can "commit" (Git terminology) your staging area by starting a new staging area.
The difference here really is "only" that the workspace, the index, and committed changes are modeled with the same concept. And that is very powerful. Admittedly you have to make an informed decision on how to map your workflows onto the model, but that is what comes with the powerful flexibility that it gives you.
And there's toppling built around jj, such as jjui https://github.com/idursun/jjui
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.
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.
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.
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.
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.
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.
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.
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.
bitbucket will merge a PR from your feature branch onto the base branch even if it's not fast-forward from the base branch. as long as you use the "squash" or "rebase" option on the PR interface (instead of the "merge" option), the resulting history will be linear
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.
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.
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.
in a stacked PR workflow, after the bottom PR merges, you now want to rebase the whole stack atop the main branch. if that's 3 PRs, that's 3 branches, 3 rebases
one thing `git rebase` doesn't let you do but `jj rebase -s source -d dest` does is move a commit from one branch to another (`git switch dest`, `git cherry-pick source`, `git switch -C source source^`, `git switch dest`)
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.
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.
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 have to say however that the mega merge workflow seems intriguing and might in fact be a solution to having to serialize my work like I do in git for now.
I think I’ll look into that.
jackblemming•6mo ago
RGBCube•6mo ago
doritosfan84•6mo ago
I’d give [1] a read if you’re interested.
1. https://zerowidth.com/2025/what-ive-learned-from-jj/#commits...
stouset•6mo ago
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.
krackers•6mo ago
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.
sunshowers•6mo ago
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...
packetlost•6mo ago
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.
sunshowers•6mo ago
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.)
frizlab•6mo ago
sunshowers•6mo ago
frizlab•6mo ago
> 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
KingMob•6mo ago
Git worktrees suffice, but they're still heavier weight than `jj new whatever`.
packetlost•6mo ago
sunshowers•6mo ago
> 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.
packetlost•6mo ago
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!
sunshowers•6mo ago
packetlost•6mo ago
stouset•6mo ago
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.
frizlab•6mo ago
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.
BeetleB•6mo ago
Again, see my other comment. It's like saying "Humans are bad at multitasking - they should never switch to another branch until the current feature is done."
frizlab•6mo ago
Neither. Rebasing is an atomic task, kinda like a commit.
BeetleB•6mo ago
I may be misinterpreting your position, but I suspect you consider rebasing and fixing conflicts together as the atomic task. In jujutsu, these are two separate tasks. You do a rebase. Even if there are conflicts, the rebase is done. Fixing conflicts is its own task, and can be done at your leisure - no need to couple the two.
It's like writing some code that broke a test. You don't say "Hey, I'd rather not work on anything else until this is fixed." If you want, you either commit it as is (or stash if you prefer), work on something else, and come back to it later to fix the broken test.
frizlab•6mo ago
Hey that’s where we differ I guess! I do (most of the time, and if I truly need to switch, my workspace is cloned usually at least twice, using worktrees, so I can indeed switch whatever the current status of everything).
This is broadly what I was saying above, most of the “problems” git have are solved with worktrees, that have the undeniable advantage (IMHO) to actually separate tasks, instead of being in a state where (waving hands) everything is here in this big bowl of stuff (and I’m not saying that dismissively; once again everybody works differently; it’s just not my thing).
I do have understood the “conflicts do not need to be resolved straight away,” though I do not really get it (if there are conflicts the code does not compile anymore, so what is even the point? ok you can switch branch, but still it’s a task that I would know I have to do anyway and I could not switch to something else; my brain does not work that way).
All of that being said, since you seem to know a lot about jj, I’ll take the liberty to ask a question I do have about it, because there is indeed a workflow that I find painful with git that, IIUC, is actually solved by jj.
Let’s say I (or the team) am (is) working on two or three different things on two or three different branches. The full feature is the merge of these branches. Is jj able to maintain a branch which is the merge of all the other branches mostly (or fully) automatically?
BeetleB•6mo ago
If you have 3 branches, you can create a new node that is the merge of all 3. But your question is will it keep up with the changes.
If all 3 branches are local to your machine, and have not been shared, and you edit a prior commit, then all descendants are rebased and thus that merged node will get the changes - no new merges needed.
I think your real question, though, is if one of the branches gets a new child node, can we have the merged node dynamically merge from the new child, rather than from its parent?
I honestly don't know - but I actually do have a need for this! jj has bookmarks, and you can create a merge using bookmarks, but whether the new node will autoupdate if the bookmark moves - something I'd need to experiment with.
Finally, when you have shared/pushed your code, jj will treat shared nodes as immutable, so some fancy jj stuff becomes disallowed.
BeetleB•6mo ago
When I was pure git, I agreed.
Let me reframe your comment:
"When working on a feature, there's no way I want to work on some other feature in the middle of it"
(which is how many people felt before ever using version control and branches).
With jj, I do it all the time. The tool made the difference. A rebase is not something "special". Switching from a rebase to working on another feature in the middle is exactly just like switching to another branch in the middle of working on the current branch. It's the same (or very similar) set of commands.
When you realize this, then yeah - leaving in the middle of a rebase is pretty normal, and you wonder why people don't do it.
tcoff91•6mo ago
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.
1718627440•6mo ago
(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.)
sunshowers•6mo ago
1718627440•6mo ago
BeetleB•6mo ago
In jj, you run the rebase command. If you get conflicts, and want to switch to something else before you fix all of them, you simply do "jj edit" or "jj new" and work on something else. Then you do "jj edit" to go back to the conflicted revision and get back to fixing conflicts.
The key point is: You don't need to learn any new concepts.
In the git example above, I need to know tagging. I need to know checkout vs "checkout --detach", etc.
I'm not saying your git commands are crazy complex. They're merely more complex than one needs to solve this particular problem.
1718627440•6mo ago
The difference here seams to be that in jj you're always in a commit while in git you're always outside. But that's the difference between one and two commands.
Tags are hardly a new concept, if anything they are easier then a rebase. You can't really expect to use something with knowing the core concepts. Thats true for every tool and also for software. Can you use jj without knowing what a commit is?
I didn't knew about --detach before. It was obvious from the error and easily found using autocomplete. Also you only need it, if you want to change something under yourself while you're already modifying it.
stouset•6mo ago
It is essentially zero work in jj. I `jj edit` the revision in question, make the change, and `jj push`.
packetlost•6mo ago
plandis•6mo ago
packetlost•6mo ago
You can also just enable that feature (rerere).
wredcoll•6mo ago
Also probably most of us are stuck with whatever git*.com supports anyways...
globular-toast•6mo ago
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.
packetlost•6mo ago
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.
wredcoll•6mo ago
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.
packetlost•6mo ago
- reverts are significantly easier
- bisect will work because the code is expected to code/run at every commit
- rebase becomes easier in the common case
- the transport does not matter, at all. Fully anonymous contributions are possible
- commit messages are easier to write because the changes are smaller and more focused
Several commits could encompass changes necessary to make a particular feature work (ie. patchset) but because each commit is self contained they can be reviewed and tested individually (in order, usually) instead of a one big diff. It's easier on the reviewer, though there's likely a bit more overhead.
deredede•6mo ago
What do you mean by that? Even if you do review by emailing patchsets those are still managed locally using branches, to my knowledge.
packetlost•6mo ago
stouset•6mo ago
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.
windward•6mo ago
deredede•6mo ago
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.
nchmy•6mo ago
deredede•6mo ago
nchmy•6mo ago
1718627440•6mo ago
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.
nchmy•6mo ago
1718627440•6mo ago
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.
nchmy•6mo ago
stouset•6mo ago
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.
MrJohz•6mo ago
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.
landr0id•6mo ago
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.
frizlab•6mo ago
steveklabnik•6mo ago
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.
1718627440•6mo ago
KingMob•6mo ago
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.
1718627440•6mo ago
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.
KingMob•6mo ago
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.
steveklabnik•6mo ago
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.
1718627440•6mo ago
steveklabnik•6mo ago
1718627440•6mo ago
steveklabnik•6mo ago
baq•6mo ago
tomasyany•6mo ago
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.