Most active commenters
  • BeetleB(4)
  • adastra22(4)
  • baq(3)
  • steveklabnik(3)

←back to thread

Jujutsu for everyone

(jj-for-everyone.github.io)
434 points Bogdanp | 19 comments | | HN request time: 0.001s | source | bottom
Show context
marcuskaz ◴[] No.45084298[source]
> Jujutsu is more powerful than Git. Despite the fact that it's easier to learn and more intuitive, it actually has loads of awesome capabilities for power users that completely leave Git in the dust.

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.

replies(7): >>45084316 #>>45084327 #>>45084439 #>>45084678 #>>45088571 #>>45092597 #>>45093098 #
pkulak ◴[] No.45084678[source]
Say you start on Main, then make a new branch that you intend to be a PR someday. You make commit 1. Then another. Maybe 6 more. Now you realize that something in commit 1 should have been done differently. So, you "edit" commit 1. All the other commits automatically rebase on top and when you go back to your last commit, it's there. Same with _after_ you PR and someone notices something in commit 3. Edit it, push, and it's fixed.

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.

replies(4): >>45084727 #>>45084733 #>>45084935 #>>45085106 #
adastra22 ◴[] No.45084733[source]
I do this every day in git. “git rebase -i [hash]” fyi.
replies(1): >>45084782 #
baq ◴[] No.45084782[source]
you think you do, but you don't; jj edit is much, much better than an edit step in a rebase - it essentially keeps rebasing while you're editing, so you can always see which changes get conflicts, then you are free to resolve them, or not, at your convenience.
replies(3): >>45084799 #>>45086057 #>>45086723 #
1. 8n4vidtmkvmk ◴[] No.45086057[source]
Jj edit isn't even the jj way of doing things. Should be jj new. Unless you have changes stacked after then you'd do jj new -A. And squish when you're done
replies(1): >>45086389 #
2. BeetleB ◴[] No.45086389[source]
As a relatively new jj user, I'm curious. Why is the jj new -A + squash better than just a jj edit?
replies(3): >>45086416 #>>45087841 #>>45089421 #
3. baq ◴[] No.45086416[source]
Separation of concerns and performance; when you edit, the commit in the middle of the branch is your working copy and you’ll update the whole branch unnecessarily many times vs just once when you’re on a logical checkpoint.
replies(1): >>45088090 #
4. hdjrudni ◴[] No.45087841[source]
I forget where I read it (maybe from Martin), but one reason I like to `jj new` before I start any work is just because it makes it easy to revert or abandon if I don't end up liking it. And also easy to diff. `jj new` is pretty much 'free' anyway, every time you make an edit you're creating new commits (not changes!) anyway.
replies(1): >>45088700 #
5. adastra22 ◴[] No.45088090{3}[source]
Not everyone works by “logical checkpoint” commit strategy. I myself want my tree clean and commits being atomic state transitions. Otherwise git bisect (which I rely on) would break.

A lot of the jj strategies in this thread are a bit more cowboy, and I’m surprised.

replies(1): >>45089163 #
6. BeetleB ◴[] No.45088700{3}[source]
Oh I'm good with jj new for any new thing I'm working on. But I was wondering why one would use jj new -A to fix a commit in the past vs jj edit,
replies(1): >>45088815 #
7. kps ◴[] No.45088815{4}[source]
Mostly to keep the new changes isolated from the original commit until they're ready, I think. The only time I use `jj edit` is to resume a leaf I left in the middle of something (where I might have used `git stash pop` but without needing to stash).
8. steveklabnik ◴[] No.45089163{4}[source]
The key is to be cowboy until you’re happy with things, and then get clean, just like with git. It’s way easier to slice and dice commits with jj and so you can be sloppy at first and it’s easy to turn beautiful afterward.
replies(1): >>45089939 #
9. sfink ◴[] No.45089421[source]
My personal opinion: `jj new` is better because it's non-modal. If you use `jj edit`, you're sort of switching to "edit mode": any change you make will trigger a rebase of all descendants.[1] You're live-mutating the core graph structure rather than a harmless appendage node. Also, if you notice something else that needs to be fixed, you can do it but then you'll need to remember to split it out into a separate commit before leaving edit mode.

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."

replies(1): >>45089922 #
10. BeetleB ◴[] No.45089922{3}[source]
Indeed, that was my point. jj new -A would also trigger rebases.
replies(1): >>45107189 #
11. adastra22 ◴[] No.45089939{5}[source]
That’s not how I use git, at all. I have a messy workspace with a lot of things going on simultaneously, and only selectively stage when something crosses the finish line. Sounds very difficult to do this in jj.
replies(3): >>45090278 #>>45091465 #>>45095204 #
12. steveklabnik ◴[] No.45090278{6}[source]
Selectively staging is easier in jj. I loved git’s index, jj does it better.
replies(1): >>45094652 #
13. sgjennings ◴[] No.45091465{6}[source]
You can work exactly this way using the “gitpatch” diff editor[1].

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...

14. adastra22 ◴[] No.45094652{7}[source]
How? The jj documentation seems to be pretty clear about getting rid of the staging area.
replies(3): >>45094935 #>>45095953 #>>45096638 #
15. baq ◴[] No.45094935{8}[source]
staging isn't needed since everything including the working copy is a commit you can split, rebase, etc. without any checkin step. yes, it does make sense and yes, it works in practice - the uncommitted vs staged vs committed states are very git-specific and don't need to be special in any way. if you check in the wrong thing (note - your working copy is a commit, so by definition you can't not have wrong things checked in at times) you split it (stage and/or commit in git).
16. BeetleB ◴[] No.45095204{6}[source]
> Sounds very difficult to do this in jj.

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.

17. Human-Cabbage ◴[] No.45095953{8}[source]
There are a couple main ways to achieve a workflow similar to Git's staging area.

#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: Splitting

Create 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
  ~
18. steveklabnik ◴[] No.45096638{8}[source]
Because it’s not a distinct feature, it’s just another commit. Which means you can bring all of the usual tools for modifying commits to bear. My sibling has some details, but at the high level, that’s the idea.
19. sfink ◴[] No.45107189{4}[source]
Heh, sorry. The `-A` part went whooshing over my head until I had already written up the rest. I just saw `jj new` and got triggered into a "well akshually..."