When I tried jj, I found a few pain points that made me return to Git. For instance, I was sharing a branch with a co-worker where we were just piling commits as soon as they were ready (after `pull --rebase` if necessary). Since jj doesn't have names branches, that workflow was easy with git and tedious with jj – even with the `tug` alias. The process in the "Tracking remote bookmarks" chapter of this tutorial still doesn't look nice to me.
Another pain point was that jj could not colocate with light clones, like `git clone --filter=blob:none`. Maybe that's fixed now.
Once you track the remote bookmark, `jj git fetch` will update your local one to match the remote.
Whereas `git checkout <bookmark>` puts you in a state where you've checked out the _branch_ and not the commit, which means if you create more commits, they'll automatically be added to <bookmark>. To create a branch forking off from <bookmark>, you need to first create a new named branch, then start adding commits to it. (There are other ways as well, but this is the most standard approach.)
The tradeoff with Jujutsu's approach is that it's very easy to create lots of lightweight spin-off branches (just `jj new <fork point>`, no need to come up with names first). But it's slightly harder to do the traditional linear approach where you're consistently committing in a straight line on a single branch, and then syncing that branch with another remote. This is because for each new commit you create, you also need to update the bookmark manually.
In the parent poster's case, it's probably complicated further by them working on the same branch as someone else. Assuming there's no rebasing going on, this shouldn't be too complicated, but it's another case where in git you would check out a branch, and `git pull` will automatically forward you to the head of the branch, whereas `jj git fetch` will just move the bookmark without moving you.
It sounds like the workflow that this person is using doesn't fit that well with how jj "wants" to be used. I believe there's been some talk in jj of a way to automatically update branches when creating a new commit, which would help somewhat, but I think it's also a natural effect of having built their workflow around git, and that flow just not quite working so well in jj.
That's because "branches" in git are an imaginary concept, they are simply a label you give to a certain commit, nothing else, nothing more.
Moreover, in git this is further complicated by the fact that you may have branches with the same identical name on different environments (your local and remotes). So you can have, by any means, 4 `feat/foo` branches all being the "right" `feat/foo`, and you need to decide which one is the right one (we tend to default to some origin, but what if you have local work that is ahead of origin? maybe on two different computers? Meanwhile your branch is on a fork of a different origin. Git branches do nothing but complicate a simpler model where what matters is the commit, history and diff, yet we fixate on label names which are just conveniences).
At the end of the day branches do nothing but add complexity, I much prefer the jj model, bookmarks are just _local_ names you give to some edit, you move the bookmark manually, and it _may_ point to a git branch just to preserve the git backend.
They're really not. The whole Git ecosystem makes a very clear distinction between branches and tags, even though they are virtually the same thing in the underlying data model.
For example, if you checked out a branch and then do a git commit, the branch name tag will be automatically moved to the new commit. Conversely, if you checked out a tag (or a random commit that is not the head of a branch) and then run git commit, the new commit will not get any tag.
Another example is `git rebase`, whose semantics only make sense if you consider that branch names refer to branches, not just to their latest commits, since they often modify many children of those branches.
> Moreover, in git this is further complicated by the fact that you may have branches with the same identical name on different environments (your local and remotes).
This is presented as more complicated than it is. If you are working with multiple remotes, you may end up in these complex situations, true. But Git is pretty clear about the names and identities of each of these. You have the local branch, `feat/foo`, which typically has as a default upstream the remote branch named `feat/foo` on a remote called `origin`; the local copy of that branch is the tracking branch called `origin/feat/foo`. If you have other remotes `remote1` and `remote2`, the tracking branches of those are called `remote1/feat/foo` and `remote2/feat/foo`. You will never want to commit directly to any of these tracking branches - instead, you'll create either separate local branches with each as an upstream(`feat/foo-remote1`, `feat/foo-remote2` - if you want different commits for each remote), or directly push from `feat/foo` to all of them if the intent is to keep them in sync (`git push feat/foo:remote1/feat/foo` or similar).
On your specific repository.
I agree with you that this is mostly unnecessary complexity — branches being anonymous is one of the big reasons I like jj, and why I think it's so useful to people just getting started with version control, because it is a simpler conceptual model. But it's not correct to say that branches in git are imaginary.
I have a repo with some code that generates a credential and writes the credential to a location specified in .gitignore so it isn't picked up by version control.
I used `jj edit` to roll back to a change before the credential path was added to the ignore file to make an unrelated change.
The result? jj instantly started tracking the credential and I didn't notice it before pushing to GitHub.
Fortunately I did figure it out pretty quickly, but that could have gone very poorly.
See also https://github.com/jj-vcs/jj/issues/7237.
I'm not sure if this is somehow related to my IDE, working in a huge monorepo, or something else, but it has been quite painful.
Aside from that, though, I do really like the flexibility jj provides.
(i've found jj undo quite robust, i'd be surprised if you ever actually lost work tbh)
It's not
False. You need to call `jj branch set -r@ XYX` manually which can be a PITA but you only need to do that once you push. Or there is `jj git push --named XYZ=@` which moves the branch.
This is a subtle difference, and most of the time it doesn't matter at all, but in this case there is a difference between how bookmarks work in jj, and how branches work in git.
Like? This isn't explained, I'm curious on why I would want to use it, but this is just an empty platitude, doesn't really give me a reason to try.
IMO, the authors and evangelists of Git are essentially correct when they argue about its power.
However, I think that it's extremely difficult to gain practical experience with using Git in a high-powered, high-agency way, mostly because there are a lot of abstract concepts at play and there is no easily accessible place where these concepts can be "discovered".
Basically, Git is as good as it's cracked up to be, but only if you're an expert.
If you're interested in becoming a Git expert, I cannot recommend Emacs Magit strongly enough.
If not, I think Jujutsu could be an quicker road to a high-agency version control workflow. It's at least worth considering. I feel confident that Jujutsu can succeed, in particular because of Git's harsh difficulty curve.
And then Jujutsu came along and casually doubled my VCS productivity. I didn't see it coming!
I am interested to know, because there seem to be a small number of people who really seem to like it, and up to this point I haven't been able to understand what it is that they are all so excited about.
1. I understood git better after ten minutes of jj than after fifteen years of git. Git just doesn't expose its underlying data model as well as jj does. I guess, if you already know git well, this isn't going to make a difference for you.
2. This question is a bit like asking what can I do with a calculator that I can't do with pen and paper? Technically, nothing, but everything will be so much easier that you'll be much more likely to use it. Even though I can, technically, stash my worktree and jump to another commit with git, it's so fiddly to unstash (especially with multiple stacked switches/stashes) that I just never did it.
With jj, I leave commits in the middle and jump to other commits (to fix a bug or make a small change I noticed I need while working on a larger change) all the time, because there's zero friction.
jj just removes all the friction that's prevalent in git. Things are easy in jj that in git are merely possible.
For git users who are wondering "What friction? I just git stash and jump to another branch":
In jj, you just jump without needing to type any command like git stash.
I'm new to jj. I'm still mixed on if I like it not. I think it's mostly familiarity. For example, switching to a commit puts things in the state before the files were committed. All my projects have a presumit step that says "hey! commit your files!" so they are all incompatible with jj at the moment or at leas the default. I end up having to do temp stuff like `jj new` (ok, now they're committed). Now run my presubmit scripts. Then `jj undo` so I don't have this unneeded commit. That said, I'm sure there's a better way, I just haven't gotten used jj yet.
Others have said this, `jj undo` and `jj op restore` have been lifesavers though. No matter what I do I can get back to where I was before I messed up.
I use the stash for changes I like or for small experiments, not tied to anything. For any other changes, I just create a wip commit and switch. It’s trivial to switch back and soft reset.
The nice thing is that all of this is part of the commit graph, not buried in stashes hidden from sight.
It's a stack of diffs.
Anyway, I've probably used this to transfer changes between branches thousands of times. Once you grasp the underlying data model all these abstractions introduced by jujutsu seem more confusing.
That said, I do understand most people aren't going to take the day or so to read through any of the hundreds of detailed "explain the data model" articles online.
>> With jj, I leave commits in the middle and jump to other commits (to fix a bug or make a small change I noticed I need while working on a larger change) all the time, because there's zero friction.
> For git users who are wondering "What friction? I just git stash and jump to another branch"
git stash is not equivalent to jumping to another commit in jj
You could just use cherry pick.
This quote confused me for a while. I was thinking "git stash isn't branch specific its just a single bucket". But I realoze you must be making lots of little changes that are highly branch specific and then not wanting to commit those, but instead stashing them. Which would leave you with a hellscape of stashes that can't just be unstashed.
The biggest problem with git is people just inventing asinine ways to do things and ending up with absolutely stupid problems like that. No sane person does these things but yet I do keep encountering people digging holes and falling in them. It's a bit like people who invent the clever idea of having one repository with multiple code bases on different root branches. It's possible but you dont deserve to be working in this industry if you think its a good idea.
Git is simple. It's stupid simple. That's its problem.
No, I'm specifically responding to the person above who claimed "git stash" is the same as switching to another commit in jj. It's not.
In git, if you get a conflict, you feel like you have to resolve it now.
With jj, most of the times I get merge conflicts, I simply ignore them and deal with them later. A conflict is not at all a blocker.
I guess I view that as a positive rather than a negative. I'm not saying that dealing with merge conflicts is a picnic -- it isn't. I just find it difficult to believe that ignoring them and resolving them later will improve the situation in the long run.
But really, it’s about something deeper: rebase is a first-class operation in memory, and not a serious of patch applications via the file system. They’re therefore lightning quick, and will always succeed, which is nice. If you get partway through resolving a conflict and want to change to something else for some reason, that’s possible and simple.
Typically for me, "later" means "immediately after the rebase command has finished", which is very similar to git's "while the rebase command is running", but has some subtle and important differences.
For example, because the rebase completes first, I get to see roughly what the end-state of the rebase is before I start doing the hard work of fixing conflicts. This is useful as a sanity check - sometimes the reason I'm getting a bunch of merge conflicts is because I was rebasing the wrong things in the first place and included an extra commit somewhere. Seeing the result first gives me the chance to sanity check what I'm doing.
Another thing is that my repository is never in a broken state where I can't continue doing other things. There's no way in git to stash a rebase, say because I've realised a different approach might work better or just because I urgently need to work on something different. I either need to cancel the rebase and start it again later, or keep on going until it's over. In jj, because the conflicts are automatically checked in as part of the commits, I can easily jump backwards and forwards between the work I'm doing resolving my conflicts, and anything else.
Another way of thinking about it is that git is very modal. You check out a branch and are in the "branch" mode, and then you start a rebase and are in the "rebase" mode, and then you do a bisection and are in the "bisect" mode - it's difficult to move freely between these modes, and there's certain things you can only do in some modes and can't do in others. In contrast, jj has exactly one mode: "there is a commit checked out". The different operations like rebasing all happen atomically, so you never see the halfway state. But because things like conflicts can be encoded in the commit itself, you still have all the same power that the modal approach had, just with a simpler conceptual model.
with jj, you have the option to fix half of it and come back later. you can take a look and see how bad the conflicts are if you go a certain route and compare to another option
Sorry? You what? How do you know which bit from which source goes where?
You do a git pull, just so your branch isn't so out of sync. Immediately you get merge conflicts. You then tell jj "Hey, I'll deal with this later", and make a branch off of the last commit that was conflict free and continue your work there. jj stores the conflict as is, but your branch is conflict free.
When you feel you have the energy to deal with the conflict, you switch to the branch that has the conflict, and fix the issue(s). Then you can manipulate the graph (rebase, whatever) so you can have everything in one branch - your changes and the changes you pulled in.
So you just kick the can down the road and end up with possibly an even more difficult conflict resolution?
That sentiment is true for pretty much anything in life one may decide to defer till later :-)
More concretely, it's often not hard to tell if deferring it will make it worse, or merely the same.
The whole point of version control is to give your mind some peace in not worrying about things ("Let's make this change and we can always revert if it doesn't work out"). Conflicts are no different. There's no fundamental reason a conflict needs to be treated like an emergency.
Sounds like something that could also become a flag for git merge.
He’d also be free to edit upstream to not commit, or split a change in two so that parts unrelated to the conflict are in their own change. The big idea is that he doesn’t need to blindly decide that before seeing what the conflict is.
> Sounds like something that could also become a flag for git merge.
Before I started using Jujutsu, I didn't have any pain points with using Git. I didn't understand what all the fuss was about. Git works well! So I totally understand how most Git users have that same reaction when hearing about Jujutsu.
I think the reason I even tried it out in the first place was because Steve Klabnik wrote a tutorial about it. I have a lot of respect for him, because the Rust book is really good. So I though: If Steve thinks it's worth it, I should probably check it out.
Now that I'm used to jj, going back reveals like 100 things that are immediately super annoying when using git. I don't feel like writing it all down TBH. :-) In a general sense, Jujutsu get's out of your way much better than Git. There are a lot of situations where Git blocks you from continuing to work. Have a merge conflict? Stop working, fix it right now. Want to check out another branch? Nu-uh, clean up your dirty worktree first. jj doesn't do that. Have a conflict? I'll record it in the commit, fix it whenever you like. Checking out another branch? No worries, I'll keep your work in progress safe in a commit.
"megamerges" are one such example. ive shared many links, here and in other posts
Yeah, I was looking for something (or "things") specific. An "I hate everything about it" explanation doesn't really compel me to try out the alternative.
> "megamerges" are one such example. ive shared many links, here and in other posts
I read through one megamerge link you shared ( https://v5.chriskrycho.com/journal/jujutsu-megamerges-and-jj... ). So the argument seems to be (forgive me if I'm reading this wrong), if you have multiple versions of a single set of source files that all have differing changes, for you JuJutsu makes it easier (easier then git, that is) to merge them into the final commit you want to end up with. Is that correct?
Just trying to make sure I understand. Honestly, after reading that article I am still not feeling the need to try Jujustu out. I'm still open to being convinced, but have yet to see anything that makes me go "wow, I need to try that!".
At this point A LOT has been written in this and other threads, as well as lots of essays and tutorials about how jj just completely transforms your workflow. If you're curious, you'll seek it out. If not, that's fine as well.
You don't have to do that, and you rarely push it to others. History looks the same as git, usually, although I end up rebasing more than I ever did in git, since it's easier and safer.
You might not find that feature, but I'd suggest giving it a go anyway. The list of jj technical superiorities is short, but the numerous quality-of-life DX improvements all add up to pleasant, fearless version control.
Even without editor support or a UI, I abandoned git forever last year after using jj for a couple weeks.
Just my $.02.
Much like BitKeeper was sort of like an automated set of conventions on top of SCCS, (and Git and BitKeeper are near-interchangeable if you don't look at any of the details,) jj is like an automated set of conventions on top of Git.
(I personally wish the jj project had leaned harder into "it's just Git operations, made easier" instead of the whole "abstraction over storage layers" spiel, which needlessly scares a person familiar with Git, and makes the project sound very wishy-washy. When you peek under the hood, it's just Git! If it wasn't, I probably wouldn't use it!)
Yes, Emacs' Magit and Git Cola.
Beyond `jj undo` everything else in this thread feels just as complicated as git.
I also found the exchange about named branches funny, that ends with:
> Ok, you need to call `jj bookmark set -r@ XYX` (or `jj b s -r@ XYX`), so what?
Apparently this is excusable, but people like to complain about git's commands being too obtuse - as far as I understand the git version is "git checkout -b XYX", right? (Or I guess "git switch -c XYX" with the new commands)
The difference is actually worse than that. There is not the regular git equivalent, because this step is just done implicitly for you, normally. That is, with jj, just because you had checked out the head of main and then you added a new commit, doesn't mean your new commit is now the new head of main. `jj bookmark set -r@ main` is the way you tell jj to actually advance main to your latest commit.
But you are right - `git switch -C main` would be more or less the equivalent in git if you were working in detached head mode, which is how jj normally works (note the `-C`, not `-c`, to forcefully update main to point to this commit).
Basically, jj is just like working with git in detached head mode as far as I can tell.
jj doesn’t have a “name a new branch and switch to it” command, because you usually don’t bother naming branches until you’re using them up to a forge, and there’s no “current branch” concept. I creat new named branches with “jj git push -c” which names it for me, and switching branches is closest to jj new or jj edit.
It's kind of like asking "why would I buy a digital camera when my film camera does all the same things? I can already see what the photo will look like when I take it, and developing my own film isn't that much of a hassle", yet film cameras have gone the way of the dodo, except for the occasional nostalgic enthusiast.
Their question is more, "why would I buy a digital camera that takes pictures in a new format that only a few cameras understand? All my tooling, 3rd parties, and other camera I own use the standard format. Even though I can see why the new format has advantages, I am still going to have to use the other format for all these other photos I have to work with, and there aren't equivalent tools in the new format for all these other photos things I need to do. Even if I buy this new camera, I am still going to have to work with the old format, so I'll have to learn how to use two formats now, and get used to two tool chains. Since the existing format is something I am going to have to use either way, how is it worth it for me to have to use two formats?"
Maybe you feel that jj git fetch and jj git push are using "git" but it means you avoid the git cli in favour of the IMO better designed jj cli.
So, I claim that Jujutsu actually doesn’t work well with git repositories (and forges) very well, and I would like to see a native one.
The only issue is being in detached HEAD all the time, but in practice it's not a big problem for me.
Most of jj’s stuff is purely local. I woke on GitHub with jj just fine, the only thing people notice is that my autogenerated branch named are a bit odd.
> not missing out on power
Two very different claims, and it only makes me more skeptical.
You can do all that in Git, but I sure as hell never did; and my co-workers really appreciate PRs that are broken into lots of little commits that can be easily looked over, one by one.
> I basically always force push
How do your colleagues deal with this, or is this mostly on experimental branches or individual projects?
The net effect is that I can change "my" branches as I wish, but I can't change stuff that's been merged or other folks' branches unless I disable the safety features (either using `--ignore-immutable` or tracking the branch).
JJ also makes it really easy to push a single changeset as a branch, which means as you evolve that single commit you can keep the remote updated with your current work really easily. And it's got a specific `jj evolog` command to see how a specific changeset has evolved over time.
This has it's problems, and there's a reason things like Geritt are popular in some more sophisticated shops, as they make it much easier to review changes to PRs in response to reviews, as an example.
+1 to sibling gerrit recommendation; I used to use it a decade ago and it was better then than GitHub PRs today.
And if you have conflicts, it's really easy to rebase and fix any issue.
In a PR branch, my branches usually have a bunch of WIP commits, especially if I've worked on a PR across day boundaries. It's common for more complex PRs that I started down one path and then changed to another path, in which case a lot of work that went into earlier commits is no longer relevant to the picture as a whole.
Once a PR has been submitted for review, I NEVER want to change previous commits and force push, because that breaks common tooling that other team mates rely on to see what changes since their last review. When you do a force push, they now have to review the full PR because they can't be guaranteed exactly which lines changed, and your commit message for the old pr is now muddled.
Once the PR has been merged, I prefer it merged as a single squashed commit so it's reflective of the single atomic PR (because most of the intermediary commits have never actually mattered to debugging a bug caused by a PR).
And if I've already merged a commit to main, then I 100% don't want to rewrite the history of that other commit.
So personally I have never found the commit history of a PR branch useful enough that rewriting past commits was beneficial. The commit history of main is immensely useful, enough that you never want to rewrite that either.
While working on a maintenance team, most of the projects we handled were on svn where we couldn't squash commits and it as been a huge help enough times that I've turned against blind squashing in general. For example once a bug was introduced during the end-of-work linting cleanup, and a couple times after a code review suggestion. They were in rarely-triggered edge cases (like it came up several years after the code was changed, or were only revealed after a change somewhere else exposed them), but because there was no squash happening afterwards it was easy to look at what should have been happening and quickly fix.
By all means manually squash commits together to clean stuff up, but please keep the types of work separate. Especially once a merge request is opened, changes made from comments on it should not be squashed into the original work.
I try very hard to keep my PRs very focused on one complete unit of work at a time. So when the squash happens that single commit represents one type of change being made to the system.
So when going through history to pinpoint the cause of the big, I can still get what logical change and unit of work caused the change. I don't see the intermediary commits of that unit of work, but I have not personally gotten value out of that level of granularity (especially on team projects where each person's commit practices are different).
If I start working on one PR that starts to contain a refactor or change imthat makes sense to isolate, I'll make that it's own pr that will be squashed.
Like the GP said
> > If not, it doesn’t matter.
You might as well just conclude that it’s subjective since there’s no progress to be made.
Why?
Example #1: - I am working on implementing API calls in the client, made 3 commits and opened a PR - In the meantime, the BE team decides they screwed up and need to update the spec
If I now go and fix it in the commit #1, I lose data. I both lose the version where the API call is in its original state, and I lose the data on what really happened, pretending everything is okay.
Example #2: - I am writing a JVM implementation for our smart-lens - In commit #2 I wrongly implement something, let's say garbage collection, and I release variables after they have 2 references due to a bug. - I am now 6 commits ahead and realise "oh shit wait I have a bug"
If I edit it inline in commit #2, I lose all the knowledge of what the bug was, what the fix is, what even happened or that there was a bug.
tldr: just do an interactive rebase
You can always end up with the same set of published commits, guaranteed. But the tools you have for manufacturing them and for interacting with their history definitely include things that are possible in JJ but not in Git.
This is not contrived — this is an entirely realistic scenario that I use jj to handle all the time.
You could also keep the rebased commits, abort the rebase, rebase the already rebased commits and then continue the first rebase.
I would consider the first workflow to be impossible to do by most mere mortals in Git [1]. Meanwhile in jj it's downright trivial.
[1] There technically is a way to do this by setting a temporary branch, aborting the rebase, starting another rebase -i, carefully editing the interactive instructions, going to commit 8, editing that commit, then cherry-picking 9-15 from the temporary branch. But it's too hard to do in practice, and far too easy to get wrong.
That's what I've described?
> rebase -i, carefully editing the interactive instructions
You neither need to use interactive rebase nor carefully edit, since there is rebase --onto.
> But it's too hard to do in practice, and far too easy to get wrong.
I do this often it's not more complicated then any other rebase.
What is annoying in Git is rebaseing across multiple merges while forging committer and date information. Can JJ do that better?
I'm glad you don't find it too difficult to do. It's a workflow that seemingly works well for you!
My interpretation is that jj makes certain useful operations convenient to use that would be so complex in git as to be completely impractical. Something like jj undo would be a simple example: jj users can do it, and git users can’t, even though it’s logically possible in both systems.
It’s for other branches that hang off the commit that introduced the conflicts.
There is value here, but I think it is more like “add a new command consisting of 50-100 lines of code” not “write an entirely new VCS.”
B --> X --> Y (main) --> Z --> @
\
--> G --> H
B is a base; yesterday the name "main" pointed to it, and today "main" points to Y. Z is a commit you wrote that you haven't published yet. "@" means "Working copy", which is a way of saying "what your filesystem looks like." So, at this time, you see the changes from B, X, Y, Z, but not G or H.You want to rebase G --> H from B to Y. But unfortunately, G conflicts with X. H does not conflict with anything. When you run this rebase in Git, you will actually have to immediately fix the conflict between G and X in order for the rebase to continue. If you do not solve it right then, the entire rebase fails. Git's rebase is actually an algorithm represented by a state machine; you must solve the conflict to proceed from "conflicted state" and `git rebase --continue` the rebase algorithm. (If you imagine what you would need to do to actually implement 'git rebase' as it works today in your own code, this state machine model makes immediate sense.)
In Jujutsu, rebase is a non-stop operation and it always succeeds. There is no state machine. It will update the commit graph to look like this:
B --> X --> Y (main) --> Z --> @
\
--> G --> H
C C
Now G and H are marked as "conflicted". If any commit is marked as conflicted, then all (transitive) children are marked as conflicted, too. If you "switch over" to working on G, then you can solve the conflict and commit the solution. That will solve the conflict in G, and also H as well.But you don't have to do anything. In the above graph, G and H are conflicted, but because they are not a parent of `@`, then it does not matter. They exist in a parallel universe that does not influence your own. You can keep compiling code as usual. If you "switched over" to G, then the conflict is "materialized" in your working copy (filesystem) by putting conflict markers in the files, and so you have to solve it to keep compiling.
In short, Jujutsu separates conflict computation (do patches X,Y have a conflict?) from conflict materialization (make the conflict appear with markers in a file), and materialization of conflicts is "lazy" -- it only happens if a conflict exists transitively in the history of your working copy. Resolution is then done at your leisure.
A more brainiac way of thinking about it is that Jujutsu is a tool for manipulating _commit graphs_, and that is a purely computational notion; adding edges, removing edges, etc are all just basic algorithms. The graph's nodes contain "content" and states like "conflicts" are just defined as a relationship C(X,Y) on nodes in the graph. But all of this is "purely computational." Imagine implementing Jujutsu's rebase command; it is just a trivial reparenting of some graph nodes, something an amateur programmer could do. Calculating the relationship `C` is a bit more involved, but not complete black magic. But none of this involves "reading files from disk" or whatever. The side effect of "update the files on your filesystem to look like state XYZ in the graph" is just that: a side effect that the tool does when it is needed. Git, in contrast, only works through "side effects" in that it tends to only operate on the working copy, and never the "holistic commit graph". And so Jujutsu works at a higher, more "pure" level.
-----
Fun fact: in some cases, you do not actually have to "switch over" to G in order to solve this conflict, either. It is actually possible to craft a "solution" to the conflict in G while on top of Z. Then you can do `jj squash --from @ --into G` and you can "teleport" the resolution into the conflicted commit, solving both G and H, without ever making it appear in the working copy. This happens in cases like "G modified a file named readme.txt that was deleted by commit X"; all you have to do is "re-delete" the file inside commit G and it is trivially solved. This is something that is, quite literally, impossible to do in Git.
[G: original, G' with conflicts, G" resolved]
What value do you get from G' and H' existing with conflicts when you can't use the working tree until after you have resolved the conflicts?
So in Git it would be G -> G", but in JJ you can do G -> G' -> G". But G" in both cases only exist, until after you have put in the work of solving the conflict. And G' only ever exists without a usable working tree. So what do you get from having G' earlier, when you still have G" only after the same work?
jj's handling of merge conflicts is pretty much like in Git committing the conflict markers in git and editing the commit message to say "conflicting".
When you want to work on an older commit for a longer time and don't want to stay in a rebase, you just check it out and work normally, when you are done and want to propagate your changes, then you do a single rebase.
You can do anything with a Turing machine. That you can isn’t the point. The point is the tool does all the things you can automatically and correctly so you don’t have to. There’s no ’just do this or that during rebase, or outside of it’. There’s only ‘it rebased everything correctly without a single thought, nice’.
Yes, and my point is that having a rebase and edit everything isn't too different from first modifying everything and then doing an automatic rebase.
With `jj new` + `jj squash`[2], you're collecting work that you can review as a separate thing anytime as you go along. You don't have to remember anything. If you throw in an unrelated change, you'll notice it if you review the changes before squashing them, so you can split it out then. And I'm pretty much always working in this state even when I'm at the top of my branch, so `jj new some-deep-node` doesn't really change anything. If I get called away and have no memory of what I was doing when I return, it doesn't matter: my jj state tells me exactly where things are and what I was doing.
[1] Which is not a huge problem, you have deferred conflict resolution so if something goes wrong you can probably just repair it with normal editing or your editor's undo functionality.
[2] I don't usually bother with `jj new -A`, since I'm going to squash my "out of line" temporary commit into the linear chain anyway. `jj new -A` is more similar to `jj edit` than `jj new` -- it shares some but not all of the modal disadvantages. So perhaps my answer to your actual question is: "yeah, I dunno either."
A lot of the jj strategies in this thread are a bit more cowboy, and I’m surprised.
1. Your working copy contains whatever mish-mash of changes you want.
2. When you’re ready to stage and commit these changes, run `jj commit --tool gitpatch`
3. The iterative “stage this hunk?” UI from git lets you choose what to commit.
4. Your editor opens for a commit message.
5. The changes you selected are now in a new parent commit of your working copy, and the remaining changes are left in the working copy commit.
In addition to the _same_ workflow, jj makes it easier to have other workflows as well (you may be interested in the megamerge workflow if you’re always working on multiple tasks at once).
[1]: https://zerowidth.com/2025/jj-tips-and-tricks/#hunk-wise-sty...
#1: Squashing
Create a revision for the feature, then create another revision atop that.
$ jj new main -m 'feature'
$ jj new
$ jj
@ trtpzvno samfredrickson@gmail.com 2025-09-01 12:32:33 9ac76a0f
│ (empty) (no description set)
○ wvzltyyr samfredrickson@gmail.com 2025-09-01 12:32:31 80b2d5d0
│ (empty) feature
◆ zxrulorx samfredrickson@gmail.com 2024-12-11 03:44:38 main 351a2b30
│ all the stuff
$ vim
$ jj
@ trtpzvno samfredrickson@gmail.com 2025-09-01 12:34:50 5516c2b9
│ (no description set)
○ wvzltyyr samfredrickson@gmail.com 2025-09-01 12:32:31 80b2d5d0
│ (empty) feature
◆ zxrulorx samfredrickson@gmail.com 2024-12-11 03:44:38 main 351a2b30
│ all the stuff
~
$ jj squash -i
# interactively choose hunks to squash into parent
$ jj
@ oxqnumku samfredrickson@gmail.com 2025-09-01 12:35:48 8694aa34
│ (empty) (no description set)
○ wvzltyyr samfredrickson@gmail.com 2025-09-01 12:35:48 47110bff
│ feature
◆ zxrulorx samfredrickson@gmail.com 2024-12-11 03:44:38 main 351a2b30
│ all the stuff
~
#2: SplittingCreate a revision for the feature, then split it up retroactively.
$ jj new main -m 'feature'
$ jj
@ snomlyny samfredrickson@gmail.com 2025-09-01 12:38:39 84c6ecaa
│ (empty) feature
◆ zxrulorx samfredrickson@gmail.com 2024-12-11 03:44:38 main 351a2b30
│ all the stuff
~
$ vim
$ jj
@ snomlyny samfredrickson@gmail.com 2025-09-01 12:39:51 8038bdd4
│ feature
◆ zxrulorx samfredrickson@gmail.com 2024-12-11 03:44:38 main 351a2b30
│ all the stuff
~
$ jj split
# interactively choose hunks to keep, splitting the rest into a new revision
$ jj
@ zpnpvvzl samfredrickson@gmail.com 2025-09-01 12:41:47 5656f1c5
│ debugging junk
○ snomlyny samfredrickson@gmail.com 2025-09-01 12:41:44 1d17740b
│ feature
◆ zxrulorx samfredrickson@gmail.com 2024-12-11 03:44:38 main 351a2b30
│ all the stuff
~
Pretty easy. While inaccurate, it's useful to think of jj as two separate repositories. One is the "clean" one that has everything nice and neat. The other is a repository of all your (very) incremental changes.
You have to actively decide what goes in the "clean" one. jj automatically puts stuff in the messy one. Any time you actively commit something, you're committing to the clean one. So you decide what goes in there.
When you do a push, only the "clean" commits are pushed.
[1] https://jj-vcs.github.io/jj/latest/revsets/
[2] https://stackoverflow.com/questions/22520751/what-is-the-git...
And, well, it's silly, but I do like revnums. It's a compact way to compare changes over time, even if it's only useful for the local repo. Would be nice to have those too.
Like let’s say you have 4 separate PRs in review that have no dependency on each other. You then work on new stuff on top of an octopus merge of all 4. You are exploring different approaches to a solution so you have several anonymous branches where you have tried different things. You want to rebase on master, so you just run jj rebase -d master. All 4 PR branches, the octopus merge, the anonymous branches, they all get rebased with that 1 command. If there are conflicts the first class conflicts mean that you can fix the conflicts whenever you want. If one of your experimental anonymous branches is in conflict but you are unlikely to go with that approach, just leave it in a conflicted state unless you change your mind that you want to actually go that direction.
> Jujutsu is relatively new and doesn't cover 100% of the features of Git yet.
So it's more powerful except when it's not.
JJ is MORE powerful than git because has a BETTER SEMANTICS & ABSTRACTION.
That its. Is like when git emerge, it was more powerful than svn because was distributed.
I wanna make the point clear: Is NOT about the "amount of features or specific workflows" that with pain can be made on git and with effort could be retrofitted on jj eventually (if today are missed, like a equivalent of GitHub!).
The power is what abstraction made jj that is different to git: We work on "commits" all the time, and not need to manually sync the state. Everything derive from this.
And because is a better abstraction, it make more sense and is easier to understand.
The UX derive from this.
It’s just a very “consistent” experience. I never really had issues with git (so didn’t expect I’d stay with jj), but for anything more advanced I’d generally have to google. In jj everything is based on a couple primitives, and it’s easy to combine them to do history reshaping of arbitrary complexity.
My workflow used to be very stash-oriented, and with the way jj changes work, auto tracking, and letting me switch between changes, it ends up being much more pleasant than git.
Rebasing with conflict resolution is generally much nicer too (esp. in the stash-like workflow).
Anyway, very recommended, and effort required to switch is very small.
[0]: https://kubamartin.com/posts/introduction-to-the-jujutsu-vcs...
JJ seems to be part of a new "era" of tooling that's just really good. I mused about this a bit in a blog post:
https://pdx.su/blog/2025-08-13-the-quiet-software-tooling-re...
Mise, jj (and its phenomenal jjui TUI, which I see you mentioned there), and uv for python are nothing short of revolutionary, as far as I'm concerned. Just beautiful tools.
zoxide is similar and beautiful as well. I use both all day.
Does anyone have a use case where they use mise with Python? What do you use it for?
It's also not either/or. mise can defer to uv for setting up all the python stuff.
The mise docs are pretty good these days, I think. Task dependencies are still a little rough, though.
In fact, if uv is installed (which can be done via mise), mise uses it for python related stuff.
I use mise to install as much as I can now - different language runtimes, tools/utilities etc. I even discovered yesterday that you can use the ubi backend to install directly from Git repos that aren't yet supported by the other registries.
Or mise use -g node@version (the -g installs it globally)
Same for anything else - deno, bun, golang tooling etc
Also, it's possible, if trickier, to install from arbitrary git repos.
Finally, if you type `mise use` with no package name, it'll present a filterable list. Start typing what you think you want, and it'll quickly filter to only things match.
Part of it is that `find` seems to come from before we standardized on `--foo --bar` having an equivalent in `-fb`, and the nagging about some flags being positional, but also just the general syntax of their `-exec`, which _requires_ the `{}` to be present, but it can only ever be in one position for the `+` variant.
My one nag about `fd` is that it has two optional positional arguments, so the way I use it I sometimes wind up with `fd -e $ext "" /path/to/search`. (`fd -e $ext --search-path /path/to/search` might be a clearer alternative I should habituate myself to.)
IME positional arguments are always less ergonomic than flags/options, _especially_ if there are more than one of them and they're not mandatory.
But they're still better than the `find` syntax.
It's pretty good in a "gets out of the way" kind of way, and the `abbr` feature is preferred over aliases but it has some annoying quirks like no dictionary type, the inability to "background blocks of code", requiring spawning a subshell + string which has other issues and in a way it feels sort of stuck between being new and friendly but still carrying over enough baggage to be annoying in other ways.
I tried Nushell a week or so ago and after getting my head around it (and $it and $in, haha) I think it feels like the future. It's not quite there ergonomically in some ways eg: reedline can't edit the current command line, but it's quite close.
What really sold me on it was when I
- Had a flat text file of "url\nartist\ntitle"
- Had to download these urls and insert the artist & title into a database
and I realised I could do it all with Nu primitives,
open new-landings.txt
| lines
| each { $in | str trim}
| chunks 3
| each {{title: $in.0, artist: $in.1, url: $in.2}}
| enumerate
| par-each {
http get ($in.item.url | str replace large medium) | save $"landing-($in.index + 101).jpg"
# also insert artist title into database
run-app ... etc
}
Now, could you do the same thing with zsh/bash/sh? Yep. Some kind of awk/sed thing with xargs I would guess. But I'd have to look it up. With Nu, after a few hours of playing with it I already "knew" how to do it. That felt really powerful.For reference, I also tried Murex and Elvish. Elvish doesn't support ctrl-z, so its disqualified right there though I like the syntax. Murex seemed fine but Nu was bigger with wider support. I think I saw Nu had pattern matching and it immediately got a big desirability bump.
I remember seeing another comment, wishing we had STDIN, STDOUT, STDERR and STDDATA, maybe one day.
<new-landings.txt parallel --pipe --recend '\n\n' --colsep '\n' --plus curl '{1/large/medium}' -o '{#} + 101'.jpg
Adding multiple commands should be in a script to avoid quoting issues.
<new-landings.txt parallel --delimiter '\n\n' --colsep '\n' --plus curl '{1/large/medium}' -o '{#} + 101'.jpg
I suspect most of the current coding assistants would have been able to do that for you in most languages, including something more portable and easier to maintain like Python.
These bugs have been open for years with no progress.
Nushell had interesting ideas but frankly it's a dead project now. Something with such a core of incorrect behavior just isn't workable, and nobody's even trying to fix it. Don't build castles in swampland.
https://github.com/nushell/nushell/issues/6617
Don't get me wrong, I really like bun. I run my blogs asset pipeline on bun, and run my home automation on it as well, but at the end of the day it's a js runtime, competing with the also excellent deno
Instead of having to adapt to a particular workflow, Jujutsu is flexible enough to support pretty much any workflow. Even when mostly using it like git, I have the freedom to jump around commits and branches (no stashing required), rebase easily (this may be simple for git power users, but I really appreciate being able to do this without VSCode's git tools) and just... get work done without extra headaches caused by VCS. And all that while writing my changes to an actual git repository just like everybody else's, that git tooling can interact with. When was the last time you used a different VCS than your coworkers without having them switch to it too?
While there are some rough edges regarding tags, submodules and LFS (I might be missing something else here), it's certainly worth a try.
> Unlike in Git, the remote to push to is not derived from the tracked remote bookmarks. Use `--remote` to select the remote Git repository by name. There is no option to push to multiple remotes.
I think this was an explicit design decision, which I'm guessing might be because bookmarks in other storage backends (non-git) may not have a notion of an upstream URL. I'm no expert on this, you'll probably get a better answer by asking the maintainers themselves.
Currently the best you can probably do is creating an alias to push a branch to a specific remote to save you some keystrokes. I hope that helps!
[1]: https://jj-vcs.github.io/jj/latest/cli-reference/#jj-git-pus...
Nine years ago.
After returning to vanilla git, I was missing the jj convenience within hours.
What does that imply?
But when I had to go back to git, I learned that all those little niceties really added up.
"Within hours, I found myself being exceedingly cautious about everything. I missed the confidence jj undo gave me. I missed the simplicity of jj new and jj describe."
it really does seem like we all gonna be using jj soon enough
I recall pijul.org that was another working prototype of better git
and I wonder how much overlap is there in the way they have made the improvements.
For example, will the committing of conflicts (a good idea I agree), mess up my existing git rerere?
Also I agree that the staged vs unstaged distinction is stupid and should be abolished, but I do like intentionally staging "the parts of the patch I like" while I work with git add -p. Is there a a lightweight way to have such a 2-patch-deep patch set with JJ that won't involve touching timestamps unnecessarily, causing extra rebuilds with stupid build systems?
This generalizes to using a whole stack of "stages", by doing ´squash --into´ to select the patch to put the changes into if it's not just the next one down.
Specifically, I believe the scenario you're talking about is:
change file1
build, producing binary.out
squash the change down (leaving your working copy unmodified)
rebuild
If the squash updates the timestamp on file1, then the rebuild will redo the compilation steps that use file1 as input.When I test it out, it looks like doing a whole-file squash with jj does not update the timestamp. Hm... I guess even a partial squash doesn't update the current contents, let me try that too... yes, again jj does not touch the file nor update its timestamp.
So it looks like it does do what you want, if I'm understanding things correctly.
2. You treat @ (the working copy) like the staging area, @- (the parent of the working copy) as the commit you’re working on, and then jj squash -i (“interactive”) the parts of the diff you want back into @.
Generally IDEs will be diffing against head or staging and it's not very configurable.
In rere’s case specifically, I’d expect you’d just be using jj’s rebase, so it shouldn’t be needed though of course want and need are different things.
https://lore.kernel.org/git/CAESOdVAspxUJKGAA58i0tvks4ZOfoGf... I hope this happens, because then it seems like far less state would be needed on the jj side.
It also doesn’t super change jj itself, in the sense that it still needs to support other backends, but it does simplify the git backend.
...
> I do like intentionally staging "the parts of the patch I like" while I work with git add -p
is a mysterious perspective to me. I guess with enough $(git worktree && git diff && vi && git apply) it'd be possible to achieve the staging behavior without formally staging anything but yikes
I just checked and it seems that mercurial 7.1 still doesn't believe in $(hg add -p) so presumably that 'worktree' silliness is the only way to interactively add work in their world
(The first thing about Jujutsu that was earth-shattering for me was learning that jj amend is an alias for jj squash. I swore aloud for several minutes when I first learned that.)
I guess that also places the burden upon the user to .. I dunno, go through that whole TUI dance again?, if one wishes to amend one more line in the file
In some sense, I do recognize it's like showing up to an emacs meeting and bitching about how it doesn't do things the vim way, but seriously, who came up with that mental model for committing only part of the working directory?
I strongly prefer JJ's approach of simply doing away with the concept of untracked files, though note that this is one of the features that is designed around developers having NVMe drives these days. It wouldn't have been possible to scan the working copy with every command back in 2004.
The right way has always been FUSE, so that version control knows about every change as it happens. Push, not pull (or poll).
With FUSE passthrough, maybe this won't even be slow!
I've spent so much of my professional career profiling source control access patterns. Hot cache tends to be OS VFS layer performance, but the moment you hit disk that dominates, unless the disk is NVMe (or, back in the day, PCIe flash storage). Further compounding this is the use of a naive LRU cache on some OSes, which means that once the cache size is exceeded, linear scans absolutely destroy performance.
> FUSE
So you might think that, but FUSE turns out to be very hard to do correctly and performantly. I was on the source control team at Facebook, and EdenFS took many years to become stable and performant enough. (It was solving a harder problem though, which was to fetch files lazily.)
I believe Microsoft tried using a FUSE equivalent for the Windows repo for a while, but gave up at some point.
Forgot the constant factors of FUSE, and imagine an in-kernel git implementation. If you have a Merkel CoW filesystem, then when (ignoring journals) you modify child files, you need to update parent directories on disk anyways, this is a great time to recompute VCS hashes too.
"git status" is, if the journal is flushed and hashes are up to date, always an O(1) operation.
What you're describing is reasonably similar to EdenFS, except EdenFS runs in userspace.
Watchman layers a consistent view of file metadata on top of inotify (etc), as well as providing stateless queries on top of EdenFS. It acts as a unified interface over regular filesystems as well as Eden that provides file lstat info and hashes over a Unix domain socket.
Back in the day, Watchman sped up status queries by over 5x for a repo with hundreds of thousands of files: https://engineering.fb.com/2014/01/07/core-infra/scaling-mer... I worked directly on this and co-wrote this blog post.
In truth, getting these two components working to the standard expected by developers was a very difficult systems problem with a ton of event ordering and cache invalidation concerns. (With EdenFS, in particular, I believe there was machine learning involved to detect prefetch patterns.) For smaller repos, it is much simpler to do linear scans. Since it is really fast on modern hardware anyway, it is also the right thing to do, following the maxim of doing the simplest thing that works.
"I know this thing is bad, and it shouldn't exist, but I'm also personally used to it right now, and it does have some perverse silver linings"
Are there specific advantages to using Jujutsu over Emacs Magit?
All other Git UIs I've used have been severely lacking, but Magit has made me significantly more productive with Git, and has convinced me of the "magic of git".
Is Jujutsu interested in competing with this experience? Or is it intended as an alternative to the (to be clear, extremely poor) git user experiences outside of Emacs?
It's a whole new VCS, that just so happens to be backwards compatible with git, and uses git as it's backend
Similar to how git brought us cheap branching over svn, JJ brings cheap rebasing. Conflicts are no longer stop the world operations, and you can rebase, rearrange, and manage commits like never before.
If you've used tools like stacked diffs before, JJ will feel right at home. Making stacked diff PRs is almost trivial in jj
Say I'm doing a dependency update, and someone else updated different dependencies, and our change sets merge. I want both sets of updated versions, but our lockfiles probably conflict, and I don't want to edit them by hand. I can just leave the conflicted lockfiles till the end of the rebase/merge, then purge them and regenerate them using tooling, and then split them up and apply them down to their respective conflicting changes.
Sure beats having to toss the lockfile, regen it, then let the rebase continue only to find you have to do it AGAIN
JJ tends to fit modern software engineering a bit better than git, I've found. Here's sort of an example of what I do when I'm working with it
I'll open an empty change on top of the main branch, not really sure about where this feature is going to go but knowing that it needs to be new changes. Usually this is already done for me automatically because my main synced with the remote, and therefore is an immutable commit. As I'm writing code I'm not really concerned with how I'm going to commit it to the graph, it's just a big pile of things all changing at the same time. Maybe I do features that are unrelated, or only tangentially related, to the actual story I'm working on.
If I get interrupted, I might go over to JJ and describe my changes, typically something along the lines of work in progress with a list of what still needs to be done and what I've been trying to do, basically a capture of my state of mind at the time. I'll then create a new change on top of that, for when I can come back later.
Once I'm finished with all of the story and it's working to my satisfaction, I probably have a big ball of changes that need to be split up and organized properly. First thing I do is look at what changes are necessary for this story, which are dependence for it, and which are just things I did because it was convenient to do them at the time. I'll split the dependencies out first putting them into their own changes. Then I'll make the actual story related changes, and then I'll make the ones that are just one offs. With a little bit of rebasing, I now will typically have three or four different bookmarks, which is the JJ analog of branches, some in parallel and some dependent on each other, that I can push up to my remote repository and open up pull requests.
I made a couple shell scripts to handle some common things, like recursively splitting a change into parts, but you don't really need them, as they're just wrappers around built in JJ stuff or adaptations to some of my workflows. I touch on them in a blog post that's mostly about JJ: https://pdx.su/blog/2025-08-13-the-quiet-software-tooling-re...
They'll ask me to merge a dozen PRs into a test branch or whatever and it's a piece of cake in jj
I think Magit is an interesting parallel to jj. You say the "magic of git", but for both of them I think most of the "magic" has less to do with Git and more to do with the "design language" exposed to users, and by that I mean the tools that allow you to manipulate, navigate, and arrange commits (and diffs!) Git is more like a physical storage layer for both Magit and jj, but beyond that a lot of the special sauce is unique to their algorithms, their UX, and their "nouns and verbs".
In my experience, Magit users are generally harder to sell because for them jj isn't so revolutionary; die-hard Git powerusers who have been exclusively slugging it out on the command line to perform 10-stack rebases for the last 5+ years tend to be very easy to sell, in comparison. But all these users tend to understand and appreciate the power of a well-designed set of tools for commit graph manipulation. Git powerusers tend to be some of our most hardcore converts and advocates, really.
Full disclosure but I'm one of the jj maintainers and my opinion is that it's pretty good. Maybe try it out for a few days (perhaps without using Magit, if you can :)
I'm having a hard time wrapping my mind around what specific details would cause me to choose Jujutsu over Git, in particular because of Git's industry standard status.
I think this is a very interesting concept, but I think it could go farther with some more targeted marketing along these lines. Of course, if Git power users are not Jujutsu's intended audience, then this comment may be irrelvant.
I think one of Git's great weaknesses is its unfriendliness to newcomers (jargon, deep features, lack of discoverability, lack of accessible GUI frontends), so there's probably a lot of potential for a VC solution that is easier for a newcomer to jump into.
Jujutsu is Git-compatible, so there's nothing to lose. It can literally create the `.git` directory next to the `.jj` directory to fool all your existing tools into thinking this is a git repository.
There are a few limitations... Jujutsu currently ignores submodules, for example. So you have to run `git submodule update` sometimes. And when you yourself update the submodule, you need to `git commit` instead of `jj commit`.
Git LFS is also not supported. Apart from that, it's smooth sailing AFAIK.
> I think this is a very interesting concept, but I think it could go farther with some more targeted marketing along these lines. Of course, if Git power users are not Jujutsu's intended audience, then this comment may be irrelvant.
Git power users are definitely part of the target audience, most Jujutsu users today are retired Git power users. Because that's not the target audience of my tutorial though, I didn't write much about that. Some of the features jj users are most excited about include: - Conflicts are non-blocking. Merge and rebase always succeed, conflicts are recorded in the commit itself. You can work on something else and come back later to solve them. - There is `jj undo` and `jj redo` which work like Ctrl[+Shift]+Z in GUI apps and text editors. They affect the whole repository, because that has basically its own linear history. Reflog on steroids, basically. - `jj absorb` can find the most recent commit that touched the same lines and squash your changes into it. It's magical if you're working on several things in parallel (by merging the separate branches together, just for development.)
These are just some examples that come up the most in the "appreciation" channel on the Jujutsu discord.
> I think one of Git's great weaknesses is its unfriendliness to newcomers (jargon, deep features, lack of discoverability, lack of accessible GUI frontends), so there's probably a lot of potential for a VC solution that is easier for a newcomer to jump into.
Yes! I think Jujutsu has a lot of potential there as well. But there's a lack of learning material for that target audience... hence why I wrote this tutorial :-)
> Are there "central concepts" in the Jujutsu design?
I think a central concept in jj is that everything is a commit: there’s no staging index, stashes, or unstaged files, only commits. This has a few pretty neat implications: - changes are automatically to recorded to the current commit. - commits are very cheap to create. - as others have already said, rebases (and history manipulation in general!) are way cheaper than in git. It helps that jj as a whole is oriented around manipulating and rewriting commits. Stacked diffs fall out of this model for free. - Since everything is now a commit, merge/rebase conflicts aren’t anything special anymore because a commit can simply be marked as having “a conflict” and you, as the user, can simply resolve the conflict at your own leisure. While I’m sure you know already know thus, I’d rather make subtext text: in git and mercurial, you’d need to resolve the conflict as it got materialized or abort the rebase entirely. - because everything is a commit, jj has a universal undo button that falls out effectively, for free: `jj undo`. It feels so incredibly powerful to have a safety net!
> All other Git UIs I've used have been severely lacking, but Magit has made me significantly more productive with Git, and has convinced me of the "magic of git".
I can’t speak to this personally, but I’ve had friends who are magit users tell me that jj isn’t a big enough improvement (on a day-to-day, not conceptual, basis) over magit to switch at this time. It’d be really cool to have a magit-but-for-jj, but I don’t think anyone has written one yet.
Overall a positive experience, but if Magit is working for you, there's no killer feature that makes it worthwhile.
I'm also using https://github.com/bolivier/jj-mode.el which lets me do enough of what I need with jj from within Emacs.
A great "Megamerge" workflow
https://v5.chriskrycho.com/journal/jujutsu-megamerges-and-jj...
https://ofcr.se/jujutsu-merge-workflow
And an absolutely fantastic TUI that wraps jj cli. Might be the best TUI ive ever used, and consistently getting better.
What’s the best way to represent a stack? How do you model jj’s editing a commit at the bottom where everything above implicitly rebases when pull requests are open?
One of the most intimating things is fumbling around with your vcs when an expert is reviewing your code.
if yes:
while jj does make stacked PRs quite easy, I think this tutorial is mostly aimed at people who are still getting their feet wet and not making stacks of PRs. and if the expert is intimidating, tell them to be helpful or fuck off
if no:
well I don't understand your question, but probably don't `jj edit` the commit at the bottom of a stack; `jj new` and, when you're ready, either `jj squash` it down or `jj rebase -s @ -A @-` your change into the stack instead
And it’s better to let each commit merge down one by one in the stack versus squashing so people can follow your development process via that stack after the fact.
Comment about expert was made off hand. If common flows break with GitHub, people are going to be hesitant to use a new tool, hurting adoption. Again, given this is a for beginner tutorial. But you make a good point about not being too sensitive ;)
I'm working in a monorepo on GitHub, where PRs get squash-merged & `main` is a protected branch, and a bot automatically tags every team for review if a commit in a PR branch has changed any of their files (anything they're in an OWNERS file for), then how do I work with `jj` so as not to tag the entire company if I develop a long-running feature branch alongside several other devs on my team? We need to periodically merge in changes from `main` as the feature is developed, but don't want to merge the feature until it's ready for release. With `git`, any use of `rebase` to get changes from `main` into the feature branch causes the bot to tag every team that merged anything to `main` between the creation of the feature branch & the use of the `rebase`. I'm not on the team that controls the bot, and I don't care to try to get them to change it.
I happily use `jj` for anything I can get away with it on, but `git rebase` + monorepo + PR-centric workflow = pain, and `jj` seems like it could cause the same sort of issues. I should just try with a dummy branch & see, but that takes time & I've been busy.
I'll need to play with this. I don't know that I'll stick with it but I'd be happy enough to be wrong.