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

stupid question: can someone with a deeper understand kindly explain, why are talks lurking around the corner about hopping version control systems, whats wrong with git that jj solves?


There's a variety of things that all add up to a very pleasant experience.

For starters, git has a poorly-designed UI, whereas jj borrows from Mercurial, which was widely-considered to be better designed from a DX perspective. I can usually guess what commands and flags to use on jj without reading the manual; not so for git. E.g., `jj undo` undoes anything you might have done; For anything esoteric in git, I consult ohshitgit.com because there's half a dozen different commands to know to undo everything.

Another is a simplified model whose pieces compose better with each other. There's no need for staging or stashes in jj. You essentially work in a HEAD commit all the time, and when it's ready, you commit what you want, make a new child commit and keep going. A stash is just some random commit. Anything you've learned about manipulating commits now applies to "stashes" with no extra effort.

No need for branch names. If you come from git, learning that "stashes" are just random little HEAD commits may make you groan and think you have to give each of those branches a name. jj doesn't care about that. I still give major branches a name, but since I don't have to name them in general, it's easier to create lots of little branch "experiments". Branch names were never really a substitute for commit messages, anyway.

More rebase-friendly IDs. Git penalizes the use of SHAs when rebasing, whereas in jj, change IDs are stable after rebasing, making them actually usable. This makes rebasing easier and safer.

First-class conflicts. Git forces you to stop the world and deal with conflicts immediately, or back out entirely. This isn't really required, and jj has no opinion. It marks conflicted changes, and waits for you to get around to fixing them. This is great when your boss interrupts you for something high-priority. It also automatically rebases all downstream changes after you've fixed it, which usually clears their conflicts too.

Megamerge workflow. This is possibly doable in git, but I don't know how easy the workflow is. But in jj, creating a work commit that has multiple branches as parents is trivial, as is pushing work down into individual parent branches. This makes integration easier, as you can easily see how your upcoming changes interact with your colleagues' upcoming changes.

Jujutsu's not a revolutionary VCS change (like Pijul's theories of commutative patches), but it's generally way more pleasant to use, with better DX, than git. I gave up git forever after two weeks of jj.


> There's no need for staging or stashes in jj.

What the JJ developer seam to misunderstand is, that the index and stashes are a feature to improve the users workflow. You can bypass the index with commit -a, giving you JJ behaviour and you can commit just fine instead of using the stash. The stash is like some backlog of temporary commits and also supports saving the index separately. When you don't care about this, you don't need to use it, these features are purely additive.


So this is as an opinion I had when I heard about jj too. But the thing is, jj supports this workflow very well, and in fact better than git, because the stash/index is just a normal commit, not a separate feature.

In fact, the most popular jj workflow is closer to the git add -p workflow than it is git commit -a. I’d argue more jj developers work this way than git developers do in git, even.


JJ has a backlog of temporary commits? That's a stash under another name.


Not exactly, but sorta. You don't need to write a description for a commit, and it doesn't need to be on a branch, and things are auto-committed. So if I'm working on something, and then I "jj new" to start something else somewhere else, I can trivially come back to what I was doing. You're right that's a stash under another name: a commit. But the key is, because it's not a special separate thing from a commit, I can use any command that works on commits to work on my stash. We've unified two things into one thing. And that's useful.


I was talking about:

> backlog of temporary commits

So a list somewhere else, which commits are considered temporary. It's a todo list for commits. Does JJ have that?

> We've unified two things into one thing.

A stash in Git is also just two commits. So no you just removed a feature on top of that. That feature might not be to your taste, or even outright confusing, but you did remove it.


I am saying a feature was removed, yes. But what I'm saying is, the things you use that feature for can still be accomplished. It's just not accomplished via a dedicated feature, but as an effect of how other features work.


Yeah and I am saying that you could do that as well with Git. Git just also does have that feature and a lot of users like to use that, but you don't need to.


git and jj are functionally equivalent in this respect, yes, but it's all easier in jj. It all becomes one concept and one command set, instead of three different ones.

The git features of stashing/staging are removed, but they're superfluous given how jj works. You don't need or want them, and keeping them around would only give people two ways to do the same things.


> You don't need or want them,

> give people two ways to do the same

This is where I am disagreeing. I do want them.

for, while and goto achieve the same and are the same thing under the hood, yet they convey different semantic and you choose between them for different tasks, because they are more suited for different things.

stashes and commits achieve the same and are the same thing under the hood (both are commits), yet they convey different semantic and I choose between them for different tasks, because they are more suited for different things.

All in software is about different abstractions, that all fundamentally do the same thing.

> but it's all easier in jj

You can literally use commits instead of stashes in Git and guess what: it's the same as treating any other commit. Here JJ and Git are the same. You can also do stashes in Git, when you want temporary commits that are put into a "todo" list.


These are all good points, and if it fits how you think about the problem, git is clearly right for you.

I think for me, the unification of concepts, and simplification of DX, wins out. I like being able to use the same set of commands for all three. I didn't miss staging or stashes at all when I switched.


With the way jj works, all the functions of indexes and stashes are handled with commits.

There's no loss of speed, functionality or power, and as a bonus, instead of maintaining 3 separate concepts and commands, they're now handled by 1.

It's honestly much simpler. A git stash is effectively a commit already, but stored weirdly, doesn't show up in the logs by default, and with its own ad hoc commands.


I would love the git index to not be a thing you had to care about unless you deliberately wanted to use that feature, but in my experience this is not the case...it feels to me like an implementation detail that surfaces into the UI more than it ought to.


But that is the thing, it is not exposed as an implementation detail, it is a skipable part of the user model.


It’s not really skippable because you have to know about it in order to skip it. And a lot of commands interact with it, and so it adds complexity to those commands, git reset being the most obvious case. But even stuff like rebase. Lots of commands that need to abort on a dirty working tree just simply work in jj.


I find myself from time to time with stuff in the index (merge conflicts in particular I think can result in this). I never put anything there explicitly. I would love to be able to configure git such that the index didn't exist, but I can't.


The transparent thing would be to put everything into the index, and configure all the commands to autoupdate the index. This is what happens with the merge conflict, because there is no benefit having resolved merge conflicts not already in the index. So the merge conflicts already do what you want, the other commands need to be modified.


That isn't what I want, though. I don't see any point in the index as a construct -- I am happy with changes being either (a) not in any commit, just in the working directory or (b) in the most recent commit. Somewhere lurking in the middle is just confusing.


I constantly have something different in the index, that is neither in the commit nor in the workspace, and I find that useful.


thank you for the detailed explanation. let us say you want to edit the contents of a commit on git 10 commits ago and this was not pushed to remote. you would do a rebase which basically discards and underneath basically creates a new track to follow. How does jj rebase work? I heard you can actually edit something 10 commits ago in jj and it treats it natively as an edit


I'm not sure what you mean by "natively as an edit", but jj really doesn't care if you alter some commit 10 back from the branch tip. Everything is automatically rebased as needed, and unless you introduce conflicts, it's seamless.

The typical workflow would be:

1. `jj log ...`

Locate the change ID of whatever change needs altering.

2. `jj new $SOME_CHANGE_ID`

This creates a new, empty change whose parent is the change you want to edit

3. Make your changes, pass the tests, etc.

4. `jj squash`

All your changes get squashed into $SOME_CHANGE_ID

5. jj then automatically rebases everything downstream of $SOME_CHANGE_ID for you.

All jj change IDs remain the same (though the underlying git commit SHAs are now different.)

If there are any new conflicts introduced in the downstream changes, they'll get flagged. You'd then typically run `jj new $FIRST_CONFLICT_ID`, and repeat the whole process.

---

It's also possible to run `jj edit $SOME_CHANGE_ID`, to work directly on that change, but because of the automatic rebasing, it's preferable to work on a child commit and only squash when ready. (In case you need to do other things with downstream changes before you've finished making your updates.)

---

Does that answer your question?


How does JJ ensure that there are now change ID conflicts on push, i.e. so that no two repos generate the same change-id?


Until recently, change ids were entirely local (in the git backend). So conflicts just can’t happen.

Recently jj started including the change id in a header in the git object, so the receiving side can choose to respect/expose them as appropriate. An issue with this is that some git commands don’t guarantee the preservation of headers, so it’s possible to accidentally lose them; git rebase being a prime example.


The same way Git ensures that no two repos generate the same commit ID: by using a cryptographic hash function.


What does JJ hash here, when I can modify the whole commit, but the change id stays constant?


The parent comment is wrong.

Generally, when a new change ID is needed, jj generates a random number. The individual commits under each change still use hashed SHAs.

> when I can modify the whole commit

You can't actually modify any commits. A mutable change can be thought of as a subset of immutable git commits with a pointer to the most recent commit.

When you alter a file and jj snapshots, it adds immutable commits to the DAG end, and updates the pointer.

---

There's some exceptions, like when importing from git, it will generate new change IDs based on a transform of the git SHAs.

---

Side note: as much as I like jj, I admit the change/commit terminology is confusing as hell.




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

Search: