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