- first, that commit that’s been merged to main is marked as immutable and, unless you add a flag to say “I know this is immutable and I want to mutate it anyway”, you can’t mutate it
- second, as part of your regular workflow, you haven’t actually checked out that historical commit. You created a new, empty commit when you “checked it out” using “jj new old_commit”
- third, you can use jj undo. Or, you can use “jj obs log” to see how a change has evolved over time (read: undo your mass find+replace by reverting to a previous state)
I'm always keen to explore new things but I don't have many complaints about git. I'm wondering what this solves that made it attractive for you.
a lot of other tools ive found were lacking for one reason or another, and mightve not been git compatible. with jj you can hop between git and jj commands as you please, essentially full compatibility with git
These are less of an issue once you've molded yourself to fit into git's strange ways, but jj feels like a much nicer tool -- especially for beginners, but feels like it frees up cognitive space even for more experienced folks. You can focus less on the tool and focus more on what you actually want to do.
[1]: I've tried using multiple working trees, but that workflow never really "stuck" with me.
jj solved the biggest problem for me, which is how much time you spend rebasing when you have 1 PR = 1 stack of commits on top of main. It's easy enough to work on multiple branches this way, but it's a lot of repeated pain when `main` diverges and your changes on top are still out for review. (I honestly just started squashing all of my commits before review, so I would only have to resolve conflicts once.) jj fixes all of this. I especially enjoy working on a 3rd pending change that refers to the previous 2 pending changes; `jj new june/feature-1 june/feature-2` and then you add feature 3 there. You can even `jj squash --into june/feature-1` if something makes more sense being in a prior commit. It's all very wonderful if you are working with other people and you can't immediately mutate `main` upon finishing some work.
I exclusively move in a jj repo with `jj new` and `jj squash` or `jj squash --to <rev>` as appropriate. I've been using it 8+ hours daily for months and have never, ever even thought of having this issue.
And now people are saying it for jj.
Endless cycle it seems, with every new tool.
Thus, if you're worried about mutating existing commits, don't use it.
What exactly is so hard to understand here? You're not making the gotcha point you seem to think you are - it's not like it's some common command that is hyper-overloaded and has to be used specially.
Just another example of the usual HN skepticism that isn't even skepticism, it's just smug ignorance. It's so exhausting. But sure, the countless people that keep claiming its the single biggest tool improvement in some time are just idiots? suckers? hype-beasts? making it up? or what?
Like, the irony of you assuming that it must be as convoluted and hard to use as git is just... awesome. I love the Git defenders that literally can't fathom that there is actually a better mental model or simpler tool, and can't even be arsed to try it and see.
For example, I've configured it for me to make any commit by anyone else mutable regardless of branch:
[revset-aliases]
# To always consider changes by others immutable:
"immutable_heads()" = "builtin_immutable_heads() | (trunk().. & ~mine())"I must be a wizard because I’ve lost count of the number of times I’ve messed up my repo’s state.
I jest. Kinda. I know that git’s state might technically not be messed up and someone skilled in the ways of git could get me out of my predicament. But the fact that I’m able to easily dig myself into a hole that I don’t know how to get out of is one of git’s biggest issues, in my opinion.
That hole is very easily dug with git:
use ctrl x ctrl v to move files around commit boom, you've lost history for those files (file tracking only works in theory, not in real life), let's say you don't notice (very easy to not notice)
commit some more, merge
discover your mistake tons of commits back
good luck fixing that, without digging a bigger hole.
And that's one of 100's of examples in which git just is really really not fun or user friendly.
This is one of the things git excels at: You didn't lose your history because that's not how git handles it. Git might be the only version control that can actually handle your case (renaming files without using a special command) - it looks for file similarity between a deleted file and an added file in the same commit, with various flags to make this look in broader places.
"git mv file1 file2" is almost identical to "mv file1 file2" + "git add file1 file2" (it also handles unstaged changes instead staging them)
I'm probably doing something dumb/wrong, some setting somewhere, wrong OS, whatever it is: it proves my point that git is harder than it should be.
Internally, JJ is still backed by an append-only tree of commits. You don't normally get to see these commits directly, but they're there. A change (i.e. the thing you see in `jj log`) is always backed by one or more commits. You can see the latest commit in the log directly (the ID on the left is the change ID, the ID on the right is the commit ID), but you can also look back in history for a single change using `jj evolog`, and you can see all commits using `jj op log`.
This ensures that even if you were to exclusively use `jj edit`, never made a new commit, and kept all your work and history in a single change, you could still track the history of your project (or the "evolution", hence the name evolog). It would be kind of impractical, but it would work.
The only caveat here is that, by default, JJ only creates new snapshots/commits from the working directory whenever you run the CLI. So if you made a large change, didn't run any JJ command at all, then made a second large change, JJ would by default see that as a single change to the working directory. To catch these issues, you can use a file watcher to automatically run JJ whenever any file changes, which typically means that JJ makes much more frequent snapshots and therefore you're less likely to lose work (at the cost of having a file watcher running, and also potentially bloating your repository with lots of tiny snapshots).
Note also that the above is all local. When using the git backend, Jujutsu will only sync one commit for each change when pushing to a remote repository, so the people you're collaborating with will not see all these minor edits and changes, they'll only see the finished, edited history that you've built. But locally, all of those intermediate snapshots exist, which is why Jujutsu should never lose your data.
For example? Or should I create my own in C using inotify/kqueue? Is there a library for jj?
The default behaviour (i.e. `core.fsmonitor = "watchman"`) is to only use the file watcher as an optimisation — rather than scanning the entire folder every time JJ wants to make a snapshot, the watch keeps a list of which files have changed, and then when creating a snapshot, JJ only needs to check those files.
However, you can also add `core.watchman.register_snapshot_trigger = true` to the configuration, and this will make it so that every time the watcher sees that a file has changed, it automatically makes the new snapshot.
That said, neither of these are active by default, and neither are necessary by default. But if you're the sort of person who uses VS Code's "Timeline" view to see exactly how each file you've worked with has changed over time, then you might also appreciate the automatic snapshotting feature.
Off of the top of my head, `jj new` and occasionally `jj rebase` can create merge commits; I don't recall any others.
You really can undo every action in Jujutsu (and if you can't, that's a bug), but the `undo` mechanism can be a bit surprising - it doesn't behave like a stack, where undoing multiple times will take you further back in history. Instead, undo always undoes the most recent action, and if the most recent action is an undo, then it undoes the undo. This often catches people off-guard - in future versions, JJ will show a warning when this happens, and in even further future versions, there's a plan to make undo behave more like expected.
But if you use `jj op log` and `jj op restore`, you can always get back to any previous state, including undoing merges and other complicated changes.
For the record I’ve seen ‘cannot undo a merge’ after jj undo 3-4 times, can’t remember now. I was trying to squash a change into a single commit for a GitHub pr for the first time and couldn’t figure out how to map jj commits into something acceptable, then decided to undo the whole thing and actually managed to overwrite some of my changes in a way I couldn’t find them, fortunately only a few lines of boilerplate.
There's several things Git can't undo, such as if you delete a ref (in particular, for commits not observed by `HEAD`), or if you want to determine a global ordering between events from different reflogs: https://github.com/arxanas/git-branchless/wiki/Architecture#...
In contrast, jj snapshots the entire repo after each operation (including the assignment of refs to commits), so the above issues are naturally handled as part of the design. You can check the historical repo states with the operation log: https://jj-vcs.github.io/jj/latest/operation-log/ (That being said, there may be bugs in jj itself.)
I'd like to introduce you to a couple of my former colleagues...
Say I'm messing around with the commit that introduced a bug, somewhere deep in the history. With git, it's basically impossible to mess up the repo state. Even if I commit, or commit --amend, my downstream refs still point to the old history. This kind of sucks for making stacked PRs (hello git rebase -i --autosquash --update-refs) but gives me a lot of confidence to mess around in a repo.
With jj, it seems like all I would have to do is forget to "jj new" before some mass find+replace, and now my repo is unfixable. How does jj deal with this scenario?