Remix.run Logo
The JJ VCS workshop: A zero-to-hero speedrun(github.com)
177 points by todsacerdoti 4 days ago | 41 comments
Jenk 3 days ago | parent | next [-]

The one paragraph that helped jj begin to click for me was this one from Steve's Jujutsu Tutorial[0]:

> If you want to replicate git's behavior, typing an additional command after each change is done feels like overkill. But I would argue this is not the right way to use jj; as you'll see, we'll be either re-writing commits at the tip of branches, or doing multiple steps of work before updating where a branch points. In practice, it means I check the branch status before pushing code, rather than as I work. That is, the branch name tends to sit at the same change as the remote server, and when it's time to update the remote, that's when I update things locally.

[0]: https://steveklabnik.github.io/jujutsu-tutorial/sharing-code...

(emphasis mine)

That, as I interpreted it, is to mean one should begin to "forget" git in order to appreciate jj with a sense of new beginning.

Previously, I was continuously translating jj to git, and vice-versa. After reading this paragraph, I now mentally model the jj-git relationship as a kind of 3rd stage of the workflow, rather than previously assuming jj as an abstraction on top of git.

steveklabnik 3 days ago | parent [-]

I'm glad that helped you!

Darmani 2 days ago | parent [-]

I like it too and just added it to the slides, as an interlude after Exercise 5.

steveklabnik 2 days ago | parent [-]

Cool cool, thanks for making good jj content :) We need more of it!

oakashes 3 days ago | parent | prev | next [-]

Something I ran into during exercise 2 (and from watching the solution video[0] closely I see the author did as well):

There were four consecutive changed lines in my original commit. When I split it, I selected the deletion and addition for the first two lines. In the resulting newly created commit, those two lines had been moved to below the other two lines, so the order was now 3 -> 4 -> 1 -> 2, with the second commit moving 3 and 4 back to their original places. I didn't figure out a clean way to fix this - when I edited the commit which changed lines 1 and 2 to put them back at the top, it made a conflict with the second commit which I had to repair.

Anybody know what I should have done differently to split the commit and keep the edited lines in their original places?

[0]: https://www.loom.com/share/e3e148f07fb9420180ebb047f5ca94b3

kinghajj 2 days ago | parent | next [-]

It seems to work correctly if you include the last two lines in the first commit.

    jj-workshop on  HEAD (2cab0f1) [!]
    > jj
    @  unwquwxk samfredrickson@gmail.com 2025-07-29 18:47:18 f7db7c8a
    │  Part 2 first
    ○  wskswvnt samfredrickson@gmail.com 2025-07-29 18:47:13 git_head() 2cab0f16
    │  Part 2 second
    ◆  pnxpmvpz jkoppel@users.noreply.github.com 2025-07-27 21:54:50 main 4d04bcef
    │  Update README.md with link to JJ homepage
    ~
    
    jj-workshop on  HEAD (2cab0f1) [!]
    > jj show w --git
    Commit ID: 2cab0f167c64cea1d23407e12196fa6d1b8aab25
    Change ID: wskswvntlvtwzqutoowyqltsouklzmqr
    Author   : Sam Fredrickson <samfredrickson@gmail.com> (2025-07-29 18:34:52)
    Committer: Sam Fredrickson <samfredrickson@gmail.com> (2025-07-29 18:47:13)
    
        Part 2 second
    
    diff --git a/part1/foo.txt b/part1/foo.txt
    index 067f76475a..56c99c9cc3 100644
    --- a/part1/foo.txt
    +++ b/part1/foo.txt
    @@ -6,5 +6,5 @@
    
     * The best operating system is ______
     * The best text editor is ______
    -* The best kind of phone is _______
    -* The best superhero is ___________
    +* The best kind of phone is nothing
    +* The best superhero is nobody
    
    jj-workshop on  HEAD (2cab0f1) [!]
    > jj show u --git
    Commit ID: f7db7c8a5f37df8c29e08a6697ca6cd59c2313c4
    Change ID: unwquwxklkprlqoksmxzvoumspnmnknk
    Author   : Sam Fredrickson <samfredrickson@gmail.com> (2025-07-29 18:34:52)
    Committer: Sam Fredrickson <samfredrickson@gmail.com> (2025-07-29 18:47:18)
    
        Part 2 first
    
    diff --git a/part1/foo.txt b/part1/foo.txt
    index 56c99c9cc3..f48ae9b3f9 100644
    --- a/part1/foo.txt
    +++ b/part1/foo.txt
    @@ -4,7 +4,7 @@
    
     Maybe fill this out?
    
    -* The best operating system is ______
    -* The best text editor is ______
    +* The best operating system is Linux
    +* The best text editor is neovim
     * The best kind of phone is nothing
     * The best superhero is nobody
2 days ago | parent | prev | next [-]
[deleted]
Darmani 3 days ago | parent | prev [-]

Wow, good catch! I had not noticed. I don't know if there is a fix for this. It seems to be a fundamental issue of the delete-and-add way of representing change hunks.

palata 2 days ago | parent [-]

Is there a way to do something similar to `git add -p`? I am used to editing the patch manually, but jj forces me to use their TUI and I get into the same issue.

sastraxi 4 days ago | parent | prev | next [-]

Looks good! I might have missed it but it would be helpful to have a link to the jujutsu home page in your README.md.

Darmani 4 days ago | parent [-]

Creator here. Done.

sn9 4 days ago | parent | prev | next [-]

My understanding of jj is that's it's meant to be compatible with git.

Does that mean that working through this will also improve your git usage? Or could be used to teach someone git workflows?

baq 4 days ago | parent | next [-]

Haven’t reviewed the material, but compatible is a weak word - jj is also compatible with a plain filesystem. The point is that it makes some workflows easy and accessible which are hard and cumbersome in git, so they become the new normal instead of things to avoid.

emporas 3 days ago | parent | prev | next [-]

Every jj action is translated to a git action, but the git protocol is used only as a low level filesystem underneath, completely abstracted away. Akin to how C abstracts assembly as far I understand. No need to know assembly to use C.

nchmy 4 days ago | parent | prev [-]

You'd only really learn anything if you know nothing. The underlying git is largely invisible, and the workflows are vastly different - for the better.

It is constantly making new git commits any time you use a jj command. (I even have it set up to use watchman to create commits any time any file changes - this is unimportant for this discussion). But the commits are completely hidden - we instead interact with jj "changes", which are more like a constantly evolving, auto-adding, auto-committing, stash-free, movable git commit.

Git branches are even a 2nd class citizen. We can create endless "branches" by adding new changes on top of any existing change, and then merge that with an arbitrary amount of other changes by specifying them when creating a new change. We can also rebase/move changes around all over the place, and the changes get automatically propagated through any descendents.

We can also split and squash commits very easily, and even do so on a granular level - easily and interactively select lines or sections of changed code within one change to be split or squashed into a different change. This allows you to just work, and then tidy the history up later when you have a clue.

Conflicts that arise from merge/rebase/split/squash/etc do not require immediate intervention - fix them when you want to, and the fix will also propagate through. You can use jj resolve to open the conflicts in your merge conflict editor of choice (I use vs code).

We use git branches when we apply/update a jj bookmark to any change. This essentially creates/changes a git branch, which can then be pushed to Github. When bookmarks are pushed, the ancestor changes become "immutable" by default, which prevents things like editing, rebasing etc, so as to facilitate collaboration. But it can be override, and I presume it does things like a force push for you.

One thing to note is that your jj "branches" etc are not shared as it just stays local. You could, in theory, sync your .jj folder remotely, but I don't see that being particularly feasible unless it's just to sync with another device for your own use (or back it up in case of data loss). It's really just a local interface for individual use, and collaboration still happens via git branches and pull requests. If you were to lose your jj folder, you could clone the git repo again and you'd just start with the existing git branches etc.

I'll mention one particularly powerful/useful jj workflow - the megamerge. There's a few detailed articles out there if you search for it, and they've been discussed on hn as well (I'm on phone so can't be bothered). It allows you to merge numerous feature "branches" (again, jj branches, which are optionally also git branches if you use bookmarks) into a new branch, which lets you easily work on top of the combined functionality of various branches that are in progress. You can then interactive squash changes into their respective feature branches prior to the empty megamerge commit/change. There's also jj absorb, which does this interactive squash in a sort of magical, automatic way (though I find that sometimes it just doesn't do anything)

There's so much more to say about it, but overall you just don't need to think about git at all and instead just think about your code, and then tidy up the history etc as-needed, without much cognitive overhead or friction.

This is all made even easier by the absolutely fantastic https://github.com/idursun/jjui TUI. It's largely "just" a wrapper with shortcuts over jj commands and their resulting output. But it makes what's already simple absolutely seamless.

I hope this helps. Jj and jjui are absolutely beautiful. Ignore all the luddites who try to say "if it's just git under the hood, then I'll just keep using that myself, thank you very much".

I never intend to "raw dog" (as the kids say) git again. I do use some git mechanisms in vscode though - merge conflict resolution, diff visualization (it shows whatever has changed in the current jj change)

horse2 3 days ago | parent | next [-]

I would rather recommend ignoring people who automatically follow every latest fad without much thought, especially to those who dare call themselves “engineers”. Learn both and make an informed decision. The new shiny thing is not better just because it is new and shiny, especially when it's just an abstraction over the old thing.

Not too long ago people who refused to be dragged into the blockchain circus were called luddites by some. Now no-one even remembers that, the industry has moved on to another buzzword du jour. Not every change for the sake of change is “progress”, there are more dead-ends that lead nowhere than things that stick with us for a long time.

Also: old men make the world go brrr, it's not an insult as you seem to think it is. I wonder what the Linux kernel folk would say to your insinuations, if they had time to waste here. Or the OpenBSD guys who are much more conservative still.

nchmy 3 days ago | parent [-]

And yet, here you are, not ignoring people who are apparently fad-followers!

jj is not new, nor a fad. And, moreover, its not "just" an abstraction - you would know this if you actually read my comment. jj is built on top of git and incorporates concepts from mercurial and elsewhere. There's ample writing in the github readme, docs and lots of thoughtful essays and manuals created about jj. But becoming informed is clearly not something you have much interest in.

Most laughable is that you seemingly created this account JUST to make this comment.

Thanks for providing a perfect example of the most common detractors of jj

you do you, friend

paradox460 3 days ago | parent | prev | next [-]

Do you really need watchman for that, when each changesets has an evolog with snapshots

lowboy 3 days ago | parent | next [-]

by default, snapshots only get created on (most) `jj` invocations

the watchman integration runs snapshots on filesystem changes[0], so every time a tracked file changes on disk, a new commit is added to the evolog regardless of `jj` invocations

so say if you ran `jj status`, then changed a tracked file 3 times, and then ran `jj status` again:

without watchman you'd have 2 new evolog entries, resulting from the two `jj status` calls

with watchman you'd have 5 new evolog entries: one from `jj status`, 3 from file changes, and one from the second `jj status`

0: https://jj-vcs.github.io/jj/latest/config/#watchman

paradox460 3 days ago | parent [-]

Oh I misunderstood and thought watchman was something like fswatch or entry. Neat!

nchmy 3 days ago | parent [-]

It is. Jj uses it to watch your repos for changes and trigger commits.

paradox460 2 days ago | parent [-]

Yeah I see that its a built-in feature now. Useful

nchmy 3 days ago | parent | prev [-]

What if you do a bunch of work and dont touch jj? None of that work shows up in the evolog. using watchman also apparently makes jj more performant on large repos.

But, again, watchman was explicitly said to be out of scope for this discussion... Do you have any thoughts about jj, or what I said about it?

rifofbfpfjfb 3 days ago | parent | prev | next [-]

what's your "config" to snapshot on every file change?

nchmy 3 days ago | parent [-]

https://jj-vcs.github.io/jj/latest/config/#watchman

rifofbfpfjfb 3 days ago | parent [-]

thx!

terminalbraid 3 days ago | parent | prev [-]

What does Luddite mean to you?

nchmy 3 days ago | parent [-]

It's peculiar that you singled-out that specific sentence from my entire comment. I sense a trap being laid, but I'll bite anyway.

Luddites were skilled craftspeople who are afraid of technology/progress supplanting them, which is largely what is happening when people reject jj in favour of arcane git incantations as part of a vastly more laborious git workflow.

The biggest way in which this label does not apply here, is that luddites were also primarily fighting for their economic positions, which were of course completely threatened by new technology. Whereas jj doesn't cost anyone anything - its just a toolbox and way of operating that just makes you more efficient. My point still stands, regardless of how fitting the label was.

Luddite would more directly apply to (probably the same set of people) those who are completely against ai/llms for any degree of coding, as it clearly has the potential to eliminate jobs. Though, of course, the most experienced and skilled people are well-positioned to leverage llms as cheap junior devs who can be instructed, guided, corrected etc to produce what is needed. It new, less-skilled devs/apprentices who would be most justified in fighting against LLMs, under the Luddite banner. Though we tend to see that they embrace llms most

palata 2 days ago | parent | next [-]

> which is largely what is happening when people reject jj in favour of arcane git incantations

JJ is interesting, though I find some things worse than git (no email workflow, and nothing that does what `git add -p` does. In the article, it shows the limitation of the TUI for `jj split`).

But what I don't get is seemingly all those people who seem to believe that git is rocket science. Yes, JJ seems in a good position to become nicer than git. But git is not hard. I don't get that part at all. So many comments saying "I have been using git for 15 years, yet I don't understand what detached HEAD / stash / git add -p / some not-so-complicated concept means".

8n4vidtmkvmk 3 days ago | parent | prev [-]

The unskilled devs don't have any skills to fall back on. I don't know how they can afford to be luddites.

The skilled ones will be fired if they don't embrace it.

notnmeyer 4 days ago | parent | prev | next [-]

this looks great. excited to give it a go!

penguin_booze 4 days ago | parent | prev | next [-]

[flagged]

myst 3 days ago | parent | prev | next [-]

I was under impression, JJ is simple and is a UX step up over Git.

8n4vidtmkvmk 3 days ago | parent [-]

I found it pretty simple to get started with, but I also have a mercurial background, which I think is more closely aligned with jj.

vlovich123 4 days ago | parent | prev [-]

Shoutout to graphite.dev that gives you a stacked workflow on top of git/github - everything actually stays git, it just auto-updates the stack of branches and manages updating the PRs accordingly + will auto-merge PRs for you once they’re ready.

mpalmer 4 days ago | parent [-]

The point of jj is to improve on the git interface. It's kind of weird to imply that jj's purpose is a drawback. I don't want git to stay git. Nor do I want SAAS solving stacked PRs for me, "free option" or not.

jj is explicitly not for teams - there's no way to share jj-proprietary stuff between checkouts of a repo, because it's a frontend for interacting with a git repo.

vlovich123 3 days ago | parent | next [-]

Graphite also improves on the interface if you want that. I personally have no issues with git anymore having spent nearly two decades working with it.

nulld3v 4 days ago | parent | prev [-]

Ehhh, when I explain jj to people I like to say that it is "a tool to edit git repos", and that "the repo is still a git repo" because people have so many misconceptions of how jj works. When they see how different jj's interface is from git, they think it's a whole new VCS and are worried that it will break compatibility with other git tools.

That said, I only do this to avoid overwhelming beginners, when actually learning/using jj it is definitely more useful to think of it as a different VCS.

mpalmer 4 days ago | parent [-]

Telling people it's a tool to edit git repos makes it sound hackier than it is; not surprising they're nervous it'll break something.

Just tell them git makes mistakes easy, and jj makes them difficult.

nulld3v 4 days ago | parent [-]

I've tried that but it isn't very effective because usually if they are not a git wizard, they are already using some sort of git interface or GUI that prevents them from making catastrophic mistakes. So they don't see the appeal in relearning all their git knowledge and terminology.

Hence why I initially try to sell it as an addition to their toolbox, instead of as a replacement for their entire workflow.

nchmy 4 days ago | parent [-]

I agree with you both.

Jj makes mistakes hard, but also makes it easy to just focus on your code and easily sort out anything git-related later as an after-thought.