Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I gave jj two honest tries. While first class conflicts is a cool idea, in practice I deal with staging/committing 30x more than conflict resolution, and coming from magit, using jj’s hunk split & select felt like being thrown into stone age. Plus I rebase a lot and get a lot of jj’s benefits from magit’s various rebase shortcuts already, IIRC first class conflicts was the only truly novel thing I didn’t have. For people like me who stage/commit often and judiciously I don’t think jj will beat magit until its hunk selection UX comes close.




In jj you aren’t supposed to be even thinking about staging and committing, that’s the mental leap required to get what the fuss is about. Everything is a change and you bookmark a parent (or something further out) as the branch head into which you squash or advance the bookmark to a next ready change.

> For people like me who stage/commit often and judiciously

Focus on judiciously: mostly (functionally) atomic commits that are not every tiny change, not largely meaningless time-based snapshots, not Gerrit-style single commits for entire features, etc. I’m well aware of the mental leap you’re talking about, it’s neither hard to understand nor the liberation you might think it is. To achieve what we want to achieve we either need to be able to split, or “commit” with such atomicity that more time is wasted come squash time.* If you don’t get that, totally fine, but then you just don’t belong very much to this very conversation.

* Or we can completely change how we write code…


I see your point, but this feels like a difference in workflow philosophy, especially with AI-assisted coding becoming more common.

With jj, your working copy is always a commit. This encourages a "commit often and messy, clean up later" approach. You can rapidly iterate with or without AI, letting jj automatically save every change without stopping to craft perfect atomic commits.

Later, you use a simple jj squash to combine all those small, iterative changes into logical, clean commits before you share it. The atomicity is created retroactively, not upfront. For a finely-tuned magit workflow this might feel wrong, but for rapid, exploratory and AI-driven iteration, it's a more natural fit.


With AI there’s even more reason to have ergonomic splitting if you care about quality commits, since unless you always write the perfect focused prompt, AI can over-generate for the scope of a single commit, so sooner or later you need to split that commit during the cleanup process you mentioned.

I’m using jj for almost a year now and I definitely feel much happier whenever I need to rebase a branch or do an octopus merge. It really is liberating in an incremental way that creeps on you in day to day work. Don’t expect a revolution, though.

Small commits are the same small commits in jj as in git, you just split instead of add -p.


I mean, if you think git add -p is fine then you’re clearly not in the market of magit. And yes, jj split is about as ergonomic as git add -p, IIRC.

What does the magit interface for staging look like? I only used GUIs were you select the lines with the cursor or mouse and stage that. Is it like that or more sophisticated? Does it support editing hunks better, because that's a thing I only do on the commandline, because my Git GUI doesn't support that.

In magit, the diff is splitted into sections: Untracked, Unstaged and Stage. The sublevels are files then hunks. You can stage and unstage hunks, files and whole section by pressing s and u. Pressing k discards the node (section, file, or hunk.

You can also stage or unstage lines by selecting them first and press the relevan keys.

If you press ‘return’ on a hunk, it brings you directly the the line to edit.


jj wouldn't have any issues with supporting this workflow, if you could replace magit's git commands with some jj ones it'd probably just work. IOW the only thing that's missing is elbow grease.

actually, with jj's seamless rebases, it'd work exactly as you'd want it to (but maybe not how you'd expect if you're used to the git way) when aimed at a commit in the middle of a branch: pick lines which stay below, above or in the middle commit, which you can edit transparently.


The thing is that magit have a lot of commands that makes rebasing a breeze. Pressing c, then s, will allow you to select interactively (from the commits log) a commit to target when doing autosquash. That will create a new commit with the relevant message. Pressing c, the S (shift+s), will rebase immediately, squashing the current staging area into the selected commit.

Interactive rebasing is a breeze. Editing commits is very fast (reordering, dropping rewording, splitting,…). Same with dealing with merge conflicts (Emacs have various merge packages builtin).

The same speed Vim brings to editing, that’s nearly the same speed you get with magit for git.


It sounds like it’d would work even better with jj is all I’m saying. A rebase UI doesn’t mean a git rebase UI.

I don't mean that, I mean any tool that allows you to selectively stage can use the exact same interface to selectively split.

> Everything is a change and you bookmark a parent (or something further out) as the branch head into which you squash or advance the bookmark to a next ready change.

Does not sound any easier or more intuitive than git.


The only intuitive user interface is the nipple.

It isn’t how git porcelain wants you to work, but it does make sense once you stop thinking in git. Working copy being a first class commit unlocks all the dag manipulation tools without stashing, having to resolve conflicts in middle of tricky operations, etc. ‘Easier’ is exactly the point, but it’s about workflows which are very inconvenient in git like rebasing stacked PRs.


I’m curious what the magit hunk selection UX consists in. I couldn’t find any videos showing something substantially different from the one built into jj — the videos I found were meant as beginner intros.

I’ve never used magit but I used GitUp (https://gitup.co/) for years before jj. I don’t find the jj one super natural, but I feel like that’s mostly a matter of keyboard shortcuts — I need to see if they can be customized.


There's more than one way to do it, but the very normal UX is that you can just scroll through the diff file-by-file and stage/stash/drop each hunk individually by placing your cursor over it and issuing the appropriate command. You can do the same with files, staging/stashing/dropping changes to a file by placing the cursor on its name and issuing a command.

Crucially you can also select a region within a chunk and perform those commands, so it’s easy to untangle changes.

And you can even edit the content you stage, so that you can stage something different, than what is in the working tree. Having different content in the index vs. working tree is the feature of the index, which I think JJ just doesn't support?

jj doesn’t support it in the sense that there’s no special index feature. You can use a workflow where you have a commit represent the index (and that’s basically the most common workflow). This means you don’t need a separate feature, you just use the tools that slice and dice any commit with your “index”.

It isn’t that it doesn’t support it, it kinda does, but the recommended workflow is such that the index is simply pointless.

As an stgit user, this seems like a weird workflow to me. I never want to have that many uncommitted changes just floating around that will eventually belong to multiple commits. If I'm halfway through something and realise "oh, it would be good to do xyz first" I don't want to have xyz's changes and my half-way-through changes all mixed up -- I want to pop the half-way-through stuff, do the xyz stuff and commit it, then re-push the half-way-through stuff to keep working. If I'm looking at a diff and picking out parts of it then I've done something wrong -- I have a tool for doing it but I only need to use it every couple of months...

I end up with "neatly separated does-one-thing commits" but I get there by building them up as I go along, not by writing a ton of code and then trying to split it up afterwards.


This sort of flow is very nice in jj, primarily because of the “no index” plus “auto commit” behavior, I’ll regularly go “oh yeah I want to go do that” and I’m about to just go do it and then come back to right where I left off, since my work is already saved.

Yeah, I get the impression jj is good for this, and if I were using raw git then it would be a massive upgrade. Luckily for me stgit already does what I want in this area so I have no strong need to investigate alternatives, but if stgit ever bitrots then jj might be a useful next thing.

StGit maintainer here. I have been a jj user for over a year now. It has proven superior to StGit for all of my workloads. I even use jj when maintaining StGit.

An incomplete list of wins vs StGit includes:

- jj makes managing multiple branches fluid, whereas stg has limited tools for managing patches between stacks. 'stg pick' is largely all there is. It's a real dance to move a patch between stacks.

- jj has a much better system for naming changes. I'm super jealous of how jj solved this problem. StGit requires you to name the patches. I added the feature that allows StGit to refer to patches by relative or absolute index in addition to by name. jj's immutable change ids that can be referenced by unambiguous prefix is the correct answer to this problem.

- 'jj rebase' is so vastly superior to stg push/pop/sink/float for reordering changes that I don't even know where to start. It wasn't immediately obvious to me just how flexible, simple, and powerful 'jj rebase' is when I first started using jj, I have learned that it is in its own league relative to StGit's stack ordering story.

- Similarly 'jj squash' makes 'stg squash' look amateurish.

I could go on. If you're a StGit user, you owe it to yourself to give jj a proper try.


On that first point, there's a use case I sometimes have where stgit feels very clunky:

* I have a branch foo with a stack of patches which are the thing I'm working on, based on a master branch

* I have a separate stack of patches on a branch bar (let's say this is a feature that interacts with foo but it's not ready to upstream yet or maybe at all)

* I want to test and work on the combination of foo and bar and make changes that might need to be updates to some patch in foo, or to some patch in bar

At the moment I pick all the patches in foo onto bar in order to do the work and updates in this combined branch, squashing fixes and changes into appropriate patches. Then once I'm happy I go back to the foo branch, blow away the old patches and re-pick them off my combined branch.

This works but feels super clunky -- does jj do better here? That would be a real incentive to try it out.

For the rest, they don't sound like they're strong enough to beat "I've used stgit for 10 years and have a solid workflow with it".

And I just scanned the jj rebase docs and it looks awful, everything I moved to stgit to get away from. I do not want to think about managing a patch stack in terms of "move a bunch of revisions to a different parent". I like having a straightforward stack of patches that I can push, pop and reorder and list. Don't make me think about graphs of commits and multiple rebase suboptions and all that for something that I do all the time in my main workflow, please.


jj does do better here: its called the "mega merge" https://steveklabnik.github.io/jujutsu-tutorial/advanced/sim...

Combined with jj absorb, some people just work this way all the time, even.

> I like having a straightforward stack of patches that I can push, pop and reorder and list.

You can work this way too, what you'd want is `jj rebase` with -A, -B, and -r: r for the revision, and A and B for the before and after you want to move it too. This lets you reorder things however you want pretty straightforwardly. I tend to work in a stack of patches too, or at least, way more than I used to.


What I mean is that I do not want a single "swiss army knife" rebase command that does everything with lots of options to remember. It's fine to have that in the toolbox for the once in six months weird requirement. But for the simple cases I do every day I want simple commands that each do one thing and have memorable names.

If I'm understanding you correctly, you can have both. If there are specific rebase types that you perform regularly, you can create aliases for them and give them whatever name is meaningful to you.

For example, I frequently use `jj up` to rebase the current branch on main. Likewise, `jj pop` rebases just the current commit (popping it from its current place). I even have a `jj ppop` - better name suggestions are welcome - which does this but for the parent commit.

I suspect that the once-off effort to write your own commands would take no longer than it would take to read the documentation if the commands already existed, but with the hopeful extra benefit of giving you a better understanding of how to use rebase for those once in six months weird requirements when they may arise.

But to be clear, I'm not suggesting you must or even should put in this effort if you have something that works for you. My reply is mostly so that anyone who comes across this discussion and sees Steve's mention of -A, -B, etc isn't scared off by them. Whilst they're always there for you, you can use the power it gives you but in the form of single function commands that don't require you to think.

---

For anyone wondering, the aliases I mentioned. These can be dropped in your jj config with `jj config edit --user`.

  [aliases]
  up = ["rebase", "--skip-emptied", "-d", "trunk()"]`
  pop = ["rebase", "-r", "@", "-d", "trunk()"]
  ppop = ["rebase", "-r", "@-", "-d", "trunk()"]

Most of the time in jj you don’t even rebase manually, because it’s automatic. And the vast majority of the time, I’m using one or two flags to rebase if I am calling it. You might even need only two in this case (before only might be fine?) I just use both before and after because it’s so easy to remember the names.

Anyway you should use the tools you like, it’s all good.


Seriously! I found that you can still use the git index as long as you don’t run any jj command that changes git_head, and then I made an alias to make a commit from staged changes (squash-index and split-index): https://github.com/CGamesPlay/dotfiles/blob/2484f6f7d0ab302e...

If you understand why having a great UX on top of bare git is valuable, you have understood 95% of what's to understand about jj.

But there are dozens of great UXs on top of Git already, so if that's 95% of what jj is about then you seem to be making an argument for jj adding very little value. My understanding is that jj offers more than just UX enhancements.

Interesting, isn't it? There you are, and yet none of those dozens are garnering anywhere near the same amount of attention and enthusiasm as jj. And yet, it's genuinely mostly just that -- a novel CLI on top of the same storage backend as git. Sometimes it doesn't take that much to bring about a paradigm shift, I suppose. Just a few new ideas with a great implementation, and there you are -- they've just got to be the right new ideas. That being the hard part.

> Plus I rebase a lot

Exactly, me too. Things like `absorb` I'll take, but I don't want jj's opinionated approach to version control. And not only do I not want it for _me_, but I also don't want it for newbies because hiding too much of the underlying design, design issues, etc., seems counterproductive to me.


This does not compute, like at all. jj rebase is half the reason to use it at all. Everything git does with the commit dag, jj does the same at worst, sometimes better.

It actually sounds like you're confusing jj with something else.


I’m curious to hear more about what you find opinionated, I think one struggle I’ve had teaching jj to people is that it’s extremely flexible.

I'll have to look again. Last I looked at it I felt like jj was a straightjacket.

It's all good. If you do give it a try and feel like letting me know how it goes, I'm genuinely interested. No worries if not :)

If you open your favorite git editor in a jj repo, everything you stage will become a new commit, everything you revert will… revert. I still use Sublime Merge rather frequently.

Oh wow, they were going for "magic" with that name?? I read it as "maggot".



Consider applying for YC's Winter 2026 batch! Applications are open till Nov 10

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: