JJ is just so low friction. The seamless and painless shuffling of commits, working up and down the commit chain without explicit rebasing, unchanging change-ids. The min identifiers in change-ids being highlighted. Being able to make commits and worry about the message later. Snapshots of changes while you are working serving as a tool agnostic undo.
Painless and sensible cli args that are vastly more consistent than git.
Superior conflict handling.
You can “jj undo” anything and it works perfectly.
It’s fabulous.
> "@--+::"
I have no idea what that means and I’ve never had to type anything remotely like it.
The rev specification language is pretty elaborate but you don’t need to know it.
In your opinion, is jj losing steam? How do you feel about it requiring a CLA to contribute to, which would enable the developers to go closed-source and keep all the contributions of the community? Are you afraid of vendor lock-in?
IMO that's almost fundamentally impossible. The whole point is that it makes maintaining a "clean" linear commit history easier than git, so by the time you push anything the observable differences are already gone. At best you just see a repo without any WIP-style commits, which can be done using more effort and git alone.
Or if it were a family tree, "all descendants of your parents and their cousins" (ignoring that family trees are way more constrained than DAGs).
I have no idea why you would use that revset; I assumed it was just an example of revset's complexity and power.
As a pragmatic matter, unless I'm scripting with revsets, I personally stick to a few go-to aliases.
But I don't tell my team what they have to use as long as it's compatible. The last thing I want to do is try to teach them a different VCS when they've spent time learning how to get by with git.
I've told them about JJ, what I like, what pain points it solves for me. They are all adults, they can make their own decisions about it.
JJ is for me. It helps me unwind some rather elaborate problems we've had with rebasing, or dealing with conflicts from bringing some old support branch up to date, or back porting some particular thing. It helps me create pretty clean MRs, break things up into smaller reviewable chunks, etc. You can do all this with git, but it's easier in JJ. In particular if you have to do shuffling and rebasing and conflict arise. JJ lets you solve conflicts asynchronously to rebasing. Git stops you in your tracks. There's some rerere thing that is supposed to help but give me a break. Git's a train wreck of dumpster fires. But it's still been basically better than everything else since svn.
Until JJ IMHO. Valid arguments can be made for other things like maybe mercurial or even fossil. But JJ is the most pain-free vcs evolution I've seen.
Mercurial's big failing was making too many critical things optional. It's the same screw-up riscv is going through now in the chip architecture space.
- undo
- shuffling commits around with squash, split, and rebase is much better than git’s interactive rebase
- make commits without having to come up with a branch name (I might make three versions of the same change in parallel to see how they compare)
I see git is working on adding some of this under the history command. The revset language is really not esoteric: @ is head, @—- is two behind head. That’s about it.
Part of it is that the sorts of shuffling I used to avoid because they were a pain in git (so I didn’t feel I needed them improved) are so easy in jj that I do them all the time.
Can you explain this to me? I feel like Git is pretty easy there.
- select oldest commit to modify
- move the commits around with a mouse or the cursor
- close the editor to apply
Sure, the first step can go away (which is what they do with git history), but the rest seems pretty optimal to me.Alternatively I can add changes to older commits by recording them on top (--fixup) and tell git to auto apply them (--autosquash). I can also tell git to do the first thing automatically (git absorb, I believe it's inspired by jj).
But say you’re in the middle of working on something and you wish you had a commit you made last week on an experimental branch on the current branch before the last commit you made. That’s jj rebase -r oldercommit --before @-, without interrupting your work. I don’t like to think about how I’d do that with git.
I rebase a lot using Fork (git gui) and it’s really easy to drag and drop reorder commits, rename, apply as fixups or squash in. I can see how jj makes sense if you’re in the command line, though. And splitting changes from commits in git is really annoying, but it only comes up once in a blue moon for me.
With git, if you do this, everything after is left orphaned. Even if you have a GUI that does this for you, now rebase conflicts become an enormous pain.
This is the best pitch for jj I have seen yet. This should practically be the first text on their website.
git -c sequence.editor="sed -i '1ip oldercommit'" rebase -i @~
Git using standard UNIX tools for these things instead of a specialized syntax, means I can easily write more complicated automations.All of that is automatic with jj.
How many do I want?
> How do find the correct arguments
How do you find the correct arguments for jj?
> and git checkouts to do before?
Why would I do a checkout before? I don't want to modify the worktree? Maybe I in fact also want to alter the worktree, but that's unrelated.
> Are you sure you remembered to do everything?
Am I sure I have all my files ordered? No. Does it matter? Also no. Are you sure you have no bug in your commits in JJ?
If I want git to alter some set of commit chains, I can tell it to. Actually I never needed to do that, because I don't work on several thousand branches at the same time. I prefer it to not alter unrelated branches automatically, just because some earlier commit changed that is in both. Such things actually undermines the trust I have in a tool, because it does things I haven't told it to do, even if I'm aware it does these things.
> What if there are merge commits?
Then I resolve them. Merge conflicts occur, because there is some actual conflicting change and often it is also semantic. These don't go a way by changing the VCS. On a theoretic basis, these require outside information(=decisions) that is not there yet. There are more often semantic conflicts, that are not syntactic, then there are the other way around. If you are referring to doing the same merge conflicts again, I can tell Git to resolve them automatically too, but it actually occurred too often, that this is not actually what I want, so I actually dislike that feature now.
git cherry-pick + git rebase -i (to swap commit order)?
?
But if the cherry pick doesn’t cleanly apply at the current HEAD, then you have to remember to either do the git rebase -i first and pause at the appropriate place to cherry pick it if that works (I think it should? though I also recall rebase only letting you pause before a commit so you lose your commit message, but that’s probably a me problem) or else maybe detach your head and start doing surgery because otherwise you’ll be resolving conflicts in two different directions as you cherry pick and then rebase, and at that point I’m usually going back to git reflog to try to find the last point where history made sense. Or I guess you could just remember that you can introduce arbitrary existing commit refs into an interactive rebase like 1718627440 did. But I said I didn’t want to think about this anymore.
If you don't want to do that, you can tell git to do it automatically too? I actually thought I would like that, so I used it for a week. I did not like it at all.
> But if the cherry pick doesn’t cleanly apply at the current HEAD
If the diff you want to commit isn't applicable to the tree you want to apply it to, it's not going to work, no matter the VCS you use.
> then you have to remember to either do the git rebase -i first and pause at the appropriate place to cherry pick
You say that like these would be separate operations, but for me these are very much not, I guess like the fact that you need to supply both '-r oldercommit' and '--before @-' are for you. I think this is the point where I actually don't understand your view. Like, we both need to supply two parameters, we need to because this is the operation we want to do. You write two parameters free form, I write one free and select the other from a list. There is a difference, I actually think selecting from a list can be more convenient in some cases, less in others. But I don't get why you write like it would be crazy work.
> though I also recall rebase only letting you pause before a commit so you lose your commit message, but that’s probably a me problem
It does only let you handle full commits, if that's what you mean, if you want to split a commit you need to provide information how. But that it doesn't sound like you mean that.
> so you lose your commit message
You can apply commit metadata independently of the tree you want to commit, so that definitely occurs never.
> maybe detach your head
I bet you don't have issues with detaching the head in JJ, so why do you in Git, it's just a normal state.
> start doing surgery because otherwise you’ll be resolving conflicts in two different directions as you cherry pick and then rebase
Yes, but that is not because you use Git, but because you wrote the change against some other commit first, which you don't do in your JJ example. You could do that in Git as well and then you only have one set of merges to resolve.
> I’m usually going back to git reflog to try to find the last point where history made sense
Which is git rebase --abort or git reset @{1}. The latter is always the same, this is as silly as saying I can't remember whether it's jj undo or jj revert.
> But I said I didn’t want to think about this anymore.
Yet you do in JJ.
Because it left you fewer opportunities to experience the ecstasy of typing git commands into a terminal? Or because it didn't work so well?
I believe this to be the same with the index, that is praised as unnecessary and "overcomed" in JJ.
I am super curious as to what you mean specifically, but I also know that getting into details can be annoying, so if you have some specifics I'd love to hear more about them. I loved git's index so this isn't coming from a place of "the index sucks and you shouldn't care about it" it's "I think jj executes the index better than git does actually". But also if you don't, that's fine by me too, if you like a tool you should continue to use it.
(I never set autosquash either)
Similar to you, 95% of the time I do the simple use case that I've memorized. The problem is that even after a decade of using Git, when I get into those 5% where something goes wrong, I have no idea how to actually solve it. None of it makes any sense in my head, and I have to search for a bunch of magical commands to run, or nuke things to get back to a good state.
With JJ, it just makes sense. It's intuitive and clear. I have been able to recover without any help. Just the undo alone is basically a life saver. Rebasing and merge conflict resolution is so much simpler.
Yes I probably won't use it to its full potential, but still, the little I do use makes 10x more sense than Git ever did.
Git is a DAG of commits, each of which is a node in the DAG. There's also a staging area that isn't a node in the DAG, and a working copy that also isn't a node in the DAG. The head nodes of every branch in the DAG are always named in git, and every node in the DAG also has a description. Nodes are immutable, but the edges between nodes are mutable so you can edit a node by replacing it with a new node & updating the edges.
JJ is a DAG of commits. Each commit is a node. Nodes have stable identifiers, automatically generated. Nodes may have descriptions. Nodes may also have names (bookmarks) which get used for compatibility with git's branch names. Nodes are mutable, but by default nodes pushed to a remote will be immutable.
It wasn’t. I was wrong.
The staging area is just a commit like any other, only due to git’s design it has to be special-cased everywhere. In jj it’s just a commit.
I do all my work on an unnamed commit on the tip of a branch. As I complete bits, I extract out cohesive sets of changes into new commits (split) or as patches of earlier commits (squash).
If you care to learn more, there's tons of posts on hn and the broader web, so I won't bother sharing any links. I will say, though, that you should use it with jjui, which is a tremendous TUI for jj - you don't even really need to learn any of the actual jj syntax because it just handles it all for you.