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.
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.
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...
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.
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.)
> 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.
Git worktrees suffice, but they're still heavier weight than `jj new whatever`.
> 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.
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!
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.
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.
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."
Neither. Rebasing is an atomic task, kinda like a commit.
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.
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?
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.
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.
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.
git checkout master
git rebase [whatever]
[rebasing stuff]
git tag rebasing
git checkout --detach master
[do random other stuff]
git tag todo
git checkout rebasing
[continue rebasing]
When you are not trying to modify the same branch you're rebasing, you can omit --detach and also git tag todo.(To clarify, it has never occurred to me that I even want to do that, so I didn't knew how to do it. Yet I didn't even needed to consider a manual, it just follows naturally from the git user model even if it seams completely unidiomatic.)
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.
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.
It is essentially zero work in jj. I `jj edit` the revision in question, make the change, and `jj push`.
You can also just enable that feature (rerere).
Also probably most of us are stuck with whatever git*.com supports anyways...
Think about the word "branch". If you have a linear sequence of commits that's one branch by definition. But you go and label the middle of that branch as branches too and then get annoyed when git, you know, does some branching there.
It's not 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.
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.
- 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.
What do you mean by that? Even if you do review by emailing patchsets those are still managed locally using branches, to my knowledge.
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.
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.
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.
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.
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.
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.
I’d give [1] a read if you’re interested.
1. https://zerowidth.com/2025/what-ive-learned-from-jj/#commits...
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.
With jj you just "jj op undo <operation_id_that_fucked_your_repo>" and you're fine.
Editing prior commits is also pretty easy, which in turn makes fixing merge conflicts pretty easy too.
jj has two kinds of these logs: the evolog and the op log.
The git reflog is based on, well, refs. Whenever a ref is updated, you get an entry. This log is per ref. That's HEAD, your branches, your tags, and your stash.
jj's evolog is sorta similar, but also different: it's a log, but per change (think commit in git). This means it is broader than per ref, as it includes not just commits that correspond to a ref, but all of them.
jj's oplog is a log per repository. This lets you do things like `jj undo`, which lets you get the entire repository, not just one ref or commit, back to the previous state, easily.
But instead of commits, most of the time you're working with (the not greatly-named) "changes", which are a conceptual history of related commits. E.g., a single change ID points to the most recent commit in a list.
As long as you keep editing on a change, it keeps accumulating commits under the hood. When you move to another change (via `jj commit`, `jj new`, etc), all your new commits end up under a different change.
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.
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.
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.