A good way to think of it is that jj new is an empty git staging area. There's still a `jj commit` command that allows you to desc then jj new.
> I also often end up with in a dirty repo state with multiple changes belonging to separate features or abstractions. I usually just pick the changes I want to group into a commit and clean up the state.
jj split allows you do to this pretty well.
> Since it's git compatible, it feels like it must work to add files and keep files uncommitted, but just by reading this tutorial I'm unsure.
In jj you always have a commit - it's just sometimes empty, sometimes full, has a stable changeid regardless. jj treats the commit as a calculated value based on the contents of your folder etc, rather than the unit of change.
I guess the idea of jj's authors is that jj's commits are far more squishy and can always be changed, so a fixed finished timestamp makes less sense. I still prefer git's behaviour, marking work as finished and then keep the author (but not commit) timestamps on amends.
I use this jj alias to get git's timestamp behaviour:
[aliases]
c = ["util", "exec", "--", "bash", "-c", """
set -euo pipefail
change_id=$(jj log -r @ --no-graph -T 'change_id')
desc=$(jj log -r $change_id --no-graph -T 'description')
commit_author=$(jj log -r $change_id --no-graph -T 'author.email()')
configured_author=$(jj config get user.email)
jj commit -i "$@"
if [ -z "$desc" ] && [ z"$commit_author" = z"$configured_author" ]; then
echo "Adjusting author date"
jj metaedit --update-author-timestamp --quiet $change_id
fi
"""]
[templates]
# display author timestamp instead of commit timestamp in log
'commit_timestamp(commit)' = 'commit.author().timestamp()'This always made me feel uncomfy using `jj`. Something that I didn't realise for a while is that `jj` automatically cleans up/garbage collects empty commits. I don't write as much code as I used to, but I still have to interact with, debug and test our product a _lot_ in order to support other engineers, so my workflow was effectively:
git checkout master
git fetch
git rebase # can be just git pull but I've always preferred doing this independently
_work_/investigate
git checkout HEAD ./the-project # cleanup the things I changed while investigating
```Running `jj new master@origin` felt odd because I was creating a commit, but... when I realised that those commits don't last, things felt better. When I then realised that if I made a change or two while investigating, that these were basically stashed for free, it actually improved my workflow. I don't often have to go back to them, but knowing that they're there has been nice!
It's a mouthful, but it's more accurate and may be less confusing.
`jj new` simply means "create a new commit [ontop of <location>]" - you don't have to describe it immediately. I never do.
I know that the intention was to do that, and I tried forcing the habit, but I too found it counter-productive to invariably end up re-writing the description.
https://arialdomartini.github.io/pre-emptive-commit-comments
If you want to have a workflow similar to Git with index, check out the Squash Workflow: basically, you would edit your files in a disposable commit having the same purpose of Git’s index.
Hence I have multiple workspaces, and I shelve changes a lot (IntelliJ. I end up with dirty repos too and that can be painful to cherry-pick from. Sometimes I just create a git patch so I can squirrel the diffs into a tmp file while I cleanup the commit candidate. I often let changes sit for several days while I work on something else so that I can come back and decide if it’s actually right.
It’s chaotic and I hide all this from coworkers in a bid to seem just a bit more professional.
I admire people who are very deliberate and plan ahead. But I need to get the code under my fingers before I have conviction about it.
# I've finished something significant! Carve it out from the working "change" as its own commit.
`jj commit --interactive` # aka `jj commit -i` or `jj split`, depending on how you prefer to think of it: making a commit for some work, or splitting a separate commit out of the working change.
# Oops, missed a piece. `jj squash --interactive` # aka `jj squash -i`
# Let me look at what's left. `jj diff`
# Oh right, I had started working on something else. I could just leave it in the working change, but let me separate it out into its own commit even though it's unfinished, since I can always add pieces to it later. `jj commit -i`
# Wait, no, I kind of want it to come before that thing I finished up. Shoot, I messed up. `jj undo`
# Let me try that again, this time putting it underneath. `jj split -B @-` # aka `jj split --insert-before @-`. @ is the working change, @- is its immediate parent(s), @-- is all grandparents, etc.
# Note that instead of undoing and re-selecting the parts, you could also `jj rebase -r @- -B @--` to reorder. And in practice, you'll often be doing `jj log` to see what things are and using their change ids instead of things like `@--`.# I also have some logging code I don't need anymore. Let me discard it.
`jj diffedit`
# Do some more work. I have some additions to that part I thought was done. `jj squash -i`
# And some additions to that other part. `jj squash -i --into @--`
# etc.There's a lot more that you could do, but once you internalize the ideas that (1) everything is a commit, and (2) commits (and changes) can have multiple parents thus form a DAG, then almost everything else you want to do becomes an obvious application of a small handful of core commands.
Note: to figure out how to use the built-in diff viewer, you'll need to hover over the menu with the mouse, but you really just need f for fold/unfold and j/k for movement, then space for toggle.
I do that all the time. With git, everything starts "unstaged", so I'd use magit to selectively stage some parts and turn those into a sequence of commits, one on top of another.
With jj I'd do it "backwards": everything starts off committed (with no commit message), so I'd open the diff (`D` in majutsu), selecting some parts and "split" (`S` in majutsu) to put those into a new commit underneath the remaining changes. Once the different changes are split into separate commits, I'd give each a relevant commit message.
If I end up with multiple features or abstractions in one change (equivalent to the “dirty repo”), jj split works very well as an alternative to the git add/git commit/repeat workflow tidying up one’s working copy.
Like `jj commit -m 'Feature A' file1 file2` then `jj commit -m 'Feature B' file3 file 4`
jj doesn't "want" anything.
I always end a piece of work with `new`: it puts an empty, description-less commit as the checked-out HEAD, and is my way of saying "I'm finished with those changes (for now); any subsequent changes to this directory should go in this (currently empty) commit"
The last thing I do to a commit, once all of its contents have settled into something reasonable, is describe it.
In fact, I mostly use `commit` (pressing `C` in majutsu), which combines those two things: it gives the current commit a description, and creates a new empty commit on top.
No, you run `jj new` when you’re done with your work just like you’d run `git commit`. You can even just run `jj commit` which is a shorthand for `jj describe && jj new`.
The autosync feature is really nice too, and you can store backup repos in cloud storage folders and auto sync to those as well.
I also find it interesting that so many people want to switch to something that's not `git` but are simultaneously somehow super invested in it being basically just `git`.
Most teams could switch to Fossil and just have a better time overall. It's made for smaller, high-trust teams, `git` is not. Fossil also manages to actually support external contributions just fine; it's just that it's not the default.
The workflows are conceptually identical.
the working tree being a commit has wide ranging implications as all the commands that work with commits start working with the working tree by default.
Yes, but this is not backwards, the way you do it in git is backwards. =)
The point is that there actually isn't a correct order to do these operations, just one that you're familiar with. Other orders of operations are valid, and may be superior for your or your team's workflow.
I want to build xyz,
```
jj desc -m "feat: x y & z"
```
do the work.
```
jj split
```
Split up the parts and files that you want to be separate and name them.
This will also allow you to rename stuff.
```
jj bookmark create worklabel-1 -r rev1
jj bookmark create worklabel-2 -r rev2
# Push both commits
# since we just split them they are likely not inter-dependent
# so you can rebase them both to base
# assuming rev1 is already on top of base
jj rebase -s rev2 -d base
jj git push
```
That is it.
(Your question is not dumb, BTW. The pithy answer is: UX matters, but it does so in ways that can be hard to convey since it's about the amount of cognition you need to put in a given thing to get a desired outcome, and that's tricky to express in text. Also there will always be some people for whom a new mental model just doesn't work. That doesn't make them dumb either, at least provided they have the wisdom not to petulantly piss in the cornflakes of those who get a kick out of how much better the new thing works for them.)
You also don't have to follow what the GP said. I never say `jj describe` before writing code. I write the code then just say `jj commit -m "Foo stuff"`, just like I would in git.
The bigger difference I've noticed is:
1. Switching between changesets just feels more natural than git ever did. If I just run `jj` it shows me my tree of commits (think of it like showing you git's branches + their commits), and if I want to edit the code in one of them I just say `jj edit xyz`, or if I want to create a new commit on top of another one and branch it off in a new direction, I just say `jj new xyz`. It took a little bit for my brain to "get" jj and how it works because I was so used to git's branches, but I'm really enjoying the mental model.
2. `jj undo`. This alone is enough to convert me. I screwed something up when trying to sync something and had a bunch of conflicts I really didn't want to resolve and I knew could have been avoided if I did things differently, but my screwup was several operations ago! So I ran `jj undo`. And ran it again. And again. And again. And then I was back to my clean state several stages ago before I screwed up, despite having made several changes and operations since then. With git? Yeah I could have gotten it fixed and gone back. But every time I've had to do something like that in git, I'm only 25% confident I'm doing it right and I'm not screwing things up further.
3. Rebasing. When I would try to sync git to an upstream GitHub repo that used rebasing for PRs, I would always get merge conflicts. This was because I stack my changes on top of each other, but only merge in one at a time. Resyncing means my PR got a new commit hash, even though none of the code changed, and now git couldn't figure out how to merge this new unknown commit with my tree, even though it was the same commit I had locally, just a different hash. With jj? I never get merge conflicts anymore from that.
Overall the developer experience is just more enjoyable for me. I can't say jj's flow is fundamentally and objectively better than git's flow with branches, but personally and subjectively, I like it better.
This tool was described perfectly the other day. JJ is the Dvorak of Git. Most people could careless about Dvorak layout, 99.8% of people use qwerty just fine. Those 0.02% though, they're loud, and they want everyone to know how great the reinvention of bread is.